Tracking Index Coverage for a SPA in Search Console
Search Console’s Page Indexing report is the closest thing you get to Google’s own opinion of whether your pages deserve to be in the index — and for a single-page app it doubles as a rendering health monitor. This guide is part of monitoring JavaScript SEO in production, and it focuses on the one status that quietly drains organic traffic from client-rendered apps: Crawled — currently not indexed.
Problem Statement
For a server-rendered site, Crawled — currently not indexed usually means “thin or duplicate content.” For a client-rendered app it means something more mechanical and more fixable: Googlebot rendered the page, and the rendered DOM did not contain enough to index. The URL was fetched, the second-wave render ran, and it produced an empty root container, a spinner, or a partial DOM because a data fetch failed or hydration crashed. Google saw no meaningful content, so it declined to index — even though the page is perfect in your browser.
The report does not spell this out. It simply moves URLs between statuses. Your job is to read those movements as rendering signals: a climbing Crawled — currently not indexed count on a specific template is a render regression until proven otherwise.
Step-by-step fix
-
Open Page Indexing and sort by the “not indexed” reasons. In Search Console, go to the Page Indexing report and read the reasons list. Note the count and trend for Crawled — currently not indexed and Discovered — currently not indexed. A steady baseline is normal; a step change that lines up with a deploy is your signal.
-
Segment by template. Click into the Crawled — currently not indexed reason and scan the example URLs. If they cluster on one route pattern — every
/product/…or every/article/…— you are looking at a template-level render failure, not scattered thin pages. -
Inspect a representative URL and read the rendered HTML. Run URL Inspection on one clustered URL, open “View crawled page,” and read the rendered HTML tab. An empty root container or a DOM missing the main content confirms a render failure. A complete DOM points instead at a canonical or duplicate issue — a different problem with different statuses.
-
Reproduce the render locally. Load the same URL in a headless browser with Googlebot’s user agent and confirm you see the same emptiness. This rules out a Search Console reporting artifact and gives you a fast local reproduction to iterate against.
-
Fix the render, then request validation. Once the rendered DOM contains the content again — by fixing the data fetch, hydration, or bundle split — use “Validate fix” on the reason in Page Indexing. Google re-crawls the affected set on its own schedule and moves them back to Indexed if the render now succeeds.
When you segment the Crawled — currently not indexed examples by route pattern, one template usually towers over the rest — the signature of a template-level render failure rather than scattered thin pages.
Validation
Confirm the fix from both the fast local signal and the slow authoritative one.
// verify-rendered-content.mjs
// Reproduce what Search Console's URL Inspection would render, before you wait for it.
import { chromium } from 'playwright';
const GOOGLEBOT_UA =
'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Mobile Safari/537.36 ' +
'(compatible; Googlebot/2.1; +http://www.google.com/bot.html)';
const url = process.argv[2];
const browser = await chromium.launch();
const page = await browser.newContext({ userAgent: GOOGLEBOT_UA }).then((c) => c.newPage());
await page.goto(url, { waitUntil: 'networkidle' });
const h1 = (await page.locator('h1').first().textContent().catch(() => '')) || '';
const mainText = await page.locator('main').innerText().catch(() => '');
console.assert(h1.trim().length > 0, 'FAIL: rendered page has no h1');
console.assert(mainText.trim().length > 200, 'FAIL: rendered main content is thin');
console.log(`h1: ${JSON.stringify(h1.trim())}`);
console.log(`main text length: ${mainText.trim().length}`);
await browser.close();
If both assertions pass, the render is healthy and Search Console will catch up within one to three weeks. Use the live test in URL Inspection to confirm immediately, then watch the Crawled — currently not indexed count trend back down as validation completes.
Common Pitfalls
- Reading the totals, not the trend. A flat Crawled — currently not indexed count of a few hundred URLs is often just faceted or filtered routes Google chose not to index. The alarm is a step change, especially one aligned with a deploy.
- Confusing a render failure with a canonical choice. Alternate page with proper canonical tag and Duplicate without user-selected canonical are canonical issues, not rendering issues. Check the rendered DOM before you assume the render is at fault.
- Trusting the live test over the indexed state. URL Inspection’s live test renders on demand and can succeed while the last indexed render failed. Compare “View crawled page” (the stored render) with the live test to see whether a fix has actually propagated.
- Validating before the fix is deployed. Requesting validation against an unfixed production URL just restarts the clock. Confirm the render locally first, ship, then validate.
The distinction to internalise: a flat baseline is background noise Google will always produce, while a sharp step that lines up with a deploy is the alarm worth chasing.
Related
- Detecting rendering regressions with synthetic crawls — catch the failure days before this report does.
- Why Google indexes blank pages for React apps — the empty-render failure this status reveals.
- Understanding Googlebot’s Rendering Pipeline — why a rendered page can still be empty.
Frequently Asked Questions
What does Crawled — currently not indexed mean for a single-page app?
It means Googlebot fetched and rendered the URL but decided the result was not worth indexing. For a client-rendered app the most common cause is a render that produced too little content — an empty root container, a failed data fetch, or a hydration crash — so the rendered DOM held no meaningful text to index.
How do I tell a render failure apart from a duplicate-content filter in Search Console?
Inspect the URL and view the rendered HTML. If the rendered DOM is empty or missing the main content, it is a render failure. If the rendered DOM is complete but Google picked a different canonical, it is a duplicate or canonical issue, which appears under separate statuses such as Alternate page with proper canonical tag.
How long does Search Console take to reflect a rendering fix?
After you deploy a fix and request validation, Google must re-crawl and re-render each affected URL on its own schedule, so the Page Indexing report typically updates over one to three weeks. Use URL Inspection’s live test to confirm the fix immediately, then treat the report as delayed confirmation.
← Back to Monitoring JavaScript SEO in Production