not-found.tsx
The 404 page for a folder and everything below it, shown when a page calls notFound(). The one in app/ also answers unmatched URLs.
app/not-found.tsx
import React from 'react';
import type { Metadata } from '@gio.js/core';
export const metadata: Metadata = { title: 'Page not found' };
export default function NotFound() {
return (
<main>
<h1>Page not found</h1>
<p>This page does not exist or was moved.</p>
<a href="/">Go home</a>
</main>
);
}Reference
File name and location
not-found.tsx, not-found.jsx or not-found.js, in app/ or any folder below it (route groups and dynamic folders included, private folders never).
Props
None (NotFoundPageProps is an empty object). The navigation hooks still work while it renders: usePathname() gives the requested path and useParams() the params of the route that called notFound() ({} for an unmatched URL).
Module exports
default (required), and optionally metadata or generateMetadata, which receives the failed route's params.
When it is shown
| Cause | Which not-found.tsx |
|---|---|
notFound() in getServerSideProps, a page action or during the render | The nearest one at or above the page's folder |
return { notFound: true } from getServerSideProps | The same |
A URL no page, route.ts or public/ file answers | app/not-found.tsx only: an unmatched URL belongs to no folder |
Behavior
- The status is
404and the response is never cached, even on a page withrevalidate: a 404 can depend on whatgetServerSidePropsread, and a cached one would outlive the content appearing. - It renders inside the layouts of its own folder, never those of the page that called
notFound(). If those layouts throw, thenot-found.tsxof the folder above is tried. With none left, the server answers with a built-in 404 page. - It is server-only HTML: it does not hydrate, so keep it free of state and effects. Links in it are plain links.
- In a
route.ts,notFound()answers a JSON{"error":"Not Found"}with status404instead.
Examples
A 404 for one section
app/blog/[slug]/page.tsx
import React from 'react';
import { notFound } from '@gio.js/core';
import type { GetServerSideProps, InferPageProps } from '@gio.js/core';
import { findPost } from '../../../lib/posts';
export const getServerSideProps: GetServerSideProps<{ title: string }, '/blog/:slug'> = async (ctx) => {
const post = await findPost(ctx.params.slug);
if (post === undefined) notFound();
return { props: { title: post.title } };
};
export default function Post({ title }: InferPageProps<typeof getServerSideProps>) {
return <h1>{title}</h1>;
}app/blog/not-found.tsx
import React from 'react';
import { usePathname } from '@gio.js/react';
export default function PostNotFound() {
const pathname = usePathname();
return (
<section>
<h1>No post at {pathname}</h1>
<a href="/blog">All posts</a>
</section>
);
}/blog/missing answers 404 with the blog's own message, inside app/blog/layout.tsx if there is one. /nowhere still gets app/not-found.tsx.
Good to know
gio exportwritesapp/not-found.tsxtoout/404.html, which static hosts serve for unknown paths. Without one it writes the built-in 404 page there.- A
public/file wins over a page at the same path, so it never reaches the 404 either. - If a streamed page calls
notFound()after it has suspended inside a loading.tsx boundary, the200is already sent; the browser then shows the nearest error.tsx. Call it before suspending. - On client navigation the 404 page renders in place like any GioJS page.
Related
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | Works in any folder: notFound() and { notFound: true } use the nearest one. metadata exports. 404s are never cached. |
v0.1.0-beta.5 | Introduced for app/not-found.tsx: unmatched URLs render it with status 404. |