Single Page App (SPA)
On this page

Single Page App (SPA)

There are two ways to ship a single page app with React Router

  • as a library - Instead of using React Router's framework features, you can use it as a library in your own SPA architecture. Refer to React Router as a Library guides.
  • as a framework - This guide will focus here

1. Disable Server Rendering

Server rendering is enabled by default. Set the ssr flag to false in react-router.config.ts to disable it.

import { type Config } from "@react-router/dev/config";

export default {
  ssr: false,
} satisfies Config;

With this set to false, the server build will no longer be generated.

2. Use client loaders and client actions

With server rendering disabled, you can still use clientLoader and clientAction to manage route data and mutations.

import { Route } from "./+types/some-route";

export async function clientLoader({
  params,
}: Route.ClientLoaderArgs) {
  let data = await fetch(`/some/api/stuff/${params.id}`);
  return data;
}

export async function clientAction({
  request,
}: Route.ClientActionArgs) {
  let formData = await request.formData();
  return await processPayment(formData);
}

3. Pre-rendering

Pre-rendering can be configured for paths with static data known at build time for faster initial page loads. Refer to Pre-rendering to set it up.

4. Direct all URLs to index.html

After running react-router build, deploy the build/client directory to whatever static host you prefer.

Common to deploying any SPA, you'll need to configure your host to direct all URLs to the index.html of the client build. Some hosts do this by default, but others don't. As an example, a host may support a _redirects file to do this:

/*    /index.html   200

If you're getting 404s at valid routes for your app, it's likely you need to configure your host.

Important Note

Typical Single Pages apps send a mostly blank index.html template with little more than an empty <div id="root"></div>.

In contrast react-router build (with server rendering disabled) pre-renders your root and index routes. This means you can:

  • Send more than an empty div
  • Use React components to generate the initial page users see
  • Re-enable server rendering later without changing anything about your UI

This is also why your project still needs a dependency on @react-router/node.

Docs and examples CC 4.0