---
title: "JavaScript SEO: How Google Crawls and Indexes JS (2026)"
url: https://encited.com/blog/javascript-seo
description: "JavaScript SEO: how Googlebot crawls, renders and indexes JavaScript, CSR vs SSR vs SSG vs pre-rendering, the pitfalls that block indexing, and how to audit."
lang: en
---

Image: JavaScript SEO: How Google Crawls, Renders and Indexes JS Sites (2026) (https://encited.com/blog/thumbnails/javascript-seo.webp)

# JavaScript SEO: How Google Crawls, Renders and Indexes JS Sites (2026)

Oct 16, 2026 October 16, 2026 · Updated Oct 29, 2026 October 29, 2026 · by Aki from Encited

JavaScript SEO: how Googlebot crawls, renders and indexes JavaScript, CSR vs SSR vs SSG vs pre-rendering, the pitfalls that block indexing, and how to audit.

**TL;DR.** Google crawls the HTML your server sends, queues the page for rendering in a headless Chromium, and indexes the rendered result; on nextjs.org, Vercel and MERJ measured a median wait of 10 seconds between crawl and render, and about 18 hours for the slowest 1%. The AI crawlers behind ChatGPT, Claude and Perplexity read the raw HTML and never run your scripts. So every JavaScript SEO problem comes back to what sits in the first HTML response: the content, links as `<a href>`, one title and canonical per URL, and a real status code. Server-side rendering, static generation or pre-rendering put the finished page in that response. To check your own site, compare view-source with the rendered DOM, inspect the URL in Search Console and read your crawl log.

JavaScript SEO is the part of technical SEO that covers pages whose content, links or tags depend on JavaScript. A React, Vue or Angular app that builds the page in the browser, a storefront that loads prices and reviews from an API, and a server-rendered site that fills in sections after the first response all belong to it. The job is the same on each: make sure Googlebot, Bingbot and the AI crawlers receive the text, links, title, canonical and structured data that your scripts add.

For a single page application, start with SPA SEO (https://encited.com/blog/spa-seo). The framework guides are React SEO (https://encited.com/blog/react-seo), Vue SEO (https://encited.com/blog/vue-seo) and Angular SEO (https://encited.com/blog/angular-seo). The rendering choice is in dynamic rendering SEO (https://encited.com/blog/dynamic-rendering-seo) and dynamic rendering vs server-side rendering (https://encited.com/blog/dynamic-rendering-vs-server-side-rendering), the AI side in do AI crawlers render JavaScript (https://encited.com/blog/do-ai-crawlers-render-javascript), and the Googlebot simulator (https://encited.com/free-tools/crawler-simulator/googlebot-simulator) shows the raw response Google gets from any URL before it renders.

## What is JavaScript SEO

A site that uses JavaScript for a menu or a carousel, with the content already in the HTML, has little to worry about. A site where JavaScript builds the content depends on every crawler running that JavaScript, and running it correctly.

For any page you want found, JavaScript SEO checks four things:

1. Crawlers can reach the URL, and it returns the right status code.
2. They get the content, in the HTML or in the rendered page.
3. They get the right signals: title, description, canonical, robots tags and structured data.
4. They can follow the links to your other pages.

### Why single page applications break

A single page application sends the same `index.html` for every URL: a container like `<div id="root"></div>`, a script tag and little else. Every route, title, link and paragraph exists only after the bundle downloads, runs and fetches its data.

Two things follow. A crawler that reads the raw HTML gets the empty shell and moves on. A crawler that renders has to download and run the whole bundle before it sees a word, so each page costs Google more to index than plain HTML, and an error, a blocked file or a slow API call anywhere in that process leaves the render incomplete. The problems that only SPAs have, from hash routing to one title for every route, are in SPA SEO (https://encited.com/blog/spa-seo). The sections below apply to every site that depends on JavaScript.

### Is JavaScript bad for SEO?

No. Google renders JavaScript, and in Vercel and MERJ's study (https://vercel.com/blog/how-google-handles-javascript-throughout-the-indexing-process) of more than 100,000 Googlebot fetches on nextjs.org, every HTML page that returned a valid, indexable response got a full render, "including pages with complex JS interactions."

JavaScript hurts SEO when something your pages need depends on a step a crawler doesn't take: a click, a scroll, a script blocked in robots.txt, an API call that's too slow, or a crawler that doesn't run scripts at all.

Client-side rendering carries the most risk, because the HTML your server sends holds nothing until the scripts run. In Google, that works when every step goes right. AI crawlers and link previews get an empty page. Server-rendered and static pages carry less risk, as long as the content that matters is in the HTML they send.

## How Google crawls, renders and indexes JavaScript

Google processes a JavaScript page in three phases, and the first one decides whether the other two happen:

1. **Crawling.** Googlebot requests the URL, checks robots.txt, and reads the HTML your server returns. Links in that HTML go into the crawl queue.
2. **Rendering.** Google puts every page that returns a 200 status into a render queue, unless a robots meta tag or header tells it not to index the page. "Once Google's resources allow, a headless Chromium renders the page and executes the JavaScript," according to Google's JavaScript SEO basics (https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics). The renderer is Google's Web Rendering Service (WRS).
3. **Indexing.** Google reads the rendered HTML for new links and indexes the rendered page.

What Google reads before rendering (the status code, a `noindex` tag, the canonical tag in the raw HTML) decides whether rendering happens at all and how the result is treated. Google's changelog says "Canonicalization happens before and after rendering." What Google waits for, what times out and how to confirm each step is in how Google renders JavaScript (https://encited.com/blog/how-google-renders-javascript).

### How long Google takes to render JavaScript

Vercel and MERJ matched more than 37,000 rendered pages on nextjs.org to server beacons in April 2024, and published the results on July 31, 2024. The time from Googlebot's crawl to the finished render:

| Percentile | Time from crawl to render |
| --- | --- |
| 50th (median) | 10 seconds |
| 75th | 26 seconds |
| 90th | about 3 hours |
| 95th | about 6 hours |
| 99th | about 18 hours |

URLs with query strings waited longer. Their 75th percentile was 31 minutes and their 90th about 8.5 hours, against 22 seconds and about 2.5 hours for URLs without them.

This data shows a short queue for most pages, on one fast site that Google crawls often. A large catalog on a slow server can see different numbers, and URL Inspection and your crawl logs show your own.

### What Google's renderer doesn't do

The limits that cause most problems, from Google's JavaScript troubleshooting guide (https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript):

- **It doesn't interact.** It doesn't click, scroll or type. Content behind any of those stays hidden.
- **It doesn't keep state.** "WRS does not retain state across page loads." Cookies, localStorage and sessionStorage are cleared on every page load, and permission prompts are declined.
- **It uses HTTP only.** Content that arrives over WebSockets or WebRTC never shows up.
- **It caches hard.** Googlebot caches files aggressively, and "WRS may ignore caching headers."
- **It may skip non-200 pages.** "If the HTTP status code is non-200 (for example, on error pages with 404 status code), rendering might be skipped."

## CSR vs SSR vs SSG vs pre-rendering for SEO

Where your HTML gets built decides what a crawler receives first.

- **Client-side rendering (CSR).** The server sends a near-empty HTML shell and a script bundle, and the browser builds the page. This is the default for React and Vue apps built with Vite, Angular without SSR, and most AI website builders. Crawlers that don't run JavaScript get the shell.
- **Server-side rendering (SSR).** The server builds the HTML on each request, as in Next.js, Nuxt, Angular SSR and SvelteKit. Crawlers get that first response. Anything the page fetches in the browser after hydration still arrives later.
- **Static site generation (SSG).** The HTML is built at deploy time and served as files. Incremental Static Regeneration (ISR) rebuilds pages in the background after a deploy, so content can update without a full rebuild.
- **Hydration, streaming and React Server Components.** Hydration attaches JavaScript to server-built HTML so the page becomes interactive. Streaming sends the HTML in chunks as data resolves; the content is in the response, but it lands at the end of the document and a script moves it into place. React Server Components render on the server and ship no JavaScript for those parts. All of these put content in the HTML, as long as its data resolves on the server.
- **Pre-rendering for crawlers (dynamic rendering).** Crawlers get a snapshot of each page rendered ahead of time in a headless browser. People get the normal site.

Google's dynamic rendering page (https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering) says dynamic rendering "was a workaround and not a long-term solution" and recommends server-side rendering, static rendering or hydration. The same page still documents the setup and says Google generally doesn't treat it as cloaking. What that guidance means for an existing site is in dynamic rendering SEO (https://encited.com/blog/dynamic-rendering-seo).

For a new build, server rendering or static generation for the pages that need to rank is the cleaner start, and SEO-friendly JavaScript frameworks (https://encited.com/blog/seo-friendly-javascript-frameworks) compares what each framework renders by default. Pre-rendering fits an existing client-side app you can't rebuild, and server-rendered pages that still load important content in the browser. The trade-offs between the two are in dynamic rendering vs server-side rendering (https://encited.com/blog/dynamic-rendering-vs-server-side-rendering), and what each option gives a crawler is in SSR vs SSG vs pre-rendering (https://encited.com/blog/ssr-vs-pre-rendering).

With Encited's pre-rendering (https://encited.com/pre-rendering), crawlers get a finished snapshot of each page while people keep getting your app, on any JavaScript site and without code changes. Each URL group gets its own refresh rule (daily, weekly, monthly or quarterly), you can re-cache any page by hand after a change, and your page can set `window.prerenderReady = true` when its data has loaded so the snapshot never catches a spinner.

## JavaScript SEO pitfalls: links, routing, tags and resources

Rendering decides whether a crawler can see your content. The pitfalls below break indexing even when the render succeeds, and most of them are a one-line fix once you know where to look.

### Links crawlers can't follow

Google's rule: "Google can only discover your links if they are `<a>` HTML elements with an `href` attribute." Click handlers on other elements leave crawlers nothing to follow:

jsx

Copy Download

// Crawlers can follow these

< a href = " /pricing " > Pricing </ a >

< Link to = " /pricing " > Pricing </ Link > // React Router renders <a href="/pricing">

// Crawlers can't follow these

< span onClick = { () => navigate ("/pricing") } > Pricing </ span >

< button onClick = { () => navigate ("/pricing") } > Pricing </ button >

< a onClick = { () => navigate ("/pricing") } > Pricing </ a >

< a href = " javascript:void(0) " onClick = { openPricing } > Pricing </ a >

< a href = " #/pricing " > Pricing </ a >

Router link components (`Link` in React Router and Next.js, `RouterLink` in Vue, `routerLink` in Angular) render real anchors, so use them for every navigation link. Keep important links in the main HTML too: links inside menus that render only on hover or click don't exist until someone opens them.

Redirects done in the router run only after rendering. Google's redirect guide says to "Only use JavaScript redirects if you can't do server-side or meta refresh redirects," and AI crawlers never see them. Put permanent redirects at your server, host or CDN as 301s. When a JavaScript redirect does count, and how `nofollow` and hash links behave, is in JavaScript redirects and links (https://encited.com/blog/javascript-redirects-and-links-seo).

### Hash URLs

Routes like `/#/pricing` are one URL to a crawler. Google says, "To ensure that Googlebot can parse and extract your URLs, don't use fragments to load different page content." Switch the router to history mode so each route has a real path.

### Every URL returns 200

Your host serves `index.html` with a 200 status for any path, including `/this-page-does-not-exist`. The router shows a "not found" screen, and Google files the URL as a soft 404, or indexes it.

Google's documentation offers two client-side fixes: redirect with JavaScript to a URL that returns a real 404, or add a `noindex` robots tag to the error page with JavaScript. Google has since said that how it handles `noindex` in JavaScript pages "is not well defined and might change," so a real 404 status from your server or edge is the safer signal. With Encited, your not-found route sets `<meta name="prerender-status-code" content="404">` and crawlers get a 404.

The reverse happens too. A real page can show up as a soft 404 in Search Console when Google's render came back empty, or caught a loading spinner or an error screen. Check that page's rendered HTML in URL Inspection.

### noindex and canonical tags changed by JavaScript

**Title and description.** On a client-side app, `index.html` holds one title and one description, and the router changes them after the scripts run. Any crawler reading the raw HTML sees your homepage's title on every URL, and so do link previews on Slack and LinkedIn. Run two different routes through the ChatGPT crawler simulator (https://encited.com/free-tools/crawler-simulator/chatgpt-crawler-simulator) and compare the `<title>` in each response; if they match, every crawler that reads raw HTML gets your homepage title.

**noindex.** "When Google encounters the `noindex` tag, it may skip rendering and JavaScript execution." A script that removes `noindex` after load can't undo one in the raw HTML. This catches sites that ship a staging `noindex` in the HTML template and remove it with JavaScript. If you want a page indexed, keep `noindex` out of its original HTML.

**Canonical.** Google says "you shouldn't use JavaScript to change the canonical URL to something else than the URL you specified" in the original HTML. When `index.html` declares the homepage as canonical and JavaScript changes it per route, Google sees two different canonicals for the same page, and you get "Duplicate, Google chose different canonical than user" in Search Console, or only the homepage indexed. Set the canonical in the HTML your server sends. If your setup can't, leave it out of the original HTML and set it once with JavaScript, so the page never carries two.

The same goes for hreflang and `nofollow`. Anything that changes between the raw and rendered HTML gives Google two answers to choose from.

### Content that loads on scroll or click

**Infinite scroll and "load more".** Google's pagination guide (https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading) says its crawlers don't "click" buttons. Items that appear only after scrolling or clicking never get crawled. Give each page of results its own URL, such as `?page=2`, and link to it with a real anchor. Your script can still load it in place for people:

html

Copy Download

< a href = " /blog?page=2 " class = " load-more " > Load more posts </ a >

Give each paginated page its own canonical. Google's guide says, "Don't use the first page of a paginated sequence as the canonical page."

**Tabs, accordions and "read more".** Google indexes text that's in the HTML even when a tab or accordion hides it. Text that a tab fetches from an API on click never reaches a crawler. Render every panel into the page and hide the inactive ones with CSS, or use `<details>` and `<summary>`.

**Lazy loading.** Google's lazy-loading guide (https://developers.google.com/search/docs/crawling-indexing/javascript/lazy-loading) says "don't rely on user actions, such as scrolling or clicking, to load content." Use `loading="lazy"` for images and iframes, or `IntersectionObserver` for other content, so it loads when it enters the viewport. And "Don't add lazy-loading to content that is likely to be immediately visible when a user opens a page."

**Content fetched after load.** Prices, stock, reviews and related products that a `fetch` call adds after the page opens are indexed by Google when the call is fast and unblocked. AI crawlers and link previews never see them, and Google indexes the version it rendered, which can lag the crawl by hours. To check a page, open view-source and search for a price or a review sentence. If it's missing, fetch that data on the server or pre-render the page after it loads.

### Structured data added by JavaScript

Google reads JSON-LD that JavaScript adds, once it renders the page. Crawlers that don't render miss it. For stores there's an extra cost: Google's JavaScript structured data guide (https://developers.google.com/search/docs/appearance/structured-data/generate-structured-data-with-javascript) warns: "Be aware that dynamically-generated markup can make Shopping crawls less frequent and less reliable." It names product availability and price as the content where that hurts, and Google's October 2024 update recommends `Product` markup in the initial HTML.

Review apps and widgets that inject star ratings after load are the usual source. Test with the Rich Results Test using the URL input, which renders the page.

### Scripts and APIs Google can't fetch

**Blocked resources.** Google needs your JavaScript, CSS and API responses to render the page. A robots.txt rule like `Disallow: /assets/` or `Disallow: /api/` leaves the render empty. An API on another domain follows that domain's robots.txt. URL Inspection lists the resources that failed to load.

**Slow or chained API calls.** Google doesn't publish a render timeout. A page that waits on several API calls in a row can be captured before the content arrives. Fetch the data on the server, cut the chain, or pre-render the page.

**Old bundles.** Because Google caches files and may ignore your caching headers, it can run an old script against new HTML. Use content fingerprinting: a hash of the file's content in its filename, like `index-3f9a2c.js`. Vite does this by default for production builds.

**Geo-redirects and consent walls.** Google says "the default IP addresses of the Googlebot crawler appear to be based in the USA," so a script that redirects by IP or language sends Googlebot to your US page every time; Google recommends "separate locale URL configurations" annotated with hreflang. A consent modal that waits for a stored flag covers the page on every crawl, because the renderer clears storage on each load.

### No sitemap

Most SPA build tools don't generate a sitemap. Without one, Google finds your routes only through links it extracts, and on a client-side app those links exist only after rendering. Generate `sitemap.xml` from your route list or CMS at build time, list only canonical URLs that return 200, and submit it in Search Console.

### JavaScript cost for real visitors

Heavy JavaScript also slows your pages for people, which shows up in Core Web Vitals. Content that waits for a large bundle delays Largest Contentful Paint. Hydration and long scripts that block the main thread hurt Interaction to Next Paint (INP), which replaced First Input Delay; Google counts an INP of 200 milliseconds or less (https://web.dev/articles/inp) as good, measured at the 75th percentile of visits.

Check field data in PageSpeed Insights, find unused code in the Coverage tab of Chrome DevTools, and trace slow interactions in the Performance panel. Split code by route and defer third-party scripts. Pre-rendering for crawlers doesn't change this part: visitors still download and run your bundle.

## Googlebot vs AI crawlers

Vercel and MERJ's crawler data (https://vercel.com/blog/the-rise-of-the-ai-crawler), published December 17, 2024, found that "none of the major AI crawlers currently render JavaScript." That covered OpenAI's GPTBot, OAI-SearchBot and ChatGPT-User, Anthropic's ClaudeBot, and PerplexityBot. ChatGPT's and Claude's crawlers did download JavaScript files, in 11.50% and 23.84% of their requests, without running them. Googlebot renders, Gemini uses Googlebot's rendering, and Applebot renders too.

A crawl log makes the difference visible. On a JavaScript site without a pre-render layer, Googlebot fetches the page and then comes back for the script bundle and API responses it needs to render it; GPTBot, ClaudeBot and PerplexityBot fetch the page once, get the shell, and leave. The Encited crawl log (https://encited.com/docs/crawl-logs/read-your-crawl-log) records, for every known crawler request, the bot, the URL, the status code and the outcome: a snapshot served from the edge or from storage, a fresh render, a pass-through to your origin HTML, or a redirect, and whether the snapshot went out as HTML or Markdown. With pre-rendering on, the same AI crawlers get a snapshot with the full page in it, and the log shows that for each visit.

So a client-side app can be fully indexed in Google and still be empty to ChatGPT, Claude and Perplexity. Do AI crawlers render JavaScript (https://encited.com/blog/do-ai-crawlers-render-javascript) has the per-crawler answer and how to check each one, and the free AI crawl checker (https://encited.com/free-tools/ai-crawl-checker) reads your robots.txt rules for each AI crawler and fetches a page as GPTBot.

## How to run a JavaScript SEO audit

A JavaScript SEO audit checks, for each page template, what each crawler gets and whether it matches what people see. The walkthrough with every command and tool is in test JavaScript SEO (https://encited.com/blog/test-javascript-seo); the six checks below are the order to run them in.

### 1. Compare view-source with the rendered DOM

View source (Ctrl+U, or Cmd+Option+U on a Mac) shows the raw HTML your server sent. The Elements panel in DevTools, which opens when you right-click and choose Inspect, shows the DOM after JavaScript ran. That's what Google sees after rendering, and what crawlers that don't render never see.

Pick a sentence of body text, an internal link, the title, the canonical and the robots tag, and search for each in both views. Anything missing from view-source depends on JavaScript. To run this comparison on one URL without DevTools, the free SEO audit (https://encited.com/free-tools/seo-audit) flags text and internal links that are missing without JavaScript.

### 2. Check what Google rendered with URL Inspection

In Search Console, inspect the URL. "View crawled page" shows the HTML Google indexed. "Test live URL", then "View tested page", adds a screenshot, the page resources that failed to load, and JavaScript console messages. Compare the user-declared canonical with the Google-selected one. For structured data, run the Rich Results Test on the URL.

### 3. Fetch the page as each crawler with curl

bash

Copy Download

\# Googlebot's first request, before rendering

curl -s -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \

https://yoursite.com/pricing | grep -iE "<title>|canonical|noindex"

\# What ChatGPT's crawler gets: count a sentence from your page

curl -s -A "GPTBot" https://yoursite.com/pricing | grep -c "a sentence from your page"

\# Status code for a URL that shouldn't exist (should be 404)

curl -s -o /dev/null -w "%{http_code}\n" https://yoursite.com/this-page-does-not-exist

These requests come from your IP. A site that verifies crawlers by IP address can answer them differently from the real Googlebot.

### 4. Run the crawler simulator

The free crawler simulator (https://encited.com/free-tools/crawler-simulator) requests a URL as Googlebot, GPTBot, ClaudeBot, PerplexityBot and two dozen other user agents, and shows the status code, headers and HTML each one gets. It answers the curl questions without a terminal. The Googlebot simulator (https://encited.com/free-tools/crawler-simulator/googlebot-simulator) shows the raw response Google reads before it renders.

### 5. Read your crawl logs

Server logs show what crawlers actually requested. Check which pages Googlebot fetches and how often, the status codes it got, whether it requests your JavaScript bundles and API endpoints, and whether AI crawlers visit at all. Encited's crawl logs (https://encited.com/docs/crawl-logs/read-your-crawl-log) show which bots visited each page and what they got back, without digging through raw server logs.

### 6. Crawl the whole site

Spot checks miss problems that sit in a template. A site crawl finds pages with missing or duplicate titles, stray `noindex` tags, broken links and orphan pages across every route. Encited's site audit (https://encited.com/site-audit) groups those issues by type so you fix the template once. Then read the Pages report in Search Console: URLs stuck in Discovered - currently not indexed (https://encited.com/blog/discovered-currently-not-indexed) or Crawled - currently not indexed (https://encited.com/blog/crawled-currently-not-indexed), sorted by template, show where rendering or content is failing. Once the pages are fixed, Index Rush (https://encited.com/get-indexed-faster) shows the index status of each URL and requests indexing in one click.

## JavaScript SEO for React, Vue, Angular and storefronts

Each framework has its own way of setting titles, routing and rendering on the server:

- **React:** React SEO (https://encited.com/blog/react-seo), for React apps built with Vite or Create React App, and Next.js.
- **Vue:** Vue SEO (https://encited.com/blog/vue-seo), for Vue single page apps and Nuxt.
- **Angular:** Angular SEO (https://encited.com/blog/angular-seo), for Angular apps with or without SSR.
- **Choosing a framework:** SEO-friendly JavaScript frameworks (https://encited.com/blog/seo-friendly-javascript-frameworks), for the default rendering mode, SSR and SSG support and meta handling in React, Next.js, Vue, Nuxt, Angular, SvelteKit and Astro.
- **Storefronts:** ecommerce SEO (https://encited.com/blog/ecommerce-seo), for headless and JavaScript-heavy stores.
- **Rendering patterns:** dynamic rendering for React, Vue and Angular (https://encited.com/blog/dynamic-rendering-react-vue-angular), for router modes, per-route titles and real 404s in each framework.

One Next.js detail affects AI crawlers. Since Next.js 15.2, the Next.js docs (https://nextjs.org/docs/app/api-reference/functions/generate-metadata#streaming-metadata) say streamed metadata tags "are appended to the `<body>` tag" for bots outside Next.js's HTML-limited list, and that list leaves out GPTBot, ClaudeBot and PerplexityBot. React SEO (https://encited.com/blog/react-seo) shows the `htmlLimitedBots` fix.

## FAQ

**Is JavaScript bad for SEO?** No. Google renders JavaScript and indexes what it builds. JavaScript hurts SEO when content, links or tags exist only after a click or scroll, when a blocked or slow resource breaks the render, or when the crawler never runs scripts, which is the case for the AI crawlers.

**Can Google index a SPA built with React, Vue or Angular?** Yes. Googlebot fetches the shell, queues the page for rendering, and indexes the rendered result. Indexing is slower than for plain HTML and fails when the render errors, times out or waits for a user action. AI crawlers index only what the shell contains.

**CSR vs SSR: which is better for SEO?** SSR puts the finished page in the first HTML response, so every crawler gets it. CSR leaves that to Google's render queue and gives AI crawlers nothing. For an existing CSR app, pre-rendering for crawlers gives the same first response as SSR without a rebuild.

**Why does Googlebot see a blank page?** The render came back empty. The usual causes are a script, stylesheet or API blocked in robots.txt, a JavaScript error during rendering, data that loads after a click or scroll, or an API chain that was too slow. "Test live URL" in URL Inspection lists the failed resources and console errors.

**Is dynamic rendering or prerendering still supported?** Yes. Google's dynamic rendering page says it "was a workaround and not a long-term solution" and recommends server-side rendering for new builds, and the same page still documents the setup and says Google generally doesn't treat it as cloaking. Googlebot still processes pre-rendered pages, and AI crawlers can't read a client-side app any other way.

**How does client-side routing affect discovery and link equity?** Crawlers follow `<a href>` links only, so routes reached through click handlers or hash URLs are never discovered and pass no link equity. Router link components render real anchors in history mode, and those links count like any other.

**How do I test what Googlebot renders?** Inspect the URL in Search Console, click "Test live URL", then "View tested page". The HTML tab is the rendered DOM Google indexed, the Screenshot tab shows whether it rendered correctly, and the More info tab lists blocked resources and console errors.

**Is React still bad for SEO in 2026?** A React app that uses history routing, real links, per-route titles and real 404s indexes in Google with some delay. The same app sends AI crawlers an empty shell unless it's server-rendered, statically generated or pre-rendered. Next.js and React Router framework mode render on the server; a Vite app needs pre-rendering.

Image: Google (https://encited.com/crawler-logos/google-search.svg)

Image: Gemini (https://encited.com/crawler-logos/gemini.png)

Image: ChatGPT (https://encited.com/crawler-logos/chatgpt.svg)

Image: Claude (https://encited.com/crawler-logos/claude.svg)

Image: Grok (https://encited.com/crawler-logos/xai.svg)

Image: Meta (https://encited.com/crawler-logos/meta.svg)

Image: Perplexity (https://encited.com/crawler-logos/perplexity.png)

## Get discovered anywhere search happens

Readable, citable, outranking pages.

Talk to sales: https://encited.com/contact-sales

Image: Avatar (https://replymaven.com/api/uploads/Su6Q0nK63iZW0ZFJBRNWpmRuppVDLuab/98df5262-f992-45fa-a750-00a6c919baf9.png)

How can we help?

Get instant answers to your questions or leave a message for an engineer will reach out

Ask AI about Encited

See our docs (https://lovablehtml.com/docs)

Contact support

Leave a message

We'll get back to you soon

Ask AI about Encited

Product questions, troubleshooting and

Thinking

Image: Preview

Drop an image to attach

Powered by ReplyMaven (https://replymaven.com/)

## Structured data

```json
[
  {
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "name": "Home",
        "item": "https://encited.com/"
      },
      {
        "@type": "ListItem",
        "position": 2,
        "name": "Blog",
        "item": "https://encited.com/blog"
      },
      {
        "@type": "ListItem",
        "position": 3,
        "name": "JavaScript SEO: How Google Crawls, Renders and Indexes JS Sites (2026)",
        "item": "https://encited.com/blog/javascript-seo"
      }
    ]
  },
  {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": "JavaScript SEO: How Google Crawls, Renders and Indexes JS Sites (2026)",
    "description": "JavaScript SEO: how Googlebot crawls, renders and indexes JavaScript, CSR vs SSR vs SSG vs pre-rendering, the pitfalls that block indexing, and how to audit.",
    "image": "https://encited.com/blog/thumbnails/javascript-seo.webp",
    "datePublished": "2026-10-16",
    "dateModified": "2026-10-29",
    "author": {
      "@type": "Person",
      "name": "Aki from Encited"
    },
    "publisher": {
      "@type": "Organization",
      "name": "Encited.com",
      "alternateName": "LovableHTML.com",
      "logo": {
        "@type": "ImageObject",
        "url": "https://encited.com/brand/logo.svg"
      }
    },
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://encited.com/blog/javascript-seo"
    }
  }
]
```