Remix v2 is filesystem-routed under app/routes/. The route URL
is derived from the filename via dot-flat naming and a handful
of special prefixes:
app/routes/_index.tsx → GET /
app/routes/about.tsx → GET /about
app/routes/users._index.tsx → GET /users
app/routes/users.$id.tsx → GET /users/{id}
app/routes/_auth.login.tsx → GET /login (_auth is a
pathless layout
— no URL effect)
app/routes/api.users.ts → exports drive verbs
app/routes/$.tsx → catch-all
Verb detection:
.tsx / .jsx files render a page — emit GET.
Files exporting loader add GET (or keep it).
Files exporting action add POST / PUT / PATCH / DELETE
(Remix dispatches every non-GET to action at runtime;
we surface the full set so downstream tooling can fan out).
Resource-only .ts / .js files without loader or
action are skipped — they're not handler routes.
Out of scope for this first cut:
Per-handler request-helper scanning. Remix loaders / actions
receive { request, params, context } — accurate read
tracking needs cross-call value flow. Path placeholders
still surface via the optimizer.
Optional segments ($lang) (translated to a literal lang
today; Remix folds optionality into a wildcard match that's
hard to represent without per-route alternatives).
The legacy v1 nested-folder convention — v2 flat is what
the toolchain has been on since Remix 1.15 / Remix 2.