Develop

Next.js Server Actions 완벽 가이드 — 폼 처리와 데이터 변경을 서버에서 처리하는 방법

API 엔드포인트를 명시적으로 설계하지 않고도 리액트 컴포넌트 단에서 백엔드 데이터베이스 변조 함수를 직접 호출할 수 있는 Next.js Server Actions의 핵심 구조와, 유효성 검사 미들웨어 Zod 연동 및 CSRF 방어 기법을 심층적으로 다룹니다.

Next.jsServer Actions폼처리App Routerfrontend
Next.js Server Actions로 폼 제출이 서버에서 직접 처리되는 코드 흐름 화면 — 폼 처리 예시
  • ·Server Actions는 use server 지시어로 선언하며 클라이언트에서 호출하면 내부적으로 POST 요청으로 변환된다
  • ·form 태그의 action 속성에 Server Action을 직접 전달하면 JavaScript 없이도 폼이 서버에 제출된다
  • ·useFormState와 useFormStatus 훅으로 서버 액션의 실행 상태와 결과를 클라이언트에서 처리할 수 있다
  • ·Server Actions는 자동으로 CSRF 보호가 적용되어 같은 출처의 요청만 허용한다
예전에 댓글 등록 폼을 하나 구현하려면, 프론트 컴포넌트에 state 만들고, onClick에서 fetch('/api/comments', { method: 'POST', body }) 쏘고, 또 app/api/comments/route.ts 파일 열어서 바디 읽고 DB에 찔러주는 등 파일 서너 개를 정신없이 왔다 갔다 하며 API 배관 공사를 해야 했습니다. Server Actions를 처음 도입하고 나서는 말 그대로 유레카였습니다. 클릭 메서드나 폼 액션 단추에 'use server' 함수를 툭 던져두기만 하니 번거로운 API 라우트 배관 코드가 증발해 버리더군요. 폼 제출 후 revalidatePath 한 줄로 캐시를 날리자마자 리액트 컴포넌트가 최신 상태로 새로고침 없이 리렌더링되는 경험은 프론트 개발 생산성의 혁신이었습니다.

1. 무상태 액션 매커니즘

Next.js Server Actions 폼 전송 기술로 백엔드 API 배관 코드 제거하기

Server Actions는 리액트 컴포넌트 단에서 바로 호출할 수 있는 서버 전용 비동기 함수입니다. 파일 상단에 'use server' 지시어를 할당하면, 빌드 컴파일러는 이 함수를 클라이언트 번들에서 격리하고 내부적으로 고유 해시 ID를 가진 POST API 통신 파이프라인으로 리매핑해 줍니다. 따라서 브라우저 자바스크립트가 로딩되지 않은 극단적인 환경(JS Disable)에서도 HTML 네이티브 form 전송 메커니즘을 타고 작동하므로 접근성 면에서 엄청난 장점을 보이며, 백엔드 CRUD 로직 수립 비용을 혁신적으로 깎아 줍니다.

// app/actions/post.ts
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { prisma } from '@/lib/prisma';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const content = formData.get('content') as string;

  await prisma.post.create({ data: { title, content } });
  revalidatePath('/blog');
  redirect('/blog');
}

2. Zod 스키마 검증과 상태 트래킹

Next.js Server Actions 데이터 Zod 유효성 검사와 useActionState 상태 추적 노하우

아무리 API 작성이 편해졌다 해도, 서버 단에서 넘어온 날것의 파라미터 유효성 검사를 빼먹으면 SQL 인젝션이나 더러운 데이터 유입으로 이어집니다. 이때는 Zod 라이브러리를 기용하여 들어온 FormData를 객체로 파싱하고 스키마 검증 에러를 잡아내는 방어가 필수입니다. 또한 컴포넌트 단에서 전송 중인 로딩 스피너를 켜고 에러 메시지를 이쁘게 받아 그리하려면 React 19의 useActionState(구 useFormState)와 useFormStatus 훅을 엮어야 합니다. 폼이 도는 중에는 자동으로 pending 플래그가 참으로 변조되어 유저의 찰나 중복 클릭을 차단하게 설계해 두어야 실무 배포 환경에서 안심할 수 있습니다.

3. 보안 빗장과 CSRF 자동 방어

Next.js Server Actions 전송 보안과 사용자 인가 권한 검사 기법

Server Actions는 겉보기에 일반 리액트 함수처럼 생겨서 보안을 간과하기 십상이지만, 실상 백엔드 POST API와 다를 바 없습니다. 즉, 공격자가 컬(curl)이나 포스트맨으로 내 액션 식별 ID 주소에 다이렉트 변조 패킷을 쏠 수 있다는 뜻입니다. 액션 함수 실행부 최상단에서 세션 쿠키 정보를 매치 조회해 유저 자격을 2차 검증(Authorization)하고, 대상 레코드의 실제 소유주가 요청자 본인이 맞는지 엄격한 소유권 인가 검사를 두르지 않으면 다른 사람의 글이 맘대로 지워지는 보안 참사로 이어질 수 있습니다. 다행히 CSRF 방어는 넥스트 프레임워크가 액션 전송 시 헤더의 Host와 Origin 일치 여부를 대조해 원천 차단해 줍니다.

자주 묻는 질문

useFormStatus 훅을 사용하는데 pending 상태가 계속 false로만 나옵니다. 왜 그럴까요?+

리액트 디자인 원칙상 useFormStatus는 반드시 <form> 태그의 하위에 중첩된 '자식 컴포넌트' 내부에서만 실행될 수 있습니다. 폼을 들고 있는 동일한 page.tsx 컴포넌트 본문에 기재해 두면 상태 인식이 안 되니, 전송 버튼 영역을 별도의 커스텀 버튼 컴포넌트(예: <SubmitButton />)로 잘게 쪼개어 form 태그 밑단에 주입하셔야 정상 동작합니다.

Server Action 도중 발생한 에러를 클라이언트 에러 바운더리로 흘려보낼 수 있나요?+

액션 함수 내부에서 throw new Error()를 날리면 넥스트 프레임워크는 클라이언트의 가장 가까운 error.tsx 바운더리로 에러 전파를 유도합니다. 다만 프로덕션 환경에서는 보안을 위해 자세한 에러 스택 디테일이 숨겨지고 An error occurred in the Server Action... 같은 뭉뚱그려진 메시지로 치환되므로, 사용자 피드백 처리를 원하시면 에러 메시지를 커스텀 객체로 return하여 UI 단에서 보여주는 구조가 더 깔끔합니다.

관련 글