Next.js has changed how we build React applications, offering a powerful full-stack framework with features like Server Components, the App Router, and built-in caching.
What is Next.js?#
Next.js is a React framework that enables you to build full-stack web applications. It provides features like server-side rendering, static prerendering, and Route Handlers (API endpoints) out of the box.
Modern web development made simple and powerful
Build both frontend and backend in one
Better performance and SEO
Intuitive and scalable
Images, fonts, and more
Key Features#
Prerequisites#
v20.9 or later
npm, pnpm, yarn, or bun
Basic knowledge required
VS Code recommended
App Router vs Pages Router#
Next.js offers two routing systems. Here's a comprehensive comparison:
Default Rendering
- How components are rendered by default
- Server Components
- Pre-rendered on the server, then hydrated (no Server Components)
Streaming
- Streaming UI with React Suspense
- Supported
- Not supported
Data Fetching
- How a page loads its data
- async components, fetch, use cache
- getServerSideProps, getStaticProps
Layouts
- Shared UI across routes
- Nested layout.tsx files
- A single _app.tsx plus manual patterns
Project Setup#
Create a new Next.js application using your preferred package manager:
Using pnpm - Fast, disk space efficient package manager
pnpm create next-app@latest my-nextjs-appUsing npm - Node.js default package manager
npx create-next-app@latest my-nextjs-appUsing yarn - Fast, reliable, and secure dependency management
yarn create next-app@latest my-nextjs-appAdd --yes to skip the prompts and accept the recommended defaults. Then cd my-nextjs-app and run the dev script to start the server on http://localhost:3000.
Installation Options#
The CLI first asks whether to use the recommended defaults (TypeScript, ESLint, Tailwind CSS, App Router, Cache Components, no src/ directory). If you choose to customize, these are the settings we pick:
Recommended settings for optimal development experience
Turbopack is no longer a prompt: since Next.js 16 it is the default
bundler for both next dev and next build.
Project Structure#
hello/route.ts
globals.csslayout.tsxpage.tsx
ui/button.tsxui/input.tsx
utils.ts
favicon.icologo.svg
next.config.tspackage.jsontsconfig.json
auth/route.tsproducts/route.tsusers/route.ts
layout.tsxpage.tsx
globals.csslayout.tsxpage.tsx
login-form.tsxsignup-form.tsx
button.tsxform.tsxtable.tsx
use-auth.tsuse-local-storage.ts
auth.tsdb.tsvalidations.ts
proxy.tsnext.config.tspackage.jsontsconfig.json
orders/route.tspayments/route.tsproducts/route.ts
page.tsx
page.tsx
page.tsx
page.tsx
globals.csslayout.tsxpage.tsx
cart-item.tsxcart-summary.tsx
product-card.tsxproduct-details.tsxproduct-grid.tsx
badge.tsxbutton.tsxcard.tsx
db.tsstripe.tsutils.ts
next.config.tspackage.jsontsconfig.json
page.tsx
page.tsx
page.tsx
globals.csslayout.tsxpage.tsx
blog-card.tsxblog-list.tsx
button.tsxcard.tsx
getting-started.mdxwelcome.mdx
mdx.tsutils.ts
next.config.tspackage.jsontsconfig.json
Server and Client Components#
Understanding the difference is fundamental to modern Next.js development:
Server Components
Client Components
Client Component Example#
Add 'use client' at the top for interactive components:
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}Building Your First Page#
Create a page in app/page.tsx. With Cache Components on (the default for new apps), data that isn't request-specific should be cached with "use cache" so the page can be prerendered:
import { cacheLife } from "next/cache";
export default async function HomePage() {
"use cache";
cacheLife("hours");
const data = await fetch("https://api.example.com/data");
const posts: { id: number; title: string; excerpt: string }[] =
await data.json();
return (
<main>
<h1>Welcome to Next.js</h1>
<div className="grid grid-cols-3 gap-4">
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
</main>
);
}Creating Layouts#
Layouts wrap multiple pages:
// app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "My Next.js App",
description: "Built with Next.js 16",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<nav className="border-b">
<div className="container mx-auto px-4 py-4">
<h2>My App</h2>
</div>
</nav>
<main>{children}</main>
</body>
</html>
);
}Routing & Navigation#
Next.js uses file-based routing. Here are common routing patterns:
page.tsx
page.tsx
page.tsx
page.tsx
app/page.tsx
- Home page
- /
- Yes
app/about/page.tsx
- About page
- /about
- Yes
app/contact/page.tsx
- Contact page
- /contact
- Yes
app/pricing/page.tsx
- Pricing page
- /pricing
- Yes
page.tsxpage.tsxpage.tsx
page.tsxpage.tsx
page.tsxpage.tsx
app/blog/[slug]/page.tsx
- Dynamic blog post
- /blog/post-1, /blog/my-article
- Yes
app/products/[id]/page.tsx
- Dynamic product page
- /products/123, /products/laptop
- Yes
app/users/[id]/page.tsx
- User profile page
- /users/john, /users/123
- Yes
app/users/[id]/settings/page.tsx
- User settings page
- /users/john/settings
- Yes
page.tsx
page.tsx
layout.tsxpage.tsx
page.tsx
page.tsx
layout.tsxpage.tsx
layout.tsxpage.tsx
app/dashboard/layout.tsx
- Dashboard layout wrapper
- Layout for /dashboard/*
- Yes
app/dashboard/page.tsx
- Dashboard home
- /dashboard
- Yes
app/dashboard/analytics/page.tsx
- Analytics page
- /dashboard/analytics
- Yes
app/admin/layout.tsx
- Admin layout wrapper
- Layout for /admin/*
- Yes
route.ts
route.ts
route.ts
route.ts
route.ts
route.ts
page.tsx
app/api/hello/route.ts
- Hello API endpoint
- GET /api/hello
- Yes
app/api/users/route.ts
- Users API endpoint
- GET, POST /api/users
- Yes
app/api/auth/login/route.ts
- Login API endpoint
- POST /api/auth/login
- Yes
app/api/products/[id]/route.ts
- Product API endpoint
- GET, PUT, DELETE /api/products/123
- Yes
Recommended Project Structure#
Here's how we suggest setting up your Next.js project following best practices:
page.tsx
loading.tsxpage.tsx
loading.tsxpage.tsx
page.tsx
page.tsx
layout.tsx
page.tsx
page.tsx
loading.tsxpage.tsx
layout.tsx
page.tsx
page.tsx
page.tsx
page.tsx
page.tsx
layout.tsx
route.ts
route.ts
route.ts
loading.tsxopengraph-image.tsxpage.tsx
layout.tsxpage.tsx
error.tsxfavicon.icoglobals.csslayout.tsxloading.tsxnot-found.tsxpage.tsxrobots.tssitemap.ts
login-form.tsxregister-form.tsx
header.tsxsidebar.tsx
footer.tsxheader.tsx
button.tsxcard.tsxinput.tsx...
config.tssession.tsproviders.ts
migrations.tsschema.tsseed.ts
auth.tsteam.tsuser.ts
api.tsauth.tsconstants.tsdb.tsemail.tsstripe.tsutils.tsvalidations.ts
use-api.tsuse-auth.tsuse-dashboard.tsuse-debounce.tsuse-local-storage.tsuse-media-query.tsuse-team.tsuse-toast.ts
api.tsauth.tsblog.tsdashboard.tsdatabase.tsindex.tsteam.tsuser.ts
proxy.ts
about.jpghero.jpg
logo.svg
next.config.tspackage.jsonpostcss.config.mjstsconfig.json
Layout Structure Analysis#
The layout hierarchy in this structure follows Next.js best practices:
1. Root Layout (app/layout.tsx)#
- Purpose: Wraps the entire application
- Contains:
<html>,<body>, global providers, navigation - Applies to: All routes in the application
2. Route Group Layouts#
(auth)/layout.tsx: Auth-specific layout (login forms, auth headers)(dashboard)/layout.tsx: Dashboard-specific layout (sidebar, dashboard header)(marketing)/layout.tsx: Marketing-specific layout (marketing header, footer, CTAs)- Purpose: Provide shared UI for all routes within each group
- Applies to: All routes within their respective route groups
3. Page-Specific Layouts#
blog/layout.tsx: Blog-specific layout (blog navigation, author info)- Purpose: Layout for specific feature sections
- Applies to: All routes within the blog section
4. Layout Nesting Order#
Root Layout (app/layout.tsx)
├── Auth Group Layout ((auth)/layout.tsx)
│ └── Auth Pages (/auth/login, /auth/register, etc.)
├── Dashboard Group Layout ((dashboard)/layout.tsx)
│ └── Dashboard Pages (/dashboard, /dashboard/analytics, etc.)
├── Marketing Group Layout ((marketing)/layout.tsx)
│ └── Marketing Pages (/about, /contact, /pricing, /privacy, /terms)
├── Blog Layout (blog/layout.tsx)
│ └── Blog Pages (/blog, /blog/[slug])
└── Home Page (/)Route Handlers under app/api return responses, not UI, so no layout wraps them.
5. Route Group Benefits#
- URL Structure: Route groups don't affect URLs
(auth)/auth/login/page.tsx→/auth/login(dashboard)/dashboard/analytics/page.tsx→/dashboard/analytics
- Shared Layouts: All routes in a group share the same layout
- Organization: Groups related routes without URL impact
- Separate root layouts: Each group can have its own root layout if you remove the top-level
app/layout.tsx
6. Layout Implementation Examples#
Here's how you would implement these layouts:
// app/layout.tsx - Root Layout
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Navigation />
{children}
<Footer />
</body>
</html>
);
}// app/(auth)/layout.tsx - Auth Group Layout
export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-gray-50">
<AuthHeader />
<main className="flex items-center justify-center py-12">
<div className="w-full max-w-md">{children}</div>
</main>
</div>
);
}// app/(dashboard)/layout.tsx - Dashboard Group Layout
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex h-screen">
<DashboardSidebar />
<div className="flex flex-1 flex-col">
<DashboardHeader />
<main className="flex-1 p-6">{children}</main>
</div>
</div>
);
}// app/(marketing)/layout.tsx - Marketing Group Layout
export default function MarketingLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen">
<MarketingHeader />
<main>{children}</main>
<MarketingFooter />
</div>
);
}Dynamic Routes#
// app/blog/[slug]/page.tsx
import { getPost, getPosts } from "@/lib/posts";
// Prerender every known post at build time.
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}Navigation#
import Link from "next/link";
export default function Nav() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/blog">Blog</Link>
<Link href="/about">About</Link>
</nav>
);
}Data Fetching#
Server-Side Fetching#
Since Next.js 15, fetch is not cached by default. Each request gets fresh data unless you opt in to caching.
// Not cached: runs on every request
async function getData() {
const res = await fetch("https://api.example.com/posts");
return res.json();
}
export default async function Page() {
const posts = await getData();
return <PostList posts={posts} />;
}With Cache Components enabled, uncached data like this must sit inside a <Suspense> boundary (see Streaming below), or the dev overlay flags the route as blocking.
Revalidation#
import { cacheLife } from "next/cache";
// Cache the result and revalidate it every hour
async function getData() {
"use cache";
cacheLife("hours");
const res = await fetch("https://api.example.com/posts");
return res.json();
}If your app does not use Cache Components, the previous model still works: pass { next: { revalidate: 3600 } } to fetch.
Streaming with Suspense#
import { Suspense } from "react";
async function PostList() {
const res = await fetch("https://api.example.com/posts");
const posts: { id: number; title: string }[] = await res.json();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
export default function Page() {
return (
<div>
<h1>Blog Posts</h1>
<Suspense fallback={<div>Loading...</div>}>
<PostList />
</Suspense>
</div>
);
}Styling#
Tailwind CSS (Recommended)#
export default function Button() {
return (
<button className="rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700">
Click me
</button>
);
}CSS Modules#
/* components/Button.module.css */
.button {
background-color: blue;
color: white;
padding: 10px 20px;
border-radius: 4px;
}// components/Button.tsx
import styles from "./Button.module.css";
export default function Button() {
return <button className={styles.button}>Click me</button>;
}Performance Optimization#
Image Optimization#
import Image from "next/image";
export default function Hero() {
return (
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
preload
/>
);
}Use preload only for the above-the-fold LCP image. It replaces the priority prop, which Next.js 16 deprecated.
Font Optimization#
import { Inter, Roboto_Mono } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
const robotoMono = Roboto_Mono({
subsets: ["latin"],
variable: "--font-roboto-mono",
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={`${inter.className} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
);
}Deployment#
Deploy to Vercel#
- Push your code to GitHub
- Import your repository on Vercel
- Vercel automatically detects Next.js and deploys
# Or deploy via CLI
npm i -g vercel
vercelProduction Build#
# Build for production
npm run build
# Start production server
npm startNext Steps#
Recommended Resources#
Essential resources to master Next.js development
Official Documentation
Community Resources
Advanced Features#
Performance & SEO
- • Image optimization with next/image
- • Font optimization with next/font
- • Bundle analysis and optimization
- • Core Web Vitals monitoring
App Router Features
- • Parallel Routes
- • Intercepting Routes
- • Route Groups
- • Loading UI and Error Boundaries
Conclusion#
This comprehensive guide covered Next.js 16 with the App Router, focusing on modern development practices. The examples use TypeScript and Tailwind CSS, but the concepts apply to any Next.js setup.
Next.js gives you routing, rendering, caching and deployment in one framework. Start small, read the docs for the version you have installed, and add features as your app needs them.
Happy coding!

