Next.js bringt alles mit, was Sie für technisches SEO brauchen, aber fast nichts davon ist standardmäßig aktiv. Das ist die Checkliste, die ich bei jedem App-Router-Projekt durchgehe, grob nach Wirkung sortiert.
1. Wichtige Inhalte serverseitig rendern
Suchmaschinen können JavaScript ausführen, indexieren serverseitig gerendertes HTML aber schneller und zuverlässiger. Im App Router sind Server Components der Standard – lassen Sie Überschriften, Fließtext und Links darin und nutzen Sie Client Components für Interaktion, nicht für Inhalte.
Ein schneller Test: Öffnen Sie Ihre Seite mit deaktiviertem JavaScript oder führen Sie curl https://yoursite.com/page aus. Steht der Haupttext nicht in der Antwort, steht er auch nicht im ersten Indexierungsdurchlauf.
2. metadataBase und eine Titelvorlage setzen
Im Root-Layout:
import type { Metadata } from 'next'
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
title: {
default: 'Example Studio — Web Design and Development',
template: '%s — Example Studio',
},
description: 'Websites and products for small teams: design, development and launch.',
openGraph: { siteName: 'Example Studio', type: 'website' },
}metadataBase macht aus relativen URLs (kanonische URL, Open-Graph-Bilder) absolute. Dank der Vorlage setzen Unterseiten nur ihren eigenen Teil des Titels.
3. Eigene Titel und Beschreibungen pro Seite
Jede indexierbare Seite braucht einen eigenen <title> (etwa 50–60 Zeichen, das Keyword weit vorne) und eine eigene Meta-Beschreibung (etwa 150–160 Zeichen, geschrieben, um angeklickt zu werden). Für dynamische Seiten nutzen Sie generateMetadata:
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await getPost(slug)
if (!post) return {}
return {
title: post.title,
description: post.excerpt,
alternates: { canonical: `/blog/${post.slug}` },
openGraph: {
type: 'article',
publishedTime: post.date,
images: [{ url: post.cover, width: 1200, height: 630 }],
},
}
}4. Kanonische URLs
Setzen Sie alternates.canonical auf jeder Seite. Das schützt vor Duplikaten durch Query-Strings (?utm_source=...), Schrägstriche am Ende und alternative Domains. Entscheiden Sie sich für eine Form – mit oder ohne www, mit oder ohne abschließenden Schrägstrich – und leiten Sie die anderen beim Hoster darauf um.
5. Eine Sitemap
app/sitemap.ts erzeugt /sitemap.xml, auch bei einem statischen Export:
import type { MetadataRoute } from 'next'
import { getAllPosts } from '@/lib/blog'
export const dynamic = 'force-static'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const base = 'https://example.com'
const posts = await getAllPosts()
return [
{ url: base, changeFrequency: 'monthly', priority: 1 },
...posts.map((post) => ({
url: `${base}/blog/${post.slug}`,
lastModified: post.date,
changeFrequency: 'yearly' as const,
priority: 0.7,
})),
]
}Reichen Sie sie einmal in der Google Search Console und in den Bing Webmaster Tools ein.
6. robots.txt
import type { MetadataRoute } from 'next'
export const dynamic = 'force-static'
export default function robots(): MetadataRoute.Robots {
return {
rules: { userAgent: '*', allow: '/' },
sitemap: 'https://example.com/sitemap.xml',
}
}Verstecken Sie Seiten nicht per robots.txt vor dem Index – eine gesperrte Seite kann trotzdem über Links indexiert werden. Verwenden Sie stattdessen robots: { index: false } in den Metadaten der Seite.
7. Strukturierte Daten (JSON-LD)
Strukturierte Daten helfen Suchmaschinen zu verstehen, was eine Seite ist. Für einen Artikel rendern Sie ein Script-Tag in der Seite:
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.excerpt,
datePublished: post.date,
image: `https://example.com${post.cover}`,
author: { '@type': 'Person', name: 'Jane Doe', url: 'https://example.com' },
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, '\\u003c') }}
/>
)Nützliche Typen für kleine Unternehmen: Organization oder Person auf der Startseite, BlogPosting für Artikel, BreadcrumbList auf tiefer liegenden Seiten, Product auf Shopseiten. Prüfen Sie sie mit dem Test für Rich-Suchergebnisse von Google.
8. Open-Graph-Bilder
Links, die auf LinkedIn, X, Slack und in Messengern geteilt werden, nutzen Open-Graph-Tags. Geben Sie jeder Seite ein Bild mit 1200×630 Pixeln. Sie können ein statisches opengraph-image.png neben eine Seite legen oder eines mit ImageResponse in opengraph-image.tsx erzeugen (beim statischen Export wird es beim Build erzeugt und kann daher nicht von der Anfrage abhängen).
9. Mehrsprachige Websites: echte URLs und hreflang
Jede Sprache braucht ihre eigene URL, sonst sehen Suchmaschinen immer nur eine davon. Ein Sprachwechsel im Browser per localStorage ist für Besucher in Ordnung, für Google aber unsichtbar. Verwenden Sie ein Sprachsegment – app/[lang]/... – und geben Sie die Alternativen an:
alternates: {
canonical: '/en/blog/my-post',
languages: {
en: '/en/blog/my-post',
'zh-CN': '/zh/blog/my-post',
'sr-Latn': '/sr/blog/my-post',
'x-default': '/en/blog/my-post',
},
}Jede Sprachversion sollte alle anderen auflisten, sich selbst eingeschlossen.
10. Interne Links und Überschriften
- Eine
<h1>pro Seite, dann<h2>/<h3>in der richtigen Reihenfolge. Überschriften sind eine Gliederung, keine Schriftgröße. - Verlinken Sie verwandte Seiten untereinander mit aussagekräftigem Ankertext („Leitfaden zum statischen Export“, nicht „hier klicken“). Verwenden Sie
<Link>, damit die Links echte<a href>-Elemente sind. - Halten Sie wichtige Seiten höchstens drei Klicks von der Startseite entfernt.
11. Core Web Vitals
Geschwindigkeit ist ein Ranking-Signal und vor allem ein Conversion-Signal. Messen Sie mit PageSpeed Insights (die Felddaten stammen von echten Chrome-Nutzern) und zielen Sie auf LCP ≤ 2,5 s, INP ≤ 200 ms und CLS ≤ 0,1. Die üblichen Schuldigen im App Router: ein überdimensioniertes Hero-Bild, zu viel clientseitiges JavaScript und Layoutverschiebungen durch Schriften oder Bilder ohne Abmessungen. Wenn Ihre Website stark animiert ist, siehe Core Web Vitals für animierte Websites.
12. Bilder
- Aussagekräftiger
alt-Text bei inhaltlichen Bildern; leeresalt=""bei dekorativen. - Explizite
width/heightoder einaspect-ratio, um Layoutverschiebungen zu vermeiden. - Moderne Formate (AVIF, WebP) und sinnvolle Größen.
- Sprechende Dateinamen:
nextjs-sitemap-example.png, nichtIMG_2041.png.
13. 404-Seiten und Weiterleitungen
Liefern Sie für fehlende Seiten ein echtes 404 aus (notFound() im App Router). Wenn Sie Seiten verschieben oder umbenennen, richten Sie eine 301-Weiterleitung von der alten URL ein – beim statischen Export ist das eine _redirects-Regel beim Hoster.
Die Kurzfassung
- Inhalte in Server Components
-
metadataBaseund eine Titelvorlage im Root-Layout - Eigener Titel, eigene Beschreibung und kanonische URL auf jeder Seite
-
sitemap.tsundrobots.ts, Sitemap eingereicht - JSON-LD für Artikel, Organisation, Breadcrumbs
- Open-Graph-Bild auf jeder Seite
- Eine URL pro Sprache, mit hreflang
- Logische Überschriften und aussagekräftige interne Links
- Core Web Vitals im grünen Bereich
- Echte 404-Seiten und 301-Weiterleitungen für verschobene Seiten
Sie bauen eine statische Website? Lesen Sie den Leitfaden zum statischen Export mit Next.js für die Teile, die ohne Server anders funktionieren.

