DEV Community

kouta222
kouta222

Posted on

πŸš€ Introduction to Next.js Server Components for Beginners

Next.js Server Components (RSC) are a new way to build modern web apps that are faster, more efficient, and more secure. They help reduce JavaScript bundle sizes and improve initial load times.

In this article, you’ll learn:

  • What Server Components are
  • When to use Server vs. Client Components
  • The difference between Server Components and Server-Side Rendering (SSR)
  • How Server Components work with CDNs
  • How Server Functions and Actions improve data handling

πŸ“¦ What Are Server Components?

Server Components are React components that only run on the server. They never get shipped to the browser.

What does that mean?

  • The browser never downloads the component’s code.
  • Only the rendered HTML or data is sent to the browser.
  • This makes the app faster and more secure.

βœ… Benefits of Server Components

Problems with Client-Side Rendering (CSR)

In CSR:

  • JavaScript bundles get big.
  • The browser must download, parse, and execute a lot of JavaScript.
  • The initial display (FCP: First Contentful Paint) is often slow.

Why Server Components Are Better

  • βœ… Smaller JavaScript Bundles: Server Components are not included in the client bundle.
  • βœ… Faster FCP: Less JavaScript = faster initial loading.
  • βœ… Progressive Rendering: Content can start showing while other parts are still loading.
  • βœ… Secure Data Fetching: You can safely use API keys and database calls on the server.
  • βœ… CDN Caching: The rendered output can be stored on a CDN for super-fast delivery.

🌐 Server Components and CDNs

One of the biggest advantages of Server Components is that their rendered results can be cached in a CDN.

How It Works:

  • Server Components run on the server and generate HTML or RSC Payload (React’s special format).
  • This output can be uploaded to a CDN (Content Delivery Network).
  • The CDN delivers the content from the nearest edge location to the user.

What Does the Browser Receive?

  • The browser only gets the final HTML or RSC Payload.
  • The original Server Component logic never gets sent to the client.

Why This Is Great:

  • ⚑ Fast load speeds like static sites.
  • πŸ”’ Secure: API keys and secrets stay on the server.
  • πŸ“¦ Lightweight bundles since no server logic is included.

πŸ–₯️ Server Components vs. Client Components

Feature Server Components Client Components
Run Location Server Browser
JavaScript Bundle Not included Included
Use Cases Data fetching, secure API calls UI interactions, state, browser APIs
Example Render markdown, fetch database Handle clicks, form input

Use Client Components when you need:

  • State management (e.g. useState, onClick).
  • Lifecycle hooks (e.g. useEffect).
  • Browser APIs (e.g. window, localStorage).

Use Server Components when you need:

  • Database/API fetching.
  • Secure data handling (API keys, tokens).
  • Smaller bundles and faster initial paint (FCP).

πŸ› οΈ Server Components vs. Server-Side Rendering (SSR)

Feature Server Components Server-Side Rendering (SSR)
Rendering Server, streamed as RSC Payload Server, full HTML
JavaScript Only Client Components sent Full JavaScript sent
Initial HTML Fast preview + placeholders Fully rendered
Goal Minimize JS, improve load speed Fully hydrate the page

Key Difference:

Server Components focus on reducing JavaScript bundles and improving streaming. SSR focuses on delivering full HTML and hydrating everything.


πŸ” Example: Markdown Rendering with Server Components

import marked from 'marked'; // Not sent to browser
import sanitizeHtml from 'sanitize-html'; // Not sent to browser

async function Page({page}) {
  const content = await file.readFile(`${page}.md`);
  return <div>{sanitizeHtml(marked(content))}</div>;
}
Enter fullscreen mode Exit fullscreen mode

βœ”οΈ Expensive libraries like marked and sanitize-html stay on the server.
βœ”οΈ The client only receives the final HTML.

πŸ“‘ Example: Fetching Data with Server Components

import db from './database';

async function Note({id}) {
  const note = await db.notes.get(id);
  return (
    <div>
      <Author id={note.authorId} />
      <p>{note.content}</p>
    </div>
  );
}

async function Author({id}) {
  const author = await db.authors.get(id);
  return <span>By: {author.name}</span>;
}
Enter fullscreen mode Exit fullscreen mode

βœ”οΈ Data is fetched directly on the server.
βœ”οΈ The browser only sees the rendered result.

⏳ Async Server Components and Streaming

Server Components support async/await directly.


// Server Component
async function Page({id}) {
  const note = await db.notes.get(id);
  const commentsPromise = db.comments.get(note.id);

  return (
    <div>
      {note.content}
      <Suspense fallback={<p>Loading Comments...</p>}>
        <Comments commentsPromise={commentsPromise} />
      </Suspense>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
// Client Component
"use client";
import {use} from 'react';

function Comments({commentsPromise}) {
  const comments = use(commentsPromise);
  return comments.map(comment => <p>{comment}</p>);
}
Enter fullscreen mode Exit fullscreen mode

βœ”οΈ High-priority data (like note) is fetched first.
βœ”οΈ Lower-priority data (like comments) can load later, improving perceived speed.

βš™οΈ Server Functions and Actions

Server Functions let Client Components call functions that run on the server.


// Server Component
import Button from './Button';

function EmptyNote() {
  async function createNoteAction() {
    "use server";
    await db.notes.create();
  }

  return <Button onClick={createNoteAction} />;
}
Enter fullscreen mode Exit fullscreen mode

Example: Client Actions

Β₯
"use server";
export async function updateName(name) {
  if (!name) return {error: 'Name is required'};
  await db.users.updateName(name);
}

"use client";
import {updateName} from './actions';

function UpdateName() {
  const [name, setName] = useState('');
  const [error, setError] = useState(null);
  const [isPending, startTransition] = useTransition();

  const submitAction = async () => {
    startTransition(async () => {
      const {error} = await updateName(name);
      if (error) setError(error);
      else setName('');
    });
  };

  return (
    <form action={submitAction}>
      <input type="text" name="name" disabled={isPending} />
      {error && <span>Failed: {error}</span>}
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

βœ”οΈ Server logic stays on the server.
βœ”οΈ The client can safely trigger these functions without exposing secrets.

⚑ useActionState for Easier Server Calls

You can simplify Server Function calls using useActionState.


"use client";
import {updateName} from './actions';

function UpdateName() {
  const [state, submitAction, isPending] = useActionState(updateName, {error: null});

  return (
    <form action={submitAction}>
      <input type="text" name="name" disabled={isPending}/>
      {state.error && <span>Failed: {state.error}</span>}
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

βœ”οΈ Cleaner syntax
βœ”οΈ Built-in loading and response management

summary

Feature Purpose
Server Components Run on the server, reduce JS, secure data, CDN caching
Client Components Handle UI, state, and browser APIs
Server Functions Server-side logic triggered from the client
Actions Safely manage server calls and loading states

πŸ“š References
https://react.dev/reference/rsc/server-functions

https://react.dev/reference/rsc/server-components

Top comments (0)