Suchen

Leitfaden · Technik & Marketing

JavaScript-SEO: So sieht Google deine JavaScript-Seite

Google kann JavaScript ausführen, aber in einer eigenen Phase nach dem Crawling. Wer Inhalte, Links und Statuscodes schon im ersten HTML liefert, nimmt der Indexierung die meisten Risiken.

Nahaufnahme eines Bildschirms mit vielen Zeilen Quelltext

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:

  1. 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.
  2. Wenn das Crawling gesperrt ist, passiert nichts weiter: Die Dokumentation sagt, dass Google JavaScript aus blockierten Dateien oder auf blockierten Seiten nicht rendert.
  3. Nicht jede Information darf von JavaScript abhängen. Der Statuscode entscheidet, ob eine Seite überhaupt gerendert wird, und ein noindex im 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.

MusterWo entsteht das HTML?Inhalt im ersten HTML?Aufwand/Risiko für Indexierung
CSR (Client-Side Rendering)im Browser, per JavaScriptmeist nur eine leere Hüllehöchstes Risiko: Inhalte, Links und Titel hängen vom Rendering ab
SSR (Server-Side Rendering)auf dem Server bei jedem Aufrufjagering, braucht aber einen laufenden Server
SSG (Static Site Generation)beim Build, als fertige Dateienjagering, geeignet für Inhalte, die sich selten ändern
HydrationServer oder Build liefert HTML, JavaScript macht es interaktivjagering für Inhalte, Interaktivität entsteht erst im Browser
Islandsstatisches HTML, nur einzelne Bereiche laden JavaScriptjagering, 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.

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.

HTML
<!-- 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:

JavaScript
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:

JavaScript
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:

Text
/produkte/ 200
/gibt-es-nicht/ 404

Die 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 kein noindex tragen. Das gilt besonders für Frameworks, die ein Platzhalter-noindex in der HTML-Hülle ausliefern.
HTML
<!-- 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.

HTML
<img src="/bilder/schuh.jpg" alt="Laufschuh" width="640" height="360" loading="lazy">
JavaScript
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:

  1. Steht der Haupttext im gerenderten HTML?
  2. Sind alle wichtigen Links als <a href> vorhanden?
  3. Stimmen Titel, Canonical und Robots-Angaben?
  4. 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.title hätten alle Ansichten denselben Titel.
JavaScript
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üfpunktErfüllt, wenn …
InhaltHaupttext steht im gerenderten HTML, besser schon im ersten HTML
LinksAlle Navigations- und Listenlinks sind <a href> mit echten URLs
RoutingHistory API statt Fragment-URLs
StatuscodesUnbekannte URLs liefern 404 vom Server
noindexNicht im Original-HTML bei Seiten, die indexiert werden sollen
CanonicalGenau einer, im HTML, ohne JavaScript-Abweichung
robots.txtSkripte und Stylesheets nicht gesperrt
Lazy LoadingNur 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

  1. JavaScript-SEO-Grundlagen (Understand JavaScript SEO basics) Google Search Central · abgerufen am 28. Oktober 2026
  2. Crawlbare Links Google Search Central · abgerufen am 28. Oktober 2026
  3. Wie HTTP-Statuscodes sowie Netzwerk- und DNS-Fehler die Google-Suche beeinflussen Google Search Central · abgerufen am 28. Oktober 2026
  4. Robots-Meta-Tag und X-Robots-Tag Google Search Central · abgerufen am 28. Oktober 2026
  5. Redirects und die Google-Suche Google Search Central · abgerufen am 28. Oktober 2026
  6. Lazy-Loading-Empfehlungen für die Suche Google Search Central · abgerufen am 28. Oktober 2026
  7. Paginierung und inkrementelles Laden Google Search Central · abgerufen am 28. Oktober 2026
  8. URL-Prüftool Google Search Console-Hilfe · abgerufen am 28. Oktober 2026