Das Wichtigste in Kürze
- Google verarbeitet JavaScript-Seiten in drei Phasen: Crawling, Rendering, Indexierung. Gerendert werden Seiten mit Status 200.
- Links müssen <a>-Elemente mit href sein. Fragment-URLs wie #/seite sind laut Google schlechte Praxis.
- Ein noindex im Original-HTML kann dazu führen, dass Google Rendering und JavaScript überspringt. Entfernen per JavaScript ist unzuverlässig.
- Server-seitiges Rendering oder Pre-Rendering nennt Google weiterhin eine gute Idee, auch für Crawler ohne JavaScript.
Wie Google JavaScript-Seiten verarbeitet
Googles Dokumentation „JavaScript SEO basics" beschreibt drei Phasen: Crawling, Rendering und Indexierung. Googlebot ruft die URL ab, stellt sie danach in eine Warteschlange zum Rendern und indexiert das gerenderte HTML. Seiten mit dem Status 200 kommen in diese Rendering-Warteschlange, und das gerenderte HTML wird erneut nach Links durchsucht.
Daraus folgen drei Konsequenzen, die den Rest dieses Artikels prägen:
- Was erst nach dem Rendering im DOM steht, sieht Google später als das, was schon im ersten HTML steht. Inhalte im ersten HTML sind unabhängig vom Rendering.
- Wenn das Crawling gesperrt ist, passiert nichts weiter: Die Dokumentation sagt, dass Google JavaScript aus blockierten Dateien oder auf blockierten Seiten nicht rendert.
- Nicht jede Information darf von JavaScript abhängen. Der Statuscode entscheidet, ob eine Seite überhaupt gerendert wird, und ein
noindexim Original-HTML kann das Rendering verhindern.
Die Grundlagen von Crawling und Indexierung findest du im Leitfaden zu technischem SEO.
CSR, SSR, SSG und Hydration im Vergleich
Wie viel Arbeit Google beim Rendern hat, hängt von deiner Architektur ab. Die Tabelle beschreibt die gängigen Muster allgemein.
| Muster | Wo entsteht das HTML? | Inhalt im ersten HTML? | Aufwand/Risiko für Indexierung |
|---|---|---|---|
| CSR (Client-Side Rendering) | im Browser, per JavaScript | meist nur eine leere Hülle | höchstes Risiko: Inhalte, Links und Titel hängen vom Rendering ab |
| SSR (Server-Side Rendering) | auf dem Server bei jedem Aufruf | ja | gering, braucht aber einen laufenden Server |
| SSG (Static Site Generation) | beim Build, als fertige Dateien | ja | gering, geeignet für Inhalte, die sich selten ändern |
| Hydration | Server oder Build liefert HTML, JavaScript macht es interaktiv | ja | gering für Inhalte, Interaktivität entsteht erst im Browser |
| Islands | statisches HTML, nur einzelne Bereiche laden JavaScript | ja | gering, wenig JavaScript im Client |
Google schreibt dazu in der Dokumentation, dass serverseitiges Rendering oder Pre-Rendering weiterhin eine gute Idee ist, weil es Nutzern und Crawlern hilft, auch solchen, die kein JavaScript ausführen. Ein Beispiel für den statischen Ansatz mit Islands ist Astro. Für Inhalte aus einem Headless CMS gilt dasselbe: Entscheidend ist, ob das HTML mit dem Inhalt beim Abruf schon da ist.
Praxis-Tipp: Prüfe bei jeder neuen Seitenart, was ein Abruf ohne JavaScript liefert. Steht der Haupttext nicht im HTML, ist deine Seite von der Rendering-Phase abhängig.
Links müssen echte Links sein
Googlebot entdeckt Links nach der Dokumentation zuverlässig nur in <a>-Elementen mit href. Mit JavaScript eingefügte Links sind in Ordnung, wenn sie dieses Markup nutzen. Ein span mit onclick oder ein routerLink ohne href wird möglicherweise nicht erkannt, javascript:-Werte im href lösen sich unter Umständen nicht zuverlässig auf.
<!-- Crawlbar -->
<a href="/produkte/laufschuhe/">Laufschuhe ansehen</a>
<!-- Nicht zuverlässig crawlbar -->
<span onclick="goTo('/produkte/laufschuhe/')">Laufschuhe ansehen</span>
<a href="javascript:goTo('/produkte/laufschuhe/')">Laufschuhe ansehen</a>Bei Single-Page-Apps ist das Routing der häufigste Stolperstein. Fragment-URLs wie #/produkte bezeichnet Google als schlechte Praxis. Stattdessen verwendest du die History API, damit jede Ansicht eine echte, eigene URL hat:
document.querySelector('#nav').addEventListener('click', (event) => {
const link = event.target.closest('a[href]');
if (!link) return;
event.preventDefault();
history.pushState({}, '', link.href);
renderRoute(location.pathname);
});Die Links bleiben echte <a href>-Elemente, JavaScript fängt nur den Klick ab. Ohne JavaScript funktioniert der Link normal weiter.
Statuscodes und Soft 404 in Single-Page-Apps
In einer klassischen Single-Page-App liefert der Server für jede Route dieselbe HTML-Hülle mit Status 200, auch für Adressen, die es nicht gibt. Google nennt das in der Konsequenz Soft 404: eine Seite, die mit 200 antwortet, aber wie ein Fehler aussieht. Die Dokumentation nennt zwei Auswege für clientseitig gerenderte Apps:
- per JavaScript auf eine URL weiterleiten, die einen 404 liefert, oder
- per JavaScript ein
noindex-Meta-Tag setzen.
Sauberer ist es, den richtigen Statuscode gleich vom Server zu senden. Das folgende Node.js-Beispiel liefert dieselbe HTML-Hülle für bekannte Routen mit 200 und für unbekannte mit 404:
import http from 'node:http';
const routes = new Set(['/', '/produkte/']);
const shell = `<!doctype html>
<html lang="de"><head><meta charset="utf-8"><title>App</title></head>
<body><div id="app"></div><script src="/app.js"></script></body></html>`;
const server = http.createServer((req, res) => {
const { pathname } = new URL(req.url, 'http://localhost');
if (pathname === '/app.js') {
res.writeHead(200, { 'content-type': 'text/javascript' });
return res.end('');
}
const known = routes.has(pathname);
res.writeHead(known ? 200 : 404, { 'content-type': 'text/html; charset=utf-8' });
res.end(shell);
});
await new Promise((r) => server.listen(0, r));
const base = `http://localhost:${server.address().port}`;
for (const p of ['/produkte/', '/gibt-es-nicht/']) {
const r = await fetch(base + p);
console.log(p, r.status);
}
server.close();Ausgabe:
/produkte/ 200
/gibt-es-nicht/ 404Die Statuscode-Logik der Google-Suche (404 und 410 entfernen aus dem Index, 5xx verlangsamt das Crawling) steht in der Statuscode-Tabelle des Pillar-Artikels.
Meta-Tags, Canonical und noindex per JavaScript
Titel und Meta-Beschreibung dürfen laut Dokumentation per JavaScript gesetzt oder geändert werden. Für zwei andere Angaben ist Vorsicht geboten:
- Canonical: HTML ist die bevorzugte Methode. Setzt du ihn per JavaScript, muss er mit dem im Original-HTML übereinstimmen, und es darf nur ein
rel="canonical"geben. Mehr dazu im Artikel zum Canonical Tag. - noindex: Sieht Google im Original-HTML ein
noindex, kann es Rendering und JavaScript-Ausführung überspringen. Ein späteres Entfernen per JavaScript funktioniert dann möglicherweise nicht. Seiten, die indexiert werden sollen, dürfen daher im ursprünglichen Code keinnoindextragen. Das gilt besonders für Frameworks, die ein Platzhalter-noindexin der HTML-Hülle ausliefern.
<!-- Im ausgelieferten HTML: nur das, was dauerhaft gelten soll -->
<head>
<title>Laufschuhe</title>
<link rel="canonical" href="https://example.com/produkte/laufschuhe/">
</head>Auch JSON-LD kann per JavaScript erzeugt werden. Google weist darauf hin, das zu testen. Wie strukturierte Daten aufgebaut sind, behandelt der Artikel zu strukturierten Daten.
Lazy Loading ohne Indexierungsverlust
Google empfiehlt, Inhalte zu laden, sobald sie in den sichtbaren Bereich kommen, nicht erst nach einem Klick oder Scrollen, denn die Suche interagiert nicht mit der Seite. Drei Wege nennt die Dokumentation: das eingebaute Lazy Loading des Browsers für Bilder und iframes, die IntersectionObserver-API oder eine Bibliothek, die beim Eintritt in den Viewport lädt. Außerdem solltest du keine Inhalte verzögern, die Nutzer sofort sehen. Das verlangsamt die Seite spürbar.
<img src="/bilder/schuh.jpg" alt="Laufschuh" width="640" height="360" loading="lazy">const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
document.querySelectorAll('img[data-src]').forEach((img) => observer.observe(img));Bei Bildern gibt Google an, dass die Bild-URLs im src-Attribut eines <img>-Elements im gerenderten HTML stehen sollen. Beim Beispiel mit data-src ist das erst nach dem Observer-Aufruf der Fall, deshalb ist das eingebaute loading="lazy" die einfachere Wahl. Das Bild, das als größter sichtbarer Inhalt zählt, solltest du nicht lazy laden, siehe Core Web Vitals.
Infinite Scroll und „Mehr laden"
Inhalte, die nur durch Scrollen oder einen Button nachgeladen werden, sieht Google womöglich nicht. Die Dokumentation empfiehlt Folgendes: Jeder Abschnitt bekommt eine eigene, dauerhafte URL mit stabilem Inhalt (etwa ?page=12, nicht ?date=yesterday), die Abschnitte sind sequenziell verlinkt, und die History API aktualisiert die URL im Browser.
Testen: Was sieht Google wirklich?
Verlass dich nicht auf den Browser-Eindruck. Das URL-Prüftool der Search Console zeigt für die indexierte Version das gerenderte HTML, die HTTP-Header und die JavaScript-Konsolenausgabe. Einen Screenshot der gerenderten Seite liefert nur der Live-Test. Prüfe damit:
- Steht der Haupttext im gerenderten HTML?
- Sind alle wichtigen Links als
<a href>vorhanden? - Stimmen Titel, Canonical und Robots-Angaben?
- Zeigt „Crawling zulässig" ein Ja, werden also Skripte und Stylesheets nicht blockiert?
Der Rich-Results-Test zeigt laut Dokumentation ebenfalls das gerenderte HTML.
Caching ist ein Sonderfall: Googlebot cached aggressiv und der Rendering-Dienst ignoriert Caching-Header unter Umständen. Die Dokumentation empfiehlt deshalb Fingerprinting, also Dateinamen mit Hash (app.3f9a1c.js), damit geänderte Skripte nicht aus dem Cache kommen.
Andere Crawler und KI-Dienste
Die Google-Dokumentation sagt nur etwas über Google. Ob andere Suchmaschinen oder Crawler von KI-Diensten JavaScript ausführen, ist für jeden einzelnen Crawler in dessen eigener Dokumentation nachzulesen. Dieser Artikel trifft dazu keine Aussage. Wenn dein Inhalt schon im ausgelieferten HTML steht, ist die Frage für dich irrelevant. Das ist ein weiteres Argument für SSR oder SSG, wie es auch Google formuliert.
Weitere Details aus der Dokumentation
Ein paar Punkte aus „JavaScript SEO basics", die in Projekten gern untergehen:
- Web Components: Google flacht Shadow DOM und Light DOM ab. Indexiert werden kann, was im gerenderten HTML sichtbar ist. Inhalte, die nur in nicht ausgegebenen Slots oder Komponenten hängen, bleiben außen vor.
- Polyfills und fehlende Browser-APIs: Nutze Differential Serving und Polyfills, wenn eine API im Rendering-Browser fehlen könnte, und prüfe deren Grenzen. Eine Funktion, die in deinem Browser läuft, muss nicht in Googles Rendering funktionieren.
- Titel und Beschreibung: Beides darfst du per JavaScript setzen, etwa beim Routenwechsel. Das ist für Single-Page-Apps unverzichtbar, denn ohne dynamisches
document.titlehätten alle Ansichten denselben Titel.
function applyRouteMeta({ title, description }) {
document.title = title;
let meta = document.querySelector('meta[name="description"]');
if (!meta) {
meta = document.createElement('meta');
meta.name = 'description';
document.head.append(meta);
}
meta.content = description;
}Dieser Code läuft nur im Browser und wurde nicht ausgeführt. Er zeigt das Prinzip: Sobald sich die Ansicht ändert, aktualisierst du Titel und Beschreibung, dazu per History API die URL. Besser ist es trotzdem, diese Angaben bei SSR oder SSG schon in das ausgelieferte HTML zu schreiben.
Checkliste
| Prüfpunkt | Erfüllt, wenn … |
|---|---|
| Inhalt | Haupttext steht im gerenderten HTML, besser schon im ersten HTML |
| Links | Alle Navigations- und Listenlinks sind <a href> mit echten URLs |
| Routing | History API statt Fragment-URLs |
| Statuscodes | Unbekannte URLs liefern 404 vom Server |
| noindex | Nicht im Original-HTML bei Seiten, die indexiert werden sollen |
| Canonical | Genau einer, im HTML, ohne JavaScript-Abweichung |
| robots.txt | Skripte und Stylesheets nicht gesperrt |
| Lazy Loading | Nur für Inhalte außerhalb des Viewports, mit crawlbaren URLs bei Paginierung |
Nächster Schritt
Rufe eine deiner wichtigsten Seiten im URL-Prüftool der Search Console auf, öffne „Gecrawlte Seite ansehen" und suche im gerenderten HTML nach einem Satz aus dem Haupttext und nach einem Navigationslink. Fehlt einer von beiden, liegt das Problem im Rendering oder im Markup. Bleibt es unklar, vergleiche den Abruf ohne JavaScript mit dem gerenderten Ergebnis und entscheide danach, ob die Seite SSR oder SSG braucht.
Häufige Fragen
Ist Client-Side Rendering für Google ein Problem?
Google kann clientseitig gerenderte Seiten verarbeiten, die Dokumentation beschreibt dafür einen eigenen Rendering-Schritt. Fehleranfälliger ist es trotzdem, weil Inhalte, Links und Statuscodes erst nach dem Rendering feststehen.
Können andere Crawler, etwa von KI-Diensten, JavaScript ausführen?
Googles Dokumentation beschreibt nur Googles eigenes Verhalten. Für andere Crawler musst du in deren Dokumentation nachsehen. Wenn dein Inhalt schon im ausgelieferten HTML steht, bist du unabhängig davon.
Quellen
- JavaScript-SEO-Grundlagen (Understand JavaScript SEO basics) Google Search Central · abgerufen am 28. Oktober 2026
- Crawlbare Links Google Search Central · abgerufen am 28. Oktober 2026
- Wie HTTP-Statuscodes sowie Netzwerk- und DNS-Fehler die Google-Suche beeinflussen Google Search Central · abgerufen am 28. Oktober 2026
- Robots-Meta-Tag und X-Robots-Tag Google Search Central · abgerufen am 28. Oktober 2026
- Redirects und die Google-Suche Google Search Central · abgerufen am 28. Oktober 2026
- Lazy-Loading-Empfehlungen für die Suche Google Search Central · abgerufen am 28. Oktober 2026
- Paginierung und inkrementelles Laden Google Search Central · abgerufen am 28. Oktober 2026
- URL-Prüftool Google Search Console-Hilfe · abgerufen am 28. Oktober 2026