좋아, 이번 편은 React 18 이후의 UX 성능 최적화 핵심 기능을 완전히 정리하는 시간이다.
이전 글에서 “사용자가 느끼는 빠름”을 다뤘다면, 이번엔 그걸 실제로 구현하는 기술적 방법을 배워보자.
1. React 18의 변화 — “빠른 반응”을 위한 진화
React 18은 단순히 동시성(concurrency) 개념을 도입한 게 아니라,
사용자 체감 속도를 높이기 위한 렌더링 제어 기능이 대거 추가되었다.
그중에서도 핵심은 이 세 가지다.
기능 목적
| Suspense | 데이터 로딩 시 자연스러운 화면 제어 |
| Transition | 사용자 인터랙션 중 UI 끊김 방지 |
| Streaming SSR | 서버 렌더링된 콘텐츠를 점진적으로 스트리밍 전송 |
이 세 가지를 제대로 활용하면,
“렌더링은 느려도 사용자에게는 빠르게 느껴지는” 구조를 만들 수 있다.
2. Suspense — “로딩 중에도 멈추지 않는 UI”
2-1. 문제 인식
기존 React는 비동기 데이터가 없으면 그 자리가 비어 있거나 깜빡였다.
즉, API 요청이 끝나야 컴포넌트가 렌더링되었지.
React 18부터는 Suspense를 이용해
로딩 중에도 UI가 자연스럽게 유지되도록 할 수 있다.
2-2. 기본 문법
import { Suspense } from 'react';
import UserProfile from './UserProfile';
function App() {
return (
<Suspense fallback={<div>로딩 중...</div>}>
<UserProfile />
</Suspense>
);
}
✅ fallback
데이터가 준비되지 않았을 때 보여줄 UI (Skeleton UI 등)
✅ 내부 컴포넌트가 Promise를 던지면 React가 알아서 Suspense로 제어
2-3. React Query와의 실전 예시
import { Suspense } from "react";
import { useQuery } from "@tanstack/react-query";
function UserInfo() {
const { data } = useQuery({
queryKey: ["user"],
queryFn: () => fetch("/api/user").then(res => res.json()),
suspense: true,
});
return <div>안녕하세요, {data.name}님!</div>;
}
export default function Page() {
return (
<Suspense fallback={<div className="skeleton">유저 정보 불러오는 중...</div>}>
<UserInfo />
</Suspense>
);
}
- 데이터가 로딩 중일 땐 fallback UI 표시
- 데이터가 준비되면 자동으로 교체
➡ 사용자 입장에서는 “멈추지 않고 자연스럽게 전환되는” 느낌
3. Transition — 느린 연산도 부드럽게
3-1. 문제 인식
대량의 상태 업데이트(검색, 필터, 정렬 등)가 한꺼번에 일어나면
UI가 잠시 멈추는 느낌을 받는다.
useTransition은 이런 업데이트를 “긴급하지 않은 작업” 으로 분리해,
UI가 끊기지 않게 렌더링 우선순위를 조절한다.
3-2. 사용법
import { useState, useTransition } from "react";
export default function SearchList({ items }) {
const [query, setQuery] = useState("");
const [filtered, setFiltered] = useState(items);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setQuery(value);
startTransition(() => {
setFiltered(items.filter(item => item.includes(value)));
});
};
return (
<div>
<input type="text" value={query} onChange={handleChange} placeholder="검색..." />
{isPending && <span>검색 중...</span>}
<ul>
{filtered.map((v, i) => <li key={i}>{v}</li>)}
</ul>
</div>
);
}
✅ startTransition() 내부의 상태 업데이트는 “긴급하지 않은 작업”으로 처리됨
✅ 사용자는 입력 중에도 인터랙션이 끊기지 않는다
3-3. 체감 UX
상황 Transition 미적용 Transition 적용
| 1만 개 필터링 | 입력 중 UI 멈춤 | 입력 즉시 반응 |
| 빠른 타이핑 | 지연됨 | 자연스럽게 입력 유지 |
| 연속 클릭 | 이벤트 겹침 | 순서대로 부드럽게 처리 |
→ 체감 속도는 두 배 이상 개선된다.
4. Streaming SSR — 서버 렌더링을 “점진적으로”
4-1. 기존 SSR의 한계
기존 SSR은 HTML 전체가 완성될 때까지 기다린 뒤 한 번에 전송했다.
즉, 서버에서 데이터가 느리면 사용자도 계속 빈 화면을 본다.
4-2. React 18의 Streaming SSR
React 18부터는 서버가 렌더링된 부분부터 스트리밍으로 전송할 수 있다.
- 먼저 중요한 영역(헤더, Hero, 기본 프레임)을 렌더링
- 이후 비동기 데이터가 준비되면 나머지 콘텐츠가 합쳐진다
4-3. Next.js 13 이상에서 자동 적용
Next.js App Router는 React 18 기반의 Streaming SSR을 기본적으로 사용한다.
즉, Suspense가 자동으로 서버 렌더링 단계에서도 작동한다.
// app/page.tsx
import { Suspense } from "react";
import ProductList from "./ProductList";
export default function Page() {
return (
<>
<Hero />
<Suspense fallback={<div>상품 목록 로딩 중...</div>}>
<ProductList />
</Suspense>
</>
);
}
✅ Hero 영역은 즉시 렌더링
✅ ProductList는 서버 데이터 준비 후 점진적 로딩
사용자는 “화면이 바로 뜨는 느낌”을 받는다.
5. 세 가지 기능의 조합
기능 목적 결과
| Suspense | 비동기 로딩 제어 | 깜빡임 없는 데이터 로딩 |
| Transition | 렌더링 우선순위 조절 | 사용자 입력 시 끊김 제거 |
| Streaming SSR | 점진적 렌더링 | 첫 화면 빠르게 노출 |
이 세 가지를 조합하면,
네트워크가 느려도 “끊기지 않는 사용자 경험” 을 만들 수 있다.
6. 실제 서비스 구조 예시
app/
├─ layout.tsx
├─ page.tsx (Streaming SSR + Suspense)
├─ components/
│ ├─ SearchBar.tsx (useTransition)
│ ├─ ProductList.tsx (Suspense)
│ └─ SkeletonCard.tsx
└─ api/
└─ getProducts.ts
✅ ProductList는 서버에서 Suspense + Streaming SSR 적용
✅ SearchBar는 Transition으로 입력 부드럽게 처리
✅ SkeletonCard는 로딩 중 시각적 안정성 제공
→ 이렇게 하면 “느리지 않은 UI”가 아니라
“사용자에게 빠르게 느껴지는 UI”가 된다.
7. 실무 팁
- Suspense는 React Query, Relay, SWR과 함께 써야 진가 발휘
- Transition은 “대량 렌더링 or 복잡한 연산”에서만 써야 효과적
- Streaming SSR은 Next.js 13 이상에서 기본적으로 켜져 있음
- Skeleton UI와 함께 써야 체감 UX 상승 폭이 커짐
8. 마무리
React 18 이후의 UX 성능 최적화는
더 이상 “API 응답 빠르게”가 아니라
“사용자가 느끼기에 빠르게” 로 바뀌었다.
Suspense로 로딩을 매끄럽게,
Transition으로 인터랙션을 부드럽게,
Streaming SSR로 첫 화면을 빠르게.
이 세 가지를 익히면,
React 기반 서비스의 성능은 숫자보다 “경험”으로 측정될 것이다.