Files
next.js/docs/01-app/03-api-reference/03-file-conventions/page.mdx
dfbc3dc6b7 Loading layout docs update (#90787)
<!-- 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>&nbsp;<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>&nbsp;</p>

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Joseph Chamochumbi <[email protected]>
Co-authored-by: Delba de Oliveira <[email protected]>
2026-03-05 10:46:26 +01:00

241 lines
8.3 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: page.js
description: API reference for the page.js file.
---
The `page` file allows you to define UI that is **unique** to a route. You can create a page by default exporting a component from the file:
```tsx filename="app/blog/[slug]/page.tsx" switcher
export default function Page({
params,
searchParams,
}: {
params: Promise<{ slug: string }>
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
return <h1>My Page</h1>
}
```
```jsx filename="app/blog/[slug]/page.js" switcher
export default function Page({ params, searchParams }) {
return <h1>My Page</h1>
}
```
## Good to know
- The `.js`, `.jsx`, or `.tsx` file extensions can be used for `page`.
- A `page` is always the **leaf** of the route subtree.
- A `page` file is required to make a route segment **publicly accessible**.
- Pages are [Server Components](https://react.dev/reference/rsc/server-components) by default, but can be set to a [Client Component](https://react.dev/reference/rsc/use-client).
- In the [component hierarchy](/docs/app/getting-started/project-structure#component-hierarchy), `page.js` is the innermost file convention. It is wrapped by `loading.js` (Suspense boundary), `error.js` (error boundary), `template.js`, and `layout.js` in the same segment.
## Reference
### Props
#### `params` (optional)
A promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) from the root segment down to that page.
```tsx filename="app/shop/[slug]/page.tsx" switcher
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
}
```
```jsx filename="app/shop/[slug]/page.js" switcher
export default async function Page({ params }) {
const { slug } = await params
}
```
| Example Route | URL | `params` |
| ------------------------------------ | ----------- | --------------------------------------- |
| `app/shop/[slug]/page.js` | `/shop/1` | `Promise<{ slug: '1' }>` |
| `app/shop/[category]/[item]/page.js` | `/shop/1/2` | `Promise<{ category: '1', item: '2' }>` |
| `app/shop/[...slug]/page.js` | `/shop/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.
#### `searchParams` (optional)
A promise that resolves to an object containing the [search parameters](https://developer.mozilla.org/docs/Learn/Common_questions/What_is_a_URL#parameters) of the current URL. For example:
```tsx filename="app/shop/page.tsx" switcher
export default async function Page({
searchParams,
}: {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
const filters = (await searchParams).filters
}
```
```jsx filename="app/shop/page.js" switcher
export default async function Page({ searchParams }) {
const filters = (await searchParams).filters
}
```
Client Component **pages** can also access `searchParams` using React’s [`use`](https://react.dev/reference/react/use) hook:
```tsx filename="app/shop/page.tsx" switcher
'use client'
import { use } from 'react'
export default function Page({
searchParams,
}: {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
const filters = use(searchParams).filters
}
```
```jsx filename="app/page.jsx" switcher
'use client'
import { use } from 'react'
export default function Page({ searchParams }) {
const filters = use(searchParams).filters
}
```
| Example URL | `searchParams` |
| --------------- | ----------------------------- |
| `/shop?a=1` | `Promise<{ a: '1' }>` |
| `/shop?a=1&b=2` | `Promise<{ a: '1', b: '2' }>` |
| `/shop?a=1&a=2` | `Promise<{ a: ['1', '2'] }>` |
- Since the `searchParams` 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, `searchParams` 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.
- `searchParams` is a **[Request-time API](/docs/app/glossary#request-time-apis)** whose values cannot be known ahead of time. Using it will opt the page into **[dynamic rendering](/docs/app/glossary#dynamic-rendering)** at request time.
- `searchParams` is a plain JavaScript object, not a `URLSearchParams` instance.
### Page Props Helper
You can type pages with `PageProps` to get strongly typed `params` and `searchParams` from the route literal. `PageProps` is a globally available helper.
```tsx filename="app/blog/[slug]/page.tsx"
export default async function Page(props: PageProps<'/blog/[slug]'>) {
const { slug } = await props.params
const query = await props.searchParams
return <h1>Blog Post: {slug}</h1>
}
```
> **Good to know**
>
> - Using a literal route (e.g. `'/blog/[slug]'`) enables autocomplete and strict keys for `params`.
> - Static routes resolve `params` to `{}`.
> - Types are generated during `next dev`, `next build`, or with `next typegen`.
> - After type generation, the `PageProps` helper is globally available. It doesn't need to be imported.
## Examples
### Displaying content based on `params`
Using [dynamic route segments](/docs/app/api-reference/file-conventions/dynamic-routes), you can display or fetch specific content for the page based on the `params` prop.
```tsx filename="app/blog/[slug]/page.tsx" switcher
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
return <h1>Blog Post: {slug}</h1>
}
```
```jsx filename="app/blog/[slug]/page.js" switcher
export default async function Page({ params }) {
const { slug } = await params
return <h1>Blog Post: {slug}</h1>
}
```
### Handling filtering with `searchParams`
You can use the `searchParams` prop to handle filtering, pagination, or sorting based on the query string of the URL.
```tsx filename="app/shop/page.tsx" switcher
export default async function Page({
searchParams,
}: {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
const { page = '1', sort = 'asc', query = '' } = await searchParams
return (
<div>
<h1>Product Listing</h1>
<p>Search query: {query}</p>
<p>Current page: {page}</p>
<p>Sort order: {sort}</p>
</div>
)
}
```
```jsx filename="app/shop/page.js" switcher
export default async function Page({ searchParams }) {
const { page = '1', sort = 'asc', query = '' } = await searchParams
return (
<div>
<h1>Product Listing</h1>
<p>Search query: {query}</p>
<p>Current page: {page}</p>
<p>Sort order: {sort}</p>
</div>
)
}
```
### Reading `searchParams` and `params` in Client Components
To use `searchParams` and `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,
searchParams,
}: {
params: Promise<{ slug: string }>
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
const { slug } = use(params)
const { query } = use(searchParams)
}
```
```js filename="app/page.js" switcher
'use client'
import { use } from 'react'
export default function Page({ params, searchParams }) {
const { slug } = use(params)
const { query } = use(searchParams)
}
```
## Version History
| Version | Changes |
| ------------ | ---------------------------------------------------------------------------------------------------------------- |
| `v15.0.0-RC` | `params` and `searchParams` are now promises. A [codemod](/docs/app/guides/upgrading/codemods#150) is available. |
| `v13.0.0` | `page` introduced. |