Skip to content

Commit 833077a

Browse files
committed
fix(cloudflare): remove node-only site icon route
1 parent 4fb718f commit 833077a

File tree

13 files changed

+194
-164
lines changed

13 files changed

+194
-164
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 4.9.1 - 2026-04-12
4+
5+
- 移除仅支持 Node.js 的 `/api/site-icon` 路由,避免 `next-on-pages` 因非 Edge 路由中止构建。
6+
- 站点图标改为由服务端布局直接解析,导航栏和播放器页继续显示运行时配置的图标。
7+
- PWA manifest 回退为静态图标,确保 Cloudflare Pages、Docker 和本地构建链路一致可用。
8+
39
## 4.9.0 - 2026-04-12
410

511
- 设置页新增“版本与更新”卡片,直接显示当前版本、最近更新内容和检查结果。
@@ -11,4 +17,3 @@
1117
- 站点图标改为支持运行时配置,Docker 镜像无需重新构建即可替换。
1218
- 扩展播放器默认视口,减少桌面端播放器黑边。
1319
- 修复 Android WebView 中 Cast SDK 判定以及全屏和画中画相关兼容性问题。
14-

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ NEXT_PUBLIC_SITE_DESCRIPTION=专属视频聚合播放平台
391391
392392
## Docker 图标自定义
393393

394-
Docker 预构建镜像支持在运行时替换图标,无需重新构建镜像。该配置会同时作用于顶部 Logo、浏览器 faviconPWA 图标。
394+
Docker 预构建镜像支持在运行时替换图标,无需重新构建镜像。该配置会作用于顶部 Logo 和浏览器 favicon;如果你还要同步替换安装后的 PWA 图标,请直接覆盖仓库中的 `public/icon.png` 后重新构建镜像
395395

396396
### 可用环境变量:
397397

app-release.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,18 @@
44
"name": "KVideo",
55
"branch": "main"
66
},
7-
"currentVersion": "4.9.0",
7+
"currentVersion": "4.9.1",
88
"releases": [
9+
{
10+
"version": "4.9.1",
11+
"publishedAt": "2026-04-12",
12+
"title": "修复 Cloudflare Pages 构建失败",
13+
"notes": [
14+
"移除仅支持 Node.js 的 /api/site-icon 路由,避免 next-on-pages 因非 Edge 路由中止构建。",
15+
"站点图标改为由服务端布局直接解析,导航栏和播放器页继续显示运行时配置的图标。",
16+
"PWA manifest 回退为静态图标,确保 Cloudflare Pages、Docker 和本地构建链路一致可用。"
17+
]
18+
},
919
{
1020
"version": "4.9.0",
1121
"publishedAt": "2026-04-12",

app/api/site-icon/route.ts

Lines changed: 0 additions & 118 deletions
This file was deleted.

app/layout.tsx

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ import type { Metadata } from "next";
33
import "./globals.css";
44
import { ThemeProvider } from "@/components/ThemeProvider";
55
import { AutoSync } from '@/components/AutoSync'; // <-- 引入了自动同步组件
6+
import { SiteIconProvider } from '@/components/SiteIconProvider';
67
import { TVProvider } from "@/lib/contexts/TVContext";
78
import { TVNavigationInitializer } from "@/components/TVNavigationInitializer";
89
import { Analytics } from "@vercel/analytics/react";
910
import { ServiceWorkerRegister } from "@/components/ServiceWorkerRegister";
1011
import { PasswordGate } from "@/components/PasswordGate";
11-
import { siteConfig, SITE_ICON_PATH } from "@/lib/config/site-config";
12+
import { siteConfig } from "@/lib/config/site-config";
1213
import { AdKeywordsInjector } from "@/components/AdKeywordsInjector";
1314
import { BackToTop } from "@/components/ui/BackToTop";
1415
import { ScrollPositionManager } from "@/components/ScrollPositionManager";
1516
import { LocaleProvider } from "@/components/LocaleProvider";
1617
import { RuntimeFeaturesProvider } from "@/components/RuntimeFeaturesProvider";
1718
import { VideoTogetherController } from '@/components/VideoTogetherController';
1819
import { getRuntimeFeatures } from "@/lib/server/runtime-features";
20+
import { resolveSiteIconSrc } from '@/lib/server/site-icon';
1921
import fs from 'fs';
2022
import path from 'path';
2123

@@ -61,19 +63,24 @@ async function AdKeywordsWrapper() {
6163
return <AdKeywordsInjector keywords={keywords} />;
6264
}
6365

64-
export const metadata: Metadata = {
65-
title: siteConfig.title,
66-
description: siteConfig.description,
67-
icons: {
68-
icon: SITE_ICON_PATH,
69-
},
70-
};
66+
export async function generateMetadata(): Promise<Metadata> {
67+
const siteIconSrc = await resolveSiteIconSrc();
7168

72-
export default function RootLayout({
69+
return {
70+
title: siteConfig.title,
71+
description: siteConfig.description,
72+
icons: {
73+
icon: siteIconSrc,
74+
},
75+
};
76+
}
77+
78+
export default async function RootLayout({
7379
children,
7480
}: Readonly<{
7581
children: React.ReactNode;
7682
}>) {
83+
const siteIconSrc = await resolveSiteIconSrc();
7784
const runtimeFeatures = getRuntimeFeatures();
7885
const videoTogetherScriptUrl =
7986
process.env.VIDEOTOGETHER_SCRIPT_URL?.trim() || DEFAULT_VIDEOTOGETHER_SCRIPT_URL;
@@ -89,7 +96,7 @@ export default function RootLayout({
8996
<meta name="apple-mobile-web-app-capable" content="yes" />
9097
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
9198
<meta name="apple-mobile-web-app-title" content="KVideo" />
92-
<link rel="apple-touch-icon" href={SITE_ICON_PATH} />
99+
<link rel="apple-touch-icon" href={siteIconSrc} />
93100
{/* Theme Color (for browser address bar) */}
94101
<meta name="theme-color" content="#000000" />
95102
{/* Mobile viewport */}
@@ -99,30 +106,32 @@ export default function RootLayout({
99106
className="antialiased"
100107
suppressHydrationWarning
101108
>
102-
<ThemeProvider>
103-
<RuntimeFeaturesProvider initialFeatures={runtimeFeatures}>
104-
<VideoTogetherController
105-
envEnabled={videoTogetherEnvEnabled}
106-
scriptUrl={videoTogetherScriptUrl}
107-
settingUrl={videoTogetherSettingUrl}
108-
/>
109-
{/* 加入自动同步组件,它会在后台默默工作,我们放在 ThemeProvider 内部的最前面 */}
110-
<AutoSync />
111-
<LocaleProvider />
109+
<SiteIconProvider iconSrc={siteIconSrc}>
110+
<ThemeProvider>
111+
<RuntimeFeaturesProvider initialFeatures={runtimeFeatures}>
112+
<VideoTogetherController
113+
envEnabled={videoTogetherEnvEnabled}
114+
scriptUrl={videoTogetherScriptUrl}
115+
settingUrl={videoTogetherSettingUrl}
116+
/>
117+
{/* 加入自动同步组件,它会在后台默默工作,我们放在 ThemeProvider 内部的最前面 */}
118+
<AutoSync />
119+
<LocaleProvider />
112120

113-
<TVProvider>
114-
<TVNavigationInitializer />
115-
<PasswordGate hasAuth={!!(process.env.ADMIN_PASSWORD || process.env.ACCOUNTS || process.env.ACCESS_PASSWORD)}>
116-
<AdKeywordsWrapper />
117-
{children}
118-
<BackToTop />
119-
<ScrollPositionManager />
120-
</PasswordGate>
121-
</TVProvider>
122-
<Analytics />
123-
<ServiceWorkerRegister />
124-
</RuntimeFeaturesProvider>
125-
</ThemeProvider>
121+
<TVProvider>
122+
<TVNavigationInitializer />
123+
<PasswordGate hasAuth={!!(process.env.ADMIN_PASSWORD || process.env.ACCOUNTS || process.env.ACCESS_PASSWORD)}>
124+
<AdKeywordsWrapper />
125+
{children}
126+
<BackToTop />
127+
<ScrollPositionManager />
128+
</PasswordGate>
129+
</TVProvider>
130+
<Analytics />
131+
<ServiceWorkerRegister />
132+
</RuntimeFeaturesProvider>
133+
</ThemeProvider>
134+
</SiteIconProvider>
126135

127136
{/* ARIA Live Region for Screen Reader Announcements */}
128137
<div

components/SiteIconProvider.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use client';
2+
3+
import { createContext, useContext } from 'react';
4+
5+
const SiteIconContext = createContext('/icon.png');
6+
7+
export function SiteIconProvider({
8+
children,
9+
iconSrc,
10+
}: {
11+
children: React.ReactNode;
12+
iconSrc: string;
13+
}) {
14+
return <SiteIconContext.Provider value={iconSrc}>{children}</SiteIconContext.Provider>;
15+
}
16+
17+
export function useSiteIcon() {
18+
return useContext(SiteIconContext);
19+
}

components/layout/Navbar.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { useState } from 'react';
44
import Link from 'next/link';
55
import Image from 'next/image';
66
import { ThemeSwitcher } from '@/components/ThemeSwitcher';
7+
import { useSiteIcon } from '@/components/SiteIconProvider';
78
import { Icons } from '@/components/ui/Icon';
8-
import { siteConfig, SITE_ICON_PATH } from '@/lib/config/site-config';
9+
import { siteConfig } from '@/lib/config/site-config';
910
import { getSession, clearSession, hasPermission, type AuthSession } from '@/lib/store/auth-store';
1011
import { useRuntimeFeatures } from '@/components/RuntimeFeaturesProvider';
1112
import { LogOut } from 'lucide-react';
@@ -20,6 +21,7 @@ export function Navbar({ onReset, isPremiumMode = false }: NavbarProps) {
2021
const favoritesHref = isPremiumMode ? '/premium/favorites' : '/favorites';
2122
const [session] = useState<AuthSession | null>(() => getSession());
2223
const { iptvEnabled } = useRuntimeFeatures();
24+
const siteIconSrc = useSiteIcon();
2325

2426
const handleLogout = () => {
2527
clearSession();
@@ -45,7 +47,7 @@ export function Navbar({ onReset, isPremiumMode = false }: NavbarProps) {
4547
>
4648
<div className="w-8 h-8 sm:w-10 sm:h-10 relative flex items-center justify-center flex-shrink-0">
4749
<Image
48-
src={SITE_ICON_PATH}
50+
src={siteIconSrc}
4951
alt={siteConfig.name}
5052
width={40}
5153
height={40}

components/player/PlayerNavbar.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import Link from 'next/link';
33
import Image from 'next/image';
44
import { Button } from '@/components/ui/Button';
55
import { ThemeSwitcher } from '@/components/ThemeSwitcher';
6+
import { useSiteIcon } from '@/components/SiteIconProvider';
67
import { Icons } from '@/components/ui/Icon';
7-
import { siteConfig, SITE_ICON_PATH } from '@/lib/config/site-config';
8+
import { siteConfig } from '@/lib/config/site-config';
89

910
export function PlayerNavbar({ isPremium }: { isPremium?: boolean }) {
1011
const router = useRouter();
12+
const siteIconSrc = useSiteIcon();
1113

1214
return (
1315
<nav className="sticky top-0 z-50 pt-4 pb-2 px-4" style={{ transform: 'translateZ(0)' }}>
@@ -20,7 +22,7 @@ export function PlayerNavbar({ isPremium }: { isPremium?: boolean }) {
2022
title={isPremium ? "返回高级主页" : "返回首页"}
2123
>
2224
<Image
23-
src={SITE_ICON_PATH}
25+
src={siteIconSrc}
2426
alt={siteConfig.name}
2527
width={40}
2628
height={40}

lib/config/site-config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ export interface SiteConfig {
99
name: string;
1010
}
1111

12-
export const SITE_ICON_PATH = "/api/site-icon";
13-
1412
/**
1513
* Site configuration object
1614
* Uses environment variables with fallback to default values

0 commit comments

Comments
 (0)