You are on page 1of 1

main

Route File Naming

On this page

Route File Naming

While you can configure routes in


remix.config.js , most routes are created with
this file system convention. Add a file, get a route.

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.

Dilum Sanjaya made an awesome


visualization of how routes in the file system
map to the URL in your app that might help
you understand these conventions.

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!B. It works
just like all other routes, so you can export a
loader , action , etc.

The root route typically looks something like this.


It serves as the root layout of the entire app, all
other routes will render inside the <Outlet /> .

1 import {
2 Links,
3 Meta,
4 Outlet,
5 Scripts,
6 ScrollRestoration,
7 } from "@remix-run/react";
8
9 export default function Root() {
10 return (
11 <html lang="en">
12 <head>
13 <Links />
14 <Meta />
15 </head>
16 <body>
17 <Outlet />
18 <ScrollRestoration />
19 <Scripts />
20 </body>
21 </html>
22 );
23 }

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 is the
index route for the root route.

app/
├── routes/
│ ├── _index.tsx
│ └── about.tsx
└── root.tsx

URL Matched Routes

/ app/routes/_index.tsx

/about app/routes/about.tsx

Note that these routes will be rendered in the


outlet of app/root.tsx because of nested
routing.

Dot Delimiters

Adding a . to a route filename will create a / in


the URL.

app/
├── routes/
│ ├── _index.tsx
│ ├── about.tsx
│ ├── concerts.trending.tsx
│ ├── concerts.salt-lake-city.tsx
│ └── concerts.san-diego.tsx
└── root.tsx

URL Matched Route

/concerts/trending app/routes/concerts.

/concerts/salt-lake-city app/routes/concerts.

/concerts/san-diego app/routes/concerts.

The dot delimiter also creates nesting, see the


nesting section for more information.

Dynamic Segments

Usually your URLs aren't static but data-driven.


Dynamic segments allow you to match segments
of the URL and use that value in your code. You
create them with the $ prefix.

app/
├── routes/
│ ├── _index.tsx
│ ├── about.tsx
│ ├── concerts.$city.tsx
│ └── concerts.trending.tsx
└── root.tsx

URL Matched Route

/concerts/trending app/routes/concerts.

/concerts/salt-lake-city app/routes/concerts.

/concerts/san-diego app/routes/concerts.

Remix will parse the value from the URL and pass
it to various APIs. We call these values "URL
Parameters". The most useful places to access
the URL params are in loaders and actions.

1 export async function loader({


2 params,
3 }: LoaderFunctionArgs) {
4 return fakeDb.getAllConcertsForCity(par
5 }

You'll note the property name on the params


object maps directly to the name of your file:
$city.tsx becomes params.city .

Routes can have multiple dynamic segments, like


concerts.$city.$date , both are accessed on the
params object by name:

1 export async function loader({


2 params,
3 }: LoaderFunctionArgs) {
4 return fake.db.getConcerts({
5 date: params.date,
6 city: params.city,
7 });
8 }

See the routing guide for more information.

Nested Routes

Nested Routing is the general idea of coupling


segments of the URL to component hierarchy and
data. You can read more about it in the Routing
Guide.

You create nested routes with dot delimiters. If


the filename before the . matches another route
filename, it automatically becomes a child route to
the matching parent. Consider these routes:

app/
├── routes/
│ ├── _index.tsx
│ ├── about.tsx
│ ├── concerts._index.tsx
│ ├── concerts.$city.tsx
│ ├── concerts.trending.tsx
│ └── concerts.tsx
└── root.tsx

All the routes that start with


app/routes/concerts. will be child routes of
app/routes/concerts.tsx and render inside the
parent route's outlet_component.

URL Matched Route

/ app/routes/_index.ts

/about app/routes/about.tsx

/concerts app/routes/concerts.

/concerts/trending app/routes/concerts.

/concerts/salt-lake-city app/routes/concerts.

Note you typically want to add an index route


when you add nested routes so that something
renders inside the parent's outlet when users visit
the parent URL directly.

For example, if the URL is /concerts/salt-lake-


city then the UI hierarchy will look like this:

1 <Root>
2 <Concerts>
3 <City />
4 </Concerts>
5 </Root>

Nested URLs without Layout Nesting

Sometimes you want the URL to be nested, but


you don't want the automatic layout nesting. You
can opt out of nesting with a trailing underscore
on the parent segment:

app/
├── routes/
│ ├── _index.tsx
│ ├── about.tsx
│ ├── concerts.$city.tsx
│ ├── concerts.trending.tsx
│ ├── concerts.tsx
│ └── concerts_.mine.tsx
└── root.tsx

URL Matched Route

/ app/routes/_index.ts

/concerts/mine app/routes/concerts_

/concerts/trending app/routes/concerts.

/concerts/salt-lake-city app/routes/concerts.

Note that /concerts/mine does not nest with


app/routes/concerts.tsx anymore, but
app/root.tsx . The trailing_ underscore
creates a path segment, but it does not create
layout nesting.

Think of the trailing_ underscore as the long


bit at the end of your parent's signature, writing
you out of the will, removing the segment that
follows from the layout nesting.

Nested Layouts without Nested URLs

We call these Pathless Routes

Sometimes you want to share a layout with a


group of routes without adding any path
segments to the URL. A common example is a set
of authentication routes that have a different
header/footer than the public pages or the logged
in app experience. You can do this with a
_leading underscore.

app/
├── routes/
│ ├── _auth.login.tsx
│ ├── _auth.register.tsx
│ ├── _auth.tsx
│ ├── _index.tsx
│ ├── concerts.$city.tsx
│ └── concerts.tsx
└── root.tsx

URL Matched Route

/ app/routes/_index.ts

/login app/routes/_auth.log

/register app/routes/_auth.reg

/concerts/salt-lake-city app/routes/concerts.

Think of the _leading underscore as a blanket


you're pulling over the filename, hiding the
filename from the URL.

Optional Segments

Wrapping a route segment in parentheses will


make the segment optional.

app/
├── routes/
│ ├── ($lang)._index.tsx
│ ├── ($lang).$productId.tsx
│ └── ($lang).categories.tsx
└── root.tsx

URL Matched Route

/ app/routes/($lang)._

/categories app/routes/($lang).c

/en/categories app/routes/($lang).c

/fr/categories app/routes/($lang).c

/american-flag-speedo app/routes/($lang)._

/en/american-flag-speedo app/routes/($lang).$

/fr/american-flag-speedo app/routes/($lang).$

You may wonder why /american-flag-speedo is


matching the ($lang)._index.tsx route instead
of ($lang).$productId.tsx . This is because
when you have an optional dynamic param
segment followed by another dynamic param,
Remix cannot reliably determine if a single-
segment URL such as /american-flag-speedo
should match /:lang /:productId . Optional
segments match eagerly and thus it will match
/:lang . If you have this type of setup it's
recommended to look at params.lang in the
($lang)._index.tsx loader and redirect to
/:lang/american-flag-speedo for the
current/default language if params.lang is not a
valid language code.

Splat Routes

While dynamic segments match a single path


segment (the stuff between two / in a URLB, a
splat route will match the rest of a URL, including
the slashes.

app/
├── routes/
│ ├── _index.tsx
│ ├── $.tsx
│ ├── about.tsx
│ └── files.$.tsx
└── root.tsx

URL Ma

/ ap

/beef/and/cheese ap

/files ap

/files/talks/remix-conf_old.pdf ap

/files/talks/remix-conf_final.pdf ap

/files/talks/remix-conf-FINAL-MAY_2022.pdf ap

Similar to dynamic route parameters, you can


access the value of the matched path on the splat
route's params with the "*" key.

app/routes/files.$.tsx

1 export async function loader({


2 params,
3 }: LoaderFunctionArgs) {
4 const filePath = params["*"];
5 return fake.getFileInfo(filePath);
6 }

Escaping Special Characters

If you want one of the special characters Remix


uses for these route conventions to actually be a
part of the URL, you can escape the conventions
with [] characters.

Filename URL

app/routes/sitemap[.]xml.tsx /sitemap.xm

app/routes/[sitemap.xml].tsx /sitemap.xm

app/routes/weird-url.[_index].tsx /weird-url/

app/routes/dolla-bills-[$].tsx /dolla-bill

app/routes/[[so-weird]].tsx /[so-weird]

Folders for Organization

Routes can also be folders with a route.tsx file


inside defining the route module. The rest of the
files in the folder will not become routes. This
allows you to organize your code closer to the
routes that use them instead of repeating the
feature names across other folders.

The files inside a folder have no meaning for


the route paths, the route path is completely
defined by the folder name

Consider these routes:

app/
├── routes/
│ ├── _landing._index.tsx
│ ├── _landing.about.tsx
│ ├── _landing.tsx
│ ├── app._index.tsx
│ ├── app.projects.tsx
│ ├── app.tsx
│ └── app_.projects.$id.roadmap.tsx
└── root.tsx

Some, or all of them can be folders holding their


own route module inside.

app/
├── routes/
│ ├── _landing._index/
│ │ ├── route.tsx
│ │ └── scroll-experience.tsx
│ ├── _landing.about/
│ │ ├── employee-profile-card.tsx
│ │ ├── get-employee-data.server.tsx
│ │ ├── route.tsx
│ │ └── team-photo.jpg
│ ├── _landing/
│ │ ├── footer.tsx
│ │ ├── header.tsx
│ │ └── route.tsx
│ ├── app._index/
│ │ ├── route.tsx
│ │ └── stats.tsx
│ ├── app.projects/
│ │ ├── get-projects.server.tsx
│ │ ├── project-buttons.tsx
│ │ ├── project-card.tsx
│ │ └── route.tsx
│ ├── app/
│ │ ├── footer.tsx
│ │ ├── primary-nav.tsx
│ │ └── route.tsx
│ ├── app_.projects.$id.roadmap/
│ │ ├── chart.tsx
│ │ ├── route.tsx
│ │ └── update-timeline.server.tsx
│ └── contact-us.tsx
└── root.tsx

Note that when you turn a route module into a


folder, the route module becomes
folder/route.tsx , all other modules in the folder
will not become routes. For example:

# these are the same route:


app/routes/app.tsx
app/routes/app/route.tsx

# as are these
app/routes/app._index.tsx
app/routes/app._index/route.tsx

Scaling

Our general recommendation for scale is to make


every route a folder and put the modules used
exclusively by that route in the folder, then put the
shared modules outside of routes folder
elsewhere. This has a couple benefits:

Easy to identify shared modules, so tread


lightly when changing them
Easy to organize and refactor the modules for
a specific route without creating "file
organization fatigue" and cluttering up other
parts of the app

More Flexibility

While we like this file convention, we recognize


that at a certain scale many organizations won't
like it. You can always define your routes
programmatically in remix.config.js .

There's also the remix-flat-routes third-party


package with configurable options beyond the
defaults in Remix.

© Shopify, Inc. Edit


Docs and examples licensed under MIT

You might also like