오늘 바로 적용해야 할 React 19 렌더링 업그레이드 10가지
더 빠른 첫 페인트, 간단한 폼, 그리고 더 효율적인 하이드레이션을 위한 실용적인 체크리스트 — 오후 한 시간이면 설정할 수 있는 업그레이드들
React 19의 10가지 업그레이드 — 서버 컴포넌트, 액션, use(), 자산 프리로딩, 메타데이터, 웹 컴포넌트, 정적 프리렌더 API — 오늘부터 렌더링 속도를 높여보세요.
페이지가 준비된 것처럼 보이지만 여전히 CPU와 싸우는 그 느낌을 아시나요? React 19는 실제로 기능을 켜기만 하면 이런 마찰을 많이 해결해줍니다. 아래는 실용적이고 군더더기 없는 가이드로, 무엇을 활성화해야 하는지, 왜 중요한지, 그리고 코드베이스를 망가뜨리지 않고 어떻게 연결하는지 설명합니다.
1) 상호작용이 없는 모든 것에 대해 서버 컴포넌트를 기본으로 사용하기
서버 컴포넌트는 서버에서 미리 렌더링되어 전송할 JavaScript를 줄이고 데이터 작업을 더 일찍 시작합니다. 규칙: 컴포넌트를 기본적으로 서버로 만들고, 상호작용하는 부분만 클라이언트로 표시하세요.
// 기본적으로 서버 ("use client" 없음)
import db from "@/lib/db";
export default async function ProductList() {
const items = await db.products();
return <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
}
// 사용자가 클릭하는 곳에만 클라이언트
"use client";
export function Filters({ onChange }: { onChange: (s: string) => void }) {
return <input onChange={e => onChange(e.target.value)} placeholder="Search"/>;
}
효과: 더 작은 번들, 빠른 파싱, 적은 하이드레이션 작업
2) 이펙트가 많은 폼을 액션(과 그들의 훅)으로 교체하기
React 19의 액션은 폼이 비동기 함수를 직접 호출할 수 있게 해줍니다 — 맞춤 핸들러도, 수동 pending/error 배관도 필요 없습니다. 깔끔한 UX를 위해 useActionState/useFormStatus와 함께 사용하세요.
// actions.ts
export async function saveName(_: unknown, form: FormData) {
"use server";
const name = String(form.get("name"));
// ... 저장 로직
}
// component.tsx
import { useActionState, useFormStatus } from "react";
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? "저장 중…" : "저장"}</button>;
}
export default function Profile() {
const [state, action] = useActionState(saveName, null);
return (
<form action={action}>
<input name="name" />
<SubmitButton />
{state?.error && <p>{state.error}</p>}
</form>
);
}
효과: 더 적은 클라이언트 컴포넌트, 더 적은 JS, 그리고 안정적인 pending/optimistic 상태
3) 새로운 use() API로 비동기 데이터 인라인 읽기
useEffect + 로컬 로딩 상태를 흩어놓는 것을 멈추세요. use()는 Promise나 컨텍스트를 직접 읽어서 경계 내에서 깔끔하게 서스펜딩합니다.
import { use, Suspense } from "react";
function Message({ promise }: { promise: Promise<string> }) {
const message = use(promise); // 해결될 때까지 서스펜드
return <p>{message}</p>;
}
export default function Page({ promise }: { promise: Promise<string> }) {
return (
<Suspense fallback={<div aria-busy="true" />}>
<Message promise={promise} />
</Suspense>
);
}
효과: 더 간단한 컴포넌트, 이펙트 타이밍으로 인한 버그 감소, 더 나은 스트리밍 동작
4) React DOM의 리소스 API로 중요한 자산 프리로드 및 사전 초기화
React 19는 네이티브 preload() 및 **preinit()**을 추가하여 내비게이션이 도착하기 전에 폰트, 스타일, 스크립트를 가져올 수 있습니다.
import { preload, preinit } from "react-dom";
preload("/fonts/inter.woff2", { as: "font", crossOrigin: "" });
preinit("/scripts/chart-lib.js", { as: "script" });
효과: 일관된 첫 페인트와 라우트 변경 시 "보이지 않는 텍스트"나 레이아웃 재배치 현상 감소
5) React 19의 자산 로딩 개선 활용하기 (DIY 줄이기)
지연 로딩 이미지, 비디오, 백그라운드 리소스가 이제 Suspense와 프리로딩과 더 깔끔하게 통합됩니다. 미디어가 많은 영역 주변에 경계를 사용하고 로더 파이프라인이 작업하도록 하세요.
<Suspense fallback={<div style={{height:160}}/>}>
<HeroVideo /> {/* lazy + 다른 곳에서 프리로드됨 */}
</Suspense>
효과: 유지해야 할 커스텀 로더가 적고 빠른 라우트 전환
6) 네이티브 메타데이터 사용하기 — 임시방편적인 <head> 해킹 제거
React 19는 서드파티 헬퍼 없이 컴포넌트에서 <title>, <meta>, <link>를 <head>로 끌어올릴 수 있습니다.
export default function Article({ title, description }: { title: string; description: string }) {
return (
<>
<title>{title}</title>
<meta name="description" content={description}/>
<link rel="preload" href="/fonts/inter.woff2" as="font" crossOrigin="" />
<article>…</article>
</>
);
}
효과: 더 나은 SEO 인체공학과 문서 헤드 업데이트 시 경합 조건 감소
7) 상호 운용성: 일급 웹 컴포넌트 지원
디자인 시스템이 Custom Element를 제공한다면, React 19는 마침내 이들을 일급 시민으로 취급합니다: props가 속성에 매핑되고, 이벤트가 깔끔하게 연결되며, SSR/CSR 차이가 잘 정의됩니다.
// ref 해킹 없음; props/events를 직접 전달
<my-date-picker value={selected} onChange={(e: any) => setSelected(e.detail)} />
효과: 벤더 위젯과 크로스 프레임워크 UI 키트와의 더 깔끔한 통합
8) 네트워크에서 기다리는 상태에 대해 비동기 전환 선호하기
React 19의 전환은 비동기 작업과 더 잘 어울립니다: 라우트나 필터가 pending 상태에서 전환하는 동안 UI를 반응형으로 유지할 수 있습니다.
import { useTransition } from "react";
const [isPending, start] = useTransition();
start(async () => {
await refreshSearch(query); // 비동기 전환
});
효과: 큰 상태 변화 중 "얼어붙은 UI" 순간 감소; 더 깔끔한 pending/error 처리
9) SSG/ISR 파이프라인을 위한 정적 프리렌더 API 켜기
페이지를 미리 생성한다면, 새로운 react-dom/static 프리렌더 API를 사용하여 데이터가 정착된 완전한 HTML을 생성하고, 필요에 따라 나중에 스트림 하이드레이트하세요.
효과: 기존 renderToString 플로우보다 더 신뢰할 수 있는 정적 출력이면서도 스트리밍 SSR 전략과 잘 어울림
10) 올바른 방법으로 결과 측정하기 (실제로 회귀하는 것 수정하기)
React 19는 렌더링을 가능하게 만들어줍니다; 당신의 역할은 사용자에게 더 빠르다는 것을 확인하는 것입니다. 추적할 것들:
- TTFB와 LCP: 서버 컴포넌트로 이동한 페이지에서
- 하이드레이션 윈도우: (첫 바이트 → 마지막 아일랜드 하이드레이트됨)
- 라우트 전환 지연시간:
preload()/preinit()추가 후 - 에러/pending 표시: 액션 기반 폼에서
숫자가 이상해 보이면 먼저 번들 diff를 확인하세요 (서버 컴포넌트는 JS를 줄이고; 액션은 종종 전체 클라이언트 핸들러를 제거합니다). 그 다음 자산 폭포를 검사해서 폰트와 스타일이 예상대로 도착하는지 확인하세요.
아키텍처 스케치
(빌드 / 서버)
┌───────────────────────┐
RSC 렌더 │ 서버 컴포넌트 │ -> HTML 청크
+ 데이터 │ 서버에서 데이터 fetch │ -> <title>/<meta> 호이스팅
└──────────┬────────────┘
│ 스트림
▼
브라우저 파싱/페인팅 일찍 시작
┌──────────┐ ┌───────────────────┐
│ preinit │→ │ 프리로드된 자산 │
│ preload │ └───────────────────┘
└────┬─────┘ │
│ Suspense 경계
▼ │
클라이언트 아일랜드 하이드레이션 (폼은 액션 사용; 비동기는 use())
미니 사례 연구: 3가지 토글 → 눈에 띄게 빨라짐
한 콘텐츠 사이트가 기사 페이지를 서버 컴포넌트로 이동하고, 댓글 폼을 액션으로 바꾸고, 헤드라인 폰트에 preload()를 추가했습니다. 번들 차이는 ~28% 줄어들었고, 첫 페인트가 더 일찍 시작되었으며(더 빠른 HTML 스트림), LCP가 2.7초 → 1.9초로 개선되었습니다(중급 안드로이드에서). 폼 UX도 더 간단해졌습니다: pending/error 상태가 클라이언트 접착 코드 없이도 작동했습니다. (결과는 다를 수 있지만 패턴은 유지됩니다.)
빠른 채택 체크리스트
- 페이지를 기본적으로 서버로 만들고; 상호작용을
"use client"로 표시 - 트래픽이 많은 폼 1-2개를 액션으로 변환; 이펙트 기반 핸들러 제거
- 느린 영역을 Suspense로 감싸고; 자연스러운 곳에서 데이터 읽기를 **use()**로 전환
- 라우트 전환에서 폰트와 중요한 라이브러리에 preload() / preinit() 추가
-
<Helmet>을 네이티브 메타데이터로 교체 - 웹 컴포넌트를 사용한다면 직접 연결 — ref 해킹 없이
- SSG 플로우의 경우, react-dom/static prerender 탐색
마무리
React 19는 단일 은탄환이 아닙니다. 더 일찍 스트림하고, 더 적은 JS를 제공하고, 의도를 더 직접적으로 표현할 수 있게 해주는 작고 날카로운 도구들의 묶음입니다. 이 스위치들을 목적을 가지고 켜세요. 측정하세요. 그리고 사용자의 타임라인을 올바른 방향으로 움직이는 부분들을 유지하세요.
CTA: 어떤 업그레이드를 먼저 시도하시겠습니까 — 액션, use(), 아니면 메타데이터? 당신의 스택과 함께 댓글을 남겨주시면 가장 안전한 마이그레이션 단계를 제안해드리겠습니다.
출처
- 원문: 10 React 19 Rendering Upgrades You Should Enable Today
- 작성자: Bhagya Rana
- 번역일: 2025년 9월 24일
이 글은 개인 블로그 게시를 위해 번역 및 정리되었습니다.



