---
url: /frontend/error-pages.md
description: >-
  The pages/404 component KosmoJS renders for unmatched routes - where it lives
  per framework, how it is registered as the router's catch-all, and how it
  behaves under CSR, SSR and SSG, including which HTTP status each one returns.
---

An **error page** is a full-page route rendered when a request can't be satisfied.

It is distinct from an [error boundary](/frontend/error-boundaries.md),
which catches a *rendering* error inside an otherwise-working page,
and from [api/errors.ts](/backend/error-handling.md), which handles failures on the API side.

Today there is exactly one error page: **404**. The `pages/<code>.*` shape is deliberate,
so more can join it later without changing the convention.

## The 404 page

Every frontend source folder gets one, at the root of `pages/`:

| Framework | File |
|---|---|
| React | `pages/404.tsx` |
| SolidJS | `pages/404.tsx` |
| Vue | `pages/404.vue` |
| Svelte | `pages/404.svelte` |
| MDX | `pages/404.mdx` |

It is **seeded once when the folder is created**, carrying a placeholder you are expected to replace.
From that point it is an ordinary source file: it is never re-seeded, never overwritten by a later boilerplate pass.

### Error Pages | The 404 page | React

```tsx
// React: pages/404.tsx
export default function NotFound() {
  return (
    <main>
      <h1>404 - Not Found</h1>
      <a href="/">Back home</a>
    </main>
  );
}
```

### Error Pages | The 404 page | Solid

```tsx
// Solid: pages/404.tsx
export default function NotFound() {
  return (
    <main>
      <h1>404 - Not Found</h1>
      <a href="/">Back home</a>
    </main>
  );
}
```

### Error Pages | The 404 page | Vue

```vue
<!-- Vue: pages/404.vue -->
<template>
  <main>
    <h1>404 - Not Found</h1>
    <a href="/">Back home</a>
  </main>
</template>
```

### Error Pages | The 404 page | Svelte

```svelte
<!-- Svelte: pages/404.svelte -->
<main>
  <h1>404 - Not Found</h1>
  <a href="/">Back home</a>
</main>
```

### Error Pages | The 404 page | MDX

```mdx
{/* MDX: pages/404.mdx */}

# 404 - Not Found

[Back home](/)
```

## How it is registered

It is appended to the folder's route list as the router's **catch-all (`path: "*"`), always last**,
so it matches only after every real route has failed to.

You never register it yourself and it never appears in the typed [Link](/frontend/link-navigation.md) route map -
there is no route name to link to.

Two consequences worth knowing:

* **The app file wraps it; layouts do not.** The catch-all is a top-level sibling of your routes,
  so [app.\*](/frontend/layouts.md#global-layout-via-app-file) - your global shell, nav, providers -
  still renders around it, but no `layout.*` does.
  A 404 under `/dashboard/anything` gets the app shell, not the dashboard layout.

* **It is lazy-loaded on the client** like any other page, and imported eagerly into the SSR bundle
  so the server can render it without a dynamic import.

## CSR, SSR and SSG

The same component is used in every mode - what differs is the **HTTP status** the visitor actually receives:

| Mode | Renders the page | HTTP status |
|---|---|---|
| CSR | ✅ client router matches the catch-all | whatever served the SPA fallback - normally **200** |
| SSR | ✅ rendered on the server | **404** |
| SSG | ❌ no `404.html` is emitted | your static host decides |

::: warning A client-rendered 404 is not a 404 to a crawler
Under CSR the host has already answered - typically `200 OK` with `index.html` - before your router decides nothing matched.
The visitor sees the right page; a crawler or an uptime check sees a success.
If correct status codes matter (SEO, monitoring), either enable [SSR](/frontend/server-side-render.md) for that folder,
or configure the fallback at the host/proxy level to return 404 for unknown paths.
:::

For [SSG](/frontend/static-site-generation.md) folders, nothing is pre-rendered for unmatched paths -
static hosts have their own not-found configuration (`404.html` on GitHub Pages and Netlify, `error_page` in Nginx, and so on).
Point it at whatever your host expects.

## What the 404 page is not

* **Not for API 404s.** A missing record behind `/api/users/[id]` is a backend concern:
  return a declared `[404, "json", ...]` response variant, or let it reach [api/errors.ts](/backend/error-handling.md).
  Requests under `backend.base` never render a page.
* **Not a render-error handler.** If a page throws while rendering,
  that is an [error boundary](/frontend/error-boundaries.md)'s job.
  The 404 page only ever renders because *routing* found nothing.
* **Not `not-found.tsx`.** There is no per-route not-found convention as in Next's App Router -
  one 404 page serves the whole source folder.
  Different folders have their own, which is usually the distinction you actually wanted.
  [Migration Tips ›](/essentials/migration-tips.md#loading-tsx-error-tsx-not-found-tsx)

## Triggering it deliberately

There is no `notFound()` helper. A route that exists but has nothing to show is a normal render decision -
branch in the component and render your own not-found UI, or redirect.
The catch-all page is reserved for URLs that match no route at all.
