patterntypescriptnextjsModerate
Next.js 16 Cache Components: show datetimes in the viewer's device time zone without a dynamic layout or refresh loop
Viewed 0 times
Next.js 16 with cacheComponents, React 19
router.refreshIntl.DateTimeFormat resolvedOptions timeZonedevice time zonesetTimezoneOverridedatetime-local defaultValue
browserssrnodejs
Error Messages
Problem
Server-rendered pages must display and edit datetimes in the device time zone of the viewer, but the server cannot know it. Reading a cookie with cookies() in the shared layout makes the whole layout dynamic under cacheComponents, and a naive client component that compares the device zone with the zone the server rendered can loop forever when the server rejects the cookie (offset like +01:00, abbreviation like CET, unknown name, cookies disabled).
Solution
1) A tiny "use client" component mounted in the layout renders null; in useEffect it reads Intl.DateTimeFormat().resolvedOptions().timeZone, parses document.cookie itself (take the LAST pair with that name, which is what Next's cookie parser keeps), and if it differs writes
tz=<zone>; path=/; max-age=<1y>; samesite=lax (+ secure on https). It re-reads document.cookie and calls router.refresh() only if the write stuck. Comparing to the cookie (not to what the server chose) makes it converge after one refresh even for forged values. 2) Server side: a pure function raw -> validated canonical zone name (fallback: account zone), plus a React cache()-wrapped async getter that calls cookies(); call it only from components already inside <Suspense>. 3) Give the form fields a React key equal to the zone so they remount after the refresh; otherwise an uncontrolled input can keep the old wall-clock value while hidden fields (original value, zone) update, and a save without changes shifts the value. 4) Verify with CDP: Emulation.setTimezoneOverride in ONE long-lived session (it drops when the session closes), count Network Document vs RSC (_rsc=) requests: expect 1 document + 1 RSC on first load, 0 RSC on reload.Why
The device zone only exists in the browser (Intl), so the first server render must use a fallback; the cookie is untrusted input that the server may reject, so the client's convergence test must not depend on the server's choice.
Gotchas
- Chrome reports ICU canonical ids: an emulated Asia/Kolkata reads back as Asia/Calcutta
- Chrome refuses cookies over 4096 bytes (Network.setCookie: Sanitizing cookie failed)
- Next's cookie parser keeps the last duplicate and silently skips values that fail decodeURIComponent
- A server action re-render updates hidden inputs but a typed input keeps the user's value; wait on the hidden field when testing
Context
App Router with cacheComponents enabled, server components rendering dates, forms with datetime-local inputs whose hidden fields carry the zone and the shown value.
Revisions (0)
No revisions yet.