← Back to Guides

Next.js 14: Server Components & App Router

📖 18 min read | 📅 Updated: January 2025 | 🏷️ Web Development

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

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>
  );
}
💡 When to use Client 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

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'
}
🚀 Best Practices:

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.