Routing is a vital part of building your application. It can touch on some of the most sensitive parts of your application like authentication, data fetching, code bundling, the initial request, the compiler, and almost every interaction your users take to navigate your application. At its core, most applications have API endpoints that can also be considered routing mechanisms that fuel your entire application.
Remix is a full-stack React framework for server-side-rendered applications that is heavily based on the concept of routing, thanks to its foundation built on top of React Router.
In this post, we’ll be looking at Remix’s unique routing system in order to understand it and its concept of flat routes more thoroughly.
Remix contains and makes use of many types of routing principles, approaches, and patterns we can choose from to navigate into our application. Below are some of the built-in routing options available in Remix:
You’ll notice flat routes isn’t mentioned — that’s because it’s a third-party package. The official GitHub repository offers several ways to add this routing approach to our Remix apps today.
Let’s take a look at how to use this library, which was built and is maintained to match Remix’s routing conventions, and use flat routes in an existing Remix application.
There are some filename conventions we need to understand before we implement flat routes in our application.
Use a single underscore instead of a double underscore prefix, as stated below:
_ prefix (for pathless route)
The params prefix remains unchanged:
$ params prefix
Use .
instead of /
to use URL segments:
. path separator
This is not a layout, as it has a double underscore suffix:
_suffix (not a layout no Outlet)
As previously stated, Remix flat routes will be a core feature in a future version of Remix, but today we can enable flat routes via a third-party package using a config option.
There are two approaches to configuring flat routes: flat files and flat folders.
Flat files are suited for simpler applications with smaller numbers of files and folders — there will be no folders, in this case. Our entire application is comprised of files:
📂 our-app/routes ┃ 📜_public.tsx ┃ 📜_public.about.tsx (/about) ┃ 📜_public.feature.tsx (/feature) ┃ 📜 index.tsx (/) ┃ 📜 author.tsx ┃ 📜 author._index.tsx (/author/) ┃ 📜 author.$authorId.tsx (/author/1234) ┃ 📜 author.$authorId_.edit.tsx (/author/1234/edit)
As you can see, we don’t really have folders in our application besides the one containing our entire app.
You may have noticed the author
prefix at the beginning of some filenames. This structure acts as a parent layout for our application. This should match the longest matching prefix, which basically determines which is a parent layout is. In contrast to other filenaming conventions, author._index.tsx
becomes our home route, instead of author.tsx
.We also set the index of the author by placing a underscore after a period in author._index.tsx
.
We can describe the file as not a being parent layout using an underscore (_
), as seen in the /author/1234/edit
path in the above file tree.
There are some limitations with flat files. If there are no folders, we cannot co-locate supporting files with this type of route mechanism — it’s just not suitable for larger applications that serve hundred of routes.
Flat folders are well-suited for large apps where there are lots of files and folders to organize when assembling an application.
The main difference between flat folders and flat files is that instead of routing using filenames, the folder itself is treated as the route name. Take a look at the below flat folder architecture:
📂 our-app/routes ┃ ┣ 📂 index ┃ ┃ ┗ 📜index.tsx ┃ ┣ 📂 _public ┃ ┣ ┗ 📜layout.tsx ┃ ┣ 📂 _public.about ┃ ┣ ┗ 📜index.tsx (/about) ┃ ┣ 📂 _public.feature ┃ ┣ ┗ 📜index.tsx (/feature) ┃ ┣ 📂 author ┃ ┃ ┗ 📜layout.tsx ┃ ┣ 📂 author._index ┃ ┣ ┗ 📜index.tsx (/author) ┃ ┣ 📂 author.$authorId ┃ ┣ ┗ 📜 index.tsx (/author/1234) ┃ ┣ 📂 author.$authorId_.edit ┃ ┣ ┗ 📜index.tsx (/author/1234/edit)
Internally, Remix does not differentiate between routes and layouts: layouts are routes with outlets. So here, the files inside of the folders — e.g., index
and author
—
can have files named index
or layout
, which happen to be the same thing in most cases. They need to be more descriptive, which is why we add aliases to our filenames.
Also of note, the underscore prefix (_
) will sort routes and layouts of the files to the top of the file list during co-location.
Here are some handy aliases used in naming Remix route file names:
index.tsx
→ _index.tsx
route.tsx
→ _route.tsx
Layout.tsx
→ _layout.tsx
page.tsx
→ _page.tsx
Let’s implement our flat routes in a Remix project. We will build a route structure for an application where users can see the landing page first, authenticate, and enter the main dashboard, where they can visit the taskList
route on the dashboard, visit the dynamic ID path for a single task, and visit the root dashboard page itself.
Create a new Remix project and install our flat routes package as dependency there. Initialize the app:
npx remix-create@latest cd our-project npm i -D remix-flat-routes
After these commands, change the main remix.config.ts
file to use the flat routes package. After these changes, our config file looks something like this:
const { flatRoutes } = require('remix-flat-routes') /** @type {import('@remix-run/dev').AppConfig} */ module.exports = { // ignore all files in routes folder to prevent // default remix convention from picking up routes ignoredRouteFiles: ['**/*'], routes: async defineRoutes => { return flatRoutes('routes', defineRoutes) }, // When running locally in development mode, we use the built-in remix // server. This does not understand the vercel lambda module format, // so we default back to the standard build output. server: process.env.NODE_ENV === "development" ? undefined : "./server.ts", serverBuildPath: "api/index.js", // appDirectory: "app", // assetsBuildDirectory: "public/build", // publicPath: "/build/", serverModuleFormat: "cjs", future: { v2_dev: true, v2_errorBoundary: true, v2_headers: true, v2_meta: true, v2_normalizeFormMethod: true, v2_routeConvention: true, }, };
Now, we can start creating files and folders inside of our routes folder. Let’s take a case of flat files as an example.
Below are the files and folders we have created so far:
📂 routes ┃ 📜 _auth.forgot-password.tsx ┃ 📜 _auth.login.tsx ┃ 📜 _auth.signup.tsx ┃ 📜 _landing.about.tsx ┃ 📜 _landing.index.tsx ┃ 📜 app.tsx ┃ 📜 app_.tasklist.$id.tsx ┃ 📜 app_.tasklist.index.tsx ┃ 📜 landing.tsx ┃ 📜 auth.tsx
And here is a table that tracks where each filename directs each user:
Filename | URL served |
---|---|
_auth.forgot-password.tsx |
/forgot-password |
_auth.login.tsx |
/login |
_auth.signup.tsx |
/signup |
_landing.about.tsx |
/about |
_landing.index.tsx |
/ |
app.tsx |
N/A |
app_.tasklist.$id.tsx |
app/tasklist/:id |
app_.tasklist.index.tsx |
app/tasklist |
landing.tsx |
/landing< /td> |
auth.tsx |
/auth |
Let’s introduce the concept of hybrid routing, which helps us use a nested folder structure for our application where we can preserve the co-location feature of flat routes.
In a large application, we may need some parent layouts like _public
, _auth
, or users
. With hybrid routing, we can create a top-level folder for each and nest our routes under them. We can also append +
to a folder name to create a folder but treat it as a flat file.
Below are the files, folders, and naming standards we will create:
📂 hybrid-routing/app/routes ┃ ┣ 📂 _index ┃ ┃ ┗ 📜_index.tsx ┃ ┣ 📂 _public ┃ ┃ ┗ 📜_layout.tsx ┃ ┃ ┗ 📂 pricing ┃ ┃ ┃ ┗ 📜_index.tsx ┃ ┃ ┗ 📂 about ┃ ┃ ┃ ┗ 📜_index.tsx ┃ ┃ ┗ 📂 tasklist+ ┃ ┃ ┃ ┗ 📜$taskId.tsx ┃ ┃ ┃ ┗ 📜$taskId_.create.tsx ┃ ┃ ┃ ┗ 📜$taskId_.edit.tsx ┃ ┃ ┃ ┗ 📜_index.tsx ┃ ┃ ┗ 📂 users ┃ ┃ ┃ ┗ 📂 $userId ┃ ┃ ┃ ┃ ┗ 📜_route.tsx ┃ ┃ ┃ ┗ 📂 $userId_.create ┃ ┃ ┃ ┃ ┗ 📜_route.tsx ┃ ┃ ┃ ┗ 📂 $userId_.edit ┃ ┃ ┃ ┃ ┗ 📜_route.tsx ┃ 📜_public/layout.tsx
Now, in the terminal, let’s visualize things clearly with the following command:
npx remix routes
We should be able to see the same routing style here as we would in JSX if we were working with a React app:
<Routes> <Route file="root.tsx"> <Route index file="routes/_index/_index.tsx" /> <Route file="routes/_public/_layout.tsx"> <Route path="about" file="routes/_public/about/_index.tsx" /> <Route path="pricing" file="routes/_public/pricing/_index.tsx" /> <Route path="layout" file="routes/_public.layout.tsx" /> </Route> <Route path="another" file="routes/another/_route.tsx" /> <Route path="tasklist/:taskId" file="routes/tasklist+/$taskId.tsx" /> <Route path="tasklist/:taskId/create" file="routes/tasklist+/$taskId_.create.tsx" /> <Route path="tasklist/:taskId/edit" file="routes/tasklist+/$taskId_.edit.tsx" /> <Route path="tasklist/" index file="routes/tasklist+/index.tsx" /> <Route path="users/:userId" file="routes/users/$userId/_route.tsx" /> <Route path="users/:userId/create" file="routes/users/$userId_.create/_route.tsx" /> <Route path="users/:userId/edit" file="routes/users/$userId_.edit/_route.tsx" /> <Route path="users/" index file="routes/users/_index/index.tsx" /> </Route> </Routes>
We can clearly see the paths that have been generated for us where we used hybrid routing alongside the flat files convention. In this way, we can use the remix-flat-routes
package to create a more flexible and modern routing structure for our entire application.
Despite the breadth of inbuilt route options in Remix, the flat route package is really handy. Here are some of the advantages of using this package:
In addition to building a new app using flat routes, we can use the library to migrate an existing application to use the flat route convention.
Start by using this command, per the library documentation:
npx migrate-flat-routes <sourceDir> <targetDir> [options] Example: npx migrate-flat-routes ./app/routes ./app/flatroutes --convention=flat-folders NOTE: sourceDir and targetDir are relative to project root Options: --convention=<convention> The convention to use when migrating. flat-files - Migrates all files to a flat directory structure. flat-folders - Migrates all files to a flat directory structure, but creates folders for each route.
We can specify whether we want to migrate our existing app to a flat files or flat folders convention using the above command. Using Remix flat routes should generate the same output as a normal Remix router. We can verify this by running:
npx remix routes
This new approach to routing allows us to build a routing system for fully dynamic applications. We can easily implement and use flat routes inside our existing Remix applications. It ultimately simplifies the existing convention as well as gives you new capabilities, as it’s likely that flat routes will be a default routing option in future versions of Remix.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
One Reply to "Remix flat routes: An evolution in routing"
Why this decision was made by the team?