error-pages
A launch-ready 404 is not "404 Page Not Found" on a blank framework default. It is a page that returns 404, looks like your product, and gets the visitor moving again. And a pretty 404 served with HTTP 200 is a soft-404 that search engines treat as real content.
Activate when
- Any site with routes; especially SPAs (Vite/CRA) where unknown URLs fall through to
index.htmlwith 200. - The 404 is a framework default, unstyled, missing, or
LINK_SOFT_404showed up incheck-links. - Not for intentional redirects or for private/authenticated route handling (that is authorization).
Working alone
This skill is self-contained. Its companions (declared in its metadata) are skills whose method it may need to do its own promised work. Use of a companion can be conditional: declaring one does not mean running it. When a companion's lane applies, use the skill if it is installed; if not, follow its short entry in references/companion-methods.md and say in your report which lanes ran inline at reduced depth. Never skip an applicable lane silently. Skills mentioned here only for escalation, referral, documentation, or optional deeper follow-up are not dependencies: report the hand-off and finish honestly.
Companions: design-system-reconnaissance.
Inspect
- Status code. Request a clearly invalid URL and read the status, not just the page:
curl -s -o /dev/null -w "%{http_code}" <site>/definitely-not-a-page-xyz. Also try a deep path (/blog/nope/nope), a URL with a trailing slash, a wrong-case path, an unknown file (/missing.png), and a bad dynamic ID (/products/999999,/users/abc). Expect 404 (or 410) for each; a missing resource (dynamic route with a not-found ID) should also be a real 404 and not an empty product page with 200. - Framework mechanism (where the fix belongs): Next.js
app/not-found.tsx+notFound()in dynamic routes (andpages/404.tsx); Astrosrc/pages/404.astro; SvelteKit+error.svelte; Nuxterror.vue; Remix$.tsxsplat route with a 404 status; Vite/React SPA with a router: client-rendered NotFound route plus hosting configuration that returns a real 404 for unknown paths or an explicit trade-off (SPA fallback returns 200, so pair withnoindexon the not-found view and server-side route allow-lists where possible); static hosts (404.htmlat root for Netlify/GitHub Pages/Cloudflare Pages/Vercel); Express/others: catch-all withres.status(404). - Design. Uses the site's layout shell, header/footer, typography, and colors (
design-system-reconnaissance); works at 375px and in dark mode if the site has it. - Recovery. Says plainly that the page was not found; offers useful navigation: link home, main sections (the site's own nav), search if the product has one, and a contact/support route; possibly popular pages if real ones exist. Copy in the site's voice. No jokes that block understanding; no dead-end.
- No leakage. No stack traces, file paths, framework debug overlays, internal route lists, or environment info in 404 or 500 pages, in production mode. Check with a production build, not dev mode (dev overlays are expected).
- 500/error boundary. A generic, styled server-error page that does not leak details, plus a client-side error boundary for React apps (
failure-resilience). - Indexing. A 404 should not be in the sitemap; the 404 page itself may carry
noindex(harmless with 404 status; required for SPA 200 fallbacks).check-linksreportsLINK_SOFT_404if title/h1 say not found while status is 200 (paths relative to this skill's folder:node scripts/check-links.mjs --url <site> --render). - API routes under
/api/*should return JSON 404s, not the HTML page.
Evidence that counts
Label each claim OBSERVED, SOURCE-INDICATED, DECLARED, INFERRED, UNKNOWN, or REVIEW REQUIRED. UNKNOWN is never a pass and never a failure.
- Status code from a real request to the running (production-mode) site: OBSERVED. A
not-foundcomponent existing in source is SOURCE-INDICATED. - The hosting layer decides the final status for static and SPA deployments; local dev servers often differ from production. If production is not reachable, status behavior is UNKNOWN for that layer.
May change
Create/replace the not-found and error pages with the project's layout and components; add notFound() calls for missing dynamic resources; configure hosting rules (_redirects, vercel.json, netlify.toml, server catch-all) that return true 404s where the platform supports it; add noindex on SPA not-found views; remove debug output. Do not invent new site sections or nav items to populate the page: link only to real ones.
Must not claim
"404 handled everywhere" (list the URL classes tested), or that the host returns 404 when you only tested a dev server.
Verify
Re-request every URL class tested and confirm status 404 (or 410 where deliberate) with the designed page; view at desktop and 375px; confirm the links on the page resolve (check-links); confirm no unexpected LINK_SOFT_404; view a production build for absence of debug info.
Escalate
A platform that cannot return a 404 status for an SPA (record the limitation and the mitigation: noindex, prerendering, or a host change); authenticated areas where unknown URLs should redirect to login rather than reveal existence.
No change is valid when
Unknown URLs return a real 404 in the product's design with recovery paths and no debug details. Do not restyle a working 404 for taste.
