Filesystem Routers & Indexes

Soon, Next.js will support FS-based routes, like Sapper, Nuxt.js, and my own polydev and mdx-site.

But, there’s one design decision I’ve made that differs from the rest: all routes are index-based.

A lot of engineers work from the bottom up.

“I’ll connect to the DB, query for the data I need, expose it as an API, then wire it up to the HTML.”

But, I’ve found it more intuitive to work from the user down. (Because, ultimately, the implementation details don’t matter).

“I’ll create /some/url, hit an API, then return a query from the DB”.

Maybe we can call this approach 404-driven-development.

Since Cool URIs don’t change, designing URL structure is good design.

The Filesystem Looks Like a URL

Even when I git clone a project, I have a habit of making the filesystem look like a URL.

For example, my ~/Projects directory looks like my GitHub repos:

$ tree -L2
├── ericclemmons
│   ├── mdx-site
│   ├── medium-to-markdown
│   ├── node-recorder
│   ├── polydev
│   └── ...
├── webpack-contrib
│   └── npm-install-webpack-plugin
└── zeit
    └── next.js

So it makes sense that projects like UmiJS would find a way to map /users/:id to the filesystem:

+ pages/
  + $post/
    - index.js
    - comments.js
  + users/
  - index.js

In my projects, however, I whitelist index.* files for route creation.

Why Indexes Matter

For a small project, this seems fine:


But, as complexity grows, you’ll find yourself with an underscore smell:


Because every file under pages/ automatically becomes a page, we have to find ways to get these files blacklisted.

When designing URL structure upfront, a whitelist allows our app to grow without side-effects.

For comparison, my suggested structure is:


Benefits to Index-based Routes

  • Statically compiling about/index.js to about/index.html is well-suppored by many static servers at the URL /about.

  • Dependencies of index.js can be called whatever you’d like, without accidentally generating new pages for each file.

  • Customizing the extension allows for more functionality in the future:

    • post/:id/index.GET.js could return /post/123.

    • post/:id/index.POST.js could handle updating /post/123’s content.

    • post/:id/index.*.js could be a fallback for /post/123/* & show a custom 404 page.

Of course, what people choose for denoting parameters (e.g. :, [], $) has to be a valid filename, but the most important goal is to provide an quick, intuitive path from the URL in the browser to the file serving it.