next
Version:
The React Framework
286 lines (215 loc) • 8.32 kB
Markdown
---
title: generateViewport
description: API Reference for the generateViewport function.
related:
title: Next Steps
description: View all the Metadata API options.
links:
- app/api-reference/file-conventions/metadata
- app/getting-started/caching
- app/api-reference/config/next-config-js/cacheComponents
---
You can customize the initial viewport of the page with the static `viewport` object or the dynamic `generateViewport` function.
> **Good to know**:
>
> - The `viewport` object and `generateViewport` function exports are **only supported in Server Components**.
> - You cannot export both the `viewport` object and `generateViewport` function from the same route segment.
> - If you're coming from migrating `metadata` exports, you can use [metadata-to-viewport-export codemod](/docs/app/guides/upgrading/codemods#metadata-to-viewport-export) to update your changes.
## The `viewport` object
To define the viewport options, export a `viewport` object from a `layout.jsx` or `page.jsx` file.
```tsx filename="layout.tsx | page.tsx" switcher
import type { Viewport } from 'next'
export const viewport: Viewport = {
themeColor: 'black',
}
export default function Page() {}
```
```jsx filename="layout.jsx | page.jsx" switcher
export const viewport = {
themeColor: 'black',
}
export default function Page() {}
```
## `generateViewport` function
`generateViewport` should return a [`Viewport` object](#viewport-fields) containing one or more viewport fields.
```tsx filename="layout.tsx | page.tsx" switcher
export function generateViewport({ params }) {
return {
themeColor: '...',
}
}
```
In TypeScript, the `params` argument can be typed via [`PageProps<'/route'>`](/docs/app/api-reference/file-conventions/page#page-props-helper) or [`LayoutProps<'/route'>`](/docs/app/api-reference/file-conventions/layout#layout-props-helper) depending on where `generateViewport` is defined.
```jsx filename="layout.js | page.js" switcher
export function generateViewport({ params }) {
return {
themeColor: '...',
}
}
```
> **Good to know**:
>
> - If the viewport doesn't depend on request information, it should be defined using the static [`viewport` object](#the-viewport-object) rather than `generateViewport`.
## Viewport Fields
### `themeColor`
Learn more about [`theme-color`](https://developer.mozilla.org/docs/Web/HTML/Element/meta/name/theme-color).
**Simple theme color**
```tsx filename="layout.tsx | page.tsx" switcher
import type { Viewport } from 'next'
export const viewport: Viewport = {
themeColor: 'black',
}
```
```jsx filename="layout.jsx | page.jsx" switcher
export const viewport = {
themeColor: 'black',
}
```
```html filename="<head> output" hideLineNumbers
<meta name="theme-color" content="black" />
```
**With media attribute**
```tsx filename="layout.tsx | page.tsx" switcher
import type { Viewport } from 'next'
export const viewport: Viewport = {
themeColor: [
{ media: '(prefers-color-scheme: light)', color: 'cyan' },
{ media: '(prefers-color-scheme: dark)', color: 'black' },
],
}
```
```jsx filename="layout.jsx | page.jsx" switcher
export const viewport = {
themeColor: [
{ media: '(prefers-color-scheme: light)', color: 'cyan' },
{ media: '(prefers-color-scheme: dark)', color: 'black' },
],
}
```
```html filename="<head> output" hideLineNumbers
<meta name="theme-color" media="(prefers-color-scheme: light)" content="cyan" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="black" />
```
### `width`, `initialScale`, `maximumScale` and `userScalable`
> **Good to know**: The `viewport` meta tag is automatically set, and manual configuration is usually unnecessary as the default is sufficient. However, the information is provided for completeness.
```tsx filename="layout.tsx | page.tsx" switcher
import type { Viewport } from 'next'
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
maximumScale: 1,
userScalable: false,
// Also supported but less commonly used
// interactiveWidget: 'resizes-visual',
}
```
```jsx filename="layout.jsx | page.jsx" switcher
export const viewport = {
width: 'device-width',
initialScale: 1,
maximumScale: 1,
userScalable: false,
// Also supported but less commonly used
// interactiveWidget: 'resizes-visual',
}
```
```html filename="<head> output" hideLineNumbers
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
/>
```
### `colorScheme`
Learn more about [`color-scheme`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#:~:text=color%2Dscheme%3A%20specifies,of%20the%20following%3A).
```tsx filename="layout.tsx | page.tsx" switcher
import type { Viewport } from 'next'
export const viewport: Viewport = {
colorScheme: 'dark',
}
```
```jsx filename="layout.jsx | page.jsx" switcher
export const viewport = {
colorScheme: 'dark',
}
```
```html filename="<head> output" hideLineNumbers
<meta name="color-scheme" content="dark" />
```
## With Cache Components
When [Cache Components](/docs/app/getting-started/caching) is enabled, `generateViewport` follows the same rules as other components. If viewport accesses runtime data (`cookies()`, `headers()`, `params`, `searchParams`) or performs uncached data fetching, it defers to request time.
Unlike metadata, viewport cannot be streamed because it affects initial page load UI. If `generateViewport` defers to request time, the page would need to block until resolved.
If viewport depends on external data but not runtime data, use `use cache`:
```tsx filename="app/layout.tsx" highlight={2}
export async function generateViewport() {
'use cache'
const { width, initialScale } = await db.query('viewport-size')
return { width, initialScale }
}
```
If viewport genuinely requires runtime data, wrap the document `<body>` in a Suspense boundary to signal that the entire route should be dynamic:
```tsx filename="app/layout.tsx" highlight={1,13,17}
import { Suspense } from 'react'
import { cookies } from 'next/headers'
export async function generateViewport() {
const cookieJar = await cookies()
return {
themeColor: cookieJar.get('theme-color')?.value,
}
}
export default function RootLayout({ children }) {
return (
<Suspense>
<html>
<body>{children}</body>
</html>
</Suspense>
)
}
```
Caching is preferred because it allows static shell generation. Wrapping the document `body` in Suspense means there is no static shell or content to immediately send when a request arrives, making the entire route block until ready on every request.
> **Good to know**: Use [multiple root layouts](/docs/app/api-reference/file-conventions/layout#root-layout) to isolate fully dynamic viewport to specific routes, while still letting other routes in your application generate a static shell.
## Types
You can add type safety to your viewport object by using the `Viewport` type. If you are using the [built-in TypeScript plugin](/docs/app/api-reference/config/typescript) in your IDE, you do not need to manually add the type, but you can still explicitly add it if you want.
### `viewport` object
```tsx
import type { Viewport } from 'next'
export const viewport: Viewport = {
themeColor: 'black',
}
```
### `generateViewport` function
#### Regular function
```tsx
import type { Viewport } from 'next'
export function generateViewport(): Viewport {
return {
themeColor: 'black',
}
}
```
#### With segment props
```tsx
import type { Viewport } from 'next'
type Props = {
params: Promise<{ id: string }>
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
export function generateViewport({ params, searchParams }: Props): Viewport {
return {
themeColor: 'black',
}
}
export default function Page({ params, searchParams }: Props) {}
```
#### JavaScript Projects
For JavaScript projects, you can use JSDoc to add type safety.
```js
/** @type {import("next").Viewport} */
export const viewport = {
themeColor: 'black',
}
```
## Version History
| Version | Changes |
| --------- | --------------------------------------------- |
| `v14.0.0` | `viewport` and `generateViewport` introduced. |