From cd277ca74de00ccda008f5f2a87a26bf39dbd8df Mon Sep 17 00:00:00 2001 From: David Alexandru Ilie Date: Sun, 9 Aug 2026 00:35:10 +0200 Subject: [PATCH] docs: clarify not-found with dynamic root layouts --- .../03-file-conventions/not-found.mdx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/01-app/03-api-reference/03-file-conventions/not-found.mdx b/docs/01-app/03-api-reference/03-file-conventions/not-found.mdx index edcc3692112b..ecf667928527 100644 --- a/docs/01-app/03-api-reference/03-file-conventions/not-found.mdx +++ b/docs/01-app/03-api-reference/03-file-conventions/not-found.mdx @@ -57,6 +57,38 @@ The `global-not-found.js` file bypasses your app's normal rendering, which means - Your app has multiple root layouts (e.g. `app/(admin)/layout.tsx` and `app/(shop)/layout.tsx`), so there's no single layout to compose a global 404 from. - Your root layout is defined using top-level dynamic segments (e.g. `app/[country]/layout.tsx`), which makes composing a consistent 404 page harder. +### Root layouts in a dynamic segment + +The root `app/not-found.js` file also provides UI for globally unmatched URLs. If your only root layout is nested under a dynamic segment, such as `app/[lang]/layout.js`, Next.js cannot compose that global not-found route with the dynamic root layout because no `lang` value has been selected. + +You can use `global-not-found.js` for a routing-level 404, or add a catch-all route inside the dynamic segment when the not-found UI needs to inherit the layout: + +| | `global-not-found.js` | Catch-all route with `notFound()` | +| ------------------------------------ | ------------------------------------------------- | ------------------------------------------------------------------- | +| **Inherits the dynamic root layout** | No. Import shared styles and components directly. | Yes. The dynamic segment is resolved before `notFound()` is called. | +| **Handles** | All globally unmatched URLs. | URLs that match the catch-all route. | +| **HTTP status** | `404` | `404` for non-streamed responses and `200` for streamed responses. | + +To use the localized pattern, remove the root `app/not-found.js` file, place the not-found UI at `app/[lang]/not-found.js`, and add a catch-all page that calls `notFound()`: + +```tsx filename="app/[lang]/[...rest]/page.tsx" switcher +import { notFound } from 'next/navigation' + +export default function UnmatchedRoute() { + notFound() +} +``` + +```jsx filename="app/[lang]/[...rest]/page.js" switcher +import { notFound } from 'next/navigation' + +export default function UnmatchedRoute() { + notFound() +} +``` + +Use `global-not-found.js` when you need a global hard `404`. Use a catch-all route when rendering the not-found UI inside the dynamic root layout is more important. For streamed responses, Next.js injects a `noindex` meta tag even though the status code is `200`. See [Status Codes](/docs/app/api-reference/file-conventions/loading#status-codes) for details. + To enable it, add the `globalNotFound` flag in `next.config.ts`: ```tsx filename="next.config.ts"