Skip to content

docs: prerender/ssr:false #13005

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 30 additions & 16 deletions docs/how-to/pre-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,45 @@ title: Pre-Rendering

# Pre-Rendering

Pre-rendering allows you to render pages at build time instead of on a runtime server to speed up page loads for static content.
Pre-Rendering allows you to speed up page loads for static content by rendering pages at build time instead of at runtime. Pre-rendering is enabled via the `prerender` config in `react-router.config.ts` and can be used in two ways based on the `ssr` config value:

In some cases, you'll serve these pages _alongside_ a runtime SSR server. If you wish to pre-render pages and deploy them _without_ a runtime SSR server, please see the [Pre-rendering with `ssr:false`](#pre-rendering-without-a-runtime-ssr-server) section below.
- Alongside a runtime SSR server ith `ssr:true` (the default value)
- Deployed to a static file server with `ssr:false`

## Pre-rendering alongside a runtime SSR server
## Pre-rendering with `ssr:true`

### Configuration

Add the `prerender` option to your config, there are three signatures:

```ts filename=react-router.config.ts lines=[7-09,11-12,14-20]
```ts filename=react-router.config.ts lines=[7-8,10-11,13-21]
import type { Config } from "@react-router/dev/config";

export default {
// Can be omitted - defaults to true
ssr: true,

// all static route paths
// (no dynamic segments like "/post/:slug")
// all static paths (no dynamic segments like "/post/:slug")
prerender: true,

// any url
// specific paths
prerender: ["/", "/blog", "/blog/popular-post"],

// async function for dependencies like a CMS
async pre-render({ getStaticPaths }) {
async prerender({ getStaticPaths }) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stupid find/replace!

let posts = await fakeGetPostsFromCMS();
return ["/", "/blog"].concat(
posts.map((post) => post.href)
);
return [
"/",
"/blog",
...posts.map((post) => post.href),
];
},
} satisfies Config;
```

### Data Loading and Pre-rendering

There is no extra application API for pre-rendering. Pre-rendering uses the same route loaders as server rendering:
There is no extra application API for pre-rendering. Routes being pre-rendered use the same route `loader` functions as server rendering:

```tsx
export async function loader({ request, params }) {
Expand Down Expand Up @@ -82,18 +84,18 @@ Prerender: Generated build/client/blog/my-first-post/index.html

During development, pre-rendering doesn't save the rendered results to the public directory, this only happens for `react-router build`.

## Pre-rendering without a runtime SSR server
## Pre-rendering with `ssr:false`

The above examples assume you are deploying a runtime server, but are pre-rendering some static pages in order to serve them faster and avoid hitting the server.

To disable runtime SSR, you can set the `ssr:false` config flag:
To disable runtime SSR and configure pre-rendering to be served from a static file server, you can set the `ssr:false` config flag:

```ts filename=react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
ssr: false, // disable runtime server rendering
prerender: true, // pre-render static routes
prerender: true, // pre-render all static routes
} satisfies Config;
```

Expand Down Expand Up @@ -126,7 +128,19 @@ export default {
} satisfies Config;
```

You can configure your deployment server to serve this file for any path that otherwise would 404. Here's an example of how you can do this with the [`sirv-cli`](https://www.npmjs.com/package/sirv-cli#user-content-single-page-applications) tool:
You can configure your deployment server to serve this file for any path that otherwise would 404. Some hosts do this by default, but others don't. As an example, a host may support a `_redirects` file to do this:

```
# If you did not pre-render the `/` route
/* /index.html 200

# If you pre-rendered the `/` route
/* /__spa-fallback.html 200
```

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

Here's another example of how you can do this with the [`sirv-cli`](https://www.npmjs.com/package/sirv-cli#user-content-single-page-applications) tool:

```sh
# If you did not pre-render the `/` route
Expand Down
75 changes: 48 additions & 27 deletions docs/how-to/spa.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,22 @@ 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](../start/library/installation) guides.
- **as a framework** - This guide will focus here

## 1. Disable Server Rendering
## Overview

When using React Router as a framework, you can enable "SPA Mode" by setting `ssr:false` in your `react-router.config.ts` file. This will disable runtime server rendering and generate an `index.html` at build time that you can serve and hydrate as a SPA.

Typical Single Page apps send a mostly blank `index.html` template with little more than an empty `<div id="root"></div>`. In contrast, `react-router build` (in SPA Mode) pre-renders your root route at build time into an `index.html` file. This means you can:

- Send more than an empty `<div>`
- Use a root `loader` to load data for your application shell
- Use React components to generate the initial page users see (root `HydrateFallback`)
- Re-enable server rendering later without changing anything about your UI

It's important to note that setting `ssr:false` only disables _runtime server rendering_. React Router will still server render your root route at _build time_ to generate the `index.html` file. This is why your project still needs a dependency on `@react-router/node` and your routes need to be SSR-safe. That means you can't call `window` or other browser-only APIs during the initial render, even when server rendering is disabled.

<docs-info>SPA Mode is a special form of "Pre-Rendering" that allows you to serve all paths in your application from the same HTML file. Please refer to the [Pre-Rendering](./pre-rendering) guide if you want to do more extensive pre-rendering.</docs-info>

## 1. Disable Runtime Server Rendering

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

Expand All @@ -23,31 +38,54 @@ export default {

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

## 2. Add a `HydrateFallback` to your root route
<docs-info>It's important to note that setting `ssr:false` only disables _runtime server rendering_. React Router will still server render your root route at _build time_ to generate the `index.html` file. This is why your project still needs a dependency on `@react-router/node` and your routes need to be SSR-safe. That means you can't call `window` or other browser-only APIs during the initial render, even when server rendering is disabled.</docs-info>

## 2. Add a `HydrateFallback` and optional `loader` to your root route

SPA Mode will generate an `index.html` file at build-time that you can serve as the entry point for your SPA. This will only render the root route so that it is capable of hydrating at runtime for any path in your application.

To provide a better/faster loading UI, you can add a `HydrateFallback` component to your root route to render your loading UI into the `index.html` at build time. This way, it will be shown to users immediately while the SPA is loading/hydrating.
To provide a better loading UI than an empty `<div>`, you can add a `HydrateFallback` component to your root route to render your loading UI into the `index.html` at build time. This way, it will be shown to users immediately while the SPA is loading/hydrating.

```tsx filename=root.tsx
import { Route } from "./+types/root";
import AwesomeSpinner from "./components/spinner";
```tsx filename=root.tsx lines=[7-9]
import LoadingScreen from "./components/loading-screen";

export function Layout() {
return <html>{/*...*/}</html>;
}

// Server-rendered at build time into `index.html` (inside `<Layout>`)
export function HydrateFallback() {
return <AwesomeSpinner />;
return <LoadingScreen />;
}

export default function App() {
return <Outlet />;
}
```

Because the root route is server-rendered at build time, you can also use a `loader` in your root route if you choose, and access the data via the optional `HydrateFallback` `loaderData` prop. You cannot in include a loader in any other routes in your app when using SPA Mode.
Because the root route is server-rendered at build time, you can also use a `loader` in your root route if you choose. This `loader` will be called at build time ans the data will be available via the optional `HydrateFallback` `loaderData` prop.

```tsx filename=root.tsx lines=[5,10,14]
import { Route } from "./+types/root";

export async function loader() {
return {
version: await getVersion(),
};
}

export function HydrateFallback({
loaderData,
}: Route.ComponentProps) {
return (
<div>
<h1>Loading version {loaderData.version}...</h1>
<AwesomeSpinner />
</div>
);
}
```

You cannot include a `loader` in any other routes in your app when using SPA Mode unless you are [pre-rendering those pages](./pre-rendering).

## 3. Use client loaders and client actions

Expand All @@ -71,11 +109,7 @@ export async function clientAction({
}
```

## 4. 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](./pre-rendering) to set it up.

## 5. Direct all URLs to index.html
## 4. Direct all URLs to index.html

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

Expand All @@ -86,16 +120,3 @@ Common to deploying any SPA, you'll need to configure your host to direct all UR
```

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 route at build time. This means you can:

- Send more than an empty div
- Use a root `loader` to load data for your application shell
- Use React components to generate the initial page users see (root `HydrateFallback`)
- Re-enable server rendering later without changing anything about your UI

Therefore, setting `ssr:false` only disables _runtime server rendering_. React Router will still server render your index route at _build time_ to generate the `index.html` file. This is why your project still needs a dependency on `@react-router/node` and your routes need to be SSR-safe. That means you can't call `window` or other browser-only APIs during the initial render, even when server rendering is disabled.