Skip to content

perf(i18n): apply transparent rewrite, static pages, and zero-fouc theme - #279

Merged
O2sa merged 1 commit into
mainfrom
perf/i18n-static-pages
Nov 13, 2026
Merged

O2sa merged 1 commit into
mainfrom
perf/i18n-static-pages

Conversation

@O2sa

@O2sa O2sa commented Nov 13, 2026

Copy link
Copy Markdown
Owner

Summary

This PR migrates the application from dynamic server-side rendering (ƒ Dynamic) to pre-rendered static generation (● SSG) for all core pages across both English and Arabic locales. It implements transparent Next.js middleware rewrites to maintain clean, prefix-free URLs (/, /leaderboard, etc.) without redirect hops, while resolving theme flashing (FOUC) on both initial page load and language toggle.


Problem & Motivation

  1. Server Rendering Overhead:
    Pages previously relied on request-time headers() and cookies() in the root layout, forcing Next.js to dynamically render every HTML request on demand (ƒ Dynamic), increasing TTFB and server compute.
  2. URL Redirect Flashing:
    Standard Next.js internationalization typically forces route redirects (e.g. / $\to$ /en), causing address-bar flickering and extra roundtrips.
  3. Theme Flashing (FOUC):
    • On initial load: A purely client-side theme provider or delayed script caused a visible flash of unstyled light content before the dark class was applied.
    • On language toggle: Invoking router.refresh() during language selection fetched a fresh Server Component Flight payload that wiped out class="dark" from document.documentElement, causing a white theme flash.
  4. Console Noise & Hydration Issues:
    • Third-party extensions (e.g., Dark Reader) injected inline attributes into SVG icons prior to React hydration, triggering hydration mismatch warnings.
    • BrandLogo was aggressively preloaded as an LCP candidate despite being a lightweight SVG, triggering browser preload warnings.
    • React 19 dev mode emitted false-positive warnings for <script> tags inside <head>.

Key Changes & Architecture

1. Static Page Generation (● SSG)

  • Restructured pages under src/app/[locale]/ and exported generateStaticParams():
    export function generateStaticParams() {
      return supportedLocales.map((locale) => ({ locale }));
    }
  • Removed request-time headers() and cookies() from layout trees, enabling Next.js Turbopack to pre-render static HTML for /en and /ar at build time.
  • Configured dynamic on-demand ISR (revalidate = 3600, dynamicParams = true, generateStaticParams = () => []) for [country] leaderboard pages to eliminate cold-cache build timeouts.

2. Transparent Middleware Rewrites

  • Updated src/middleware.ts to detect the user's preferred language (app-locale cookie $\to$ Accept-Language header $\to$ default 'en').
  • Instead of issuing a 307/308 redirect, the middleware performs an internal rewrite:
    const url = request.nextUrl.clone();
    url.pathname = `/${locale}${pathname === "/" ? "" : pathname}`;
    return NextResponse.rewrite(url, { headers: requestHeaders });
  • Users see clean URLs (/, /leaderboard, /scoring-methodology) with zero address-bar changes or redirect delays.
  • Configured explicit matcher exemptions for static assets, images, /api/*, manifest.webmanifest, robots.txt, and sitemap.xml.

3. Zero-FOUC Theme & Flash-Free Language Switching

  • Initial Load: Inlined a blocking theme resolution script in <head> that synchronizes localStorage and prefers-color-scheme to document.documentElement.classList before browser paint (0ms FOUC).
  • Language Switch: Language switching in useI18nProvider is purely client-reactive (updates state dictionary, updates document.documentElement.lang/dir, and writes the cookie). Removed router.refresh() from LanguageSwitcher to prevent RSC DOM reconciliation from resetting class="dark".
  • Added dev-only console filter in theme-provider.tsx for React 19's script tag advisory.

4. Hydration & Performance Guardrails

  • Added "darkreader-lock": "" to metadata in src/app/[locale]/layout.tsx to stop Dark Reader from mutating SVG icon DOM nodes before hydration.
  • Changed BrandLogo priority to false by default to eliminate browser preload warnings.
  • Removed deprecated typedRoutes: true from next.config.js to ensure compatibility with transparent internal rewrites.

Review Checklist

  • All core routes pre-rendered to static HTML (● SSG)
  • Unprefixed URLs (/, /leaderboard, etc.) work seamlessly without redirects
  • Zero theme flashing on initial cold reload in dark mode
  • Zero theme flashing when switching between English and Arabic
  • No SVG hydration mismatch errors from dark mode browser extensions
  • Clean browser console without logo preload warnings or React 19 dev warnings
  • All 251 tests passing across 31 test suites

- Route directory restructured from src/app/ to src/app/[locale]/ with generateStaticParams() pre-rendering static HTML (SSG) for English and Arabic.
- Middleware transparently rewrites unprefixed URLs (/, /leaderboard, etc.) directly to localized pages with zero URL redirect flicker.
- Bypasses static assets, metadata files (manifest.webmanifest, robots, sitemap), and API routes from rewrite.
- Retains inline blocking script in head for 0ms initial theme FOUC prevention and keeps client-side language switching reactive without router.refresh() to avoid theme flashing.
- Adds darkreader-lock to suppress browser extension hydration mismatches and disables unused logo preload.
- Adds comprehensive unit test suite in src/lib/i18n/tests/middleware.test.ts.
@O2sa
O2sa merged commit dd66c95 into main Nov 13, 2026
5 of 6 checks passed
@O2sa

O2sa commented Nov 13, 2026

Copy link
Copy Markdown
Owner Author

✨ Thank you, @O2sa! Another great contribution merged! 🚀

You've been a fantastic contributor! We truly appreciate your continued support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant