GioJSdocs
On this page

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

CauseWhich not-found.tsx
notFound() in getServerSideProps, a page action or during the renderThe nearest one at or above the page's folder
return { notFound: true } from getServerSidePropsThe same
A URL no page, route.ts or public/ file answersapp/not-found.tsx only: an unmatched URL belongs to no folder

Behavior

  • The status is 404 and the response is never cached, even on a page with revalidate: a 404 can depend on what getServerSideProps read, 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, the not-found.tsx of 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 status 404 instead.

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 export writes app/not-found.tsx to out/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, the 200 is 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.

Version history

VersionChanges
v0.1.0-beta.8Works in any folder: notFound() and { notFound: true } use the nearest one. metadata exports. 404s are never cached.
v0.1.0-beta.5Introduced for app/not-found.tsx: unmatched URLs render it with status 404.