# Custom 404 page and redirects with the Next.js App Router SDK

> Recipe: show a custom 404 page for URLs that have no Uniform route. Optionally, let authors manage the 404 content in Uniform. Learn how Uniform redirects work.

Source: https://docs.uniform.app/docs/sdk/nextjs-app-router/recipes/not-found

**Goal:** a URL with no Uniform route shows your own 404 page, with the status code `404`. Optionally, Uniform authors manage the content of the 404 page.

## Prerequisites

- An app with the composition route `app/uniform/[code]/page.tsx`. Refer to [Add the composition route](https://docs.uniform.app/docs/sdk/nextjs-app-router/tutorial#step-10-add-the-composition-route).
- For the Uniform-managed 404 page: the package `@uniformdev/canvas`, and a published composition with the slug `page-not-found`.

## How the SDK finds a missing page

The middleware makes no API calls, so it does not know if a route exists. It rewrites all requests to `app/uniform/[code]/page.tsx`. Then `UniformComposition` resolves the route and does one of these actions:

| Route API result | What `UniformComposition` does | Response |
| --- | --- | --- |
| A composition | Renders the composition. | `200` |
| A redirect with the status 301 or 308 | Calls `permanentRedirect()`. | `308` |
| A redirect with a different status | Calls `redirect()`. | `307` |
| No route, or an API error | Calls `notFound()`. | `404` |

`notFound()` from `next/navigation` renders the nearest `not-found.tsx` file. For `app/uniform/[code]`, this is usually `app/not-found.tsx`.

## Step 1: Add the 404 page

Make `app/not-found.tsx`:

`app/not-found.tsx`

```tsx
import Link from "next/link";

export default function NotFound() {
  return (
    <main>
      <h1>Page not found</h1>
      <p>We cannot find the page that you asked for.</p>
      <Link href="/">Go to the home page</Link>
    </main>
  );
}
```

- Next.js renders this file in the root layout.
- Next.js adds `<meta name="robots" content="noindex">` to the 404 page.
- The root `app/not-found.tsx` also handles URLs that match no route of your app.

## Step 2 (optional): Show 404 content from Uniform

Authors can manage the 404 content in a composition. `not-found.tsx` gets no props, so it gets no code from the middleware. Thus you make the page state on the page, and resolve the composition by its slug.

1. In Uniform, make a composition with the slug `page-not-found`. Do not attach it to a project map node, so that it has no URL of its own. Publish the composition.
2. Replace `app/not-found.tsx` with this file:

   `app/not-found.tsx`

   ```tsx
   import { CANVAS_PUBLISHED_STATE } from "@uniformdev/canvas";
   import {
     resolveCompositionBySlug,
     UniformContext,
     UniformResolvedComposition,
   } from "@uniformdev/next-app-router";
   import type { PageState } from "@uniformdev/next-app-router-shared";
   import Link from "next/link";
   import { resolveComponent } from "@/components/resolveComponent";

   // The page state of a published request. Set all fields.
   const pageState: PageState = {
     compositionState: CANVAS_PUBLISHED_STATE,
     routePath: "/page-not-found",
     keys: undefined,
     releaseId: undefined,
     defaultConsent: true, // the same value as defaultConsent in uniform.server.config.ts
     previewMode: undefined,
     locale: undefined,
   };

   export default async function NotFound() {
     const result = await resolveCompositionBySlug({ slug: "page-not-found", pageState });

     // No composition, or an API error: show a fallback.
     if (!result.route) {
       return (
         <main>
           <h1>Page not found</h1>
           <Link href="/">Go to the home page</Link>
         </main>
       );
     }

     return (
       <UniformContext result={result}>
         <UniformResolvedComposition result={result} resolveComponent={resolveComponent} />
       </UniformContext>
     );
   }
   ```
3. Set `defaultConsent` to the same value as `defaultConsent` in `uniform.server.config.ts`. Without a server configuration file, the SDK uses `true`. With a file that does not set `defaultConsent`, the SDK uses `false`.
4. Open a URL that does not exist, for example `/no-page-here`. Make sure that the page shows the Uniform content and that the status is `404`.

### How it works

- `not-found.tsx` can be an async server component. Thus it can get data before it renders.
- `PageState` has these fields:

  | Field | Value for the 404 page |
  | --- | --- |
  | `compositionState` | `CANVAS_PUBLISHED_STATE` (`64`): get the published composition. |
  | `routePath` | A path for analytics. The resolver also uses it as the matched route. |
  | `keys` | `undefined`. |
  | `releaseId` | `undefined`: get the content outside of a release. |
  | `defaultConsent` | The default consent of new visitors. The browser Context starts with this value. |
  | `previewMode` | `undefined`. |
  | `locale` | `undefined`, or a fixed locale. |
- `resolveCompositionBySlug` uses the Composition API, not the Route API. Thus it applies no redirects.
- `UniformContext` starts the Uniform Context in the browser. Render it one time on the page.
- A published composition uses the Next.js data cache. When you publish the composition again, the webhook revalidates its cache tags. Refer to [Caching](https://docs.uniform.app/docs/sdk/nextjs-app-router/caching#webhooks).

### Limits

- **Draft content:** the 404 page always shows the published composition, also in draft mode.
- **Locale:** `not-found.tsx` gets no route parameters. It does not know the locale of the URL. Use a fixed locale, or put a `not-found.tsx` file in each locale segment.
- **Personalization:** the page state has no `edgeMode`. Thus the browser chooses the variants of personalizations and A/B tests after hydration.
- **Status after the response starts:** Next.js sends the status `404` only when it calls `notFound()` before the response streams. After that, the status stays `200`, and Next.js adds the `noindex` tag.

## Uniform redirects

Authors make redirects in Uniform. The SDK applies them on the page, not in the middleware:

- The Route API applies redirects only to published content. In draft and editor states, it ignores them.
- `UniformComposition` calls `requireComposition`. For a redirect with the status 301 or 308, it calls `permanentRedirect()`, and Next.js sends `308`. For other status codes, it calls `redirect()`, and Next.js sends `307`.
- Next.js does not keep the exact status code that the author set.
- If the response streams already, for example below `loading.tsx`, Next.js redirects in the browser with a meta tag.
- `resolveCompositionById` and `resolveCompositionBySlug` apply no redirects.

If your page resolves the route itself, call `requireComposition(result)` to apply the redirect or the 404. To apply only the redirect, and render the page when there is no composition, use this code:

```tsx
import { requireComposition, type ResolvedRouteResult } from "@uniformdev/next-app-router";

export function applyUniformRedirect(result: ResolvedRouteResult) {
  if (result.route?.type === "redirect") {
    requireComposition(result); // calls redirect() or permanentRedirect()
  }
  return result;
}
```

For more information, refer to [Resolve compositions on a page](https://docs.uniform.app/docs/sdk/nextjs-app-router/resolving-compositions).
