How Google processes a JavaScript page
Google works in three phases: crawling, rendering and indexing (Google, JavaScript SEO basics, updated March 4, 2026).
- Crawl. Googlebot fetches the URL and reads the HTML the server sent. It finds links in that HTML.
- Render. Pages that return a
200status go into a render queue, where a headless Chromium runs your JavaScript. Google notes that pages with a non-200 status may not be rendered at all. - Index. Google indexes the rendered HTML.
Two things follow. Anything only in the rendered version arrives later and depends on rendering working. And a crawler that never renders, which includes many AI search bots, only ever sees step 1.
Rendering options compared
| Approach | What the crawler gets first | SEO risk | Typical tools |
|---|---|---|---|
| Client-side rendering (CSR) | An almost empty HTML shell | High: content and links depend on rendering | Create React App style SPAs, plain Vue or Angular SPAs |
| Server-side rendering (SSR) | Full HTML for every request | Low | Next.js, Nuxt, Remix, SvelteKit, Angular SSR |
| Static generation (SSG) | Full HTML built ahead of time | Lowest | Next.js static export, Astro, Gatsby, Eleventy |
| Hybrid (SSR or SSG, then hydrate) | Full HTML, then JavaScript takes over | Low, if the server HTML is complete | Most modern frameworks by default |
Google calls dynamic rendering (serving bots a different version) a workaround, not a long-term solution. Prefer SSR or static generation.
The checklist: 12 checks with a test for each
| # | Check | How to test | Fix |
|---|---|---|---|
| 1 | Main content is in the server HTML | curl -s https://site.com/page and search for a sentence from the page | SSR or static generation for indexable templates |
| 2 | One <title> and meta description per URL, unique | View source, then URL Inspection > View tested page | Set them on the server per route (Next.js metadata, Nuxt useHead) |
| 3 | Canonical in the server HTML and not changed by JavaScript | Compare the raw HTML with the rendered HTML | Google says JavaScript should not change the canonical to a different URL than the one in the original HTML |
| 4 | Links are <a href="/path"> | Inspect your nav, pagination and product cards | Replace onclick handlers and <span> links with real anchors |
| 5 | Every view has its own URL | Click through the app and watch the address bar | Use the History API. Google cannot reliably resolve #/ fragment routes |
| 6 | Unknown routes return 404 | curl -I https://site.com/does-not-exist | Server returns 404, or JavaScript redirects to a URL that does, or adds noindex |
| 7 | No noindex in the server HTML that JavaScript later removes | Search the raw HTML for noindex | Google may skip rendering when it sees noindex, so the removal never runs. Decide on the server |
| 8 | JavaScript and API files are not blocked | robots.txt and URL Inspection > Page resources | Google will not render JavaScript from blocked files |
| 9 | Lazy-loaded content loads without scrolling or clicking | Rendered HTML in URL Inspection | Load in the viewport with IntersectionObserver or native loading="lazy", never only on scroll events |
| 10 | Bundle file names are fingerprinted | Check that main.js URLs change when content changes | Use hashed names like main.2bb85551.js so Google does not use a stale cached file |
| 11 | Structured data present after rendering | Rich Results Test on one URL per template | Output JSON-LD on the server where possible |
| 12 | Status codes are real, not shown only in the page | curl -I on a deleted product | Send 404 or 410 for gone pages and 301 for moved ones from the server |
Checks 3, 5, 6, 7, 8 and 10 come straight from Google's JavaScript SEO basics page linked above.
Soft 404s: the most common SPA problem
A single-page app often answers every URL with 200 and the same shell, then shows "Not found" with JavaScript. Google sees a 200 page with nothing useful on it and reports it as a soft 404, or worse, indexes it. Google gives two fixes: redirect with JavaScript to a URL where the server returns a 404, or add <meta name="robots" content="noindex"> to error views with JavaScript. If your framework supports it, a real server 404 is cleaner than both.
The noindex trap
Teams sometimes ship noindex in the HTML of a staging build and plan to remove it with JavaScript in production. Google says that when it sees noindex it may skip rendering, so the script that removes the tag never runs and the page stays out of the index. Set robots tags on the server, per environment.
Framework notes
- Next.js: the App Router renders on the server by default. Use the
metadataAPI orgenerateMetadatafor titles, descriptions and canonicals, andnotFound()so missing pages return 404. Be careful with pages marked"use client"at the top level that fetch their main content in the browser. - Nuxt: SSR is on by default. Use
useHeadoruseSeoMeta, and throw a 404 error in data fetching for missing records. - Astro: static HTML by default with islands of JavaScript. Usually the lowest risk.
- Plain React or Vue SPA: the highest risk. Consider prerendering the public pages or moving them to a framework with SSR.
- Angular: use Angular SSR for public routes.
AI crawlers read the raw HTML
Google renders JavaScript. Many AI search crawlers fetch the HTML and do not. If check 1 fails, assistants like ChatGPT search or Perplexity may see an empty page and never cite you. See robots.txt for AI crawlers for which bots to let in.
Test every release in five minutes
curl -sone URL per template and grep for the H1 and a sentence of body text.curl -Ia made-up URL and confirm a 404.- URL Inspection > Test live URL on one page per template. Compare the screenshot with the real page.
- Rich Results Test on the product or article template.
- Check Search Console > Indexing > Pages a week later for new soft 404s or "Crawled, currently not indexed" spikes.
How to use this with an AI assistant
Connect SEOmatic at https://seomatic.ai/mcp and ask: "Run check_site on mysite.com and tell me if my pages only render with JavaScript. Then sign me in with connect_seomatic and use site_pages (action: crawl) on one URL per template to show the title, canonical, headings and links in the HTML." check_site is free and needs no sign-in.
More from the technical SEO checklist
- The checklistTechnical SEO Checklist (2026)
- Guiderobots.txt for AI Crawlers: Which to Allow and Which to Block
- GuideHow to Fix INP (Interaction to Next Paint)
- GuideHow to Do a Technical SEO Audit, Step by Step (2026)
- GuideHreflang Checklist: Set It Up Right and Fix Common Errors
- GuideStructured Data Checklist 2026: What Still Earns a Rich Result


