Recommended Strategy: UTC Serialization with Client-Side Formatting
To maintain consistency and avoid hydration mismatches, you should keep dates as UTC ISO strings in server-side props and perform the timezone conversion exclusively on the client. This ensures the server renders a deterministic value that matches the initial client-side render, preventing the "flash" of incorrect content from triggering a React hydration error.
The Likely Explanation for Hydration Mismatches
Hydration errors occur when the HTML generated by the server differs from the first render on the client. If you use toLocaleString() or Intl.DateTimeFormat during the initial render, the server (likely running in UTC) produces one string, while the browser (running in the user's local zone) produces another. React detects this discrepancy and may throw a warning or fail to attach event listeners correctly.
Implementation Steps
- Server-Side: Serialize dates as ISO 8601 strings. In
getServerSideProps or getStaticProps, ensure dates are passed as strings: date.toISOString().
- Client-Side: Use a
useEffect hook or a state-managed wrapper to handle the conversion. This ensures the conversion happens after the initial hydration.
- Rendering: Render a placeholder or the UTC string initially, then update to the localized string once the component mounts.
// Example Client Component
import { useState, useEffect } from 'react';
export default function LocalizedDate({ utcDate }) {
const [formattedDate, setFormattedDate] = useState(null);
useEffect(() => {
// This runs only on the client after hydration
setFormattedDate(new Date(utcDate).toLocaleString());
}, [utcDate]);
// Return null or a skeleton to avoid the UTC flash,
// or return the UTC date as a fallback
return {formattedDate || 'Loading...'};
}
Evaluating Server-Side Timezone Derivation
Deriving the timezone from request headers or cookies in getServerSideProps is technically viable but often impractical. While you can use a cookie to store the user's IANA timezone, this requires an initial client-side request to set that cookie. If the cookie is missing, you still fall back to a default, which may cause inconsistencies for first-time visitors.
Verification and Diagnostics
To verify your implementation, use the following checks:
- View Page Source: Confirm the HTML contains the UTC ISO string, not a localized date.
- Console Check: Ensure no "Hydration failed" warnings appear in the browser console during page load.
- Timezone Test: Use browser DevTools to emulate different locales or a VPN to verify the
Intl API is responding to the system clock.
Missing Diagnostic: Are you using a specific date library (e.g., date-fns, Day.js) or the native Intl API? Library-specific serialization can sometimes introduce hidden offsets that affect the toISOString() output.