JavaScript-Rendered Content Not Showing Up in Google Index

Your SPA renders fine for users, but Search Console shows the page indexed with a blank `<body>`. Why Googlebot's rendering quirks lose your content — and how to fix it.

You ship a React/Vue/Svelte SPA. The URL is indexed in Google. But search a unique phrase from the page body — nothing. Run Search Console URL Inspection → “View crawled page” → “Rendered HTML.” The <body> is nearly empty, or contains only a loading spinner. Googlebot got the shell, never the content. This shows up as “Indexed but no impressions,” “Crawled but no rich result,” or articles failing for keywords that literally exist on the page.

Google does render JavaScript, but with a deferred second pass that’s slower, capped on resources, and trips on patterns developers don’t usually test against. The fix is rarely “wait longer.”

Common causes

1. Content fetched after a user event

Your component does useEffect(() => fetch('/api/article')) but actually only triggers on a requestIdleCallback, scroll, or click. Googlebot doesn’t scroll, doesn’t click, doesn’t go idle the way a browser does.

How to spot it: Use Chrome DevTools → Network → Throttle to “Slow 3G,” disable any interaction, wait 5 seconds. If content never paints, Googlebot likely sees the same blank.

2. API blocked by robots.txt or CORS

The HTML loads, then JS calls /api/data. But /api/* is in robots.txt Disallow:. Googlebot fetches the HTML, then can’t fetch the API to render content.

How to spot it: Check robots.txt. If your data endpoints are blocked, Googlebot can’t render the JS that depends on them.

3. Render times out (>5s for first meaningful paint)

Googlebot’s Web Rendering Service has a soft budget. Heavy JS bundles, slow API calls, or render-blocking third-party scripts push past that budget and Googlebot indexes what was painted at timeout — usually nothing.

How to spot it: Lighthouse score. If LCP > 4s or TBT > 600ms, your render is at risk.

4. JS errors break the render

A console error like Cannot read properties of undefined halts hydration. The shell renders, the data layer never paints.

How to spot it: Search Console URL Inspection → “More info” → “Page resources / JavaScript console messages.” Look for runtime errors there. Or open DevTools with ?_escaped_fragment_= style — anything that throws in production breaks indexing.

5. Client-side router never updates the title / meta

User navigates /article/foo via the SPA router. Title and meta tags stay stuck on the home-page values because the SPA didn’t update them. Googlebot indexes the second URL with home-page metadata.

How to spot it: Click into 3 articles in a row, look at the <title> in DevTools Elements after each navigation. If it doesn’t change, your router isn’t updating document.title.

6. Service worker serves stale content to Googlebot

A PWA service worker has cached an old version of the bundle that breaks against the current API. Users get fresh content via cache-bust, Googlebot fetches plain and hits the broken cached version.

How to spot it: Compare HTTP response from a User-Agent set to “Googlebot/2.1” vs. a regular browser. If they differ, the service worker (or a CDN) is splitting traffic.

7. CSR-only with no fallback for crawlers

Pure client-side rendering: HTML is <div id="root"></div> and a script tag. Googlebot’s render pass is the only chance to see content. If anything goes wrong (causes 1-4 above), there’s no fallback.

How to spot it: curl https://yoursite.com/article/foo returns mostly the same shell regardless of URL. No prerendered HTML, no SSR.

Shortest path to fix

Step 1: Confirm what Googlebot sees

Use Search Console → URL Inspection → “Test live URL” → “View tested page” → “More info” → “HTML.” That’s what Googlebot rendered. Not what curl returns, not what your browser shows.

Step 2: Render server-side or prerender

The most reliable fix: stop relying on client-side render for indexable content. Options:

  • SSR: Next.js getServerSideProps, Nuxt asyncData, SvelteKit +page.server.ts.
  • SSG: Astro, Next.js getStaticProps, Hugo. Best for content sites.
  • Prerender: Tools like Prerender.io, Rendertron — serve a static snapshot to crawlers.
// Next.js example: SSR for an article page
export async function getServerSideProps({ params }) {
  const res = await fetch(`https://api.example.com/articles/${params.slug}`);
  const article = await res.json();
  return { props: { article } };
}

Step 3: Make critical content render without user interaction

Move data fetches out of requestIdleCallback, scroll handlers, intersection observers. Critical content (title, body, schema) should be in the initial server response or hydrated in useEffect with no gating.

Step 4: Allow your API endpoints in robots.txt

If client-side render is unavoidable, don’t block API endpoints needed for rendering:

User-agent: Googlebot
Allow: /api/articles/
Allow: /api/categories/
Disallow: /api/admin/

Step 5: Update title and meta on client-side route changes

For SPAs that stay client-side, use React Helmet, Vue Meta, or directly update document.title and meta on route change:

router.afterEach((to) => {
  document.title = to.meta.title || 'Site Name';
  document.querySelector('meta[name="description"]').setAttribute('content', to.meta.description);
});

Step 6: Test rendering at scale

Don’t rely on one URL inspection. Use a JS-rendering crawler — Screaming Frog with JS rendering enabled, Sitebulb, or puppeteer-based scripts — to crawl 100+ pages and diff the rendered HTML against the source HTML. Pages with big diffs are at risk.

Step 7: Monitor Search Console

After fixing, expect 2-4 weeks before “Crawled but not indexed” counts drop and impressions for body-text queries appear.

When this is not on you

Some JS frameworks have known rendering quirks with Googlebot (older Angular Universal versions, legacy Ember). Upgrading is the real fix, not patches.

Easy to misdiagnose as

A content quality issue. The article seems indexed by URL, but Google has no idea what it’s about because the body never reached the index. So it ranks for nothing despite “being indexed.”

Prevention

  • Default to SSR or SSG for any content meant to rank.
  • Never block APIs you need for rendering in robots.txt.
  • Run a monthly rendered-vs-source diff on a sample of URLs.
  • Set a budget for JS bundle size and render time; CI fail above the threshold.
  • For SPAs, always update document.title and meta on route change.

FAQ

  • Doesn’t Google fully render JavaScript now? It does, but the second pass is delayed, resource-capped, and skips pages with rendering errors. “Renders JS” isn’t “renders everything reliably.”
  • Will prerendering be penalized as cloaking? No — as long as the prerendered HTML and the rendered HTML show the same content to users and crawlers.

Tags: #SEO #Troubleshooting #Indexing #Search Console #javascript #spa #rendering