A single-page application (SPA) is a web application that loads a single HTML page and updates the content dynamically as the user interacts with the app. Instead of loading entire new pages from the server, SPAs use JavaScript to rewrite the current page in place. This creates a smoother, faster user experience similar to native desktop or mobile applications.
When you click a link in a traditional website, the browser discards the current page and loads a completely new one from the server. In an SPA, the browser stays on the same page while JavaScript fetches new data and updates only the parts of the screen that changed. The URL in the address bar updates via the History API, but no full page reload occurs.
How SPAs work
When a user visits an SPA, the server sends a minimal HTML shell with a JavaScript bundle. The JavaScript then:
- Renders the initial view, often showing a loading spinner or skeleton screen
- Handles navigation via the browser’s History API (changing the URL without reloading)
- Fetches data from APIs as needed, typically as JSON
- Updates the DOM to reflect new content based on the fetched data
- Manages state, caching, and client-side routing rules
Popular SPA frameworks include React, Vue, Angular, Svelte, and Solid. Each provides tools for component-based UI development, state management, and client-side routing.
SPA vs traditional multi-page application
| Multi-page application | Single-page application |
|---|---|
| Each navigation loads a new HTML document | Navigation updates content via JavaScript |
| Simpler initial render, no JS required | Requires JavaScript to display any content |
| SEO-friendly by default | Needs server-side rendering or prerendering for SEO |
| Full page reloads between views | Instant transitions, app-like feel |
| Server handles routing | Client handles routing |
| Simpler caching and CDN setup | May need complex cache invalidation strategies |
| Lower initial JavaScript payload | Large initial bundle to download |
Many modern sites are not pure SPAs or pure MPAs. They use hybrid approaches like Next.js, Nuxt, or SvelteKit that combine server-side rendering with client-side hydration for the best of both worlds.
SEO challenges with SPAs
SPAs present unique challenges for search engines and crawlers:
- Empty initial HTML - The first load may contain only a
<div id="root"></div>with no content. Search engines that do not render JavaScript see nothing. - JavaScript-dependent content - Headings, links, and text only appear after scripts run. If the crawler does not wait for rendering, it misses the content.
- Client-side routing - URL changes do not correspond to actual server requests. The crawler must intercept and follow these virtual routes.
- Delayed rendering - Content may load in stages, making it hard to know when the page is “complete”. A product description might load in 200ms while reviews load in 800ms.
- Meta tag updates - Title and description tags may be set by JavaScript after the initial HTML arrives. Static crawlers see the generic shell tags instead of the page-specific ones.
- Lazy-loaded images - Images that load only when scrolled into view may be missing from a crawler snapshot.
To address these issues, SPA developers often use server-side rendering (SSR), static site generation (SSG), or prerendering to serve meaningful HTML to crawlers. Frameworks like Next.js and Nuxt make SSR relatively straightforward by running the same components on the server.
SPA framework detection
Crawler tools and site profilers look for signatures of SPA frameworks in the HTML:
- React:
data-reactroot,__NEXT_DATA__, or_reactListening - Vue:
data-v-attributes or__VUE__global - Angular:
ng-app,ng-version, orangularin script filenames - Svelte:
svelte-classes or__sveltein the DOM - Generic: High script-to-text ratio, empty body with a single mount point div
These signals help crawlers decide whether to enable JavaScript rendering before attempting to extract content.
How crawler.sh handles SPAs
crawler.sh is designed to crawl SPAs effectively. Its JavaScript rendering engine executes the site’s JavaScript and waits for the DOM to stabilize before extracting content. The site profiler detects SPA frameworks automatically by looking for the markers above and enables JS rendering when needed.
The crawler also:
- Follows client-side routes by intercepting
history.pushStateandhistory.replaceStatecalls - Extracts links from the rendered DOM, not just the initial HTML, so client-side navigation paths are discovered
- Records the final URL after client-side navigation, even if the server initially returned a 200 for the shell page
- Captures titles, descriptions, and headings that are set by JavaScript after the initial load
- Applies appropriate drain time budgets so content that loads asynchronously has time to appear
For SPAs that use hash-based routing (/#/products), the crawler treats the hash as part of the unique URL and processes each route separately.