ScreenshotNeo

BlogGuides

What Are Single-Page Applications? Examples and Frameworks

Learn how single-page applications work, how they differ from MPAs, and when to choose Angular, React, Vue, SSR, or static rendering.

By the ScreenshotNeo team1 October 20268 min read

Short answer: A single-page application (SPA) loads one initial HTML document, then uses browser-side JavaScript to fetch data, change the DOM, and switch views without requesting a complete new document for every route. MDN defines an SPA as a web app that loads a single web document and updates its body through JavaScript APIs such as Fetch. MDN explains the definition here.

How a single-page application works

  1. Initial request: The browser requests an entry document, commonly index.html.
  2. Application bootstrap: JavaScript bundles load, create the root component, and initialize shared state.
  3. Data loading: The app calls APIs or other services with fetch(), GraphQL, WebSockets, or a client library.
  4. Rendering: Components turn state into DOM nodes.
  5. Client-side routing: A router maps the URL to a view and handles links, browser history, and back/forward navigation.
  6. In-app navigation: Later route changes update the existing document instead of downloading another full HTML document.

Angular’s routing guide describes the key distinction this way: after the first index.html request, a client-side router controls which content is displayed from the URL. Vue’s routing guide describes the same pattern: JavaScript intercepts navigation, fetches data, and updates the current page without a full reload.

<!-- index.html -->
<div id='app'></div>
<script type='module' src='/src/main.js'></script>

// src/main.js
const app = document.querySelector('#app');

const routes = {
  '/': () => '<h1>Home</h1><p>Welcome.</p>',
  '/reports': () => '<h1>Reports</h1><p>Loading report data...</p>'
};

async function render(path = window.location.pathname) {
  const view = routes[path] || (() => '<h1>Not found</h1>');
  app.innerHTML = view();
  document.title = path === '/reports' ? 'Reports' : 'Home';
}

document.addEventListener('click', event => {
  const link = event.target.closest('a[data-route]');
  if (!link) return;
  event.preventDefault();
  history.pushState({}, '', link.href);
  render();
});

window.addEventListener('popstate', () => render());
render();

A production router also needs route-level data loading, error boundaries, cancellation for stale requests, scroll restoration, focus management, analytics, and a server fallback that serves the application shell for deep links.

SPA versus MPA

Concern SPA MPA
Navigation unit Usually one document with changing views A new document for each page
Rendering owner Mostly browser JavaScript Usually server-rendered or pre-rendered HTML
State between views Can persist in one client session Must be restored or sent with each request
Initial load Can require large JavaScript downloads and execution Can display server HTML sooner
Deep links Require history fallback configuration Usually map directly to server routes
Best fit Dashboards, editors, workspaces, authenticated flows Content-heavy sites and independent pages

These are patterns rather than mutually exclusive categories. A public marketing site can use server-rendered or static pages while its account area behaves as an SPA. Google’s web.dev overview describes SPA and MPA as two primary patterns and notes that applications can mix them.

Examples of SPA architecture

  • Email-style client: Switching folders, messages, and search results changes the view while the shell and selected account remain loaded.
  • Analytics or admin dashboard: Filters and panels request new API data and update only the affected components.
  • Project-management workspace: Boards, tasks, comments, dialogs, and optimistic updates share client state.
  • Checkout or account flow: Several steps coordinate validation, payment state, and navigation inside one stateful client.

These are architectural examples, not claims that a particular vendor uses a pure SPA.

Angular, React, and Vue compared

Option Scope Routing Rendering choices Good starting point
Angular Full framework with conventions for application structure Angular Router is the official router CSR, SSR, static generation, and hydration through the Angular ecosystem Teams wanting an integrated framework and strong defaults
React UI library; routing and application architecture come from the surrounding ecosystem React Router is a widely used routing library CSR, SPA, SSR, and SSG through React frameworks Teams that want a flexible component model and broad ecosystem
Vue Progressive framework usable for client applications or progressively enhanced pages Vue Router is the official router CSR, SSR, static generation, and hydration through Vue tooling Teams that prefer approachable templates and incremental adoption

Framework choice should follow the application’s rendering needs, team experience, accessibility discipline, deployment model, and maintenance expectations. MDN’s framework overview also discusses Svelte and Ember and points to ecosystem solutions such as Next.js, Nuxt, FastBoot, and Angular Universal. Tooling changes over time, so verify current framework recommendations before starting a new project.

CSR, SSR, SSG, and hydration

Client-side rendering (CSR)

The server sends a shell and JavaScript builds much of the interface in the browser. CSR works well for authenticated tools, but the browser must download, parse, and execute code before it can build the interface. web.dev explains why JavaScript rendering affects performance.

Server-side rendering (SSR)

The server sends HTML for the requested route. JavaScript then hydrates that HTML so it becomes interactive. SSR can improve first content visibility and public-page metadata, but it adds server work, caching decisions, and hydration failure modes.

Static site generation (SSG)

HTML is built ahead of time and served from storage or a CDN. SSG is effective for stable content and can be combined with client-side data loading for interactive portions.

Hybrid rendering

Many applications use SSG or SSR for public pages and SPA behavior for dashboards, editors, checkout, or other stateful flows. React’s official documentation lists CSR, SPA, and SSG as supported approaches; major frameworks also provide SSR and hydration.

Accessibility requirements for client-side navigation

  • Update document.title for every route.
  • Move focus to the new page heading or main landmark after navigation.
  • Announce route changes to screen readers when the change is not obvious.
  • Use real links so keyboard users, context menus, and browser history work.
  • Restore or intentionally reset scroll position.
  • Expose loading, success, and error states with accessible status messaging.
  • Keep focus inside dialogs and return it to the trigger when dialogs close.

Browser navigation does not automatically change focus or announce a new title when a client router swaps views. MDN calls out these responsibilities in its SPA guidance.

Public SPA routes need deliberate metadata and rendering. Provide a real URL for each view, configure the server to return the shell for deep links, generate route-specific titles and descriptions, and use SSR or pre-rendering when crawlers and social previews must receive meaningful HTML without executing the entire app. Authenticated dashboards usually have little indexing value and can remain client-rendered.

Performance checklist

  • Measure the initial JavaScript transfer, parse, compile, and execution time.
  • Split bundles by route and lazy-load infrequently used features.
  • Render useful shell content before noncritical data arrives.
  • Cache immutable assets with hashed filenames.
  • Cancel requests when users leave a route and avoid applying stale responses.
  • Use pagination or virtualization for large lists.
  • Track route transition latency and Interaction to Next Paint in real user monitoring.
  • Do not assume a universal benchmark; the authoritative sources in this guide provide qualitative guidance rather than one current SPA score.

Reliability, security, and data handling

  • Show explicit loading, empty, permission, offline, timeout, and server-error states.
  • Retry only idempotent requests, with a cap and backoff.
  • Validate authorization on the server; hiding a button in the client is not access control.
  • Keep secrets and privileged API keys off the client.
  • Protect state-changing requests against CSRF where cookie authentication is used.
  • Sanitize untrusted HTML and use a suitable content security policy.
  • Design deploys so an old tab can recover when its JavaScript chunk no longer exists.

Capture an SPA after it finishes rendering

A plain HTTP client receives the shell, not the final view. For screenshots, use a real browser, wait for the application and data to settle, then capture the viewport or full page.

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 });
await page.goto('https://example.com/app/reports', { waitUntil: 'networkidle' });
await page.waitForSelector('[data-report-ready]');
await page.screenshot({ path: 'spa-report.png', fullPage: true });
await browser.close();

Use a stable readiness selector when possible. A fixed delay alone is less reliable because API latency, animations, and third-party resources vary. For a specific component, pass its locator to locator.screenshot(). For visual regression, freeze dates and random data, use a consistent viewport and device scale, and wait for fonts and images.

Or skip the browser setup

ScreenshotNeo captures a rendered website with one request, including SPA routes after the page loads. See the API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. ScreenshotNeo also provides an MCP server for AI agents, with tools for screenshots, page information, and PDFs. One thousand screenshots per month are free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Troubleshooting SPAs

Symptom Likely cause Fix
Refreshing a deep link returns 404 Server lacks a history fallback Route unknown application paths to the SPA shell while preserving real asset and API errors.
Back button does nothing Navigation changes state without History API integration Use pushState for app links and listen for popstate.
Screenshot shows a blank shell Capture happened before JavaScript or API data completed Wait for a readiness selector or network idle in a browser capture.
Old users see chunk-load errors A deployment removed assets referenced by an older shell Keep old assets briefly, or reload and recover when a dynamic import fails.
SEO page has no title or description Metadata changes only after client execution Set route metadata and use SSR or pre-rendering for public pages.
Screen reader user is lost after navigation Focus and announcements were not handled Move focus to the new heading and announce the route transition.
Stale API data overwrites newer data Requests resolve out of order Cancel obsolete requests with AbortController or ignore responses with an outdated request token.

FAQ

Is an SPA always faster than an MPA?

No. It can make later transitions feel quick, but a large JavaScript bundle can make the first view slower. Measure the actual routes and devices your users have.

Is React a framework?

React is a UI library. Routing, data loading, rendering mode, and deployment conventions usually come from a React framework and its ecosystem.

Can an SPA use server-side rendering?

Yes. An application can render the first view on the server, hydrate it in the browser, and continue with client-side route transitions.

Do SPAs require APIs?

Most data-driven SPAs use APIs, but an SPA can also render bundled or locally stored data. APIs become important when data changes independently of the deployed frontend.

When should I avoid a pure SPA?

Prefer MPA, SSG, SSR, or a hybrid when pages are mostly content, need dependable indexing before JavaScript runs, or do not benefit from persistent client state.