Keep your routes centralized


Often, in React projects, I notice that developers usually write each route of the application as follows:

<Router>
  <Route to="todo/create" component={CreatePage} />
  <Route to="todo/:id/duplicate" component={DuplicatePage} />
  <Route to="todo/:id/edit" component={EditPage} />
  <Route to="todo/:firstTodo/:secondTodo/merge" component={MergePage} />
  // ...
</Router>

And if it's necessary to refer to a route, we need to repeat it everywhere:

<Link to={`todo/${todoId}/duplicate`} />

There's nothing wrong with this approach for small projects that demand little maintenance. However, as the project scales, more routes are added and more references to these routes can be made on several parts of the project, causing a few issues:

  • It's easy to let a typo go unnoticed.
  • if the route is a little more complex and we need to update it, it's not so simple to just search and replace using an IDE.
  • We have to guarantee that we have enough tests covering every place where these routes are referenced, otherwise our flow can break.

An approach that I usually take is to keep all the routes in a constants file:

const ROUTES = {
  CREATE: "todo/create",
  DUPLICATE: "todo/:id/duplicate",
  EDIT: "todo/:id/edit",
  MERGE: "todo/:firstTodoId/:secondTodoid/merge",
}

Then use these constants every time I need to reference a route:

<Router>
  <Route to={ROUTES.CREATE} component={CreatePage} />
  <Route to={ROUTES.DUPLICATE} component={DuplicatePage} />
  <Route to={ROUTES.EDIT} component={EditPage} />
  <Route to={ROUTES.MERGE} component={MergePage} />
</Router>

For routes with slugs we can take advantage of an auxiliary method:

const buildUrl = (url, ...params) => {
  let result = url

  params.forEach(param => {
    result = result.replace(/:\w+/, param)
  })

  return result
}
<Link to={buildUrl(ROUTES.DUPLICATE, todoId)} />

This way, if we have to make an update in the application's routes, the process becomes less complex.