Skip to content
C.W.K.
Stream
Lesson 02 of 08 · published

Why Server Components?

~20 min · bundle, perf, security

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

The bundle problem they solve

Traditional React ships every component's code to the browser as JavaScript. Markdown parsers, syntax highlighters, date utilities — all sent to every user, even though they only transform data once.

The four concrete wins

  1. Smaller bundles. Server Components contribute zero bytes to the client bundle. Heavy data-shaping libs stay on the server.
  2. Direct data access. Query Postgres, read files, call internal services without an API hop — same data center, same process, microsecond latency.
  3. Security. API keys, DB credentials, internal URLs never leave the server.
  4. Streaming. Server Components can stream HTML progressively. Cheap parts ship instantly, slow parts stream as data arrives.

The real-world bundle math

A blog post page using marked (~200KB) and highlight.js (~800KB) on the client costs every visitor 1MB of JavaScript. The same page as a Server Component ships zero bytes for those libraries; the rendered HTML is what reaches the browser.

Look for client dependencies whose only job is to transform data into display output. Markdown parsing, syntax highlighting, and large date libraries can often stay on the server while the browser receives only their rendered result. Measure the resulting client chunk, not just the source diff.

Zero client bytes does not mean zero cost. The server still runs the library and sends HTML. If the work repeats on every request, compute can become the new bottleneck. Cache stable results and measure response time along with bundle size. Move one heavy display dependency across the boundary and compare JavaScript transfer, hydration work, server duration, and a cached repeat request. A successful optimization improves the total path rather than moving an invisible bill.

Code

Heavy libraries stay on the server·tsx
import { marked } from 'marked';
import hljs from 'highlight.js';
import { format } from 'date-fns';
import { db } from '@/lib/db';

export default async function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = await db.post.findUnique({ where: { slug } });
  if (!post) return null;

  const html = marked(post.content, {
    highlight: (code, lang) => hljs.highlight(code, { language: lang }).value,
  });

  return (
    <article>
      <time>{format(post.date, 'MMMM d, yyyy')}</time>
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </article>
  );
}
// Client receives: rendered HTML. Bundle impact: 0 bytes.
Read secrets safely·tsx
// app/admin/page.tsx — only reachable behind auth, but still a great place to see secret access
export default async function Admin() {
  const stripe = process.env.STRIPE_SECRET_KEY!;
  const { data } = await fetch('https://api.stripe.com/v1/customers', {
    headers: { Authorization: `Bearer ${stripe}` },
    cache: 'no-store',
  }).then(r => r.json());
  return <CustomersTable rows={data} />;
}

External links

Exercise

Find one heavy dependency in your package.json that's used purely for data shaping (markdown, syntax highlight, date util). Move its usage into a Server Component and confirm bundle analyzer shows it dropping out of the client bundle.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.