diff --git a/sites/mainweb/app/(portal)/providers.tsx b/sites/mainweb/app/(portal)/providers.tsx index b3557549..89a60197 100644 --- a/sites/mainweb/app/(portal)/providers.tsx +++ b/sites/mainweb/app/(portal)/providers.tsx @@ -4,17 +4,39 @@ import type { ReactNode } from "react"; import { SessionProvider } from "next-auth/react"; import { QueryClientProvider } from "@tanstack/react-query"; import { httpBatchLink } from "@trpc/client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import superjson from "superjson"; import { trpc } from "@/lib/trpc"; import { createAppQueryClient } from "@/lib/query-client"; import { ThemeProvider as NextThemesProvider } from "next-themes"; +// next-themes writes the portal theme onto (a `light` or `dark` class +// and an inline color-scheme) and leaves it there when the portal unmounts. A +// client-side move back to a marketing page, such as the browser's back button, +// then kept the portal's light ramp on the dark site until a reload. Clearing +// it on unmount hands those pages back their own dark default. +function ClearThemeOnLeave() { + useEffect( + () => () => { + const root = document.documentElement; + root.classList.remove("light", "dark"); + root.style.removeProperty("color-scheme"); + }, + [], + ); + return null; +} + function AppThemeProvider({ children, ...props }: React.ComponentProps & { children: ReactNode }) { - return {children}; + return ( + + + {children} + + ); } export function Providers({ children }: { children: React.ReactNode }) { diff --git a/sites/mainweb/proxy.ts b/sites/mainweb/proxy.ts index e2a954ab..dc261890 100644 --- a/sites/mainweb/proxy.ts +++ b/sites/mainweb/proxy.ts @@ -42,11 +42,17 @@ const PRIVATE_PREFIXES = [ "/scan", ]; +/** Exact paths only: a prefix would catch a dynamic page added under one. */ +const STATIC_PAGES = new Set(["/", "/bootcamp", "/status", "/team"]); + function getCacheControl(pathname: string): string { if (PRIVATE_PREFIXES.some((prefix) => pathname.startsWith(prefix))) { return "private, no-store, must-revalidate"; } - if (pathname === "/") { + // Prerendered pages that read no cookie and revalidate every five minutes. + // `public` lets App Hosting's CDN serve them from an edge near the visitor; + // they fell through to no-store below, so every visit went to Cloud Run. + if (STATIC_PAGES.has(pathname)) { return "public, max-age=300, must-revalidate"; } // Must match next.config.mjs: this runs after it and would overwrite its day