Repository navigation
Branded 404 that redirects pre-redesign URLs - #3
Merged
Merged
Conversation
Pre-redesign URLs (/career, /travel, /contact, /blog, /projects) render Next's default 404 today, verified live. This replaces it with a branded page: recognized old paths show a 3-second countdown then redirect to the matching section; unknown paths get a plain no-guessing 404. Diverges from the plan's given code in two places to satisfy this repo's eslint-config-next (React Compiler) rules: reads window.location via useSyncExternalStore instead of useEffect+setState (avoids react-hooks/set-state-in-effect), and uses next/link for the two links that target "/" (avoids @next/next/no-html-link-for-pages). Behavior is unchanged - static out/404.html still SSRs the generic copy first and swaps to the redirect copy on client hydration, confirmed by the e2e suite running real browser JS.
HIGH: /blog, /writing, /posts redirected to /#writing, which doesn't exist while POSTS is empty (hasPosts() false) - visitors landed at the top of the homepage with a dead #writing anchor. Gate the redirect target and the .nf-map Writing link on hasPosts(), same predicate BrutalistLanding/Nav use. Guarded by a new unit test. HIGH: the reflected pathname had no length cap and .nf-lead had no overflow-wrap, so a long/crafted URL blew the page out to 2x viewport width. Cap displayed path length and add overflow-wrap. MEDIUM: split app/not-found.tsx into a server component (owns metadata - was silently inheriting the homepage's <title> and canonical URL) rendering the interactive part from the new components/not-found/NotFoundClient.tsx. Added a "Stay on this page" control and an onClickCapture guard so the 3s auto-redirect can be cancelled (WCAG 2.2.1) and can't override a navigation the visitor already started by clicking a section link. Split the countdown's aria-live announcement from the visual ticker so screen readers get one announcement, not one per second. Fixed .nf-map's dead gap token (.writing-all's own margin-top was overriding it) and removed CSS that duplicated .section's existing spacing. e2e: added a /blog fallback test, a redirect-cancellation race test, a viewport assertion on the redirect target, and a comment on what this suite does and doesn't prove about the static export.
Redirecting /blog to '/' with label "the front page" produced "It now lives on the front page, under the front page." Exclude the Writing entry from REDIRECTS entirely while hasPosts() is false instead, so /blog falls through to the honest plain 404 (no redirect, no guessing) until a real post ships and gives it somewhere to go.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements plan 07 from the 2026-08-31 site audit (
audit/2026-08-31-plans, not yet merged).Pre-redesign URLs (
/career,/skills,/education,/travel,/contact,/blog,/projects) currently render Next's default 404 on the live site, gold chat bubble and all. This adds a brandedapp/not-found.tsx: recognized old paths get a 3-second countdown then a client-side redirect to the matching in-page section, with a "Go there now" skip button and a "Stay on this page" cancel; unrecognized paths get a plain, no-guessing 404 with links to every section.Ships as
out/404.htmlviaoutput: 'export', which GitHub Pages serves for any unknown path.Notable deviations from the plan doc
useSyncExternalStorerather thanuseState+useEffect, and usesnext/linkfor the two links targeting/— both required to satisfy this repo'seslint-config-nextReact Compiler lint rules (react-hooks/set-state-in-effect,@next/next/no-html-link-for-pages), which the plan's given code didn't pass as written.app/not-found.tsx, ownsmetadata— a'use client'not-found page otherwise silently inherits the homepage's<title>and canonical URL) pluscomponents/not-found/NotFoundClient.tsxfor the interactive part./blog,/writing,/postsonly redirect to/#writingoncehasPosts()is true (the Writing section doesn't render, and has no anchor, whilePOSTSis empty) — otherwise they fall through to the plain 404, same as any other unrecognized path.A
code-reviewerpass on the first draft caught the/blogdead-anchor and the overflow bug as HIGH findings; both are fixed and covered by tests.Test plan
npx tsc --noEmit— cleannpm run lint— clean except two pre-existing errors unrelated to this change (TravelMap.tsx,vitest.setup.ts; tracked in plan 10)npm test -- --run— 16/16 unit tests pass (13 existing + 3 new, table-driven over the redirect map)npx playwright test— 25/25 e2e pass (18 existing + 5 new ine2e/not-found.spec.ts), plus 2 pre-existing suites unaffectednpm run build && ls out/404.html— static export produces the 404 shellscrollWidth==clientWidthat 1280px for a 301-char path, vs. 2581px before the fix)https://dommango.github.io/traveland confirm it lands on the map within 3 seconds