Skip to content
Search visibility7 min read

JavaScript rendering and whether your content is actually seen

A page can look complete in a browser and be almost empty to a crawler. Google renders JavaScript, but on its own schedule and with no guarantee; most AI answer engines do not render it at all. If your primary content arrives only after hydration, you are gambling on a second pass that may never come.

Part ofTechnical SEO Engineering

This is the single most common structural cause of "our pages are not indexed and we cannot work out why." The content exists, the URL returns 200, the page renders perfectly for a human — and the initial HTML response contains a loading skeleton.

What each crawler actually does

Treating "crawlers" as one category is where the reasoning goes wrong. Their JavaScript behaviour differs enormously, and the gap has widened as AI answer engines have become significant traffic sources.

CrawlerExecutes JavaScript?Practical consequence
GooglebotYes, in a deferred second passClient-rendered content can be indexed — eventually, and not reliably
BingbotYes, more limitedTreat client-only content as at risk
Most AI answer crawlersGenerally noClient-rendered content is effectively invisible
Social preview fetchersNoClient-rendered meta tags produce blank link previews

Google's two-phase model is the source of the false confidence. The first pass reads the HTML; rendering is queued separately and may happen minutes or weeks later. For a site with modest crawl demand, that render queue is where content goes to be forgotten.

Test what is served, not what is displayed

Opening a page in a browser tells you nothing, because the browser runs the JavaScript. You need to see the raw response before any script executes.

bash
# The definitive check: what does the server actually return?
curl -s https://example.com/product/123 > raw.html

# Does the primary content exist in it?
grep -c "Add to basket" raw.html
grep -oE "<title>[^<]*</title>" raw.html
grep -oE '<link rel="canonical"[^>]*>' raw.html

# How much real text is there, with scripts and styles stripped out?
sed -e 's/<script[^>]*>.*<\/script>//g' \
    -e 's/<style[^>]*>.*<\/style>//g' \
    -e 's/<[^>]*>/ /g' raw.html | tr -s ' ' | wc -c

If that character count is a few hundred on a page that visibly contains a thousand words, the content is client-rendered and at risk. Cross-check with Search Console's URL Inspection tool, which shows both the raw HTML and Google's rendered version — the difference between them is precisely your exposure.

  1. Fetch the raw HTML with curl and confirm primary content, title, description and canonical are present.
  2. Run URL Inspection in Search Console and compare the HTML tab against the rendered screenshot.
  3. Disable JavaScript in the browser and reload — a rough but fast approximation of a non-rendering crawler.
  4. Check the Coverage report for "Crawled — currently not indexed" clustering on client-rendered templates.

The strategies, and what each costs

The decision is per route, not per application. A marketing page and an authenticated dashboard have entirely different requirements, and forcing one strategy across both is what creates the problem.

StrategyContent in initial HTMLBest for
Static generationCompleteMarketing pages, docs, articles, catalogues that change predictably
Incremental regenerationComplete, bounded stalenessLarge catalogues with frequently changing data
Server renderingComplete, generated per requestPersonalised pages that still need indexing
Streaming server renderingShell plus primary content firstContent-heavy pages with slow secondary data
Client renderingEmpty or skeletonAuthenticated tools that should not be indexed anyway

Note the last row. Client rendering is not wrong — it is wrong for content you want found. An internal dashboard behind a login has no indexing requirement, so its rendering strategy is purely a product decision.

The hybrid trap

Modern frameworks make it easy to render a page on the server and then move the important part to the client by accident. A single component that fetches its data in a browser-side effect can empty the part of the page that mattered.

tsx
// Server-rendered shell, client-rendered content. The product
// description — the thing you want indexed — is not in the HTML.
"use client";
export function ProductDetail({ id }) {
  const [product, setProduct] = useState(null);
  useEffect(() => {
    fetch(`/api/products/${id}`).then(r => r.json()).then(setProduct);
  }, [id]);

  if (!product) return <Skeleton />;        // <- what the crawler sees
  return <article>{product.description}</article>;
}

// Fetch on the server instead. The description ships in the HTML,
// and no loading state is ever rendered to a crawler.
export async function ProductDetail({ id }) {
  const product = await getProduct(id);
  return <article>{product.description}</article>;
}

Dynamic rendering, and why it is a last resort

Serving prerendered HTML to crawlers and client-rendered pages to humans works, and Google permits it provided both receive equivalent content. It is also a permanent second code path that drifts from the first, and a prerender service that fails silently takes your indexing with it.

It is a legitimate stopgap for a legacy single-page application that cannot be migrated soon. It is not a design to choose deliberately for a new build, because modern frameworks make server rendering the cheaper option.

The rule worth holding

Whatever a page is meant to be found for — its primary content, its title, its description, its canonical — belongs in the initial HTML response. Everything else can arrive later. A page that depends on a render queue is a page whose indexing you do not control.

Questions

Does Google index JavaScript-rendered content?

Often, yes — Googlebot renders pages in a deferred second pass. But it is queued rather than guaranteed, the delay varies from minutes to weeks, and pages with low crawl demand wait longest. Most AI answer engines and all social preview fetchers do not render JavaScript at all, so client-only content is invisible to them regardless.

How can I see what a crawler sees?

Fetch the raw HTML with curl and inspect it — that is the definitive answer, because it bypasses the browser's JavaScript execution entirely. Then compare against Search Console's URL Inspection tool, which shows Google's rendered output. The gap between the two is your exposure.

Is a single-page application bad for SEO?

The architecture is not the problem; where content is generated is. An SPA that server-renders its public routes indexes normally. An SPA that client-renders everything is gambling on the render queue. If the application cannot be changed, dynamic rendering for crawlers is a workable stopgap with real maintenance cost.

What about lazy-loaded content further down the page?

Content loaded on scroll or on interaction is frequently not indexed, because crawlers do not scroll or click. Anything that needs to rank should be in the initial HTML, even if it is visually collapsed — an accordion whose answers are present in the markup indexes fine, one that fetches on open does not.

Want this applied to your systems?

Most of what is described here begins as an assessment: a fixed-scope review producing a prioritised written report you can act on with any supplier.

Prefer email? hello@kornspire.com