Next.js uses file-based routing. This means it looks in your pages directory and uses the path of any page components it finds to decide what the route for that page should be. Next.js has a few different ways of figuring out what the route for your page should be, so I’ll explain each one.

To demonstrate this, I’ve set up a simple site with a simple component that we’ll be reusing that just displays the current route for a page.

Here’s the component:

export default function RouteName() {
    const { asPath } = useRouter();
    return <div className="mx-auto my-12 text-3xl">Path: {asPath}</div>;
}

It uses the useRouter hook from Next.js to get the current path, with a bit of styling from TailwindCSS.

Index Based Routing

This is the simplest method of routing in Next.js. Any folder that contains an “index.jsx” file with a valid page export will route that folder to that index file.

Here’s our file path:

And here’s our page. I’ve set up a simple component that just displays its current route, and you can see this in the URL above as well.

Nested Routes

Within a folder, any page files will be routed to that path/filename.

To show this, let’s update our previous example to add a nested route. On the left is my new file structure, with the page on the right.

We can still navigate to upmostly/routing through our index route, but now if we navigate to “upmostly/routing/nested”, nested.jsx gets served.

When there’s a conflict, the non-index route will take priority, e.g. in this scenario both “nested/index.jsx” and “nested.jsx” map to the same route.

Navigating to “upmostly/routing/nested” will serve nested.jsx, instead of “nested/index.jsx”

Dynamic Routing with Parameters

Next.js allows you to use brackets to define dynamic routes. The dynamic parts of the route will then be passed to the page as query string parameters.

Let’s update our routes to add a dynamic route.

Here’s our new file structure and our page:

And inside our component:

export default function Name() {
    const { query } = useRouter();
    return (
        <div className="mx-auto my-12 text-3xl">
            <div>Name: {query.name}</div>
            <RouteName />
        </div>
    );
}

Navigating to “routing/users/omari” means Next.js serves us “[name].jsx”, and passes the username to the component as a query string parameter.

These parameters get combined with any actual query string parameters, and they will all be found in the query object. If there’s a conflict, the dynamic route will overwrite any query string parameters.

Multiple Dynamic Parameters

You can nest these parameters with a file inside any number of folders.

I’ve updated our file structure to include a nested route.

And here are the contents of “[lastName].jsx”

export default function FullName() {
    const { query } = useRouter();

    return (
        <div className="mx-auto my-12 text-3xl">
            <div>
                Name: {query.firstName} {query.lastName}
            </div>
            <RouteName />
        </div>
    );
}

Both dynamic parameters get passed to “[lastName].jsx”, and we can access them from there. One important thing to note is that I had to remove the original “[name.jsx]”, so that we did not have two pages matching the same dynamic path.

Catching All Routes

By default, having a dynamic route at “upmostly/routing/nested/[name.jsx]” will only match “upmostly/routing/users/name”, and not for example “upmostly/routing/users/nameA/nameB/nameC”.

You can change this behaviour by adding three dots to the start of your dynamic route. Let’s rewrite our previous example to take advantage of this. Here’s our new route, and the page.

Instead of having to create our nested route manually, we can create a “catch-all” route. Which approach you use might depend on your situation. You may for example choose the first option because you know you want a first name and a last name, or you may choose this option in which how long the user’s name is doesn’t matter.

In this situation, you can match “upmostly/routing/users/name” and “upmostly/routing/users/nameA/nameB/nameC”, but “upmostly/routing/users” will give us a 404 error.

You could add an index file in the same folder if you do want to handle this separately, but Next.js also allows for optional catch-all routes, by adding an extra set of square brackets around the filename.

Let’s update the routing and component to handle this:

And here’s our code:

export default function FullName() {
    const { query } = useRouter();
    const name = query.name ? [...query.name].join(' ') : 'No Name Provided';
    return (
        <div className="mx-auto my-12 text-3xl">
            <div>Name: {name}</div>
            <RouteName />
        </div>
    );
}

Now if you navigate to “upmostly/routing/users”, instead of getting a 404, it sends us to the same component, but the name parameter is undefined. In our scenario, we have to check for this, and I’ve chosen just to swap it for a placeholder name. You could use this however you wish, e.g. serving up a completely different page, or redirecting to another page.

Hopefully, this was a good introduction to routing in Next.js, and its path-based approach. If you liked this article, or if you’re having any issues, feel free to leave a comment below!

Avatar photo
👋 Hey, I'm Omari Thompson-Edwards
Hey, I'm Omari! I'm a full-stack developer from the UK. I'm currently looking for graduate and freelance software engineering roles, so if you liked this article, reach out on Twitter at @marile0n

💬 Leave a comment

Your email address will not be published. Required fields are marked *

We will never share your email with anyone else.