Repository navigation
fix: disable expo router sitemap in production builds - #164
devs-rootstrap wants to merge 7 commits into
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues remain for the change that disables the sitemap in production while preserving it for non-production debugging.
No open issues 🎉
What changed in this PR
Overall Review: ai:clean
Disables Expo Router’s sitemap route in production while preserving it as a debugging aid in non-production environments. No actionable findings remain.
Changes:
- Sets the sitemap plugin option based on
Env.APP_ENV. - Adds configuration tests for production, development, QA, and staging.
- Documents sitemap availability and how projects can override the default.
| File | Description |
|---|---|
docs/src/content/docs/guides/navigation.mdx |
Documents environment-specific sitemap behavior and customization. |
app.config.ts |
Disables the sitemap for production. |
__tests__/app-config.test.ts |
Checks sitemap configuration across all four environments. |
Review effort: Balanced
|
|
||
| ## Sitemap route | ||
|
|
||
| Expo Router injects a [`/_sitemap`](https://docs.expo.dev/router/reference/sitemap/) route that lists every route in the app, which is handy for debugging. Since it exposes the full route list, the template only generates it outside production: the `expo-router` plugin in `app.config.ts` is configured with `sitemap: Env.APP_ENV !== 'production'`. On `development`, `qa` and `staging` builds you can open `<scheme>://_sitemap` (or `/_sitemap` on web); on `production` builds that path shows the not-found screen. |
There was a problem hiding this comment.
@devs-rootstrap Can you attach a screenshot of the sitemap screen for more context in this docs page? Run the app in an Android or iOS emulator.
There was a problem hiding this comment.
@asdolo Good idea, a screenshot would make that section clearer. I don't have an Android emulator or iOS simulator set up on the machine I'm working from right now, so I can't capture a native one yet and haven't pushed any change for this. One question so I can move forward: would a screenshot of /_sitemap taken from the web build (pnpm web, development env) be fine for the docs page, or do you specifically want it from Android/iOS? If it has to be native, I'll add it once I can run the app on a simulator.
There was a problem hiding this comment.
That'd work, yes. But also try to set up an Android and/or iOS emulator meanwhile.
There was a problem hiding this comment.
@asdolo Done in d4045f2: I added a screenshot of /_sitemap to the "Sitemap route" section of docs/src/content/docs/guides/navigation.mdx, captured from the web build in the development environment (phone-sized viewport). The image is committed as docs/src/assets/sitemap-screen.png and referenced with a relative path, and the text notes it was taken on web. I checked it renders on the docs dev server.
Noted on the emulators. Once I have an Android emulator or iOS simulator running, I will swap in a native capture.
There was a problem hiding this comment.
@devs-rootstrap I left a few comments on this PR, please take a look.
| 'expo-dev-client', | ||
| 'expo-localization', | ||
| 'expo-router', | ||
| ['expo-router', { sitemap: Env.APP_ENV !== 'production' }], |
There was a problem hiding this comment.
@devs-rootstrap this gate fails open: env.js defaults APP_ENV to development when it is unset, so anything other than an explicit production keeps the sitemap on. The production build profile in eas.json sets APP_ENV=production, but there is no eas update script or update profile. An OTA update published to the production channel without APP_ENV=production would ship extra.router.sitemap=true and re-enable /_sitemap in the production app. Please add an explicit update script that sets APP_ENV (or document the requirement) so this can't happen silently.
There was a problem hiding this comment.
@fernandatoledo good catch. In 0ded3f2 I added update:development, update:qa, update:staging and update:production scripts to package.json. They run eas update with APP_ENV set through cross-env, the same way the build scripts do, and each one publishes to the matching channel from eas.json. The navigation guide now says OTA updates have to go through these scripts (e.g. pnpm run update:production --message "..."), because env.js falls back to development and would turn /_sitemap back on. The scripts are also listed in agent_docs/commands.md. They have not been run against EAS yet.
| expect(getExpoRouterOptions(config)).toEqual({ sitemap: false }); | ||
| }); | ||
|
|
||
| it.each(['development', 'qa', 'staging'])( |
There was a problem hiding this comment.
@devs-rootstrap these tests only assert the plugin tuple for four explicit APP_ENV values. Could you add a case for unset APP_ENV (which defaults to development), and make the expo-router lookup fail loudly if the entry reverts to the bare string 'expo-router' instead of returning undefined? Also worth noting that nothing here verifies the runtime behavior (/_sitemap unreachable in production), and the PR says it hasn't been checked on a device.
There was a problem hiding this comment.
@fernandatoledo done in b0a6b07. There is now a case for unset APP_ENV that expects { sitemap: true } (env.js falls back to development). The expo-router lookup now throws if the entry is a bare string or missing instead of returning undefined, and a test covers the bare 'expo-router' case. You are right that these tests only check the config and not the runtime behavior: /_sitemap being unreachable in a production build still needs a manual check on a device.
|
|
||
| ## Sitemap route | ||
|
|
||
| Expo Router injects a [`/_sitemap`](https://docs.expo.dev/router/reference/sitemap/) route that lists every route in the app, which is handy for debugging. Since it exposes the full route list, the template only generates it outside production: the `expo-router` plugin in `app.config.ts` is configured with `sitemap: Env.APP_ENV !== 'production'`. On `development`, `qa` and `staging` builds you can open `<scheme>://_sitemap` (or `/_sitemap` on web); on `production` builds that path shows the not-found screen. |
There was a problem hiding this comment.
@devs-rootstrap two corrections for this paragraph. (1) In this template /_sitemap in production lands on the app's own catch-all (src/app/[...messing].tsx), not expo-router's built-in not-found screen. If someone removes that catch-all, the built-in Unmatched view still renders a hard-coded link to /_sitemap, which would dead-end in production, so it's worth mentioning. (2) "Since it exposes the full route list" overstates the protection: route names stay in the JS bundle and typed-routes output. I'd phrase it as removing the in-app route browser from production builds.
There was a problem hiding this comment.
@fernandatoledo updated in 7d1eff9. The paragraph now describes the change as removing the in-app route browser from production builds, and notes that route names still ship in the JS bundle and the typed-routes output. It says production /_sitemap lands on the template's catch-all src/app/[...messing].tsx. It also warns that if the catch-all is removed, expo-router's built-in Unmatched screen links to /_sitemap, which would dead-end in production.
|
❌ Action Required: We ran Expo Doctor for this PR and found some issues that need to be addressed. Please review the complete report below 👇 npm warn exec The following package was not found and will be installed: expo-doctor@1.21.6
Running 18 checks on your project...
16/18 checks passed. 2 checks failed. Possible issues detected:
Use the --verbose flag to see more details about passed checks.
✖ Validate packages against React Native Directory package metadata
The following issues were found when validating your dependencies against React Native Directory:
Unmaintained: react-native-edge-to-edge
Advice:
Use libraries that are actively maintained and support the New Architecture. Find alternative libraries with https://reactnative.directory.
Add packages to expo.doctor.reactNativeDirectoryCheck.exclude in package.json to selectively skip validations, if the warning is not relevant.
Update React Native Directory to include metadata for unknown packages. Alternatively, set expo.doctor.reactNativeDirectoryCheck.listUnknownPackages in package.json to false to skip warnings about packages with no metadata, if the warning is not relevant.
✖ Check that packages match versions required by installed Expo SDK
🔧 Patch version mismatches
package expected found
expo ~54.0.37 54.0.34
expo-constants ~18.0.14 18.0.13
expo-font ~14.0.12 14.0.11
expo-localization ~17.0.9 17.0.8
expo-router ~6.0.24 6.0.23
jest-expo ~54.0.18 54.0.17
Changelogs:
- expo-constants → https://github.com/expo/expo/blob/sdk-54/packages/expo-constants/CHANGELOG.md
- expo-font → https://github.com/expo/expo/blob/sdk-54/packages/expo-font/CHANGELOG.md
- expo-localization → https://github.com/expo/expo/blob/sdk-54/packages/expo-localization/CHANGELOG.md
- expo-router → https://github.com/expo/expo/blob/sdk-54/packages/expo-router/CHANGELOG.md
6 packages out of date.
Advice:
Use 'npx expo install --check' to review and upgrade your dependencies.
To ignore specific packages, add them to "expo.install.exclude" in package.json. Learn more: https://expo.fyi/dependency-validation
2 checks failed, indicating possible issues with the project. |
Jira board reference:
What does this do?
expo-routerplugin inapp.config.tswithsitemap: Env.APP_ENV !== 'production', so the auto-generated/_sitemaproute is no longer included in production builds. Development, QA and staging builds keep it.__tests__/app-config.test.ts, which loadsapp.config.tsfor eachAPP_ENVand asserts theexpo-routerplugin options (sitemap: falsefor production,truefor the rest).docs/src/content/docs/guides/navigation.mdx), including how to change it.Why did you do this?
Expo Router injects a
/_sitemaproute listing every route in the app. The Expo docs don't say whether it is excluded from release builds, so I checked the source: inexpo-router6.0.23 (the version we use),getRoutesCore.jsadds the sitemap route wheneveroptions.sitemap !== false, with no check for dev vs. release. The option comes from the config plugin'ssitemapsetting, which is written toextra.routerand read at runtime. So today the route ships in production builds and exposes the full route list.It's still a useful debugging aid, so it stays enabled in every non-production environment.
Assumptions:
productionshould drop the sitemap;development,qaandstagingkeep it.Env.APP_ENVfollows the same pattern already used for the app icon badge inapp.config.ts.Who/what does this impact?
<scheme>://_sitemap(and/_sitemapon web) now lands on the not-found screen instead of the route list.app.config.ts.Layer 2: Risk Classification
Author applies a risk label when opening the PR. When in doubt, escalate.
risk:lowrisk:mediumrisk:highSetup
risk:low,risk:medium,risk:highlabels in the repoHow did you test this?
Verification
pnpm lintpnpm type-checkpnpm lint:translationspnpm testAPP_ENV=production npx expo config --type public --jsonextra.routeris{ "sitemap": false }, plugin entry["expo-router", { "sitemap": false }]APP_ENV=development npx expo config --type public --jsonextra.routeris{ "sitemap": true }APP_ENV=qa/APP_ENV=staging(same command)extra.routeris{ "sitemap": true }Not yet verified on a device or emulator.
How to test locally
pnpm start:development, thenpnpm iosorpnpm android) and open<scheme>://_sitemap(e.g.npx uri-scheme open "RootstrapApp://_sitemap" --ios). The sitemap screen should list the app routes.APP_ENV=production npx expo config --type public --jsonand check thatextra.router.sitemapisfalse.pnpm start:production), open the same link: it should land on the not-found screen.Notes:
This only changes the default in the template. Projects that want the sitemap in production can set the
sitemapoption of theexpo-routerplugin totrueinapp.config.ts.Screenshots / Previews
N/A (no UI changes).