Το Next.js Metadata API είναι ο επίσημος μηχανισμός του App Router για να ορίζεις title, description, Open Graph cards, Twitter cards, canonical URLs, robots directives και dynamic OG images από κάθε layout.tsx ή page.tsx, είτε στατικά με το αντικείμενο metadata, είτε δυναμικά με την async function generateMetadata. Σε αντίθεση με τα παλιά <Head> components του Pages Router, το Metadata API εκτελείται 100% στον server, υποστηρίζει deduplication, merging κατά μήκος των layouts και έχει type-safe definitions μέσω του Metadata type. Στην πράξη: αν θες να ranks-άρει η σελίδα σου στο Google και να δείχνει σωστά στο Slack, εκεί ζει η αλήθεια.
Το Metadata API αντικαθιστά πλήρως το next/head στο App Router. Δεν χρειάζεσαι τίποτα τρίτο για βασικό SEO.
Χρησιμοποίησε static metadata για σταθερές σελίδες και generateMetadata για routes με dynamic params (π.χ. /blog/[slug]).
Τα metadata από parent layouts κάνουν merge με τα child. Ορισμός μία φορά στο root layout.tsx καλύπτει όλο το site.
Για social previews χρειάζεσαι openGraph.images 1200×630px και Twitter card type. Χωρίς αυτά, τα links σου δείχνουν "γυμνά".
Με το ImageResponse από το next/og φτιάχνεις per-page dynamic OG images σε edge runtime, χωρίς headless browsers.
Τα sitemap.ts και robots.ts file conventions είναι μέρος του Metadata API. Δεν χρειάζεσαι plugin.
Τι είναι το Next.js Metadata API;
Το Metadata API είναι ένα δηλωτικό σύστημα που εισήχθη στο App Router (Next.js 13.2) και αντικαθιστά πλήρως τα παλιά manual <Head> patterns. Σε κάθε αρχείο layout.tsx, page.tsx (ή route.ts για file conventions όπως sitemap), μπορείς να εξάγεις είτε ένα static metadata object, είτε μια async function generateMetadata. Το Next.js συλλέγει τα metadata από όλα τα nested segments, κάνει merge τα fields και τα σερβίρει ως <head> tags στην HTML response του server.
Στην πράξη, αυτό σημαίνει ότι ένα ορισμένο title.template στο root layout εφαρμόζεται αυτόματα σε όλες τις child pages. Το ίδιο ισχύει για openGraph.siteName, metadataBase, icons και robots. Από Next.js 15 και μετά, το API έχει σταθεροποιηθεί πλήρως, με type-safe support για canonical URLs, languages alternates, theme colors και viewport (το τελευταίο έχει αποσπαστεί σε ξεχωριστό viewport export, οπότε πρόσεξέ το όταν κάνεις migration). Σε σοβαρά projects, το Metadata API είναι το single source of truth, όχι meta tags σκορπισμένα σε διαφορετικά layers.
Static metadata vs generateMetadata: πότε χρησιμοποιώ τι;
Ο κανόνας είναι απλός. Αν τα metadata της σελίδας δεν εξαρτώνται από params, query, cookies ή external data, χρησιμοποίησε το static metadata object. Είναι μηδέν runtime cost και μπορεί να γίνει tree-shake. Αν χρειάζεσαι dynamic τίτλο από τη βάση (/blog/[slug], /products/[id], /users/[username]), χρησιμοποίησε generateMetadata. Το Next.js δεν θα κάνει duplicate fetch. Η fetch() request στο generateMetadata γίνεται deduplicate με την ίδια request στο page.tsx, εφόσον έχουν τα ίδια arguments.
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
type Props = { params: Promise<{ slug: string }> }
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 3600, tags: [`post:${slug}`] },
})
if (!res.ok) return null
return res.json() as Promise<{
title: string; excerpt: string; coverImage: string; publishedAt: string;
}>
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
if (!post) return { title: 'Δεν βρέθηκε' }
return {
title: post.title,
description: post.excerpt,
alternates: { canonical: `/blog/${slug}` },
openGraph: {
title: post.title,
description: post.excerpt,
type: 'article',
publishedTime: post.publishedAt,
images: [{ url: post.coverImage, width: 1200, height: 630, alt: post.title }],
},
twitter: { card: 'summary_large_image', title: post.title, description: post.excerpt },
}
}
export default async function Page({ params }: Props) {
const { slug } = await params
const post = await getPost(slug) // ίδιο fetch, deduplicate από Next
if (!post) notFound()
return <article>{/* ... */}</article>
}
Για routes που χρειάζονται streaming SEO (π.χ. PPR-enabled pages), το generateMetadata τρέχει πάνω από το <Suspense> boundary και αναβάλλει το render του shell μέχρι να επιστρέψει. Γι' αυτό κράτησε τα fetches εκεί όσο πιο γρήγορα γίνεται. Αν χρησιμοποιείς data fetching και caching στο App Router με cache: 'force-cache', το metadata generation γίνεται build-time στις περισσότερες περιπτώσεις.
Πώς προσθέτω σωστά Open Graph και Twitter Cards;
Τα Open Graph metadata είναι αυτά που βλέπουν χρήστες στο Slack, Discord, LinkedIn, Facebook και iMessage όταν επικολλούν το URL σου. Χωρίς αυτά, το link σου δείχνει "γυμνό", απλό URL χωρίς image preview, χωρίς title. Στις περισσότερες περιπτώσεις, αυτό αρκεί για να μην το κλικάρει κανείς. Ο Twitter (X) έχει το δικό του spec, αλλά κάνει fallback στα OG values αν λείπει το αντίστοιχο Twitter tag.
Field
Open Graph
Twitter
Σχόλιο
title
openGraph.title
twitter.title
Twitter κάνει fallback στο OG
description
openGraph.description
twitter.description
Όριο ~200 χαρακτήρων
image
openGraph.images
twitter.images
1200×630 ιδανικά
card type
type: 'article'
card: 'summary_large_image'
Twitter απαιτεί explicit card
locale
openGraph.locale
(δεν υποστηρίζεται)
Format: el_GR, όχι el
siteName
openGraph.siteName
twitter.site
OG: brand name, Twitter: @handle
Στην πράξη, ορίζω τα defaults στο root layout.tsx και κάνω override μόνο τα title, description, images ανά page. Το merging το κάνει το Next αυτόματα. Όμως πρόσεξε: αρχίζοντας από Next.js 14, τα openGraph.images και twitter.images ΔΕΝ κάνουν inherit. Αν τα ορίσεις στο layout και δεν τα override στο page, σταθερά εμφανίζονται. Αν τα override μερικώς (μόνο title), το image του layout διατηρείται. Αυτή η συμπεριφορά κατέγραψε δεκάδες bug reports στο Next.js discussions.
Πώς δημιουργώ dynamic OG images με ImageResponse;
Στο 2023 έπρεπε να τρέχεις headless Chromium για κάθε social card. Σήμερα, με το ImageResponse από το next/og, γράφεις React markup και παίρνεις PNG στο edge runtime σε ~100ms. Το ImageResponse χρησιμοποιεί το Satori κάτω από το hood, μια Rust-based engine που μετατρέπει JSX σε SVG και μετά σε PNG. Δεν χρειάζεται Puppeteer, δεν χρειάζεται serverless cold start με Chromium.
Δημιούργησε ένα opengraph-image.tsx route στο ίδιο folder με το page σου:
Το Next.js αυτόματα τα παίρνει αυτά τα generated images και τα προσθέτει στα openGraph.images και twitter.images των αντίστοιχων pages, αν δεν τα έχεις ήδη ορίσει manually. Σημαντικό: το Satori υποστηρίζει subset του CSS. Δεν δουλεύουν Grid, CSS variables, ή percentage-based dimensions σε flex children. Χρησιμοποίησε flexbox με explicit dimensions και inline styles. Αν θες δυναμικές εικόνες με remote URLs, βεβαιώσου ότι τα remote hosts είναι configured στο next.config.ts όπως κάνεις και με το next/image για image optimization.
Sitemap.ts, robots.ts και canonical URLs
Το Metadata API δεν είναι μόνο meta tags. Περιλαμβάνει και file conventions για sitemap, robots, manifest. Δημιούργησε ένα app/sitemap.ts και επιστρέφει array με τα URLs σου. Το Next.js παράγει το /sitemap.xml automatically στο build. Για sites με χιλιάδες URLs, χρησιμοποίησε generateSitemaps για να σπάσεις σε πολλαπλά sitemap files (η Google δέχεται μέχρι 50.000 URLs ή 50MB ανά file).
Το alternates.canonical στο page metadata είναι κρίσιμο για να μην έχεις duplicate content penalties, ειδικά αν υποστηρίζεις tracking params, pagination, ή filter UI που αλλάζει το URL. Πάντα όρισέ το ρητά σε pages με dynamic params. Αν χρειάζεσαι protection σε admin routes, συνδύασέ το με το Next.js Middleware για authentication και ασφάλεια ώστε bots να μην βρίσκουν καν τα URLs.
Structured data (JSON-LD) και rich results
Το Metadata API δεν έχει first-class support για JSON-LD. Αλλά αυτό δεν είναι περιορισμός, είναι σχεδιαστική επιλογή. Επειδή τα Schema.org types είναι τόσα πολλά (Article, Product, FAQPage, Recipe, Event, BreadcrumbList, Organization), η Vercel team προτείνει να τα κάνεις render ως <script type="application/ld+json"> μέσα στο page component σου. Επειδή τα Server Components κάνουν render σε static HTML, ο Googlebot τα διαβάζει χωρίς JavaScript execution.
Για FAQ rich results, χρειάζεσαι @type: 'FAQPage' με array από Question entities. Για breadcrumbs, @type: 'BreadcrumbList'. Δοκίμασε πάντα τα schemas στο Rich Results Test της Google πριν τα ανεβάσεις. Από προσωπική εμπειρία: μην προσθέτεις schema που δεν αντιστοιχεί στο visible content. Η Google έχει manual actions για deceptive structured data, και το έχω δει σε πελάτη να ρίχνει traffic 30% για 6 εβδομάδες.
Internationalization και hreflang
Αν τρέχεις multilingual site, τα alternates.languages είναι ο τρόπος να πεις στη Google ποια version να σερβίρει σε ποιο locale. Χωρίς hreflang, η Google μπορεί να σερβίρει την αγγλική version σε Έλληνες χρήστες, ή να θεωρήσει ότι έχεις duplicate content. Το format είναι κρίσιμο: BCP 47 language tags (el, el-GR, en, en-US) και απόλυτα URLs.
Σημείωσε το x-default. Λέει στη Google ποια version να σερβίρει σε χρήστες με locale που δεν υποστηρίζεις. Πρέπει να είναι πάντα παρόν σε multilingual setups. Για την πραγματική logic του routing, δες πώς να ρυθμίσεις το middleware για i18n και ανίχνευση locale.
Συχνά λάθη και debugging
Αφού έχω στήσει το Metadata API σε 6+ production apps, υπάρχουν 5 footguns που τραβάει σχεδόν κάθε ομάδα την πρώτη φορά. Καταγράφω εδώ τι να ψάχνεις:
Λείπει το metadataBase: warning στο build, σπασμένα social cards στα preview URLs. Σταθερά set στο root layout.
Mixing 'use client' με metadata: το Metadata API είναι server-only. Αν προσπαθήσεις να εξάγεις metadata από client component, το Next το αγνοεί silently.
Override μόνο μερικών OG fields: αν στο child page ορίσεις μόνο openGraph.title, τα openGraph.images του parent ΚΡΑΤΟΥΝΤΑΙ, που είναι συνήθως αυτό που θες, αλλά όχι πάντα. Να είσαι ρητός.
Ξεχνάς το viewport export: από Next.js 14, το themeColor και colorScheme ΔΕΝ μπαίνουν στο metadata. Θα δεις deprecation warning στο dev server.
Duplicate fetches στο dev: αν το generateMetadata και το page κάνουν διαφορετικά arguments στο fetch(), το request γίνεται δύο φορές. Πέρασε τα ίδια headers/params για deduplication.
Για debugging, χρησιμοποίησε το View Source στον browser και ψάξε τα meta tags. Το React DevTools δεν τα δείχνει επειδή είναι server-rendered. Στο production, το OpenGraph.xyz validator δείχνει πώς θα φαίνεται το link σου σε Slack, Twitter, LinkedIn και iMessage. Πίστεψέ με, αξίζει 5 λεπτά πριν από κάθε major launch.
Συχνές Ερωτήσεις
Πού ορίζω τα SEO metadata στο Next.js App Router;
Εξάγεις ένα metadata object ή μια generateMetadata function από οποιοδήποτε layout.tsx ή page.tsx. Τα metadata από parent layouts κάνουν merge με τα child, οπότε ορίζεις defaults στο root και κάνεις override per route.
Ποια η διαφορά static metadata vs generateMetadata;
Το static metadata είναι ένα plain object για σταθερές σελίδες, με μηδέν runtime cost. Το generateMetadata είναι async function για routes που εξαρτώνται από params ή external data (π.χ. blog posts, product pages). Το Next.js κάνει automatic deduplication των fetch requests μεταξύ generateMetadata και page.
Πώς φτιάχνω dynamic OG images στο Next.js;
Δημιούργησε ένα opengraph-image.tsx file στο route folder σου, εισάγαγε το ImageResponse από next/og, και επιστρέφεις JSX. Το Next.js τα κάνει render σε edge runtime με το Satori (όχι Puppeteer), ~100ms latency. Τα generated images γίνονται automatically attach στα Open Graph και Twitter metadata.
Χρειάζομαι το next-seo package στο App Router;
Όχι. Το next-seo ήταν essential στο Pages Router, αλλά το Metadata API του App Router καλύπτει 100% των use cases: OG tags, Twitter cards, canonical, robots, JSON-LD, sitemap, hreflang. Επιπλέον είναι server-only και type-safe με το Metadata type.
Γιατί δεν εμφανίζεται το preview image στο Slack ή Twitter;
Συνηθέστερες αιτίες: (1) λείπει το metadataBase στο root layout, οπότε relative URLs δεν γίνονται resolve, (2) το image δεν είναι 1200×630px ή είναι >5MB, (3) λείπει το twitter.card: 'summary_large_image'. Χρησιμοποίησε το OpenGraph.xyz validator για να δεις τι ακριβώς διαβάζει κάθε platform.
Πλήρης οδηγός για Auth.js v5 στο Next.js 15 App Router: OAuth, Credentials, JWT vs database sessions, middleware και Drizzle adapter με έτοιμα παραδείγματα.
Πώς να διαχειριστείτε σφάλματα στο Next.js App Router με error.tsx, global-error.tsx, not-found.tsx και Server Actions. Οδηγός με useActionState, Sentry monitoring και πρακτικά παραδείγματα κώδικα.