SEO blog · Technical SEO

JavaScript SEO: How Google Renders and Indexes Dynamic Sites

Key takeaways

  • JavaScript SEO means making sure Google can see the content and links of a site that is built in the browser. Google can render JavaScript, but it does so in a separate, slower step.
  • Essential content (title, main text, internal links, canonical) should be in the HTML the server sends, not added only after scripts run.
  • Links must be anchor elements with an href attribute and real URLs. Routing with fragments (#) or click handlers without href is not reliably discovered.
  • Always test what Google sees, not what you see: URL Inspection in Search Console and a comparison of page source against the rendered DOM reveal the gaps.
  • Server-side rendering or static generation is the safest choice for pages that must be indexed, including for crawlers that may not run JavaScript.

JavaScript SEO means making sure Google can see the content and links of a site that is built in the browser rather than on the server. Google can render JavaScript, but it does so in a separate step after crawling, which adds delays and risks. The practical rule: everything that needs to be indexed (text, headings, links, canonical) should be available as early as possible, ideally directly in the HTML.

How Google processes a page with JavaScript

Google's documentation describes three phases: crawling, rendering, indexing.

  1. Crawling. Googlebot requests the URL, checks robots.txt and receives the initial HTML. From it, it extracts links found in <a href> elements. If the page is allowed and returns a 200 status, rendering follows.
  2. Rendering. Pages enter a queue, then a headless Chromium executes the JavaScript and builds the final page. How long a page waits in the queue varies, from seconds to longer.
  3. Indexing. Google uses the rendered HTML to understand and index the content, and to discover new links.

Two consequences follow. First, anything that does not appear in the rendered HTML does not exist for Google. Second, anything that depends on rendering reaches the index later than what is already in the HTML. For a blog that publishes daily or a store with changing stock, that delay can matter.

One useful detail: Googlebot uses a continuously updated Chromium, so it supports modern browser APIs. The problem is rarely that "Google doesn't understand JavaScript." It is how the site is written. That is why rendering belongs to technical SEO, together with indexing, speed and status codes.

Rendering models: where the page gets built

The model you pick is the decision that most affects search behavior. web.dev has longer explanations of each.

ModelWho builds the HTMLFor SEOTypical use
Client-side rendering (CSR)The browser, after scripts loadRisky: the initial HTML is nearly emptyApps behind a login, admin dashboards
Server-side rendering (SSR)The server, on every requestGood: content is in the HTMLStores, pages with frequently changing content
Static generation (SSG)At build time, onceVery good and fastBlogs, brochure sites, documentation
Hydration / hybridThe server sends HTML, then the browser "activates" itGood, if the HTML holds the real contentModern apps with public pages

In short: for pages that must be found on Google, choose SSR or SSG. Pure CSR is fine for areas that have no place in search.

Dynamic rendering

Serving a pre-rendered version only to bots (dynamic rendering) was a popular workaround. Google's documentation calls it a workaround and points to server-side rendering or pre-rendering as the better option, because dynamic rendering adds complexity and the risk that bots and people see different things.

What breaks most often

Googlebot discovers URLs from <a> elements with an href attribute. A button or a <span> with onclick is not followed.

<!-- Correct: a crawler can follow the link -->
<a href="/services/audit">Full audit</a>

<!-- Wrong: there is no URL to discover -->
<span onclick="navigate('/services/audit')">Full audit</span>

<!-- Wrong for indexing: the fragment does not identify a separate page -->
<a href="#/services/audit">Full audit</a>

For single-page apps, use the History API, not fragments (#). The old AJAX crawling scheme has been deprecated since 2015.

Content that appears only after interaction

Googlebot generally does not click, scroll or fill in forms the way a visitor does. If important text only appears after clicking "Read more" or after scrolling, it may be missing from the render. For lazy loading, use recommended mechanisms such as IntersectionObserver or the native loading="lazy" attribute, not scroll events that assume a real user.

Metadata changed by JavaScript

You can add or change the title, canonical and meta robots with JavaScript, with limits:

  • if the initial HTML contains noindex, Google may skip rendering, so do not count on removing it with JavaScript;
  • do not change the canonical from what is in the HTML, and never leave more than one on a page;
  • the safest approach is to send title, description and canonical from the server. For canonical logic, see the article on canonical tags and duplicate content.

Soft 404 errors in SPAs

A single-page app often returns 200 for any address while the interface displays "Page not found." Google sees a 200 and may treat the page as a soft 404. Fixes: return a real 404 status from the server for nonexistent URLs, redirect to a page that returns 404, or dynamically add noindex on the error page. Common reasons for exclusion from the index are explained in the guide to pages not indexed.

Blocked resources

If robots.txt blocks the JavaScript or CSS files the page needs to display, Google cannot render it correctly. Do not block script directories. Check your file with the robots.txt generator and test with inspection tools.

Data that does not persist

Googlebot does not keep local storage, session storage or cookies across page loads. If the page depends on them to show content, it will look empty to Google. Do not make indexable content depend on permissions (camera, location): Googlebot declines them.

How to check what Google sees: practical steps

Every JavaScript SEO audit starts with one comparison: what the server sends versus what results after rendering.

  1. Open the page source (Ctrl+U) and search for a sentence from the main content. If you find it, the content is in the HTML. If not, it appears only after rendering.
  2. Compare with the rendered DOM in the browser's developer tools (Inspect). The differences show what scripts add.
  3. Use URL Inspection in Search Console, then the live test. You see the rendered HTML, the screenshot, the resources that failed to load and console errors.
  4. Run a crawler with JavaScript rendering on a sample of pages and compare with the non-rendered result.
  5. Search Google for an exact sentence from the page. If the page is indexed but the sentence does not appear, Google did not see that text.
  6. Check speed. Heavy scripts also hurt user experience; see Core Web Vitals and site speed and test with the site speed test.

Common symptoms and probable causes

When a dynamic page does not behave as expected on Google, the table below helps you start from the symptom rather than from assumptions.

Symptom in Search Console or searchProbable causeFirst check
Page is indexed, but snippets are empty or genericText appears only after rendering, or the title is set lateCompare page source with the rendered HTML in URL Inspection
"Discovered, currently not indexed" on many new URLsInternal links appear late or are missing from the HTMLLook for <a href> in the source and in the sitemap
"Soft 404" on pages that should existContent does not load during rendering, or the app shows an errorLive test and the console errors
Nonexistent pages show up as indexedThe app returns 200 for any URLThe real HTTP response for a made-up address
Images do not appear in searchLazy loading implemented only with scroll eventsUse loading="lazy" or IntersectionObserver

A three-step example check on a product or service page: (1) request the URL with a simple client, without JavaScript, and see whether the product name, description and category link are in the response; (2) open the same URL in Search Console's inspection and compare the rendered HTML; (3) change one letter in the address and check that the server returns 404. If any of the three fails, you have found your priority.

For large sites, work by template: do not check a thousand pages, check one page type at a time (home, category, product, article). If the template is right, thousands of pages are right; if it is wrong, thousands of pages share the same problem.

Structured data and JavaScript

Google accepts JSON-LD injected with JavaScript, but the documentation recommends testing carefully. It is safer to include it in the HTML the server sends. If you use a framework, generate the JSON-LD block during server-side rendering. Examples and validation are in the structured data guide.

File size and performance

Googlebot has a size limit for each fetched file, applied to uncompressed data. Google's Googlebot documentation currently says it crawls the first 2 MB of a supported file type (64 MB for PDFs), and the limit applies separately to each resource referenced in the HTML (JavaScript, CSS). Other Google crawlers may have different limits. Whatever exceeds the limit is not processed. In practice, a huge JavaScript bundle measured in megabytes is a problem for users as well, not only for Google. Split the code, load non-critical parts on demand and use fingerprinted file names, because Googlebot may cache files and keep using an old version.

AI crawlers and JavaScript

Googlebot executes JavaScript, but you should not assume every crawler does. For the bots that feed AI search, the providers' bot documentation does not generally promise that they render scripts, and behavior may differ between bots and change over time. If you want your text to be cited there, the safest route is to have it in the initial HTML. For how to allow or block their access, see the article on AI bots and robots.txt.

Common mistakes

  • An empty initial HTML, with a single <div id="root"> and all content fetched through scripts.
  • Links without href or with fragments instead of real addresses.
  • The same URL for different content (for example, changing content without changing the URL).
  • Identical title and meta description on every page of an SPA.
  • Error pages with a 200 status.
  • JavaScript blocked in robots.txt.
  • Essential content hidden behind a click.
  • Assuming "it shows in my browser, so it shows on Google."
  • Redirects done only in JavaScript when a server-side 301 was possible. Server redirects are clearer for bots; see the article on 301 and 302 redirects.

Limits: when JavaScript is not the problem

Not every indexing problem comes from scripts. Before you redesign the architecture, rule out the simple causes: blocked pages, a noindex left from testing, a wrong canonical, thin or duplicate content. Also:

  • a small, static site with a little decorative JavaScript has nothing to worry about;
  • login areas (accounts, dashboards) should not be indexed, so their rendering does not matter for SEO;
  • no setup guarantees indexing: Google does not index every page it finds.

Conclusion: what to do next

JavaScript is not an enemy of SEO, but it demands discipline. Concrete steps:

  1. Choose SSR or static generation for pages that must be found in search.
  2. Check links: <a href> with real URLs, no fragments.
  3. Compare page source with the rendered HTML from Search Console on one page of each type.
  4. Make sure the server returns correct status codes (200, 301, 404).

If you have a dynamic site that does not index the way it should, our technical SEO service starts with exactly these checks. You will find more guides on the blog.

Sources and further reading

Frequently asked questions

Frequently asked questions

Can Google index a site built with React, Vue or Angular?

Yes. Googlebot uses an evergreen Chromium and executes JavaScript. Success depends on the implementation: if links are real, URLs are distinct and content shows up without interaction, pages usually index well. Without those conditions, pages can stay empty or undiscovered.

Is dynamic rendering still recommended?

Not as a long-term solution. Google describes it as a workaround, not a long-term solution, because it adds complexity and can make bots and people see different things. Server-side rendering, static generation or hydration are sturdier alternatives.

How do I know whether Google sees my JavaScript-generated content?

Use URL Inspection in Search Console and run the live test: compare the rendered HTML and screenshot with what you see in the browser. You can also search Google for an exact sentence from the page. If the text is missing from the rendered HTML, the script did not run fully or was blocked.

Does blocking JavaScript files in robots.txt hurt SEO?

Yes. If you block scripts or styles the page needs in order to display, Google cannot render it correctly and may misread the content or layout. Keep rendering resources accessible and use robots.txt for areas with no search value, such as the cart or internal search.

Does an XML sitemap help a JavaScript-built site?

Yes, it helps discover URLs, especially when internal links are generated late. It does not fix rendering problems, though: if the rendered page is empty or returns the wrong status code, a sitemap will not save it. Use it as a safety net, not as a substitute for real links.

Related service

Technical SEO & speed

See the service →

Let’s grow your site’s organic traffic

Send us your website address and we’ll reply with a free initial analysis and a concrete SEO strategy — no strings attached.