Customize Remix route convention
Tags: remix, router
Published: 2022-09-06 09:56:25


I love [Remix](https://remix.run) for so many reasons. Whether its way of managing data fetching, its forms closer to pure html, or its nested routes.
One thing I particularly like is its customizable file system routing convention.
As you will see in this post, it is possible to define your own routing convention.

## Remix flat routes

I discovered this possibility through the publication of an npm package [remix-flat-routes](https://github.com/kiliman/remix-flat-routes)
To be short it is enough to modify the configuration file **remix.config.js**

## Remix feature routes
Personally I like to organize my files by feature. The problem with the previous conventions is that all the files present in the routes folder are effective routes. That's not what I want. 
I want to be able to add method files for example in a feature but not considering this file as a route.
I wrote my own package for this convention: [@remix-routes/feature](https://github.com/simonboisset/remix-routes/tree/main/packages/feature)
The routes will only be files in a folder or nested folder named **routes** if there is a folder in a routes folder then its page will be its **index.tsx** and its nested pages will be in a subfolder **routes** . 
For more details, see the [documentation](https://github.com/simonboisset/remix-routes/tree/main/packages/feature).
To use it we define this convention in **remix.config.js**
```javascript
const { defineFeatureRoutes } = require('@remix-routes/feature');
module. exports = {
  ignoredRouteFiles: ['**/*'],
  routes: async(defineRoutes) => {
    return defineFeatureRoutes('app', 'routes', 'routes', defineRoutes);
  },
};
```
## Customize your own convention

If you want to write your own convention I will explain you how to do it now.

### Remix routes
To simplify remix routes convention writing I published a package [@remix-routes/core](https://github.com/simonboisset/remix-routes/tree/main/packages/core)

I will teach you how to use it then I will explain how it works.

### Setup

First install this package
```bash
yarn add -D @remix-routes/core
```

Then customize your **remix.config.js**

```javascript
const { defineRemixRoutes } = require('@remix-routes/core');
module. exports = {
  ignoredRouteFiles: ['**/*'],
  routes: async(defineRoutes) => {
    return defineRemixRoutes(appDir, routesDir, defineRoutes, myConvention);
  },
};
```

**appDir** is the name of remix app folder. Default is 'app'.

**routesDir** is the name of the routes folder. Default is 'routes'.

**myConvention** will be your custom function that take a list of file's names in the routes folder and return an object with route's ids as keys and files used for these routes as values.

### Define your convention

Create your convention function which will create an Object  **files** with route ids keys and file names values.

```ts
const myConvention = (routesDir: string, outletDir: string) => (filesList: string[]) => {
  let files: Record<string, string> = {};

  for (const file of filesList) {
    if (isRouteModuleFile(file) && isRouteFile(file, outletDir)) {
      let routeId = createRouteId(path.join(routesDir, file), outletDir);
      if (!files[routeId]) {
        files[routeId] = path.join(routesDir, file);
      } else {
        console.error('[Define routes] routeId is already defined :', routeId);
      }
    }
  }

  return files;
};
```

### Define which file is a route

```ts
import { stripFileExtension } from '@remix-routes/core';

function isRouteFile(filename: string, outletDir: string): boolean {
  const path = filename.split('/');
  if (path.length === 1) {
    return true;
  }
  if (path.length === 2 && stripFileExtension(path[1]) === 'index') {
    return true;
  }
  if (path[1] === outletDir && path[0] !== 'index') {
    return isRouteFile(path.slice(2).join('/'), outletDir);
  }

  return false;
}
```
**stripFileExtension** return the file name without extension.
 
### Create route id with file name

```ts
import { normalizeSlashes, stripFileExtension } from '@remix-routes/core';

function createRouteId(file: string, outletDir: string) {
  let path = normalizeSlashes(stripFileExtension(file));

  if (path.length > 2 && path[path.length - 1] === 'index') {
    if (path[path.length - 2] === outletDir) {
      path.splice(-2, 1);
    } else {
      path.splice(-1, 1);
    }
  }
  path = path.filter((name, i) => i === 0 || name !== outletDir);

  return path.join('/');
}

```
**normalizeSlashes** return an array of his router path.

Example :
'routes/auth/login' => ['routes', 'auth', 'login']
'routes/auth.logout' => ['routes', 'auth', 'logout']

## Explanations

Finally we will see how it happens in detail.
The routes property of the remix configuration expects a method that takes in parameters a method that allows to define its routes. 
We will have to call this method each time we want to define a route for a file.
The main idea is to browse the folders to list all the files to include with the url that will be assigned to them and then to call the **defineRoutes** method to declare it for the configuration.

**@remix-routes/core** will visites all filles in the routes folder and list them for your convention callback. Then it will get your convention result object to define remix routes and nested routes based on your route ids.

## Conclusion
I hope you like this article.
Now that you know how to define your own convention, it's up to you to create your own.