SimpleNext.js

How to implement dynamic routing in Next.js

Cover Image for How to implement dynamic routing in Next.js
Marouane Reda
Marouane Reda
If you need to understand the basics of Next.js, i recommend this course. (Disclaimer : this is an affiliate link that may earn me a small commission, but with no extra cost to you if you choose to enroll)

While routing in React applications is generally done by React-router, Next.js comes with built-in, directory-based routing system. We will dive in this post in how static and dynamic routing is achieved in Next.js.

Static Routing

Routing system in Next.js is different from what you have been used to in React by react-router. Routing is directory-based and there is no need to type any code to create your routes. If you look up in your project root, you can find a folder named “pages”. The routing of your application is completely related to this folder and files inside it. The “index.js” file in this folder is your “/” route. So, if you open your browser and type http://localhost:3000 you will see the index.js contents. Consider that we want to create a “/about” route in our application. Simply create a new file in “pages” directory named “about” and create a functional component like this in it:

const About = () => (
  <>
    <div>
      <h2>A place for playing with Next JS! :D</h2>
    </div>
  </>
);

export default About;

And now open http://localhost:3000/about in your browser and you can see your about page rendering nice and our routing works perfectly. If you want to create a route like “/contact/map” for example, you can easily create a directory named “contact” and then a “map.js” file!

Navigating between routes

In Next.js we don’t have any NavLink components to navigate between our routes. And we should not use <a> tag because this will refresh your page completely and you can’t use high performance or the benefits of React’s Virtual DOM! So how do we navigate between our routes in a smooth and modern way? Next.js has a nice API called “next/link” which you can import “Link” from this API and wrap everything you want with it to act as a link for you. First, you should import Link and then wrap your <a> tag with it. Required property of this component is “href”. Let’s create a link to our about page in index.js:

import Link from "next/link";

const Index = () => (
  <>
    <div>
      <Link href="/about">
        <a>About App</a>
      </Link>
    </div>
  </>
);

export default Index;

Dynamic Routing

But what if you have an array of cities and you want to show a description for every city in your app? How do we implement a dynamic routing in our Next.js application? Dynamic routing in Next is very simple too! If you wrap your file name with [ ], its behavior will be like “:var” in react-router. For example, if you create a directory named “cities” and inside it create a file named “[city].js”, you can type anything instead of city in your route and your [city].js component will be shown.This approach works for folders too. Let’s test it. Consider we have an array of country and cities in our index.js:

import Link from "next/link";

const cityList = [
  {
    country: "USA",
    city: "NewYork",
  },
  {
    country: "Spain",
    city: "Madrid",
  },
  {
    country: "England",
    city: "London",
  },
];

const Index = () => (
  <>
    <div>
      <Link href="/about">
        <a>About App</a>
      </Link>
    </div>
  </>
);

export default Index;

We want to show a simple sentence like “City is placed in Country” when we click on every city. Create a directory named “[country]” and a file “[city].js” inside it. Our city component can be like this:

import { useRouter } from "next/router";

const CityItem = () => {
  const router = useRouter();
  const { country, city } = router.query; // Destructuring our router object

  return (
    <>
      <h2>
        {city} is placed in {country}
      </h2>
    </>
  );
};

export default CityItem;

We can access the values of our query parameters in our route by useRouter hook, imported from “next/router”. useRouter returns an object of your current route in the location bar and you can access its variables easily! Now we should change our index.js to be able to navigate to our dynamic route:

import Link from "next/link";

const cityList = [
  {
    country: "USA",
    city: "NewYork",
  },
  {
    country: "Spain",
    city: "Madrid",
  },
  {
    country: "England",
    city: "London",
  },
];

const Index = () => (
  <>
    <div>
      <Link href="/about">
        <a>About App</a>
      </Link>
    </div>
    <hr />
    <ul>
      {cityList.map((item, index) => (
        <li key={index}>
          <Link href={`/${item.country}/${item.city}`}>
            <a>
              {item.country}-{item.city}
            </a>
          </Link>
        </li>
      ))}
    </ul>
  </>
);

export default Index;

Awesome! But if you open your developer tools and look into your inspect tab, you see that all of the codes are changing. This is the bad behavior of <a> tag! To avoid this happening in dynamic routing you should pass your route template in “href” property, and the exact route in “as” property. So, change your index.js file like this:

import Link from "next/link";

const cityList = [
  {
    country: "USA",
    city: "NewYork",
  },
  {
    country: "Spain",
    city: "Madrid",
  },
  {
    country: "England",
    city: "London",
  },
];

const Index = () => (
  <>
    <div>
      <Link href="/about">
        <a>About App</a>
      </Link>
    </div>
    <hr />
    <ul>
      {cityList.map((item, index) => (
        <li key={index}>
          <Link as={`/${item.country}/${item.city}`} href="/[country]/[city]">
            <a>
              {item.country}-{item.city}
            </a>
          </Link>
        </li>
      ))}
    </ul>
  </>
);

export default Index;

This works perfectly!