Skip to main content

Authenticated Pages CSR SSR Reversal

This retrospective covers the move through 1ad0cb0, 438adc1, 4e997a9, and the reversal in aae5e48, plus the later auth hardening that kept the CSR model safe.

Symptoms

  • Authenticated list and detail pages gained more server-side complexity without producing a public SEO benefit.
  • A single login or short burst of admin navigation could scale the frontend harder than expected.
  • The app was paying server-render cost on routes whose data was already owned by client components and TanStack Query.

Diagnosis

The current app/admin/students/page.tsx is intentionally minimal again:

export default function StudentsPage() {
return (
<div>
<StudentList />
</div>
);
}

The Playwright suite in lumie-frontend/e2e/ssr/ssr-regression.spec.ts now treats that as the expected model for authenticated admin and dashboard routes:

  • AC-1 expects the primary /api/v1/... request to fire client-side.
  • AC-2 expects the page to render correctly after the client fetch settles.
  • AC-3 still guards against hydration mismatch warnings.

At the same time, lumie-frontend/src/shared/api/serverFetch.ts still exists and is still used by getServerUser() for route shells, layouts, and other server-side identity checks. The reversal was selective, not a blanket rejection of SSR.

Root Cause

The frontend first moved toward SSR in two steps:

  • 1ad0cb0 introduced getServerUser() so some auth-gated pages could read the current user on the server.
  • 4e997a9 expanded that pattern into broad SSR prefetch for authenticated list and detail routes by adding serverFetch(), resolveBaseUrl(), and HydrationBoundary-based page wrappers.

That expansion solved the wrong problem on login-gated pages. Those routes were already rendered by client features with their own loading states, and they were not public pages where crawlers or cacheable HTML mattered. The result was extra per-request server work with little user-facing gain.

438adc1 is still an important lesson from that period: once SSR was introduced, the server fetch path had to propagate X-Tenant-Slug from the JWT or it violated the frontend-to-backend tenancy contract.

Fix

aae5e48 reversed the data-loading model for authenticated admin and dashboard list/detail routes:

  • remove HydrationBoundary, dehydrate, and prefetchQuery from those pages
  • let the existing client features fetch their primary data again
  • keep SSR for surfaces that still benefit from it, such as public marketing pages, tenant landing pages, and server-side user/session shells

Later auth hardening kept that reversal safe. The current proxy.ts falls back to lumie_refresh_token when the access token expires, which gives the client-side refresh flow time to renew the session instead of bouncing the user to login on the first navigation after expiry.

Prevention And Verification

  • Default authenticated list and detail pages to CSR when the page already delegates to a client feature with TanStack Query and no public SEO requirement.
  • Keep SSR for identity, layout, and public-route cases where the server render is materially useful.
  • Verify the boundary in source:
    • lumie-frontend/app/admin/students/page.tsx is a plain wrapper around StudentList with no server-side prefetch.
    • lumie-frontend/e2e/ssr/ssr-regression.spec.ts expects client-side fetches on authenticated routes.
    • lumie-frontend/src/shared/api/serverFetch.ts still exists for the narrower server-side cases that remain.