Temporal in Production: Field Notes on Replacing JavaScript Date, the Same-Day Bug, and What Doesn't Cross the RSC Boundary
Temporal is the TC39 API that replaces JavaScript's Date object with immutable, time-zone-aware types. The migration is mechanical almost everywhere, except at two boundaries: a Temporal object cannot be passed as a prop from a Server Component to a Client Component, and it has to become a string again before it reaches a database driver. I have written the same date bug at least four…
Temporal is a TC39 API that replaces JavaScript's mutable Date object with immutable, time-zone-aware types. The migration is mostly seamless, except at two boundaries: passing Temporal objects from Server Components to Client Components, and converting Temporal objects to strings before they reach a database driver. The same-day bug, where a user in Cairo reports an issue at 22:30 on the 22nd, but the serverless function runs in UTC and the dashboard records it as the 21st, has plagued developers multiple times.
The fix usually involves patching Date with a helper or a library. However, the author decided to move their Next.js app's date handling to Temporal instead. Temporal replaces Date but not Intl, and it owns arithmetic, comparison, and time zones, while Intl.DateTimeFormat handles human-facing formatting. Every Temporal object exposes toLocaleString().
Temporal types are chosen based on the value they represent. For example, a birthday is Temporal.PlainDate, an audit timestamp is Temporal.Instant, and "3pm in Cairo" is Temporal.ZonedDateTime. Temporal objects are class instances, so React cannot serialize them across the RSC boundary. To resolve this, call .toString() in the Server Component and .from() in the Client Component.
Temporal objects are immutable, so .add(), .subtract(), and .with() return new objects, and === never correctly compares two Temporal values — use .equals() or the static compare(). Install the temporal-polyfill package today, as native support across browsers and Node is still uneven. Temporal replaces the JavaScript Date object, which has three major defects: mutability, limited time zones, and inconsistent parsing.
Temporal addresses these issues by turning the distinction between UTC and local time into a type distinction, so Date parsing is no longer inconsistent by specification. For each field, pick the Temporal type based on what the value means to the business. For example, use Temporal.PlainDate for birthdays and invoice due dates, Temporal.PlainTime for store opening hours, Temporal.ZonedDateTime for meeting times in different time zones, Temporal.Instant for created_at and audit log entries, Temporal.Duration for session length and cache TTL, and Temporal.PlainYearMonth for card expiry.
To compare two Temporal values in the same day across time zones, use .toZonedDateTimeISO(zone).toPlainDate().equals() instead of two .toDateString() comparisons. Passing Temporal objects from Server to Client Components is not possible due to React rejecting class instances at the Server-to-Client boundary. Instead, serialize Temporal values to strings in the Server Component and parse them back in the Client Component using .from().
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.