Next.js
React-based full-stack framework. Standard support for SSR, SSG, and ISR enabling high-performance web application development. Established as the #1 choice for enterprise adoption.
GitHub Overview
vercel/next.js
The React Framework
Topics
Star History
Framework
Next.js
Overview
Next.js is a React-based full-stack web application development framework.
Details
Next.js is a React-based full-stack web application development framework developed by Vercel (formerly Zeit). Since its release in 2016, it has been widely adopted as the de facto standard for React application development. It supports multiple rendering methods including Static Site Generation (SSG), Server-Side Rendering (SSR), and Incremental Static Regeneration (ISR), significantly improving performance, SEO, and developer experience. It provides two routing systems: App Router and Pages Router, and supports the latest React features including React Server Components. With built-in features like automatic code splitting, image optimization, TypeScript integration, API Routes, and middleware, you can build full-scale web applications without configuration. Currently adopted by world-class services like Nike, Netflix, Notion, and TikTok, it leads the forefront of web frontend development.
Pros and Cons
Pros
- Zero Configuration: Automatic setup for Webpack, Babel, TypeScript, etc.
- Multiple Rendering Methods: Flexible choice between SSG, SSR, and ISR
- Excellent Performance: Core Web Vitals improvement through automatic optimization
- Great SEO: Search engine compatibility through server-side rendering
- Rich Built-in Features: Image optimization, font optimization, code splitting, etc.
- Excellent Developer Experience: Hot Reload, TypeScript integration, ESLint integration
- Strong Ecosystem: Vercel integration and community support
- Latest React Support: Supports Server Components, Suspense, etc.
Cons
- Learning Curve: New concepts like App Router, Server Components
- Version Changes: Breaking changes in major updates
- Hosting Constraints: Requires Node.js environment for full functionality
- Over-engineering: Can be excessive for small projects
- Debugging Complexity: Server-client debugging challenges
- Vendor Lock-in: Risk of dependency on Vercel-specific features
Key Links
- Next.js Official Site
- Next.js Official Documentation
- Next.js GitHub Repository
- Next.js Learn
- Vercel
- Next.js Examples
Code Examples
Hello World (Pages Router)
// pages/index.js
export default function Home() {
return (
<div>
<h1>Hello, Next.js!</h1>
<p>A simple page using Pages Router.</p>
</div>
);
}
Hello World (App Router)
// app/page.js
export default function Home() {
return (
<div>
<h1>Hello, Next.js!</h1>
<p>A simple page using App Router.</p>
</div>
);
}
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Static Site Generation (SSG)
Pages Router
// pages/blog/[slug].js
export default function BlogPost({ post }) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
// Generate pages at build time
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`);
const post = await res.json();
return {
props: { post },
// Regenerate after 60 seconds (ISR)
revalidate: 60,
};
}
// Define paths for dynamic routes
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map((post) => ({
params: { slug: post.slug },
}));
return {
paths,
fallback: 'blocking', // Generate new paths on request
};
}
App Router
// app/blog/[slug]/page.js
export const revalidate = 60; // ISR configuration
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 } = await params;
// Data fetching (SSG)
const post = await fetch(`https://api.example.com/posts/${slug}`, {
cache: 'force-cache', // Default (can be omitted)
}).then((res) => res.json());
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}
Server-Side Rendering (SSR)
Pages Router
// pages/dashboard.js
export default function Dashboard({ user, posts }) {
return (
<div>
<h1>Dashboard</h1>
<p>Welcome, {user.name}</p>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
// Executed on server for each request
export async function getServerSideProps(context) {
const { req } = context;
// Get user info from session
const user = await getUserFromSession(req);
// Get user-specific data
const posts = await fetch(`https://api.example.com/users/${user.id}/posts`)
.then(res => res.json());
return {
props: { user, posts },
};
}
App Router
// app/dashboard/page.js
import { cookies, headers } from 'next/headers';
export default async function Dashboard() {
// Using dynamic APIs triggers SSR
const cookieStore = await cookies();
const headersList = await headers();
// Data fetching for each request
const user = await fetch('https://api.example.com/user', {
cache: 'no-store', // SSR configuration
headers: {
'Authorization': `Bearer ${cookieStore.get('token')?.value}`,
},
}).then(res => res.json());
const posts = await fetch(`https://api.example.com/users/${user.id}/posts`, {
cache: 'no-store',
}).then(res => res.json());
return (
<div>
<h1>Dashboard</h1>
<p>Welcome, {user.name}</p>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
API Routes
Pages Router
// pages/api/users/[id].js
export default async function handler(req, res) {
const { id } = req.query;
if (req.method === 'GET') {
try {
const user = await getUserById(id);
res.status(200).json(user);
} catch (error) {
res.status(404).json({ error: 'User not found' });
}
} else if (req.method === 'POST') {
try {
const updatedUser = await updateUser(id, req.body);
res.status(200).json(updatedUser);
} catch (error) {
res.status(500).json({ error: 'Update failed' });
}
} else {
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
App Router (Route Handlers)
// app/api/users/[id]/route.js
import { NextResponse } from 'next/server';
export async function GET(request, { params }) {
const { id } = await params;
try {
const user = await getUserById(id);
return NextResponse.json(user);
} catch (error) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
);
}
}
export async function POST(request, { params }) {
const { id } = await params;
const body = await request.json();
try {
const updatedUser = await updateUser(id, body);
return NextResponse.json(updatedUser);
} catch (error) {
return NextResponse.json(
{ error: 'Update failed' },
{ status: 500 }
);
}
}
Form Handling (Server Actions)
// app/contact/page.js
import { redirect } from 'next/navigation';
async function submitContact(formData) {
'use server'; // Server Action
const name = formData.get('name');
const email = formData.get('email');
const message = formData.get('message');
// Validation
if (!name || !email || !message) {
throw new Error('Please fill in all required fields');
}
// Save to database
await saveContact({ name, email, message });
// Redirect
redirect('/contact/success');
}
export default function ContactForm() {
return (
<form action={submitContact}>
<div>
<label htmlFor="name">Name:</label>
<input type="text" id="name" name="name" required />
</div>
<div>
<label htmlFor="email">Email:</label>
<input type="email" id="email" name="email" required />
</div>
<div>
<label htmlFor="message">Message:</label>
<textarea id="message" name="message" rows={4} required />
</div>
<button type="submit">Submit</button>
</form>
);
}
Data Revalidation and Cache Management
// app/admin/posts/page.js
import { revalidatePath, revalidateTag } from 'next/cache';
async function createPost(formData) {
'use server';
const title = formData.get('title');
const content = formData.get('content');
// Create new post
await fetch('https://api.example.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, content }),
next: { tags: ['posts'] }, // Tag for caching
});
// Invalidate cache for specific path
revalidatePath('/blog');
// Invalidate tagged cache
revalidateTag('posts');
}
export default async function AdminPosts() {
// Data fetching with tags
const posts = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] },
}).then(res => res.json());
return (
<div>
<h1>Post Management</h1>
<form action={createPost}>
<input name="title" placeholder="Title" required />
<textarea name="content" placeholder="Content" required />
<button type="submit">Create Post</button>
</form>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
Middleware
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
// Authentication check
const token = request.cookies.get('auth-token');
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
// Add response headers
const response = NextResponse.next();
response.headers.set('x-middleware', 'true');
return response;
}
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};