Skip to content

fix: disable expo router sitemap in production builds - #164

Open
devs-rootstrap wants to merge 7 commits into
masterfrom
fix/disable-sitemap-in-production
Open

devs-rootstrap wants to merge 7 commits into
masterfrom
fix/disable-sitemap-in-production

Conversation

@devs-rootstrap

Copy link
Copy Markdown
Collaborator

Jira board reference:


What does this do?

  • Configures the expo-router plugin in app.config.ts with sitemap: Env.APP_ENV !== 'production', so the auto-generated /_sitemap route is no longer included in production builds. Development, QA and staging builds keep it.
  • Adds __tests__/app-config.test.ts, which loads app.config.ts for each APP_ENV and asserts the expo-router plugin options (sitemap: false for production, true for the rest).
  • Documents the behavior in the Expo Router guide (docs/src/content/docs/guides/navigation.mdx), including how to change it.

Why did you do this?

Expo Router injects a /_sitemap route listing every route in the app. The Expo docs don't say whether it is excluded from release builds, so I checked the source: in expo-router 6.0.23 (the version we use), getRoutesCore.js adds the sitemap route whenever options.sitemap !== false, with no check for dev vs. release. The option comes from the config plugin's sitemap setting, which is written to extra.router and 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:

  • Only production should drop the sitemap; development, qa and staging keep it.
  • Gating on Env.APP_ENV follows the same pattern already used for the app icon badge in app.config.ts.

Who/what does this impact?

  • Production builds: <scheme>://_sitemap (and /_sitemap on web) now lands on the not-found screen instead of the route list.
  • Non-production builds: no change.
  • Projects created from the template inherit this default and can flip it in app.config.ts.

Layer 2: Risk Classification

Author applies a risk label when opening the PR. When in doubt, escalate.

Label When to use
risk:low Config, copy, minor UI tweaks. Low blast radius.
risk:medium Feature work, refactors touching multiple files.
risk:high Auth, payments, migrations, security-sensitive code.

Setup

  • Create risk:low, risk:medium, risk:high labels in the repo
  • Add risk criteria to contributing guide or PR template
  • Add reminder in PR template to apply the label before requesting review
  • Define who can override/escalate (suggested: tech lead)

How did you test this?

  • Tested on iOS
  • Tested on Android
  • Tested on a small device
  • Tested on a real device
  • Tested all flows related with this PR changes
  • Tested accessibility
  • Added tests

Verification

Check Result
pnpm lint Passed
pnpm type-check Passed
pnpm lint:translations Passed
pnpm test Passed
APP_ENV=production npx expo config --type public --json extra.router is { "sitemap": false }, plugin entry ["expo-router", { "sitemap": false }]
APP_ENV=development npx expo config --type public --json extra.router is { "sitemap": true }
APP_ENV=qa / APP_ENV=staging (same command) extra.router is { "sitemap": true }

Not yet verified on a device or emulator.

How to test locally

  1. Run a development build (pnpm start:development, then pnpm ios or pnpm android) and open <scheme>://_sitemap (e.g. npx uri-scheme open "RootstrapApp://_sitemap" --ios). The sitemap screen should list the app routes.
  2. Run APP_ENV=production npx expo config --type public --json and check that extra.router.sitemap is false.
  3. Optionally, with a production build (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 sitemap option of the expo-router plugin to true in app.config.ts.


Screenshots / Previews

N/A (no UI changes).

Copilot AI balanced review requested due to automatic review settings November 3, 2026 12:05
@devs-rootstrap
devs-rootstrap requested a review from a team as a code owner November 3, 2026 12:05
@devs-rootstrap devs-rootstrap added the risk:low Config, copy, minor UI tweaks. Low blast radius. label Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

💯 Test Coverage

Lines Statements Branches Functions
Coverage: 56%
56.23% (347/617) 59.18% (174/294) 48.76% (118/242)

😎 Tests Results

Tests Skipped Failures Errors Time
145 0 💤 0 ❌ 0 🔥 26.005s ⏱️
👀 Tests Details • (56%)
File% Stmts% Branch% Funcs% LinesUncovered Line #s
All files56.2359.1848.7656.9 
report-only-changed-files is enabled. No files were changed in this commit :)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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

@label-pr-severity-rs label-pr-severity-rs Bot added the ai:clean No significant issues label Nov 3, 2026

## 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.

@asdolo asdolo Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That'd work, yes. But also try to set up an Android and/or iOS emulator meanwhile.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

@fernandatoledo fernandatoledo left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devs-rootstrap I left a few comments on this PR, please take a look.

Comment thread app.config.ts
'expo-dev-client',
'expo-localization',
'expo-router',
['expo-router', { sitemap: Env.APP_ENV !== 'production' }],

@fernandatoledo fernandatoledo Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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'])(

@fernandatoledo fernandatoledo Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

@fernandatoledo fernandatoledo Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

❌ 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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai:clean No significant issues risk:low Config, copy, minor UI tweaks. Low blast radius.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants