Unit 1: Fundamentals of Next.js and Routing
1. Introduction to Next.js Architecture
Next.js is an open-source, full-stack React framework developed by Vercel that streamlines building high-performance, search-engine-optimized, production-ready web applications. While standard React applications (such as those generated via Vite or Create React App) operate as client-rendered Single-Page Applications (SPAs), Next.js introduces a hybrid architecture combining server-side rendering, static site generation, and client-side interactivity.
Core Value Propositions of Next.js:
- Hybrid Rendering Paradigms: Supports Static Site Generation (SSG), Server-Side Rendering (SSR), Client-Side Rendering (CSR), and Incremental Static Regeneration (ISR).
- Zero-Configuration Bundling: Powered by Turbopack (in modern Next.js versions) and SWC (Speedy Web Compiler), offering rapid hot-reloading and minification.
- File-System Based Routing: Eliminates external routing libraries like
react-router-dom; file and folder conventions automatically dictate route URLs. - Built-in Optimizations: Native components for image optimization (
next/image), font loading (next/font), script optimization (next/script), and metadata management. - Full-Stack Capabilities: Seamlessly blends frontend UI with backend Route Handlers and Server Actions without needing a standalone Node.js Express server.
Project Directory Structure: App Router vs. Pages Router
Next.js 13 introduced the App Router (app/ directory), built on React Server Components (RSC), which supersedes the legacy Pages Router (pages/ directory).
textmy-next-app/ ├── app/ # App Router root (Next.js 13+) │ ├── favicon.ico │ ├── globals.css # Global stylesheets │ ├── layout.tsx # Mandatory Root Layout (html, body) │ ├── page.tsx # Home page UI (/) │ ├── loading.tsx # Global loading UI │ ├── not-found.tsx # Global 404 UI │ ├── error.tsx # Root error boundary ('use client') │ ├── blog/ # Nested route (/blog) │ │ ├── [slug]/ # Dynamic segment (/blog/:slug) │ │ │ └── page.tsx │ │ └── page.tsx │ └── api/ # Backend Route Handlers │ └── hello/ │ └── route.ts ├── public/ # Static assets (images, fonts, robots.txt) ├── next.config.js # Framework configuration ├── package.json └── tsconfig.json
2. The App Router Conventions & Special Files
In the App Router, folders define route paths, while specific reserved filenames define user interface components and boundary behaviors for that route segment:
| Special Filename | Component Role | Rendering Environment |
|---|---|---|
page.jsx | Unique UI for a route segment; makes the path publicly accessible. | Server Component by default |
layout.jsx | Shared layout wrapping child routes; preserves state across navigation and never re-renders. | Server Component by default |
template.jsx | Similar to layout.jsx, but creates a brand new component instance on every navigation (resets state & effects). | Server Component by default |
loading.jsx | Instant fallback UI wrapped automatically inside React <Suspense>. | Server Component |
error.jsx | Error boundary wrapping child route segments in a React error boundary. | Must be a Client Component ('use client') |
not-found.jsx | Rendered when notFound() function is invoked or an unmatched URL is hit. | Server Component |
route.js | Backend API endpoint (HTTP methods: GET, POST, PUT, DELETE, PATCH). | Server execution only |
The Root Layout (app/layout.jsx)
The root layout is mandatory in every Next.js App Router application. It must define the top-level <html> and <body> tags:
jsx// app/layout.jsx
import './globals.css';
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: 'Modern Web Application',
description: 'Built with Next.js 14 App Router'
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
<header className="site-header">My Application</header>
<main>{children}</main>
<footer>© 2026 Modern Web Inc.</footer>
</body>
</html>
);
}
3. Server Components vs. Client Components
A cornerstone of modern Next.js is React Server Components (RSC).
React Server Components (Default)
In the app/ directory, all components are Server Components by default unless explicitly instructed otherwise.
- Execution: Execute exclusively on the web server during build or request time.
- Bundle Weight: Emit zero JavaScript into the client browser bundle.
- Capabilities:
- Can directly query backend databases (PostgreSQL, Prisma, MongoDB, Redis).
- Can securely access server environment variables and secrets without exposing them.
- Can read the host filesystem (
fs/promises).
- Restrictions:
- Cannot use React state or lifecycle hooks (
useState,useReducer,useEffect,useLayoutEffect). - Cannot listen to browser DOM events (
onClick,onChange,onSubmit). - Cannot access browser APIs (
window,localStorage,navigator,document).
- Cannot use React state or lifecycle hooks (
Client Components ('use client')
To introduce browser interactivity, developers mark a file as a Client Component using the 'use client' directive at the very top of the file:
jsx'use client';
import React, { useState } from 'react';
export default function InteractiveCounter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked: {count} times
</button>
);
}
- Pre-rendering: Client Components are still pre-rendered on the server into static HTML during initial request, then 'hydrated' by React in the browser.
- Boundary Placement: Best practice recommends pushing
'use client'directives as far down the component tree as possible (leaf components), keeping container layouts as Server Components.
Composing Server and Client Components
Server Components can be passed into Client Components as children (slots), preserving their server-only execution benefits:
jsx'use client';
export function ModalShell({ children }) {
const [isOpen, setIsOpen] = useState(true);
return isOpen ? <div className="modal">{children}</div> : null;
}
// In a Server Component page:
import { ModalShell } from './ModalShell';
import { HeavyServerData } from './HeavyServerData'; // Server Component
export default function Page() {
return (
<ModalShell>
<HeavyServerData /> {/* Rendered on server, passed as JSX prop */}
</ModalShell>
);
}
4. Navigation and Linking
Next.js provides declarative and imperative navigation mechanisms optimized for performance:
The <Link> Component (next/link)
The primary way to navigate between routes in Next.js:
jsximport Link from 'next/link';
<Link href="/dashboard" prefetch={true} className="nav-item">
Go to Dashboard
</Link>
- Automatic Viewport Prefetching: Whenever a
<Link>enters the browser's viewport, Next.js automatically prefetches the corresponding route bundle and server component data in the background, making page transitions nearly instantaneous. - Soft Navigation: Preserves React state across transitions without full page reloads.
Imperative Navigation: useRouter (next/navigation)
In Client Components, navigation can be triggered programmatically using the useRouter hook imported from next/navigation (not next/router from the legacy Pages Router):
jsx'use client';
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
export function NavigationControls() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const handleCheckout = () => {
// Navigate to order page
router.push('/checkout');
// Replace current history entry
// router.replace('/login');
// Refresh current route data
// router.refresh();
};
return <button onClick={handleCheckout}>Checkout ({pathname})</button>;
}
Server-Side Redirection
In Server Components, Route Handlers, or Server Actions, redirection is accomplished via the redirect() function:
jsximport { redirect } from 'next/navigation';
export default async function ProfilePage({ params }) {
const session = await getSession();
if (!session) {
redirect('/login');
}
return <h1>Welcome, {session.user.name}</h1>;
}
5. Dynamic Routing and Segment Matching
Dynamic Segments ([folderName])
Dynamic routes are created by enclosing a folder name in square brackets:
- Folder path:
app/products/[id]/page.jsx - Matches:
/products/101,/products/abc - Parameters are passed to the page component via the
paramsprop:
jsxexport default function ProductPage({ params }) {
return <h1>Product SKU: {params.id}</h1>;
}
Catch-All Segments ([...folderName])
Matches all subsequent nested path segments:
- Folder path:
app/docs/[...slug]/page.jsx - Matches:
/docs/intro,/docs/installation/nextjs,/docs/a/b/c params.slugis provided as an array:['installation', 'nextjs'].- Note: Does not match
/docsitself.
Optional Catch-All Segments ([[...folderName]])
By wrapping the parameter in double square brackets:
- Folder path:
app/shop/[[...categories]]/page.jsx - Matches:
/shop,/shop/clothing,/shop/clothing/shoes/sneakers.
Route Groups ((folderName))
To organize route files into logical sections without affecting the URL pathname:
- Folders wrapped in parentheses
(marketing)or(admin)are omitted from the URL path. - Example:
app/(marketing)/about/page.jsxresolves to/about. - Enables defining multiple independent Root Layouts or scoping layouts to specific sub-audiences.
Private Folders (_folderName)
Prefixing a folder with an underscore _lib/ tells the App Router to ignore that directory from routing, allowing developers to safely colocate internal utilities, helper components, or unit tests right alongside routes.