Web Devlopment/NextJs

#36. form action(Server Actions) vs onSubmit(Client Handler)

키모형 2026. 1. 1. 14:04
반응형

폼 제출 2가지 방식 비교: form action(Server Actions) vs onSubmit(Client Handler)

1) 핵심 차이: “어디서 처리하나?”

  • Server Actions (<form action={serverAction}>)
    • 폼 제출이 곧바로 서버 함수 실행으로 연결됩니다.
    • DB 처리/권한 체크/캐시 무효화까지 서버에서 끝냅니다.
  • Client Handler (<form onSubmit={handleSubmit}>)
    • 제출을 JS로 가로채서(preventDefault) 클라이언트에서 fetch/axios로 API를 호출합니다.
    • UI 제어(로딩/검증/토스트/optimistic)가 매우 자유롭습니다.

2) Server Actions 방식이 유리한 경우

장점

  • 코드가 단순: API 라우트 없이도 CRUD 구현 가능
  • 보안에 유리: DB/비밀키를 브라우저로 내보낼 필요 없음
  • 캐시/ISR 연동이 자연스러움: updateTag, revalidatePath로 “저장 직후 즉시 반영” 처리
  • Progressive Enhancement: JS가 꺼져도 기본 폼 제출은 동작

단점(주의)

  • 입력 즉시 검증, 복잡한 UX(단계형 폼/즉시 미리보기/optimistic UI)는 추가 설계가 필요
  • 파일 업로드/외부 SDK 등 브라우저 전용 기능은 Client 로직이 필요해지는 경우가 많음

✅ 추천 상황: 관리자 CRUD, 간단한 등록/수정/삭제 폼, DB 작업 중심 화면


3) Client onSubmit 방식이 유리한 경우

장점

  • UX 제어 최강: 즉시 검증, 로딩/토스트, optimistic UI, 실패 시 복구 등 마음대로 가능
  • 외부 SDK/결제/지도/에디터 등 브라우저 전용 기능과 잘 맞음
  • 이미 REST API 중심 구조면 자연스럽게 이어짐

단점

  • JS가 깨지면 폼이 동작하지 않을 수 있음(기본 제출 막는 패턴이 많음)
  • 보통 API 라우트가 추가로 필요해서 구조가 2단계가 됨
  • 인증/권한/CSRF/에러 처리 등 보안·운영 고려 포인트가 늘어남

✅ 추천 상황: 복잡한 인터랙션, 즉각 반응 UI, 외부 API/SDK 연동, SPA 스타일


4) “캐시/즉시 반영” 관점에서의 차이

  • Server Actions
    • 저장 후 updateTag("products"), revalidatePath("/product-db") 같은 처리를 서버에서 바로 수행 → 저장 직후 화면 반영이 깔끔
  • Client onSubmit
    • 저장 후 클라이언트에서 상태를 갱신하거나, 다시 fetch해야 함
    • “즉시 반영”은 쉬울 수 있으나, 서버 캐시(ISR/태그)와 일관성 유지는 설계가 필요

5) 실무에서 많이 쓰는 결론(추천 가이드)

  • CRUD + 캐시/ISR까지 Next가 책임지는 구조면 → Server Actions가 1순위
  • UX/상호작용이 핵심이고 API 중심이면 → onSubmit(Client)이 1순위
  • 둘 중 하나만 고집하기보다,
    • 저장은 Server Action,
    • 검증/UX 보조는 Client
      같은 혼합 패턴도 매우 흔합니다.

6) 짧은 예시 코드(비교용)

Server Actions

import { createProductAction } from "./actions";

<form action={createProductAction}>
  <input name="title" />
  <button type="submit">저장</button>
</form>
 
 
Client onSubmit
<form onSubmit={handleSubmit}>
  <input value={title} onChange={(e) => setTitle(e.target.value)} />
  <button type="submit">저장</button>
</form>

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  await fetch("/api/products", { method: "POST", body: JSON.stringify({ title }) });
};
 

 

반응형