# JavaScript SEO: how search and AI crawlers read JavaScript sites

> JavaScript SEO makes sure search engines and AI crawlers can find, render and index a site built with JavaScript. How Google handles scripts, what breaks, and how to fix it.

Updated 2026-09-26 · Technical SEO · HTML version: https://getreport.app/guides/javascript-seo

JavaScript SEO is the part of technical SEO that makes sure search engines and AI crawlers can find, render and index a site whose content, links or tags are built by JavaScript. The short version: Google can run your scripts, but later and not always completely, and most other crawlers never run them at all, so anything that has to rank or be quoted belongs in the HTML your server sends. This guide is for site owners, marketers and developers working on a React, Vue, Angular or other JavaScript site, or on any site where scripts add important content. By the end you will know how Google processes JavaScript, which patterns lose pages, how to check your own site in two minutes and which rendering setup to ask your developer for.

## Quick answer

- **Google processes JavaScript in three steps:** it crawls the HTML, queues the page for rendering in a headless Chromium, then indexes the rendered result. The render can come seconds or much later.
- **Most other readers do not run JavaScript.** AI crawlers such as GPTBot and ClaudeBot, and link previews in Slack, WhatsApp and LinkedIn, read only the HTML the server sends.
- **Server-render what must rank:** titles, headings, main text, internal links, structured data and the canonical and robots tags. Server-side rendering (SSR) or static generation (SSG) does this; keep JavaScript for interaction.
- **Use real links** (`<a href="https://getreport.app/shoes/">`), real URLs per view (the History API, not `#/` routes) and real status codes for missing pages.
- **Never let JavaScript add or change a `noindex` or canonical.** Google may act on the HTML version before your script ever runs.
- Check any page for free with the [JavaScript SEO check](https://getreport.app/tools/js-rendering-check): it compares the raw HTML with the rendered page, field by field.

## What is JavaScript SEO?

A classic website sends finished HTML: the server builds the page, and the browser only displays it. Many modern sites work the other way round. The server sends a nearly empty HTML shell and a bundle of JavaScript, and the browser runs the scripts, fetches data and builds the page. React, Vue and Angular apps work like this by default unless a framework renders them on the server first.

For a visitor with a fast phone, both look the same. For a crawler, they are very different documents. JavaScript SEO is the work of closing that gap: choosing a rendering setup that puts the important content in the first HTML response, making links and URLs crawlable, keeping head tags stable, and checking what crawlers really receive.

It overlaps with speed work too. The same scripts that hide content from crawlers also delay what visitors see: large bundles block rendering, and long tasks make the page slow to respond. [Render-blocking resources](https://getreport.app/guides/render-blocking-resources) and [long tasks on the main thread](https://getreport.app/guides/long-tasks-and-main-thread-work) cover that side.

## How does Google process JavaScript?

Google's documentation, [Understand the JavaScript SEO basics](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) on Google Search Central, describes three phases:

1. **Crawling.** Googlebot fetches the URL and reads the HTML the server returns. It collects the links it finds in `<a href>` elements and checks robots.txt before fetching anything.
2. **Rendering.** The page goes into a render queue. When Google has resources, a headless Chromium that tracks the current Chrome version loads the page, runs the scripts and builds the final DOM. Google says a page may stay in the queue "for a few seconds, but it can take longer than that".
3. **Indexing.** Google indexes the rendered HTML: the text, the links it now finds and the tags as they stand after the scripts ran.

Three details in that process decide most JavaScript SEO problems.

**A `noindex` in the HTML stops the process.** Google's documentation says that when it finds `noindex` in the HTML, it may skip rendering and JavaScript execution. A script that removes a `noindex` later is never run, so the page stays out of the index.

**Googlebot does not behave like a person.** It does not click buttons, open tabs, scroll to load more or accept a cookie banner. It does not keep cookies, local storage or session storage from one page load to the next. Content that appears only after one of those actions is not rendered.

**Everything the render needs must be fetchable.** If robots.txt blocks the API endpoint or the script file that builds the content, the renderer cannot fetch it, and Google indexes an empty or half-built page.

The cluster guide on [how Google crawls, renders and indexes JavaScript content](https://getreport.app/guides/javascript-rendered-content-and-google) goes through this process in depth: the four patterns that lose pages, the `curl` and URL Inspection checks that show what Googlebot saw, and what "Crawled – currently not indexed" often means on a JavaScript site.

## Is JavaScript bad for SEO?

No. JavaScript itself is not a ranking problem, and Google renders JavaScript-built pages every day. The risk comes from one specific setup: content, links or tags that exist **only** after scripts run in the browser. Then you depend on a second, later step that can fail quietly, and on crawlers that may not take that step at all.

A site built with a JavaScript framework that renders on the server, such as Next.js, Nuxt, SvelteKit, Astro or Angular with server rendering switched on, sends complete HTML and is as crawlable as any WordPress site. A single-page app that sends `<div id="root"></div>` and builds everything in the browser is the risky case, whichever framework it uses. The rendering mode matters far more than the brand of framework.

## Which crawlers do not run JavaScript?

Google is the most capable renderer on the web. Almost everything else that reads your pages is simpler.

| Reader | Runs JavaScript? | What it sees on a client-rendered page |
| --- | --- | --- |
| Googlebot | Yes, in a later rendering step | The rendered page, after a delay |
| Bingbot | Yes, according to Bing, but be conservative | Usually the rendered page; serve essentials in HTML |
| GPTBot, ClaudeBot, PerplexityBot and most AI crawlers | No | The raw HTML only |
| Link previews (Slack, WhatsApp, LinkedIn, Facebook, iMessage) | No | The raw HTML and its Open Graph tags |
| Most SEO crawlers | Only with rendering switched on | Depends on the setting |

The AI crawler row is not a guess. A joint analysis by Vercel and MERJ, published by Vercel in December 2024 as "The rise of the AI crawler", looked at hundreds of millions of fetches across Vercel's network and found no evidence that the major AI crawlers, including OpenAI's, Anthropic's, Perplexity's, Meta's and ByteDance's, execute JavaScript. Some of them downloaded script files but did not run them. Google's Gemini is the exception, because it uses Google's crawling infrastructure.

So a client-rendered page may rank in Google and still be invisible when someone asks an AI assistant about your product. The guide to [AI crawlers and robots.txt](https://getreport.app/guides/ai-crawlers-and-robots-txt) covers which bots to allow, and the [AI crawler check](https://getreport.app/tools/ai-crawler-check) shows what they can fetch.

## Rendering strategies: CSR, SSR, SSG and hydration

The single most important JavaScript SEO decision is where the HTML gets built. There are four common answers.

| Strategy | Where the HTML is built | What crawlers get | SEO fit |
| --- | --- | --- | --- |
| Client-side rendering (CSR) | In the visitor's browser | An empty shell until scripts run | Risky for pages that must rank |
| Server-side rendering (SSR) | On the server, per request | Complete HTML | Good |
| Static site generation (SSG) | At build time, as files | Complete HTML | Good, and the fastest |
| Incremental regeneration (ISR) | At build time, refreshed in the background | Complete HTML | Good for large catalogues |

Most frameworks combine a server-rendered or static first view with **hydration**: the browser receives full HTML, then JavaScript attaches to it and makes it interactive. Crawlers get the complete page and visitors get the app. This is what Google recommends, and it is the default in Next.js, Nuxt, SvelteKit and Remix.

### Server-side vs client-side rendering

Server rendering puts the content in the first response, which helps Largest Contentful Paint as well as indexing. Client rendering saves server work but moves it to the visitor's phone and to Google's render queue. For pages that need to rank, server or static rendering is the safe choice; for a dashboard behind a login, client rendering is fine.

Our comparison of [CSR vs SSR for SEO and speed](https://getreport.app/guides/ssr-vs-csr-seo) puts client rendering, server rendering and static generation side by side, including what each does to time to first byte, Largest Contentful Paint and Interaction to Next Paint, and recommends a mode per page type.

### Dynamic rendering and prerendering

Dynamic rendering means detecting crawlers and serving them a prerendered HTML snapshot, while visitors get the client-side app. Prerendering services and self-hosted headless browsers do this. Google's own page on [dynamic rendering](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering) now calls it "a workaround and not a long-term solution", and recommends server-side rendering, static rendering or hydration instead.

Google does not treat dynamic rendering as cloaking as long as the snapshot shows the same content visitors see. It still adds a second system that can go stale or break, and it has to recognise every crawler you care about, AI bots included. Use it as a bridge while you move to server rendering, not as the destination.

The guide to [dynamic rendering and prerendering](https://getreport.app/guides/dynamic-rendering-and-prerendering) explains when the bridge is worth building, the checklist for running one (bot list, status codes, cache refresh), and the difference from build-time prerendering, which serves the same HTML to everyone and is what Google calls static rendering.

## The most common JavaScript SEO issues

These are the patterns that cost pages their rankings, roughly from most to least serious.

1. **`noindex` or canonical changed by JavaScript.** The HTML says one thing and the rendered page another. Google may act on either; with `noindex` in the HTML it may never render at all. This is the worst issue because the page can drop out of the index entirely.
2. **Main content fetched in the browser.** The product description, price, article text or reviews arrive through a client-side API call. If the call fails, times out or is blocked in robots.txt, Google indexes the loading state.
3. **Links that are not links.** Navigation built with `<span onclick>` or `<div>` elements, or `<a>` tags without an `href`, gives Googlebot nothing to follow. Google only follows `<a>` elements with an `href` attribute.
4. **Hash routes.** URLs such as `example.com/#/shoes` look like separate pages to visitors, but everything after `#` is a fragment Google does not use to separate pages. Use the History API and real paths such as `/shoes/`.
5. **Soft 404s.** A single-page app answers `200 OK` for every path and shows "Product not found" with JavaScript. Google sees a working page with no content. Google's documentation gives two fixes: redirect with JavaScript to a URL that answers `404`, or add `<meta name="robots" content="noindex">` to the error view.
6. **Head tags set late.** Titles, descriptions and Open Graph tags set by a client-side head manager are read by Google after rendering but are missing for link previews and AI crawlers.
7. **Content behind interaction.** Tabs that load their text on click, "load more" buttons and infinite scroll without paginated URLs underneath. Googlebot does not click or scroll.
8. **Structured data injected by a tag manager.** Google can read JSON-LD added by JavaScript, but only after rendering, and other consumers may not. Put it in the server HTML.

### Infinite scroll and lazy loading

Infinite scroll is the most common version of issue 7. It works for search when every batch of items also exists at its own URL, such as `/shoes/?page=2`, linked with ordinary `<a href>` links, so Google can crawl page 2 without scrolling.

Our guide to [infinite scroll SEO and crawlable load-more lists](https://getreport.app/guides/infinite-scroll-seo) shows the full pattern: paginated pages that work without JavaScript, a load-more link the script takes over, `IntersectionObserver` instead of scroll events, and the canonical and title rules for each page. The guide on [pagination after rel=prev/next](https://getreport.app/guides/pagination-after-rel-prev-next) covers paginated series on WordPress, WooCommerce and Shopify.

Lazy loading below the fold is fine when it triggers as the element enters the viewport, and fails when it waits for a scroll event or click. [Lazy loading done right](https://getreport.app/guides/lazy-loading-done-right) shows the safe patterns.

## How to check your JavaScript SEO

Start with the page that earns the most, such as your top product, category or article, and compare what the server sends with what the browser builds.

> **Free tool:** [JavaScript rendering checker: raw vs rendered HTML](https://getreport.app/tools/js-rendering-check): Free JavaScript rendering checker: compare raw HTML with the rendered page and see which text, links, tags and structured data appear only after scripts run.

The check loads the page twice: once as plain HTML, the way a crawler's first pass and every non-rendering bot sees it, and once in Chromium after the scripts have run. It then compares title, description, canonical, robots, headings, links, word count and JSON-LD field by field, flags any tag JavaScript changes, canonical and `noindex` first, and counts the text that exists only in the rendered version. It uses one page load on a phone profile, so content that needs a click, scroll or login is not rendered, which is also how Googlebot behaves.

The full getReport report runs two findings from the same comparison:

> **Check: JavaScript leaves the title, canonical and robots tags alone.** Search engines read the HTML first and the rendered page later, if at all. A canonical or noindex that JavaScript changes sends two different instructions, and Google may act on either.
>
> 1. Put the final title, description, canonical and robots tags in the HTML the server sends.
> 2. In a single-page app, set them on the server for each route (Next.js metadata, Nuxt useHead with SSR, Angular Universal).
> 3. Never add noindex with JavaScript to a page you want indexed; Google may drop it before the script runs.

> **Check: The visible text is present without JavaScript.** Google renders JavaScript later and with a budget, so text that only appears after scripts run can be indexed late or not at all. Other search engines and link previews may never see it.
>
> 1. Serve the main content in the HTML (server-side rendering or static generation) and use JavaScript only to enhance it.
> 2. Check the difference in the technical detail; menus and widgets are fine, headlines and body copy are not.

Read the second one with the page in mind. A cookie banner, a chat widget and a "recently viewed" strip add some words after JavaScript on almost any site. The number to act on is when the headline, the product description or the article body is in the rendered column only.

The report's other SEO findings read the raw HTML, just as a crawler's first pass does. A canonical or robots tag injected by a script therefore shows up there as missing:

> **Check: Canonical tag is present.** Without a canonical URL, search engines may index duplicate versions of this page (with and without trailing slash, with UTM parameters) and split its ranking signals.
>
> 1. Add <link rel="canonical" href="https://your-site.com/page/"> in <head>.
> 2. In WordPress, Yoast or Rank Math add this automatically — check it is not disabled for this page.

> **Check: The page is not set to noindex.** A noindex directive tells Google to drop this page from search results. Unless that is intended, every visit from search is lost.
>
> 1. Remove noindex from the robots meta tag and from the X-Robots-Tag header, unless the page should stay out of search.
> 2. In WordPress, untick Settings → Reading → "Discourage search engines" and check the page's SEO plugin settings.

Then confirm with Google's own view. In Google Search Console, URL Inspection shows the HTML Googlebot rendered under "View crawled page", and "Test live URL" renders the current version with a screenshot. For a quick check without any tool, compare View Source (the raw HTML) with the Elements panel in your browser's developer tools (the rendered DOM), or search the output of `curl -s https://example.com/page/` for a sentence from your main text.

## JavaScript SEO best practices

A checklist to hand to your developer. Each point is something a crawler can verify.

1. **Render the first view on the server or at build time.** Titles, headings, main text, internal links, images with `alt` text and structured data should all be in the first HTML response.
2. **Keep the head in the server HTML.** One `<title>`, one meta description, one canonical and the robots meta tag, final values, per route. Do not change them after load.
3. **Use crawlable links.** Every navigation element that leads to a page is an `<a href>` with a real path. Click handlers can add behaviour on top.
4. **Give every view its own URL.** Use the History API and clean paths. Filters and sorting that create new content get their own paths or parameters; ones that only reorder the same items do not need indexing.
5. **Answer with real status codes.** `404` or `410` for missing pages, `301` for moved ones, from the server. When a client-side router cannot, use one of Google's two soft-404 fixes above.
6. **Do not block what the render needs.** robots.txt must allow the scripts, styles and API endpoints the page uses to build its content.
7. **Do not require interaction for content.** Text in tabs and accordions should be in the HTML and only hidden with CSS. Load-more lists need paginated URLs underneath.
8. **Redirect on the server.** Google's [redirects documentation](https://developers.google.com/search/docs/crawling-indexing/301-redirects) says to use JavaScript redirects only when server-side or meta refresh redirects are not possible, because rendering can fail. The [301 redirects guide](https://getreport.app/guides/301-redirects) explains the options.
9. **Version your bundles.** Use content hashes in file names (`main.2bb85551.js`), as Google suggests, so crawlers and visitors never combine new HTML with an old cached script.
10. **Keep the bundle lean.** Less JavaScript renders faster for Google and for visitors on mid-range phones, which helps Interaction to Next Paint.

## JavaScript SEO by framework

The rules above apply everywhere. What changes between frameworks is which setting turns server rendering on and where the head tags live.

### React

React on its own renders in the browser, and Create React App, which scaffolded exactly that setup, was deprecated in February 2025. The React team now recommends starting with a framework that can render on the server, such as Next.js or React Router in framework mode (the successor to Remix), whose `prerender` option also produces static HTML per route.

Our [React SEO guide](https://getreport.app/guides/react-seo) covers the four pitfalls that cost client-side React sites their pages, which rendering option to choose per page type, and how to set titles and canonicals per route, including React 19's built-in `<title>` and `<meta>` support. If you have an existing client-rendered app, move the public pages first; the logged-in app can stay client-side.

### Next.js

Next.js renders on the server by default, and the Metadata API (`export const metadata` or `generateMetadata`) writes the head tags into the server HTML. Data loaded in a Client Component with `useEffect` reintroduces the client-rendering problem.

The guide to [Next.js SEO](https://getreport.app/guides/nextjs-seo) walks through metadata, canonicals and hreflang, `sitemap.ts` and `robots.ts`, status codes after streaming has started, and one setting worth knowing: since Next.js 15.2, streamed metadata lands at the end of the body for readers that are not on Next.js's list of HTML-only bots, which leaves out the main AI crawlers.

### Vue and Nuxt

A plain Vue app renders in the browser. Nuxt renders on the server by default (`ssr: true`) and can prerender routes at build time. Set head tags with `useSeoMeta` or `useHead`, which are rendered on the server when SSR is on. Switching SSR off for the whole app turns a Nuxt site into a client-rendered single-page app, with all the risks above.

The guide to [Nuxt SEO and Vue SEO](https://getreport.app/guides/vue-nuxt-seo) shows how to keep content in the server HTML with `useFetch`, set tags with `useSeoMeta` and `useHead`, mix prerendered, cached and client-only routes with `routeRules`, and answer missing pages with a real `404`. It also covers the options for a plain Vue app whose public pages need to rank.

### Angular

Angular renders in the browser unless server-side rendering is added. Since Angular 17, `ng new` offers SSR and prerendering through `@angular/ssr`, the successor to Angular Universal, and existing apps can add it with `ng add @angular/ssr`. Set the title per route with the router's `title` property and meta tags with the `Meta` service, which are included in the server-rendered HTML when SSR is on.

The [Angular SEO guide](https://getreport.app/guides/angular-seo) covers choosing a render mode per route (`Prerender`, `Server` or `Client`), a small service for canonical tags, and returning a real `404` from the not-found route with the `RESPONSE_INIT` token.

### Single-page applications

A single-page application (SPA) loads one HTML document and swaps views with JavaScript. It can be made indexable: every route needs its own path, its own title and canonical in the server response, crawlable links between routes and correct status codes. On static hosting, that usually means prerendering each public route to its own HTML file at build time rather than serving the same `index.html` for every path, which makes every missing URL answer `200` too.

Our guide to [SEO in single page applications](https://getreport.app/guides/single-page-application-seo) goes through each requirement for any framework: History API routing instead of hash URLs, per-route head tags without conflicting defaults in the shell, Google's two workarounds for soft 404s, and the SPA fallbacks of static hosts such as Netlify, Vercel and Cloudflare Pages.

### WordPress, Shopify and other CMSs

Traditional CMSs render on the server, so the main content is in the HTML. JavaScript problems there come from add-ons: reviews, prices or product variants loaded by an app or plugin in the browser, sliders that hold the only H1, and tag managers that inject structured data or canonicals. The same check finds them.

## Common mistakes

- **Testing only in a browser.** Your browser runs every script and remembers your cookies. Compare with the raw HTML.
- **Adding `noindex` to the shell and removing it with JavaScript.** Google may never run the script.
- **Serving the same `index.html` with `200` for every path.** Every typo URL becomes an indexable empty page. Missing routes should answer `404`.
- **Relying on Google alone.** Rendering in Google does not help link previews, AI assistants or other search engines. Server rendering helps all of them at once.
- **Changing the title in the browser after load for A/B tests.** Google may index either version, and previews show the original.

## Questions people ask

### How do I make JavaScript content crawlable?

Put it in the HTML the server sends. Use server-side rendering or static generation for the main text, headings, internal links and structured data, and use JavaScript to add interaction on top. Link between pages with real `<a href>` elements, give every view its own URL, and keep the canonical and robots tags in the server HTML. Then compare the raw and rendered page to confirm the important content is in both.

### What does JavaScript SEO involve?

It involves making sure crawlers can find, render and index a JavaScript-built site. In practice that means choosing server-side or static rendering for pages that must rank, making links crawlable, giving every view a real URL and status code, keeping head tags stable, not blocking scripts the page needs in robots.txt, and checking the raw HTML against the rendered page regularly.

### Is JavaScript bad for SEO?

No, but content that exists only after scripts run is a risk. Google renders JavaScript later and can miss content when scripts fail, time out or wait for a click, and most AI crawlers and link previews do not run JavaScript at all. Sites that render the main content on the server, even when built with React, Vue or Angular, are as crawlable as any other site.

### How long does Google take to render JavaScript pages?

Often seconds, sometimes much longer. Google's documentation says a page may stay in the render queue for a few seconds but can take longer, and it gives no fixed upper limit. Until the render happens, Google works with the raw HTML, so new pages on client-rendered sites can appear in the index with a missing title or snippet at first. Server-rendered pages avoid that wait.

### Does Google follow links created by JavaScript?

Yes, when they are real links in the rendered page. Google follows `<a>` elements with an `href` attribute, whether the server sent them or a script added them, although links in the raw HTML are found sooner. It does not follow click handlers on buttons, spans or divs, and it does not treat `#/` hash routes as separate pages. Use `<a href="https://getreport.app/path/">` for every navigation link.

### Do AI crawlers run JavaScript?

Mostly not. A Vercel and MERJ analysis published in December 2024 found no evidence that the major AI crawlers, including OpenAI's, Anthropic's and Perplexity's, execute JavaScript; some download script files without running them. Google's Gemini is the exception because it uses Google's infrastructure. So content that only appears after scripts run is missing when AI assistants read your page. Serve it in the HTML.

### Can I set the title and meta description with JavaScript?

Yes for Google, but not for other readers. Google reads a title and meta description set by JavaScript once it renders the page, and its documentation allows it. Other readers cannot: link previews and most AI crawlers take the tags from the raw HTML. Set the final title, description, canonical and robots tags in the server response for each route, and do not change them after the page loads.
