fix(production) : prevent hydration and service failures

This commit is contained in:
2026-09-29 16:28:15 +07:00 Unverified
parent c6f0428aa7
commit 76478fe56d
6 changed files with 37 additions and 12 deletions
+1
View File
@@ -36,6 +36,7 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
<html
lang="th"
className={`dark ${anuphan.variable} ${geistMono.variable} h-full antialiased`}
suppressHydrationWarning
>
<body className="flex min-h-full flex-col">
<PageProgress />
+2 -5
View File
@@ -1,5 +1,3 @@
import Script from "next/script";
const clientId = process.env.NEXT_PUBLIC_GOOGLE_ADSENSE_CLIENT_ID?.trim();
export function GoogleAdsense() {
@@ -8,10 +6,9 @@ export function GoogleAdsense() {
}
return (
<Script
id="google-adsense"
<script
async
src={`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${encodeURIComponent(clientId)}`}
strategy="lazyOnload"
crossOrigin="anonymous"
/>
);
+6 -1
View File
@@ -770,7 +770,12 @@ async function executeCatalogSyncJob(initial: CatalogSyncJob, actor: AuditActor)
}
export async function getCurrentCatalogVersion(): Promise<string | null> {
return (await getCurrentCatalogState())?.version ?? null;
try {
return (await getCurrentCatalogState())?.version ?? null;
} catch (error) {
console.error("Unable to read the current catalog version", error);
return null;
}
}
async function getCurrentCatalogState() {
+9 -4
View File
@@ -121,10 +121,15 @@ export async function getActiveCatalog() {
if (!characters.length) return null;
const groupedAliases = aliasesByItem(aliases);
const materialByKey = new Map(materials.map((item) => [item.key, item]));
const storage = await getMediaStorage();
const { version } = catalogVersionSchema.parse(
await storage.file("catalog/current.json").json(),
);
let version = "unknown";
try {
const storage = await getMediaStorage();
version = catalogVersionSchema.parse(
await storage.file("catalog/current.json").json(),
).version;
} catch (error) {
console.error("Unable to read the active catalog version", error);
}
return {
version,
characters: characters.map((item) => ({ ...catalogCharacterWithUrls(item, materialByKey), aliases: groupedAliases.character.get(item.key) ?? [] })),
+4 -2
View File
@@ -1,12 +1,14 @@
export function securityHeaders(production: boolean) {
const ads = "https://*.googlesyndication.com https://*.doubleclick.net https://*.googleadservices.com https://*.googletagservices.com https://www.google.com";
const cloudflareAnalytics = "https://static.cloudflareinsights.com";
const adConnections = "https://*.adtrafficquality.google";
const policy = [
"default-src 'self'",
`script-src 'self' 'unsafe-inline' ${production ? "" : "'unsafe-eval'"} https://challenges.cloudflare.com ${ads}`,
`script-src 'self' 'unsafe-inline' ${production ? "" : "'unsafe-eval'"} https://challenges.cloudflare.com ${cloudflareAnalytics} ${ads}`,
"style-src 'self' 'unsafe-inline'",
`img-src 'self' data: blob: https://buzz-cdn.astrxl.dev ${ads}`,
"font-src 'self' data:",
`connect-src 'self' https://buzz-cdn.astrxl.dev https://challenges.cloudflare.com ${ads}${production ? "" : " ws: wss:"}`,
`connect-src 'self' https://buzz-cdn.astrxl.dev https://challenges.cloudflare.com ${adConnections} ${ads}${production ? "" : " ws: wss:"}`,
`frame-src https://challenges.cloudflare.com https://www.youtube-nocookie.com ${ads}`,
"media-src 'self' blob:",
"worker-src 'self' blob:",
+15
View File
@@ -72,6 +72,8 @@ describe("public rendering performance", () => {
expect(adsense).not.toContain('from "@/lib/auth/client"');
expect(adsense).toContain('process.env.NODE_ENV !== "production"');
expect(adsense).toContain("pagead2.googlesyndication.com");
expect(adsense).toContain("<script");
expect(adsense).not.toContain('from "next/script"');
expect(home).toContain("<GoogleAdsense />");
expect(guide).toContain("<GoogleAdsense />");
expect(guideSection).toContain("<GoogleAdsense />");
@@ -79,4 +81,17 @@ describe("public rendering performance", () => {
"google.com, pub-9687404323559597, DIRECT, f08c47fec0942fa0",
);
});
it("allows the production analytics and advertising endpoints", async () => {
const headers = await repositoryFile("lib/security/headers.ts");
expect(headers).toContain("https://static.cloudflareinsights.com");
expect(headers).toContain("https://*.adtrafficquality.google");
});
it("accepts Next.js deployment metadata on the document root", async () => {
const layout = await repositoryFile("app/layout.tsx");
expect(layout).toContain("suppressHydrationWarning");
});
});