Deep Dive into Next JS Routing: From Basics to Advanced

This article will explore Next js routing in detail and present a thorough example to demonstrate its functionality. Next.js is a widely used framework for React that facilitates the development of applications with server-side rendering and static generation capabilities. A key feature that enhances the usability and effectiveness of Next.js is its file-based routing system.

What is Next JS Routing

Routing is a critical aspect of any web application, as it allows users to navigate between different pages and content within the app. In a Next.js app, the router plays a central role in shaping and managing the routes that structure your application.

Next js routing system is built on a file-based approach, where the structure of the files and directories inside the pages folder directly defines the routes of your application.

This design eliminates the need for configuring a complex routing system manually, allowing developers to focus on building features rather than setting up routes. Following are some Key Features of File-Based Next JS Routing

  • Simplicity: The Next js routing system is automatically configured based on your file structure, significantly reducing boilerplate code.
  • Automatic Code Splitting: Each page is automatically code-split, meaning only the necessary code for the current page is loaded, leading to faster load times.
  • Dynamic Routes: Next.js allows you to create dynamic routes by defining file names using square brackets, e.g., [id].js.
  • Built-in API Routes: Next.js offers a convenient way to create API routes within the pages/api directory, allowing for server-side logic without the need for a separate backend framework.

Next.js also includes improved support for TypeScript and enhanced performance optimizations, making it easier to develop scalable and maintainable web applications.

Basic Next JS Routing

Next js routing is incredibly straightforward. Each JavaScript or TypeScript file inside the pages directory automatically becomes a route, with the file name (excluding the extension) corresponding to the URL path.

This eliminates the need for a separate router configuration file, which is common in other frameworks like React Router.

How Basic Next JS Routing Works

  • Index Routes: The index.js file inside the pages directory maps to the root of your application (/). If placed in a subdirectory, it maps to the root of that subdirectory.
  • Nested Routes: You can create nested routes by placing files in subdirectories within pages. For example, if you create a file about.js inside a folder called company, it will map to the /company/about route.
  • Automatic TypeScript Support: If you prefer TypeScript, you can create .ts or .tsx files instead of .js, and Next.js will automatically handle the TypeScript configuration.

Example of Basic Next JS Routing

Create a new file named about.js in the pages directory:

// pages/about.js
import Link from 'next/link';

export default function About() {
return (
<div>
<h1>About Page</h1>
<Link href="/">Go back to Home</Link>
</div>
);
}

In this example, navigating to http://localhost:3000/about will display the “About Page”. The Link component from Next.js is used for client-side navigation, which prevents a full page reload, enhancing the user experience.

SEO Benefits

Next js routing system is also SEO-friendly. Since routes are mapped directly to files, Next.js automatically generates a static HTML file for each route during the build process (if the route is statically generated). This makes your pages more easily indexable by search engines, improving your site’s SEO.

Dynamic Next JS Routing

Dynamic Next JS routing allows you to create routes that are determined at runtime, rather than being predefined. This is particularly useful for pages like blog posts or product details, where the content changes frequently, and the URL includes dynamic segments (e.g., /blog/[id]).

Creating Dynamic Routes

To create a dynamic route, use square brackets in your file name within the pages directory. For example, creating a file named [id].js inside the pages/blog directory will allow you to map routes like /blog/1, /blog/2, etc.

Example of Dynamic Next JS Routing

Create a file named [id].js in the pages/blog directory:

// pages/blog/[id].js
import { useRouter } from 'next/router';

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

return (
<div>
<h1>Blog Post: {id}</h1>
</div>
);
}

In this example, useRouter is a hook provided by Next.js that gives you access to the router object. The id parameter is extracted from the URL, allowing you to display dynamic content based on the route.

Pre-rendering with getStaticProps and getStaticPaths

Next js Routing improves pre-rendering capabilities by allowing you to generate static pages at build time, even for dynamic routes. You can use getStaticProps to fetch data for the page and getStaticPaths to define which paths should be pre-rendered.

// pages/blog/[id].js
export default function BlogPost({ post }) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}

export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();

const paths = posts.map((post) => ({
params: { id: post.id.toString() },
}));

return { paths, fallback: true };
}

export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
const post = await res.json();

return { props: { post } };
}

In this example:

  • getStaticPaths fetches all possible blog posts and generates static pages for them.
  • getStaticProps fetches the data for a specific post based on the id parameter.

Using fallback: true allows Next.js to serve paths that haven’t been generated yet, displaying a loading state until the content is ready.

Nested Routing

Nested Next js routing is as simple as organizing files within subdirectories in the pages folder. This approach is ideal for structuring your routes in a hierarchical manner, such as organizing a blog, an e-commerce site, or a documentation site.

How Nested Next JS Routing Works

  • Directory-Based Structure: To create a nested route, simply place a file inside a subdirectory. For example, pages/blog/index.js maps to /blog, and pages/blog/[id].js maps to /blog/[id].
  • SEO and Breadcrumbs: Nested routes are beneficial for SEO, as they create a clear URL structure. They also allow you to easily implement breadcrumbs, which are navigation aids that show users their current location within the site hierarchy.

Example of Nested Next JS Routing

Consider a blog with categories and individual posts. You might structure your pages directory like this:

pages/
blog/
index.js
[id].js
category/
[category].js

This setup creates routes like:

  • /blog – Displays the list of blog posts.
  • /blog/[id] – Displays an individual blog post.
  • /blog/category/[category] – Displays posts within a specific category.

Example of a category page:

// pages/blog/category/[category].js
import { useRouter } from 'next/router';

export default function CategoryPage() {
const router = useRouter();
const { category } = router.query;

return (
<div>
<h1>Category: {category}</h1>
<p>Displaying posts for {category}</p>
</div>
);
}

In this example, useRouter is used to extract the category parameter from the URL, allowing the page to display content specific to that category.

Linking Between Nested Routes

You can easily link between nested routes using the Link component. This component provides client-side navigation, meaning it avoids a full page reload and improves performance.

import Link from 'next/link';

export default function Blog() {
const categories = ['tech', 'lifestyle', 'news'];

return (
<div>
<h1>Blog Categories</h1>
<ul>
{categories.map((category) => (
<li key={category}>
<Link href={`/blog/category/${category}`}>
{category}
</Link>
</li>
))}
</ul>
</div>
);
}

This code generates a list of links to different blog categories, each leading to a nested route.

API Routes

API routes in Next.js allow you to create serverless functions within your application. These routes are placed in the pages/api directory and can handle various HTTP methods, such as GET, POST, PUT, and DELETE. API routes are ideal for building full-stack applications, as they provide a way to include backend logic directly within your Next.js project.

How API Routes Work

  • Routing: Each file in the pages/api directory corresponds to a specific API endpoint. For example, pages/api/hello.js maps to /api/hello.
  • Request Handling: API routes are simply functions that receive a request (req) and response (res) object. These functions can handle different HTTP methods, allowing you to create RESTful endpoints.

Example of a Simple API Route

Create a file named hello.js in the pages/api directory:

// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ message: 'Hello, world!' });
}

This example defines a simple API route that returns a JSON object. Navigating to http://localhost:3000/api/hello in the browser will show:

{ "message": "Hello, world!" }

Handling Different HTTP Methods

API routes can handle different HTTP methods using conditional logic. For instance, you might want to handle GET and POST requests differently:

// pages/api/posts.js
export default function handler(req, res) {
if (req.method === 'GET') {
res.status(200).json({ message: 'This is a GET request' });
} else if (req.method === 'POST') {
const data = req.body;
res.status(201).json({ message: 'Data received', data });
} else {
res.status(405).json({ message: 'Method Not Allowed' });
}
}

In this example:

  • A GET request returns a simple message.
  • A POST request returns the received data.

Middleware and Authentication

Next.js allows you to use middleware with API routes, such as authentication checks or request logging. You can also combine API routes with third-party authentication libraries like Auth0 or Firebase to protect your endpoints.

Example of a simple authentication check:

// pages/api/secure-data.js
export default function handler(req, res) {
const { authorization } = req.headers;

if (authorization !== 'Bearer my-secret-token') {
return res.status(401).json({ message: 'Unauthorized' });
}

res.status(200).json({ data: 'Secure data' });
}

This route checks for a specific authorization token in the request headers and only returns data if the token is correct.

Example Project

To consolidate our understanding, let’s build a simple blog application using Next.js. This example will incorporate basic, dynamic, and nested Next js routing, as well as an API route for fetching posts.

Project Structure

pages/
index.js
about.js
blog/
index.js
[id].js
api/
posts.js

This structure provides a clear and organized way to handle routes and API logic.

Home Page

The home page (index.js) serves as the entry point of the application, linking to other parts of the site.

// pages/index.js
import Link from 'next/link';

export default function Home() {
return (
<div>
<h1>Home Page</h1>
<nav>
<ul>
<li><Link href="/about">About</Link></li>
<li><Link href="/blog">Blog</Link></li>
</ul>
</nav>
</div>
);
}

Blog Listing Page

The blog listing page (blog/index.js) displays a list of blog posts, each linking to its individual post page.

// pages/blog/index.js
import Link from 'next/link';

export default function Blog() {
const posts = [
{ id: 1, title: 'First Post' },
{ id: 2, title: 'Second Post' },
];

return (
<div>
<h1>Blog</h1>
<ul>
{posts.map(post => (
<li key={post.id}>
<Link href={`/blog/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
</div>
);
}

Dynamic Blog Post Page

The dynamic blog post page (blog/[id].js) fetches and displays content based on the post ID.

// pages/blog/[id].js
import { useRouter } from 'next/router';

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

return (
<div>
<h1>Blog Post: {id}</h1>
<p>This is the content of blog post {id}.</p>
</div>
);
}

API Route for Posts

An API route (api/posts.js) fetches a list of posts, simulating a backend data source.

// pages/api/posts.js
export default function handler(req, res) {
const posts = [
{ id: 1, title: 'First Post' },
{ id: 2, title: 'Second Post' },
];
res.status(200).json(posts);
}

Reference:

Credits:

Conclusion

Next.js provides a powerful and intuitive routing system that leverages the file structure of your project. By understanding and utilizing basic, dynamic, and API routes, you can build complex and performant web applications with ease. This guide should give you a solid foundation to start building your Next.js projects with confidence.