How to design a custom 404 page in Astro
Broken links happen. Someone mistypes a URL, an old post gets renamed, a search engine holds onto a stale link. When that happens the visitor lands on a page that does not exist — and the default result is an ugly, generic error screen. A custom 404 page turns that dead end into something useful. Here is how I built one in Astro, and why each part matters.
The convention: just name the file 404.astro
Astro has a built-in rule: a file at src/pages/404.astro becomes the site’s not-found page. There is no config to write and nothing to register. Build the site, and Astro produces a 404.html at the root of your output.
That single naming convention is the whole “wiring.” Everything else is design.
What a good 404 page actually needs
A 404 is not just an apology — it is a recovery tool. A visitor who hit a dead end still wants to get where they were going. So a good 404 page should:
- Clearly say the page was not found (do not be cute to the point of confusion).
- Give a way back — at minimum a link to the home page.
- Offer search so they can find what they meant to reach — this blog links to its Pagefind-powered search page.
- Match the rest of the site so it does not feel like they left.
The real page
Here is this blog’s 404.astro, trimmed slightly for readability:
// blog/src/pages/404.astro
---
import { SITE } from "@/consts"
import BaseLayout from "@/layouts/BaseLayout.astro"
import TerminalWindow from "@/components/TerminalWindow.astro"
---
<BaseLayout
title={`404 — Not Found — ${SITE.author}`}
description="Page not found."
>
<TerminalWindow title="~/blog — signal lost">
<div class="flex flex-col items-center gap-6 py-10 text-center">
<p class="glitch font-mono text-5xl font-black ..." data-text="404">
404
</p>
<p class="font-mono text-sm tracking-widest ... uppercase">
NO SIGNAL — page not found
</p>
<p class="max-w-md font-mono text-sm leading-relaxed ...">
The route you requested doesn't resolve. It may have moved, or never
existed.
</p>
<div class="flex flex-wrap items-center justify-center gap-3">
<a href="/" class="border ...">← Back to posts</a>
<a href="/search" class="border ...">Search</a>
</div>
</div>
</TerminalWindow>
</BaseLayout>Let me walk through the choices.
Reusing the site’s layout
<BaseLayout
title={`404 — Not Found — ${SITE.author}`}
description="Page not found."
>The 404 page is wrapped in BaseLayout — the exact same layout every other page uses. This is deliberate: the header, fonts, colors, and metadata handling all come along for free, so the not-found page looks like part of the site rather than a bare error screen. The title still reads “404 — Not Found” so the browser tab and any bots are honest about what this page is.
SITE.author comes from a small consts.ts file, so the branding stays consistent site-wide without hardcoding my name here.
The message
<p class="glitch ..." data-text="404">404</p>
<p>NO SIGNAL — page not found</p>
<p>The route you requested doesn't resolve. It may have moved, or never existed.</p>Three levels of clarity: the big 404 code, a short human label, and a sentence explaining what happened. The glitch class and data-text="404" drive a CSS animation that fits the site’s terminal/CRT aesthetic — style, but never at the expense of the plain-English explanation underneath.
The escape routes
<a href="/">← Back to posts</a>
<a href="/search">Search</a>This is the most important part. Two clear next steps: go home, or search. Without these, a 404 is a dead end and the visitor leaves. With them, they have an obvious way to recover.
How static hosts serve it
Because Astro is a static site generator, 404.astro is compiled to a 404.html file at build time — there is no server running my code per request. So how does it get shown for a bad URL?
Most static hosts (this blog is deployed on Vercel) automatically serve 404.html whenever a requested path does not match any real file, and they return it with the correct HTTP 404 status code. That status matters: it tells search engines “this URL is gone, do not index it,” which keeps your search results clean.
flowchart LR
A[Visitor requests /missing] --> B{Does /missing.html exist?}
B -- yes --> C[Serve the page]
B -- no --> D[Serve 404.html with 404 status]You do not configure any of this on Vercel for an Astro site — building the 404.astro file is enough.
Build your own
- Create
src/pages/404.astro. - Wrap it in your normal layout so it matches the site.
- State clearly that the page was not found.
- Add a home link and, ideally, a search link.
- Deploy — your host will serve it for unmatched URLs.
What to remember
- Naming a file
src/pages/404.astrois all it takes; Astro handles the rest. - Reuse your base layout so the 404 feels native, not broken.
- A 404’s real job is recovery — always give a way home and a way to search.
- Static hosts serve the generated
404.htmlwith a real404status for any unmatched URL.