Site Fix Notes: A 404 Calligraphy Link and a Stuck Home Return
A homepage calligraphy record opened a 404, then returning home stopped at the intro H. The two symptoms came from separate layers: stale development routes and intro state that did not respond to history restoration.
This bug had a very specific sequence:
- Open a practice record from the homepage calligraphy section.
- Reach a 404 instead of the work detail page.
- Return to the homepage from the 404.
- Stop at the centered blackletter
HandWe Are the World, with the page content never appearing.
The two symptoms happened one after another, so they initially looked like one routing failure. They came from two different layers.
Checking Whether the Link Was Actually Wrong
The homepage does not hardcode an address for a specific work. Calligraphy data passes through calligraphyWorkHref(), then the localized Link adds the locale prefix. The browser produced this address:
/en/calligraphy/zjes-practice-050The slug existed in the calligraphy YAML and Velite output. The dynamic route at src/app/[locale]/calligraphy/[slug]/page.tsx also existed. Further checks showed that calligraphy was not the only affected area: dynamic blog and photography detail pages were also returning 404 at the time.
The problem was the running Next.js development process. Its development route manifest was stale and only knew about the homepage and the not-found page. Restarting the development server rebuilt both the Velite content and the Turbopack route manifest. The calligraphy detail page then opened normally. The production build also listed:
/[locale]/calligraphy/[slug]There was no reason to rewrite a correct URL just to hide the symptom. The development routing state was what needed to be restored.
Why Returning Home Stopped at the H
The second problem lived in HomeEntrance.tsx.
The homepage entrance has four phases:
type IntroPhase = "checking" | "intro" | "exiting" | "done";After a normal entrance finishes, the module-level introCompletedInRuntime flag remembers that it has completed during the current runtime. Returning through normal in-site navigation can reveal the homepage immediately instead of replaying the intro.
Browser history restoration is different. Returning from a 404 can restore cached homepage markup in the checking phase without rerunning the original effect. The overlay remains mounted while the homepage content keeps these effective styles:
opacity: 0;
pointer-events: none;The runtime flag still existed, but it was not state that React could subscribe to. It could not actively ask the restored page to check again.
Turning the Runtime Flag into a Subscribable Snapshot
The fix does not put the skip condition back into sessionStorage. Doing that would also skip the entrance after an ordinary reload and change the intended first-visit behavior.
The current implementation exposes the runtime value through useSyncExternalStore:
function useRuntimeIntroCompleted() {
return useSyncExternalStore(
subscribeRuntimeIntroCompletion,
getRuntimeIntroCompletionSnapshot,
getRuntimeIntroCompletionServerSnapshot,
);
}The subscription also listens for pageshow, which fires when the browser restores a history entry:
function subscribeRuntimeIntroCompletion(onStoreChange: () => void) {
introCompletionListeners.add(onStoreChange);
window.addEventListener("pageshow", onStoreChange);
return () => {
introCompletionListeners.delete(onStoreChange);
window.removeEventListener("pageshow", onStoreChange);
};
}Completion is mirrored to a data-home-intro-complete attribute on the current document. Restoring that same document can read the marker immediately. A real reload creates a new document without the attribute, so the entrance still plays normally.
The rendered result no longer trusts only the component's older internal state:
const homeIsReady = homeReady || introCanSkipOnMount;
const introIsDone = introPhase === "done" || introCanSkipOnMount;Once the runtime snapshot says the entrance has completed, the content becomes visible and interactive, and the overlay can no longer hold the page closed.
Verifying the Complete Path
The final check followed the visitor's actual path instead of stopping at a unit-level assertion:
- Open the English homepage and let the first-visit entrance finish.
- Click
/en/calligraphy/zjes-practice-050. - Confirm that the work title and detail content appear instead of a 404.
- Use browser Back.
- Confirm that the homepage calligraphy section exists and the intro overlay count is zero.
The change also passed pnpm lint, the full test suite, and pnpm build. All 170 tests passed, and the production build generated the dynamic blog, photography, and calligraphy routes.
Takeaway
The tempting response was to change the link when seeing a 404, then remove the animation when seeing a stuck overlay.
The better fix was to separate the adjacent symptoms. The link was correct, but the development route state was stale. The entrance could remain, but its completion state needed to respond to browser history restoration. The first visit still keeps its intended rhythm, ordinary reloads do not skip it, and returning home no longer leaves a finished curtain in front of the page.

