My beef with Next.js's file-based routing

craftzniaccraftzniac
4 min read

As someone who has mostly worked with Next.js up until 3 weeks ago, I have gradually moved from really liking file-based routing to having mixed feelings about it, especially since I started working with react-router-v7 3 weeks ago.

What is this all about?
File-based routing does not allow you separately define your components and routes hierarchies, and this becomes a real pain once you have to add new routes in a codebase with deeply nested folders and shared layout.tsx files.

The Next.js strategy

In nextjs, by default, component hierarchy and route hierarchy are tightly coupled to folder hierarchy, a.k.a file-based routing. This means you don't have to manually set up routing; want to add a new route? Just add a new folder in the specific subfolder, give it a `page.tsx` file, and you have a new route. Awesome! No dedicated routing file required!

When I first got started in Next.js, I liked this approach and I still do (for certain use cases). However, once you start working on apps that have deeply nested route hierarchies and shared layout files, file-based routing becomes really painful, really quickly.

Consider that I have the following folder structure

app
 |-- blog/
     |-- personal/       /blog/personal
     |-- collab/         /blog/collab
     layout.tsx

In the above folder structure, /blog/personal and /blog/collab both share some components via the layout.tsx file. I intentionally hid the corresponding page.tsx files as they aren't important for this.

Now, say I need to introduce a third route /blog/company and for some reason, the page for this new route does not share most of the components inside the shared layout.tsx file. All of a sudden, I am now forced to rethink the structure of my folders because creating a company/page.tsx file inside blog/ in it's current form, will include the shared components from the layout.tsx file into the rendered /blog/company route and I don't want this.

To solve this problem, I can restructure my folder to look like this, using a route group

app
 |-- blog/
     |-- (group)
         |-- personal/       /blog/personal
         |-- collab/         /blog/collab
         layout.tsx          
     |-- company/          /blog/company

Now, the layout.tsx will only apply to /blog/personal and /blog/collab but not to /blog/company. Problemo solved. This was a simple example, but imagine having to make a change like this in a codebase with folder structure that is deeply nested with multiple levels of shared layout.tsx files. Yikes!

Now, to React router

In react router, by default, our folder structure does not dictate the route paths, neither does it dictate the component hierarchy. Instead, we explicitly define what component is linked to what route inside the routes.ts file. Inside the same file, we can also explicitly define our component hierarchy by nesting route() within route() and layout().

And the best part is that adding a new route, reordering existing routes, or reordering sections of the component hierarchy, does not require reordering our folder structure, and this is cool.

Now, let's see the exact situation we had with Next.js but in react router v7.

//  routes.ts

export default [
    layout("routes/blog/layout.tsx", [                            // shared layout for both /blog/personal and /blog/collab
        route("/blog/personal", "routes/blog/personal.tsx")         //  /blog/personal
        route("/blog/collab", "routes/blog/collab.tsx")             //  /blog/collab    
    ])    
]

To add the /blog/component route, we simply do;

export default [
    layout("routes/blog/layout.tsx", [     // layout.tsx shared by /blog/personal and /blog/collab
        route("/blog/personal", "routes/blog/personal.tsx")         //  /blog/personal
        route("/blog/collab", "routes/blog/collab.tsx")             //  /blog/collab    
    ]),
    route("/blog/company", "routes/blog/company.tsx")             //  /blog/company    
]

All we needed to do to register this new route was just to declare the route outside the shared layout(). It was simple.

In conclusion

Despite how this might all sound, I'm not trying to say that file-based routing is inherently bad. In fact, the routes.ts approach of react-routerv7 has its issues too. One issue that comes to mind is that, since the route hierarchy isn't coupled to folder hierarchy, maintaining a consistent folder structure is now the task of whoever is working on that codebase at any point in time.

If you take anything away from this rant of mine, it should be that every tool/approach has its strengths and weaknesses, and understanding this is key to effectively using that tool/approach for the task at hand.

0
Subscribe to my newsletter

Read articles from craftzniac directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

craftzniac
craftzniac

Just a guy on an adventure to explore the awesome world of software engineering