Next.js Routing and Pages
Next.js Routing and Pages
Next.js is a React framework that adds powerful features on top of React, and its most distinctive feature is file-based routing. Instead of configuring routes in code, you create files and folders — each one automatically becomes a page in your app.
App Router vs Pages Router
Next.js has two routing systems:
| App Router | Pages Router | |
|---|---|---|
| Directory | app/ | pages/ |
| Released | Next.js 13+ (2023) | Original |
| Status | Recommended | Still supported |
| Components | Server Components by default | Client Components by default |
The App Router is the modern standard. AI tools generate App Router code by default. This tutorial focuses entirely on the App Router.
File-Based Routing Basics
In Next.js, the file structure inside the app/ directory determines your URLs:
app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
├── contact/
│ └── page.tsx → /contact
├── blog/
│ ├── page.tsx → /blog
│ └── [slug]/
│ └── page.tsx → /blog/my-first-post
└── dashboard/
├── page.tsx → /dashboard
├── settings/
│ └── page.tsx → /dashboard/settings
└── profile/
└── page.tsx → /dashboard/profile
The key file is page.tsx — it defines the content for that route. Every folder with a page.tsx file becomes a URL in your app.
A Basic Page
// app/page.tsx — this is the home page (/) export default function HomePage() { return ( <main> <h1>Welcome to My App</h1> <p>This is the home page.</p> </main> ); }
// app/about/page.tsx — this is /about export default function AboutPage() { return ( <main> <h1>About Us</h1> <p>Learn more about our team.</p> </main> ); }
Notice: the function name doesn't determine the URL — only the file path does.
What to ask your AI: "Create a new page at /[path] in my Next.js app. It should display [content]."
Dynamic Routes with [slug]
Square brackets create dynamic routes — pages that match different URLs:
// app/blog/[slug]/page.tsx — matches /blog/anything interface BlogPostPageProps { params: Promise<{ slug: string }>; } export default async function BlogPostPage({ params }: BlogPostPageProps) { const { slug } = await params; // Fetch the blog post using the slug const post = await fetch(`https://api.example.com/posts/${slug}`).then( res => res.json() ); return ( <article> <h1>{post.title}</h1> <p>{post.content}</p> </article> ); }
The params object contains the dynamic segments:
| File Path | URL | params |
|---|---|---|
app/blog/[slug]/page.tsx | /blog/hello-world | { slug: "hello-world" } |
app/users/[id]/page.tsx | /users/42 | { id: "42" } |
app/shop/[category]/[id]/page.tsx | /shop/shoes/123 | { category: "shoes", id: "123" } |
What to ask your AI: "Create a dynamic route for /products/[id] that fetches and displays a single product."
Layouts — Shared UI
Layouts wrap pages with shared UI like headers, sidebars, and footers. They persist across page navigations:
// app/layout.tsx — the root layout (wraps every page) import { ReactNode } from "react"; export default function RootLayout({ children }: { children: ReactNode }) { return ( <html lang="en"> <body> <header> <nav> <a href="/">Home</a> <a href="/about">About</a> <a href="/blog">Blog</a> </nav> </header> <main>{children}</main> <footer> <p>My App 2025</p> </footer> </body> </html> ); }
Nested Layouts
You can have layouts at any level — they nest automatically:
// app/dashboard/layout.tsx — wraps all /dashboard/* pages import { ReactNode } from "react"; export default function DashboardLayout({ children }: { children: ReactNode }) { return ( <div className="flex"> <aside className="w-64 bg-gray-100 p-4"> <h2>Dashboard</h2> <nav> <a href="/dashboard">Overview</a> <a href="/dashboard/settings">Settings</a> <a href="/dashboard/profile">Profile</a> </nav> </aside> <div className="flex-1 p-6"> {children} </div> </div> ); }
Now every page under /dashboard/* gets the sidebar automatically. The {children} placeholder is where the page content renders.
What to ask your AI: "Create a dashboard layout with a sidebar navigation and a main content area. Include links to [these pages]."
Loading States
Next.js has built-in loading UI support. Create a loading.tsx file and it shows automatically while the page is loading:
// app/blog/loading.tsx — shows while /blog page loads export default function BlogLoading() { return ( <div className="animate-pulse space-y-4"> <div className="h-8 bg-gray-200 rounded w-1/3"></div> <div className="h-4 bg-gray-200 rounded w-full"></div> <div className="h-4 bg-gray-200 rounded w-2/3"></div> </div> ); }
This loading component displays automatically while the page.tsx in the same directory is fetching data.
Error Handling
Similarly, create error.tsx to handle errors:
// app/blog/error.tsx "use client"; // Error components must be Client Components export default function BlogError({ error, reset, }: { error: Error; reset: () => void; }) { return ( <div className="text-center py-10"> <h2 className="text-2xl font-bold text-red-600">Something went wrong!</h2> <p className="text-gray-600 mt-2">{error.message}</p> <button onClick={reset} className="mt-4 px-4 py-2 bg-blue-500 text-white rounded" > Try again </button> </div> ); }
The Link Component — Client-Side Navigation
Use Next.js Link component instead of regular <a> tags for navigation between pages:
import Link from "next/link"; function Navigation() { return ( <nav className="flex gap-4"> <Link href="/">Home</Link> <Link href="/about">About</Link> <Link href="/blog">Blog</Link> <Link href="/blog/my-first-post">First Post</Link> <Link href="/dashboard" className="text-blue-500 hover:underline" > Dashboard </Link> </nav> ); }
Why use Link instead of <a>:
- Client-side navigation — no full page reload, instant transitions
- Prefetching — Next.js preloads linked pages in the background
- Preserves state — React state is maintained during navigation
Programmatic Navigation
Sometimes you need to navigate in code (e.g., after a form submission):
"use client"; import { useRouter } from "next/navigation"; function LoginForm() { const router = useRouter(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // ... login logic router.push("/dashboard"); // Navigate programmatically }; return <form onSubmit={handleSubmit}>...</form>; }
Special Files Summary
Next.js uses special file names for different purposes:
| File | Purpose |
|---|---|
page.tsx | The page content for that route |
layout.tsx | Shared wrapper that persists across navigations |
loading.tsx | Loading UI shown while page is loading |
error.tsx | Error UI shown when something fails |
not-found.tsx | 404 page for that route segment |
template.tsx | Like layout, but re-mounts on every navigation |
What to ask your AI: "Set up the file structure for a Next.js app with [these pages]. Include layouts for [these sections] and loading states."
What's Next?
You understand routing and page structure. The next tutorial covers Data Fetching in Next.js — how to load data in server components and client components.