CLI commands
rndl routes
Print the deep-linkable route table of an Expo Router or React Navigation app, as an aligned table or JSON.
rndl routes scans your app and prints every deep-linkable route with its URL pattern and params.
It is the foundation the other commands build on: validate cross-checks this table against your
AASA file, open and interactive build URLs from it, and typegen turns it into types.
rndl routes # auto-detects app/ then src/app/
rndl routes --json # routes, API routes, layouts, diagnostics
rndl routes --app-dir src/app # explicit Expo Router directory
rndl routes --config src/navigation/linking.ts # default or `linking` export
rndl routes --config src/navigation/linking.ts#named # a specific named export
rndl routes --config src/navigation/linking.ts --dotenv # module imports from '@env'Flags
| Flag | Description |
|---|---|
--json | Print the full scan result as JSON instead of the aligned table. |
--app-dir <dir> | Expo Router app directory. Defaults to app/, then src/app/. |
--config <module[#export]> | React Navigation linking module, with an optional named export after #. |
--dotenv [path] | Dotenv file backing @env imports in the --config module. Bare flag: .env. |
--app-dir and --config are mutually exclusive: an app has one router. --dotenv is available
on every command that accepts --config.
Expo Router
The adapter walks your app directory and understands every file convention:
- Dynamic segments (
[id]) and catch-alls ([...slug]). - Route groups (
(group)), including the array syntax ((a,b)) that expands one file into a route per group. - Platform-specific variants (
.ios,.android,.native,.web). _layoutfiles (structural, not routes),indexfiles (the parent path), and+not-found.- The special files
+api(excluded and tagged as an API route),+html,+native-intent, and+middleware.
Conventions it does not recognize produce a warning, never a crash, so a new router release does not break your CI run.
React Navigation
The adapter imports your linking module under Node (TypeScript and ESM are handled for you) and
walks config.screens recursively, honoring nested navigators, path, exact, alias,
regex-constrained and optional params, wildcards, and prefixes. Params with a custom parse
function are reported as unknown (custom parse), since their runtime type is not knowable from
the config.
Because the module is executed outside the native runtime, keep the linking config in an isolated
module that only exports plain data and parse/stringify functions (react-navigation imports
are fine as import type):
// src/navigation/linking.ts
import type { LinkingOptions } from '@react-navigation/native';
export const linking = {
prefixes: ['myapp://', 'https://example.com'],
config: {
screens: {
Home: '',
Article: { path: 'article/:slug', exact: true },
},
},
} satisfies LinkingOptions<Record<string, unknown>>;If the import fails because the module pulls in native code, rndl prints an actionable error
suggesting this isolated-module pattern.
Configs that import from @env (react-native-dotenv)
@env is react-native-dotenv's virtual module: it only exists inside the Metro/babel build, so a
linking module that imports from it fails to load under Node with
Cannot find module '@env'. Pass --dotenv [path] (default .env) and rndl parses the dotenv
file and serves its values as the @env module while your config loads:
rndl routes --config src/navigation/linking.ts --dotenv
rndl routes --config src/navigation/linking.ts --dotenv .env.stagingVariables missing from the file come back undefined (matching react-native-dotenv's
allowUndefined), and the parser does no variable expansion. A missing dotenv file reports
DOTENV_NOT_FOUND.