---
title:
  "Standardizing frontend routes and dynamic URL generation in Neeto products"
description:
  "Explore Neeto's best practices for frontend route standardization and dynamic
  URL generation with the buildUrl function"
canonical_url: "https://www.bigbinary.com/blog/standardizing-frontend-routes"
markdown_url: "https://www.bigbinary.com/blog/standardizing-frontend-routes.md"
---

# Standardizing frontend routes and dynamic URL generation in Neeto products

Explore Neeto's best practices for frontend route standardization and dynamic
URL generation with the buildUrl function

- Author: Navaneeth D
- Published: September 24, 2024
- Categories: React

We often benefit from the ability to easily identify which component is rendered
by simply examining the application UI. By consistently defining routes and
mapping them to components, we can easily locate the rendered component by
searching for the corresponding route. This practice also helps us understand
the component's behavior, including when it is rendered and the events leading
up to it.

This blog post explores a standardized approach to defining frontend routes. The
goal is to enhance the searchability of components based on the URL structure.
[Neeto](https://www.neeto.com/) has adopted a structured and hierarchical
approach to defining frontend routes, prioritizing navigational clarity and
ensuring consistency and scalability throughout its application ecosystem. Let's
have a closer look at this structure.

### Structuring the routes

The philosophy behind route structure is to create a clear, hierarchical, and
organized way of defining routes for a web application. Let's understand how, at
Neeto, we follow this philosophy with an example. Given below is the route
definition of a meeting scheduling application like
[NeetoCal](https://www.neeto.com/neetocal).

```jsx
const routes = {
  login: "/login",
  admin: {
    meetingLinks: {
      index: "/admin/meeting-links",
      show: "/admin/meeting-links/:id",
      design: "/admin/meeting-links/:id/design",
      new: {
        index: "/admin/meeting-links/new",
        what: "/admin/meeting-links/new/what",
        type: "/admin/meeting-links/new/type",
      },
    },
  },
};
```

The routes here are organized hierarchically to reflect the logical structure of
the application. Each nested level represents a deeper level of specificity or
functionality. For instance, under the `admin` route, there are further nested
routes for `meetingLinks`, and within `meetingLinks`, there are routes for
specific actions like `index` and `show`. This indicates that the admin panel of
the application includes provisions for listing meeting links and showing
details of individual meeting links.

These routes also follow RESTful principles, whenever possible, by using
descriptive and meaningful path names. The paths indicate the resource being
accessed and the action being performed. For example:

- `index` routes like `/admin/meeting-links` is for listing resources.
- `show` routes like `/admin/meeting-links/:id` is for viewing a specific
  resource.
- Action-specific routes like `/admin/meeting-links/:id/design` is for
  performing actions on a specific resource.

By defining routes in a nested object structure, it becomes clear how routes are
related. This improves readability and maintainability. The nested structure
allows for easy scalability as well. New routes can be added in a logical place
within the hierarchy without disrupting the existing structure. For example, if
a new action needs to be added to meeting-links, it can be easily included under
the appropriate `new` subroute.

String interpolation should be strictly avoided in the path values. Otherwise
they can lead to inconsistencies in route definitions and make searching
difficult.

At Neeto, we have an ESLint rule `routes-should-match-object-path` in
`@bigbinary/eslint-plugin-neeto` which ensures that the path value matches the
key. Let's take a few examples to discuss this ESLint rule.

In the above case we have the key `routes.admin.meetingLinks.index`. The path
for that key is `/admin/meeting-links`. What if I change the path value from
`/admin/meeting-links` to `/admin/meeting-urls`. If we do that then ESLint will
throw an error because now the key will not match with the path.

Since the key has the value `meetingLinks` the path can be either
`meeting-links` or `meeting_links`. But the path can't be `meetinglinks`.
Because then `L` will not be camelcased on the key side and that will throw and
error by ESLint.

Similarly if we have the key `routs.admin.meetingLinks.video` then the path must
be `/admin/meeting-links/video`.

### Usage of index key

Imagine we're enhancing our application by introducing a feature that lists all
available time slots for scheduling meetings with a person. This scenario
requires an `index` action. However, if listing is the sole action within the
`availabilities` context, there's no need to explicitly use the `index` key.
Instead, we can directly use `availabilities` as the key for the path.

```jsx
const routes = {
  // rest of the routes
  admin: {
    availabilities: "/admin/availabilities",
    // rest of the routes
  },
};
```

However, if we plan to support multiple actions under the `availabilities`
scope, we will need to use the `index` key to differentiate between actions.

```jsx
const routes = {
  // rest of the routes
  admin: {
    availabilities: {
      index: "/admin/availabilities",
      show: "/admin/availabilities/:id",
    },
    // rest of the routes
  },
};
```

### Improving searchability

The structured route definitions can significantly enhance the ease of searching
for specific route keys. Let's see how this works in practice.

Assume you are on the `/admin/meeting-links` page of the application, as
indicated by the address bar in the browser. To determine the component
associated with this route follow these steps:

- Generate the key by replacing all forward slashes with periods and convert the
  path to camelCase. We adhere to camelCase for all path keys to ensure
  consistency. Thus, `/admin/meeting-links` becomes `admin.meetingLinks`.

- By examining the page associated with `/admin/meeting-links`, we can determine
  if multiple actions exist under the `meeting-links` scope. If multiple actions
  exists, then append `.index` to the key. If listing is the only action,
  `admin.meetingLinks` will suffice. Let's say there are multiple actions like
  showing details or editing, under the `meeting-links` scope. So we should use
  `admin.meetingLinks.index` as the key for searching.

- Use this formatted key to search in your preferred code editor. This search
  should help you locate the relevant route definitions and associated
  components.

### Avoid nesting for dynamic routes

When dealing with dynamic elements like `:id` in route paths, avoiding nesting
can enhance searchability and maintain consistency across different parts of the
application.

Consider a scenario where we need to manage various aspects of meeting links in
an admin panel. Each meeting link has a unique identifier `:id`, and we want to
create routes for actions like viewing details, designing, and managing members
of these meeting links. Initially, we might be tempted to nest these actions
under `id` as shown below:

```jsx
const routes = {
  // rest of the routes
  admin: {
    // rest of the routes
    meetingLinks: {
      index: "/admin/meeting-links",
      id: {
        show: "/admin/meeting-links/:id",
        design: "/admin/meeting-links/:id/design",
        members: "/admin/meeting-links/:id/members",
      },
    },
    // rest of the routes
  },
};
```

This structure appears logical but has a critical flaw. The goal of structured
routing is to enhance searchability. In this scenario, if a developer sees a
path like `/admin/meeting-links/9482af15-9443-42d1-9b3d-61daeadf6982/design` in
the browser's address bar, they might search for
`routes.admin.meetingLinks.meetingId.design` or
`routes.admin.meetingLinks.mId.design` to find the associated component.
However, neither of these searches would yield relevant results because the
actual key is `routes.admin.meetingLinks.id.design`. This confusion arises
because we allowed for assumptions about the key used for the dynamic part of
the route.

By avoiding the use of dynamic elements in nested object path, we can prevent
this issue. Here's how the corrected nesting should look:

```jsx
const routes = {
  // rest of the routes
  admin: {
    // rest of the routes
    meetingLinks: {
      index: "/admin/meeting-links",
      show: "/admin/meeting-links/:id",
      design: "/admin/meeting-links/:id/design",
      members: "/admin/meeting-links/:id/members",
    },
    // rest of the routes
  },
};
```

This approach ensures that the routes are structured logically and predictably.
Now the developer won't face any confusion since the key
`routes.admin.meetingLinks.design` will not have any dynamic elements in it.

### File structure

To maintain consistency and organization, route definitions should be placed in
a centralized file, `src/routes.js`. The routes should be defined as a constant
and exported as the default export like given below:

```jsx
const routes = {
  login: "/login",
  admin: {
    availabilities: {
      index: "/admin/availabilities",
      show: "/admin/availabilities/:id",
    },
    meetingLinks: {
      index: "/admin/meeting-links",
      show: "/admin/meeting-links/:id",
      design: "/admin/meeting-links/:id/design",
      new: {
        index: "/admin/meeting-links/new",
        what: "/admin/meeting-links/new/what",
        type: "/admin/meeting-links/new/type",
      },
    },
  },
};

export default routes;
```

This approach allows for easy importing and ensures that IntelliSense can
auto-complete the fields, enhancing developer productivity.

### Using the route keys in the application

Usage of the routes within the application is as equally important as defining
them to catalyze searchability. Let's take a look at some of the concepts to
consider while using routes keys in the application.

Firstly, do not destructure keys in the route object when you utilize them in
various parts of the application, like below:

```jsx
const {
  admin: { meetingLinks: index },
} = routes;
history.push(index);
```

It can hamper searchability. Maintain the complete route path as a single key to
ensure clarity and ease of searching.

Secondly, during in-page navigation, we must use the route keys instead of
hardcoded strings. This practice not only enhances searchability but also
minimizes the risk of errors due to typos or incorrect paths.

```jsx
// Navigate to the meeting links index page
history.push(routes.admin.meetingLinks.index);
```

When dealing with dynamic parameters in URLs, we can make use of the `buildUrl`
function from `@bigbinary/neeto-commons-frontend`.
`@bigbinary/neeto-commons-frontend` is a library that packages common
boilerplate frontend code necessary for all Neeto products. The `buildUrl`
function builds a URL by inflating a route-like template string, say
`/admin/meeting-links/:id/design`, using the provided parameters. It allows you
to create URLs dynamically based on a template and replace placeholders with
actual values. Any additional properties in the parameters will be transformed
to snake case and attached as query parameters to the URL.

```jsx
buildUrl(routes.admin.meetingLinks.design, { id: "123" }); // output: `/admin/meeting-links/123/design`
buildUrl(routes.admin.meetingLinks.design, { id: "123", search: "abc" }); // output: `/admin/meeting-links/123/design?search=abc`
```

The `@bigbinary/eslint-plugin-neeto` used within the Neeto ecosystem features a
rule called `use-common-routes` that disallows the usage of strings and template
literals in the path prop of `Route` component and in the `to` prop of `Link`,
`NavLink`, and `Redirect` components. It also prevents the usage of strings and
template literals in `history.push()` and `history.replace()` methods.

### Edge cases to consider

Even with a structured approach, you may encounter scenarios where adhering to
the guidelines is challenging. Let's explore some of these scenarios and how to
ensure minimal searchability in such cases.

#### Routes starting with a dynamic element

We have discussed omitting intermittent dynamic contents in paths. However, when
there are actions with paths beginning with a dynamic element, we can group them
under a meaningful name. While this might hinder searchability, it allows the
code editor to partially match the routes. Consider the below case:

```jsx
const routes = {
  login: "/login",
  calendar: {
    show: "/:slug",
    preBook: {
      index: "/:slug/pre-book",
    },
    cancellationPolicy: "/:slug/cancellation-policy",
    troubleshoot: "/:slug/troubleshoot",
  },
  admin: {
    // Rest of the routes
  },
};

export default routes;
```

Here, `calendar` is the name chosen to group all actions whose paths start with
the dynamic element `:slug`.

#### Routes ending with consecutive dynamic elements

Consider the path `/bookings/:bookingId/:view`. Using `routes.bookings.show` can
cause confusion and omit important information about the dynamic element
`:view`. In such cases, we can use a meaningful name to group the last dynamic
element. Here is how the object would look:

```jsx
const routes = {
  // Rest of the routes
  bookings: {
    views: {
      show: "/bookings/:bookingId/:view",
    },
  },
  admin: {
    // Rest of the routes
  },
};

export default routes;
```

Here, the key `routes.bookings.views.show` is used. By allowing any meaningful
name in place of `views`, we maintain partial searchability.

#### Routes with intermittent consecutive dynamic elements

When paths contain consecutive dynamic elements, such as
`/bookings/:bookingId/:view/time`, we can omit the dynamic elements directly.
Here is how the route would look:

```jsx
const routes = {
  // Rest of the routes
  bookings: {
    time: "/bookings/:bookingId/:view/time",
  },
  admin: {
    // Rest of the routes
  },
};

export default routes;
```

With that, we come to the end of the discussion on structuring frontend routes.
Standardizing frontend routes and dynamic URL generation improves searchability,
maintainability, and scalability. By following a structured, hierarchical
approach and utilizing tools like the `buildUrl` function, developers can
efficiently manage and navigate the application's routing system.

## Links

- [Human page](https://www.bigbinary.com/blog/standardizing-frontend-routes)
