Audit my site
Technical SEOIntermediateChecked against Google docs · Oct 2026

JavaScript SEO: how Google crawls, renders and indexes JavaScript sites

Google indexes the rendered HTML of a page, not the code you ship. If content, links or meta tags only exist after scripts run, rendering has to succeed for them to count, and many other crawlers will never see them at all.

Author: Eugene Serbin, Head of SEO at Semalt Updated: 30 October 2026 Reading time: 17 min Visuals: 5

In short

Google crawls a JavaScript page, puts it in a render queue, renders it in headless Chromium and indexes the rendered HTML. Anything that is missing from that rendered HTML, such as content behind a click, links without <a href> or data from blocked scripts, cannot be indexed. Server-side rendering or static generation removes most of the risk, and the URL Inspection tool shows you exactly what Google got.

How Google processes JavaScript

Google's documentation starts with the pipeline: “Google processes JavaScript web apps in three main phases:” crawling, rendering and indexing. A plain HTML page goes through the same steps. The difference is how much of the page exists before rendering.

Crawling. Googlebot takes a URL from the crawl queue and first checks robots.txt. “If it marks the URL as disallowed, then Googlebot skips making an HTTP request to this URL and skips the URL.” If the URL is allowed, Googlebot fetches the HTML. “Googlebot then parses the response for other URLs in the href attribute of HTML links and adds the URLs to the crawl queue.”

Rendering. “All pages with a 200 HTTP status code are sent to the rendering queue, no matter whether JavaScript is present on the page.” Rendering is not instant: “The page may stay on this queue for a few seconds, but it can take longer than that.” Then “a headless Chromium renders the page and executes the JavaScript.” Links that appear only after rendering are found at this stage and also go to the crawl queue.

Indexing. “Google also uses the rendered HTML to index the page.” This is the key sentence for JavaScript SEO. Google indexes what the page looks like after your scripts have run, not what you see in your own browser after you click around.

Diagram · Crawl, render, index
How Google crawls, renders and indexes a JavaScript page A URL moves from the crawl queue to the crawler, which checks robots.txt and fetches the HTML. Processing reads the raw HTML and sends links found in href attributes back to the crawl queue. The page waits in the render queue, is rendered by headless Chromium, and the rendered HTML is indexed. Links found after rendering also go back to the crawl queue. A URL disallowed in robots.txt is skipped. A noindex in the raw HTML may stop rendering. Crawl queueCrawler ProcessingRender queue Renderer Index URLs to fetchfetches the HTML reads raw HTMLwaits its turn headless Chromium rendered HTML Disallowed in robots.txt: no request, URL skipped noindex in the raw HTML: rendering may be skipped links from href in raw HTML links found after rendering
Links are collected twice, from the raw HTML and from the rendered page, but only the rendered HTML is indexed.

Two practical consequences follow. First, the raw HTML still matters: robots.txt, the status code, a noindex and the links in it are acted on before any JavaScript runs. Second, anything that needs the render step to exist waits for the render queue, so it reaches the index later than content that is already in the HTML.

What Google officially says

“Google also uses the rendered HTML to index the page.”

Google Search Central — Understand the JavaScript SEO basics

“If the content isn't visible in the rendered HTML, Google won't be able to index it.”

Google Search Central — Understand the JavaScript SEO basics

“Instead, we recommend that you use server-side rendering, static rendering, or hydration as a solution.”

Google Search Central — Dynamic rendering as a workaround

Other statements from the same pages that this lesson relies on:

WRS is the Web Rendering Service, the part of Google that renders pages.

CSR, SSR, SSG and hydration compared

The rendering strategy decides what is in the very first HTML response, and that decides how much depends on Google's render step. Google says that “server-side or pre-rendering is still a great idea”, and its dynamic rendering page recommends server-side rendering, static rendering or hydration.

Table · Rendering strategies and search
StrategyFirst HTML responseContent is readySearch riskTypical use
Client-side rendering (CSR)An almost empty shell: <div id="app"></div> plus a script bundleOnly after the JavaScript runs and its API calls succeedHighest: everything depends on renderingDashboards, apps behind a login
Server-side rendering (SSR)Full HTML with text, links and meta tags, built per requestImmediatelyLowShops, news, pages with frequently changing data
Static generation (SSG)Full HTML built at deploy timeImmediatelyLowest, as long as rebuilds keep content currentBlogs, documentation, marketing pages
HydrationFull HTML from SSR or SSG; JavaScript then makes it interactiveImmediately; interactivity followsLow, if the hydrated page shows the same contentModern frameworks in SSR or SSG mode
Dynamic renderingPrerendered HTML for crawlers, CSR for usersImmediately for crawlersA workaround with extra moving partsLegacy CSR sites that cannot move to SSR yet
The further a strategy moves content into the first HTML response, the less your indexing depends on rendering.

Client-side rendering

With CSR the server sends a shell and a script bundle; the browser builds the page. Google can index CSR pages, but every failure point is yours: a script error, a slow or blocked API, a resource disallowed in robots.txt. Any of them leaves Google with an empty or partial page.

Server-side rendering and static generation

With SSR the server runs the framework and returns complete HTML for each request. With SSG the HTML is generated once at build time and served as files. In both cases the text, links, title, canonical and robots meta tag are in the first response, so they do not wait for the render queue and other crawlers can read them too.

Hydration

Hydration is not a separate rendering mode but the step after SSR or SSG: the browser attaches JavaScript to the HTML that is already there. Keep the server output and the hydrated page the same. If the client replaces content, titles or canonicals after hydration, you are back to depending on the render step.

Dynamic rendering

Dynamic rendering detects crawlers and serves them a prerendered version while users get the client-side app. Google is clear about its status: “Dynamic rendering is a workaround and not a recommended solution, because it creates additional complexities”. It also warns: “Using dynamic rendering to serve completely different content to users and crawlers can be considered cloaking.” Treat it as a temporary bridge, not as an architecture.

Google's rule for links is short: “Generally, Google can only crawl your link if it's an <a> HTML element”, and “Google can't reliably extract URLs from <a> elements that don't have an href attribute”. A <span> or <div> with a click handler is not a link to Google, however much it behaves like one for users.

Code · Links Google may miss vs links it can follow

✕ Not reliably crawlable

<a routerLink="products/category">Shoes</a>

<span href="https://example.com">Home</span>

<a onclick="goto('https://example.com')">
  Home</a>

<div @click="go('/sale')">Sale</div>
  • No href, or href on the wrong element
  • The URL exists only inside JavaScript
  • Fine for users, invisible as a link to crawlers

✓ Crawlable

<a href="https://example.com">Home</a>

<a href="/products/category/shoes">
  Shoes</a>

<!-- a click handler is fine next to href -->
<a href="/products/category/shoes"
   onclick="javascript:goTo('shoes')">
  Shoes</a>
  • A real <a> element
  • An href that resolves to a real URL
  • Anchor text between the tags
The first three “bad” examples and all “good” ones come from Google's crawlable-links documentation; the click handler on a div is a common framework pattern with the same problem.

Inserting links with JavaScript is allowed: “Links are also crawlable when you use JavaScript to insert them into a page dynamically”, as long as they are real <a href> elements. They are only found after rendering, though. Google adds: “If you are using JavaScript to insert anchor text, use the URL Inspection Tool to make sure it's present in the rendered HTML.”

In practice, most frameworks have a link component (router link, Link, NuxtLink and similar) that outputs a normal <a href> in the final HTML. Check the rendered output, not the component code. Navigation, pagination, faceted filters and “load more” buttons are the places where real links most often go missing.

URLs in single-page apps: fragments and the History API

“In client-side rendered single-page apps, routing is often implemented as client-side routing.” How that routing builds URLs decides whether Google sees one page or many.

Hash routes such as https://example.com/#/products put the route in the URL fragment. The fragment is never sent to the server, and for Google the whole app is effectively one URL. Google says: “To ensure that Googlebot can parse and extract your URLs, don't use fragments to load different page content.” The old workaround is gone too: “The AJAX-crawling scheme has been deprecated since 2015, so you can't rely on URL fragments to work with Googlebot.”

<!-- Avoid: every view lives behind # -->
<a href="#/products/shoes">Shoes</a>

<!-- Use: a real path, handled with the History API -->
<a href="/products/shoes">Shoes</a>
<script>
  // intercept the click, update the view, keep a real URL
  history.pushState({}, '', '/products/shoes');
</script>

Google's recommendation is the History API: “We recommend using the History API to load different content based on the URL in a SPA.” Two rules make it work for search. Each route must also work when it is opened directly, so the server has to answer /products/shoes with the right page (ideally server-rendered), not just the home page. And fragments are still fine for what they were made for: jumping to a section within one page, as in /guide/#install.

Many apps change the <head> on every route. Google handles this, but each tag has its own rules.

Title and meta description

“You can use JavaScript to set or change the meta description as well as the <title> element.” Google will use the rendered values. Still, put a correct title and description in the server HTML when you can: they are then available before rendering and to crawlers that never run JavaScript. Every route should get its own title, not the app's default one.

Canonical

Google's advice: “The best way to set the canonical URL is to use HTML”. A canonical injected by JavaScript is accepted: “Google Search will pick up the injected canonical URL when rendering the page.” The danger is two different values, one in the server HTML and another after rendering. “Conflicting or multiple rel="canonical" link tags may lead to unexpected results.” If you must set it with JavaScript, make sure the server HTML has no canonical at all, or the same one.

Robots meta tag and the noindex trap

“You can use JavaScript to add a robots meta tag to a page or change its content.” Adding a noindex with JavaScript works. Removing one does not work reliably, because of this rule: “When Google encounters the noindex tag, it may skip rendering and JavaScript execution”. If the server HTML says noindex, your script that would remove it may never run.

Pattern · The noindex trap

✓ Works

  • Server HTML has no noindex; the page is indexable by default
  • JavaScript adds noindex only to views that should stay out, such as error states
  • Pages you want indexed never carry noindex in the original code

✕ Trap

  • Server HTML ships noindex as a “safe default”
  • JavaScript is supposed to remove it once data loads
  • Google may skip rendering, sees only noindex, and the page stays out of the index
Google's own wording: “If you do want the page indexed, don't use a noindex tag in the original page code.”

Status codes and soft 404s in SPAs

Status codes are read before rendering, so they have to be right on the server: “To tell Googlebot if a page can't be crawled or indexed, use a meaningful status code”. A client-side app often answers every route with 200 and the same shell, then shows “Product not found” in JavaScript. To Google that is a normal page with error text, a soft 404. Google warns: “In a single-page application (SPA), this can be especially difficult.” and “This can lead to error pages being indexed and possibly shown in search results.”

When the server cannot return a real 404, Google gives two strategies:

  1. “Use a JavaScript redirect to a URL for which the server responds with a 404 HTTP status code (for example /not-found).”
  2. “Add a <meta name="robots" content="noindex"> to error pages using JavaScript.”
fetch('/api/products/' + id)
  .then(r => {
    if (r.status === 404) {
      // option 1: go to a URL that really returns 404
      window.location.href = '/not-found';
      // option 2: keep the URL, keep it out of the index
      // const m = document.createElement('meta');
      // m.name = 'robots'; m.content = 'noindex';
      // document.head.appendChild(m);
    }
  });

The best fix is on the server: with SSR, a missing product returns 404 (or 410) directly, and a moved one returns a 301 redirect instead of a JavaScript redirect.

Content Googlebot may not see

Rendering is not the same as a person using the page. These are the usual reasons content is missing from the rendered HTML.

Content behind interaction

Text loaded only after a click on a tab, a “show more” button or a scroll event may never be loaded during rendering. Do not assume Googlebot clicks or scrolls like a user. Put content in the HTML and hide or show it with CSS, or load it on page load.

Lazy-loading done wrong

Google recommends lazy-loading for images: “A good strategy is to use lazy-loading to only load images when the user is about to see them.” Use native loading="lazy" or IntersectionObserver, and do not lazy-load the main content or the first screen of a page on scroll events.

Permission prompts

“Expect Googlebot to decline user permission requests.” Content that appears only after the visitor allows location, notifications or camera access is not rendered for Google.

Stored state

“Don't rely on data persistence to serve content.” Each page load starts clean: “Local Storage and Session Storage data are cleared across page loads.” and “HTTP Cookies are cleared across page loads.” A page that needs a stored region, preference or session to show its content shows Google the empty version.

Non-HTTP connections

“Googlebot uses HTTP requests to retrieve content from your server.” “It does not support other types of connections, such as WebSockets or WebRTC connections.” Provide an HTTP way to fetch the content that matters for search.

Shadow DOM

“Google supports web components.” “When Google renders a page, it flattens the shadow DOM and light DOM content.” What counts is whether the content ends up visible in the rendered HTML.

Stale cached scripts

“Googlebot caches aggressively in order to reduce network requests and resource usage.” and “WRS may ignore caching headers.” If you deploy a new bundle under the same filename, Google may render with the old one. Use content fingerprinting in filenames, such as main.2f9c1a.js: “The fingerprint depends on the content of the file, so updates generate a different filename every time.”

Blocked resources

“Google Search won't render JavaScript from blocked files or on blocked pages.” A robots.txt rule written years ago to “save crawl budget” can now break rendering for the whole site, because the page itself is allowed but the bundle or the API that fills it is not.

# Breaks rendering: the page needs these files
User-agent: *
Disallow: /static/
Disallow: /api/

# Better: block only what really should not be crawled
User-agent: *
Disallow: /admin/
Disallow: /cart/

Check every script, stylesheet and API endpoint a template needs. Third-party scripts on another host follow that host's robots.txt, which you cannot change, so do not load critical content from them. The URL Inspection tool lists page resources that could not be loaded.

Other crawlers and AI bots

Everything above describes Google. Other crawlers vary, and in practice many of them do not execute JavaScript at all. That includes many AI crawlers, SEO tools and the bots that build link previews for messengers and social networks. For them, a client-side rendered page is the empty shell: no text, no links, sometimes not even a title.

If you want your content to be read by AI assistants, previewed correctly when shared, or analysed by crawling tools, the safest choice is the same one Google recommends: put the content, links and meta tags in the server HTML with SSR or SSG. Check what a non-rendering crawler gets by looking at the raw HTML (view source, or a request with curl), not the page in your browser.

How to test what Google sees

“To test how Google crawls and renders a URL, use the Rich Results Test or the URL Inspection Tool in Search Console.” Both tools render the page the way Google does. “You can see loaded resources, JavaScript console output and exceptions, rendered DOM, and more information.” The URL Inspection tool works for verified properties and shows the indexed version; its live test checks the current page. The Rich Results Test works for any public URL.

  1. Look at the raw HTML. View source or fetch the URL with curl. Is the main text there? The title, canonical, robots meta tag and links?
  2. Run a live test in URL Inspection and open the rendered HTML. Search it for a unique sentence from the page.
  3. Read the extra information: resources that failed to load, console errors, the HTTP status.
  4. Compare raw and rendered title, canonical and robots tags. They should match, or the raw HTML should have none of them.
  5. Check the index: the indexed status in URL Inspection and the Page indexing report for soft 404s, “Excluded by ‘noindex’ tag” and similar statuses.
  6. Do not use client-side analytics as evidence of Google's visits. Use the server logs and the Crawl stats report.
Decision tree · Content on a JavaScript page is not indexed
Debugging JavaScript content that is not indexed First ask whether the content is in the raw HTML; if yes, it is not a rendering problem, so check noindex, canonical and quality. If not, ask whether it is in the rendered HTML in URL Inspection; if not, check blocked resources, JavaScript errors and content that needs a click. If yes, ask whether the raw HTML has noindex or a non-200 status; if yes, fix it on the server. If not, ask whether the page is reachable through a href links and the sitemap; if not, add crawlable links. If yes, rendering works and the page needs time or server-side rendering. YesNoYesNo NoYesNoYes Is the content in the raw HTML(view source)? Is it in the rendered HTMLin URL Inspection? Does the raw HTML have noindexor a non-200 status? Is the page reachable viaa href links and the sitemap? Not a rendering problem Rendering fails Fix it on the server Discovery problem check noindex, canonical, quality blocked files, JS errors, click-only content JavaScript cannot reliably undo it add crawlable links, list it in the sitemap Rendering works allow time; move key templates to SSR
Start with the raw HTML: it tells you in one step whether you are dealing with a rendering problem at all.

Check how your JavaScript site is crawled

OurSEO Tools runs a 69-point checklist that includes rendering and speed, plus a technical crawl of up to 1,000 pages with the status code, canonical, indexability and load time of every URL.

69-point checklistTechnical crawlContent analysis

Common mistakes

FAQ

Can Google index JavaScript content?

Yes. Google renders pages in headless Chromium and indexes the rendered HTML. The condition is that the content actually appears in that rendered HTML without clicks, permissions or blocked resources.

Is client-side rendering bad for SEO?

Not automatically, but it is the riskiest option. Everything depends on rendering succeeding, content waits for the render queue, and crawlers that do not run JavaScript see an empty page. For pages that must rank, SSR or SSG is the safer choice.

How long does rendering take?

Google does not give a fixed time: “The page may stay on this queue for a few seconds, but it can take longer than that.” Content that is already in the server HTML does not have to wait for it.

Should I use dynamic rendering?

Only as a temporary bridge for a client-side app that cannot move to SSR yet. Google calls it a workaround and recommends server-side rendering, static rendering or hydration instead.

Are links added with JavaScript followed?

Yes, if they are real <a> elements with an href and appear in the rendered HTML. They are found later than links in the server HTML, and crawlers that do not render never see them.

How do I see what Google rendered?

Run the URL Inspection tool in Search Console (or the Rich Results Test for any public URL) and open the rendered HTML, the loaded resources and the console messages. Compare it with the raw HTML from view source.

Sources

Quotes checked against the official pages on 30 October 2026.