React 19의 use() 훅과 Server Actions 실전 도입기

배경

9월 중순 React 19가 정식 릴리즈되면서 use() 훅과 Server Actions가 안정화되었다. 사내 어드민 프로젝트를 Next.js 15와 함께 업그레이드하며 실제 도입해봤다.

use() 훅 적용

기존에는 Promise를 컴포넌트 밖에서 처리하거나 useEffect로 감싸야 했다. use()를 사용하면 컴포넌트 내부에서 직접 Promise를 unwrap할 수 있다.

// Before
function UserProfile({ userId }: Props) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  if (!user) return <Loading />;
  return <div>{user.name}</div>;
}

// After
function UserProfile({ userPromise }: Props) {
  const user = use(userPromise);
  return <div>{user.name}</div>;
}

Suspense와 조합하면 로딩 상태 관리가 선언적으로 바뀐다. 다만 Promise는 부모에서 생성해야 한다는 제약이 있어서 데이터 페칭 로직 구조를 재설계해야 했다.

Server Actions 마이그레이션

폼 처리를 Server Actions로 전환했다. 기존 API 라우트 대비 타입 안정성이 확실히 좋았다.

// actions/user.ts
'use server'

export async function updateUser(formData: FormData) {
  const name = formData.get('name') as string;
  await db.user.update({ name });
  revalidatePath('/profile');
}

// components/UserForm.tsx
function UserForm() {
  return (
    <form action={updateUser}>
      <input name="name" />
      <button type="submit">저장</button>
    </form>
  );
}

단, formData 파싱이 번거로워서 zod 스키마로 검증 레이어를 추가했다.

마주친 이슈

  1. use() 훅은 조건부로 호출할 수 없다. 분기 처리가 필요하면 컴포넌트를 분리해야 했다.
  2. Server Actions의 에러 핸들링이 명확하지 않아서 try-catch로 감싸고 결과 객체를 반환하는 패턴을 정립했다.
  3. 개발 환경에서 HMR 시 Server Actions가 간헐적으로 실패하는 버그가 있었다. Next.js 15.0.3에서 수정되었다.

결론

React 19의 새 기능들은 확실히 DX를 개선했다. 특히 Server Actions는 풀스택 개발 시 API 레이어를 줄여주는 효과가 컸다. 다만 프로덕션 적용 전 충분한 테스트가 필요하다.