Web razvoj
Next.jsSEOOpen GraphPerformanseEdge RuntimePredmemoriranje

Dinamične Open Graph slike u Next.js-u: generiranje OG slika, predmemoriranje, fontovi i savjeti za Edge runtime

AO
Adrijan Omićević
·13 min čitanja

# Š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#

ZahtjevVerzijaNapomene
Next.js14 ili 15Preporučen App Router
Node.js18+Lokalni dev i build alati
Deploy targetVercel ili sličnoEdge runtime opcionalan
FontoviWOFF ili TTFCommitajte u repo radi podudarnosti
Izvor sadržajaMDX, CMS, DBTreba stabilan slug i naslov

ℹ️ Napomena: next/og se 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:

ObrazacPrimjer URL-aPrednostiNedostaci
Jedna OG ruta za više stavki/api/og?slug=my-postJednostavno, fleksibilnoQuery parametri mogu zakomplicirati caching
Segment rute po stavci/og/my-post.pngStabilni URL-ovi, lakše predmemoriranjeTraž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.ts za rute po stavci
  • Koristite ImageResponse iz next/og
  • Dohvatite naslov i ostale metapodatke koje želite renderirati
TypeScript
// 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 1200x630 za 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.

TypeScript
// 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#

  1. 1
    Stavite font datoteke u repo, npr. app/og/_assets/Inter-SemiBold.ttf
  2. 2
    Učitajte ih s fetch koristeći new URL(..., import.meta.url)
  3. 3
    Proslijedite učitane byteove fonta u ImageResponse
TypeScript
// 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 fontaDobro radi s next/ogVeličina datotekeNapomene
TTFDaSrednjaNajčešći u primjerima
OTFPonekadSrednjaMože failati ovisno o glyph tablicama
WOFF/WOFF2Nije idealnoMalaČ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/font u OG rutama. next/og treba 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 cacheabilnim
  • s-maxage cilja CDN-ove
  • stale-while-revalidate omogućuje brze odgovore dok se u pozadini osvježava
TypeScript
// 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#

PoljePrimjerIzvorRenderira se u OG
slugnextjs-og-imagesURL paramOpcionalno
titleDynamic OG Images in Next.jsCMS/MDXDa
updatedAt2026-05-25CMS/DBKoristi se za ?v=
tagNext.jsCMS/MDXDa
authorAdrijan OmićevićCMS/MDXOpcionalno

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#

StavkaStatus na Edge runtimeuŠto napraviti
Node.js moduli poput fsNije dostupnoKoristite fetch i bundleajte assete u repo
Velike ovisnostiRizičnoDržite OG rutu minimalnom
Sharp ili canvas bibliotekeNije podržanoKoristite samo next/og rendering
Mrežni izlaz prema privatnoj baziČesto blokiranoKoristite javne API-je ili cacheirani sloj
Cold startoviUglavnom niskiI 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 localhost u metapodacima.
  • Koristite environment varijable za SITE_URL kada 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.

TypeScript
// 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:3000 u produkcijske metapodatke. Validirajte NEXT_PUBLIC_SITE_URL u 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#

PolugaTipičan utjecajKako implementirati
Cache headeriVisoks-maxage + stale-while-revalidate
Verzioniranje URL-aVisok?v=updatedAt ili hash sadržaja
Smanjite vanjske poziveSrednje do visokoDohvatite jednom, bez lančanih API poziva
Render tree neka bude jednostavanSrednjeIzbjegavajte ogromne inline SVG-ove ili velike slike
Predizračun “linija naslova”Nisko do srednjePredvidljivo 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/_assets i učitavajte preko new 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-maxage ako 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/og i route handlerima poput /og/[slug], a zatim ih referencirajte u generateMetadata.
  • Bundleajte fontove u repo i proslijedite raw byteove fonta u ImageResponse kako biste izbjegli lokalne font fallbackove.
  • Koristite Cache-Control sa s-maxage i stale-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

Share
A
Adrijan OmićevićSamioda Team
All articles →

Trebate pomoć s projektom?

Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.