From 74d8441e5293a2c66388fa99a4c77cc8f15ccfad Mon Sep 17 00:00:00 2001 From: aamoghS Date: Thu, 5 Nov 2026 23:55:48 -0500 Subject: [PATCH 1/2] fix(mainweb): drop the portal theme from when leaving the portal next-themes writes the portal theme onto as a `light` or `dark` class and an inline color-scheme, and leaves both there when the portal's providers unmount. Going from the portal back to a marketing page without a reload (the browser's back button, or a header link) kept the portal's light gray ramp on the dark site: body text went from #9ca3af to #4c5553 on near-black until the page was refreshed. The portal providers now clear both on unmount. Re-entering the portal mounts next-themes again, which applies the theme as before. --- sites/mainweb/app/(portal)/providers.tsx | 26 ++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) 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 }) { From d92f7aff5b927bcc84e7ea3933383cf304cd8931 Mon Sep 17 00:00:00 2001 From: aamoghS Date: Fri, 6 Nov 2026 00:07:12 -0500 Subject: [PATCH 2/2] perf(mainweb): let the CDN cache /team, /bootcamp and /status App Hosting puts Cloud CDN in front of Cloud Run, and it already serves the homepage from cache (Cdn-Cache-Status: hit) because the proxy marks it public. /team, /bootcamp and /status are prerendered too and revalidate every five minutes, but they fell through to the no-store default, so every visit went to the single Cloud Run region instead of an edge near the visitor. They now get the homepage's header. Exact paths only, so a dynamic page added under one of them later does not inherit public caching. --- sites/mainweb/proxy.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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