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.
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:
// 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>
);
}
// 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"
// pages/blog/index.js
export default function BlogIndexPage() {
return (
<div>
<h1>Our Blog</h1>
<p>Read our latest articles</p>
</div>
);
}
// 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:
// 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.
// 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>
);
}
Navigation Between Pages
Next.js provides the Link
component for client-side navigation between pages. This component prefetches page resources in the background, making navigation nearly instantaneous.
// 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:
// 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:
// 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:
// 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>
);
}
// 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>
);
}
// 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:
- getStaticProps: Fetch data at build time
- getStaticPaths: Specify dynamic routes to pre-render based on data
- getServerSideProps: Fetch data on each request
Let's enhance our blog example with proper data fetching:
// 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,
},
};
}
// 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:
// 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
, andgetServerSideProps
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
- Create a simple portfolio website with pages for Home, Projects, and Contact using the Pages Router.
- Implement a dynamic page for project details where the URL includes the project ID.
- Add a blog section with dynamically generated pages for each blog post.
- Create a custom 404 page with a stylish design and a link back to the homepage.
- 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! :)