eduardweb.
DeploymentIntermediar#seo#nextjs#typescript#react#webdev

Metadata API în Next.js 16: SEO dinamic, OG images și JSON-LD fără bătai de cap

De George Iliescu, 29 iul. 2026 · 12 vizualizări · 3 like-uri

Postat 29 iul. 2026
typescript
import type { Metadata, ResolvingMetadata } from 'next';

type Props = {
  params: Promise<{ slug: string }>;
};

async function getProduct(slug: string) {
  const res = await fetch(`https://api.site.ro/products/${slug}`, {
    next: { revalidate: 3600 }
  });
  if (!res.ok) return null;
  return res.json();
}

export async function generateMetadata(
  { params }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  const { slug } = await params;
  const product = await getProduct(slug);

  if (!product) {
    return { title: 'Produs negăsit' };
  }

  const previousImages = (await parent).openGraph?.images || [];

  return {
    title: `${product.title} | ShopDemo`,
    description: product.summary,
    openGraph: {
      title: product.title,
      description: product.summary,
      images: [`/api/og?title=${encodeURIComponent(product.title)}`, ...previousImages],
    },
  };
}

export default async function ProductPage({ params }: Props) {
  const { slug } = await params;
  const product = await getProduct(slug);

  if (!product) return <div>Produsul nu există.</div>;

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.title,
    image: product.image,
    description: product.summary,
    offers: {
      '@type': 'Offer',
      price: product.price,
      priceCurrency: 'RON',
      availability: 'https://schema.org/InStock',
    },
  };

  return (
    <section>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <h1>{product.title}</h1>
      <p>{product.price} RON</p>
    </section>
  );
}

M-am lovit recent de o problemă clasică pe un e-commerce cu vreo 40k de pagini de produs: SEO-ul arăta groaznic pe rețelele sociale pentru că vechea implementare de SSR făcea request-uri anapoda, iar imaginile de Open Graph erau statice. În Next.js 16, Metadata API și funcția generateMetadata rezolvă elegant problema asta, dar dacă nu ești atent la caching, îți distrugi TTFB-ul instant.

Hai să-ți arăt cum am configurat dynamic metadata, imagini sociale calculate pe loc și structured data (JSON-LD) fără să adaug nicio bibliotecă terță în package.json.

Când folosești generateMetadata și unde te lovești de zid

Dacă ai rute dinamice de tip app/produse/[slug]/page.tsx, Next.js îți permite să exporți o funcție asincronă generateMetadata. Partea faină e că apelurile fetch din generateMetadata beneficiază de memoization automat dacă folosești aceleași endpoint-uri ca în componenta de pagină. Adică Next.js nu face două request-uri identice la API, ci deduplică apelul.

Totuși, am pățit-o la un release: backend-ul nostru de Laravel răspundea în ~400ms. Pentru că generateMetadata rulează pe server înainte să înceapă streaming-ul de HTML, utilizatorul aștepta 400ms doar ca serverul să-și dea seama ce <title> să pună în head.

Trade-off-ul e clar: ai meta tag-uri perfecte pentru crawlerii de Google sau bot-ul de WhatsApp, dar dacă API-ul tău e lent, tot render-ul paginii e blocate. Soluția mea a fost să pun cache la nivel de fetch (next: { revalidate: 3600 }) direct în apelul de date pentru meta.

Imaginile dinamice (OG Images) și baza de URL

Altă chestie de care m-am lovit a fost generarea de link-uri absolute. Facebook și Twitter refuză să afișeze card-ul dacă og:image este o cale relativă gen /images/og.jpg.

În layout.tsx principal, trebuie neapărat să setezi metadataBase. Fără el, Next.js îți aruncă un warning obositor în terminal la fiecare build și ghicește el domeniul pe localhost.

Pentru OG-uri dinamice pe produs, combin generateMetadata cu ImageResponse (inclus direct în Next.js via next/og). Randezi un card HTML/CSS cu prețul, titlul și stocul direct la edge, iar în metadata trimiți doar URL-ul dinamic calculat.

JSON-LD curat fără pachete dubioase

Înainte vedeam des pachete gen next-seo sau tot felul de wrapper-e peste React. În App Router chiar nu mai ai nevoie de așa ceva. Structured Data pentru Google (Schema.org) se pune direct în componenta de pagină printr-un tag <script> cu dangerouslySetInnerHTML.

E o abordare simplă, ai zero JavaScript trimis în bundle-ul de client și Google îl parsează instant. Am observat o creștere de ~14% pe rich snippets în Search Console la o lună după ce am curățat schema de Product și BreadcrumbList.

Voi ce strategie folosiți pentru OG images în producție? Generați totul la edge cu ImageResponse sau le compuneți din backend-ul principal și le serviți dintr-un CDN?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.