Remix v2 Routing Code Review
Loaded by review-remix-v2 (umbrella) to flag routing anti-patterns in app/routes/ modules. See beagle-react:remix-v2-routing for canonical patterns.
Quick Reference
| Issue Type | Reference |
|---|---|
Filename smells (index.tsx, __auth, wrong escape, non-route files) |
references/route-files.md |
Missing <Outlet />, orphan dotted segments, duplicated layout logic |
references/layouts-outlets.md |
Default export on a resource, <Link> without reloadDocument, splat params |
references/resource-routes.md |
react-router-dom imports, __double folders, v1-adapter fallback |
references/v1-holdovers.md |
Missing <Meta />/<Links />/<Scripts />/<ScrollRestoration />, Vite-vs-Classic <LiveReload />, root ErrorBoundary without document shell |
references/root-shell.md |
Scope
This skill flags issues in:
- Files under
app/routes/(filenames, exports, imports, JSX shape) app/root.tsx(document shell, root<Outlet />)remix.config.js(entries that change route discovery:routes(),ignoredRouteFiles,@remix-run/v1-route-convention)- Any module that links to a resource route (
<Link>usage)
Out of scope: loader/action data contracts (covered by remix-v2-data-flow-review), form behavior (remix-v2-forms-review), meta/headers (remix-v2-meta-sessions-review).
Review Checklist
- Index routes named
_index.tsx, notindex.tsx - Pathless layouts use single underscore (
_auth.tsx), not double (__auth/) - Dotted child routes (
users.profile.tsx) have a parent module or use trailing underscore (users_.profile.tsx) - Parent route modules render
<Outlet /> - Splat segments read
params["*"], neverparams.splat/params.rest - Literal dots/special chars escaped with brackets (
sitemap[.]xml.tsx) - Resource routes have no
defaultexport -
<Link>to a resource route usesreloadDocument(or is a plain<a>) - Imports come from
@remix-run/react, notreact-router-dom - Non-route files (CSS, helpers, tests) live in a folder with
route.tsx, or are listed inignoredRouteFiles - Trailing-underscore opt-outs only used when a parent layout actually exists to escape
- Optional segments
($lang)narrowparams.langin the loader (see references/route-files.md)
Valid Patterns (Do NOT Flag)
- Resource route with no default export — this is the convention that makes it a resource route. Never flag.
- Any
_-prefixed pathless layout file without<Outlet />when the module is intentionally a wrapper that renders fixed UI only. Confirm by checking children — if no*.{segment}.tsxsiblings exist, the wrapper-only shape is intentional. - Files prefixed with
_that don't appear in any URL — pathless layouts and_indexare supposed to be hidden from the URL. @remix-run/v1-route-conventionwired up inremix.config.js— legitimate migration adapter, not a smell on its own. Only flag if v1-style files appear without the adapter installed.useLoaderData<typeof loader>()— type annotation, not assertion.- Splat route accessing
params["*"]with bracket syntax — that is the only correct access pattern. - Folder
app/routes/dashboard/withroute.tsxplus sibling.server.ts,.css, component files — co-location is the documented pattern. - Trailing underscore (
concerts_.mine.tsx) when a siblingconcerts.tsxlayout exists and this URL intentionally skips it.
Context-Sensitive Rules
Only flag these issues when the specific context applies:
| Issue | Flag ONLY IF |
|---|---|
index.tsx under app/routes/ |
Project is v2 and @remix-run/v1-route-convention is NOT wired in remix.config.js |
Parent module without <Outlet /> |
Sibling dotted children (parent.*.tsx) exist in app/routes/ |
__double underscore folder |
No v1-convention adapter is installed |
| Trailing-underscore segment | No corresponding parent layout exists (nothing to opt out of) |
| Default export on a module returning non-HTML | The loader/action actually returns a raw Response (PDF, JSON, RSS) |
<Link> to resource route |
Target route has no default export AND <Link> lacks reloadDocument |
Hard gates (before writing findings)
Run these in order. Do not draft user-facing findings until every gate passes for the batch you are about to report.
Location evidence — Pass: Each issue lists a repo path (file under
app/routes/orremix.config.js) and either a line range or a short verbatim quote from the file you read. Filename-only smells must quote the literal filename.Exemption check — Pass: For each issue, state in one line why it is not covered by Valid Patterns (Do NOT Flag). Resource-route flags require explicit evidence of a
defaultexport or a<Link>withoutreloadDocument.Version check — Pass: Confirm the project is Remix v2 (check
package.jsonfor@remix-run/react^2, or presence of v2 flat-routes filenames elsewhere inapp/routes/). If@remix-run/v1-route-conventionis wired inremix.config.js, v1 filenames (__auth/,index.tsx) are intentional — do not flag them as smells.Protocol — Pass: Complete the Pre-Report Verification Checklist in review-verification-protocol for this review.
When to Load References
- Reviewing filenames under
app/routes/→ references/route-files.md - Reviewing parent modules and shared chrome → references/layouts-outlets.md
- Reviewing a module that returns non-HTML, or any
<Link>to such a module → references/resource-routes.md - Reviewing imports or filenames that look like v1 → references/v1-holdovers.md
- Reviewing
app/root.tsx(document shell,<Meta />/<Links />/<Scripts />/<ScrollRestoration />,<LiveReload />on Vite vs Classic Compiler, rootErrorBoundary) → references/root-shell.md
Review Questions
- Does every parent route render
<Outlet />, or is it a deliberate wrapper-only? - Do filenames match the v2 grammar (single
_for pathless,_indexfor index,[...]for escapes)? - Are resource routes free of
defaultexports, and do all<Link>s to them usereloadDocument? - Are all router imports from
@remix-run/react(and server helpers from@remix-run/node)? - If v1 filenames exist, is
@remix-run/v1-route-conventionwired up — or are they accidental?
Additional Documentation
- Route file naming smells: references/route-files.md —
index.tsx,__doublefolders, wrong escape syntax, dot/underscore confusion, non-route files underapp/routes/. - Layouts and outlets: references/layouts-outlets.md — pathless layout misuse, missing
<Outlet />, orphan dotted children, duplicated layout logic. - Resource routes: references/resource-routes.md — accidental default export,
<Link>withoutreloadDocument, splatparams["*"]access. - v1 holdovers: references/v1-holdovers.md —
react-router-domimports,__authfolders,@remix-run/v1-route-conventionas a deliberate-vs-accidental tell.