| 1 | ---
|
| 2 | title: Pre-Rendering
|
| 3 | ---
|
| 4 |
|
| 5 | # Pre-Rendering
|
| 6 |
|
| 7 | [MODES: framework]
|
| 8 |
|
| 9 | <br/>
|
| 10 | <br/>
|
| 11 |
|
| 12 | Pre-Rendering allows you to speed up page loads for static content by rendering pages at build time instead of at runtime.
|
| 13 |
|
| 14 | ## Configuration
|
| 15 |
|
| 16 | Pre-rendering is enabled via the `prerender` config in `react-router.config.ts`.
|
| 17 |
|
| 18 | The simplest configuration is a boolean `true` which will pre-render all off the applications static paths based on `routes.ts`:
|
| 19 |
|
| 20 | ```ts filename=react-router.config.ts
|
| 21 | import type { Config } from "@react-router/dev/config";
|
| 22 |
|
| 23 | export default {
|
| 24 | prerender: true,
|
| 25 | } satisfies Config;
|
| 26 | ```
|
| 27 |
|
| 28 | The boolean `true` will not include any dynamic paths (i.e., `/blog/:slug`) because the parameter values are unknown.
|
| 29 |
|
| 30 | To configure specific paths including dynamic values, you can specify an array of paths:
|
| 31 |
|
| 32 | ```ts filename=react-router.config.ts
|
| 33 | import type { Config } from "@react-router/dev/config";
|
| 34 |
|
| 35 | let slugs = getPostSlugs();
|
| 36 |
|
| 37 | export default {
|
| 38 | prerender: [
|
| 39 | "/",
|
| 40 | "/blog",
|
| 41 | ...slugs.map((s) => `/blog/${s}`),
|
| 42 | ],
|
| 43 | } satisfies Config;
|
| 44 | ```
|
| 45 |
|
| 46 | If you need to perform more complex and/or asynchronous logic to determine the paths, you can also provide a function that returns an array of paths. This function provides you with a `getStaticPaths` method you can use to avoid manually adding all of the static paths in your application:
|
| 47 |
|
| 48 | ```ts filename=react-router.config.ts
|
| 49 | import type { Config } from "@react-router/dev/config";
|
| 50 |
|
| 51 | export default {
|
| 52 | async prerender({ getStaticPaths }) {
|
| 53 | let slugs = await getPostSlugsFromCMS();
|
| 54 | return [
|
| 55 | ...getStaticPaths(), // "/" and "/blog"
|
| 56 | ...slugs.map((s) => `/blog/${s}`),
|
| 57 | ];
|
| 58 | },
|
| 59 | } satisfies Config;
|
| 60 | ```
|
| 61 |
|
| 62 | ### Concurrency
|
| 63 |
|
| 64 | By default, pages are pre-rendered one path at a time. You can enable concurrency to pre-render multiple paths in parallel which can speed up build times in many cases. You should experiment with the value that provides the best performance for your app.
|
| 65 |
|
| 66 | To specify concurrency, move your `prerender` config down into a `prerender.paths` field and you can specify the concurrency in `prerender.concurrency`:
|
| 67 |
|
| 68 | ```ts filename=react-router.config.ts
|
| 69 | import type { Config } from "@react-router/dev/config";
|
| 70 |
|
| 71 | let slugs = getPostSlugs();
|
| 72 |
|
| 73 | export default {
|
| 74 | prerender: {
|
| 75 | paths: [
|
| 76 | "/",
|
| 77 | "/blog",
|
| 78 | ...slugs.map((s) => `/blog/${s}`),
|
| 79 | ],
|
| 80 | concurrency: 4,
|
| 81 | },
|
| 82 | } satisfies Config;
|
| 83 | ```
|
| 84 |
|
| 85 | ## Pre-Rendering with/without a Runtime Server
|
| 86 |
|
| 87 | Pre-Rendering can be used in two ways based on the `ssr` config value:
|
| 88 |
|
| 89 | - Alongside a runtime SSR server with `ssr:true` (the default value)
|
| 90 | - Deployed to a static file server with `ssr:false`
|
| 91 |
|
| 92 | ### Pre-rendering with `ssr:true`
|
| 93 |
|
| 94 | When pre-rendering with `ssr:true`, you're indicating you will still have a runtime server but you are choosing to pre-render certain paths for quicker Response times.
|
| 95 |
|
| 96 | ```ts filename=react-router.config.ts
|
| 97 | import type { Config } from "@react-router/dev/config";
|
| 98 |
|
| 99 | export default {
|
| 100 | // Can be omitted - defaults to true
|
| 101 | ssr: true,
|
| 102 | prerender: ["/", "/blog", "/blog/popular-post"],
|
| 103 | } satisfies Config;
|
| 104 | ```
|
| 105 |
|
| 106 | #### Data Loading and Pre-rendering
|
| 107 |
|
| 108 | There is no extra application API for pre-rendering. Routes being pre-rendered use the same route `loader` functions as server rendering:
|
| 109 |
|
| 110 | ```tsx
|
| 111 | export async function loader({ request, params }) {
|
| 112 | let post = await getPost(params.slug);
|
| 113 | return post;
|
| 114 | }
|
| 115 |
|
| 116 | export function Post({ loaderData }) {
|
| 117 | return <div>{loaderData.title}</div>;
|
| 118 | }
|
| 119 | ```
|
| 120 |
|
| 121 | Instead of a request coming to your route on a deployed server, the build creates a `new Request()` and runs it through your app just like a server would.
|
| 122 |
|
| 123 | When server rendering, requests to paths that have not been pre-rendered will be server rendered as usual.
|
| 124 |
|
| 125 | #### Static File Output
|
| 126 |
|
| 127 | The rendered result will be written out to your `build/client` directory. You'll notice two files for each path:
|
| 128 |
|
| 129 | - `[url].html` HTML file for initial document requests
|
| 130 | - `[url].data` file for client side navigation browser requests
|
| 131 |
|
| 132 | The output of your build will indicate what files were pre-rendered:
|
| 133 |
|
| 134 | ```sh
|
| 135 | > react-router build
|
| 136 | vite v5.2.11 building for production...
|
| 137 | ...
|
| 138 | vite v5.2.11 building SSR bundle for production...
|
| 139 | ...
|
| 140 | Prerender: Generated build/client/index.html
|
| 141 | Prerender: Generated build/client/blog.data
|
| 142 | Prerender: Generated build/client/blog/index.html
|
| 143 | Prerender: Generated build/client/blog/my-first-post.data
|
| 144 | Prerender: Generated build/client/blog/my-first-post/index.html
|
| 145 | ...
|
| 146 | ```
|
| 147 |
|
| 148 | During development, pre-rendering doesn't save the rendered results to the public directory, this only happens for `react-router build`.
|
| 149 |
|
| 150 | ### Pre-rendering with `ssr:false`
|
| 151 |
|
| 152 | The above examples assume you are deploying a runtime server but are pre-rendering some static pages to avoid hitting the server, resulting in faster loads.
|
| 153 |
|
| 154 | To disable runtime SSR and configure pre-rendering to be served from a static file server, you can set the `ssr:false` config flag:
|
| 155 |
|
| 156 | ```ts filename=react-router.config.ts
|
| 157 | import type { Config } from "@react-router/dev/config";
|
| 158 |
|
| 159 | export default {
|
| 160 | ssr: false, // disable runtime server rendering
|
| 161 | prerender: true, // pre-render all static routes
|
| 162 | } satisfies Config;
|
| 163 | ```
|
| 164 |
|
| 165 | If you specify `ssr:false` without a `prerender` config, React Router refers to that as [SPA Mode](./spa). In SPA Mode, we render a single HTML file that is capable of hydrating for _any_ of your application paths. It can do this because it only renders the `root` route into the HTML file and then determines which child routes to load based on the browser URL during hydration. This means you can use a `loader` on the root route, but not on any other routes because we don't know which routes to load until hydration in the browser.
|
| 166 |
|
| 167 | If you want to pre-render paths with `ssr:false`, those matched routes _can_ have loaders because we'll pre-render all of the matched routes for those paths, not just the root. You cannot include `actions` or `headers` functions in any routes when `ssr:false` is set because there will be no runtime server to run them on.
|
| 168 |
|
| 169 | #### Pre-rendering with a SPA Fallback
|
| 170 |
|
| 171 | If you want `ssr:false` but don't want to pre-render _all_ of your routes - that's fine too! You may have some paths where you need the performance/SEO benefits of pre-rendering, but other pages where a SPA would be fine.
|
| 172 |
|
| 173 | You can do this using the combination of config options as well - just limit your `prerender` config to the paths that you want to pre-render and React Router will also output a "SPA Fallback" HTML file that can be served to hydrate any other paths (using the same approach as [SPA Mode](./spa)).
|
| 174 |
|
| 175 | This will be written to one of the following paths:
|
| 176 |
|
| 177 | - `build/client/index.html` - If the `/` path is not pre-rendered
|
| 178 | - `build/client/__spa-fallback.html` - If the `/` path is pre-rendered
|
| 179 |
|
| 180 | ```ts filename=react-router.config.ts
|
| 181 | import type { Config } from "@react-router/dev/config";
|
| 182 |
|
| 183 | export default {
|
| 184 | ssr: false,
|
| 185 |
|
| 186 | // SPA fallback will be written to build/client/index.html
|
| 187 | prerender: ["/about-us"],
|
| 188 |
|
| 189 | // SPA fallback will be written to build/client/__spa-fallback.html
|
| 190 | prerender: ["/", "/about-us"],
|
| 191 | } satisfies Config;
|
| 192 | ```
|
| 193 |
|
| 194 | 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:
|
| 195 |
|
| 196 | ```
|
| 197 | # If you did not pre-render the `/` route
|
| 198 | /* /index.html 200
|
| 199 |
|
| 200 | # If you pre-rendered the `/` route
|
| 201 | /* /__spa-fallback.html 200
|
| 202 | ```
|
| 203 |
|
| 204 | If you're getting 404s at valid routes for your app, it's likely you need to configure your host.
|
| 205 |
|
| 206 | 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:
|
| 207 |
|
| 208 | ```sh
|
| 209 | # If you did not pre-render the `/` route
|
| 210 | sirv-cli build/client --single index.html
|
| 211 |
|
| 212 | # If you pre-rendered the `/` route
|
| 213 | sirv-cli build/client --single __spa-fallback.html
|
| 214 | ```
|
| 215 |
|
| 216 | #### Invalid Exports
|
| 217 |
|
| 218 | When pre-rendering with `ssr:false`, React Router will error at build time if you have invalid exports to help prevent some mistakes that can be easily overlooked.
|
| 219 |
|
| 220 | - `headers`/`action` functions are prohibited in all routes because there will be no runtime server on which to run them
|
| 221 | - When using `ssr:false` without a `prerender` config (SPA Mode), a `loader` is permitted on the root route only
|
| 222 | - When using `ssr:false` with a `prerender` config, a `loader` is permitted on any route matched by a `prerender` path
|
| 223 | - If you are using a `loader` on a pre-rendered route that has child routes, you will need to make sure the parent `loaderData` can be determined at run-time properly by either:
|
| 224 | - Pre-rendering all child routes so that the parent `loader` can be called at build-time for each child route path and rendered into a `.data` file, or
|
| 225 | - Use a `clientLoader` on the parent that can be called at run-time for non-pre-rendered child paths
|