mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
<!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change(s) that you're making: ## For Contributors ### Improving Documentation - Run `pnpm prettier-fix` to fix formatting issues before opening the PR. - Read the Docs Contribution Guide to ensure your contribution follows the docs guidelines: https://nextjs.org/docs/community/contribution-guide ### Fixing a bug - Related issues linked using `fixes #number` - Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Adding a feature - Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas) - Related issues/discussions are linked using `fixes #number` - e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) - Documentation added - Telemetry added. In case of a feature if it's used or not. - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ## For Maintainers - Minimal description (aim for explaining to someone not on the team to understand the PR) - When linking to a Slack thread, you might want to share details of the conclusion - Link both the Linear (Fixes NEXT-xxx) and the GitHub issues - Add review comments if necessary to explain to the reviewer the logic behind a change ### What? This PR updates the documentation to clarify the interaction between `loading.js` and `layout.js`. ### Why? To address a common point of confusion and potential blocking navigation issues when layouts are dynamic. It provides clear guidance on how `loading.js` behaves relative to layouts and how to use `<Suspense>` for dynamic layout parts. ### How? - Added a "Good to know" callout in `loading.mdx` explaining that `loading.js` does not wrap the layout. - Introduced a new "Interaction with `loading.js`" section under Caveats in `layout.mdx`, including an explanation and a code example for wrapping dynamic layout components with `<Suspense>`. - Added a brief "Good to know" callout in `fetching-data.mdx` for consistency. Closes NEXT- Fixes # --> --- [Slack Thread](https://vercel.slack.com/archives/C05KYT5S9FF/p1772239839900129?thread_ts=1772239839.900129&cid=C05KYT5S9FF) <p><a href="https://cursor.com/agents/bc-d69fb736-2a00-5f68-9e28-9b31c194f12a"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-d69fb736-2a00-5f68-9e28-9b31c194f12a"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </p> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Joseph Chamochumbi <[email protected]> Co-authored-by: Delba de Oliveira <[email protected]>
732 lines
20 KiB
Plaintext
732 lines
20 KiB
Plaintext
---
|
|
title: layout.js
|
|
description: API reference for the layout.js file.
|
|
---
|
|
|
|
The `layout` file is used to define a layout in your Next.js application.
|
|
|
|
```tsx filename="app/dashboard/layout.tsx" switcher
|
|
export default function DashboardLayout({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode
|
|
}) {
|
|
return <section>{children}</section>
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/dashboard/layout.js" switcher
|
|
export default function DashboardLayout({ children }) {
|
|
return <section>{children}</section>
|
|
}
|
|
```
|
|
|
|
In the [component hierarchy](/docs/app/getting-started/project-structure#component-hierarchy), `layout.js` is the outermost component in a route segment. It wraps `template.js`, `error.js`, `loading.js`, `not-found.js`, and `page.js`.
|
|
|
|
A **root layout** is the top-most layout in the root `app` directory. It is used to define the `<html>` and `<body>` tags and other globally shared UI.
|
|
|
|
```tsx filename="app/layout.tsx" switcher
|
|
export default function RootLayout({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode
|
|
}) {
|
|
return (
|
|
<html lang="en">
|
|
<body>{children}</body>
|
|
</html>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/layout.js" switcher
|
|
export default function RootLayout({ children }) {
|
|
return (
|
|
<html lang="en">
|
|
<body>{children}</body>
|
|
</html>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Reference
|
|
|
|
### Props
|
|
|
|
#### `children` (required)
|
|
|
|
Layout components should accept and use a `children` prop. During rendering, `children` will be populated with the route segments the layout is wrapping. These will primarily be the component of a child [Layout](/docs/app/api-reference/file-conventions/page) (if it exists) or [Page](/docs/app/api-reference/file-conventions/page), but could also be other special files like [Loading](/docs/app/api-reference/file-conventions/loading) or [Error](/docs/app/getting-started/error-handling) when applicable.
|
|
|
|
#### `params` (optional)
|
|
|
|
A promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) object from the root segment down to that layout.
|
|
|
|
```tsx filename="app/dashboard/[team]/layout.tsx" switcher
|
|
export default async function Layout({
|
|
children,
|
|
params,
|
|
}: {
|
|
children: React.ReactNode
|
|
params: Promise<{ team: string }>
|
|
}) {
|
|
const { team } = await params
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/dashboard/[team]/layout.js" switcher
|
|
export default async function Layout({ children, params }) {
|
|
const { team } = await params
|
|
}
|
|
```
|
|
|
|
| Example Route | URL | `params` |
|
|
| --------------------------------- | -------------- | ---------------------------------- |
|
|
| `app/dashboard/[team]/layout.js` | `/dashboard/1` | `Promise<{ team: '1' }>` |
|
|
| `app/shop/[tag]/[item]/layout.js` | `/shop/1/2` | `Promise<{ tag: '1', item: '2' }>` |
|
|
| `app/blog/[...slug]/layout.js` | `/blog/1/2` | `Promise<{ slug: ['1', '2'] }>` |
|
|
|
|
- Since the `params` prop is a promise. You must use `async/await` or React's [`use`](https://react.dev/reference/react/use) function to access the values.
|
|
- In version 14 and earlier, `params` was a synchronous prop. To help with backwards compatibility, you can still access it synchronously in Next.js 15, but this behavior will be deprecated in the future.
|
|
|
|
### Layout Props Helper
|
|
|
|
You can type layouts with `LayoutProps` to get a strongly typed `params` and named slots inferred from your directory structure. `LayoutProps` is a globally available helper.
|
|
|
|
```tsx filename="app/dashboard/layout.tsx"
|
|
export default function Layout(props: LayoutProps<'/dashboard'>) {
|
|
return (
|
|
<section>
|
|
{props.children}
|
|
{/* If you have app/dashboard/@analytics, it appears as a typed slot: */}
|
|
{/* {props.analytics} */}
|
|
</section>
|
|
)
|
|
}
|
|
```
|
|
|
|
> **Good to know**:
|
|
>
|
|
> - Types are generated during `next dev`, `next build` or `next typegen`.
|
|
> - After type generation, the `LayoutProps` helper is globally available. It doesn't need to be imported.
|
|
|
|
### Root Layout
|
|
|
|
The `app` directory **must** include a **root layout**, which is the top-most layout in the root `app` directory. Typically, the root layout is `app/layout.js`.
|
|
|
|
```tsx filename="app/layout.tsx" switcher
|
|
export default function RootLayout({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode
|
|
}) {
|
|
return (
|
|
<html>
|
|
<body>{children}</body>
|
|
</html>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/layout.js" switcher
|
|
export default function RootLayout({ children }) {
|
|
return (
|
|
<html>
|
|
<body>{children}</body>
|
|
</html>
|
|
)
|
|
}
|
|
```
|
|
|
|
- The root layout **must** define `<html>` and `<body>` tags.
|
|
- You should **not** manually add `<head>` tags such as `<title>` and `<meta>` to root layouts. Instead, you should use the [Metadata API](/docs/app/getting-started/metadata-and-og-images) which automatically handles advanced requirements such as streaming and de-duplicating `<head>` elements.
|
|
- You can create **multiple root layouts**. Any layout without a `layout.js` above it is a root layout. Two common approaches:
|
|
- Using [route groups](/docs/app/api-reference/file-conventions/route-groups) like `app/(shop)/layout.js` and `app/(marketing)/layout.js`
|
|
- Omitting `app/layout.js` so layouts in subdirectories like `app/dashboard/layout.js` and `app/blog/layout.js` each become root layouts for their respective directories.
|
|
- Navigating **across multiple root layouts** will cause a **full page load** (as opposed to a client-side navigation).
|
|
- The root layout can be under a **dynamic segment**, for example when implementing [internationalization](/docs/app/guides/internationalization) with `app/[lang]/layout.js`.
|
|
|
|
## Caveats
|
|
|
|
### Request Object
|
|
|
|
Layouts are cached in the client during navigation to avoid unnecessary server requests.
|
|
|
|
[Layouts](/docs/app/api-reference/file-conventions/layout) do not rerender. They can be cached and reused to avoid unnecessary computation when navigating between pages. By restricting layouts from accessing the raw request, Next.js can prevent the execution of potentially slow or expensive user code within the layout, which could negatively impact performance.
|
|
|
|
To access the request object, you can use [`headers`](/docs/app/api-reference/functions/headers) and [`cookies`](/docs/app/api-reference/functions/cookies) APIs in [Server Components](/docs/app/getting-started/server-and-client-components) and Functions.
|
|
|
|
```tsx filename="app/shop/layout.tsx" switcher
|
|
import { cookies } from 'next/headers'
|
|
|
|
export default async function Layout({ children }) {
|
|
const cookieStore = await cookies()
|
|
const theme = cookieStore.get('theme')
|
|
return '...'
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/shop/layout.js" switcher
|
|
import { cookies } from 'next/headers'
|
|
|
|
export default async function Layout({ children }) {
|
|
const cookieStore = await cookies()
|
|
const theme = cookieStore.get('theme')
|
|
return '...'
|
|
}
|
|
```
|
|
|
|
### Query params
|
|
|
|
Layouts do not rerender on navigation, so they cannot access search params which would otherwise become stale.
|
|
|
|
To access updated query parameters, you can use the Page [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) prop, or read them inside a Client Component using the [`useSearchParams`](/docs/app/api-reference/functions/use-search-params) hook. Since Client Components re-render on navigation, they have access to the latest query parameters.
|
|
|
|
```tsx filename="app/ui/search.tsx" switcher
|
|
'use client'
|
|
|
|
import { useSearchParams } from 'next/navigation'
|
|
|
|
export default function Search() {
|
|
const searchParams = useSearchParams()
|
|
|
|
const search = searchParams.get('search')
|
|
|
|
return '...'
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/ui/search.js" switcher
|
|
'use client'
|
|
|
|
import { useSearchParams } from 'next/navigation'
|
|
|
|
export default function Search() {
|
|
const searchParams = useSearchParams()
|
|
|
|
const search = searchParams.get('search')
|
|
|
|
return '...'
|
|
}
|
|
```
|
|
|
|
```tsx filename="app/shop/layout.tsx" switcher
|
|
import Search from '@/app/ui/search'
|
|
|
|
export default function Layout({ children }) {
|
|
return (
|
|
<>
|
|
<Search />
|
|
{children}
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/shop/layout.js" switcher
|
|
import Search from '@/app/ui/search'
|
|
|
|
export default function Layout({ children }) {
|
|
return (
|
|
<>
|
|
<Search />
|
|
{children}
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Pathname
|
|
|
|
Layouts do not re-render on navigation, so they do not access pathname which would otherwise become stale.
|
|
|
|
To access the current pathname, you can read it inside a Client Component using the [`usePathname`](/docs/app/api-reference/functions/use-pathname) hook. Since Client Components re-render during navigation, they have access to the latest pathname.
|
|
|
|
```tsx filename="app/ui/breadcrumbs.tsx" switcher
|
|
'use client'
|
|
|
|
import { usePathname } from 'next/navigation'
|
|
|
|
// Simplified breadcrumbs logic
|
|
export default function Breadcrumbs() {
|
|
const pathname = usePathname()
|
|
const segments = pathname.split('/')
|
|
|
|
return (
|
|
<nav>
|
|
{segments.map((segment, index) => (
|
|
<span key={index}>
|
|
{' > '}
|
|
{segment}
|
|
</span>
|
|
))}
|
|
</nav>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/ui/breadcrumbs.js" switcher
|
|
'use client'
|
|
|
|
import { usePathname } from 'next/navigation'
|
|
|
|
// Simplified breadcrumbs logic
|
|
export default function Breadcrumbs() {
|
|
const pathname = usePathname()
|
|
const segments = pathname.split('/')
|
|
|
|
return (
|
|
<nav>
|
|
{segments.map((segment, index) => (
|
|
<span key={index}>
|
|
{' > '}
|
|
{segment}
|
|
</span>
|
|
))}
|
|
</nav>
|
|
)
|
|
}
|
|
```
|
|
|
|
```tsx filename="app/docs/layout.tsx" switcher
|
|
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'
|
|
|
|
export default function Layout({ children }) {
|
|
return (
|
|
<>
|
|
<Breadcrumbs />
|
|
<main>{children}</main>
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/docs/layout.js" switcher
|
|
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'
|
|
|
|
export default function Layout({ children }) {
|
|
return (
|
|
<>
|
|
<Breadcrumbs />
|
|
<main>{children}</main>
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Interaction with `loading.js`
|
|
|
|
Because `loading.js` sits below `layout.js` in the [component hierarchy](/docs/app/getting-started/project-structure#component-hierarchy), it cannot show a fallback for uncached or runtime data access in the layout itself, such as calling [`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), or making uncached fetches.
|
|
|
|
The behavior depends on whether [Cache Components](/docs/app/getting-started/caching) is enabled:
|
|
|
|
- **Without Cache Components:** The navigation will block until the layout finishes rendering, and the `loading.js` fallback will not be shown.
|
|
- **With Cache Components:** `loading.js` is treated as a regular `<Suspense>` boundary rather than a special prefetch marker. Uncached or runtime data access in the layout must be explicitly wrapped in its own `<Suspense>` boundary, otherwise Next.js guides you with a build-time error. The static shell streams immediately, and the uncached content swaps in as it resolves.
|
|
|
|
In both cases, to ensure instant navigation, either:
|
|
|
|
- Wrap runtime data access in your layout in its own `<Suspense>` boundary with a fallback.
|
|
- Move uncached data fetching from `layout.js` into `page.js` where `loading.js` can show a fallback.
|
|
|
|
```tsx filename="app/dashboard/layout.tsx" switcher highlight={8-10}
|
|
import { Suspense } from 'react'
|
|
import { NavSkeleton } from './nav-skeleton'
|
|
import { DashboardNav } from './dashboard-nav'
|
|
|
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<>
|
|
<Suspense fallback={<NavSkeleton />}>
|
|
<DashboardNav />
|
|
</Suspense>
|
|
<main>{children}</main>
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/dashboard/layout.js" switcher highlight={8-10}
|
|
import { Suspense } from 'react'
|
|
import { NavSkeleton } from './nav-skeleton'
|
|
import { DashboardNav } from './dashboard-nav'
|
|
|
|
export default function Layout({ children }) {
|
|
return (
|
|
<>
|
|
<Suspense fallback={<NavSkeleton />}>
|
|
<DashboardNav />
|
|
</Suspense>
|
|
<main>{children}</main>
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Fetching Data
|
|
|
|
Layouts cannot pass data to their `children`. However, you can fetch the same data in a route more than once, and use React [`cache`](https://react.dev/reference/react/cache) to dedupe the requests without affecting performance.
|
|
|
|
Alternatively, when using [`fetch`](/docs/app/api-reference/functions/fetch)in Next.js, requests are automatically deduped.
|
|
|
|
```tsx filename="app/lib/data.ts" switcher
|
|
export async function getUser(id: string) {
|
|
const res = await fetch(`https://.../users/${id}`)
|
|
return res.json()
|
|
}
|
|
```
|
|
|
|
```tsx filename="app/dashboard/layout.tsx" switcher
|
|
import { getUser } from '@/app/lib/data'
|
|
import { UserName } from '@/app/ui/user-name'
|
|
|
|
export default async function Layout({ children }) {
|
|
const user = await getUser('1')
|
|
|
|
return (
|
|
<>
|
|
<nav>
|
|
{/* ... */}
|
|
<UserName user={user.name} />
|
|
</nav>
|
|
{children}
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/dashboard/layout.js" switcher
|
|
import { getUser } from '@/app/lib/data'
|
|
import { UserName } from '@/app/ui/user-name'
|
|
|
|
export default async function Layout({ children }) {
|
|
const user = await getUser('1')
|
|
|
|
return (
|
|
<>
|
|
<nav>
|
|
{/* ... */}
|
|
<UserName user={user.name} />
|
|
</nav>
|
|
{children}
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
```tsx filename="app/dashboard/page.tsx" switcher
|
|
import { getUser } from '@/app/lib/data'
|
|
import { UserName } from '@/app/ui/user-name'
|
|
|
|
export default async function Page() {
|
|
const user = await getUser('1')
|
|
|
|
return (
|
|
<div>
|
|
<h1>Welcome {user.name}</h1>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/dashboard/page.js" switcher
|
|
import { getUser } from '@/app/lib/data'
|
|
import { UserName } from '@/app/ui/user-name'
|
|
|
|
export default async function Page() {
|
|
const user = await getUser('1')
|
|
|
|
return (
|
|
<div>
|
|
<h1>Welcome {user.name}</h1>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Accessing child segments
|
|
|
|
Layouts do not have access to the route segments below itself. To access all route segments, you can use [`useSelectedLayoutSegment`](/docs/app/api-reference/functions/use-selected-layout-segment) or [`useSelectedLayoutSegments`](/docs/app/api-reference/functions/use-selected-layout-segments) in a Client Component.
|
|
|
|
```tsx filename="app/ui/nav-link.tsx" switcher
|
|
'use client'
|
|
|
|
import Link from 'next/link'
|
|
import { useSelectedLayoutSegment } from 'next/navigation'
|
|
|
|
export default function NavLink({
|
|
slug,
|
|
children,
|
|
}: {
|
|
slug: string
|
|
children: React.ReactNode
|
|
}) {
|
|
const segment = useSelectedLayoutSegment()
|
|
const isActive = slug === segment
|
|
|
|
return (
|
|
<Link
|
|
href={`/blog/${slug}`}
|
|
// Change style depending on whether the link is active
|
|
style={{ fontWeight: isActive ? 'bold' : 'normal' }}
|
|
>
|
|
{children}
|
|
</Link>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/ui/nav-link.js" switcher
|
|
'use client'
|
|
|
|
import Link from 'next/link'
|
|
import { useSelectedLayoutSegment } from 'next/navigation'
|
|
|
|
export default function NavLinks({ slug, children }) {
|
|
const segment = useSelectedLayoutSegment()
|
|
const isActive = slug === segment
|
|
|
|
return (
|
|
<Link
|
|
href={`/blog/${slug}`}
|
|
style={{ fontWeight: isActive ? 'bold' : 'normal' }}
|
|
>
|
|
{children}
|
|
</Link>
|
|
)
|
|
}
|
|
```
|
|
|
|
```tsx filename="app/blog/layout.tsx" switcher
|
|
import { NavLink } from './nav-link'
|
|
import getPosts from './get-posts'
|
|
|
|
export default async function Layout({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode
|
|
}) {
|
|
const featuredPosts = await getPosts()
|
|
return (
|
|
<div>
|
|
{featuredPosts.map((post) => (
|
|
<div key={post.id}>
|
|
<NavLink slug={post.slug}>{post.title}</NavLink>
|
|
</div>
|
|
))}
|
|
<div>{children}</div>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/blog/layout.js" switcher
|
|
import { NavLink } from './nav-link'
|
|
import getPosts from './get-posts'
|
|
|
|
export default async function Layout({ children }) {
|
|
const featuredPosts = await getPosts()
|
|
return (
|
|
<div>
|
|
{featuredPosts.map((post) => (
|
|
<div key={post.id}>
|
|
<NavLink slug={post.slug}>{post.title}</NavLink>
|
|
</div>
|
|
))}
|
|
<div>{children}</div>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Examples
|
|
|
|
### Metadata
|
|
|
|
You can modify the `<head>` HTML elements such as `title` and `meta` using the [`metadata` object](/docs/app/api-reference/functions/generate-metadata#the-metadata-object) or [`generateMetadata` function](/docs/app/api-reference/functions/generate-metadata#generatemetadata-function).
|
|
|
|
```tsx filename="app/layout.tsx" switcher
|
|
import type { Metadata } from 'next'
|
|
|
|
export const metadata: Metadata = {
|
|
title: 'Next.js',
|
|
}
|
|
|
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
return '...'
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/layout.js" switcher
|
|
export const metadata = {
|
|
title: 'Next.js',
|
|
}
|
|
|
|
export default function Layout({ children }) {
|
|
return '...'
|
|
}
|
|
```
|
|
|
|
> **Good to know**: You should **not** manually add `<head>` tags such as `<title>` and `<meta>` to root layouts. Instead, use the [Metadata APIs](/docs/app/api-reference/functions/generate-metadata) which automatically handles advanced requirements such as streaming and de-duplicating `<head>` elements.
|
|
|
|
### Active Nav Links
|
|
|
|
You can use the [`usePathname`](/docs/app/api-reference/functions/use-pathname) hook to determine if a nav link is active.
|
|
|
|
Since `usePathname` is a client hook, you need to extract the nav links into a Client Component, which can be imported into your layout:
|
|
|
|
```tsx filename="app/ui/nav-links.tsx" switcher
|
|
'use client'
|
|
|
|
import { usePathname } from 'next/navigation'
|
|
import Link from 'next/link'
|
|
|
|
export function NavLinks() {
|
|
const pathname = usePathname()
|
|
|
|
return (
|
|
<nav>
|
|
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
|
|
Home
|
|
</Link>
|
|
|
|
<Link
|
|
className={`link ${pathname === '/about' ? 'active' : ''}`}
|
|
href="/about"
|
|
>
|
|
About
|
|
</Link>
|
|
</nav>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/ui/nav-links.js" switcher
|
|
'use client'
|
|
|
|
import { usePathname } from 'next/navigation'
|
|
import Link from 'next/link'
|
|
|
|
export function Links() {
|
|
const pathname = usePathname()
|
|
|
|
return (
|
|
<nav>
|
|
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
|
|
Home
|
|
</Link>
|
|
|
|
<Link
|
|
className={`link ${pathname === '/about' ? 'active' : ''}`}
|
|
href="/about"
|
|
>
|
|
About
|
|
</Link>
|
|
</nav>
|
|
)
|
|
}
|
|
```
|
|
|
|
```tsx filename="app/layout.tsx" switcher
|
|
import { NavLinks } from '@/app/ui/nav-links'
|
|
|
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<html lang="en">
|
|
<body>
|
|
<NavLinks />
|
|
<main>{children}</main>
|
|
</body>
|
|
</html>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/layout.js" switcher
|
|
import { NavLinks } from '@/app/ui/nav-links'
|
|
|
|
export default function Layout({ children }) {
|
|
return (
|
|
<html lang="en">
|
|
<body>
|
|
<NavLinks />
|
|
<main>{children}</main>
|
|
</body>
|
|
</html>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Displaying content based on `params`
|
|
|
|
Using [dynamic route segments](/docs/app/api-reference/file-conventions/dynamic-routes), you can display or fetch specific content based on the `params` prop.
|
|
|
|
```tsx filename="app/dashboard/layout.tsx" switcher
|
|
export default async function DashboardLayout({
|
|
children,
|
|
params,
|
|
}: {
|
|
children: React.ReactNode
|
|
params: Promise<{ team: string }>
|
|
}) {
|
|
const { team } = await params
|
|
|
|
return (
|
|
<section>
|
|
<header>
|
|
<h1>Welcome to {team}'s Dashboard</h1>
|
|
</header>
|
|
<main>{children}</main>
|
|
</section>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/dashboard/layout.js" switcher
|
|
export default async function DashboardLayout({ children, params }) {
|
|
const { team } = await params
|
|
|
|
return (
|
|
<section>
|
|
<header>
|
|
<h1>Welcome to {team}'s Dashboard</h1>
|
|
</header>
|
|
<main>{children}</main>
|
|
</section>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Reading `params` in Client Components
|
|
|
|
To use `params` in a Client Component (which cannot be `async`), you can use React's [`use`](https://react.dev/reference/react/use) function to read the promise:
|
|
|
|
```tsx filename="app/page.tsx" switcher
|
|
'use client'
|
|
|
|
import { use } from 'react'
|
|
|
|
export default function Page({
|
|
params,
|
|
}: {
|
|
params: Promise<{ slug: string }>
|
|
}) {
|
|
const { slug } = use(params)
|
|
}
|
|
```
|
|
|
|
```js filename="app/page.js" switcher
|
|
'use client'
|
|
|
|
import { use } from 'react'
|
|
|
|
export default function Page({ params }) {
|
|
const { slug } = use(params)
|
|
}
|
|
```
|
|
|
|
## Version History
|
|
|
|
| Version | Changes |
|
|
| ------------ | --------------------------------------------------------------------------------------------- |
|
|
| `v15.0.0-RC` | `params` is now a promise. A [codemod](/docs/app/guides/upgrading/codemods#150) is available. |
|
|
| `v13.0.0` | `layout` introduced. |
|