▼ Umesh Yadav
5 min read
How File-System Based Routing Works in Next.js

Next.js has file-system based routing built-in. You don’t have to explicitly define the path in the router. It’s as easy as adding a file inside the folder and you are done. There are multiple ways you can define a path using the file structure. Anything we add inside the pages directory is served as a path. Definitely there are exceptions like _app.js and _document.js.

There are two types of routes in Next.js

  • API Routes: Any file you put inside pages/api is treated as an API endpoint and is served at https://hostname/api/*.
  • Page Routes: Any file which is inside pages can be served as a web page to the user, it can be a Static Generation or Server-side Rendered. Any file which is inside pages/api is not treated as a web page.

We will go through all of these in detail in this blog post.

Setup a Next.js Project

We will first create a Next.js project for this. We will name this nextjs-routing

npx create-next-app
# or
yarn create next-app

Page Routing

  • Index Routing

Once your project is ready it should look something like this. You can see that there is index.js inside the pages folder. So if you run your application and visit http://localhost:3000 you will see the welcome next.js page, which is mapped to the index.js.

$ tree -I node_modules
├── README.md
├── package.json
├── pages
│   ├── _app.js
│   ├── api
│   │   └── hello.js
│   └── index.js
├── public
│   ├── favicon.ico
│   └── vercel.svg
├── styles
│   ├── Home.module.css
│   └── globals.css
└── yarn.lock

Here is how these pages are mapped to path by router. pages/index.js -> http://[hostname]/ pages/posts/index.js -> http://[hostname]/posts/

Any index.js is mapped to the directory path it belongs to.

  • Nested Routing

Now suppose you want to display deeply nested pages like dashboard settings etc.

pages/about.js -> http://[hostname]/about/ pages/blog/hello.js -> http://[hostname]/blog/hello pages/blog/series/hello.js -> http://[hostname]/blog/series/hello

  • Dynamic Routing

Suppose you don’t have the exact route beforehand and it depends upon API data or some dynamic data. Here is how you can create these routes and map them.

pages/blog/[slug].js -> http://[hostname]/blog/:slug

over here slug can be anything and you can use getStaticPaths to set the dynamic path.

pages/blog/[...slug].js -> http://[hostname]/blog/hello as well as http://[hostname]/blog/series/hello

The above example is to catch all route. Basically, it can map a path to any level of nesting which happens after blog/. If you replace [...slug].js -> [[...slug]].js, it will match to /blog along with other routes like blog/hello, blog/series/hello.

You can’t use catch-all routes with the directory. Eg: pages/blog/[[..slug]]/comments.js will give you Error: Catch-all must be the last part of the URL.

pages/[username]/dashboard -> http://[hostname]/:username/dashboard

In the above example, a username can be a dynamic value. It can be used to generate user-specific static pages.

API Routing

Next.js provides a way to directly create API. You can consume these API from anywhere inside your project. Creating an API is pretty much straight forward, you just have to create a file inside pages/api and it gets mapped to api/*. It won’t get exposed on the client-side and will only be used on the server-side. API routing is very much similar to page routing. Let’s see how this works.

  • Index Routing

The router will automatically route files named index to the root of the directory.

pages/api/blog/index.js -> http://[hostname]/api/blog

  • Nested Routing

Now suppose you want to create a deeply nested API. You can create nested folder structure files and it will get mapped in the same way.

pages/api/users/blog/create.js -> http://[hostname]/api/users/blog/create

  • Dynamic Routing

You can use brackets to define the dynamic routes for your API.

pages/api/blog/[slug].js -> http://[hostname]/api/blog/hello

In the above example, the slug can take any value. You can get this value inside your code.

pages/api/blog/[...slug].js -> http://[hostname]/api/blog/hello or http://[hostname]/api/blog/hello/world

The above example is to catch all routes. All the routes with api/blog/* get routed to this.

pages/api/blog/[[...slug]].js -> http://[hostname]/api/blog or http://[hostname]/api/blog/hello or http://[hostname]/api/blog/hello/world

You can’t use catch-all routes with the directory. Eg: pages/api/blog/[[..slug]]/comments.js will give you Error: Catch-all must be the last part of the URL.

The above example is for optional catch-all routes. Basically it makes the slug optional.

  1. Predefined routes take precedence over dynamic routes. So if you have pages/api/blog/create.js and pages/api/blog/[slug].js then pages/api/blog/create.js gets mapped to http://[hostname]/api/blog/create and pages/api/blog/[slug].js gets mapped to http://[hostname]/api/blog/abc
  1. There is no way to specify which REST calls are allowed on an API route, you have to handle them from your handler code.

Now suppose you want to create api/blogs/:slug/comments/:commentId. People you are familiar with the concept of resources in REST know there are use cases where we might need a nested resource.

pages/api/blogs/[slug]/comments/[commentId].js -> http://[hostname]/api/blogs/:slug/comments/:commentId


Using Next.js routing is pretty much easy and straightforward and has very less overhead. It covers most of the use cases, I really couldn’t think or find a use case which you won’t be able to solve it.

If you liked this post please share it with others so that it can help them as well. You can tag me on Twitter @imumesh18. You can subscribe to my newsletter to read more from me.