notFound
Stop rendering and answer 404 with the nearest not-found.tsx - from getServerSideProps, a component, an action or a route handler.
import { notFound, type GetServerSideProps } from '@gio.js/core';
export const getServerSideProps = (async (ctx) => {
const post = await db.posts.find(ctx.params.id);
if (post === null) notFound();
return { props: { post } };
}) satisfies GetServerSideProps<{ post: Post }, '/posts/:id'>;Reference
notFound() takes no arguments. Its return type is never: it always throws, so TypeScript narrows the value you checked after the call (above, post is a Post on the last line).
Behavior
What answers depends on where it is called:
| Called from | Response |
|---|---|
getServerSideProps, or a page or layout while it renders | 404 with the nearest not-found.tsx at or above the page's folder, inside that folder's layouts. Without one, the built-in 404 page. |
A page action | The same 404 page. |
A route.ts handler | 404 with the JSON body {"error":"Not Found"}. |
A 404 is never cached, even on a page that exports revalidate. When a cached page starts answering 404 - its data was deleted - the background revalidation that sees it evicts the cached copy. Returning { notFound: true } from getServerSideProps has the same effect as calling notFound(). When an action re-rendered the page with headers (a flash cookie) and getServerSideProps then calls notFound(), those headers are sent with the 404.
Examples
A missing record in a route handler
import { notFound, type GioRequest } from '@gio.js/core';
export async function GET(req: GioRequest<'/api/posts/:id'>) {
const post = await db.posts.find(req.params.id);
if (post === null) notFound(); // 404 {"error":"Not Found"}
return post; // 200, JSON
}A section with its own 404 page
app/
not-found.tsx # unmatched URLs, and pages without a closer file
shop/
layout.tsx
not-found.tsx # notFound() in any /shop page, rendered inside shop/layout.tsx
[id]/page.tsximport { GioLink } from '@gio.js/react';
export default function ProductNotFound() {
return (
<main>
<h1>That product is gone</h1>
<GioLink href="/shop">Back to the shop</GioLink>
</main>
);
}Good to know
- It works by throwing. A
try/catcharound the call swallows it: call it outside thetry, or rethrow what you did not expect. - Streaming. Called before a streamed page suspends, the answer is a real
404. Inside a suspended part (underloading.tsxor a<Suspense>), the200is already sent: React finishes that part in the browser, wherenotFound()shows the nearesterror.tsx. - URLs that match no route always get
app/not-found.tsx- they belong to no folder. - Static export.
gio exportskips pages that callnotFound()and writes no HTML for them. - Safe in the browser. The module has no Node imports, so a component that calls it may ship in a client bundle.
Related
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | Introduced: getServerSideProps, render, page actions and route.ts handlers, with per-folder not-found.tsx. |