Expo Router v3 beta is now available
The third major release of Expo Router is now in beta for the next month. Expo Router v3 is part of Expo SDK 50 (React Native 0.73.0).
Expo Router v3 offers a new experimental system for building server endpoints: API Routes, reduced bundle size, and more powerful web support––we’re still on track to deliver full web support in v4.
The full release notes for Expo Router v3 won’t be available until the stable release, but you can browse the changes in the expo/expo CHANGELOG to learn more about the scope of the release and any breaking changes.
New Features
- 2x faster static website exports:
npx expo export -p web
is over 2x faster. An average v2 project exported in ~23s, v3 exports in ~11s. These savings scale with the project! - 30% smaller base bundle size: The base bundle size for production websites is now 30% smaller (from 1.48mb to 1.05mb). The initial bundle size is further decreased by enabling the new bundle splitting functionality on web.
- API Routes: Based on our API Routes RFC––This is a zero-config system for creating server endpoints with a unified build process. Adding a
+api.js
extension to a route will ensure it’s only rendered on the server. API routes are hosted from the same dev server as the website and app in development and must be deployed to a dynamic hosting service in production. Learn more in API Routes. This is a very large feature and will remain experimental even after the v3 stable release.
- Bundle Splitting: Expo CLI now supports bundle splitting on async imports when bundling for web platform. We’ve added this to Expo Router to automatically split on routes and eagerly load chunks to prevent network waterfalls. This also works with static rendering (SSG). Learn more in Async Routes. This was the last major blocker for adding universal React Server Components to Expo.
- Configurable app directory: While not recommended, you can now change the
/app
directory to be any directory in your project. Learn more about the root directory.
- Testing Library: To provide robust test coverage for Expo Router, we created a set of Jest utilities that could quickly emulate entire navigation structures. This testing library is now available for public consumption. Learn more in Testing Expo Router.
- URL Support: We’ve added built-in support for the standard URL API and updated all of our APIs to use this, leading to a net bundle size reduction on all platforms, especially web. This is part of a larger effort to improve data fetching across Expo. Learn more about the URL API.
- Not Found routes: To account for API Routes, we’ve added an official convention to match all 404 / Not Found routes. By creating a
+not-found.js
route, you can match anything that’s unmatched in a directory. This is supported on all native platforms and web in server-mode. When this route is matched, a 404 status code will also be returned on web. Learn more about +not-found. - New
router.navigate
API: To fix issues with pushing, we’ve changed therouter.push
API to always push new routes, whereas the previous version would pop occasionally. You can use the newrouter.navigate
API to obtain this previous behavior. `Link` components currently use the navigate behavior by default still, but you can change this to use push by passing the newpush
prop. e.g.<Link push href=”/” />
- Dynamic Routes on web: The new
server
output mode supports server navigation to dynamic routes on web. Previously, you could only perform client-side navigation to routes likeapp/[id].tsx
but the server API is capable of redirecting requests to any route in your project automatically. This is not supported with standard static output. - New Link Props: The Link component now supports
target
,rel
, anddownload
props on web. Link also now hasclassName
support which works as-is on web and can be used with tools like Nativewind to add Tailwind support on all platforms. - Universal Fast Refresh: We’ve fixed universal Fast Refresh upstream so you no longer need resolutions on react-refresh. The same Fast Refresh implementation now works across all platforms universally and should be substantially more stable!
- Base URL Support: Expo Router now support deploying to subdomains with
experiments.baseUrl
— this applies to all platforms so you may want to configure it with an environment variable inapp.config.js
. With the addition of this feature, you can now deploy static Expo Router websites to GitHub Pages. We plan to stabilize this API in SDK 51. - Auto Font Optimization: Fonts loaded with
expo-font
are now automatically extracted and preloaded on web when usingstatic
orserver
output. This enables fonts to start loading before the JavaScript has finished, leading to better initial styles. This also enables you to statically render your app even if there’s a top-level render guard. Learn more.
- Improved Tailwind/PostCSS on web: PostCSS with Expo’s Metro web will no longer be blocked on caching. This means you can use full Tailwind + PostCSS on web and integrate with fantastic UI packages like Shadcn UI (web-only). Metro CSS is now enabled by default!
- Support for external links: You can now link to popular external URLs like
mailto:
andsms:
which don’t follow the standard://
convention of other URLs. - Added mjs and cjs support. All modules are converted to commonjs in the bundler as ESM is not supported on native, but you can now import mjs modules as expected.
- Custom Metro resolvers and transforms: Users can now extend the Metro resolver and modify the transformer using the Babel caller, enabling better control over the bundling process. Learn more in the new Expo Metro docs.
- Improved Typed Routes: Typed routes ensure better stability over time by automatically generating TypeScript types for your project. You can now generate types in CI with
npx expo customize tsconfig.json
- tsconfig paths enabled by default: tsconfig/jsconfig path aliases are now enabled by default and follow the TypeScript resolution more closely, they’re also now supported in
jest-expo
. - Better Source Maps: Source map exports in production web are now supported, we’ve renamed the
npx expo export
flag--dump-sourcemap
to--source-maps
— Hermes source maps now work more reliably. - Improved monorepo support: Projects no longer need
expo-yarn-workspaces
to enable monorepo support in their app. The majority of standard monorepo functionality is built-in to Expo CLI and Expo Metro Config.
Breaking Changes
- Expo Webpack is deprecated in favor of our universal Metro for web.
- Expo Router v3 only works with Expo SDK 50. Ensure you upgrade your project before upgrading Expo Router.
- The deprecated
useSearchParams
anduseLink
hooks have been removed. - Default CSS reset has been updated to match the latest
react-native-web@0.19.8
recommendation. You may need to update your local template or+html
styles if you were depending on styles from the previous reset. - React Native Gesture Handler is no longer added automatically. If you use the Drawer navigator or any React Native Gesture Handler features, you’ll need to add the global provider to the Root Layout. We recommend avoiding this dependency on web platforms as it will increase bundle size substantially and mostly be unused on web.
- The
SplashScreen
export is now deprecated, and all the features have been moved upstream toexpo-splash-screen
- Remove
expo-router/babel
babel plugin in favor ofbabel-preset-expo
- If you have a root
[...missing].tsx
route, rename this to+not-found.tsx
otherwise API routes will not be available. Normal routes, including catch-alls, have higher priority over API routes and not found routes. - Expo Router no longer has client-side mocks for
__dirname
and__filename
globals. - Remove deprecated
<Screen />
propredirect
in favor of the<Redirect />
component which works more reliably. We still plan to improve redirection in the upcoming versions. expo-head
native module is now part of expo-router and should not be installed separately.- The source for expo-router now lives in
build
instead ofsrc
— if you were dangerously importing nested modules, update them to useexpo-router/build/*
Bug Fixes
We’ve been actively fixing bugs and improving stability with Expo Router. The majority of notable fixes have all been backported to v2 and released progressively throughout the previous release-cycle.
Expect improved TypeScript support, more stable redirection, smaller bundle sizes, and better web support.
Using the Beta
- Initialize a new project with SDK 50 beta:
npm:npx create-expo-app --template tabs@beta
yarn:yarn create expo-app --template tabs@beta
Note:create-expo-app
will install dependencies with npm whennpx
is used and yarn whenyarn create
used. - Upgrade an existing project:
Install the beta version of the Expo package:npm install expo@next
oryarn add expo@next
Upgrade all dependencies to match SDK 50:npx expo install --fix
- Install the latest Expo Go for iOS to your physical device:
Use this TestFlight open beta link and follow the instructions. - Install the latest Expo Go for iOS simulators or Android emulators/physical devices:
Launch your project through Expo CLI (press thei
ora
keyboard shortcut after runningnpx expo start
) and the updated version of Expo Go will be automatically installed. - SDK 50 beta is not yet available on Snack.
- Read the documentation by selecting it from the version selector in the API reference section.
Migrating from Expo Router v2 to v3
- Ensure you remove any Yarn
resolution
or NPMoverrides
especially formetro
,metro-resolver
, andreact-refresh
. If you skip this, then Fast Refresh may not work as expected. - Possibly the largest behavior change in any version of Expo Router––
router.push
is nowrouter.navigate
androuter.push
always pushes routes. This is technically a bug fix, but it may cause unexpected changes in complex navigation behavior. - Remove the
expo-router/babel
preset in favor ofbabel-preset-expo
and be sure to clear the Metro cache before restarting your dev server—this means runningnpx expo start --clear
ornpx expo export --clear
- If you have a top-level
[...missing].js
route, ensure you rename this to+not-found.js
if you plan to use API Routes. Otherwise, you’ll only see the “not found” route when you ping an API endpoint. react-native-gesture-handler
is no longer added automatically and must be injected if you wish to use the drawer navigator. We recommend avoiding this dependency on web platforms as it will increase bundle size substantially and mostly be unused on web. Learn more Drawer navigator.- Enable Async Routes on web to use the new bundle splitting functionality on web. Async Routes may have issues on Android with Reanimated, you can disable the feature per-platform if needed.
- If you have custom splash screen handling, change the import of
SplashScreen
inexpo-router
toexpo-splash-screen
- If you were using the
hrefAttrs
prop on theLink
component for adding additional web props, migrate to top-level props by the same name, e.g.hrefAttrs={{ target: '_blank' }}
should now betarget="_blank"
— this applies totarget, rel, download
— all of which are web-only and automatically shimmed on native. - If you’re using the
react-native-web
style escape hatch (style={{ $$css: true, _: “myclass” }}
) to setclassName
on Link components for web, migrate to the top-levelclassName
prop, e.g.<Link className="myclass" />
How to report issues
- Create an issue on https://github.com/expo/expo/issues and be sure to fill out the appropriate template (and include a minimal reproducible example, please!).
- Figuring out the underlying causes of issues is super helpful.
- Let us know that you are using the SDK 50 beta so we can prioritize the issue.
- The most helpful beta testers will be listed in the final release notes (and possibly even provided with some Discord flair — you can link your Discord and GitHub accounts to your Expo account).
- Do not report issues to expo/router as this repo will now be archived.
Thank you for helping us with testing the release — we look forward to shipping it soon! 🚀