Routing in Next.js – A Complete Beginner‘s Guide

Next.js has rapidly become one of the most popular React frameworks since its initial release in 2016. As of 2022, it sees over 1.5 million downloads per week. A big part of its appeal is the intuitive file-based routing system. In this in-depth guide, we‘ll explore everything you need to know about routing in Next.js to take full advantage of its capabilities.

What Makes Next.js Special?

Before we dive into the specifics of routing, let‘s take a moment to understand what makes Next.js special and why you might choose to use it over plain React or another framework.

Some of the key benefits of Next.js include:

  • Automatic code splitting for faster page loads
  • Server-side rendering for better performance and SEO
  • A simple and intuitive page-based routing system
  • An API routes feature for building fullstack applications
  • Static site generation and incremental static regeneration
  • Easy deployment to Vercel and other platforms

Perhaps most importantly, Next.js aims to provide a great developer experience with sensible defaults, while maintaining full flexibility to customize as needed. This balance of convention and configuration makes it a compelling choice.

Code Splitting and Performance

One of the standout features of Next.js is its automatic code splitting. Code splitting is a technique where the JavaScript bundle for a page is split into smaller chunks. This allows for loading only the necessary code for the initial page load, significantly speeding up the load time.

Consider these statistics:

  • The average webpage is now over 2MB in size, with JavaScript being the largest contributor.
  • A 1 second delay in page load can result in a 7% reduction in conversions.
  • 53% of mobile site visits are abandoned if pages take longer than 3 seconds to load.

Next.js‘s automatic code splitting addresses these issues by ensuring that each page only loads the JavaScript it needs. This is a significant improvement over traditional bundling, where a single large JavaScript file is loaded on every page, even if much of the code isn‘t needed.

File-System Based Routing in Next.js

At the core of Next‘s routing system is the concept of file-system based routing. This means that the files and directories you create under the pages directory automatically become the routes for your application.

For example, if you create a file at pages/about.js, it will be accessible at the /about route. If you create a file at pages/blog/first-post.js, it will be accessible at the /blog/first-post route.

This approach has several benefits over configuration-heavy approaches like those used in Express.js or older versions of Angular:

  1. It‘s simple and intuitive. The directory structure directly maps to the URL structure.
  2. It‘s easy to understand an existing Next.js application, as the directory structure tells you a lot about the available pages and routes.
  3. It reduces the need for configuration files, keeping your codebase lean and focused.
  4. It encourages a modular, component-based architecture.

Index Routes

In Next.js, files named index.js have a special meaning. They represent the "root" of a directory. So pages/index.js maps to the / route, while pages/blog/index.js would map to the /blog route.

This is a common convention in web development and makes the routing structure clean and intuitive.

Nested Routes

You can create nested routes by simply nesting your files and directories under the pages directory. If we wanted to create a /blog/first-post/comments route, we could do so with the following structure:

pages/
  blog/
    first-post/
      comments.js

This makes it easy to create complex, nested application structures while keeping your code organized.

Dynamic Routes

One of the most powerful features of Next.js routing is the ability to create dynamic routes. Dynamic routes allow you to create pages that can render different content based on URL parameters.

To create a dynamic route in Next.js, you use square brackets in the file name. For example, pages/post/[id].js would match routes like /post/1, /post/2, etc.

Inside a dynamic route, you can access the actual value of the dynamic segment using the useRouter hook from next/router.

import { useRouter } from ‘next/router‘;

export default function Post() {
  const router = useRouter();
  const { id } = router.query;

  // ...
}

Catch-All Routes

Sometimes you may want a route to match multiple dynamic segments. For this, you can use a catch-all route. To create a catch-all route, you use three dots inside the square brackets: [...slug].js.

A file named pages/post/[...slug].js would match /post/a, /post/a/b, /post/a/b/c, and so on.

Inside a catch-all route, the slug parameter will be an array containing all the matched segments.

Optional Catch-All Routes

You can also mark a catch-all route as optional by including the parameter in double brackets: [[...slug]].js.

This will match /post, /post/a, /post/a/b, and so on. The difference from a regular catch-all route is that it will also match the base route.

Complex Dynamic Patterns

You can combine these dynamic routing features to create very powerful and expressive routing patterns. For example:

pages/
  post/
    [id].js
    [id]/
      comments.js
      [commentId].js
  user/
    [userId]/
      post/
        [postId].js

This structure would allow for routes like:

  • /post/1 – A specific post
  • /post/1/comments – Comments for a specific post
  • /post/1/2 – A specific comment on a specific post
  • /user/1/post/1 – A specific post for a specific user

The possibilities are nearly endless!

Linking Between Pages

Of course, having routes is only half the story. We also need a way to navigate between them. In Next.js, this is accomplished using the Link component.

The Link component is a wrapper around HTML anchor tags that enables client-side route transitions. This means that when a user clicks on a Link, Next.js will intercept the click, fetch the necessary JavaScript code for the new page (if it hasn‘t been loaded already), and then render the new page – all without a full browser refresh.

Here‘s a simple example:

import Link from ‘next/link‘;

export default function Navigation() {
  return (
    <nav>
      <Link href="/">
        <a>Home</a>
      </Link>
      <Link href="/about">
        <a>About</a>
      </Link>
    </nav>
  );
}

Programmatic Navigation

In addition to using the Link component for declarative navigation, you can also navigate programmatically using the next/router module.

The router object provides methods like push, replace, and prefetch that you can use to navigate to new pages, replace the current page in the browser history, or prefetch the JavaScript for a page in the background.

Here‘s an example of navigating programmatically after an event:

import { useRouter } from ‘next/router‘;

export default function MyButton() {
  const router = useRouter();

  const handleClick = () => {
    router.push(‘/about‘);
  };

  return <button onClick={handleClick}>Go to About</button>;
}

Shallow Routing

By default, navigating to a new URL in Next.js will trigger a full page refresh, including re-running any data fetching methods like getStaticProps or getServerSideProps. However, sometimes you may want to change the URL without triggering a full refresh. This is called shallow routing.

Shallow routing is useful for creating smooth, app-like experiences where the URL updates to reflect client-side state changes without a full refresh. Some examples where you might use shallow routing:

  • Updating a search query parameter without reloading the full page
  • Changing a tab or accordion without fetching new data
  • Updating a pagination parameter

To do a shallow route change, you can pass the shallow: true option to the push or replace method of the next/router.

import { useRouter } from ‘next/router‘;

export default function MyComponent() {
  const router = useRouter();

  const handleClick = () => {
    router.push(‘/?counter=10‘, undefined, { shallow: true });
  };

  // ...
}

Other Performance Optimizations

Next.js‘s routing system is closely tied to several other performance optimizations that make your application faster and more efficient:

  1. Automatic Prefetching: By default, Next.js automatically prefetches the JavaScript for pages that are linked with the Link component. This means that when a user clicks on a link, the code for the destination page is already loaded, making the transition nearly instant.

  2. Dynamic Imports: Next.js supports ES2020 dynamic import() syntax, which allows you to load JavaScript modules dynamically and on-demand. This can be used in conjunction with routing to load code only when it‘s needed, reducing the initial bundle size.

  3. Optimized Bundling: Next.js uses webpack under the hood with a highly optimized configuration. This includes features like tree-shaking to remove unused code, and code splitting based on pages and dynamic imports.

These optimizations work together with the routing system to create applications that are fast, efficient, and provide a great user experience.

Comparison to Other Frameworks

While Next.js‘s file-system based routing is very powerful, it‘s not the only approach to routing in React applications. Here‘s a quick comparison to some other popular options:

  • React Router (Create React App): React Router is a popular third-party library for routing in React. It uses a declarative approach where routes are defined as components. While powerful, it can become complex for larger applications and doesn‘t provide some of Next.js‘s advanced features out of the box.

  • Vue Router (Nuxt.js): Vue Router is the official routing library for Vue.js and is used by Nuxt.js (Vue‘s equivalent to Next.js). It uses a similar declarative approach to React Router. Nuxt.js layers a file-system based convention on top of Vue Router, similar to Next.js.

  • Angular Router: Angular has its own powerful and feature-rich routing system. It uses a declarative approach where routes are defined in a configuration file and mapped to components. Angular‘s routing has advanced features like lazy-loading and route guards, but can have a steeper learning curve compared to Next.js‘s approach.

In general, Next.js‘s routing system strikes a balance between simplicity and power. The file-system based approach is easy to understand and get started with, while features like dynamic routes and shallow routing allow for building complex, performant applications.

Real-World Examples

To see the power of Next.js‘s routing in action, let‘s look at a couple of real-world examples:

  1. Hulu: Hulu‘s web application is built with Next.js. They leverage dynamic routes to generate pages for each TV show and movie, and shallow routing to smoothly navigate between episodes without a full page refresh.

  2. Twitch: Twitch uses Next.js for their web application. They use dynamic routes for streamer profile pages and game directory pages. They also leverage Next.js‘s API routes feature for certain API endpoints.

  3. Ticketmaster: Ticketmaster uses Next.js for their event pages. They use dynamic routes to generate a unique page for each event, and shallow routing for smooth navigation between dates and ticket types.

These are just a few examples of how Next.js‘s routing system is being used in large-scale, high-traffic applications.

Conclusion

Routing in Next.js is designed to be intuitive, flexible, and powerful. Whether you‘re creating a simple blog or a complex, dynamic application, Next.js provides the tools you need to model your routes and create great user experiences.

In this guide, we‘ve covered the fundamentals of Next.js routing, from basic page-based routing to dynamic routes, shallow routing, and more. We‘ve also seen how Next.js‘s routing system is closely tied to its other performance optimizations, and how it compares to routing in other popular frameworks.

Remember, this is just the beginning. To truly master routing in Next.js, the best thing you can do is build your own applications and experiment with these features yourself. The official Next.js documentation is a great resource, and the Next.js community is always there to help.

Happy coding!

Similar Posts