Development··6 min read

React Server Actions 실전에서 만난 함정들

Server Actions가 간단해 보여서 도입했다가 만난 실제 문제들

처음에는 진짜 깔끔해 보였다

Server Actions를 처음 봤을 때 "이거 API 라우트 안 만들어도 되는 거잖아"라는 생각이었다. 폼 제출할 때 서버 함수를 바로 호출하면 되니까. 보일러플레이트가 확 줄어드는 느낌.

그래서 새 프로젝트에 바로 적용했다. 사용자 회원가입, 게시글 CRUD, 파일 업로드까지. 깔끔하게 잘 돌아갔다. 처음 2주 동안은.

에러 핸들링이 생각보다 까다롭다

첫 번째 함정. Server Action에서 에러가 나면 클라이언트한테 어떻게 알려줄까. throw를 하면 클라이언트에서 에러 바운더리를 타는데, 이게 폼 제출 에러를 처리하는 방식으로는 적절하지 않다. "비밀번호가 틀렸습니다" 같은 건 에러 바운더리가 아니라 폼 필드 옆에 메시지로 보여줘야 한다.

결국 Server Action에서 에러를 throw하지 않고 리턴 객체로 넘기는 패턴을 썼다.

'use server'
 
export async function login(formData: FormData) {
  const email = formData.get('email') as string;
  const password = formData.get('password') as string;
 
  const user = await authenticate(email, password);
 
  if (!user) {
    return { error: '이메일 또는 비밀번호가 올바르지 않습니다.' };
  }
 
  return { success: true };
}

이게 표준 패턴이 된 건 알겠는데, 처음에는 "이게 맞나?" 싶었다. 에러를 리턴값으로 처리하는 게 좀 이상한 느낌.

revalidatePath의 타이밍 문제

두 번째 함정. revalidatePath를 호출하면 해당 경로의 캐시가 무효화되는데, 이 타이밍이 미묘하다.

게시글을 작성하고 revalidatePath('/posts')를 호출한 뒤 redirect('/posts')로 보냈다. 근데 가끔 새 글이 목록에 안 보이는 경우가 있었다. 새로고침하면 보인다. 캐시 무효화가 리다이렉트보다 늦게 처리되는 것 같았다.

이거 해결하는 데 반나절을 쓴 건 좀 아깝다. 결국 revalidateTag로 바꾸고, 데이터 페칭에 태그를 달아서 해결했다. 근데 이 과정이 문서에 잘 안 나와 있어서 삽질의 연속이었다.

파일 업로드에서 난관

세 번째 함정. Server Actions로 파일 업로드를 구현했는데, 큰 파일에서 문제가 생겼다. 기본 설정으로는 요청 본문 크기 제한이 1MB다. 이미지 한 장이면 괜찮은데, 여러 장이나 영상 파일은 안 된다.

next.config.ts에서 serverActions.bodySizeLimit을 올려야 하는데, 이걸 찾는 데 시간이 꽤 걸렸다. 에러 메시지가 "request entity too large"라고만 나와서, Server Actions 설정 문제라는 걸 바로 연결하지 못했다.

그리고 업로드 진행률을 보여주는 건 Server Actions로는 안 된다. XMLHttpRequestfetch의 스트리밍 기능이 필요한데, Server Actions는 이걸 지원하지 않는다. 결국 파일 업로드만 별도 API 라우트로 뺐다. (그러면 Server Actions를 쓰는 의미가...)

useActionState와의 조합

useActionState를 써서 폼 상태를 관리하면 깔끔해진다고 해서 적용했다. 실제로 로딩 상태 관리가 편해지긴 했다. isPending으로 버튼 비활성화하는 게 간단하다.

근데 초기 상태 설정이 좀 헷갈린다. 리턴 타입이 Server Action의 리턴값과 일치해야 하는데, 초기에는 아무것도 없으니까 빈 객체를 넣어야 하고, 그러면 타입이 맞지 않아서 union 타입을 쓰게 되고... TypeScript와의 전쟁이 시작된다.

그래도 쓸 만한가

쓸 만하다. 단순한 폼 제출 (로그인, 회원가입, 댓글 작성 같은 것)에는 API 라우트보다 확실히 간결하다. 근데 복잡한 요구사항이 들어오면 결국 API 라우트를 병행하게 된다.

내 기준으로 Server Actions만으로 해결할 수 있는 비율은 전체 서버 통신의 60% 정도다. 나머지 40%는 여전히 API 라우트가 필요하다.

"Server Actions가 API 라우트를 대체한다"는 틀린 말이다. "Server Actions가 간단한 서버 통신을 더 쉽게 만든다"가 맞다. 이 기대치를 잘 잡는 게 중요하다.

관련 글