Next.js 14: Server Components & App Router
Introduction to Next.js 14
Next.js 14 represents a major evolution in React development, introducing React Server Components by default, enhanced App Router, Server Actions, and significant performance improvements. This guide covers everything you need to build modern, production-ready applications.
1. App Router vs Pages Router
Next.js 14 fully embraces the App Router (app directory), though Pages Router is still supported:
App Router Structure
app/
├── layout.js // Root layout
├── page.js // Home page
├── loading.js // Loading UI
├── error.js // Error handling
├── not-found.js // 404 page
├── blog/
│ ├── layout.js
│ ├── page.js
│ └── [slug]/
│ └── page.js // Dynamic route
└── api/
└── route.js // API route
Benefits of App Router
- Server Components by default (better performance)
- Nested layouts and templates
- Built-in loading and error states
- Streaming and Suspense support
- Improved data fetching patterns
2. React Server Components
Server Components render on the server and send only HTML to the client, reducing JavaScript bundle size and improving performance.
Server Component Example
// app/posts/page.js (Server Component by default)
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
cache: 'no-store' // or 'force-cache'
});
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<div>
<h1>Blog Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
Client Component
// components/Counter.js
'use client'
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
- Need interactivity and event listeners
- Using hooks (useState, useEffect, etc.)
- Using browser-only APIs
- Relying on React class components
3. Data Fetching Patterns
Server-side Data Fetching
// app/users/[id]/page.js
async function getUser(id) {
const res = await fetch(`https://api.example.com/users/${id}`, {
next: { revalidate: 60 } // Revalidate every 60 seconds
});
if (!res.ok) throw new Error('Failed to fetch user');
return res.json();
}
export default async function UserPage({ params }) {
const user = await getUser(params.id);
return <div>{user.name}</div>;
}
Parallel Data Fetching
async function getData() {
// Fetch in parallel
const [posts, users, comments] = await Promise.all([
fetch('/api/posts').then(r => r.json()),
fetch('/api/users').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { posts, users, comments };
}
Streaming with Suspense
// app/dashboard/page.js
import { Suspense } from 'react';
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<LoadingSkeleton />}>
<Stats />
</Suspense>
<Suspense fallback={<LoadingChart />}>
<Chart />
</Suspense>
</div>
);
}
4. Server Actions
Server Actions enable server-side mutations without API routes:
// app/actions.js
'use server'
export async function createPost(formData) {
const title = formData.get('title');
const content = formData.get('content');
// Database operation
await db.posts.create({
data: { title, content }
});
revalidatePath('/blog');
redirect('/blog');
}
// app/new-post/page.js
import { createPost } from '../actions';
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" required />
<textarea name="content" required />
<button type="submit">Create Post</button>
</form>
);
}
5. Layouts and Templates
Root Layout
// app/layout.js
export const metadata = {
title: 'My App',
description: 'An awesome Next.js app'
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<nav>
<Link href="/">Home</Link>
<Link href="/blog">Blog</Link>
</nav>
{children}
<footer>© 2025</footer>
</body>
</html>
);
}
Nested Layouts
// app/blog/layout.js
export default function BlogLayout({ children }) {
return (
<div className="blog-container">
<aside>Sidebar</aside>
<main>{children}</main>
</div>
);
}
6. Route Handlers (API Routes)
// app/api/users/route.js
import { NextResponse } from 'next/server';
export async function GET(request) {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
const users = await fetchUsers(id);
return NextResponse.json(users);
}
export async function POST(request) {
const data = await request.json();
const user = await createUser(data);
return NextResponse.json(user, { status: 201 });
}
7. Metadata and SEO
Static Metadata
// app/blog/page.js
export const metadata = {
title: 'Blog',
description: 'Read our latest posts',
openGraph: {
title: 'Blog',
description: 'Read our latest posts',
images: ['/og-image.jpg']
}
};
Dynamic Metadata
export async function generateMetadata({ params }) {
const post = await getPost(params.id);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
images: [post.coverImage]
}
};
}
8. Performance Optimization
Image Optimization
import Image from 'next/image';
export default function Profile() {
return (
<Image
src="/profile.jpg"
alt="Profile"
width={500}
height={500}
priority // Load image with high priority
placeholder="blur"
blurDataURL="data:image/..."
/>
);
}
Font Optimization
// app/layout.js
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap'
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}
9. Caching Strategies
- Default: fetch() results are cached indefinitely
- No caching:
{ cache: 'no-store' } - Revalidation:
{ next: { revalidate: 3600 } } - On-demand: Use
revalidatePath()orrevalidateTag()
10. Deployment
Vercel (Recommended)
# Install Vercel CLI
npm i -g vercel
# Deploy
vercel
# Production deployment
vercel --prod
Self-Hosting
# Build for production
npm run build
# Start production server
npm start
# Or use standalone output
# next.config.js
module.exports = {
output: 'standalone'
}
- Use Server Components by default, Client Components when needed
- Leverage caching and revalidation for better performance
- Implement proper error boundaries and loading states
- Use TypeScript for better type safety
- Monitor Core Web Vitals with analytics
- Implement proper SEO with metadata
Conclusion
Next.js 14 with Server Components and App Router represents the cutting edge of React development. By understanding these patterns, you can build faster, more maintainable applications with better user experiences.