ISO Dates Default to UTC. That Broke Our Audit.
When we built the ops-audit page, each audit JSON file has a date field: a plain ISO string like "2026-06-05". The page renders that date in the header: "Prepared for Jane Owner · June 5, 2026."
For about a day, it was showing the wrong date.
What Was Happening
The original render code:
new Date(audit.date).toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})
When you pass a date-only ISO string to new Date(), the JavaScript spec treats it as UTC midnight. "2026-06-05" becomes 2026-06-05T00:00:00Z. Then toLocaleDateString() converts that UTC timestamp to the user's local timezone. Anyone in the US, East or West, is behind UTC. UTC midnight on June 5th is June 4th at 8PM Eastern, or 5PM Pacific.
The audit was prepared on June 5th. Every US prospect saw June 4th.
A one-day offset in a document you're sending to a business owner is exactly the kind of small credibility detail that shouldn't be wrong.
The Fix
Append a local-time marker:
new Date(`${audit.date}T00:00:00`).toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})
"2026-06-05T00:00:00" has no timezone offset. The spec treats date-time strings without an offset as local time. The date parses to midnight local, and toLocaleDateString() renders it correctly regardless of where the server or the reader's browser is.
Why This Is Easy to Miss
In development, you're usually in the same timezone as your machine. The date looks right. In testing, you check the output and the date looks right. The bug only manifests for users whose local time is behind UTC. In the US, that's everyone.
The rule: bare ISO date strings (YYYY-MM-DD) parse as UTC. Date-time strings without an offset (YYYY-MM-DDThh:mm:ss) parse as local. If you store dates as YYYY-MM-DD and want to display them in local time, always append T00:00:00.
The Second Fix in That Same Commit
The same commit also changed the page's root element from <main> to <article>. The audit page is rendered inside the site's root layout, which already wraps the page area in a <main> landmark. Two <main> elements on one page is invalid HTML. Screen readers treat the first <main> as the primary content area; a second one breaks that contract.
The audit content is a self-contained document inside the page, which is exactly what <article> is for. The fix:
// Before
<main className="min-h-screen bg-white text-neutral-900">
// After
<article className="min-h-screen bg-white text-neutral-900">
Neither of these bugs would cause the page to visually break. That's part of what makes them easy to ship past. The date looks plausible, and two <main> elements render fine in every browser. But one sends a wrong date to a client prospect, and the other fails an accessibility validator.
We caught both in the same pass. Worth documenting since both patterns show up constantly in Next.js App Router pages built by routes that assume they control the full document structure.