Experiment · Oct 2026 · 3 min read

My static site thought it was still September

The availability label in my nav said 'Sep 2026' twelve days into October. The fix was one hook, one build-time variable, and a rule about dates in copy.

LAB‑10

On the twelfth of October the label in my nav still read 'Available — Sep 2026'. It was one string in data/site.ts, typed by hand, and it had been wrong for twelve days. I was offering a start date in the past to anyone who opened the site, in three places: the nav, the menu footer and the hero.

01 Why 'just compute it' fails on a static site

The obvious fix is new Date() and a month formatter. On this site that creates a second bug. Every page is prerendered when I push to main, so the HTML carries whatever month it was at build time. If the browser then works out the month for itself during hydration, and a new month has started since the last deploy, the server HTML says October, the client says November, and React reports a mismatch.

The other standard answer is a scheduled rebuild on the first of each month. I didn't want one. It's a cron job that exists to keep a single word accurate, and when it fails nothing tells you, because a stale label looks exactly like last month's correct one.

02 Start from the build date, then catch up

What shipped is a hook of about thirty lines, useAvailability. next.config.mjs writes the build timestamp into an environment variable, which Next inlines into both the server and the client bundle. The hook's first render formats that timestamp, so server and client agree by construction. An effect then runs once after mount, formats the real current date, and updates the state if the answer has changed. A visitor arriving weeks after my last deploy sees the old month for an instant, then the right one.

The formatting goes through Intl.DateTimeFormat with timeZone set to Europe/Tallinn. Without it, a visitor in Los Angeles would watch my availability roll over ten hours late, which is an odd thing for a label about my calendar to do.

Every date the hook builds is the 15th of a month at noon UTC. A date at midnight on the 1st sits on a boundary, and a timezone conversion can push it back into the previous month. No offset on earth moves the 15th at noon.

03 Next month, with an off-by-one doing the work

Then the rule changed, because the current month was the wrong answer anyway. If you're reading this in October, October is taken. The label should always name the month after. Intl returns the month as a number from 1 to 12. Date.UTC expects 0 to 11. Passing the first straight into the second lands one month ahead, and in December the 12 rolls over into January of the following year without an if statement. The cases worth checking are the last evening of October and New Year's Eve, both in Tallinn time.

For the times I'm booked further out there's one override, AVAILABLE_FROM, set to something like '2027-02'. Once the calendar catches up with it, the label goes back to rolling by itself.

04 The other dates were hiding

The nav was only the first one. The contact page said 'Open for Summer 2026'. Three marquees carried the year. The footer had a hardcoded copyright. The contact page's meta description promised summer availability to anyone finding me through search. All of them typed by hand, each on its own schedule for becoming false.

They now fall into two groups. Anything React renders reads from the same hook, or from a four-line version of it in the footer. The meta description can't: it's written into the HTML at build time and shown in search results as is, so I took the date out of it. That's the rule I'm keeping for static sites. A date in the copy is computed in the browser or it's removed. A typed one is a bug with a due date.

Next upThe Flash sites I built can't be opened anymore ↗
Thinking out loudBuilding in the openMore soonThinking out loudBuilding in the openMore soon