mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
### What? Document loading request-independent assets at module scope, and update the OG image examples to follow it. ### Why? Reading a local asset inside the component is async I/O during render, so the route defers to request time. A top-level `await` at module scope keeps the route prerendered without reaching for `use cache`. The pattern previously only lived as an inline snippet in the OG image docs. ### How? - Add an "Asynchronous I/O at module scope" section to the Cache Components guide as the canonical explanation. - Load fonts and local assets at module scope in `opengraph-image.mdx` and `image-response.mdx`, and link both to the new section. <!-- NEXT_JS_LLM_PR --> --------- Co-authored-by: Joseph <[email protected]>
530 lines
16 KiB
Plaintext
530 lines
16 KiB
Plaintext
---
|
|
title: opengraph-image and twitter-image
|
|
description: API Reference for the Open Graph Image and Twitter Image file conventions.
|
|
---
|
|
|
|
The `opengraph-image` and `twitter-image` file conventions allow you to set Open Graph and Twitter images for a route segment.
|
|
|
|
They are useful for setting the images that appear on social networks and messaging apps when a user shares a link to your site.
|
|
|
|
There are two ways to set Open Graph and Twitter images:
|
|
|
|
- [Using image files (.jpg, .png, .gif)](#image-files-jpg-png-gif)
|
|
- [Using code to generate images (.js, .ts, .tsx)](#generate-images-using-code-js-ts-tsx)
|
|
|
|
## Image files (.jpg, .png, .gif)
|
|
|
|
Use an image file to set a route segment's shared image by placing an `opengraph-image` or `twitter-image` image file in the segment.
|
|
|
|
Next.js will evaluate the file and automatically add the appropriate tags to your app's `<head>` element.
|
|
|
|
| File convention | Supported file types |
|
|
| ----------------------------------------------- | ------------------------------- |
|
|
| [`opengraph-image`](#opengraph-image) | `.jpg`, `.jpeg`, `.png`, `.gif` |
|
|
| [`twitter-image`](#twitter-image) | `.jpg`, `.jpeg`, `.png`, `.gif` |
|
|
| [`opengraph-image.alt`](#opengraph-imagealttxt) | `.txt` |
|
|
| [`twitter-image.alt`](#twitter-imagealttxt) | `.txt` |
|
|
|
|
> **Good to know**:
|
|
>
|
|
> The `twitter-image` file size must not exceed [5MB](https://developer.x.com/en/docs/x-for-websites/cards/overview/summary), and the `opengraph-image` file size must not exceed [8MB](https://developers.facebook.com/docs/sharing/webmasters/images). If the image file size exceeds these limits, the build will fail.
|
|
|
|
### `opengraph-image`
|
|
|
|
Add an `opengraph-image.(jpg|jpeg|png|gif)` image file to any route segment.
|
|
|
|
```html filename="<head> output"
|
|
<meta property="og:image" content="<generated>" />
|
|
<meta property="og:image:type" content="<generated>" />
|
|
<meta property="og:image:width" content="<generated>" />
|
|
<meta property="og:image:height" content="<generated>" />
|
|
```
|
|
|
|
### `twitter-image`
|
|
|
|
Add a `twitter-image.(jpg|jpeg|png|gif)` image file to any route segment.
|
|
|
|
```html filename="<head> output"
|
|
<meta name="twitter:image" content="<generated>" />
|
|
<meta name="twitter:image:type" content="<generated>" />
|
|
<meta name="twitter:image:width" content="<generated>" />
|
|
<meta name="twitter:image:height" content="<generated>" />
|
|
```
|
|
|
|
### `opengraph-image.alt.txt`
|
|
|
|
Add an accompanying `opengraph-image.alt.txt` file in the same route segment as the `opengraph-image.(jpg|jpeg|png|gif)` image its alt text.
|
|
|
|
```txt filename="opengraph-image.alt.txt"
|
|
About Acme
|
|
```
|
|
|
|
```html filename="<head> output"
|
|
<meta property="og:image:alt" content="About Acme" />
|
|
```
|
|
|
|
### `twitter-image.alt.txt`
|
|
|
|
Add an accompanying `twitter-image.alt.txt` file in the same route segment as the `twitter-image.(jpg|jpeg|png|gif)` image its alt text.
|
|
|
|
```txt filename="twitter-image.alt.txt"
|
|
About Acme
|
|
```
|
|
|
|
```html filename="<head> output"
|
|
<meta name="twitter:image:alt" content="About Acme" />
|
|
```
|
|
|
|
## Generate images using code (.js, .ts, .tsx)
|
|
|
|
In addition to using [literal image files](#image-files-jpg-png-gif), you can programmatically **generate** images using code.
|
|
|
|
Generate a route segment's shared image by creating an `opengraph-image` or `twitter-image` route that default exports a function.
|
|
|
|
| File convention | Supported file types |
|
|
| ----------------- | -------------------- |
|
|
| `opengraph-image` | `.js`, `.ts`, `.tsx` |
|
|
| `twitter-image` | `.js`, `.ts`, `.tsx` |
|
|
|
|
> **Good to know**:
|
|
>
|
|
> - By default, generated images are [**statically optimized**](/docs/app/glossary#prerendering) (generated at build time and cached) unless they use [Request-time APIs](/docs/app/glossary#request-time-apis) or uncached data.
|
|
> - You can generate multiple Images in the same file using [`generateImageMetadata`](/docs/app/api-reference/functions/generate-image-metadata).
|
|
> - `opengraph-image.js` and `twitter-image.js` are special Route Handlers that are cached by default unless it uses a [Request-time API](/docs/app/glossary#request-time-apis) or [dynamic config](/docs/app/guides/caching-without-cache-components#dynamic) option.
|
|
|
|
The easiest way to generate an image is to use the [ImageResponse](/docs/app/api-reference/functions/image-response) API from `next/og`.
|
|
|
|
```tsx filename="app/about/opengraph-image.tsx" switcher
|
|
import { ImageResponse } from 'next/og'
|
|
import { readFile } from 'node:fs/promises'
|
|
import { join } from 'node:path'
|
|
|
|
// Image metadata
|
|
export const alt = 'About Acme'
|
|
export const size = {
|
|
width: 1200,
|
|
height: 630,
|
|
}
|
|
|
|
export const contentType = 'image/png'
|
|
|
|
const interSemiBold = await readFile(
|
|
join(process.cwd(), 'assets/Inter-SemiBold.ttf')
|
|
)
|
|
|
|
// Image generation
|
|
export default async function Image() {
|
|
return new ImageResponse(
|
|
(
|
|
// ImageResponse JSX element
|
|
<div
|
|
style={{
|
|
fontSize: 128,
|
|
background: 'white',
|
|
width: '100%',
|
|
height: '100%',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
About Acme
|
|
</div>
|
|
),
|
|
// ImageResponse options
|
|
{
|
|
// For convenience, we can re-use the exported opengraph-image
|
|
// size config to also set the ImageResponse's width and height.
|
|
...size,
|
|
fonts: [
|
|
{
|
|
name: 'Inter',
|
|
data: interSemiBold,
|
|
style: 'normal',
|
|
weight: 400,
|
|
},
|
|
],
|
|
}
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/about/opengraph-image.js" switcher
|
|
import { ImageResponse } from 'next/og'
|
|
import { readFile } from 'node:fs/promises'
|
|
import { join } from 'node:path'
|
|
|
|
// Image metadata
|
|
export const alt = 'About Acme'
|
|
export const size = {
|
|
width: 1200,
|
|
height: 630,
|
|
}
|
|
|
|
export const contentType = 'image/png'
|
|
|
|
const interSemiBold = await readFile(
|
|
join(process.cwd(), 'assets/Inter-SemiBold.ttf')
|
|
)
|
|
|
|
// Image generation
|
|
export default async function Image() {
|
|
return new ImageResponse(
|
|
(
|
|
// ImageResponse JSX element
|
|
<div
|
|
style={{
|
|
fontSize: 128,
|
|
background: 'white',
|
|
width: '100%',
|
|
height: '100%',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
About Acme
|
|
</div>
|
|
),
|
|
// ImageResponse options
|
|
{
|
|
// For convenience, we can re-use the exported opengraph-image
|
|
// size config to also set the ImageResponse's width and height.
|
|
...size,
|
|
fonts: [
|
|
{
|
|
name: 'Inter',
|
|
data: interSemiBold,
|
|
style: 'normal',
|
|
weight: 400,
|
|
},
|
|
],
|
|
}
|
|
)
|
|
}
|
|
```
|
|
|
|
```html filename="<head> output"
|
|
<meta property="og:image" content="<generated>" />
|
|
<meta property="og:image:alt" content="About Acme" />
|
|
<meta property="og:image:type" content="image/png" />
|
|
<meta property="og:image:width" content="1200" />
|
|
<meta property="og:image:height" content="630" />
|
|
```
|
|
|
|
### Props
|
|
|
|
The default export function receives the following props:
|
|
|
|
#### `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 the segment `opengraph-image` or `twitter-image` is colocated in.
|
|
|
|
> **Good to know**: If you use [`generateImageMetadata`](/docs/app/api-reference/functions/generate-image-metadata), the function will also receive an `id` prop that is a promise resolving to the `id` value from one of the items returned by `generateImageMetadata`.
|
|
|
|
```tsx filename="app/shop/[slug]/opengraph-image.tsx" switcher
|
|
export default async function Image({
|
|
params,
|
|
}: {
|
|
params: Promise<{ slug: string }>
|
|
}) {
|
|
const { slug } = await params
|
|
// ...
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/shop/[slug]/opengraph-image.js" switcher
|
|
export default async function Image({ params }) {
|
|
const { slug } = await params
|
|
// ...
|
|
}
|
|
```
|
|
|
|
| Route | URL | `params` |
|
|
| ------------------------------------------ | ----------- | ---------------------------------- |
|
|
| `app/shop/opengraph-image.js` | `/shop` | `undefined` |
|
|
| `app/shop/[slug]/opengraph-image.js` | `/shop/1` | `Promise<{ slug: '1' }>` |
|
|
| `app/shop/[tag]/[item]/opengraph-image.js` | `/shop/1/2` | `Promise<{ tag: '1', item: '2' }>` |
|
|
|
|
### Returns
|
|
|
|
The default export function should return a `Response`.
|
|
|
|
> **Good to know**: `ImageResponse` satisfies this return type.
|
|
|
|
### Config exports
|
|
|
|
You can optionally configure the image's metadata by exporting `alt`, `size`, and `contentType` variables from `opengraph-image` or `twitter-image` route.
|
|
|
|
| Option | Type |
|
|
| ----------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
| [`alt`](#alt) | `string` |
|
|
| [`size`](#size) | `{ width: number; height: number }` |
|
|
| [`contentType`](#contenttype) | `string` - [image MIME type](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types) |
|
|
|
|
#### `alt`
|
|
|
|
```tsx filename="opengraph-image.tsx | twitter-image.tsx" switcher
|
|
export const alt = 'My images alt text'
|
|
|
|
export default function Image() {}
|
|
```
|
|
|
|
```jsx filename="opengraph-image.js | twitter-image.js" switcher
|
|
export const alt = 'My images alt text'
|
|
|
|
export default function Image() {}
|
|
```
|
|
|
|
```html filename="<head> output"
|
|
<meta property="og:image:alt" content="My images alt text" />
|
|
```
|
|
|
|
#### `size`
|
|
|
|
```tsx filename="opengraph-image.tsx | twitter-image.tsx" switcher
|
|
export const size = { width: 1200, height: 630 }
|
|
|
|
export default function Image() {}
|
|
```
|
|
|
|
```jsx filename="opengraph-image.js | twitter-image.js" switcher
|
|
export const size = { width: 1200, height: 630 }
|
|
|
|
export default function Image() {}
|
|
```
|
|
|
|
```html filename="<head> output"
|
|
<meta property="og:image:width" content="1200" />
|
|
<meta property="og:image:height" content="630" />
|
|
```
|
|
|
|
#### `contentType`
|
|
|
|
```tsx filename="opengraph-image.tsx | twitter-image.tsx" switcher
|
|
export const contentType = 'image/png'
|
|
|
|
export default function Image() {}
|
|
```
|
|
|
|
```jsx filename="opengraph-image.js | twitter-image.js" switcher
|
|
export const contentType = 'image/png'
|
|
|
|
export default function Image() {}
|
|
```
|
|
|
|
```html filename="<head> output"
|
|
<meta property="og:image:type" content="image/png" />
|
|
```
|
|
|
|
#### Route Segment Config
|
|
|
|
`opengraph-image` and `twitter-image` are specialized [Route Handlers](/docs/app/api-reference/file-conventions/route) that can use the same [route segment configuration](/docs/app/api-reference/file-conventions/route-segment-config) options as Pages and Layouts.
|
|
|
|
### Examples
|
|
|
|
#### Using external data
|
|
|
|
This example uses the `params` object and external data to generate the image.
|
|
|
|
> **Good to know**:
|
|
> By default, this generated image will be statically optimized. You can configure the individual `fetch` [`options`](/docs/app/api-reference/functions/fetch) or route segments [options](/docs/app/guides/caching-without-cache-components#route-segment-config-revalidate) to change this behavior.
|
|
|
|
```tsx filename="app/posts/[slug]/opengraph-image.tsx" switcher
|
|
import { ImageResponse } from 'next/og'
|
|
|
|
export const alt = 'About Acme'
|
|
export const size = {
|
|
width: 1200,
|
|
height: 630,
|
|
}
|
|
export const contentType = 'image/png'
|
|
|
|
export default async function Image({
|
|
params,
|
|
}: {
|
|
params: Promise<{ slug: string }>
|
|
}) {
|
|
const { slug } = await params
|
|
const post = await fetch(`https://.../posts/${slug}`).then((res) =>
|
|
res.json()
|
|
)
|
|
|
|
return new ImageResponse(
|
|
(
|
|
<div
|
|
style={{
|
|
fontSize: 48,
|
|
background: 'white',
|
|
width: '100%',
|
|
height: '100%',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
{post.title}
|
|
</div>
|
|
),
|
|
{
|
|
...size,
|
|
}
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/posts/[slug]/opengraph-image.js" switcher
|
|
import { ImageResponse } from 'next/og'
|
|
|
|
export const alt = 'About Acme'
|
|
export const size = {
|
|
width: 1200,
|
|
height: 630,
|
|
}
|
|
export const contentType = 'image/png'
|
|
|
|
export default async function Image({ params }) {
|
|
const { slug } = await params
|
|
const post = await fetch(`https://.../posts/${slug}`).then((res) =>
|
|
res.json()
|
|
)
|
|
|
|
return new ImageResponse(
|
|
(
|
|
<div
|
|
style={{
|
|
fontSize: 48,
|
|
background: 'white',
|
|
width: '100%',
|
|
height: '100%',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
{post.title}
|
|
</div>
|
|
),
|
|
{
|
|
...size,
|
|
}
|
|
)
|
|
}
|
|
```
|
|
|
|
#### Using Node.js runtime with local assets
|
|
|
|
These examples use the Node.js runtime to fetch a local image from the file system and pass it to the `<img>` `src` attribute, either as a base64 string or an `ArrayBuffer`. Place the local asset relative to the project root, not the example source file.
|
|
|
|
The asset doesn't depend on request data, so read it once at module scope. See [Predictable values](/docs/app/getting-started/caching#predictable-values).
|
|
|
|
```tsx filename="app/opengraph-image.tsx" switcher
|
|
import { ImageResponse } from 'next/og'
|
|
import { join } from 'node:path'
|
|
import { readFile } from 'node:fs/promises'
|
|
|
|
const logoData = await readFile(join(process.cwd(), 'logo.png'), 'base64')
|
|
const logoSrc = `data:image/png;base64,${logoData}`
|
|
|
|
export default async function Image() {
|
|
return new ImageResponse(
|
|
(
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
<img src={logoSrc} height="100" />
|
|
</div>
|
|
)
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/opengraph-image.js" switcher
|
|
import { ImageResponse } from 'next/og'
|
|
import { join } from 'node:path'
|
|
import { readFile } from 'node:fs/promises'
|
|
|
|
const logoData = await readFile(join(process.cwd(), 'logo.png'), 'base64')
|
|
const logoSrc = `data:image/png;base64,${logoData}`
|
|
|
|
export default async function Image() {
|
|
return new ImageResponse(
|
|
(
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
<img src={logoSrc} height="100" />
|
|
</div>
|
|
)
|
|
)
|
|
}
|
|
```
|
|
|
|
Passing an `ArrayBuffer` to the `src` attribute of an `<img>` element is not part of the HTML spec. The rendering engine used by `next/og` supports it, but because TypeScript definitions follow the spec, you need a `@ts-expect-error` directive or similar to use this [feature](https://github.com/vercel/satori/issues/606#issuecomment-2144000453).
|
|
|
|
```tsx filename="app/opengraph-image.tsx" switcher
|
|
import { ImageResponse } from 'next/og'
|
|
import { join } from 'node:path'
|
|
import { readFile } from 'node:fs/promises'
|
|
|
|
const logoData = await readFile(join(process.cwd(), 'logo.png'))
|
|
const logoSrc = Uint8Array.from(logoData).buffer
|
|
|
|
export default async function Image() {
|
|
return new ImageResponse(
|
|
(
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
{/* @ts-expect-error Satori accepts ArrayBuffer/typed arrays for <img src> at runtime */}
|
|
<img src={logoSrc} height="100" />
|
|
</div>
|
|
)
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/opengraph-image.js" switcher
|
|
import { ImageResponse } from 'next/og'
|
|
import { join } from 'node:path'
|
|
import { readFile } from 'node:fs/promises'
|
|
|
|
const logoData = await readFile(join(process.cwd(), 'logo.png'))
|
|
const logoSrc = Uint8Array.from(logoData).buffer
|
|
|
|
export default async function Image() {
|
|
return new ImageResponse(
|
|
(
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
<img src={logoSrc} height="100" />
|
|
</div>
|
|
)
|
|
)
|
|
}
|
|
```
|
|
|
|
## Version History
|
|
|
|
| Version | Changes |
|
|
| --------- | ---------------------------------------------------- |
|
|
| `v16.0.0` | `params` is now a promise that resolves to an object |
|
|
| `v13.3.0` | `opengraph-image` and `twitter-image` introduced. |
|