Technical SEO Audit Checklist for Single Page Applications: 28 Checks for React, Vue, and Angular Sites

One of the most popular services we offer is ongoing website maintenance because most clients we work with become return clients.

Technical SEO Audit Checklist for Single Page Applications: 28 Checks for React, Vue, and Angular Sites

Most SPA SEO articles hand you a list of rules and leave you guessing whether your own app actually breaks them. This single page application SEO checklist does the opposite: every item comes with a test you can run in the next ten minutes, using Google Search Console URL Inspection, a raw versus rendered HTML comparison, and Chrome DevTools.

If you build with React, Vue, Angular, Next.js, Nuxt or SvelteKit, work through the 28 checks below in order. The first six catch the issues that silently remove entire sections of your site from the index.

Before You Start: How Googlebot Actually Renders Your SPA

You cannot audit what you do not understand. Googlebot processes a JavaScript application in three distinct stages:

  1. Crawl: Googlebot requests the URL and receives the initial server response. For a pure client-side app, this is often an empty <div id='root'></div> plus script tags.
  2. Render queue: the URL is queued for the Web Rendering Service, which runs a headless, evergreen Chromium instance. Rendering usually happens within minutes, but it can stretch to days on large or low-authority sites.
  3. Index: only the rendered DOM is used for indexing. Links discovered during rendering go back into the crawl queue, which means every JavaScript-only navigation layer adds a full round trip to your discovery cycle.

Two consequences matter for your audit:

  • Googlebot does not click, scroll, hover, accept cookie banners, or log in. Anything gated behind an interaction does not exist.
  • Googlebot renders with a stateless browser. No localStorage, no service worker cache, no session from a previous URL.

There is also a newer pressure point. Most AI and answer-engine crawlers (the ones feeding LLM-based search surfaces) fetch raw HTML and do not execute JavaScript at all. A client-side-only SPA can be indexed by Google and still be completely invisible to that traffic layer. That alone has moved server-side rendering or prerendering from nice-to-have to default.

web developer code screen

The Three Tests You Will Repeat Throughout This Checklist

Test A: Raw HTML versus rendered HTML comparison

This is the single most valuable diagnostic in SPA SEO. Run it once per template type (home, category, product or article, filtered list, paginated view).

Get the raw HTML (what the server sends, no JavaScript executed):

curl -A 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' -L https://yoursite.com/some-route > raw.html

Get the rendered HTML: open the same URL in Chrome, open DevTools, right-click the <html> node in the Elements panel and choose Copy > Copy outerHTML. Paste into rendered.html.

Then diff them. Count words, titles, canonical tags and links in each. The gap between the two files is your rendering debt.

Test B: Search Console URL Inspection, live test

Open the URL Inspection tool, enter the URL, click Test Live URL, then open View Tested Page. Look at all three tabs: HTML, Screenshot, and More Info (which lists resources Google could not load, plus JavaScript console messages). The screenshot is the fastest way to spot a blank render or a cookie wall.

Also compare the live test with the indexed version (the default view). A difference between them means your fix has not been recrawled yet, or the indexed version is stale. There’s a good explainer over at vierviertel.com.

Test C: JavaScript disabled

In Chrome DevTools, press Ctrl+Shift+P, type Disable JavaScript, reload. This shows what non-rendering crawlers, AI bots and some social scrapers see. It is not how Googlebot behaves, but it is a brutal, honest baseline.

Section 1: Crawlability and Routing (Checks 1 to 6)

# Check How to test Severity
1 Every view has a unique, clean URL (History API, not hash fragments) Navigate the app and watch the address bar. Any #/ in the path is a fail. Critical
2 Navigation uses real anchor tags with href attributes Inspect the nav in rendered.html. Search for onclick handlers on divs or buttons that act as links. Critical
3 robots.txt does not block JS, CSS, fonts or JSON API endpoints URL Inspection > More Info > Page resources. Anything marked as blocked by robots.txt breaks rendering. Critical
4 No stray noindex in the initial HTML that JavaScript later removes grep raw.html for noindex. Googlebot may drop the page before your script runs. Critical
5 Invalid URLs return a genuine 404 or 410, not a 200 with an error component curl -I a fake URL. If it returns 200, expect soft 404 reports in Search Console. High
6 URL casing, trailing slashes and query parameter order are normalised Request the same route four ways and confirm one canonical target with 301s. Medium

Fixing check 5 on a client-rendered app

You cannot set a status code from the browser. The realistic options are:

  • Handle the route server-side (Next.js notFound: true, Nuxt createError({ statusCode: 404 }), Angular Universal with an Express status hook).
  • If SSR is impossible, inject <meta name='robots' content='noindex'> into the error view and redirect to a real 404 URL. This is a workaround, not a fix.
web developer code screen

Section 2: Rendering Parity (Checks 7 to 12)

This is where you use Test A on every template. The question is simple: does the rendered DOM contain the content you want ranked, without any user interaction? Originally covered on https://donhesh.com.au.

  1. Primary body content appears in the rendered HTML. Pick a distinctive sentence from the page and search for it in rendered.html. If it is missing, the content is arriving after render or behind an interaction.
  2. The H1 and heading structure exist in the rendered DOM. Many component libraries ship headings as styled spans. Confirm real <h1> and <h2> elements.
  3. No content requires a click, tab, accordion or hover to exist in the DOM. Content hidden by CSS is fine. Content not yet mounted is not indexed.
  4. Infinite scroll has a paginated fallback. Provide crawlable ?page=2 URLs linked with real anchors, or a View all page. Googlebot does not scroll.
  5. No blocking cookie or consent wall over the content. Check the URL Inspection screenshot. If you see the banner and grey overlay instead of the page, your consent layer is blocking render for bots.
  6. Zero uncaught JavaScript errors during render. URL Inspection > More Info > JavaScript console messages. A single thrown error can abort the whole component tree and leave an empty page.

A practical parity scorecard

For each template, record these numbers. Anything where raw is dramatically smaller than rendered is a rendering dependency you are betting your rankings on.

Metric Raw HTML Rendered HTML Acceptable gap
Word count of main content ? ? Under 10 percent
Title tag present and correct ? ? None
Canonical tag present ? ? None
Internal anchor tags with href ? ? Under 20 percent
JSON-LD blocks ? ? None

Section 3: Metadata, Canonicals and Head Management (Checks 13 to 18)

Head management is the number one source of SPA indexing bugs, because it works perfectly in the browser and fails silently for crawlers on the first load.

  1. Every route has a unique title in the rendered HTML. Crawl the site with Screaming Frog in JavaScript rendering mode and sort by title. Duplicate titles across dozens of routes usually means the head library never received route-specific data.
  2. Every indexable route has a unique meta description. Same crawl, same sort. Missing is better than duplicated, but unique is better than both.
  3. Exactly one canonical tag per page, absolute, self-referencing by default. Search rendered.html for rel='canonical' and count occurrences. Two canonicals (one from the server shell, one injected by the client) cause Google to ignore both.
  4. Canonicals are not injected only client-side on important pages. If the canonical exists only after JavaScript runs, it depends on the render queue. Serve it from the server for anything commercial.
  5. hreflang annotations are in the rendered head or HTTP headers, and are reciprocal. Test with the live URL Inspection HTML tab, not the browser after client navigation.
  6. Meta robots directives are route-specific and do not leak. A common bug: a noindex set on a filtered view persists after client-side navigation to an indexable route, because the head state was never reset.

Framework-specific head handling

Stack Recommended approach Common failure
React (Vite, CRA legacy) React 19 native document metadata, or react-helmet-async if you are still on 18 Unmaintained react-helmet causing duplicate or missing tags during SSR streaming
Next.js App Router Exported metadata object or generateMetadata per route segment Mixing client-side head libraries with the Metadata API, producing two canonicals
Vue 3 SPA @unhead/vue with server-side rendering Head updates fired in onMounted only, so first paint has no metadata
Nuxt 3 and 4 useSeoMeta and useHead, with definePageMeta for route rules Routes silently falling back to ssr: false in nuxt.config route rules
Angular Title and Meta services plus Angular SSR (the successor to Angular Universal) Metadata set inside a resolver that never runs during the server pass

Section 4: Hydration and Content Stability (Checks 19 to 22)

SSR that hydrates badly can be worse than no SSR, because Google indexes the server output and users see something different.

  1. No hydration mismatch warnings in the console. Load a server-rendered page with a clean profile and watch for text content did not match or hydration failed. Mismatches cause React and Vue to discard the server DOM and re-render from scratch, wiping your pre-rendered content for a moment.
  2. Server-rendered content is not replaced by different content after hydration. Throttle the network to Slow 4G, reload, and compare the first paint with the settled page. If the server shows placeholder skeletons and the client shows the real article, Google may index the skeleton.
  3. Head tags survive client-side navigation and browser back. Navigate A to B to A with the router, and check document.title and the canonical each time. Broken head resets are invisible to crawlers on first load but they break your own QA and any prerender cache.
  4. No layout shift caused by late-arriving hydrated components. Reserve space with explicit dimensions or CSS aspect-ratio for anything mounted client-side. This protects CLS and prevents content jumping above the fold.
web developer code screen

Section 5: Sitemaps, Internal Links and Structured Data (Checks 23 to 26)

  1. The XML sitemap is generated from the same source of truth as your router. A hand-written sitemap always drifts. Generate it at build time from your route manifest or CMS query. In Next.js use sitemap.ts, in Nuxt use the sitemap module, in Angular generate during prerender.
  2. The sitemap contains only canonical, indexable, 200-status URLs. Export the sitemap, run it through a crawler in list mode, and check that zero URLs return 3xx, 4xx, or carry a noindex. Keep lastmod accurate, because an always-today lastmod is ignored.
  3. Every indexable page is reachable through rendered anchor tags within three clicks of the home page. Run a JavaScript-rendered crawl and inspect crawl depth. Orphan pages that only exist in the sitemap get crawled slowly and ranked poorly.
  4. Structured data is present in the rendered DOM and valid. Test with the Rich Results Test on the live URL, not with a pasted code snippet. If the JSON-LD is injected client-side, confirm it survives in the URL Inspection HTML tab.

Section 6: Performance and Monitoring (Checks 27 and 28)

  1. Core Web Vitals pass on real user data, not just lab data. For SPAs the two chronic offenders are INP, inflated by heavy hydration and large event handlers, and LCP, delayed when the hero element depends on a client-side fetch. Check the Core Web Vitals report in Search Console, segmented by mobile, then confirm your LCP element exists in raw.html.
  2. Rendering regressions are caught in CI, not in Search Console three weeks later. Add a build step that fetches key routes with a headless browser and asserts: title present, canonical present and correct, H1 present, word count above a threshold. Fail the build on violation. This is the single highest-leverage item on this checklist for teams shipping weekly.
web developer code screen

Choosing a Rendering Strategy After the Audit

Once you know where the gaps are, the fix usually falls into one of four patterns.

Strategy Best for Trade-off
Static generation (SSG) Blogs, docs, marketing pages, stable catalogues Build times grow with page count
Server-side rendering (SSR) Ecommerce, listings, personalised or frequently changing content Server cost and TTFB discipline required
Incremental regeneration Large catalogues with predictable update cycles Cache invalidation complexity
Prerendering service for bots Legacy apps that cannot be migrated soon Ongoing cost, cache staleness, must serve identical content to users and bots

A quick decision rule: if a URL is meant to earn organic traffic, its main content, title and canonical should be in the HTTP response. Everything else in the app can stay client-side without penalty.

A 90 Minute Audit Workflow

  1. 15 minutes: run Test A on five representative templates and fill in the parity scorecard.
  2. 15 minutes: run live URL Inspection on the same five URLs, review screenshots, blocked resources and console errors.
  3. 20 minutes: full crawl with JavaScript rendering enabled, then export duplicate titles, missing canonicals, non-200 URLs and crawl depth.
  4. 15 minutes: check Search Console Pages report for Crawled currently not indexed, Discovered currently not indexed and Soft 404 buckets. These three buckets map almost one to one onto rendering problems.
  5. 15 minutes: validate the sitemap in list mode.
  6. 10 minutes: write the ticket list, ordered by the severity column above.

Frequently Asked Questions

Can Google index a single page application without server-side rendering?

Yes, Googlebot renders JavaScript and can index client-side content. The risks are delay, budget and fragility: rendering is queued rather than immediate, a single script error can produce an empty page, and non-Google crawlers including most AI answer engines will see nothing. SSR or static generation removes all three risks at once. Related reading: WordPress SEO checklist (2025): A step-by-step guide.

Is React Helmet still the right way to manage meta tags?

If you are on React 19 or later, use the built-in document metadata support and skip the extra dependency. On React 18, use react-helmet-async rather than the original react-helmet, which is unmaintained and behaves unpredictably under streaming SSR. In Next.js App Router, use the Metadata API exclusively and avoid mixing libraries, or you will end up with duplicate canonical tags.

How do I know if a page has a hydration problem or a rendering problem?

Compare three states. If raw.html is empty and rendered.html is complete, it is a rendering dependency. If raw.html is complete but the page visibly rebuilds or changes content after load, it is hydration. If both look correct but Search Console shows Crawled currently not indexed, the issue is more likely content quality, duplication or canonical signals than JavaScript.

Should hash-based routing ever be used?

Not for anything you want indexed. Google ignores the fragment portion of a URL when determining the page identity, so every #/route collapses into the same URL. Use the History API. If you inherited a hash-routed app, migrate the routes and 301 the old paths at the server level, since fragments never reach the server.

How often should this checklist be run?

Run the full 28 checks quarterly and after any framework upgrade, router change, consent platform change or head library swap. Run the automated subset (check 28) on every build. Most SPA indexing incidents are introduced by a deploy, not by a Google update.

Do AI crawlers change what I need to do?

They change the priority, not the checklist. Since most of them do not execute JavaScript, the items about serving content, titles, canonicals and structured data in the raw HTML response have moved from best practice to prerequisite. If your content is only visible after render, you are opting out of an entire discovery channel.

Next Step

Run Test A on your five most valuable templates today and fill in the parity scorecard. If the raw column is close to empty, you already know which section of this single page application SEO checklist to start with. If you want a second pair of eyes on the results, the Pixelseed team audits React, Vue and Angular builds and delivers the findings as a prioritised developer ticket list rather than a PDF.

Subscription Form

Contact Details

Quick Links

Copyright © 2022 Pixel Seed. All Rights Reserved.