Overview

A single-page app can render perfectly in a browser and still hand crawlers an empty shell, unreachable routes, or links they cannot follow. This page lists the SPA patterns that cause that and the two checks that prove the fix. It assumes the rendering choice is already made; for choosing between static, SSR, and client-side rendering, and for the render budget, see javascript-seo.

Avoid the four SPA patterns that break crawlers

Fix these four patterns first; they are the common ways a single-page app hides content or links from crawlers.

  • Client-only routing without server fallback. Hash routes (/#/article/1) are not crawlable. Use the History API and configure the server to return the correct HTML for every path.
  • Content behind a click or scroll. Tabs, accordions, and “load more” buttons that fetch content on interaction hide it from the initial render. Ship the content in the HTML; hide with CSS if it should not be visible.
  • Lazy-loaded content above the fold. loading="lazy" on the LCP image delays LCP. Use fetchpriority="high" on the LCP image and lazy-load only below-the-fold content.
  • JavaScript-only navigation. <a onclick="..."> without an href is not a link to a crawler. Use real <a href> elements; intercept the click in JS if needed.

The pattern check: load the page with JS disabled. If the content and the nav both work, the SEO surface works.

Debug with URL Inspection and view-source

Use two checks to confirm the crawler sees what you see.

  • Google Search Console URL Inspection: “Test live URL” returns the rendered HTML and a screenshot of what Googlebot sees. The gap between the rendered HTML and your browser’s DOM is the bug.
  • View-source vs the rendered DOM: view-source: in Chrome shows the initial HTML; DevTools shows the post-JS DOM. If a paragraph is in the rendered DOM but not in view-source, it is client-rendered.

Run both on every page template before launch. The view-source check catches client-only content; the URL Inspection check catches Googlebot-specific rendering issues (blocked resources, render timeouts).

# Quick check from the terminal
curl -A "Googlebot" https://example.com/page | grep -c "<main>"
# Zero means the main content is not in the initial HTML.