This document explains what changed, why it changed, and how it affected speed, rendering mode, and perceived bundle/runtime cost.
- Make first paint faster.
- Avoid root-level blocking work.
- Reduce hydration flicker.
- Keep cache behavior simple and predictable.
- Keep only required
Suspenseboundaries.
- Removed the dedicated
SessionProviderwrapper file. - Root layout now uses
Providersdirectly. - Session is resolved client-side in
ContextProviderwithauthClient.getSession()after hydration.
Why:
- Server-side session calls at root can force request-time rendering and delay initial HTML.
- Client-side session resolution preserves a fast static shell.
- Added
isUserHydratedstate inContextProvider. - Navbar hides Sign In/Sign Out until hydration completes.
- Shows a pulsing placeholder instead of flashing between auth states.
Why:
- Prevents hydration flicker and auth-button state jumps.
Kept:
SuspensearoundPostHogPageViewin layout.SuspensearoundItemDisplayListon home.Suspensearound async settings content in/settings.
Removed:
- Extra page-level
SuspensearoundLazyMapsection (not needed,dynamic(..., { ssr: false, loading })already handles client loading).
Why:
- With
cacheComponents, Next.js requires some dynamic reads to be underSuspense. - Removing all Suspense around
ItemDisplayListcaused a build error (blocking-route) because it uses navigation/search param APIs.
getAllItems()keeps"use cache"only.- Removed explicit
cacheLife(...). - Removed
cacheTag(...). - Item mutations use
revalidatePath("/")(create/edit/delete/update).
Why:
- This is the smallest setup that still keeps homepage fresh after writes.
- Matches the request to avoid extra cache complexity.
- Added animation stabilization hints in global CSS:
animation-fill-mode: bothbackface-visibility: hidden- GPU/compositor hints for animated shell elements
- Reduced over-broad transition usage on homepage wrappers.
Why:
- Lowers repaint artifacts and dithering during hydration.
Homepage (/) is static because:
- It is cacheable (
"use cache"). - It does not perform request-time server APIs like
headers()at root render. - Dynamic client concerns are isolated under required
Suspense.
Build output reflects this as:
/=>○ Static
/settings is partial prerender because:
- It loads user-specific async data (
findPhoneNumber). - That async section is wrapped in
Suspense, so static shell can stream dynamic content.
Build output reflects this as:
/settings=>◐ Partial Prerender
Even without a formal bundle analyzer comparison, runtime cost dropped because:
- Root server session blocking was removed.
- Redundant provider layer was removed.
- Auth-guess/cookie machinery was removed.
- Unnecessary Suspense wrappers were removed.
- Hydration state transitions were simplified.
This reduces:
- work before first meaningful HTML,
- hydration churn,
- unnecessary UI state corrections.
- With
cacheComponents, dynamic reads can requireSuspenseto avoid build-time blocking-route errors. revalidateroute segment config conflicts withcacheComponents.- If using
revalidateTagin this Next version, it requires a second argument/profile. - Simpler invalidation (
revalidatePath("/")) is often enough if your freshness scope is homepage-only.
- Build passes.
/is static and faster to first paint./settingsremains PPR for personalized data.- Auth button flicker is controlled by hydration gating.
- Cache behavior is simple and minimal.