This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

Async routes

Edit page

Learn how to speed up development with async bundling in Expo Router.


Expo Router can automatically split your JavaScript bundle based on the route files using React Suspense. This enables faster development as only the routes you navigate to will be bundled or loaded into memory. This can also be useful for reducing the initial bundle size for your application.

Apps using the Hermes Engine will not benefit as much from bundle splitting as the bytecode is already memory mapped ahead of time. However, it will improve your over-the-air updates, React Server Components, and web support.

How it works

All Routes are wrapped inside a suspense boundary and are loaded asynchronously. This means that the first time you navigate to a route, it will take a little longer to load. However, once it is loaded, it will be cached and subsequent visits will be instant.

Loading errors are handled in the parent route, via the ErrorBoundary export.

Async routes cannot be statically analyzed during development, so all files will be treated as routes even if they don't export a default component. After the component is bundled and loaded, any invalid route will use a fallback warning screen.

For those familiar with advanced bundling techniques, the async routes feature is composed of React Suspense, route-based bundle splitting and lazy bundling (in development).

Setup

In SDK 58 and later, async routes are enabled by default on web in development and production. Native platforms default to disabled. In SDK 57 and earlier, async routes require an explicit opt-in on every platform.

Configure asyncRoutes in the Expo Router config plugin of your app config. To disable async routes on web, set asyncRoutes to { "web": false }. Setting asyncRoutes to false disables the feature on all platforms:

app.json
{ "expo": { "plugins": [["expo-router", { "asyncRoutes": false }]] } }

You can also use "development" or "production" to enable async routes only in that mode, or an object with platform-specific settings (default, android, ios, or web):

  • An explicit platform value takes precedence over default.
  • Setting { "default": false } disables async routes on web unless web explicitly enables them.
  • In SDK 58 and later, setting only native platforms keeps the web default. For example, { "android": true } behaves as { "android": true, "web": true }. Native production builds still load routes synchronously.

For example, this configuration enables async routes on web in both modes and on iOS in development, while disabling them on Android. It also works as an explicit opt-in in SDK 57 and earlier:

app.json
{ "expo": { "plugins": [ [ "expo-router", { "origin": "https://acme.com", "asyncRoutes": { "web": true, "android": false, "default": "development" } } ] ] } }

After changing the setting, clear the Metro cache with --clear when starting or exporting your project:

Terminal
- npx expo start --clear

# Or when exporting
- npx expo export --clear
- yarn expo start --clear

# Or when exporting
- yarn expo export --clear
- pnpm expo start --clear

# Or when exporting
- pnpm expo export --clear
- bun expo start --clear

# Or when exporting
- bun expo export --clear

Static rendering

Static rendering is supported in production web apps by rendering all Suspense boundaries synchronously in Node.js, then linking all of async chunks together in the HTML based on all the selected routes for a given HTML file. This ensures you don't encounter a waterfall of loading states on server navigations. Subsequent navigations will recursively load any missing chunks.

To ensure a consistent first render, all layout routes leading up to the leaf route for a URL will be included in the initial server response.

All anchor routes, defined with unstable_settings = { anchor: '...' } will be included in the initial HTML file as they are required for the first render. For example, if the server request is for a modal, the screen rendered under the modal will also be included to ensure the modal is rendered correctly.

Caveats

The following limitations apply to async routes:

  • Async routes do not support native production apps yet.
  • In development, the runtime JavaScript is lazily bundled so you may encounter cases where the HTML doesn't match the available JavaScript.
  • Custom SuspenseFallback exports do not work with async routes. In SDK 58 and later, web apps use the default loading fallback unless async routes are disabled. To keep a custom fallback, set asyncRoutes: { web: false } in the expo-router config plugin. See the migration guide for an app config example.