Skip to main content

Next.js Pages Concept

Next.js introduces a powerful file-based routing system through its Pages concept, which simplifies how you structure and navigate through your web application. Unlike traditional React applications where routing requires additional libraries and configuration, Next.js provides an intuitive way to create routes based on your file structure.

Introduction to Pages in Next.js

In Next.js, a page is a React Component exported from a file in the pages directory. Each page is associated with a route based on its filename. This approach significantly reduces the complexity of setting up routes and allows you to focus on building your application's functionality.

Key Benefits of Next.js Pages

  • Simple File-Based Routing: Create new routes by adding files to the pages directory
  • Automatic Code Splitting: Each page loads only the necessary JavaScript
  • Built-in Server-Side Rendering: Improves performance and SEO
  • API Routes Support: Create API endpoints as part of your Next.js application
  • Support for Dynamic Routes: Create pages that can match variable paths

Basic Page Structure

Let's start by understanding how to create a basic page in Next.js:

jsx
// pages/index.js - this becomes your home route (/)
import React from 'react'

export default function HomePage() {
return (
<div>
<h1>Welcome to my Next.js Website</h1>
<p>This is the homepage</p>
</div>
)
}

By creating this file, Next.js automatically makes it available at the root URL of your website. No additional configuration is required!

Creating Multiple Pages

Adding more pages is as simple as creating additional files in the pages directory:

jsx
// pages/about.js - this becomes the /about route
export default function AboutPage() {
return (
<div>
<h1>About Us</h1>
<p>Learn more about our company and mission.</p>
</div>
)
}
jsx
// pages/contact.js - this becomes the /contact route
export default function ContactPage() {
return (
<div>
<h1>Contact Us</h1>
<p>Get in touch with our team.</p>
</div>
)
}

Now your application has three routes:

  • / - Homepage
  • /about - About page
  • /contact - Contact page

Nested Routes

You can create nested routes by adding folders inside the pages directory:

jsx
// pages/blog/index.js - becomes /blog
export default function BlogIndexPage() {
return (
<div>
<h1>Blog Posts</h1>
<p>Read our latest articles</p>
</div>
)
}
jsx
// pages/blog/first-post.js - becomes /blog/first-post
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 Next.js pages is the ability to create dynamic routes. These are pages that can handle variable paths. Dynamic segments are denoted by square brackets [] in the filename.

jsx
// pages/posts/[id].js - matches /posts/1, /posts/2, etc.
import { useRouter } from 'next/router'

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

return (
<div>
<h1>Post #{id}</h1>
<p>This is the post with ID: {id}</p>
</div>
)
}

When a user navigates to /posts/123, the component will render with id equal to "123". You can access this parameter using the useRouter hook from Next.js.

Catch-All Routes

For even more flexible routing, Next.js provides catch-all routes using the spread syntax ([...param]):

jsx
// pages/docs/[...slug].js - matches /docs/a, /docs/a/b, /docs/a/b/c, etc.
import { useRouter } from 'next/router'

export default function DocsPage() {
const router = useRouter()
const { slug } = router.query
// slug will be an array of path segments

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

Next.js provides the Link component to enable client-side navigation:

jsx
import Link from 'next/link'

export default function NavigationExample() {
return (
<nav>
<ul>
<li>
<Link href="/">Home</Link>
</li>
<li>
<Link href="/about">About</Link>
</li>
<li>
<Link href="/blog">Blog</Link>
</li>
<li>
<Link href="/posts/[id]" as="/posts/first-post">
First Post
</Link>
</li>
</ul>
</nav>
)
}

Using the Link component instead of regular <a> tags enables fast client-side transitions without full page reloads.

Pre-rendering Pages

Next.js gives you two forms of pre-rendering:

  1. Static Generation (Recommended): HTML is generated at build time and reused for each request.
  2. Server-side Rendering: HTML is generated on each request.

Static Generation with Data

For pages that need data at build time:

jsx
// pages/products.js
export default function Products({ products }) {
return (
<div>
<h1>Products</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
)
}

// This function runs at build time
export async function getStaticProps() {
// Fetch data from an API
const res = await fetch('https://api.example.com/products')
const products = await res.json()

// Pass data to the page via props
return {
props: {
products
}
}
}

Server-side Rendering

For pages that need to render content that's updated on every request:

jsx
// pages/dashboard.js
export default function Dashboard({ userData }) {
return (
<div>
<h1>Dashboard</h1>
<p>Welcome back, {userData.name}</p>
<p>Latest activity: {userData.lastActive}</p>
</div>
)
}

// This function runs on every request
export async function getServerSideProps() {
// Fetch data from an API
const userData = await fetchUserData()

// Pass data to the page via props
return {
props: {
userData
}
}
}

Creating API Routes

Next.js allows you to create API endpoints within your application by adding files to the pages/api directory:

jsx
// pages/api/hello.js
export default function handler(req, res) {
// req is an instance of http.IncomingMessage
// res is an instance of http.ServerResponse

res.status(200).json({ message: 'Hello from the API!' })
}

This creates an API endpoint at /api/hello that returns a JSON response.

Real-world Example: Building a Blog

Let's put everything together with a more complete example of a simple blog:

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

export default function Home({ posts }) {
return (
<div>
<h1>My Developer Blog</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>
{post.title}
</Link>
</li>
))}
</ul>
</div>
)
}

export async function getStaticProps() {
// This would typically come from a CMS or database
const posts = [
{ id: 1, title: 'Getting Started with Next.js', slug: 'getting-started-nextjs' },
{ id: 2, title: 'Why I Love React Hooks', slug: 'react-hooks-love' },
{ id: 3, title: 'Building a Portfolio with Next.js', slug: 'nextjs-portfolio' },
]

return {
props: {
posts
}
}
}
jsx
// pages/blog/[slug].js
import { useRouter } from 'next/router'
import Link from 'next/link'

export default function BlogPost({ post }) {
const router = useRouter()

// If the page is still being generated, show a loading state
if (router.isFallback) {
return <div>Loading...</div>
}

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

export async function getStaticPaths() {
// Specify which paths to pre-render
const posts = [
{ id: 1, slug: 'getting-started-nextjs' },
{ id: 2, slug: 'react-hooks-love' },
]

const paths = posts.map((post) => ({
params: { slug: post.slug }
}))

return { paths, fallback: true }
}

export async function getStaticProps({ params }) {
// Fetch post data based on slug
// In a real app, this would come from a CMS or API
const post = {
title: `Post about ${params.slug}`,
date: '2023-04-15',
content: `<p>This is the content for ${params.slug}</p><p>It supports HTML content.</p>`,
}

return {
props: {
post
}
}
}

Special Pages

Next.js also supports special pages with specific behaviors:

Custom App Component (_app.js)

jsx
// pages/_app.js
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
return (
<div className="app-container">
<header>My Website Header</header>
<main>
<Component {...pageProps} />
</main>
<footer>© 2023 My Website</footer>
</div>
)
}

export default MyApp

This custom App component is used to:

  • Persist layout between page changes
  • Maintain state across pages
  • Add global styles
  • Handle global error boundaries

Custom Document Component (_document.js)

jsx
// pages/_document.js
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
return (
<Html lang="en">
<Head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}

The custom Document component allows you to modify the initial HTML document that gets sent to the client.

Error Page (_error.js)

jsx
// pages/_error.js
function Error({ statusCode }) {
return (
<div>
<h1>Error {statusCode}</h1>
<p>
{statusCode
? `An error ${statusCode} occurred on server`
: 'An error occurred on client'}
</p>
</div>
)
}

Error.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404
return { statusCode }
}

export default Error

This page is shown when server-side errors occur.

Custom 404 Page (404.js)

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

This page is shown when a page is not found.

Summary

Next.js Pages concept provides a powerful, yet intuitive way to structure your application:

  • Routes are automatically created based on files in the pages directory
  • Dynamic routes allow for flexible URL patterns using [param] syntax
  • Pre-rendering options (Static Generation and Server-side Rendering) improve performance
  • API routes enable backend functionality within your Next.js application
  • Special pages like _app.js and _document.js provide application-wide customization

The Pages concept is fundamental to understanding Next.js, as it represents one of the framework's key advantages over standard React applications. By leveraging this file-based routing system, you can build complex web applications with clean architecture and improved performance.

Additional Resources

Exercises

  1. Create a simple Next.js application with a homepage, about page, and contact page
  2. Implement a blog with dynamic routes for individual posts
  3. Add an API route that returns a list of users
  4. Create a custom 404 page with a link back to the homepage
  5. Build a product catalog with categories (nested routes) and individual product pages (dynamic routes)

By practicing these exercises, you'll gain a solid understanding of the Next.js Pages concept and be well-equipped to build modern web applications.



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