Viewing docs for an older release. View latest
On this page

Route File Naming

Setting up routes in Remix is as simple as creating files in your app directory. These are the conventions you should know to understand how routing in Remix works.

Please note that you can use either .js, .jsx or .tsx file extensions depending on whether or not you use TypeScript. We'll stick with .tsx in the examples to avoid duplication.

Root Route

app/
├── routes/
└── root.tsx

The file in app/root.tsx is your root layout, or "root route" (very sorry for those of you who pronounce those words the same way!). It works just like all other routes:

Basic Routes

Any JavaScript or TypeScript files in the app/routes/ directory will become routes in your application. The filename maps to the route's URL pathname, except for index.tsx which maps to the root pathname.

app/
├── routes/
│   ├── about.tsx
│   └── index.tsx
└── root.tsx
URL Matched Route
/ app/routes/index.tsx
/about app/routes/about.tsx

The default export in this file is the component that is rendered at that route and will render within the <Outlet /> rendered by the root route.

Dynamic Route Parameters

app/
├── routes/
│   ├── blog/
│   │   ├── $postId.tsx
│   │   ├── categories.tsx
│   │   └── index.tsx
│   ├── about.tsx
│   └── index.tsx
└── root.tsx
URL Route Matches
URL Matched Route
/blog app/routes/blog/index.tsx
/blog/categories app/routes/blog/categories.tsx
/blog/my-post app/routes/blog/$postId.tsx

Routes that begin with a $ character indicate the name of a dynamic segment of the URL. It will be parsed and passed to your loader and action data as a value on the param object.

For example: app/routes/blog/$postId.tsx will match the following URLs:

  • /blog/my-story
  • /blog/once-upon-a-time
  • /blog/how-to-ride-a-bike

On each of these pages, the dynamic segment of the URL path is the value of the parameter. There can be multiple parameters active at any time (as in /dashboard/:client/invoices/:invoiceId view example app) and all parameters can be accessed within components via useParams and within loaders/actions via the argument's params property:

import type {
  ActionArgs,
  LoaderArgs,
} from "@remix-run/node"; // or cloudflare/deno
import { useParams } from "@remix-run/react";

export const loader = async ({ params }: LoaderArgs) => {
  console.log(params.postId);
};

export const action = async ({ params }: ActionArgs) => {
  console.log(params.postId);
};

export default function PostRoute() {
  const params = useParams();
  console.log(params.postId);
}

Nested routes can also contain dynamic segments by using the $ character in the parent's directory name. For example, app/routes/blog/$postId/edit.tsx might represent the editor page for blog entries.

See the routing guide for more information.

Optional Segments

Wrapping a route segment in parens will make the segment optional.

app/
├── routes/
│   ├── ($lang)/
│   │   ├── $pid.tsx
│   │   ├── categories.tsx
│   │   └── index.tsx
│   └── index.tsx
└── root.tsx
URL Route Matches
URL Matched Route
/categories app/routes/($lang)/categories.tsx
/en/categories app/routes/($lang)/categories.tsx
/fr/categories app/routes/($lang)/categories.tsx
/american-flag-speedo app/routes/($lang)/$pid.tsx
/en/american-flag-speedo app/routes/($lang)/$pid.tsx
/fr/american-flag-speedo app/routes/($lang)/$pid.tsx

Layout Routes

app/
├── routes/
│   ├── blog/
│   │   ├── $postId.tsx
│   │   ├── categories.tsx
│   │   └── index.tsx
│   ├── about.tsx
│   ├── blog.tsx
│   └── index.tsx
└── root.tsx
URL Route Matches
URL Matched Route Layout
/ app/routes/index.tsx app/root.tsx
/about app/routes/about.tsx app/root.tsx
/blog app/routes/blog/index.tsx app/routes/blog.tsx
/blog/categories app/routes/blog/categories.tsx app/routes/blog.tsx
/blog/my-post app/routes/blog/$postId.tsx app/routes/blog.tsx

In the example above, the blog.tsx is a "layout route" for everything within the blog directory (blog/index.tsx and blog/categories.tsx). When a route has the same name as its directory (routes/blog.tsx and routes/blog/), it becomes a layout route for all of the routes inside that directory ("child routes"). Similar to your root route, the parent route should render an <Outlet /> where the child routes should appear. This is how you can create multiple levels of persistent layout nesting associated with URLs.

Pathless Layout Routes

app/
├── routes/
│   ├── __app/
│   │   ├── dashboard.tsx
│   │   └── $userId/
│   │       └── profile.tsx
│   └── __marketing
│   │   ├── index.tsx
│   │   └── product.tsx
│   ├── __app.tsx
│   └── __marketing.tsx
└── root.tsx
URL Route Matches
URL Matched Route Layout
/ app/routes/__marketing/index.tsx app/routes/__marketing.tsx
/product app/routes/__marketing/product.tsx app/routes/__marketing.tsx
/dashboard app/routes/__app/dashboard.tsx app/routes/__app.tsx
/chance/profile app/routes/__app/$userId/profile.tsx app/routes/__app.tsx

You can also create layout routes without adding segments to the URL by prepending the directory and associated parent route file with double underscores: __.

For example, all of your marketing pages could be in app/routes/__marketing/* and then share a layout by creating app/routes/__marketing.tsx. A route app/routes/__marketing/product.tsx would be accessible at the /product URL because __marketing won't add segments to the URL, just UI hierarchy.

Be careful, pathless layout routes introduce the possibility of URL conflicts

Dot Delimiters

app/
├── routes/
│   ├── blog/
│   │   ├── $postId.tsx
│   │   ├── categories.tsx
│   │   └── index.tsx
│   ├── about.tsx
│   ├── blog.authors.tsx
│   ├── blog.tsx
│   └── index.tsx
└── root.tsx
URL Route Matches
URL Matched Route Layout
/blog app/routes/blog/index.tsx app/routes/blog.tsx
/blog/categories app/routes/blog/categories.tsx app/routes/blog.tsx
/blog/authors app/routes/blog.authors.tsx app/root.tsx

By creating a file with . characters between segments, you can create a nested URL without nested layouts. For example, a file app/routes/blog.authors.tsx will route to the pathname /blog/authors, but it will not share a layout with routes in the app/routes/blog/ directory.

Splat Routes

app/
├── routes/
│   ├── blog/
│   │   ├── $postId.tsx
│   │   ├── categories.tsx
│   │   └── index.tsx
│   ├── $.tsx
│   ├── about.tsx
│   ├── blog.authors.tsx
│   ├── blog.tsx
│   └── index.tsx
└── root.tsx
URL Route Matches
URL Matched Route Layout
/ app/routes/index.tsx app/root.tsx
/blog app/routes/blog/index.tsx app/routes/blog.tsx
/somewhere-else app/routes/$.tsx app/root.tsx

Files that are named $.tsx are called "splat" (or "catch-all") routes. These routes will map to any URL not matched by other route files in the same directory.

Similar to dynamic route parameters, you can access the value of the matched path on the splat route's params with the "*" key.

import type {
  ActionArgs,
  LoaderArgs,
} from "@remix-run/node"; // or cloudflare/deno
import { useParams } from "@remix-run/react";

export const loader = async ({ params }: LoaderArgs) => {
  console.log(params["*"]);
};

export const action = async ({ params }: ActionArgs) => {
  console.log(params["*"]);
};

export default function PostRoute() {
  const params = useParams();
  console.log(params["*"]);
}

Escaping special characters

Because some characters have special meaning, you must use our escaping syntax if you want those characters to actually appear in the route. For example, if I wanted to make a Resource Route for a /sitemap.xml, I could name the file app/routes/[sitemap.xml].tsx. So you simply wrap any part of the filename with brackets and that will escape any special characters.

Note, you could even do `app/routes/sitemap[.]xml.tsx` if you wanted to only wrap the part that needs to be escaped. It makes no difference. Choose the one you like best.

v2 Route Convention

The Route file naming convention is changing in v2 to make file organization simpler and make co-location of modules used by your routes simpler. You can opt-in to the new convention today, see the Route Convention v2 page but you don't need to today, or ever if you don't want to.

Docs and examples licensed under MIT