Next.js
Next.js is a React framework for building full-stack web applications with server-side rendering and static site generation.
Quick Start
App Router (Next.js 13+)
File Structure
app/
├── layout.js # Root layout
├── page.js # Home page
├── about/
│ └── page.js # /about route
└── blog/
├── [slug]/
│ └── page.js # /blog/[slug] dynamic route
└── page.js # /blog route
Root Layout
// app/layout.js
export const metadata = {
title: 'My App',
description: 'My awesome app',
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Page Component
// app/page.js
export default function HomePage() {
return (
<main>
<h1>Welcome to Next.js</h1>
</main>
);
}
Server vs Client Components
Server Component (Default)
// app/blog/page.js
async function getBlogPosts() {
const res = await fetch('https://api.example.com/posts');
return res.json();
}
export default async function BlogPage() {
const posts = await getBlogPosts();
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</div>
);
}
Client Component
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Data Fetching
Server-side Fetch
// Default: cache enabled
async function getData() {
const res = await fetch('https://api.example.com/data');
return res.json();
}
// Revalidate every 60 seconds
async function getDataRevalidate() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 60 }
});
return res.json();
}
// No caching
async function getDataNoCache() {
const res = await fetch('https://api.example.com/data', {
cache: 'no-store'
});
return res.json();
}
Dynamic Routes
// 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 async function BlogPost({ params }) {
const { slug } = params;
const post = await fetch(`https://api.example.com/posts/${slug}`).then(res => res.json());
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
API Routes
// app/api/hello/route.js
export async function GET(request) {
return Response.json({ message: 'Hello World' });
}
export async function POST(request) {
const body = await request.json();
return Response.json({
message: 'Data received',
data: body
});
}
Dynamic API Routes
// app/api/posts/[id]/route.js
export async function GET(request, { params }) {
const { id } = params;
return Response.json({
postId: id
});
}
Navigation
Link Component
import Link from 'next/link';
export default function Nav() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/blog/hello-world">Blog Post</Link>
</nav>
);
}
Programmatic Navigation
'use client';
import { useRouter } from 'next/navigation';
export default function Page() {
const router = useRouter();
return (
<button onClick={() => router.push('/dashboard')}>
Go to Dashboard
</button>
);
}
Image Optimization
import Image from 'next/image';
export default function Avatar() {
return (
<Image
src="/profile.jpg"
alt="Profile"
width={500}
height={500}
priority
/>
);
}
Metadata
// Static metadata
export const metadata = {
title: 'My Page',
description: 'Page description',
};
// Dynamic metadata
export async function generateMetadata({ params }) {
const post = await getPost(params.id);
return {
title: post.title,
description: post.excerpt,
};
}
Loading States
// app/dashboard/loading.js
export default function Loading() {
return <div>Loading dashboard...</div>;
}
Error Handling
// app/dashboard/error.js
'use client';
export default function Error({ error, reset }) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
Middleware
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const response = NextResponse.next();
// Add custom header
response.headers.set('x-custom-header', 'value');
return response;
}
export const config = {
matcher: '/api/:path*',
};
Environment Variables
// Client-side (NEXT_PUBLIC_ prefix required)
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
// Server-side only
const dbUrl = process.env.DATABASE_URL;
Configuration
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ['example.com'],
},
async redirects() {
return [
{
source: '/old-route',
destination: '/new-route',
permanent: true,
},
];
},
};
module.exports = nextConfig;