SPA Indexing

Written by Backbone Tutorials Team

Last updated: June 2026 · 10 min read

A single page application is the hardest thing to get indexed well, because almost everything that makes it pleasant to use, one HTML shell, instant in-app navigation, content assembled on the fly, works against a crawler. The good news is that the fixes are concrete and they are the same handful you have met across this cluster, applied together. This guide assembles them into one playbook for getting an SPA fully indexed.

It pulls together rendering for search engines, crawlability, and Googlebot rendering, and is the practical companion to the SEO for single page apps overview.

From one shell to many indexable pages A single shell becomes indexable when each view has a real URL, content in the HTML, its own head, and crawlable links. One shellclient-rendered real URL per view content + head + links Indexable pageseach can rank
An SPA becomes indexable when each view earns a real URL, renders its content and head, and is reachable by a crawlable link.

Why SPAs are hard to index

The defining trait of an SPA is that the server sends one shell and JavaScript builds every view inside it. To a user this feels fast and seamless; to a crawler arriving at a deep URL, it can look like a blank page that may or may not fill in.

The empty-shell problem

Three habits compound the risk: rendering only on the client, so content waits on scripts; routing through in-memory state, so views have no address; and navigating through click handlers, so there is nothing to follow. Each is solvable on its own, and an indexable SPA is simply one where all three are solved at once.

Every view needs its own URL

Indexing happens per URL, so any view you want in search has to be a URL, one that returns that view when visited directly, not only when reached by clicking through the app.

Real routes, not hash fragments

// Client router updates the address with a real path
history.pushState({}, '', '/products/42');

Use the History API to give each view a clean path like /products/42, and make sure the server returns the app for that path on a direct hit so it does not 404. Prefer real paths over hash fragments, which search engines treat less reliably as distinct pages. This is exactly the discipline from the routing basics guide, and it is the foundation everything else rests on.

Render content into the initial HTML

A real URL is necessary but not sufficient: when a crawler fetches it, the response should already contain the view's content, rather than an empty container that depends on a later render.

SSR or prerendering per route

Server-render the dynamic routes and prerender the stable ones so each URL's content and head ship in the first response, the approach laid out in rendering for search engines. Pure client rendering can still be indexed, but it leans on Google's renderer and its timeouts, which is a gamble on the pages you most want to rank. Putting content in the HTML removes that gamble.

Because the page never reloads, the document head will not change between views unless you change it. Every indexable route needs its own, correct head, or they will compete and confuse.

Unique head for each route

Give each route a unique title and description, and a self-referencing canonical pointing at that route's clean URL, ideally rendered on the server so it is right in the first response. Without this, every view inherits the shell's title and a single canonical, and Google sees many near-duplicate pages instead of distinct ones.

Even with URLs, content, and heads in place, Google still has to discover the routes, and it does that by following links. A view nothing links to is an orphan no matter how well it renders.

Crawlable navigation between views

<!-- Real link, real path: crawlable and shareable -->
<a href="/products/42">Product 42</a>

Navigate with real anchor elements that carry an href to the route's path, letting your router enhance the click rather than replace it. A handler on a div moves a user but leaves nothing for a crawler to follow. Real links are simultaneously good navigation, good accessibility, and the mechanism by which your routes get discovered.

Verify the SPA is actually indexed

With the four fixes shipped, confirm they worked rather than assuming. SPAs regress quietly, so verification is part of the job, not an afterthought.

Checking routes in Search Console

Run URL Inspection on a sample of routes to confirm the rendered HTML carries the content, head, and links, and watch the Pages report for routes flagged as soft 404 or "Crawled, currently not indexed", the audit habit applied to an SPA. Pay special attention to how unknown routes behave: a missing item should return a real 404, not a 200 with an empty view. Get these signals clean and a single page application indexes like any other site. The next guide turns to debugging when a render still does not produce what you expect.

Frequently Asked Questions

Can a single page application be indexed by Google?

Yes. A single page application can rank well if each view has its own real URL, its content reaches the rendered HTML, the head is set per route, and navigation uses real links. The difficulty is not the app model itself but the defaults, which hide content behind client rendering and stateful routing.

Do I need server-side rendering for an SPA?

Not strictly, but server-side rendering or prerendering is the reliable path, because it puts each route's content in the first response. Pure client-side rendering can be indexed too, but it depends on Google rendering your scripts well and promptly, which is riskier for pages that must rank.

Should I use hash URLs in an SPA for SEO?

Prefer real path URLs through the History API. Hash-based routes are weaker for indexing because the fragment is not treated as a separate page in the same way. Give every indexable view a clean, distinct path that returns content on a direct visit.

Why are my SPA routes showing as soft 404 or not indexed?

Common causes are unknown routes that render an empty "not found" view while returning a 200, content that depends on a slow or blocked render, and views that lack a real URL or crawlable link. Fix routing and status codes, render content into the HTML, and link views with real anchors.

Read next: back to the JavaScript SEO hub, or revisit rendering for search engines for the strategies behind step 3.

Building the single page app itself?

Indexable views start with real routes and structure. See how an SPA is built in the complete Backbone guide.

Explore the Backbone Guide →