The two-pass crawl
Google indexes JavaScript in two passes: first it fetches the raw HTML and indexes what is there; later it queues the URL for rendering, runs the scripts, and re-reads the result. The second pass can lag by days or, on low-priority pages, never arrive. AI crawlers are stricter — GPTBot, ClaudeBot and PerplexityBot fetch raw HTML and typically do not execute scripts at all. Content that exists only after rendering is invisible to all of them, permanently.
The JS shell problem
The signature is a page whose visible text is under a couple hundred words and whose markup contains an empty mount point — a div with id root, app, __next or __nuxt waiting for scripts to fill it. Google may render it eventually and see the content; AI assistants and fast fetchers never do, because they do not run scripts. A store that moved to a JS-heavy theme and watched impressions stagnate while rankings "should" have improved is usually looking at exactly this.
Stores: prices and names in raw HTML
For product pages the bar is specific: the price and the product name must exist in the raw HTML — ideally as JSON-LD structured data, because that is what Merchant Center reads, what rich results read, and what a crawler trusts. A price injected by a script after the fact is invisible to the first pass, disagrees with whatever the feed says, and feeds the price-mismatch disapproval class. If your product page needs JavaScript to show what the item costs, that is the finding.
The inspector shows the page after scripts have run — the rendered second pass. View-source shows the raw HTML, which is what the first pass sees. If the content is only in the inspector, it is invisible to every crawler that does not render.
Server-rendering: what actually fixes it
- Server-render the essentials: name, price, availability and description belong in the initial HTML for every product. Everything interactive can stay client-side.
- Keep JSON-LD in the template — schema injected by JavaScript is schema a first-pass crawler never reads.
- Check what a non-rendering reader gets before assuming the fix worked — view-source on a product URL, or run the check below and compare its raw-HTML findings against your browser.
Run the free JavaScript content check — it fetches your product pages the way a non-rendering crawler does and reports what is missing from the raw HTML: prices, names, and whether the page is a client-side shell.
Related: Core Web Vitals · structured data check · product schema on Shopify
Shipwork fetches your product pages as a non-rendering crawler does and checks the raw HTML for prices, names and server-rendered content — reporting anything that exists only after JavaScript runs. Free, no account, no signup — paste your store address.
Check JavaScript contentQuestions
- Does Google index JavaScript?
- Yes, in a second render pass — but the first pass reads raw HTML only, rendering is queued separately and can lag, and AI crawlers mostly do not render at all. Content only in scripts reaches a fraction of readers.
- Is my site a JavaScript shell?
- The signature: view-source shows almost no text and a near-empty div, while the rendered page is full. The check does exactly this comparison on your product pages.
- Do I need a full SSR rewrite?
- No. Server-render the essentials — title, name, price, availability, description, JSON-LD — and keep interactivity client-side. Most themes and frameworks support partial SSR without a rewrite.
- Does the check render JavaScript?
- No — deliberately. It reads the raw HTML the way the first crawl pass and AI fetchers do, which is the gap this check exists to expose.
Keep reading