React 19가 가져온 근본적인 변화를 살펴봅니다. Actions, Server Components, 새로운 훅, React Compiler까지 React의 새로운 패러다임을 이해합니다.
React 18이 동시성 렌더링(Concurrent Rendering)이라는 새로운 기반을 마련한 지 약 2년 만에, React 19가 2024년 12월 5일 정식 출시되었습니다. 이번 메이저 업데이트는 단순한 기능 추가가 아닌, React 애플리케이션을 설계하고 구축하는 방식 자체를 근본적으로 재정의합니다.
이 장에서는 React 19가 등장한 배경, 핵심 변경사항의 전체 그림, 그리고 이 시리즈에서 다룰 내용을 개괄적으로 살펴봅니다.
React의 메이저 버전은 각각 명확한 패러다임 전환을 가져왔습니다.
| 버전 | 핵심 변화 | 시기 |
|---|---|---|
| React 16 | Fiber 아키텍처, Error Boundaries, Portals | 2017 |
| React 16.8 | Hooks 도입 | 2019 |
| React 18 | Concurrent Rendering, Suspense for Data Fetching | 2022 |
| React 19 | Server Components, Actions, Compiler | 2024 |
React 16.8의 Hooks가 클래스 컴포넌트에서 함수 컴포넌트로의 전환을 이끌었다면, React 19는 클라이언트 중심 렌더링에서 서버-클라이언트 하이브리드 아키텍처로의 전환을 이끕니다.
기존 React 애플리케이션에서 데이터를 가져오는 코드를 떠올려봅시다.
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetchUser(userId)
.then((data) => {
if (!cancelled) {
setUser(data);
setLoading(false);
}
})
.catch((err) => {
if (!cancelled) {
setError(err);
setLoading(false);
}
});
return () => { cancelled = true; };
}, [userId]);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return <div>{user?.name}</div>;
}로딩 상태 관리, 에러 처리, 경쟁 조건(Race Condition) 방지를 위한 취소 로직까지, 단순한 데이터 페칭에도 상당한 보일러플레이트가 필요합니다. React 19에서는 이 패턴이 극적으로 단순해집니다.
async function UserProfile({ userId }: { userId: string }) {
const user = await fetchUser(userId);
return <div>{user.name}</div>;
}Server Component에서는 async/await를 직접 사용할 수 있고, 로딩과 에러 처리는 Suspense와 Error Boundary가 선언적으로 담당합니다.
폼 제출 역시 useState와 useEffect의 조합으로 처리해왔습니다.
function CreatePost() {
const [title, setTitle] = useState('');
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setIsPending(true);
setError(null);
try {
await createPost({ title });
setTitle('');
} catch (err) {
setError('게시물 생성에 실패했습니다.');
} finally {
setIsPending(false);
}
}
return (
<form onSubmit={handleSubmit}>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<button disabled={isPending}>
{isPending ? '저장 중...' : '저장'}
</button>
{error && <p>{error}</p>}
</form>
);
}React 19의 Actions와 새로운 훅들은 이 패턴을 획기적으로 개선합니다.
function CreatePost() {
const [state, formAction, isPending] = useActionState(
async (prevState: FormState, formData: FormData) => {
const title = formData.get('title') as string;
const result = await createPost({ title });
if (!result.success) return { error: '게시물 생성에 실패했습니다.' };
return { error: null };
},
{ error: null }
);
return (
<form action={formAction}>
<input name="title" />
<SubmitButton />
{state.error && <p>{state.error}</p>}
</form>
);
}상태 관리, 로딩 처리, 에러 핸들링이 하나의 훅으로 통합되었습니다.
클라이언트 사이드 렌더링(CSR)에서는 모든 코드가 브라우저로 전송됩니다. 마크다운 파서, 날짜 라이브러리, 유효성 검사 로직 등 서버에서만 실행해도 충분한 코드까지 클라이언트 번들에 포함됩니다.
React Server Components는 서버에서만 실행되는 컴포넌트를 도입하여 이 문제를 근본적으로 해결합니다. 서버 컴포넌트에서 사용하는 라이브러리는 클라이언트 번들에 포함되지 않습니다.
React 개발자라면 useMemo, useCallback, React.memo를 어디에 적용해야 할지 고민한 경험이 있을 것입니다. 성능 최적화를 위한 메모이제이션은 코드 복잡도를 높이고, 잘못 적용하면 오히려 성능을 저하시킵니다.
React Compiler는 빌드 타임에 자동으로 메모이제이션을 적용하여 이 부담을 완전히 제거합니다.
React 19의 변경사항은 크게 다섯 가지 축으로 구성됩니다.
서버에서만 실행되는 컴포넌트입니다. 데이터베이스 접근, 파일 시스템 읽기, 외부 API 호출을 컴포넌트 안에서 직접 수행할 수 있으며, 관련 JavaScript는 클라이언트에 전송되지 않습니다.
// 기본적으로 모든 컴포넌트는 Server Component
async function ArticleList() {
const articles = await db.articles.findMany({
orderBy: { createdAt: 'desc' },
take: 10,
});
return (
<ul>
{articles.map((article) => (
<li key={article.id}>
<ArticleCard article={article} />
</li>
))}
</ul>
);
}클라이언트에서 호출하면 서버에서 실행되는 비동기 함수입니다. 폼 제출, 데이터 변경(Mutation) 등의 서버 측 로직을 안전하게 처리합니다.
'use server';
export async function updateArticle(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
await db.articles.update({
where: { id: formData.get('id') as string },
data: { title, content },
});
revalidatePath('/articles');
}| 훅/API | 역할 |
|---|---|
useActionState | 폼 액션의 상태(결과, 펜딩) 관리 |
useFormStatus | 부모 폼의 제출 상태 조회 |
useOptimistic | 낙관적 UI 업데이트 |
use() | Promise나 Context를 조건부로 읽기 |
빌드 타임에 컴포넌트를 분석하여 자동으로 메모이제이션을 적용하는 최적화 컴파일러입니다. useMemo, useCallback, React.memo를 수동으로 작성할 필요가 없어집니다.
forwardRef 래퍼가 불필요해졌습니다.<title>, <meta> 등을 컴포넌트 내에서 직접 렌더링하면 자동으로 <head>로 이동합니다.preload(), preinit() 등으로 리소스 로딩을 세밀하게 제어합니다.React 19의 가장 본질적인 변화는 컴포넌트가 실행되는 위치를 명시적으로 구분한다는 점입니다.
두 개의 디렉티브가 이 경계를 정의합니다.
'use client': 이 파일의 컴포넌트는 클라이언트에서 실행됩니다. useState, useEffect 등 클라이언트 훅을 사용할 수 있습니다.'use server': 이 함수는 서버에서 실행됩니다. 클라이언트에서 호출하면 자동으로 서버 요청이 발생합니다.디렉티브가 없는 컴포넌트는 기본적으로 Server Component로 취급됩니다. 이는 React의 기본값이 "서버 우선"으로 전환되었음을 의미합니다.
React 19는 출시 이후에도 빠르게 진화하고 있습니다.
React 19.1 (2025년 6월)에서는 안정성 개선과 버그 수정이 이루어졌습니다.
React 19.2 (2025년 10월)에서는 다음과 같은 주요 기능이 추가되었습니다.
<Activity /> 컴포넌트: 숨겨진 상태에서도 컴포넌트의 state를 유지하며 프리렌더링할 수 있는 새로운 컴포넌트입니다.useEffectEvent: Effect의 의존성에서 이벤트 로직을 분리하여 불필요한 재실행을 방지합니다.이 시리즈는 React 19의 모든 핵심 기능을 깊이 있게 다룹니다. 각 장은 독립적으로 읽을 수 있지만, 순서대로 읽으면 React 19의 전체 패러다임을 체계적으로 이해할 수 있습니다.
| 장 | 주제 | 핵심 내용 |
|---|---|---|
| 1장 | 소개 | React 19의 등장 배경과 전체 그림 |
| 2장 | RSC 아키텍처 | Server Components의 동작 원리와 설계 철학 |
| 3장 | Server Actions | 서버 측 데이터 변경과 폼 처리 |
| 4장 | use() API | Promise와 Context의 새로운 소비 방식 |
| 5장 | 새로운 훅 | useActionState, useFormStatus, useOptimistic |
| 6장 | Suspense | 고급 패턴과 스트리밍 SSR |
| 7장 | React Compiler | 자동 최적화의 원리와 적용 |
| 8장 | API 변경 | ref 개선, 메타데이터, 리소스 로딩 |
| 9장 | 성능 최적화 | 실전 최적화 전략과 베스트 프랙티스 |
| 10장 | 마이그레이션 | React 18에서 19로의 업그레이드 가이드 |
| 11장 | 실전 프로젝트 | React 19 기능을 활용한 풀스택 앱 |
이 시리즈를 최대한 활용하려면 다음 지식이 필요합니다.
React 19는 프레임워크에 독립적이지만, 실전에서는 Next.js, Remix 등과 함께 사용되는 경우가 대부분입니다. 이 시리즈에서는 Next.js App Router를 기준으로 예제를 구성하되, 프레임워크에 종속되지 않는 핵심 개념에 집중합니다.
다음 장에서는 React 19의 가장 핵심적인 변화인 React Server Components의 아키텍처를 심층적으로 분석합니다.
이 글이 도움이 되셨나요?
React Server Components의 동작 원리, 직렬화 프로토콜, 번들 전략, 합성 규칙을 심층적으로 분석합니다. 서버와 클라이언트의 경계를 이해합니다.
Server Actions의 동작 원리, 폼 처리 패턴, 데이터 뮤테이션, 에러 핸들링, 보안 고려사항을 실전 코드와 함께 다룹니다.
React 19의 use() API로 Promise와 Context를 조건부로 소비하는 방법, 서버-클라이언트 스트리밍 패턴, 기존 훅과의 차이를 다룹니다.