In the Next.js 15.1.6 docs (App Router), the last code snippet on the Metadata Files: opengraph-image and twitter-image | Next.js page explains how to fetch a local image on the file system and pass it as an ArrayBuffer to the src
attribute of an <img>
element in order to programmatically generate an OpenGraph image (very useful for dynamic route segments).
Here is the snippet:
app/opengraph-image.tsx:
import { ImageResponse } from 'next/og'
import { join } from 'node:path'
import { readFile } from 'node:fs/promises'
export default async function Image() {
const logoData = await readFile(join(process.cwd(), 'logo.png'))
const logoSrc = Uint8Array.from(logoData).buffer
return new ImageResponse(
(
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<img src={logoSrc} height="100" />
</div>
)
)
}
When I copy and paste this snippet into a new Next.js 15 project (create-next-app@latest), I get a type error saying:
Type 'ArrayBuffer' is not assignable to type 'string'.ts(2322)
index.d.ts(3051, 9): The expected type comes from property 'src' which is declared here on type 'DetailedHTMLProps<ImgHTMLAttributes, HTMLImageElement>'
⚠ Error (TS2322) | Type
ArrayBuffer
is not assignable to typestring
.(property) ImgHTMLAttributes.src?: string | undefined
Are the docs wrong?
Here is a reproduction repository:
You can see the type error by opening src/app/opengraph-image.tsx
in a Typescript-enabled editor or by running npm run build
In the Next.js 15.1.6 docs (App Router), the last code snippet on the Metadata Files: opengraph-image and twitter-image | Next.js page explains how to fetch a local image on the file system and pass it as an ArrayBuffer to the src
attribute of an <img>
element in order to programmatically generate an OpenGraph image (very useful for dynamic route segments).
Here is the snippet:
app/opengraph-image.tsx:
import { ImageResponse } from 'next/og'
import { join } from 'node:path'
import { readFile } from 'node:fs/promises'
export default async function Image() {
const logoData = await readFile(join(process.cwd(), 'logo.png'))
const logoSrc = Uint8Array.from(logoData).buffer
return new ImageResponse(
(
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<img src={logoSrc} height="100" />
</div>
)
)
}
When I copy and paste this snippet into a new Next.js 15 project (create-next-app@latest), I get a type error saying:
Type 'ArrayBuffer' is not assignable to type 'string'.ts(2322)
index.d.ts(3051, 9): The expected type comes from property 'src' which is declared here on type 'DetailedHTMLProps<ImgHTMLAttributes, HTMLImageElement>'
⚠ Error (TS2322) | Type
ArrayBuffer
is not assignable to typestring
.(property) ImgHTMLAttributes.src?: string | undefined
Are the docs wrong?
Here is a reproduction repository: https://github/shawninder/opengraph-type-error-repro
You can see the type error by opening src/app/opengraph-image.tsx
in a Typescript-enabled editor or by running npm run build
1 Answer
Reset to default 0I got my answer after submitting a but report on GitHub.
tl;dr
The code is fine. Typescript just needs some convincing.
Explanation
The ImageResponse
component uses satori to convert JSX into an image. This adds the possibility of providing an Array buffer as src
for <img>
s, which is not usually supported. Unfortunately, there doesn't seem to be a way for satori to let Typescript know about this without affecting JSX more generally.
As shuding explains:
this is unfortunately something Satori can't work around. If it provides a
.d.ts
to allow Buffer/ArrayBuffer values for img tag'ssrc
field, it will be a global TypeScript override and will affect other JSX code.Unless TS gives us a way to affect global types of a scoped value (e.g. JSX values passed into satori(...)), there's nothing Satori can do here.
Workarounds
Thankfully, there are a few suggestions in the GitHub issues:
- As suggested by icyJoseph: Let typescript know everything's OK using a comment:
// @ts-expect-error Satori does support buffers as src
<img src={logoSrc} height="100" />
- As suggested by souporserious, use
as any
:
<img src={logoSrc as any} height="100" />
- As suggested by souporserious, convert to base64:
<img src={`data:image/png;base64,${Buffer.from(logoSrc).toString('base64')}`} height="100" />
What's Next?
icyJoseph has offered to submit a PR to improve the documentation surrounding the code snippet.