generateMetadata v Next.js 16: Dynamický SEO, Open Graph obrázky a sitemap
Kompletní průvodce po Metadata API v Next.js 16. Naučte se používat generateMetadata pro dynamické titulky, vytvářet Open Graph obrázky přes opengraph-image.tsx, generovat sitemap.ts i robots.ts a přidávat JSON-LD pro rich snippets.
generateMetadata je asynchronní funkce v Next.js 16 App Routeru, která generuje <title>, <meta>, Open Graph tagy a další SEO metadata dynamicky na základě dat stránky, parametrů URL nebo obsahu z databáze. Používá se místo statického exportu metadata, když hodnoty závisí na runtime datech (typicky detail produktu, článek blogu nebo profil uživatele). V tomhle průvodci projdeme kompletní Metadata API v Next.js 16, dynamické Open Graph obrázky přes opengraph-image.tsx, generování sitemap.ts a robots.ts, JSON-LD strukturovaná data i časté chyby, které stojí projekty pozici v Google. Popravdě, na většinu z nich jsem si sám za posledních pár releasů nabil nos.
V Next.js 16 se metadata definují buď staticky (export const metadata) nebo dynamicky přes export async function generateMetadata(). Druhý přístup je vyžadován, jakmile hodnoty závisí na params, searchParams nebo fetchovaných datech.
Metadata z rodičovských layoutů se dědí a slučují (shallow merge); pole jako title.template umožňují jednotné formátování napříč sekcí.
Soubor opengraph-image.tsx generuje 1200×630 OG obrázek za běhu pomocí ImageResponse, čímž nahrazuje ruční nastavení openGraph.images.
Konvence sitemap.ts a robots.ts nahrazují statické soubory a umožňují programové generování z CMS nebo databáze.
Metadata jsou renderovány server-side a jsou plně kompatibilní s Cache Components a Partial Prerendering v Next.js 16.
JSON-LD se vkládá jako <script type="application/ld+json"> přímo do stránky. Next.js pro něj nemá dedikované API, ale vzor je jednoduchý.
Statická vs. dynamická metadata: kdy použít kterou
Next.js 16 poskytuje dvě cesty, jak nastavit SEO metadata pro stránku nebo layout. Statický export const metadata se hodí všude, kde titulek a popis nezávisí na datech (typicky stránka /about, /pricing nebo landing page). Dynamický generateMetadata nasazujete kdykoli je hodnota funkcí runtime kontextu: slug článku, ID produktu, jazyk detekovaný z headers(), nebo výsledek dotazu do databáze.
Rozhodovací pravidlo je jednoduché. Pokud potřebujete znát params, searchParams nebo cokoli, co přichází v request scope, musíte použít asynchronní funkci. Statický export je za běhu rychlejší (žádný fetch), ale nedokáže reagovat na dynamický obsah. Oba přístupy vracejí stejný tvar objektu; rozdíl je jen v tom, zda hodnoty známe v build time nebo až v runtime. V Next.js 16 platí, že params je Promise, takže i uvnitř generateMetadata se na ni musí čekat pomocí await.
// app/about/page.tsx (statická metadata)
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'O nás | Next.js Launchpad',
description: 'Kdo jsme a proč píšeme o Next.js.',
openGraph: {
title: 'O nás',
description: 'Tým redaktorů zaměřený na Next.js App Router.',
url: 'https://nextjslaunchpad.com/about',
siteName: 'Next.js Launchpad',
locale: 'cs_CZ',
type: 'website',
},
}
export default function AboutPage() {
return <main>...</main>
}
Jak funguje generateMetadata krok za krokem
Funkce generateMetadata běží na serveru před renderem stránky. Přijímá dva argumenty. První obsahuje params a searchParams (obě jsou Promise v Next.js 16), druhý je parent, tedy Promise s metadaty rodičovského segmentu, které můžete použít pro dědění. Návratová hodnota může být buď objekt Metadata, nebo Promise s tímto objektem. Uvnitř funkce můžete fetchovat data přesně tak, jak byste to dělali v Server Component, a Next.js 16 tyhle fetche automaticky deduplikuje s fetchem uvnitř samotné stránky.
Typický scénář: detail článku, kde titulek, popis a OG obrázek pochází z databáze. Klíčová optimalizace je, že stejný fetch nebo databázový dotaz uvnitř generateMetadata i uvnitř page.tsx se spustí jen jednou, protože si ho Next.js zapamatuje v request cache. V praxi to znamená, že vytáhnout data ve dvou místech je zdarma z hlediska výkonu, pokud používáte stejný cachovací klíč. V jednom z mých projektů jsem tenhle detail poznal až podle profileru; do té doby jsem si myslel, že musím data protahovat přes globální kontext.
Klíčové pole, které lidé často opomíjejí, je alternates.canonical. Bez explicitního canonical URL Google často indexuje duplicity vzniklé z tracking parametrů (?utm_source=...) jako samostatné stránky. Vždy nastavujte kanonickou URL na verzi bez parametrů. Podobně alternates.languages říká Google, které jazykové varianty existují; bez toho hreflang tagy chybí a mezinárodní SEO strategie se rozpadá.
Dědění a slučování metadat mezi layouty
Metadata se v App Routeru dědí odshora dolů podle segmentů URL. Kořenový app/layout.tsx nastavuje výchozí hodnoty, každý podřazený layout je může přepsat nebo doplnit. Slučování je shallow, tedy pole jako openGraph se nahrazuje jako celek a nikoli hluboko slučuje. Pokud v podřazeném segmentu definujete openGraph.title, musíte znovu uvést i openGraph.description, jinak zmizí.
Speciální mechanismus je title.template. Umožňuje definovat šablonu jako '%s | Můj web' na úrovni layoutu a dceřiné stránky pak nastaví jen svůj vlastní titulek. Výsledek je automaticky zabalen do šablony. Pokud potřebujete titulek, který se do šablony nezaobalí (například homepage), použijte title.absolute.
Pole metadataBase je zásadní. Říká Next.js, jaká je základní URL pro všechny relativní cesty v metadatech. Bez něj se relativní URL v openGraph.images nerozřeší a v produkci uvidíte upozornění v konzoli. Vždy ho nastavte v kořenovém layoutu, nejlépe z environment proměnné, aby staging a produkce používaly správnou doménu.
opengraph-image.tsx: dynamické OG obrázky
Před Next.js 13 se OG obrázky generovaly buď staticky (PNG v public/), nebo přes externí služby jako Cloudinary. Konvence souboru opengraph-image.tsx to zásadně zjednodušila. V jakémkoli segmentu App Routeru vytvoříte soubor s tímto názvem, exportujete funkci, která vrací ImageResponse, a Next.js automaticky vygeneruje 1200×630 obrázek při buildu (statický segment) nebo při requestu (dynamický segment). URL obrázku je automaticky přidána do openGraph.images, takže ji nemusíte ručně registrovat.
ImageResponse renderuje JSX pomocí Satori (podprogram v Rust portu) do PNG. Podporovaná podmnožina CSS je omezená (flexbox ano, grid ne, žádné externí fonty bez explicitního načtení), ale pro OG karty je víc než dostatečná. Pro dynamické OG karty se nejčastěji čte parametr URL, načtou se data z databáze a text se vloží do šablony.
Rozměr 1200×630 pixelů je standard, na kterém se shodly Facebook, LinkedIn, X i Slack. Pokud potřebujete jinou variantu pro Twitter (například čtvercovou), vytvořte druhý soubor twitter-image.tsx s vlastními rozměry. Next.js 16 obě konvence hlídá a přidá odpovídající meta tagy.
sitemap.ts a robots.ts v praxi
Sitemap a robots.txt patří k základní SEO výbavě, ale v Next.js 16 jsou elegantně řešeny konvencí souborů, které vracejí data programově. Konec statického public/sitemap.xml, který se musí manuálně regenerovat. Místo toho definujete app/sitemap.ts, který vrací pole objektů, a Next.js z něj vygeneruje validní XML.
Pro velké weby (nad 50 000 URL) existuje konvence sitemap.ts uvnitř dynamického segmentu s exportem generateSitemaps, který vrací více sitemap souborů a jeden hlavní sitemap index. Google akceptuje maximálně 50 000 URL nebo 50 MB na jeden soubor, takže rozdělení je pro rozsáhlé katalogy nezbytné.
Konvence robots.ts je analogicky programová verze robots.txt. Můžete v ní podmínečně blokovat prostředí (třeba staging přes environment proměnnou), povolit různá pravidla pro různé user-agenty, nebo dynamicky přidat cesty na základě obsahu.
Podmíněné blokování stagingu je jedna z nejčastěji zapomínaných pojistek. Bez ní Google indexuje verzi staging.example.com a v SERP se objevují duplicity, které kanibalizují váš vlastní obsah. Sám jsem si tímhle prošel a Search Console pak měsíc čistil hlášené duplicitní stránky. Testujte oba případy v Google Robots Testing Tool. Pokud řešíte i mezinárodní SEO a URL prefixy, hodí se propojit sitemap s middlewarovou vrstvou popsanou v průvodci Next.js 16 proxy.ts.
JSON-LD strukturovaná data pro rich snippets
Next.js 16 nemá vestavěné API pro JSON-LD, protože strukturovaná data mají příliš mnoho variant (Article, Product, FAQPage, BreadcrumbList, VideoObject…). Doporučený vzor je vygenerovat objekt v Server Componentě a vypsat ho jako <script type="application/ld+json"> přímo do body stránky. Google i Bing tento formát preferují a Search Console poskytuje detailní diagnostiku toho, které typy jsou rozpoznány.
Pro FAQ sekci v článku je adekvátní typ FAQPage, který Google zobrazí jako rozbalovací seznam přímo v SERP. Pro navigační drobečky použijte BreadcrumbList; ten nahradí URL cestu čitelnějším formátem. Kompletní přehled podporovaných typů je v Google Search Structured Data gallery.
Viewport export a theme-color
Od Next.js 14 jsou viewport, colorScheme a themeColor odděleny od hlavního metadata exportu a mají vlastní export viewport. Důvod je pragmatický: viewport a theme-color se často mění mezi routami (třeba tmavé pozadí pro landing page vs. bílé pro dashboard) a oddělený export tuhle změnu činí explicitnější.
themeColor se v mobilních prohlížečích promítne do barvy adresního řádku (Chrome, Samsung Internet). Kombinace se @media query znamená, že iOS Safari použije správnou variantu podle systémového nastavení uživatele. Pro PWA to je jeden z nejlevnějších způsobů, jak dosáhnout „nativního" pocitu bez extra kódu.
Časté chyby a jak se jim vyhnout
Chybí metadataBase, relativní URL se rozpadnou
Nejčastější chyba, kterou vídám v produkčních buildech: bez metadataBase Next.js log varuje, že relativní URL v openGraph.images nemůže rozřešit a rezignuje na resolved URL v <meta> tagu. Facebook a Twitter potřebují absolutní URL, jinak sdílení karty selže. Vždy nastavte metadataBase v kořenovém layoutu a čtěte hodnotu z environment proměnné.
Duplicitní titulky mezi šablonou a stránkou
Když v layoutu nastavíte title: { template: '%s | Můj web', default: 'Můj web' } a stránka pak nastaví title: 'Můj web | Domů', výsledek je Můj web | Domů | Můj web. Řešení: pro rootové stránky používejte title.absolute, které šablonu obchází, nebo nastavujte title ve stránkách bez opakování suffixu.
OG obrázek se neaktualizuje po redeployi
Facebook i LinkedIn agresivně cachují první verzi OG obrázku. Pokud vygenerujete novou verzi, sdílení dál ukazuje starou. Použijte Facebook Sharing Debugger a klikněte na „Scrape Again". Pro Twitter použijte Card Validator. Trvalá cache-busting strategie je přidat query parametr s hash contentu (třeba ?v={updatedAt}).
Sitemap obsahuje neveřejné URL
Když generujete sitemap z databáze, nezapomeňte filtrovat drafty a smazané položky. Jinak Google Search Console začne hlásit „soft 404" pro každou položku, která vrací 404 přes notFound(). Vždy v query přidejte WHERE status = 'published' AND deletedAt IS NULL. Tohle jsem si musel naordinovat jako check-listový bod, protože jsem tam draft pushnul hned dvakrát po sobě.
Metadata se generují znovu při každém requestu
Pokud generateMetadata volá pomalý externí API a stránka je vysoce navštěvovaná, můžete narazit na výkonnostní strop. Řešení: buď segment označte jako export const revalidate = 3600, nebo použijte fetch(url, { next: { revalidate: 3600 } }), nebo přejděte na 'use cache' direktivu. V našem průvodci Route Handlers pro REST API v App Router najdete detaily o kombinování cachovacích strategií napříč vrstvami.
OG obrázek nemá správné proporce
Facebook doporučuje 1200×630 (1.91:1), ale některé mobilní nástroje ořezávají na čtverec. Kompozice s klíčovým obsahem uprostřed (ne u okrajů) zajistí, že náhled dobře vypadá na všech platformách. Pro obrázky nad optimalizací v souvisejících kontextech doporučujeme článek next/image v Next.js 16 a Core Web Vitals.
Chybí canonical URL, Google indexuje duplicity
Nejčastější důvod, proč se stránka propadá v rankingu i s dobře napsaným obsahem: uživatelé sdílejí URL s tracking parametry (?utm_source=twitter), Google je indexuje jako samostatné verze a rozděluje mezi ně link equity. Vždy nastavujte alternates.canonical na verzi bez query parametrů. Pro detaily o kanonicích a hreflang doporučujeme Google Search Central: Consolidate duplicate URLs.
Fonty v ImageResponse spadnou na fallback
Bez explicitního načtení fontu Satori vykreslí text v systémovém fontu. U českých znaků to znamená, že diakritika buď vypadne, nebo se nahradí čtverečkem. Vždy načtěte font jako ArrayBuffer a předejte ho v fonts poli. Pokud nechcete bundlovat font do repozitáře, uložte ho do public/ a načtěte přes fetch(new URL('/fonts/Inter.ttf', request.url)).
Jaký je rozdíl mezi metadata a generateMetadata v Next.js 16?
Statický export const metadata se vyhodnocuje v build time a nemá přístup k params ani searchParams. Asynchronní generateMetadata běží v runtime, přijímá parametry stránky a může fetchovat data. Použijte statickou verzi pro stránky s pevným obsahem, dynamickou pro detaily, které závisejí na datech.
Jak vytvořit dynamický Open Graph obrázek v Next.js 16?
Vytvořte soubor opengraph-image.tsx uvnitř segmentu App Routeru. Exportujte funkci vracející ImageResponse z next/og, uvnitř použijte JSX s podmnožinou CSS (flexbox, absolute positioning). Next.js automaticky přidá URL obrázku do openGraph.images a vygeneruje ho v build time nebo za běhu.
Musím ručně vytvářet sitemap.xml v Next.js 16?
Ne. Použijte konvenci app/sitemap.ts, která exportuje funkci vracející pole objektů s url, lastModified, changeFrequency a priority. Next.js z ní vygeneruje validní XML na cestě /sitemap.xml. Pro weby nad 50 000 URL použijte generateSitemaps pro rozdělení do více souborů.
Fungují metadata s Cache Components a Partial Prerendering?
Ano, plně. Statická metadata se vloží do prerenderované statické shell verze. Dynamické generateMetadata běží na serveru a lze ho označit direktivou 'use cache' pro sdílení mezi requesty. Fetche uvnitř generateMetadata se automaticky deduplikují s fetchem uvnitř page komponenty.
Kde v layoutu se má nastavit metadataBase?
Vždy v kořenovém app/layout.tsx jako metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL!). Hodnota se dědí do všech podřazených segmentů a používá se pro rozřešení relativních URL v openGraph.images, twitter.images a alternates.canonical. Bez ní Next.js log varuje a náhledy sdílení selžou.