Skip to content

Render dates in the user's time zone on the server too - #2

Merged
Sky-walkerX merged 1 commit into
mainfrom
fix/render-in-user-zone
Oct 2, 2026
Merged

Sky-walkerX merged 1 commit into
mainfrom
fix/render-in-user-zone

Conversation

@Sky-walkerX

Copy link
Copy Markdown
Owner

Follow-up to #1. Pages now arrive with their data, so the server renders dates, and on Vercel it runs in UTC. For anyone outside UTC the HTML disagreed with the browser: "2 Oct" against "3 Oct", and no "due today" count. React then threw the server HTML away (minified error #418 in production).

  • A clock. The notebook layout works out the user's zone (from the lk-tz cookie, UTC without one) and the current time, and passes both to ClockProvider (app/components/clock.tsx). Components that render dates or work out "today" use it through date-fns's in option with @date-fns/tz, so the server and the browser agree. The prefetch keys "today" the same way.
  • Self-correcting zone. If the browser's zone differs from the server's, it saves its zone in the cookie and refreshes once.
  • Relative times. ago() takes the clock's now, which matches the server's render until hydration.
  • Shared pages get the same clock.
  • Sorting lists. SortableList gives dnd-kit a stable id.

Checked against a production build on a server with TZ=UTC:

  • No hydration errors on any notebook page or shared page, from browsers in Asia/Kolkata and America/New_York.
  • Each viewer sees their own date (3 Oct in IST, 2 Oct in New York) in the server's HTML, and the due count renders on the server.
  • tsc, lint, 377 tests, the smoke and route checks, and every end-to-end script pass.

Since pages arrive with their data, the server renders dates and "today", and it runs in UTC. For anyone elsewhere the HTML disagreed with the browser ("2 Oct" against "3 Oct", no due count), and React threw the server's HTML away.

The notebook layout now works out the user's zone (the lk-tz cookie, UTC without one) and the time once, and hands both to the browser through a clock (app/components/clock.tsx). Every component that renders a date or works out today reads that clock, through date-fns's in option with @date-fns/tz, so server and browser agree; the prefetch keys today the same way. If the browser is in a different zone than the cookie said, it saves its own and renders again. Relative times ("4 minutes ago") use the server's now until hydration for the same reason.

Shared pages get the same clock. Sorting lists give dnd-kit a stable id, since its global counter differs between server and browser.
@vercel

vercel Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
lockin Ready Ready Preview Oct 2, 2026 7:58pm UTC
plan-wise Ready Ready Preview Oct 2, 2026 7:58pm UTC

@Sky-walkerX
Sky-walkerX merged commit b99e2e0 into main Oct 2, 2026
3 checks passed
@Sky-walkerX
Sky-walkerX deleted the fix/render-in-user-zone branch October 2, 2026 19:58

This branch was successfully deployed

1 active deployment
Preview — 49c08328 Deployed Oct 2, 2026 by vercel[bot]
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