Skip to main content

Next.js Pages Router

Introduction

The Pages Router is Next.js's original and traditional routing system that has powered thousands of production applications. It provides a file-system-based routing mechanism that's intuitive and easy to use, especially for beginners. In this guide, we'll explore how the Pages Router works, its key features, and best practices for implementing navigation in your Next.js applications.

note

Next.js now offers two routing systems: the Pages Router (covered in this guide) and the newer App Router. While the App Router is the future of Next.js routing with enhanced features, the Pages Router remains fully supported and is still widely used in existing applications.

How Pages Router Works

The Pages Router in Next.js uses a file-system-based approach where each file in the pages directory automatically becomes a route in your application. This system is intuitive because the URL paths directly correspond to your file structure.

Basic File Structure

my-next-app/
├── pages/
│ ├── index.js # maps to "/"
│ ├── about.js # maps to "/about"
│ └── contact.js # maps to "/contact"

Let's see this in action by creating some basic pages:

jsx
// pages/index.js
export default function HomePage() {
return (
<div>
<h1>Welcome to My Next.js Website</h1>
<p>This is the home page</p>
</div>
);
}
jsx
// pages/about.js
export default function AboutPage() {
return (
<div>
<h1>About Us</h1>
<p>Learn more about our company</p>
</div>
);
}

When you run your Next.js application, visiting / will display the HomePage component, and visiting /about will display the AboutPage component.

Nested Routes

For more complex applications, you often need nested routes. With the Pages Router, you can create nested directories to represent nested URL paths.

my-next-app/
├── pages/
│ ├── index.js # maps to "/"
│ ├── about.js # maps to "/about"
│ └── blog/
│ ├── index.js # maps to "/blog"
│ └── first-post.js # maps to "/blog/first-post"
jsx
// pages/blog/index.js
export default function BlogIndexPage() {
return (
<div>
<h1>Our Blog</h1>
<p>Read our latest articles</p>
</div>
);
}
jsx
// pages/blog/first-post.js
export default function FirstPostPage() {
return (
<div>
<h1>My First Blog Post</h1>
<p>This is the content of my first blog post</p>
</div>
);
}

Dynamic Routes

One of the most powerful features of the Pages Router is dynamic routing, which allows you to create pages with parameters that can change. This is perfect for content-driven pages like blog posts, product details, or user profiles.

To create a dynamic route, wrap the dynamic part of the path with square brackets []:

my-next-app/
├── pages/
│ ├── index.js
│ └── products/
│ └── [id].js # maps to "/products/1", "/products/2", etc.

Here's an example of how to implement a dynamic product page:

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

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

return (
<div>
<h1>Product Details</h1>
<p>You are viewing product with ID: {id}</p>
</div>
);
}

When you visit /products/123, the id variable will contain the value "123".

Catch-all Routes

For even more flexible routing, Next.js supports catch-all routes using the spread operator (...). This allows you to capture multiple path segments.

my-next-app/
├── pages/
│ └── docs/
│ └── [...slug].js # matches "/docs/feature", "/docs/feature/usage", etc.
jsx
// pages/docs/[...slug].js
import { useRouter } from 'next/router';

export default function DocsPage() {
const router = useRouter();
const { slug } = router.query;
// slug is an array of path segments
// e.g., for "/docs/feature/usage", slug = ["feature", "usage"]

return (
<div>
<h1>Documentation</h1>
<p>Current path: {slug ? slug.join('/') : ''}</p>
</div>
);
}

Next.js provides the Link component for client-side navigation between pages. This component prefetches page resources in the background, making navigation nearly instantaneous.

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

export default function HomePage() {
return (
<div>
<h1>Welcome to My Next.js Website</h1>
<nav>
<ul>
<li>
<Link href="/about">About Us</Link>
</li>
<li>
<Link href="/blog">Blog</Link>
</li>
<li>
<Link href="/products/featured">Featured Products</Link>
</li>
</ul>
</nav>
</div>
);
}

For programmatic navigation (e.g., after form submission), you can use the useRouter hook:

jsx
// pages/login.js
import { useState } from 'react';
import { useRouter } from 'next/router';

export default function LoginPage() {
const [username, setUsername] = useState('');
const router = useRouter();

const handleSubmit = (e) => {
e.preventDefault();
// Process login...

// Navigate to dashboard after login
router.push('/dashboard');
};

return (
<div>
<h1>Login</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Username"
/>
<button type="submit">Login</button>
</form>
</div>
);
}

Real-world Application: Creating a Blog

Let's put everything together to create a simple blog with the Pages Router:

my-blog/
├── pages/
│ ├── index.js # Home page
│ ├── blog/
│ │ ├── index.js # Blog listing page
│ │ └── [slug].js # Individual post page

First, let's create a simple data file for our posts:

jsx
// data/posts.js
export const posts = [
{
slug: 'getting-started-with-nextjs',
title: 'Getting Started with Next.js',
content: 'Next.js is a React framework for production...',
date: '2023-01-01',
},
{
slug: 'understanding-react-hooks',
title: 'Understanding React Hooks',
content: 'Hooks are a new addition in React 16.8...',
date: '2023-01-15',
},
{
slug: 'css-tricks-for-developers',
title: 'CSS Tricks for Developers',
content: 'Here are some CSS tricks that every developer should know...',
date: '2023-02-01',
},
];

export function getPostBySlug(slug) {
return posts.find(post => post.slug === slug);
}

Now, let's implement our blog pages:

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

export default function HomePage() {
return (
<div>
<h1>My Next.js Blog</h1>
<p>Welcome to my blog built with Next.js Pages Router</p>
<Link href="/blog">
Read the Blog
</Link>
</div>
);
}
jsx
// pages/blog/index.js
import Link from 'next/link';
import { posts } from '../../data/posts';

export default function BlogIndexPage() {
return (
<div>
<h1>Blog Posts</h1>
<ul>
{posts.map(post => (
<li key={post.slug}>
<Link href={`/blog/${post.slug}`}>
<div>
<h2>{post.title}</h2>
<p>{post.date}</p>
</div>
</Link>
</li>
))}
</ul>
</div>
);
}
jsx
// pages/blog/[slug].js
import { useRouter } from 'next/router';
import Link from 'next/link';
import { getPostBySlug } from '../../data/posts';

export default function BlogPostPage() {
const router = useRouter();
const { slug } = router.query;

// Handle the initial render when slug is undefined
if (!slug) return <div>Loading...</div>;

const post = getPostBySlug(slug);

// Handle case where post isn't found
if (!post) return <div>Post not found</div>;

return (
<article>
<h1>{post.title}</h1>
<p>Published on: {post.date}</p>
<div>{post.content}</div>
<div>
<Link href="/blog">
Back to Blog
</Link>
</div>
</article>
);
}

Data Fetching with Pages Router

The Pages Router provides several powerful data fetching methods:

  1. getStaticProps: Fetch data at build time
  2. getStaticPaths: Specify dynamic routes to pre-render based on data
  3. getServerSideProps: Fetch data on each request

Let's enhance our blog example with proper data fetching:

jsx
// pages/blog/index.js
import Link from 'next/link';
import { posts } from '../../data/posts';

export default function BlogIndexPage({ posts }) {
return (
<div>
<h1>Blog Posts</h1>
<ul>
{posts.map(post => (
<li key={post.slug}>
<Link href={`/blog/${post.slug}`}>
<div>
<h2>{post.title}</h2>
<p>{post.date}</p>
</div>
</Link>
</li>
))}
</ul>
</div>
);
}

// This function runs at build time
export async function getStaticProps() {
// In a real app, you might fetch from an API here
return {
props: {
posts,
},
};
}
jsx
// pages/blog/[slug].js
import Link from 'next/link';
import { posts, getPostBySlug } from '../../data/posts';

export default function BlogPostPage({ post }) {
// No need to use router here anymore
if (!post) return <div>Post not found</div>;

return (
<article>
<h1>{post.title}</h1>
<p>Published on: {post.date}</p>
<div>{post.content}</div>
<div>
<Link href="/blog">
Back to Blog
</Link>
</div>
</article>
);
}

// Generate all paths at build time
export async function getStaticPaths() {
return {
paths: posts.map(post => ({
params: { slug: post.slug }
})),
fallback: false, // Show 404 for paths not returned by getStaticPaths
};
}

// Fetch data for each post at build time
export async function getStaticProps({ params }) {
const post = getPostBySlug(params.slug);

if (!post) {
return {
notFound: true, // Return a 404 page
};
}

return {
props: {
post,
},
};
}

404 Pages and Custom Error Handling

Next.js allows you to create custom error pages by adding special files to your pages directory:

jsx
// pages/404.js
export default function Custom404() {
return (
<div>
<h1>404 - Page Not Found</h1>
<p>Sorry, the page you're looking for doesn't exist.</p>
</div>
);
}

// pages/500.js
export default function Custom500() {
return (
<div>
<h1>500 - Server Error</h1>
<p>Sorry, something went wrong on our server.</p>
</div>
);
}

Summary

The Pages Router in Next.js provides an intuitive file-system-based routing system that makes it easy to create and organize routes in your application. Key features include:

  • File-system based routing through the pages directory
  • Support for nested routes via nested directories
  • Dynamic routes with parameters using [param] syntax
  • Catch-all routes using [...param] syntax
  • Client-side navigation with the Link component
  • Programmatic navigation with the useRouter hook
  • Powerful data fetching methods with getStaticProps, getStaticPaths, and getServerSideProps

While Next.js is evolving with the newer App Router, the Pages Router remains a reliable and straightforward routing solution, especially for beginners and existing projects.

Additional Resources

Exercises

  1. Create a simple portfolio website with pages for Home, Projects, and Contact using the Pages Router.
  2. Implement a dynamic page for project details where the URL includes the project ID.
  3. Add a blog section with dynamically generated pages for each blog post.
  4. Create a custom 404 page with a stylish design and a link back to the homepage.
  5. Implement server-side rendering for a page that displays real-time data (like weather or stocks) using getServerSideProps.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)