Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /Server Components
Your Progress0%
0 of 70 completed

NextJS Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication

Understanding Server Components

The default rendering model in Next.js App Router

One of the most transformative features in Next.js 15 is React Server Components. By default, every component you create is a Server Component—rendering on the server, accessing backend resources directly, and sending zero JavaScript to the client. This isn't just an optimization; it's a fundamental shift in how we build React applications. Server Components give you better performance, smaller bundles, and direct data access by default. Let's understand this powerful new paradigm!

What Are Server Components?

Server Components are React components that render exclusively on the server:

  • Run on the server: Execute during build or request time
  • Zero client JavaScript: Don't add to your bundle
  • Direct backend access: Can query databases, read files
  • Async by default: Can use async/await directly
  • Default in App Router: No directive needed

Server Components in Your App

Server Components are the default - no special marking needed

appImportant

Select a file or folder to see details

Key Principle: Server-First

Next.js follows a server-first approach. Components render on the server unless you explicitly opt into client-side with 'use client'. This means better performance by default!

Your First Server Component

Every component in Next.js App Router is a Server Component by default:

Basic Server Component

app/components/BlogPosts.tsx
// This is a Server Component (no directive needed!)
export default function BlogPosts() {
  return (
    <div>
      <h2>Latest Blog Posts</h2>
      <ul>
        <li>Understanding Server Components</li>
        <li>Next.js 15 Features</li>
        <li>React Performance Tips</li>
      </ul>
    </div>
  );
}

// ✅ Renders on server
// ✅ Sends only HTML to client
// ✅ Zero JavaScript in bundle

Async Server Component (The Magic!)

app/components/BlogPosts.tsx
// Server Components can be async!
async function BlogPosts() {
  // Direct data fetching - no useEffect needed!
  const posts = await fetch('https://api.example.com/posts')
    .then(res => res.json());

  return (
    <div>
      <h2>Latest Blog Posts</h2>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

export default BlogPosts;

// ✅ Async function - just works!
// ✅ Data fetched on server
// ✅ No loading states needed
// ✅ SEO-friendly

🎯 No 'use server' Needed

There's no 'use server' directive for components. Server Components are the default. You only need 'use client' when you want client-side interactivity.

Benefits of Server Components

1. Zero JavaScript on Client

Server Component code never reaches the browser:

❌ Traditional React (All Client)

TYPESCRIPT
// Component JS: 50KB
// Dependencies: 200KB
// Total: 250KB downloaded

function App() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(setData);
  }, []);
  
  return <div>{/* render */}</div>;
}

Bundle size: 250KB

✅ Server Component

TYPESCRIPT
// Component JS: 0KB (runs on server!)
// Dependencies: 0KB (server-side!)
// Total: Only HTML sent

async function App() {
  const data = await fetch('...')
    .then(res => res.json());
  
  return <div>{/* render */}</div>;
}

Bundle size: 0KB (just HTML!)

2. Direct Backend Access

app/dashboard/page.tsx
import { db } from '@/lib/database';
import { readFile } from 'fs/promises';

async function DashboardPage() {
  // Direct database access (no API route needed!)
  const users = await db.query('SELECT * FROM users');
  
  // File system access
  const config = await readFile('config.json', 'utf-8');
  
  // Environment variables (safely on server)
  const apiKey = process.env.SECRET_API_KEY;
  
  return (
    <div>
      <h1>Dashboard</h1>
      <p>Total Users: {users.length}</p>
    </div>
  );
}

export default DashboardPage;

// ✅ No API routes needed
// ✅ Secrets stay on server
// ✅ Direct database access

3. Automatic Code Splitting

Heavy dependencies stay on the server:

TYPESCRIPT
import { marked } from 'marked'; // 100KB library
import Prism from 'prismjs'; // 50KB library

async function BlogPost({ slug }) {
  const post = await getPost(slug);
  
  // These libraries run ONLY on server
  const html = marked(post.content);
  const highlighted = Prism.highlight(html, Prism.languages.javascript);
  
  return <div dangerouslySetInnerHTML={{ __html: highlighted }} />;
}

// ✅ marked + Prism = 0KB to client
// ✅ Processing done on server
// ✅ Client gets final HTML

4. Better Performance

  • Smaller bundles: No component JS sent to client
  • Faster initial load: Less to download and parse
  • Less client work: No rendering, no hydration for Server Components
  • Better for low-power devices: Server does the work

5. Improved SEO

TYPESCRIPT
async function ProductPage({ params }) {
  // Data available immediately for crawlers
  const product = await getProduct(params.id);
  
  return (
    <div>
      <h1>{product.title}</h1>
      <p>{product.description}</p>
      <img src={product.image} alt={product.title} />
    </div>
  );
}

// ✅ Content in initial HTML
// ✅ No waiting for client-side fetch
// ✅ Perfect for SEO

What Can Server Components Do?

✅ Server Components CAN:

  • Fetch data directly - await fetch, database queries
  • Access backend resources - file system, environment variables
  • Use async/await - async component functions
  • Import server-only libraries - database clients, heavy packages
  • Keep secrets secure - API keys, database credentials
  • Use any Node.js APIs - fs, path, crypto, etc.

❌ Server Components CANNOT:

  • Use useState, useEffect, or other hooks - state is client-side
  • Use event handlers - onClick, onChange, etc.
  • Use browser APIs - localStorage, window, document
  • Use Context providers - Context is for client state
  • Use browser-only libraries - libraries that need window/document
TYPESCRIPT
// ✅ GOOD: Server Component doing server things
async function GoodServerComponent() {
  const data = await fetch('https://api.example.com/data');
  const posts = await data.json();
  
  return <div>{posts.map(p => <div key={p.id}>{p.title}</div>)}</div>;
}

// ❌ BAD: Server Component trying to do client things
function BadServerComponent() {
  const [count, setCount] = useState(0); // ❌ No hooks!
  
  return (
    <button onClick={() => setCount(count + 1)}> {/* ❌ No onClick! */}
      Count: {count}
    </button>
  );
}

// ✅ GOOD: Use Client Component for interactivity
'use client';
import { useState } from 'react';

function GoodClientComponent() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

Practical Server Component Examples

Example 1: Blog Post with Direct Data Fetch

app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';

interface Post {
  id: string;
  title: string;
  content: string;
  author: string;
  publishedAt: string;
}

async function getPost(slug: string): Promise<Post | null> {
  const res = await fetch(`https://api.example.com/posts/${slug}`, {
    next: { revalidate: 60 }, // Cache for 60 seconds
  });
  
  if (!res.ok) return null;
  return res.json();
}

// This is a Server Component - async is allowed!
export default async function BlogPostPage({
  params,
}: {
  params: { slug: string };
}) {
  const post = await getPost(params.slug);
  
  if (!post) {
    notFound();
  }
  
  return (
    <article className="prose lg:prose-xl mx-auto px-4 py-8">
      <h1>{post.title}</h1>
      <div className="text-gray-600 mb-4">
        By {post.author} • {new Date(post.publishedAt).toLocaleDateString()}
      </div>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

// ✅ Data fetched on server
// ✅ SEO-friendly (content in HTML)
// ✅ No loading states needed
// ✅ No client-side JavaScript for this component

Example 2: Dashboard with Database Access

app/dashboard/page.tsx
import { db } from '@/lib/database';

async function getStats() {
  // Direct database query (no API route!)
  const [users, orders, revenue] = await Promise.all([
    db.query('SELECT COUNT(*) FROM users'),
    db.query('SELECT COUNT(*) FROM orders'),
    db.query('SELECT SUM(amount) FROM orders'),
  ]);
  
  return {
    totalUsers: users[0].count,
    totalOrders: orders[0].count,
    totalRevenue: revenue[0].sum,
  };
}

export default async function DashboardPage() {
  const stats = await getStats();
  
  return (
    <div className="p-8">
      <h1 className="text-3xl font-bold mb-8">Dashboard</h1>
      
      <div className="grid grid-cols-3 gap-6">
        <div className="bg-white rounded-lg shadow p-6">
          <h3 className="text-gray-600 mb-2">Total Users</h3>
          <p className="text-4xl font-bold">{stats.totalUsers}</p>
        </div>
        
        <div className="bg-white rounded-lg shadow p-6">
          <h3 className="text-gray-600 mb-2">Total Orders</h3>
          <p className="text-4xl font-bold">{stats.totalOrders}</p>
        </div>
        
        <div className="bg-white rounded-lg shadow p-6">
          <h3 className="text-gray-600 mb-2">Revenue</h3>
          <p className="text-4xl font-bold">
            ${stats.totalRevenue.toLocaleString()}
          </p>
        </div>
      </div>
    </div>
  );
}

// ✅ Direct database access
// ✅ Parallel queries with Promise.all
// ✅ No API routes needed
// ✅ Data ready on first render

Example 3: Product Page with Multiple Sources

app/products/[id]/page.tsx
async function getProductData(id: string) {
  // Fetch from multiple sources in parallel
  const [product, reviews, recommendations] = await Promise.all([
    fetch(`https://api.example.com/products/${id}`).then(r => r.json()),
    fetch(`https://api.example.com/products/${id}/reviews`).then(r => r.json()),
    fetch(`https://api.example.com/products/${id}/recommendations`).then(r => r.json()),
  ]);
  
  return { product, reviews, recommendations };
}

export default async function ProductPage({
  params,
}: {
  params: { id: string };
}) {
  const { product, reviews, recommendations } = await getProductData(params.id);
  
  return (
    <div className="container mx-auto px-4 py-8">
      {/* Product Details */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-12">
        <img
          src={product.image}
          alt={product.title}
          className="w-full rounded-lg"
        />
        <div>
          <h1 className="text-4xl font-bold mb-4">{product.title}</h1>
          <p className="text-3xl text-green-600 mb-6">${product.price}</p>
          <p className="text-gray-700 mb-6">{product.description}</p>
        </div>
      </div>
      
      {/* Reviews */}
      <div className="mb-12">
        <h2 className="text-2xl font-bold mb-6">Customer Reviews</h2>
        <div className="space-y-4">
          {reviews.map(review => (
            <div key={review.id} className="bg-white p-6 rounded-lg shadow">
              <div className="flex items-center gap-2 mb-2">
                <span className="font-semibold">{review.author}</span>
                <span className="text-yellow-500">★ {review.rating}</span>
              </div>
              <p className="text-gray-700">{review.content}</p>
            </div>
          ))}
        </div>
      </div>
      
      {/* Recommendations */}
      <div>
        <h2 className="text-2xl font-bold mb-6">You May Also Like</h2>
        <div className="grid grid-cols-4 gap-6">
          {recommendations.map(rec => (
            <a key={rec.id} href={`/products/${rec.id}`}>
              <img src={rec.image} alt={rec.title} className="rounded-lg" />
              <h3 className="mt-2 font-semibold">{rec.title}</h3>
              <p className="text-green-600">${rec.price}</p>
            </a>
          ))}
        </div>
      </div>
    </div>
  );
}

// ✅ Three API calls in parallel
// ✅ All data ready on first render
// ✅ No loading spinners needed
// ✅ Perfect for SEO

Data Fetching in Server Components

Basic Fetch

TYPESCRIPT
async function getData() {
  const res = await fetch('https://api.example.com/data');
  return res.json();
}

export default async function Page() {
  const data = await getData();
  return <div>{data.title}</div>;
}

With Caching

TYPESCRIPT
async function getData() {
  const res = await fetch('https://api.example.com/data', {
    next: { revalidate: 3600 }, // Cache for 1 hour
  });
  return res.json();
}

// Or no caching
async function getFreshData() {
  const res = await fetch('https://api.example.com/data', {
    cache: 'no-store', // Always fetch fresh
  });
  return res.json();
}

Parallel Fetching

TYPESCRIPT
async function Page() {
  // Fetch in parallel
  const [users, posts, comments] = await Promise.all([
    fetch('https://api.example.com/users').then(r => r.json()),
    fetch('https://api.example.com/posts').then(r => r.json()),
    fetch('https://api.example.com/comments').then(r => r.json()),
  ]);
  
  return (
    <div>
      <UserList users={users} />
      <PostList posts={posts} />
      <CommentList comments={comments} />
    </div>
  );
}

Sequential Fetching

TYPESCRIPT
async function Page({ params }) {
  // First, get the user
  const user = await fetch(`https://api.example.com/users/${params.id}`)
    .then(r => r.json());
  
  // Then, get their posts (depends on user ID)
  const posts = await fetch(`https://api.example.com/users/${user.id}/posts`)
    .then(r => r.json());
  
  return (
    <div>
      <h1>{user.name}</h1>
      <PostList posts={posts} />
    </div>
  );
}

Server Component Best Practices

1. Default to Server Components

Start with Server Components for everything. Only add 'use client' when you need interactivity, state, or browser APIs.

2. Keep Heavy Logic on Server

TYPESCRIPT
// ✅ Good: Heavy processing on server
import { marked } from 'marked'; // 100KB
import { minify } from 'html-minifier'; // 50KB

async function BlogPost({ slug }) {
  const post = await getPost(slug);
  const html = marked(post.markdown);
  const minified = minify(html);
  
  return <div dangerouslySetInnerHTML={{ __html: minified }} />;
}

// Libraries stay on server, client gets final HTML

3. Use Parallel Data Fetching

TYPESCRIPT
// ✅ Good: Parallel fetching
async function Page() {
  const [user, posts] = await Promise.all([
    getUser(),
    getPosts(),
  ]);
  // ...
}

// ❌ Bad: Sequential fetching (slower)
async function Page() {
  const user = await getUser();
  const posts = await getPosts(); // Waits for user first
  // ...
}

4. Handle Errors Properly

TYPESCRIPT
async function Page({ params }) {
  try {
    const data = await getData(params.id);
    return <div>{data.title}</div>;
  } catch (error) {
    console.error('Failed to fetch data:', error);
    return (
      <div>
        <h2>Failed to load data</h2>
        <p>Please try again later</p>
      </div>
    );
  }
}

// Or let error.tsx handle it
async function Page({ params }) {
  const data = await getData(params.id); // Throws if fails
  return <div>{data.title}</div>;
}
// Error caught by nearest error.tsx

5. Use TypeScript

TYPESCRIPT
interface Post {
  id: string;
  title: string;
  content: string;
}

async function getPosts(): Promise<Post[]> {
  const res = await fetch('https://api.example.com/posts');
  return res.json();
}

export default async function BlogPage() {
  const posts: Post[] = await getPosts();
  // posts is properly typed!
  return <div>{posts.map(p => <div key={p.id}>{p.title}</div>)}</div>;
}

Common Server Component Patterns

Pattern 1: Data Fetching Component

TYPESCRIPT
// Reusable data-fetching component
async function UserProfile({ userId }: { userId: string }) {
  const user = await fetch(`https://api.example.com/users/${userId}`)
    .then(r => r.json());
  
  return (
    <div className="flex items-center gap-4">
      <img src={user.avatar} alt={user.name} className="w-12 h-12 rounded-full" />
      <div>
        <h3 className="font-semibold">{user.name}</h3>
        <p className="text-sm text-gray-600">{user.email}</p>
      </div>
    </div>
  );
}

// Use it anywhere
export default async function Page() {
  return (
    <div>
      <h1>Team Members</h1>
      <UserProfile userId="1" />
      <UserProfile userId="2" />
      <UserProfile userId="3" />
    </div>
  );
}

Pattern 2: Layout with Data

TYPESCRIPT
// Layout can fetch data too!
async function DashboardLayout({ children }) {
  const user = await getCurrentUser();
  
  return (
    <div className="flex">
      <aside className="w-64">
        <div className="p-4">
          <img src={user.avatar} alt={user.name} />
          <p>{user.name}</p>
        </div>
        <nav>{/* ... */}</nav>
      </aside>
      <main className="flex-1">{children}</main>
    </div>
  );
}

Pattern 3: Streaming with Suspense

TYPESCRIPT
import { Suspense } from 'react';

async function SlowComponent() {
  await new Promise(resolve => setTimeout(resolve, 3000));
  const data = await getData();
  return <div>{data}</div>;
}

export default function Page() {
  return (
    <div>
      <h1>Dashboard</h1>
      
      {/* Show immediately */}
      <QuickStats />
      
      {/* Stream in when ready */}
      <Suspense fallback={<div>Loading chart...</div>}>
        <SlowComponent />
      </Suspense>
    </div>
  );
}

Key Takeaways

  • Server Components are the default - no directive needed
  • Zero JavaScript to client - only HTML/RSC payload sent
  • Can be async - await directly in component
  • Direct backend access - databases, files, secrets
  • No hooks or event handlers - those are client-side
  • Better performance - smaller bundles, faster loads
  • SEO-friendly - content in initial HTML
  • Server-first approach - use client only when needed

What's Next?

You've learned the foundation of Next.js's rendering model—Server Components! But you still need interactivity for buttons, forms, and dynamic UI. That's where Client Components come in.

In the next lesson, we'll explore Client Components with the 'use client' directive. You'll learn when to use them, how they differ from Server Components, and how to add interactivity to your applications while maintaining the performance benefits of Server Components.

🎯 Server-First Mindset

Adopt a server-first mindset. Start with Server Components for everything, then strategically add 'use client' only where you need interactivity. This approach gives you the best performance by default!

Test Your Understanding

Question 1 of 4

What is the default component type in Next.js App Router?

Master React Server Components in Next.js! Learn the default rendering model and build faster apps.

Previous
Error Handling with error.tsx
Next
Client Components with 'use client'

Master Server and Client Components

Join 2,000+ developers mastering Next.js component architecture. Get the next lesson on Client Components delivered to your inbox - 100% FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

NextJS Tutorials

0 of 70 completed

Your Progress0%

Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo