Route File Naming

The route file convention is changing in v2. You can prepare for this change at your convenience with the v2_routeConvention future flag. For instructions on making this change see the v2 guide.

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, .ts or .tsx file extensions. We’ll stick with .tsx in the examples to avoid duplication.

Root Route

  1. app/
  2. ├── routes/
  3. └── 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.

  1. app/
  2. ├── routes/
  3. ├── about.tsx
  4. └── index.tsx
  5. └── root.tsx
URLMatched Route
/app/routes/index.tsx
/aboutapp/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

  1. app/
  2. ├── routes/
  3. ├── blog/
  4. ├── $postId.tsx
  5. ├── categories.tsx
  6. └── index.tsx
  7. ├── about.tsx
  8. └── index.tsx
  9. └── root.tsx

URL Route Matches

URLMatched Route
/blogapp/routes/blog/index.tsx
/blog/categoriesapp/routes/blog/categories.tsx
/blog/my-postapp/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:

  1. import type {
  2. ActionArgs,
  3. LoaderArgs,
  4. } from "@remix-run/node"; // or cloudflare/deno
  5. import { useParams } from "@remix-run/react";
  6. export const loader = async ({ params }: LoaderArgs) => {
  7. console.log(params.postId);
  8. };
  9. export const action = async ({ params }: ActionArgs) => {
  10. console.log(params.postId);
  11. };
  12. export default function PostRoute() {
  13. const params = useParams();
  14. console.log(params.postId);
  15. }

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.

  1. app/
  2. ├── routes/
  3. ├── ($lang)/
  4. ├── $pid.tsx
  5. ├── categories.tsx
  6. └── index.tsx
  7. └── root.tsx

URL Route Matches

URLMatched Route
/categoriesapp/routes/($lang)/categories.tsx
/en/categoriesapp/routes/($lang)/categories.tsx
/fr/categoriesapp/routes/($lang)/categories.tsx
/american-flag-speedoapp/routes/($lang)/$pid.tsx
/en/american-flag-speedoapp/routes/($lang)/$pid.tsx
/fr/american-flag-speedoapp/routes/($lang)/$pid.tsx

Layout Routes

  1. app/
  2. ├── routes/
  3. ├── blog/
  4. ├── $postId.tsx
  5. ├── categories.tsx
  6. └── index.tsx
  7. ├── about.tsx
  8. ├── blog.tsx
  9. └── index.tsx
  10. └── root.tsx

URL Route Matches

URLMatched RouteLayout
/app/routes/index.tsxapp/root.tsx
/aboutapp/routes/about.tsxapp/root.tsx
/blogapp/routes/blog/index.tsxapp/routes/blog.tsx
/blog/categoriesapp/routes/blog/categories.tsxapp/routes/blog.tsx
/blog/my-postapp/routes/blog/$postId.tsxapp/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 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

  1. app/
  2. ├── routes/
  3. ├── __app/
  4. ├── dashboard.tsx
  5. └── $userId/
  6. └── profile.tsx
  7. └── __marketing
  8. ├── index.tsx
  9. └── product.tsx
  10. ├── __app.tsx
  11. └── __marketing.tsx
  12. └── root.tsx

URL Route Matches

URLMatched RouteLayout
/app/routes/marketing/index.tsxapp/routes/marketing.tsx
/productapp/routes/marketing/product.tsxapp/routes/marketing.tsx
/dashboardapp/routes/app/dashboard.tsxapp/routes/app.tsx
/chance/profileapp/routes/app/$userId/profile.tsxapp/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

  1. app/
  2. ├── routes/
  3. ├── blog/
  4. ├── $postId.tsx
  5. ├── categories.tsx
  6. └── index.tsx
  7. ├── about.tsx
  8. ├── blog.authors.tsx
  9. ├── blog.tsx
  10. └── index.tsx
  11. └── root.tsx

URL Route Matches

URLMatched RouteLayout
/blogapp/routes/blog/index.tsxapp/routes/blog.tsx
/blog/categoriesapp/routes/blog/categories.tsxapp/routes/blog.tsx
/blog/authorsapp/routes/blog.authors.tsxapp/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

  1. app/
  2. ├── routes/
  3. ├── blog/
  4. ├── $postId.tsx
  5. ├── categories.tsx
  6. └── index.tsx
  7. ├── $.tsx
  8. ├── about.tsx
  9. ├── blog.authors.tsx
  10. ├── blog.tsx
  11. └── index.tsx
  12. └── root.tsx

URL Route Matches

URLMatched RouteLayout
/app/routes/index.tsxapp/root.tsx
/blogapp/routes/blog/index.tsxapp/routes/blog.tsx
/somewhere-elseapp/routes/$.tsxapp/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.

  1. import type {
  2. ActionArgs,
  3. LoaderArgs,
  4. } from "@remix-run/node"; // or cloudflare/deno
  5. import { useParams } from "@remix-run/react";
  6. export const loader = async ({ params }: LoaderArgs) => {
  7. console.log(params["*"]);
  8. };
  9. export const action = async ({ params }: ActionArgs) => {
  10. console.log(params["*"]);
  11. };
  12. export default function PostRoute() {
  13. const params = useParams();
  14. console.log(params["*"]);
  15. }

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.