修复favicon配置不生效并改为动态拉取
This commit is contained in:
parent
6cac41f1c0
commit
ec8959af19
65
app/api/favicon/route.ts
Normal file
65
app/api/favicon/route.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { NextResponse } from "next/server";
|
||||
import { getSiteSettings } from "@/lib/db/queries";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = 0;
|
||||
|
||||
const DEFAULT_CONTENT_TYPE = "image/x-icon";
|
||||
|
||||
function noStoreHeaders(contentType: string) {
|
||||
return {
|
||||
"Content-Type": contentType,
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate, proxy-revalidate",
|
||||
Pragma: "no-cache",
|
||||
Expires: "0",
|
||||
};
|
||||
}
|
||||
|
||||
function inferContentType(url: string) {
|
||||
const lower = url.toLowerCase();
|
||||
if (lower.endsWith(".png")) return "image/png";
|
||||
if (lower.endsWith(".svg")) return "image/svg+xml";
|
||||
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
||||
if (lower.endsWith(".webp")) return "image/webp";
|
||||
return DEFAULT_CONTENT_TYPE;
|
||||
}
|
||||
|
||||
async function readLocalFallbackFavicon() {
|
||||
const file = await readFile(join(process.cwd(), "app", "favicon.ico"));
|
||||
return new NextResponse(file, {
|
||||
status: 200,
|
||||
headers: noStoreHeaders(DEFAULT_CONTENT_TYPE),
|
||||
});
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const settings = await getSiteSettings();
|
||||
const faviconUrl = String(settings?.favicon || "").trim();
|
||||
|
||||
if (!faviconUrl) {
|
||||
return await readLocalFallbackFavicon();
|
||||
}
|
||||
|
||||
const targetUrl = `${faviconUrl}${faviconUrl.includes("?") ? "&" : "?"}v=${Date.now()}`;
|
||||
const upstream = await fetch(targetUrl, { cache: "no-store" });
|
||||
|
||||
if (!upstream.ok) {
|
||||
return await readLocalFallbackFavicon();
|
||||
}
|
||||
|
||||
const buffer = await upstream.arrayBuffer();
|
||||
const contentType =
|
||||
upstream.headers.get("content-type") || inferContentType(faviconUrl);
|
||||
|
||||
return new NextResponse(buffer, {
|
||||
status: 200,
|
||||
headers: noStoreHeaders(contentType),
|
||||
});
|
||||
} catch (_error) {
|
||||
return await readLocalFallbackFavicon();
|
||||
}
|
||||
}
|
||||
@ -11,11 +11,7 @@ import { getSiteSettings } from "@/lib/db/queries";
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const settings = await getSiteSettings();
|
||||
|
||||
// 处理 favicon URL,添加缓存破坏参数确保更新
|
||||
const faviconUrl = settings.favicon
|
||||
? `${settings.favicon}${settings.favicon.includes('?') ? '&' : '?'}v=${Date.now()}`
|
||||
: "/favicon.ico";
|
||||
const faviconUrl = "/api/favicon";
|
||||
|
||||
return {
|
||||
title: {
|
||||
|
||||
@ -6,47 +6,40 @@ export function FaviconUpdater() {
|
||||
useEffect(() => {
|
||||
let isDisposed = false;
|
||||
|
||||
// 从 API 获取最新的 favicon 设置
|
||||
fetch("/api/site-settings")
|
||||
.then((res) => res.json())
|
||||
.then((settings) => {
|
||||
if (!settings.favicon || isDisposed) {
|
||||
return;
|
||||
const timestamp = Date.now();
|
||||
const urlWithCache = `/api/favicon?v=${timestamp}`;
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
if (isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 仅处理本组件创建的 link,避免删除 Next/React 管理的 head 节点
|
||||
const oldLinks = document.querySelectorAll(
|
||||
'link[data-managed-favicon="true"]'
|
||||
);
|
||||
oldLinks.forEach((link) => {
|
||||
if (link.parentNode) {
|
||||
link.parentNode.removeChild(link);
|
||||
}
|
||||
|
||||
const faviconUrl = settings.favicon;
|
||||
const timestamp = Date.now();
|
||||
const urlWithCache = `${faviconUrl}${faviconUrl.includes("?") ? "&" : "?"}v=${timestamp}`;
|
||||
|
||||
// 仅处理本组件创建的 link,避免删除 Next/React 管理的 head 节点
|
||||
const oldLinks = document.querySelectorAll(
|
||||
'link[data-managed-favicon="true"]'
|
||||
);
|
||||
oldLinks.forEach((link) => {
|
||||
if (link.parentNode) {
|
||||
link.parentNode.removeChild(link);
|
||||
}
|
||||
});
|
||||
|
||||
const createManagedLink = (rel: string, href: string, type?: string) => {
|
||||
const link = document.createElement("link");
|
||||
link.rel = rel;
|
||||
link.href = href;
|
||||
link.setAttribute("data-managed-favicon", "true");
|
||||
if (type) {
|
||||
link.type = type;
|
||||
}
|
||||
document.head.appendChild(link);
|
||||
};
|
||||
|
||||
createManagedLink("icon", urlWithCache, "image/x-icon");
|
||||
createManagedLink("apple-touch-icon", urlWithCache);
|
||||
createManagedLink("shortcut icon", urlWithCache);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("[Favicon] Error updating favicon:", error);
|
||||
});
|
||||
|
||||
const createManagedLink = (rel: string, href: string, type?: string) => {
|
||||
const link = document.createElement("link");
|
||||
link.rel = rel;
|
||||
link.href = href;
|
||||
link.setAttribute("data-managed-favicon", "true");
|
||||
if (type) {
|
||||
link.type = type;
|
||||
}
|
||||
document.head.appendChild(link);
|
||||
};
|
||||
|
||||
createManagedLink("icon", urlWithCache, "image/x-icon");
|
||||
createManagedLink("apple-touch-icon", urlWithCache);
|
||||
createManagedLink("shortcut icon", urlWithCache);
|
||||
});
|
||||
|
||||
return () => {
|
||||
isDisposed = true;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user