Skip to content

Project Structure

As your application grows beyond a single file, you need a clear way to organize your code. This guide explains the recommended project structure for Remix V3 applications and the conventions behind it.

Overview

Here is what a typical Remix V3 project looks like:

my-app/
├── server.ts              # Server entry point
├── package.json
├── tsconfig.json
├── public/                # Static files (CSS, images, etc.)
│   ├── styles.css
│   └── images/
├── app/
│   ├── router.ts          # Router configuration with middleware
│   ├── routes.ts          # Route definitions (URL patterns)
│   ├── controllers/       # Route handlers (actions)
│   │   ├── home.tsx
│   │   ├── about.tsx
│   │   └── auth/
│   │       └── controller.tsx
│   ├── middleware/         # Custom middleware
│   │   ├── session.ts
│   │   └── auth.ts
│   ├── data/              # Database schema, seeds, migrations
│   │   ├── schema.ts
│   │   └── setup.ts
│   ├── ui/                # Shared UI components
│   │   ├── layout.tsx
│   │   └── document.tsx
│   ├── assets/            # Client-side interactive components
│   │   └── entry.tsx
│   └── utils/             # Shared utilities
│       └── render.tsx
└── db/
    └── migrations/        # Database migration files

Let's walk through each part.

Root Files

server.ts

The server entry point. This is the file that starts your application. It creates the HTTP server, wires up the router, and begins listening for requests. You saw this file in the Quick Start guide.

In a well-organized project, server.ts stays small -- it imports the router from app/router.ts and starts the server. The routing logic itself lives elsewhere.

package.json

Tracks your project's dependencies and scripts (like npm run dev). This is standard for any Node.js project.

tsconfig.json

Configures TypeScript for your project. See the Installation guide for the recommended settings.

public/ -- Static Files

The public/ directory holds files that are served directly to the browser without any processing: CSS stylesheets, images, fonts, favicons, and so on. These files are served as-is at their file path. For example, public/styles.css would be accessible at http://localhost:3000/styles.css.

app/ -- Application Code

This is where the bulk of your application lives. Everything inside app/ is organized by purpose.

app/routes.ts -- Route Definitions

This file defines all of your application's URL patterns in one place. A route definition maps a name to a URL pattern:

ts
import { route } from 'remix/fetch-router/routes'

export let routes = route({
  home: '/',
  about: '/about',
  greet: '/hello/:name',
  login: '/auth/login',
  signup: '/auth/signup',
  dashboard: '/dashboard',
})

Having all routes in a single file makes it easy to see every URL your application responds to at a glance. The names (like home, about, greet) are used throughout your code to reference routes in a type-safe way -- if you rename a route, TypeScript will flag every place that needs to be updated.

app/router.ts -- Router Configuration

This file creates the router and connects middleware. It imports routes from routes.ts and wires everything together:

ts
import { createRouter } from 'remix/fetch-router'
import { logger } from 'remix/logger-middleware'
import { routes } from './routes.ts'
import { session } from './middleware/session.ts'

export let router = createRouter({
  middleware: [logger(), session()],
})

Keeping the router configuration separate from route definitions and handlers makes each piece easier to understand and modify independently.

app/controllers/ -- Route Handlers

Controllers contain the functions that handle incoming requests and return responses. Each controller file corresponds to a route or group of related routes.

Use flat files for simple, standalone routes:

controllers/
├── home.tsx
├── about.tsx

Use folders for routes that are more complex or logically grouped:

controllers/
└── auth/
    └── controller.tsx

A controller file exports a handler function:

ts
// app/controllers/home.tsx
export function home() {
  return new Response('<h1>Welcome to Remix!</h1>', {
    headers: { 'Content-Type': 'text/html' },
  })
}

The .tsx extension (instead of .ts) is used when a controller produces HTML, since you may use JSX to build the markup.

app/middleware/ -- Custom Middleware

Middleware functions run before your route handlers. They handle cross-cutting concerns -- behavior that applies across many routes rather than belonging to a single one. Common examples:

  • session.ts -- Reads and writes session data (like "is this user logged in?"). A session is a way to remember information about a visitor across multiple requests.
  • auth.ts -- Checks whether the user is authorized to access a route. Authorization means verifying that someone has permission to do something.

Middleware keeps this shared logic in one place instead of duplicating it in every controller.

app/data/ -- Database and Data Layer

This directory holds everything related to your data storage:

  • schema.ts -- Defines the shape of your data (what tables exist, what columns they have). A schema is a blueprint for your database.
  • setup.ts -- Initializes the database connection and configuration.

If you are not using a database yet, you can skip this directory entirely.

app/ui/ -- Shared UI Components

The ui/ directory contains server-rendered UI components that are shared across multiple pages. These are pieces of HTML that get assembled on the server before being sent to the browser:

  • layout.tsx -- A shared page layout (header, footer, navigation) that wraps your page content.
  • document.tsx -- The HTML document shell (<html>, <head>, <body> tags) that every page uses.

Server-rendered vs. client-side

Components in ui/ run on the server. They generate HTML that is sent to the browser as a finished page. Components in assets/ (below) run in the browser and add interactivity after the page loads.

app/assets/ -- Client-Side Interactive Components

The assets/ directory contains JavaScript that runs in the browser. This is where you put interactive components -- things like dropdown menus, form validation, or any behavior that needs to respond to user actions in real time.

  • entry.tsx -- The client-side entry point. This file is the starting point for any JavaScript that gets sent to the browser.

Not every application needs client-side JavaScript. If your app is server-rendered with plain HTML and forms, you can skip this directory.

app/utils/ -- Shared Utilities

Helper functions used across your application. For example:

  • render.tsx -- A utility for rendering JSX components to HTML response strings.

Put anything here that does not fit neatly into another category.

db/ -- Database Migrations

The db/migrations/ directory holds migration files. A migration is a script that changes your database structure (creating tables, adding columns, etc.) in a controlled, repeatable way. Keeping migrations in a dedicated top-level directory separates infrastructure concerns from application logic.

Conventions Summary

ConventionPurpose
routes.ts defines URL patterns as dataAll URLs visible in one file; type-safe route references
Controllers handle requests and return responsesClear separation between URL patterns and request handling
Middleware adds cross-cutting concernsShared logic (logging, sessions, auth) stays DRY
ui/ for server-rendered componentsReusable HTML assembled on the server
assets/ for client-side interactive componentsBrowser JavaScript kept separate from server code
Flat files for simple routes, folders for complex onesStructure scales naturally with complexity

Starting Small

You do not need all of these directories on day one. Start with just server.ts, app/routes.ts, and a few controllers. Add middleware, UI components, and a data layer as your application grows. The structure is a guide, not a requirement -- adapt it to fit your needs.

Released under the MIT License.