Pitfalls of File-based Routing

Oct 20, 2023

File-based routing is a popular strategy for frontend frameworks but is one of the contributors to the complexity of the frontend stack.

File-based routing is (somewhat) configuration-free. Simply create a directory or a file.

It has a long precedent in web development — in the past, a vast majority of web servers simply served static content from a filesystem. URLs mapped directly to their path (this is still true for static sites).

But in modern web development, static sites are rarely enough. Library code vs. routing code, dynamic routes, bundling, and code organization all become open questions. Configuration becomes necessary, but unfortunately, magical paths are the only way to encode the configuration.

Not all files map to routes. You don’t want to serve the majority of your files. How do you distinguish files that should be served vs. those not?

Routes can be dynamic. Web servers have to serve dynamic routes. That means variables at the start, middle, or end of a path (e.g., /orders/status/:orderId, /products/:productId/details, or /:userId/profile). How do you represent these in the filesystem?

Lossy upon compilation/bundling. File-based routing information is lost when the code is compiled. The framework that is handling the build step has to convert the routing information to a new representation anyway. This layer of indirection can make it tougher for developers to debug in production (or even downstream tools).

Not read-optimized. The more you deeply nest your routes, the further that code lives from the code it depends on (e.g., non-route library code). Today’s websites have layers of compilation and bundling that didn’t exist in the era of static sites. Layouts, components, and mixes of server and client-side rendering get confusing when configured via the filesystem.

What’s the alternative? Convention in consistency is useful, especially when there’s no true right answer. No-configuration is the simplest configuration (until it’s not). The alternative seems to be going back to declarative configuration (preferably in code) for routes.