- Shipped
- September 9, 2026 at 4:23 PM UTC
- Author
- Kamo
- Commit
- c4fb5a8
The homepage has been throwing React #418 — "the server rendered text didn't match the client" — on every load, in production as well as locally. The mismatch is the offer banner's clock: const [time, setTime] = useState(() => calcCountdown(new **************** // reads Date.now() ); A lazy initialiser that reads the clock runs twice on two different clocks: once when the server prerenders the page, once when the browser hydrates it. Those cannot agree, and with `revalidate = 300` the served HTML is up to five minutes old, so they disagree by minutes. React reported the seconds box as 25 on the server and 23 on the client, then regenerated the whole banner subtree on the client — which is the actual cost, on the one element of the hero that is above the fold and animating. The seed now comes from the server as a prop, so the first client render is a pure function of what crossed the boundary and reproduces the server's HTML exactly; the effect moves to the reader's own clock immediately after mount, before the interval's first tick. Not OfferBand's fix, which is right there: it starts at null and renders no clock until mount. That is correct for a band further down the page, but this banner is above the fold and the clock is a third of its width, so a pop-in would shift the hero on every load. Verified against a production build with the prerender deliberately aged 100 seconds: the HTML seed read 21 07 41 40, the browser showed 21 07 38 57 — matching a remaining time computed independently of the page — and there were no hydration errors on /en, /en/watch, /en/pricing, /en/pricing/offer, /en/features, /fr or /en/site-map. `Date.now()` in a server component is not a dynamic API, and the route table still shows the homepage prerendered.