|
| 1 | +--- |
| 2 | +title: next/root-params |
| 3 | +description: API Reference for the next/root-params module that provides access to root-level route parameters. |
| 4 | +version: canary |
| 5 | +--- |
| 6 | + |
| 7 | +The `next/root-params` module provides individual getter functions for accessing root-level route parameters in **Server Components**. This experimental feature enables type-safe access to parameters defined in root layout segments. |
| 8 | + |
| 9 | +## Configuration |
| 10 | + |
| 11 | +To use `next/root-params`, you must enable the experimental flag in your `next.config.js`: |
| 12 | + |
| 13 | +```js filename="next.config.js" |
| 14 | +/** @type {import('next').NextConfig} */ |
| 15 | +const nextConfig = { |
| 16 | + experimental: { |
| 17 | + rootParams: true, |
| 18 | + }, |
| 19 | +} |
| 20 | + |
| 21 | +module.exports = nextConfig |
| 22 | +``` |
| 23 | + |
| 24 | +## Usage |
| 25 | + |
| 26 | +The module automatically generates individual getter functions for each root parameter found in your app directory structure. Root parameters are dynamic segments that appear in the path leading up to the first `layout.tsx` file. |
| 27 | + |
| 28 | +```tsx filename="app/[lang]/[locale]/page.tsx" |
| 29 | +import { lang, locale } from 'next/root-params' |
| 30 | + |
| 31 | +export default async function Page() { |
| 32 | + return ( |
| 33 | + <div> |
| 34 | + <h1>Language: {await lang()}</h1> |
| 35 | + <p>Locale: {await locale()}</p> |
| 36 | + </div> |
| 37 | + ) |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +```tsx filename="app/[lang]/[locale]/layout.tsx" |
| 42 | +import { lang, locale } from 'next/root-params' |
| 43 | + |
| 44 | +export default async function Layout({ children }) { |
| 45 | + return ( |
| 46 | + <html lang={`${await lang()}-${await locale()}`}> |
| 47 | + <body>{children}</body> |
| 48 | + </html> |
| 49 | + ) |
| 50 | +} |
| 51 | + |
| 52 | +export async function generateStaticParams() { |
| 53 | + return [ |
| 54 | + { lang: 'en', locale: 'us' }, |
| 55 | + { lang: 'fr', locale: 'ca' }, |
| 56 | + ] |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +## Root Parameters vs Dynamic Parameters |
| 61 | + |
| 62 | +Root parameters are different from regular dynamic parameters. They are only the parameters that exist in the path segments up to the first layout file: |
| 63 | + |
| 64 | +File structure: |
| 65 | + |
| 66 | +- `app/[lang]/[locale]/layout.tsx` - Root layout |
| 67 | +- `app/[lang]/[locale]/posts/[slug]/page.tsx` |
| 68 | + |
| 69 | +```tsx |
| 70 | +import { lang, locale } from 'next/root-params' // Available |
| 71 | +// import { slug } from 'next/root-params' // Not available - 'slug' is not a root param |
| 72 | + |
| 73 | +export default async function PostPage({ |
| 74 | + params, |
| 75 | +}: { |
| 76 | + params: { slug: string } |
| 77 | +}) { |
| 78 | + const { slug } = await params // Access non-root params this way |
| 79 | + |
| 80 | + return ( |
| 81 | + <div> |
| 82 | + <h1>Language: {await lang()}</h1> |
| 83 | + <h2>Locale: {await locale()}</h2> |
| 84 | + <h3>Post: {slug}</h3> |
| 85 | + </div> |
| 86 | + ) |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +## Multiple Root Layouts |
| 91 | + |
| 92 | +Applications can have multiple root layouts with different parameter structures: |
| 93 | + |
| 94 | +File structure: |
| 95 | + |
| 96 | +- `app/dashboard/[id]/layout.tsx` - Root layout with 'id' param |
| 97 | +- `app/marketing/layout.tsx` - Root layout with no params |
| 98 | + |
| 99 | +```tsx |
| 100 | +// In dashboard pages: |
| 101 | +import { id } from 'next/root-params' |
| 102 | + |
| 103 | +export default async function DashboardPage() { |
| 104 | + const dashboardId = await id() // Type: string | undefined |
| 105 | + return <div>Dashboard ID: {dashboardId}</div> |
| 106 | +} |
| 107 | + |
| 108 | +// In marketing pages: |
| 109 | +import { id } from 'next/root-params' |
| 110 | + |
| 111 | +export default async function MarketingPage() { |
| 112 | + const dashboardId = await id() // Type: string | undefined, returns undefined |
| 113 | + return <div>Marketing Page</div> |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +When you have multiple root layouts with different parameters, the getter functions are typed to account for all possible routes. Since `id` doesn't exist in the marketing layout, `await id()` has the type `string | undefined` to reflect that it may return `undefined` when accessed from routes without that parameter. |
| 118 | + |
| 119 | +## Catch-all and Optional Catch-all Routes |
| 120 | + |
| 121 | +Root parameters work with catch-all and optional catch-all routes: |
| 122 | + |
| 123 | +```tsx filename="app/docs/[...path]/layout.tsx" |
| 124 | +import { path } from 'next/root-params' |
| 125 | + |
| 126 | +export default async function DocsLayout({ children }) { |
| 127 | + const pathSegments = await path() // string[] | undefined |
| 128 | + return ( |
| 129 | + <div> |
| 130 | + <nav>Path: {pathSegments?.join(' / ')}</nav> |
| 131 | + {children} |
| 132 | + </div> |
| 133 | + ) |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +```tsx filename="app/optional/[[...segments]]/page.tsx" |
| 138 | +import { segments } from 'next/root-params' |
| 139 | + |
| 140 | +export default async function OptionalPage() { |
| 141 | + const pathSegments = await segments() // string[] | undefined |
| 142 | + return ( |
| 143 | + <div> |
| 144 | + {pathSegments ? ( |
| 145 | + <p>Segments: {pathSegments.join(', ')}</p> |
| 146 | + ) : ( |
| 147 | + <p>No segments</p> |
| 148 | + )} |
| 149 | + </div> |
| 150 | + ) |
| 151 | +} |
| 152 | +``` |
| 153 | + |
| 154 | +## Return Values |
| 155 | + |
| 156 | +Each root parameter getter returns a `Promise` that resolves to: |
| 157 | + |
| 158 | +- `string` for regular dynamic segments like `[id]` |
| 159 | +- `string[]` for catch-all segments like `[...path]` |
| 160 | +- `string[] | undefined` for optional catch-all segments like `[[...path]]` |
| 161 | +- `undefined` if the parameter doesn't exist in the current route's root layout |
| 162 | + |
| 163 | +## Restrictions |
| 164 | + |
| 165 | +### Server Components Only |
| 166 | + |
| 167 | +Root parameter getters can only be used in **Server Components**: |
| 168 | + |
| 169 | +```tsx |
| 170 | +// This will cause a build error |
| 171 | +'use client' |
| 172 | + |
| 173 | +import { lang } from 'next/root-params' // Error: Cannot import in Client Component |
| 174 | +
|
| 175 | +export default function ClientComponent() { |
| 176 | + // ... |
| 177 | +} |
| 178 | +``` |
| 179 | + |
| 180 | +### Not Available in Server Actions |
| 181 | + |
| 182 | +```tsx |
| 183 | +import { lang } from 'next/root-params' |
| 184 | + |
| 185 | +// This will cause a runtime error |
| 186 | +async function serverAction() { |
| 187 | + 'use server' |
| 188 | + const language = await lang() // Error: Not supported in Server Actions |
| 189 | +} |
| 190 | +``` |
| 191 | + |
| 192 | +### Not Available in Route Handlers |
| 193 | + |
| 194 | +```tsx filename="app/[lang]/api/route.ts" |
| 195 | +import { lang } from 'next/root-params' |
| 196 | + |
| 197 | +// This will cause a runtime error (support planned for future) |
| 198 | +export async function GET() { |
| 199 | + const language = await lang() // Error: Not supported in Route Handlers yet |
| 200 | + return Response.json({ lang: language }) |
| 201 | +} |
| 202 | +``` |
| 203 | + |
| 204 | +## Version History |
| 205 | + |
| 206 | +| Version | Changes | |
| 207 | +| --------- | ------------------------------------------------------ | |
| 208 | +| `v15.1.0` | `next/root-params` introduced as experimental feature. | |
0 commit comments