This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
Static rendering
Edit page
Learn how to render routes to static HTML and CSS files with Expo Router.
To enable Search Engine Optimization (SEO) on the web you must statically render your app. This guide will walk you through the process of statically rendering your Expo Router app.
With static rendering, data loaders are executed during the build process and their results are embedded in the output HTML files.
Setup
1
Enable static rendering in your project's app config:
2
Start the development server:
Production
In SDK 58 and later, you can combine static rendering with API routes by setting apiRoutes: true in the expo-router config plugin. This exports prerendered HTML and API routes to dist/server, with client assets in dist/client, and requires a deployed server.
To bundle your static website for production, run the export command:
This will create a dist directory with your statically rendered website. If you have files in a local public directory, these will be copied over as well.
When API routes are enabled, use npx expo serve to test the production build locally. Otherwise, run the following command and open the linked URL in your browser:
With API routes disabled, this project can be deployed to almost every hosting service. Note that this is not a single-page application, nor does it contain a custom server API. This means dynamic routes (for example, src/app/[id].tsx) will not arbitrarily work. You may need to build a serverless function to handle dynamic routes.
Dynamic Routes
The static output will generate HTML files for each route. This means dynamic routes (src/app/[id].tsx) will not work out of the box. You can generate known routes ahead of time using the generateStaticParams function.
This will output a file for each post in the dist directory. For example, if the generateStaticParams method returned [{ id: "alpha" }, { id: "beta" }], the following files would be generated:
distblogalpha.htmlbeta.htmlgenerateStaticParams
A server-only function evaluated at build-time in a Node.js environment by Expo CLI. This means it has access to __dirname, process.cwd(), process.env, and more. It also has access to every environment variable that's available in the process. However, the values prefixed with EXPO_PUBLIC_ do not run in a browser environment, so it cannot access browser APIs such as localStorage or document. It also cannot access native Expo APIs such as expo-camera or expo-location.
generateStaticParams cascades from nested parents down to children. The cascading parameters are passed to every dynamic child route that exports generateStaticParams.
Now the dynamic child routes will be invoked twice, once with { id: 'one' } and once with { id: 'two' }. All variations must be accounted for.
Read files using process.cwd()
Since Expo Router compiles your code into a separate directory you cannot use __dirname to form a path as its value will be different than expected.
Instead, use process.cwd(), which gives you the directory where the project is being compiled.
Root HTML
By default, every page is wrapped with some small HTML boilerplate, this is known as the root HTML.
You can customize the root HTML file by creating a src/app/+html.tsx file in your project. This file exports a React component that only ever runs in Node.js, which means global CSS cannot be imported inside of it. The component will wrap all routes in the app directory. This is useful for adding global <head> elements or disabling body scrolling.
Note: Global context providers should go in the Root Layout component, not the Root HTML component.
In SDK 58 and later, static rendering requires custom HTML components to render the values from useServerDocumentContext(). Render headNodes inside <head> and bodyNodes after children inside <body>.
- The
childrenprop comes with the root<div id="root" />tag included inside. - The
pathnameandparamsprops contain the rendered route's pathname and URL parameters. Available in SDK 58 and later. headNodescontains generated metadata and styles, such as React Native web styles.bodyNodesincludes font resources and JavaScript bundles.- Global CSS should not be imported into this file. Instead, use the Root Layout. Expo Router traverses the dependency graph starting from the root layout, so importing CSS elsewhere can cause unexpected loading order where node_modules CSS takes precedence over your custom styles.
- Browser APIs like
window.locationare unavailable in this component as it only runs in Node.js during static rendering.
expo-router/html
The exports from expo-router/html are related to the Root HTML component.
useServerDocumentContext: Returns the generated head and body nodes and document attributes for your root HTML component to render.ScrollViewStyleReset: Root style-reset for full-screen React Native web apps with a root<ScrollView />should use the following styles to ensure native parity.
Meta tags
In SDK 58 and later, export a generateMetadata() function from a route to include metadata in its generated HTML. Expo CLI resolves this function at build time for each static path.
The function can be asynchronous and receives undefined as its first argument and the route's parameters as its second argument. Static rendering does not provide a request, including during development. See metadata with server rendering for supported fields and dynamic examples.
Use generateMetadata() for metadata that must be present before JavaScript runs. The <Head> component from expo-router/head can update metadata after hydration, but its tags are not included in the initial static HTML. In SDK 57 and earlier, use <Head> for initial static metadata.
When upgrading from SDK 57, follow Migrate static page metadata to move existing Head tags and document attributes.
Static files
Expo CLI supports a root public directory that gets copied to the dist directory during static rendering. This is useful for adding static files like images, fonts, and other assets.
publicfavicon.icologo.png.well-knownapple-app-site-associationSome paths such as
/assetsare reserved by Metro. Avoid placing files in public/assets/ or other reserved paths. See Reserved paths for the complete list.
These files will be copied to the dist directory during static rendering:
distindex.htmlfavicon.icologo.png.well-knownapple-app-site-association_expostaticjsindex-xxx.jscssindex-xxx.cssWeb only: Static assets can be accessed in runtime code using relative paths. For example, the logo.png can be accessed at
/logo.png:
Fonts
Expo Font has automatic static optimization for font loading in Expo Router. When you load a font with expo-font, Expo CLI will automatically extract the font resource and embed it in the page's HTML, enabling preloading, faster hydration, and reduced layout shift.
The following snippet will load Inter into the namespace and statically optimize on web:
This generates the following static HTML:
- Static font optimization requires the font to be loaded synchronously. If the font isn't statically optimized, it could be because it was loaded inside a
useEffect, deferred component, or async function. - Static optimization is only supported with
Font.loadAsyncandFont.useFontsfromexpo-font. Wrapper functions are supported as long as the wrappers are synchronous.
Common questions
How do I add a custom server?
There is no prescriptive way to add a custom server. You can use any server you want. However, you will need to handle dynamic routes yourself. You can use the generateStaticParams function to generate static HTML files for known routes.
In SDK 58 and later, set apiRoutes: true in the expo-router config plugin to add API routes alongside your static HTML. To render pages at request time, use server rendering.
Server-side rendering
Rendering at request-time is not supported with web.output: 'static'. To render pages dynamically on each request, use server rendering with web.output: 'server' instead.
Where can I deploy statically rendered websites?
You can deploy your statically rendered website to any static hosting service. Here are some popular options:
Note: You don't need to add Single-Page Application styled redirects to your static hosting service. The static website is not a single-page application. It is a collection of static HTML files.