# Što ćete izgraditi i zašto je to važno#
Dinamične Open Graph slike omogućuju da svaki blog post, proizvod ili landing page dobije jedinstveni social preview bez dizajniranja stotina asseta. Kada se preview poklapa s naslovom stranice, kategorijom i autorom, povećava se stopa dijeljenja i smanjuje broj “generičkih previewa” koji izgledaju spamerski.
Ovaj vodič prikazuje production-ready pristup za dinamične Open Graph slike u Next.js-u koristeći App Router i next/og, uključujući rad s fontovima, cache headere, ograničenja Edge runtimea te usklađenost lokalnog razvoja i deploya.
Ako još postavljate strukturu projekta, krenite s Uvod u Next.js. Za SEO kontekst o tome zašto su ovi previewi važni, pogledajte Zašto Next.js za SEO. Za dublje obrasce predmemoriranja koji su direktno povezani s OG generiranjem, pročitajte Next.js strategije predmemoriranja: SSR, ISR, SWR.
# Preduvjeti#
| Zahtjev | Verzija | Napomene |
|---|---|---|
| Next.js | 14 ili 15 | Preporučen App Router |
| Node.js | 18+ | Lokalni dev i build alati |
| Deploy target | Vercel ili slično | Edge runtime opcionalan |
| Fontovi | WOFF ili TTF | Commitajte u repo radi podudarnosti |
| Izvor sadržaja | MDX, CMS, DB | Treba stabilan slug i naslov |
ℹ️ Napomena:
next/ogse izvršava na serveru. Ne možete pozivati API-je koji postoje samo u pregledniku. Ako radite na Edgeu, ne možete koristiti ni mnoge Node.js built-in module.
# Kako radi dinamično generiranje OG slika u Next.js-u#
U App Routeru tipično izložite zasebnu rutu koja vraća image response. Metadata stranice zatim postavlja openGraph.images na tu rutu, obično s parametrom slug.
Postoje dva česta obrasca:
| Obrazac | Primjer URL-a | Prednosti | Nedostaci |
|---|---|---|---|
| Jedna OG ruta za više stavki | /api/og?slug=my-post | Jednostavno, fleksibilno | Query parametri mogu zakomplicirati caching |
| Segment rute po stavci | /og/my-post.png | Stabilni URL-ovi, lakše predmemoriranje | Traži podešavanje segmenta rute |
Za performanse i caching preferirajte stabilne URL-ove koje možete verzionirati. Social scrapersi i CDN-ovi agresivno cacheiraju, pa je bolje da se /og/my-post.png?v=2026-05-25 promijeni kada se promijeni sadržaj.
# Korak 1: Napravite OG rutu s next/og#
Napravite route handler koji vraća sliku. Uobičajen pristup je:
app/og/[slug]/route.tsza rute po stavci- Koristite
ImageResponseiznext/og - Dohvatite naslov i ostale metapodatke koje želite renderirati
// app/og/[slug]/route.ts
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(
_req: Request,
context: { params: Promise<{ slug: string }> }
) {
const { slug } = await context.params;
const title = await getTitleBySlug(slug);
return new ImageResponse(
(
<div
style={{
width: '1200px',
height: '630px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
padding: '64px',
background: '#0B1020',
color: 'white',
}}
>
<div style={{ fontSize: 56, fontWeight: 700, lineHeight: 1.1 }}>
{title}
</div>
<div style={{ marginTop: 24, fontSize: 28, opacity: 0.8 }}>
samioda.com
</div>
</div>
),
{
width: 1200,
height: 630,
}
);
}
async function getTitleBySlug(slug: string) {
// Replace with CMS/DB/MDX lookup
return `Post: ${slug}`;
}Ovo renderira PNG u runtimeu. Next.js će ga izvršiti server-side, a ako je deployano iza CDN-a, može se predmemorirati.
💡 Savjet: Uvijek se držite
1200x630za OG preglede. To odgovara najčešćim scraperima i izbjegava neočekivano rezanje.
# Korak 2: Povežite OG sliku u metadata stranice#
Koristite generateMetadata u ruti stranice. Cilj je da OG URL bude determinističan za stranicu.
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> }
): Promise<Metadata> {
const { slug } = await params;
const title = await getPostTitle(slug);
const updatedAt = await getPostUpdatedAtISO(slug);
const ogUrl = `/og/${slug}?v=${encodeURIComponent(updatedAt)}`;
return {
title,
openGraph: {
title,
images: [{ url: ogUrl, width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
title,
images: [ogUrl],
},
};
}
async function getPostTitle(slug: string) {
return `Blog: ${slug}`;
}
async function getPostUpdatedAtISO(slug: string) {
// Use a real updatedAt from CMS, git history, or DB.
return new Date().toISOString().slice(0, 10);
}Zašto je verzioniranje važno#
Čak i ako vaš CDN poštuje cache headere, platforme poput Facebooka, X-a, Slacka i LinkedIna mogu imati vlastite cacheve. Ažuriranje sadržaja bez promjene URL-a OG slike čest je razlog za “još uvijek prikazuje staru sliku”.
Verzioniranje čini ažuriranje eksplicitnim.
🎯 Ključna poruka: Tretirajte URL-ove OG slika kao statičke assete. Stabilno, cacheabilno i verzionirano je bolje od “uvijek dinamično” u stvarnom ponašanju scrapersa.
# Korak 3: Fontovi koji se podudaraju lokalno i u produkciji#
Najčešći production-only OG bug je fallback font. Lokalno okruženje često ima fontove instalirane na računalu, dok Edge okruženja nemaju.
Preporučena strategija za fontove#
- 1Stavite font datoteke u repo, npr.
app/og/_assets/Inter-SemiBold.ttf - 2Učitajte ih s
fetchkoristećinew URL(..., import.meta.url) - 3Proslijedite učitane byteove fonta u
ImageResponse
// app/og/[slug]/route.ts
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
const interSemiBold = fetch(
new URL('../_assets/Inter-SemiBold.ttf', import.meta.url)
).then((res) => res.arrayBuffer());
export async function GET(
_req: Request,
context: { params: Promise<{ slug: string }> }
) {
const { slug } = await context.params;
const title = `Post: ${slug}`;
const fontData = await interSemiBold;
return new ImageResponse(
(
<div
style={{
width: '1200px',
height: '630px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
padding: '64px',
background: '#0B1020',
color: '#FFFFFF',
fontFamily: 'Inter',
}}
>
<div style={{ fontSize: 60, fontWeight: 600, lineHeight: 1.1 }}>
{title}
</div>
</div>
),
{
width: 1200,
height: 630,
fonts: [
{
name: 'Inter',
data: fontData,
weight: 600,
style: 'normal',
},
],
}
);
}Napomene o formatima fontova#
| Format fonta | Dobro radi s next/og | Veličina datoteke | Napomene |
|---|---|---|---|
| TTF | Da | Srednja | Najčešći u primjerima |
| OTF | Ponekad | Srednja | Može failati ovisno o glyph tablicama |
| WOFF/WOFF2 | Nije idealno | Mala | Često treba konverziju za server render |
Ako trebate WOFF2 na webu, ali TTF za OG slike, držite oba. OG ruta je zaseban render pipeline.
⚠️ Upozorenje: Ne oslanjajte se na
next/fontu OG rutama.next/ogtreba raw byteove fonta, ne CSS-injectane font-face definicije.
# Korak 4: Cache headeri koji stvarno rade#
OG slike su savršen kandidat za CDN caching. Generiranje slike je skupo u usporedbi sa serviranjem iz cachea, a scrapersi je traže više puta.
Praktična caching politika#
Koristite cache headere s dugim CDN cacheom i razumnim “stale” prozorom.
publicčini odgovor cacheabilnims-maxagecilja CDN-ovestale-while-revalidateomogućuje brze odgovore dok se u pozadini osvježava
// app/og/[slug]/route.ts
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export async function GET(
_req: Request,
context: { params: Promise<{ slug: string }> }
) {
const { slug } = await context.params;
const res = new ImageResponse(
(
<div style={{ width: '1200px', height: '630px', background: '#0B1020' }}>
<div style={{ color: 'white', padding: 64, fontSize: 56 }}>
{slug}
</div>
</div>
),
{ width: 1200, height: 630 }
);
res.headers.set(
'Cache-Control',
'public, s-maxage=2592000, stale-while-revalidate=86400'
);
return res;
}Ta politika cacheira 30 dana na edgeu i dopušta 24 sata stale razdoblja. Ako dodatno verzionirate URL s ?v=updatedAt, dobivate visoke cache hit rateove bez prikazivanja zastarjelih previewa.
Ako se želite uskladiti s ISR revalidacijom, koristite manji s-maxage, npr. 1 do 6 sati, i zadržite verzioniranje.
Za šire caching koncepte i tradeoffe, pogledajte Next.js strategije predmemoriranja: SSR, ISR, SWR.
CDN caching i scraper caching nisu isto#
Ni savršeni Cache-Control headeri ne jamče trenutačna ažuriranja u social previewima. Mnogi scrapersi cacheiraju satima ili danima. Najjača poluga je verzioniranje URL-a, ne samo cache headeri.
# Korak 5: Dohvat podataka za OG rute bez iznenađenja#
OG ruta obično treba:
- naslov
- kategoriju
- autora
- datum objave
- možda cijenu proizvoda ili badge
Svedite vanjske pozive na minimum. Jedan API poziv po OG renderu je prihvatljiv ako se cacheira, ali više poziva povećava cold-start vrijeme i broj mogućih točaka kvara.
Predloženi data contract#
| Polje | Primjer | Izvor | Renderira se u OG |
|---|---|---|---|
| slug | nextjs-og-images | URL param | Opcionalno |
| title | Dynamic OG Images in Next.js | CMS/MDX | Da |
| updatedAt | 2026-05-25 | CMS/DB | Koristi se za ?v= |
| tag | Next.js | CMS/MDX | Da |
| author | Adrijan Omićević | CMS/MDX | Opcionalno |
Ako već računate metadata za stranicu, izbjegnite dupliciranje logike tako da dohvat sadržaja premjestite u zajednički server-only modul i ponovno ga koristite i u generateMetadata i u OG ruti.
💡 Savjet: Ako je vaš CMS spor, cacheirajte lookup sadržaja odvojeno od byteova slike. Najbrža OG ruta je ona koja u većini zahtjeva uopće ne pogađa CMS.
# Korak 6: Savjeti za Edge runtime i kada koristiti Node runtime#
Edge runtime je privlačan jer generiranje stavlja bliže korisniku i može smanjiti latenciju. No ima i stroga ograničenja.
Edge runtime checklista#
| Stavka | Status na Edge runtimeu | Što napraviti |
|---|---|---|
Node.js moduli poput fs | Nije dostupno | Koristite fetch i bundleajte assete u repo |
| Velike ovisnosti | Rizično | Držite OG rutu minimalnom |
| Sharp ili canvas biblioteke | Nije podržano | Koristite samo next/og rendering |
| Mrežni izlaz prema privatnoj bazi | Često blokirano | Koristite javne API-je ili cacheirani sloj |
| Cold startovi | Uglavnom niski | I dalje držite rutu malom |
Ako vašem OG generiranju treba pristup privatnoj mreži prema bazi, razmislite o pokretanju OG rute na Node runtimeu ili prosljeđivanju kroz API dizajniran za Edge pristup.
Za promjenu uklonite export const runtime = 'edge'; ili ga postavite na Node, ovisno o verziji Next.js-a i mogućnostima deploy okruženja.
# Korak 7: Usklađenost lokalnog razvoja i deploy okruženja#
Čest problem u timskom workflowu je “lokalno izgleda ok, na preview deployu je pokvareno”. Paritet popravljate tako da lokalno ponašanje bude kao produkcija:
- Uvijek učitavajte fontove iz asseta u repozitoriju.
- Izbjegavajte apsolutne URL-ove s
localhostu metapodacima. - Koristite environment varijable za
SITE_URLkada morate generirati apsolutne URL-ove.
Apsolutni naspram relativnih OG URL-ova#
Većina platformi pouzdano prihvaća apsolutne URL-ove. Relativni URL-ovi mogu raditi u nekim kontekstima, ali mogu i failati ovisno o scraperu.
Koristite apsolutni base URL izveden iz environment varijabli.
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
function siteUrl() {
const url = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000';
return url.replace(/\/$/, '');
}
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> }
): Promise<Metadata> {
const { slug } = await params;
const updatedAt = new Date().toISOString().slice(0, 10);
const ogPath = `/og/${slug}?v=${encodeURIComponent(updatedAt)}`;
const ogAbsolute = `${siteUrl()}${ogPath}`;
return {
openGraph: {
images: [{ url: ogAbsolute, width: 1200, height: 630 }],
},
twitter: {
card: 'summary_large_image',
images: [ogAbsolute],
},
};
}⚠️ Upozorenje: Nemojte slučajno poslati
http://localhost:3000u produkcijske metapodatke. ValidirajteNEXT_PUBLIC_SITE_URLu CI-u ili ga postavite na razini platforme.
# Korak 8: Performanse koje stvarno čine razliku#
OG renderiranje može postati skriveni trošak kada:
- post postane viralan i OG ruta dobije puno zahtjeva
- scrapersi traže OG sliku više puta
- imate multi-tenant ili personalizirani OG rendering
Praktične poluge za performanse#
| Poluga | Tipičan utjecaj | Kako implementirati |
|---|---|---|
| Cache headeri | Visok | s-maxage + stale-while-revalidate |
| Verzioniranje URL-a | Visok | ?v=updatedAt ili hash sadržaja |
| Smanjite vanjske pozive | Srednje do visoko | Dohvatite jednom, bez lančanih API poziva |
| Render tree neka bude jednostavan | Srednje | Izbjegavajte ogromne inline SVG-ove ili velike slike |
| Predizračun “linija naslova” | Nisko do srednje | Predvidljivo skraćivanje i prelamanje |
Layout teksta i truncation#
Najčešći vizualni bug je overflow ili odrezani naslovi. Odredite pravilo i provodite ga, npr.:
- maksimalno 90 znakova
- zamijenite uzastopne razmake
- fallback naslov ako je prazan
Cleanup napravite prije renderiranja.
# Rješavanje čestih problema pri deployu#
Ovi se problemi stalno pojavljuju pri deployu Next.js OG generiranja na Vercel, Cloudflare ili container platforme.
1) Prazna slika ili 500 error samo u produkciji#
Tipični uzroci:
- Učitavanje fonta ne uspije zbog patha ili bundlanja
- Korištenje Node API-ja u Edge runtimeu
- Ovisnost koja nije kompatibilna s Edgeom
Rješenja:
- Bundleajte fontove pod
app/og/_assetsi učitavajte prekonew URL(..., import.meta.url) - Uklonite Node-only kod iz OG rute
- Privremeno prebacite na Node runtime kako biste potvrdili jesu li Edge ograničenja uzrok
2) “Unexpected token” ili build errori nakon dodavanja OG markupa#
Tipičan uzrok:
- Slučajni JSX ili MDX parsing problemi drugdje, često zbog nevaljanih znakova ili alata
Rješenja:
- Držite OG rutu kao TypeScript route handler pod
app - Izbjegavajte dynamic importove koji uvlače client komponente u bundle rute
- Izolirajte OG rutu s minimalnim ovisnostima
3) OG slika je zastarjela čak i nakon redeploya#
Tipični uzroci:
- Cache društvene platforme
- CDN cache s dugim TTL-om bez verzioniranja
- Ponovno korišten URL bez verzije sadržaja
Rješenja:
- Verzionirajte URL s
?v=updatedAt - Smanjite
s-maxageako ne možete verzionirati - Koristite debug alate platformi da prisilite re-scrape
4) Fontovi izgledaju ispravno lokalno, ali pogrešno na Vercelu#
Tipični uzroci:
- Lokalni OS font fallback prikriva da font nije bundlan
- Nedostaju težine (weights), npr. traži se 700, a učitan je samo 400
Rješenja:
- Eksplicitno učitajte byteove fonta i navedite ispravan
weight - Dodajte više font težina ako ih koristite u OG layoutu
5) Spori OG odgovori pri prvom zahtjevu#
Tipični uzroci:
- Cold cache
- Težak CMS poziv
- Velika font datoteka ili više fontova
Rješenja:
- Agresivno cacheirajte i verzionirajte URL-ove
- Koristite jednu font težinu gdje je moguće
- Smanjite CMS pozive i koristite mali, cacheirani API response za OG podatke
# Ključne poruke#
- Generirajte previewe po stranici s
next/ogi route handlerima poput/og/[slug], a zatim ih referencirajte ugenerateMetadata. - Bundleajte fontove u repo i proslijedite raw byteove fonta u
ImageResponsekako biste izbjegli lokalne font fallbackove. - Koristite
Cache-Controlsas-maxageistale-while-revalidate, i kombinirajte to s verzioniranjem URL-a poput?v=updatedAt. - Preferirajte stabilne OG URL-ove po stavci kako biste maksimizirali CDN cache hitove i smanjili scraper nedosljednosti.
- Za Edge runtime izbjegavajte Node.js API-je i držite graf ovisnosti OG rute minimalnim kako biste spriječili kvarove koji se događaju samo nakon deploya.
# Zaključak#
Dinamični OG previewi su jedno od poboljšanja s najvećim ROI-jem za SEO i dijeljenje koje možete isporučiti u Next.js aplikaciji, jer svaka stranica dobiva prilagođeni vizual bez dizajnerskog overhead-a. Implementirajte OG rutu s next/og, bundleajte i učitavajte fontove eksplicitno te tretirajte caching i verzioniranje URL-a kao dio funkcionalnosti, a ne kao naknadnu misao.
Ako želite da Samioda implementira dinamične Open Graph slike u Next.js-u s production-grade cachingom, Edge-safe renderiranjem i CMS integracijom, kontaktirajte nas putem samioda.com i isporučit ćemo postavku koja se ponaša isto lokalno, u previewu i u produkciji.
FAQ
Više iz kategorije Web razvoj
Sve →Kontrolni popis za tehnički SEO audit u Next.js-u (App Router): indeksiranje, metapodaci, Core Web Vitals i strukturirani podaci
Kontrolni popis za tehnički SEO audit u Next.js-u (App Router) korak po korak: kontrole crawlanja i indeksiranja, metapodaci i kanonikali, sitemapovi i robots, paginacija, Core Web Vitals te JSON-LD schema s kodom koji možete kopirati i zalijepiti.
Izgradnja React dizajn sustava s dizajnerskim tokenima: Tailwind CSS + Radix UI + TypeScript
Praktičan vodič za 2026. o izgradnji React dizajn sustava s Tailwindom i Radixom uz dizajnerske tokene, pristupačne primitive, tematiziranje i ponovno iskoristive pakete kroz više aplikacija.
Next.js + Supabase SaaS početna arhitektura (App Router): Auth, RLS, naplata i multi-tenancy
Production-ready nacrt za Next.js App Router + Supabase SaaS početnu arhitekturu: autentikacija, Postgres podatkovni model, RLS politike, Stripe naplata i multi-tenant dizajn organizacija s konkretnim primjerima.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Kontrolni popis za tehnički SEO audit u Next.js-u (App Router): indeksiranje, metapodaci, Core Web Vitals i strukturirani podaci
Kontrolni popis za tehnički SEO audit u Next.js-u (App Router) korak po korak: kontrole crawlanja i indeksiranja, metapodaci i kanonikali, sitemapovi i robots, paginacija, Core Web Vitals te JSON-LD schema s kodom koji možete kopirati i zalijepiti.
Kontrolni popis za migraciju na Next.js App Router (s Pages Routera) + česte zamke
Praktičan, korak-po-korak plan migracije na Next.js App Router s Pages Routera, uključujući kontrolni popis za routing, dohvat podataka, SEO metadata, deployment i vodič za rješavanje čestih zamki.
Optimizacija performansi web stranice: Potpuni checklist (Next.js + Core Web Vitals) za 2026.
Praktičan, production-ready checklist za optimizaciju performansi web stranice u Next.js-u: Core Web Vitals, slike, lazy loading, CDN i caching—uz prije/poslije metrike i copy-paste konfiguraciju.