React 19 Deep Dive: Server Components, Actions, and the New React Paradigm



React 19 represents the biggest shift in React’s architecture since hooks. Server Components, Actions, and a new compilation model fundamentally change how we build React applications.

React development Photo by Lautaro Andreani on Unsplash

The Big Picture

React 19 embraces the server-first paradigm:

  • Server Components render on the server by default
  • Client Components opt-in with 'use client'
  • Actions handle mutations without API routes
  • React Compiler optimizes automatically

Let’s break down each major feature.

Server Components

Server Components run only on the server. They can:

  • Access databases directly
  • Read the file system
  • Keep secrets safe
  • Send less JavaScript to clients
// app/posts/page.jsx - Server Component (default)
import { db } from '@/lib/database';

export default async function PostsPage() {
  // Direct database access - no API needed
  const posts = await db.posts.findMany({
    orderBy: { createdAt: 'desc' },
    take: 10
  });

  return (
    <div>
      <h1>Latest Posts</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  );
}

Zero JavaScript sent to client for this component.

Coding workspace Photo by Christopher Gower on Unsplash

Client Components

When you need interactivity, add 'use client':

'use client';

import { useState } from 'react';

export function LikeButton({ postId, initialCount }) {
  const [count, setCount] = useState(initialCount);
  const [isLiked, setIsLiked] = useState(false);

  async function handleLike() {
    setIsLiked(!isLiked);
    setCount(prev => isLiked ? prev - 1 : prev + 1);
    
    await fetch(`/api/posts/${postId}/like`, {
      method: 'POST'
    });
  }

  return (
    <button onClick={handleLike}>
      {isLiked ? '❤️' : '🤍'} {count}
    </button>
  );
}

Server Actions

Actions eliminate the need for API routes for mutations:

// app/posts/new/page.jsx
import { redirect } from 'next/navigation';
import { db } from '@/lib/database';

export default function NewPostPage() {
  async function createPost(formData) {
    'use server';
    
    const title = formData.get('title');
    const content = formData.get('content');
    
    const post = await db.posts.create({
      data: { title, content }
    });
    
    redirect(`/posts/${post.id}`);
  }

  return (
    <form action={createPost}>
      <input name="title" placeholder="Title" required />
      <textarea name="content" placeholder="Content" required />
      <button type="submit">Publish</button>
    </form>
  );
}

Actions with useActionState

The new useActionState hook provides loading and error states:

'use client';

import { useActionState } from 'react';
import { submitForm } from './actions';

export function ContactForm() {
  const [state, formAction, isPending] = useActionState(submitForm, null);

  return (
    <form action={formAction}>
      <input name="email" type="email" required />
      <textarea name="message" required />
      
      <button disabled={isPending}>
        {isPending ? 'Sending...' : 'Send'}
      </button>
      
      {state?.error && <p className="error">{state.error}</p>}
      {state?.success && <p className="success">Message sent!</p>}
    </form>
  );
}

useOptimistic

Instant UI updates while waiting for server confirmation:

'use client';

import { useOptimistic } from 'react';
import { addComment } from './actions';

export function Comments({ comments }) {
  const [optimisticComments, addOptimisticComment] = useOptimistic(
    comments,
    (state, newComment) => [...state, { ...newComment, pending: true }]
  );

  async function handleSubmit(formData) {
    const comment = {
      id: crypto.randomUUID(),
      text: formData.get('text'),
      author: 'You'
    };
    
    addOptimisticComment(comment);
    await addComment(formData);
  }

  return (
    <div>
      {optimisticComments.map(comment => (
        <div key={comment.id} style=>
          <strong>{comment.author}:</strong> {comment.text}
        </div>
      ))}
      
      <form action={handleSubmit}>
        <input name="text" placeholder="Add comment..." />
        <button>Post</button>
      </form>
    </div>
  );
}

The React Compiler

React Compiler (formerly React Forget) eliminates the need for manual memoization:

// Before: Manual optimization
const MemoizedComponent = memo(function Component({ items }) {
  const sorted = useMemo(() => items.sort(), [items]);
  const handleClick = useCallback(() => {
    console.log(sorted);
  }, [sorted]);
  
  return <List items={sorted} onClick={handleClick} />;
});

// After: React Compiler handles it
function Component({ items }) {
  const sorted = items.sort();
  const handleClick = () => console.log(sorted);
  
  return <List items={sorted} onClick={handleClick} />;
}
// Compiler automatically adds memoization where beneficial

Enable in next.config.js:

module.exports = {
  experimental: {
    reactCompiler: true
  }
};

New Hooks Summary

HookPurpose
useRead promises and context
useActionStateTrack action state
useOptimisticOptimistic updates
useFormStatusForm submission state

Migration Strategy

  1. Start with new projects - Use the app router from day one
  2. Incremental adoption - Move routes one at a time
  3. Identify client boundaries - Only add 'use client' where needed
  4. Remove API routes - Replace with Server Actions

Common Patterns

Data Fetching

// Parallel data fetching
async function Dashboard() {
  const [user, stats, notifications] = await Promise.all([
    getUser(),
    getStats(),
    getNotifications()
  ]);

  return (
    <div>
      <Header user={user} />
      <Stats data={stats} />
      <Notifications items={notifications} />
    </div>
  );
}

Streaming with Suspense

import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      <Header />
      <Suspense fallback={<PostsSkeleton />}>
        <Posts />
      </Suspense>
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments />
      </Suspense>
    </div>
  );
}

Conclusion

React 19 is a paradigm shift. Server-first rendering, type-safe actions, and automatic optimization represent the future of React development. Start experimenting today—the patterns you learn will define React for years to come.


What’s your favorite React 19 feature? Let us know in the comments!

이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)