dev.syw

'use server' 로 폼 제출을 서버에서 처리한다.

서버 액션과 폼

서버 액션은 클라이언트에서 서버 함수를 직접 호출하게 해 주는 기능입니다. 별도 API 엔드포인트를 만들지 않고도 폼 제출, DB 쓰기 같은 변경(mutation)을 처리할 수 있습니다. 'use server' 한 줄이면 시작됩니다.

학습 목표

  • 'use server' 로 서버 액션을 정의한다.
  • <form action={...}> 에 서버 액션을 연결한다.
  • useFormStatus, useActionState 로 제출 상태를 다룬다.
  • revalidatePath 로 캐시를 갱신하고 폼을 검증한다.

서버 액션 정의

'use server' 를 함수 안 첫 줄에 두거나, 파일 맨 위에 두면 그 함수(들)는 서버에서 실행되는 액션이 됩니다. 폼의 action 에 직접 연결할 수 있습니다.

// app/new/page.tsx — 서버 컴포넌트
import { revalidatePath } from 'next/cache';

async function createTodo(formData: FormData) {
  'use server';
  const title = formData.get('title') as string;
  await db.todo.create({ data: { title } });
  revalidatePath('/'); // 목록 페이지 캐시 갱신
}

export default function NewTodo() {
  return (
    <form action={createTodo}>
      <input name="title" placeholder="할 일" />
      <button type="submit">추가</button>
    </form>
  );
}

JavaScript가 로드되기 전에도 폼이 동작합니다(점진적 향상). formData.get(name) 으로 입력값을 읽습니다.

revalidatePath / revalidateTag

데이터를 바꾼 뒤에는 화면에 반영되도록 캐시를 무효화해야 합니다.

import { revalidatePath, revalidateTag } from 'next/cache';

revalidatePath('/todos');   // 특정 경로의 캐시 갱신
revalidateTag('todos');     // 태그가 붙은 fetch 캐시 갱신

💡 TIPredirect('/todos') 를 액션 끝에서 호출하면 작업 후 다른 페이지로 이동시킬 수도 있습니다. redirectnext/navigation 에서 가져옵니다.

제출 상태 — useFormStatus

제출 중 버튼을 비활성화하려면 useFormStatus 를 씁니다. 이 훅은 폼 안쪽의 클라이언트 컴포넌트에서만 동작합니다.

'use client';
import { useFormStatus } from 'react-dom';

export function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? '저장 중...' : '저장'}
    </button>
  );
}

결과·검증 — useActionState

서버 액션의 반환값(검증 메시지 등)을 화면에 보여주려면 useActionState 를 씁니다. 액션의 첫 인자로 이전 상태가 들어옵니다.

// actions.ts
'use server';
export async function submit(prev: unknown, formData: FormData) {
  const email = formData.get('email') as string;
  if (!email.includes('@')) {
    return { error: '이메일 형식이 올바르지 않습니다.' };
  }
  await save(email);
  return { error: null, ok: true };
}
'use client';
import { useActionState } from 'react';
import { submit } from './actions';

export default function Form() {
  const [state, action] = useActionState(submit, { error: null });
  return (
    <form action={action}>
      <input name="email" />
      {state.error && <p style={{ color: 'red' }}>{state.error}</p>}
      <button type="submit">구독</button>
    </form>
  );
}

⚠️ 주의 — 서버 액션은 누구나 호출할 수 있는 엔드포인트와 같습니다. 입력값 검증과 권한 확인을 반드시 서버 쪽에서 하세요. 클라이언트 검증만 믿으면 안 됩니다.

요약

  • 'use server' 로 정의한 함수를 <form action={...}> 에 직접 연결한다.
  • formData.get() 으로 입력값을 읽고, revalidatePath/revalidateTag 로 캐시를 갱신한다.
  • useFormStatus 로 제출 중 상태를, useActionState 로 결과·검증 메시지를 다룬다.
  • 검증과 권한 확인은 반드시 서버에서 한다.

연습문제

  1. 입력한 제목을 목록에 추가하는 서버 액션을 만들고, 추가 후 revalidatePath 로 목록을 갱신하세요.
  2. useFormStatus 로 제출 중 버튼을 "저장 중..."으로 바꾸고 비활성화하세요.
  3. 이메일에 @ 가 없으면 에러 메시지를 반환하는 검증을 useActionState 로 화면에 표시하세요.
  4. 액션 성공 후 redirect 로 목록 페이지로 이동시켜 보세요.

힌트useFormStatus 는 폼 내부 의 클라이언트 컴포넌트에서만 pending 을 읽을 수 있습니다. useActionState(action, 초기상태) 의 반환값 [state, action] 에서 action 을 폼에 연결하세요.

💡 연습문제 풀이

불러오는 중…

함께 보면 좋은 자료

댓글 0

Next.js” 강좌에 대한 댓글입니다.

댓글을 작성하려면 로그인이 필요합니다.