Error Pages

Files in pages/errors/ are not routes. They are what JAF renders when a navigation has nowhere to go.

The filename is the status code:

pages
404.htmlNo route matched
403.htmlA route was forbidden
500.htmlThe first page would not load

Select a file to see what it is for

You cannot navigate to /errors/404 - the folder is kept out of the route table.

404 Not Found

When no route matches, JAF tears down the layouts, clears the page and mounts 404.html on its own.

Your 404 page gets no layout and no props. If you want your site's header and footer around it, put them in the page itself, and read what you need from the browser.
source
<!-- pages/errors/404.html -->
<script>
const requestedPath = window.location.pathname
</script>

<Nav />

<main class="not-found">
  <h1>404</h1>
  <p>Nothing lives at <code>{requestedPath}</code>.</p>
  <a href="/">Back home</a>
</main>

<Footer />

Ship one. Without a 404.html nothing renders at all: the router logs a warning and the previous page simply stays on screen, so a dead link looks like a link that does nothing.

403 Forbidden

A denied <guard> does not render this page. Denial is a navigation now: redirect= on the tag, else guards.redirect in config/jaf.config.js, else /, carrying ?return=. See Route Guards.

Changed in 0.1. A guard without a redirect used to render 403.html in place of its children, which left the protected page's URL in the address bar and made a guard mean two different things. If your app kept denial content in 403.html, move it to the page you redirect to.

The 403 entry stays in the error-pages config, as the renderer for a forbidden route - the router's answer to a server or a route loader saying no, which is a different question from a guard deciding what to show a visitor. No path in the framework reaches it today, so 403.html is optional and inert in a normal app.

500 - the first page would not load

The route matched, but its HTML never arrived: the origin answered 500, a deploy was in flight, the visitor was offline.

On the app's first page, JAF mounts pages/errors/500.html in the root element and names the failure on the console:

source
[jaf] The first page failed to load, so route "/" never mounted:
Failed to load component: /pages/index.html.
The configured 500 page is showing in its place.

With no 500.html, the message says so and tells you to add one. Either way the boot finishes and <body> still gets jaf-ready, so a page whose CSS hides the app until then is not invisible as well as empty.

First page only. A later navigation that cannot load its page still rejects, so your own navigate() call can catch it and decide. The boot has no caller to hand the failure to, which is why it gets a page instead.

Your server still needs a fallback

A JAF app is a single page, so the 404 above is a client 404: the server has to serve the app shell for unknown paths and let the router decide.

Any SPA fallback works.

source
# nginx
location / {
  try_files $uri $uri/ /index.html;
}

The consequence: a missing page answers HTTP 200, because the shell was served successfully.

Crawlers that do not run JavaScript see the shell, not your 404.