# Generated OG images in the App Router

An opengraph-image.tsx file next to the route produces the PNG through ImageResponse. What Satori supports, fonts to load, the duplicate.

> Page: https://ui.eneawork.it/blog/nextjs-opengraph-image
> Markdown: https://ui.eneawork.it/blog/nextjs-opengraph-image.md

*2026-11-01*

Un'immagine Open Graph generata è un file opengraph-image.tsx accanto alla rotta: Next.js lo esegue e restituisce un PNG. Il nome è una convenzione, non una scelta, e il file vale per la pagina e per le rotte figlie.

## Il file minimo

// app/blog/[slug]/opengraph-image.tsx import { ImageResponse } from "next/og"; export const size = { width: 1200, height: 630 }; export const contentType = "image/png"; export default async function Image({ params }: { params: Promise }) { const { slug } = await params; return new ImageResponse( ( ui.eneawork.it/blog {slug.replace(/-/g, " ")} ), size, ); }

Il file esporta size, contentType e un componente async. Se legge params o una fetch non in cache, l'immagine viene generata a ogni richiesta; altrimenti Next.js la produce una volta in build.

## Cosa Satori non fa

## Dove entra il font

// inside the function that renders the image const font = await readFile( join(process.cwd(), "public/fonts/inter-semibold.ttf"), ); return new ImageResponse( Titolo, { ...size, fonts: [{ name: "Inter", data: font, weight: 600, style: "normal" }], }, );

Senza fonts il testo esce in un carattere che non hai scelto, e i pesi non corrispondono al sito. Carica il file una volta sola e riusalo in tutte le funzioni immagine.

## Il doppione da evitare

Se dichiari openGraph.images in generateMetadata e lasci anche opengraph-image.tsx, la convenzione ha la precedenza e la tua immagine sparisce. Scegli una strada: file per le immagini generate, metadata per quelle statiche.

Il catalogo EneUI indicizza 111.985 componenti, 1.556 blocchi e 12.808 icone da 1.472 librerie: numeri misurati il 1 novembre 2026 su /api/stats. I 398 post del blog hanno 398 copertine PNG italiane e 398 inglesi in public/blog-covers, contate il 1 novembre 2026.

I pezzi della scena arrivano dal catalogo: hero, badge e tipografia si ricompongono in stili inline.

I metadata dinamici sono in metadata dinamica, il titolo in come scrivere un title tag e la descrizione in meta description che funziona. Confronto: Astro vs Next.js.

curl -sI http://localhost:3000/blog/hello/opengraph-image

Limite noto: il risultato è un PNG raster. Il testo sotto i 24 px si legge male su LinkedIn e la generazione consuma CPU: se la scena è sempre identica, un file statico è più veloce.

## FAQ

### Dove va il file opengraph-image?

Nella cartella della rotta, accanto a page.tsx. Vale per quella pagina e per le rotte figlie che non ne dichiarano una propria.

### Posso usare Tailwind dentro ImageResponse?

No. Satori accetta stili inline: converti i token in un oggetto JavaScript e passa quello.

### Perché il font esce sbagliato?

Perché non hai passato il font. ImageResponse non legge i font del sito: carica il file come ArrayBuffer e dichiaralo nell'opzione fonts.

### L'immagine viene generata a ogni richiesta?

Solo se legge dati dinamici come params o una fetch non in cache. Con contenuto statico Next.js la genera una volta in build.

### Serve anche twitter-image?

Solo se vuoi un'immagine diversa da quella Open Graph. Se il formato va bene, Twitter usa la stessa immagine.
