Remix remix-project-layout
Describe the ideal layout of a Remix application, including canonical directories, route ownership, naming conventions, and file locations on disk. When asked to bootstrap that layout in a new directory, run the bundled TypeScript script.
git clone https://github.com/remix-run/remix
T=$(mktemp -d) && git clone --depth=1 https://github.com/remix-run/remix "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/remix-project-layout" ~/.claude/skills/remix-run-remix-remix-project-layout && rm -rf "$T"
skills/remix-project-layout/SKILL.mdRemix Project Layout
Use this skill when defining, reviewing, or bootstrapping the on-disk layout of a Remix application.
This skill is about structure and conventions. It defines where code belongs, how route ownership maps to files on disk, and how a Remix app should be organized as it grows. When the user wants a new app scaffolded, run the bundled script instead of recreating the starter files by hand.
Root Layout
Use these root directories consistently:
for runtime application codeapp/
for database artifacts such as migrations and SQLite filesdb/
for static files served as-ispublic/
for shared test helpers, fixtures, and cross-app integration coveragetest/
for runtime scratch files such as uploads, caches, or local session filestmp/
App Layout
Inside
app/, organize code by responsibility:
for client entrypoints and client-owned behaviorassets/
for route-owned handlers and route-local UIcontrollers/
for schema, queries, persistence setup, and runtime data initializationdata/
for request lifecycle concerns such as auth, sessions, database injection, and uploadsmiddleware/
for shared cross-route UI primitivesui/
for genuinely cross-layer runtime helpersutils/
for the route contractroutes.ts
for router setup and route wiringrouter.ts
Placement Precedence
When code could plausibly live in more than one place, use this order of precedence:
- Put code in the narrowest owner first.
- If it belongs to one route, keep it with that route.
- If it is reused across route areas but is still UI, move it to
.app/ui/ - If it is part of request lifecycle setup, keep it in
.app/middleware/ - If it is schema, query, persistence, or startup data logic, keep it in
.app/data/ - Use
only when the code is genuinely cross-layer and does not clearly belong to one of the other app layers.app/utils/
Prefer moving code to a narrower owner over introducing generic shared buckets.
Route Ownership
The disk layout should make it possible to start from a route key in
app/routes.ts and find the
implementation immediately.
Flat Leaf Routes
Use a flat file in
app/controllers/ when a route is implemented by one exported BuildAction.
Examples:
->routes.homeapp/controllers/home.tsx
->routes.aboutapp/controllers/about.tsx
->routes.searchapp/controllers/search.tsx
->routes.uploadsapp/controllers/uploads.tsx
Flat leaf route files are self-contained. If a helper component is used only by that route, keep it in the same module.
Controller Folders
Use a folder with
controller.tsx when the route is implemented by a Controller, owns nested
child routes, or owns multiple actions such as index, action, show, or update.
Examples:
->routes.contactapp/controllers/contact/controller.tsx
->routes.authapp/controllers/auth/controller.tsx
->routes.accountapp/controllers/account/controller.tsx
->routes.cartapp/controllers/cart/controller.tsx
Nested Route Objects
If a route is nested in
app/routes.ts, mirror that nesting on disk.
Examples:
->routes.auth.loginapp/controllers/auth/login/controller.tsx
->routes.account.settingsapp/controllers/account/settings/controller.tsx
->routes.account.ordersapp/controllers/account/orders/controller.tsx
->routes.cart.apiapp/controllers/cart/api/controller.tsx
->routes.admin.usersapp/controllers/admin/users/controller.tsx
Shared UI
If UI is reused across route areas, it belongs in
app/ui/, not under app/controllers/.
Examples:
app/ui/document.tsxapp/ui/layout.tsxapp/ui/form-field.tsxapp/ui/restful-form.tsx
Only keep a component inside a controller folder when that UI is owned by that route or controller feature.
Route-Local UI
If a page component or helper is used only by one action or controller feature, keep it next to the owning route.
Examples:
app/controllers/contact/page.tsxapp/controllers/auth/login/page.tsxapp/controllers/admin/books/form.tsxapp/controllers/books/index-page.tsx
For flat leaf actions, route-local UI lives in the same file as the action.
Promotion Rule
If a route starts as a flat leaf file and later grows child routes or multiple actions, promote it from:
app/controllers/home.tsx
to:
app/controllers/home/controller.tsx
Do this only when the route actually needs it. Do not create controller folders preemptively for every leaf route.
Naming Conventions
Use predictable file names so route ownership is obvious without opening files:
for controller entrypointscontroller.tsx
for a single controller-owned page modulepage.tsx
,index-page.tsx
,show-page.tsx
, andedit-page.tsx
for resource-style controller UIform.tsx- flat action files named after the route key, such as
,home.tsx
,about.tsx
, orsearch.tsxuploads.tsx - colocated tests named after the route owner, such as
orhome.test.tscontroller.test.ts
Do not invent one-off naming schemes when an existing convention already fits.
Bootstrap
When the user wants this layout scaffolded into a new directory, run:
node skills/remix-project-layout/scripts/bootstrap_remix_application.ts <target-dir>
Optional flags:
to override the generated app name--app-name <name>
to override the default--remix-version <version>
versionremix
to write into a non-empty target directory--force
The script generates the starter app, including
README.md, route handlers, shared UI, test
helpers, and the root directory structure described in this skill.
Anti-Patterns
- Do not create
as a generic dumping ground.app/lib/ - Do not create
as a second shared UI bucket whenapp/components/
already owns that role.app/ui/ - Do not put shared cross-route UI in
.app/controllers/ - Do not put route-owned page modules in
.app/ui/ - Do not put middleware, session, auth, or database lifecycle helpers in
when they belong inapp/utils/
.app/middleware/ - Do not put schema, query, or database setup code in
when it belongs inapp/utils/
.app/data/ - Do not create folders for simple leaf actions unless they are real controllers.
- Do not split solo actions across multiple files.
- Do not create vague files like
,helpers.ts
, orcommon.ts
unless the name is truly accurate for the module's ownership.misc.ts