Skip to content

Add a "Not found" template target for designed 404 pages#52

Merged
DavidBabinec merged 2 commits into
mainfrom
feat/not-found-template
Jun 12, 2026
Merged

Add a "Not found" template target for designed 404 pages#52
DavidBabinec merged 2 commits into
mainfrom
feat/not-found-template

Conversation

@DavidBabinec

Copy link
Copy Markdown
Contributor

What

Adds a third template target kind — Not found (404) — so a designed page can be served for every public 404, replacing the bare { "error": "Not found" } JSON response.

How it works

Core (src/core)

  • TemplateTargetSchema gains { kind: 'notFound' } (+ tolerant parseTarget branch). No DB migration — template config lives in cells_json.
  • New resolveNotFoundTemplate(site) picks the winner (highest priority, document order breaks ties). A notFound template never enters resolveTemplateChain — it isn't a breadth level; route matching never "matches" a 404.

Publish (server/publish)

  • renderPublishedNotFound composes the template like a regular page — wrapped by the everywhere layout chain, so the 404 page carries the site chrome.
  • Full publish bakes it to 404.html in the slot — deliberately the static-hosting convention, so a raw static export keeps a working error page on Netlify / GitHub Pages. Baked first, so a literal page with slug 404 would overwrite it and stay authoritative.

Serve (server/router.ts, publicRouter.ts)

  • New last dispatcher route tryServeNotFoundPage: any GET no other route claimed serves the baked artefact (one disk read, no DB — the path bot probes hit) with status 404; falls back to a live render through the Layer B LRU under the reserved /404 key (stored once per publish version, shared by every missed URL). No notFound template → today's JSON 404, unchanged.
  • Namespaced prefixes (/admin/api/*, /_instatic/*, /uploads/*) keep their own 404s; the fresh-install setup redirect still wins (it runs earlier).

Editor / AI

  • "Not found (404)" option in Template settings → Applies to; explorer meta shows "Not found".
  • Canvas wraps the template in the everywhere layout (mirrors the publish composition); "Open live page" resolves to /404.
  • setPageTemplate AI tool accepts the new kind.

Known trade-off

A direct GET of /404 serves the baked artefact with status 200 — the same convention as GitHub Pages. Documented in docs/features/templates.md.

Testing

  • bun test — 5385 pass (new: notFoundResponse.test.ts covering Layer A artefact serving incl. a DB-free proof, live-render fallback + caching, and the no-template null path; matching/parse/label/dialog tests extended).
  • bun run build, bun run lint — clean.

🤖 Generated with Claude Code

DavidBabinec and others added 2 commits June 12, 2026 11:48
A page can now be marked as the site's 404 page via a third template
target kind, `{ kind: 'notFound' }` — "Not found (404)" in the Template
settings "Applies to" select.

- Core: new target kind in TemplateTargetSchema + parsePageTemplate;
  resolveNotFoundTemplate(site) picks the winner (priority, then
  document order). A notFound template never enters route chains.
- Publish: full publish bakes the template (wrapped in the everywhere
  layout chain, like any page) to 404.html in the slot — the
  static-hosting convention, so raw static exports keep a working
  error page on Netlify/GH Pages.
- Serve: new last dispatcher route catches every GET no other route
  claimed and serves the baked artefact (one disk read, no DB) — or a
  live render through the Layer B LRU under the reserved /404 key —
  always with status 404. No notFound template → JSON 404 as before.
- Editor: third "Applies to" option; "Open live page" resolves to
  /404; canvas wraps the template in the everywhere layout.
- AI: setPageTemplate accepts the new kind; prompts updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@DavidBabinec DavidBabinec merged commit 828853e into main Jun 12, 2026
6 checks passed
@DavidBabinec DavidBabinec deleted the feat/not-found-template branch June 12, 2026 13:29
DavidBabinec added a commit that referenced this pull request Jun 13, 2026
…ifier

SITE_DEFAULTS_ID is only used within MetaTab — make it a private const.
The design spec now lives on main (landed via #52), so drop the "(local)"
qualifier in the feature doc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant