How to Create Pages in Nextjs
How to Create Pages in Next.js Next.js has rapidly become the go-to framework for building modern, high-performance web applications in React. One of its most powerful features is its file-system-based routing system, which simplifies the process of creating pages without the need for complex configuration. Whether you're building a personal blog, an e-commerce platform, or a corporate website, un
How to Create Pages in Next.js
Next.js has rapidly become the go-to framework for building modern, high-performance web applications in React. One of its most powerful features is its file-system-based routing system, which simplifies the process of creating pages without the need for complex configuration. Whether you're building a personal blog, an e-commerce platform, or a corporate website, understanding how to create pages in Next.js is fundamental to leveraging its full potential. Unlike traditional React applications that require manual routing libraries like React Router, Next.js automatically generates routes based on the structure of your pages directory (in Next.js 12 and earlier) or the app directory (in Next.js 13+). This tutorial will guide you through every step of creating pages in Next.js—from basic setups to advanced patterns—while emphasizing performance, scalability, and maintainability.
Creating pages in Next.js isn’t just about adding files—it’s about structuring your application for optimal SEO, fast loading times, and seamless user experiences. With built-in support for Server-Side Rendering (SSR), Static Site Generation (SSG), and Incremental Static Regeneration (ISR), Next.js empowers developers to craft pages that load instantly and rank higher in search engines. This guide will walk you through the core concepts, practical implementation, best practices, and real-world examples to ensure you can confidently build scalable, SEO-friendly applications using Next.js.
Step-by-Step Guide
Setting Up Your Next.js Project
Before you begin creating pages, you need a functional Next.js project. If you haven’t already set one up, open your terminal and run the following command:
npx create-next-app@latest my-nextjs-app
This command initializes a new Next.js application with the latest stable version. During setup, you’ll be prompted to choose options such as TypeScript support, ESLint configuration, and whether to use the App Router or Pages Router. For this guide, we’ll focus on the newer App Router (introduced in Next.js 13), which is now the recommended approach for all new projects.
Once the installation completes, navigate into your project folder:
cd my-nextjs-app
Start the development server:
npm run dev
Your application will now be accessible at http://localhost:3000. You’ll see the default Next.js welcome page, confirming your environment is ready.
Understanding the App Router Structure
In Next.js 13 and above, the app directory replaces the traditional pages directory as the primary location for defining routes. The App Router uses a hierarchical folder structure to define routes, where each folder corresponds to a URL segment.
By default, your project includes an app folder with a page.js file inside. This file is the root page of your application and renders when users visit /.
To create additional pages, simply create new folders inside the app directory. Each folder name becomes part of the URL path. For example:
app/about/page.js→/aboutapp/blog/page.js→/blogapp/products/iphone/page.js→/products/iphone
Each page.js file must export a default React component. Here’s a minimal example:
// app/about/page.js
export default function AboutPage() {
return <h1>About Us</h1>;
}
When you save this file, Next.js automatically creates the route and refreshes your browser. No manual configuration is needed.
Creating Dynamic Routes
Dynamic routes allow you to generate pages based on variable data—such as product IDs, blog slugs, or user profiles. In the App Router, dynamic segments are defined using square brackets: [param].
For example, to create a dynamic blog post page, create a folder named [slug] inside the app/blog directory:
app/
└── blog/
├── page.js
└── [slug]/
└── page.js
Inside app/blog/[slug]/page.js, you can access the dynamic segment using the params prop:
// app/blog/[slug]/page.js
export default function BlogPost({ params }) {
const { slug } = params;
return <h1>Blog Post: {slug}</h1>;
}
Now, visiting /blog/my-first-post will render “Blog Post: my-first-post”.
You can also create multiple dynamic segments:
app/
└── users/
└── [id]/
└── posts/
└── [postId]/
└── page.js
This creates a route like /users/123/posts/456. Access both parameters:
export default function PostPage({ params }) {
const { id, postId } = params;
return <h1>User {id}, Post {postId}</h1>;
}
Creating Nested Routes and Layouts
Next.js allows you to define shared layouts for groups of pages using layout.js files. This is especially useful for headers, footers, navigation, or sidebars that appear across multiple pages.
Create a layout.js file in any directory to wrap all its child pages:
// app/blog/layout.js
export default function BlogLayout({ children }) {
return (
<div>
<header>
<h2>Blog Navigation</h2>
</header>
<main>{children}</main>
<footer>
<p>© 2024 Blog</p>
</footer>
</div>
);
}
Now, every page inside the app/blog directory—including page.js and [slug]/page.js—will automatically be wrapped with this layout. This eliminates redundancy and improves maintainability.
You can also create nested layouts. For example:
app/
├── layout.js // Root layout
├── blog/
│ ├── layout.js // Blog layout
│ ├── page.js // /blog
│ └── [slug]/
│ └── page.js // /blog/[slug]
└── dashboard/
├── layout.js // Dashboard layout
└── page.js // /dashboard
The root layout.js wraps the entire application, while the nested layouts wrap only their respective sections. This modular structure makes it easy to manage complex UIs.
Using Loading and Error Boundaries
Next.js provides built-in components to handle loading states and errors gracefully. Create a loading.js file to show a spinner or placeholder while data is being fetched:
// app/blog/loading.js
export default function BlogLoading() {
return <p>Loading blog posts...</p>;
}
Similarly, create an error.js file to display a user-friendly message when a page fails to load:
// app/blog/error.js
export default function BlogError() {
return <h2>Failed to load blog posts. Please try again later.</h2>;
}
These files must be placed in the same directory as the page they’re associated with. Next.js automatically detects them and renders them at the appropriate time.
Generating Static and Dynamic Pages with Data Fetching
Next.js supports three main data fetching strategies: Static Site Generation (SSG), Server-Side Rendering (SSR), and Client-Side Rendering (CSR). For optimal performance and SEO, SSG and SSR are preferred.
To fetch data at build time (SSG), use the generateStaticParams function inside a dynamic route:
// app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(res => res.json());
return posts.map(post => ({
slug: post.slug
}));
}
export default function BlogPost({ params }) {
return <h1>{params.slug}</h1>;
}
This ensures that every blog post is pre-rendered at build time, resulting in blazing-fast page loads and perfect SEO.
For pages that require fresh data on every request (SSR), use async components with await:
// app/dashboard/page.js
export default async function Dashboard() {
const user = await fetch('https://api.example.com/user', { cache: 'no-store' }).then(res => res.json());
return (
<div>
<h1>Welcome, {user.name}</h1>
<p>Last login: {new Date(user.lastLogin).toLocaleString()}</p>
</div>
);
}
The cache: 'no-store' option ensures the data is not cached and is fetched on every request.
Creating Redirects and Custom 404 Pages
To redirect users from one route to another, use the redirect function from next/navigation:
// app/old-page/page.js
import { redirect } from 'next/navigation';
export default function OldPage() {
redirect('/new-page');
}
To create a custom 404 page, create a file named not-found.js in the root of the app directory:
// app/not-found.js
export default function NotFound() {
return (
<div>
<h1>404 - Page Not Found</h1>
<p>The page you’re looking for doesn’t exist.</p>
<a href="/">Go Home</a>
</div>
);
}
Next.js will automatically display this page when a route doesn’t match any existing file or dynamic parameter.
Best Practices
Use Semantic Folder Structures
Organize your app directory logically. Group related pages under common parent folders. For example:
app/
├── home/
│ └── page.js
├── blog/
│ ├── page.js
│ └── [slug]/
│ └── page.js
├── products/
│ ├── page.js
│ ├── [category]/
│ │ └── page.js
│ └── [id]/
│ └── page.js
├── account/
│ ├── login/
│ │ └── page.js
│ └── profile/
│ └── page.js
└── layout.js
This structure makes it easy for developers to navigate the codebase and understand the application’s architecture at a glance.
Minimize Client-Side Data Fetching
While client-side data fetching using useEffect and useState is possible, it should be avoided for content that should be indexed by search engines. Always prefer server-side or static data fetching to ensure content is available in the initial HTML response.
If you must fetch data on the client (e.g., for user-specific interactions), use React’s useSWR or react-query libraries for better caching and error handling.
Optimize Images and Assets
Next.js includes a built-in Image Component that automatically optimizes images. Always use it instead of standard <img> tags:
import Image from 'next/image';
export default function HomePage() {
return (
<Image
src="/hero-image.jpg"
alt="Hero banner"
width={1200}
height={600}
priority
/>
);
}
The priority prop ensures the image is loaded with high priority, ideal for above-the-fold content.
Implement Proper Metadata
Next.js allows you to define metadata at the page level using the generateMetadata function or the metadata export:
// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
const post = await fetch(https://api.example.com/posts/${params.slug}).then(res => res.json());
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
};
}
export default function BlogPost({ params }) {
return <h1>{params.slug}</h1>;
}
This ensures each page has unique, search-engine-friendly titles and descriptions, improving click-through rates and SEO performance.
Avoid Deeply Nested Routes When Possible
While Next.js supports deep nesting, overly complex URL structures can hurt usability and SEO. Aim for URLs that are short, readable, and meaningful:
- Good:
/products/shoes - Avoid:
/category/electronics/phones/smartphones/iphone/15/pro
If you need to represent hierarchy, use URL parameters or breadcrumbs instead of deep folder nesting.
Test Your Pages Thoroughly
Always test your pages in production mode to catch issues that may not appear in development:
npm run build
npm run start
Use tools like Lighthouse (in Chrome DevTools) to audit performance, accessibility, and SEO. Pay attention to Core Web Vitals: Largest Contentful Paint (LCP), First Input Delay (FID), and Cumulative Layout Shift (CLS).
Use TypeScript for Type Safety
If you’re using TypeScript, define interfaces for your data to improve code reliability:
interface Post {
id: number;
slug: string;
title: string;
excerpt: string;
image: string;
}
export async function generateStaticParams(): Promise<{ slug: string }>[] {
const posts: Post[] = await fetch('https://api.example.com/posts').then(res => res.json());
return posts.map(post => ({ slug: post.slug }));
}
TypeScript helps prevent runtime errors and improves developer experience in large codebases.
Tools and Resources
Official Next.js Documentation
The Next.js Documentation is the most authoritative source for learning about routing, data fetching, and performance optimization. It includes detailed guides, code examples, and API references.
Next.js Starter Templates
GitHub hosts numerous open-source Next.js templates that demonstrate best practices:
- Official Next.js Examples
- Astro + Next.js Integration (for hybrid rendering)
- Next.js Blog Example
These templates can serve as blueprints for your own projects.
VS Code Extensions
Enhance your development workflow with these extensions:
- ESLint – For code quality and consistency
- Prettier – For automatic code formatting
- Next.js Snippets – For quick code generation (e.g., typing “npx” to generate a page component)
- React Router for VS Code – Helps visualize route structures
Performance Monitoring Tools
Monitor your application’s real-world performance using:
- Google Search Console – Track indexing status and search performance
- Google Analytics 4 – Understand user behavior
- Web Vitals – Measure Core Web Vitals directly in your app
- Netlify or Vercel Analytics – Built-in performance dashboards when deploying on these platforms
Deployment Platforms
Next.js applications can be deployed on any platform, but these are the most optimized:
- Vercel – Created by the Next.js team. Offers zero-config deployment, automatic caching, and edge functions.
- Netlify – Excellent for static sites with serverless functions.
- Render – Simple and affordable for full-stack apps.
- Docker + Nginx – For self-hosted environments.
Deploying on Vercel is as simple as connecting your GitHub repository and clicking “Deploy.”
Community and Learning Resources
Stay updated with the Next.js ecosystem through:
- Next.js Discord – Active community for troubleshooting and advice
- YouTube Channels – Traversy Media, Web Dev Simplified, and Netlify
- Twitter/X – Follow @nextjs and @vercel for announcements
- Next.js Newsletter – Weekly updates on new features and best practices
Real Examples
Example 1: E-Commerce Product Page
Imagine you’re building an online store with thousands of products. Each product has a unique URL like /products/123.
Folder structure:
app/
├── products/
│ ├── page.js // /products (list of all products)
│ └── [id]/
│ ├── page.js // /products/123
│ └── loading.js // Loading state
├── layout.js
└── not-found.js
app/products/page.js fetches all products at build time:
// app/products/page.js
export default async function ProductsPage() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // ISR: revalidate every hour
}).then(res => res.json());
return (
<div>
<h1>All Products</h1>
<ul>
{products.map(product => (
<li key={product.id}>
<a href={/products/${product.id}}>{product.name}</a>
</li>
))}
</ul>
</div>
);
}
app/products/[id]/page.js fetches individual product data:
// app/products/[id]/page.js
export async function generateStaticParams() {
const products = await fetch('https://api.example.com/products').then(res => res.json());
return products.map(product => ({ id: product.id.toString() }));
}
export default async function ProductPage({ params }) {
const product = await fetch(https://api.example.com/products/${params.id}).then(res => res.json());
return (
<div>
<h1>{product.name}</h1>
<p>${product.price}</p>
<p>{product.description}</p>
</div>
);
}
This setup ensures every product page is pre-rendered (SSG) and updated hourly (ISR), balancing performance and freshness.
Example 2: News Blog with Categories
For a news site with categories like /news/tech and /news/sports, structure your routes as:
app/
├── news/
│ ├── layout.js
│ ├── page.js // /news (latest articles)
│ ├── [category]/
│ │ ├── page.js // /news/tech
│ │ └── loading.js
│ └── [slug]/
│ └── page.js // /news/ai-breakthrough
├── layout.js
└── not-found.js
app/news/[category]/page.js filters articles by category:
// app/news/[category]/page.js
export async function generateStaticParams() {
const categories = ['tech', 'sports', 'politics'];
return categories.map(category => ({ category }));
}
export default async function CategoryPage({ params }) {
const articles = await fetch(https://api.example.com/articles?category=${params.category}).then(res => res.json());
return (
<div>
<h1>{params.category.charAt(0).toUpperCase() + params.category.slice(1)} News</h1>
{articles.map(article => (
<article key={article.id}>
<h2><a href={/news/${article.slug}}>{article.title}</a></h2>
<p>{article.excerpt}</p>
</article>
))}
</div>
);
}
Each category page is statically generated, ensuring fast load times and SEO benefits.
Example 3: Multi-Language Site
Next.js supports internationalization (i18n) out of the box. To create a multi-language site:
app/
├── en/
│ ├── layout.js
│ └── page.js
├── es/
│ ├── layout.js
│ └── page.js
├── fr/
│ ├── layout.js
│ └── page.js
├── layout.js
└── not-found.js
Configure i18n in next.config.js:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
i18n: {
locales: ['en', 'es', 'fr'],
defaultLocale: 'en',
},
};
module.exports = nextConfig;
Each language folder serves as a route prefix. Users visiting /es see the Spanish version, and so on.
FAQs
What is the difference between the Pages Router and the App Router in Next.js?
The Pages Router (used in Next.js 12 and earlier) defines routes using files in the pages directory. The App Router (introduced in Next.js 13) uses the app directory and supports features like server components, nested layouts, and streaming. The App Router is now the recommended approach for all new projects due to its improved performance and flexibility.
Can I use both the Pages Router and App Router in the same project?
Yes, Next.js supports a hybrid approach. You can have both pages and app directories in the same project. However, routes in the app directory take precedence. It’s best to migrate fully to the App Router for long-term maintainability.
How do I handle authentication in Next.js pages?
Use NextAuth.js, the official authentication library for Next.js, to handle sign-in, sign-out, and session management. You can protect routes by checking authentication status in layout or page components using server-side logic.
Do I need a backend to create pages in Next.js?
No. You can create static pages using hardcoded content or fetch data from public APIs. However, for dynamic, user-specific, or frequently updated content, connecting to a backend (like a CMS or database) is recommended.
How do I deploy a Next.js app to production?
Use Vercel for the easiest deployment. Alternatively, build your app using npm run build, then serve the output in the .next folder using a Node.js server or static host like Netlify or AWS S3.
Can I use custom domains with Next.js?
Yes. If you deploy on Vercel or Netlify, you can easily connect a custom domain through their dashboard. You’ll need to update your DNS records to point to their servers.
How do I add analytics to my Next.js site?
Use Google Analytics 4 by installing the next-google-analytics package or manually adding the GA script in your root layout.js using the <Script> component from Next.js.
What is ISR and why should I use it?
Incremental Static Regeneration (ISR) allows you to update static pages after they’ve been built, without rebuilding the entire site. It’s ideal for content that changes occasionally—like blogs or product listings—because it combines the speed of static sites with the freshness of dynamic ones.
Can I use Next.js for non-React applications?
No. Next.js is built on top of React and requires React components. However, it can integrate with other libraries like Vue or Svelte via custom server setups, though this is not recommended or supported.
How do I optimize page load speed in Next.js?
Use the Image component, lazy-load non-critical components with dynamic(), prefetch links with next/link, minimize third-party scripts, and leverage server-side rendering or static generation for content-heavy pages.
Conclusion
Creating pages in Next.js is a streamlined, intuitive process that empowers developers to build fast, scalable, and SEO-friendly web applications without the complexity of manual routing or configuration. By leveraging the App Router’s file-system-based architecture, you can define routes simply by organizing files and folders—no external libraries or boilerplate code required.
From static blogs to dynamic e-commerce platforms, Next.js provides the tools to handle any use case. Its built-in support for Server-Side Rendering, Static Site Generation, and Incremental Static Regeneration ensures your pages load quickly and rank well in search engines. Combined with features like automatic image optimization, metadata generation, and seamless deployment on Vercel, Next.js sets a new standard for modern web development.
As you continue building with Next.js, remember to prioritize structure, performance, and user experience. Start with a clean folder hierarchy, fetch data efficiently, and test your pages rigorously. The ecosystem around Next.js is vibrant and constantly evolving—stay updated, explore the official examples, and don’t hesitate to contribute to the community.
Whether you’re a beginner taking your first steps or an experienced developer optimizing a large-scale application, mastering how to create pages in Next.js is a foundational skill that will serve you well in the modern web development landscape. Start small, build with intention, and let Next.js handle the heavy lifting—so you can focus on what matters most: delivering exceptional user experiences.