반응형
서버 액션(Server Actions)이란?
브라우저(클라이언트)에서 호출할 수 있는 서버에서 실행되는 비동기 함수
- 별도 API 라우트 생성 없이 서버 측 로직을 직접 처리
- 'use server' 지시어로 서버 전용 함수임을 명시
핵심 특징
- 'use server' 지시어 사용
- 함수나 파일 상단에 선언하여 서버에서만 실행되도록 지정
- 별도 API 라우트 불필요
- 기존: 컴포넌트 → API 라우트 → 서버 로직
- 서버 액션: 컴포넌트 → 서버 액션 (직접 호출)
- 서버 전용 작업 가능
- 데이터베이스 접근, 파일 시스템 조작, 환경 변수 사용 등
기본 사용법
function ReviewEditor() {
async function createReviewAction(formData) { // ✅ formData 매개변수 추가
'use server'
const content = formData.get("content")?.toString();
const author = formData.get("author")?.toString();
console.log("server action called", { content, author });
// 실제 DB 저장 로직 등...
}
return (
<section>
<form action={createReviewAction}>
<input name="content" placeholder="리뷰를 작성하세요." />
<input name="author" placeholder="작성자 이름" />
<button type="submit">작성하기</button>
</form>
</section>
);
}
왜 사용하는가?
- 간결한 개발: 함수 하나로 API 역할을 수행
- 직접적인 서버 로직 처리: AJAX → Controller 없이 바로 서버 작업 가능
- 타입 안전성: 클라이언트-서버 간 타입 공유 자연스럽게 처리
주의사항
- App Router 전용 (Pages Router에서는 사용 불가)
- 폼 액션으로 주로 사용 (<form action={serverAction}>)
- 클라이언트 컴포넌트에서는 import해서 사용
Hidden Input과 readOnly
<input
type="hidden"
name="bookId"
value={bookId}
readOnly // ✅ 필수! 없으면 React 경고 발생
/>
왜 readOnly가 필요한가?
- React에서 value prop이 있는 input은 제어 컴포넌트로 인식
- onChange 핸들러 없이 value만 있으면 "uncontrolled to controlled" 경고 발생
- readOnly 추가하면 경고 해결 (값이 변경되지 않음을 명시)
데이터 재검증 (Revalidation)
revalidatePath
서버 컴포넌트와 서버 액션에서만 사용 가능
async function createReviewAction(formData) {
'use server'
// 리뷰 저장 로직...
revalidatePath(`/book/${bookId}`); // 특정 페이지 재검증
}
- 문제 해결
- 문제: 리뷰 등록 후 페이지 새로고침해야만 새 리뷰가 보임 해결: revalidatePath 사용하면 자동으로 최신 데이터 반영
- 사용 제한사항
- 서버 컴포넌트에서만 사용 가능
- 서버 액션에서만 사용 가능
- 클라이언트 컴포넌트에서는 직접 호출 불가
- 특징:
- 페이지의 모든 캐시를 무효화 (Data Cache, Full Route Cache)
- force-cache 옵션이 있어도 캐시 삭제됨
- 페이지 새로고침 시 Full Route Cache 업데이트
다양한 재검증 방식
// 1. 특정 페이지만 재검증
revalidatePath(`/book/${bookId}`);
// 2. 특정 경로의 모든 동적 페이지 재검증
revalidatePath("/book/[id]", "page");
// 3. 특정 레이아웃의 모든 페이지 재검증
revalidatePath("/(with-searchbar)", "layout");
// 4. 모든 데이터 재검증
revalidatePath("/", "layout");
// 5. 태그 기준 데이터 캐시만 재검증 ⭐ 추천
revalidateTag(`review-${bookId}`);
revalidateTag (권장)
더 정밀한 캐시 제어가 가능
// 서버 액션에서
async function createReviewAction(formData) {
'use server'
// 리뷰 저장 로직...
revalidateTag(`review-${bookId}`); // 특정 태그만 재검증
}
// fetch에서 태그 설정
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/review/book/${bookId}`,
{
next: {
tags: [`review-${bookId}`] // 태그 지정
}
}
);
클라이언트 컴포넌트에서의 서버 액션
useActionState 활용
'use client'
import { useActionState, useEffect } from 'react';
export default function ReviewEditor({ bookId }) {
const [state, formAction, isPending] = useActionState(createReviewAction, null);
useEffect(() => {
if (state && !state.status) {
alert(state.error);
}
}, [state]);
return (
<section>
<form action={formAction}>
<input
type="hidden"
name="bookId"
value={bookId}
readOnly // ✅ hidden input에는 readOnly 필요
/>
<textarea
disabled={isPending}
required
name="content"
placeholder="리뷰를 작성하세요."
/>
<div>
<input
disabled={isPending}
required
name="author"
placeholder="작성자 이름"
/>
<button disabled={isPending} type="submit">
{isPending ? "..." : "작성하기"}
</button>
</div>
</form>
</section>
);
}
서버 액션 함수 (별도 파일)
'use server'
export async function createReviewAction(prevState, formData) {
const bookId = formData.get("bookId")?.toString();
const content = formData.get("content")?.toString();
const author = formData.get("author")?.toString();
if (!bookId || !content || !author) {
return {
status: false,
error: "리뷰 내용과 작성자를 입력해주세요"
};
}
try {
// 실제 DB 저장 로직
// await saveReview({ bookId, content, author });
revalidateTag(`review-${bookId}`);
return {
status: true,
error: ""
};
} catch (error) {
return {
status: false,
error: "리뷰 저장에 실패했습니다"
};
}
}
핵심 포인트
- 서버 액션은 기존 AJAX → API Controller 패턴을 대체
- revalidateTag가 가장 정밀한 캐시 제어 방식
- useActionState로 로딩 상태와 에러 처리 간편하게 처리
- hidden input에는 readOnly 속성 필요
- 서버에서만 실행되므로 민감한 로직 안전하게 처리 가능
이 링크를 통해 구매하시면 제가 수익을 받을 수 있어요. 🤗
https://inf.run/4oB9v
반응형
'기술, 개발 > nextjs' 카테고리의 다른 글
| 11차 강의 노트 (이미지 최적화, 배포) (7) | 2025.08.28 |
|---|---|
| 10차 강의 노트(고급 라우팅 패턴) (0) | 2025.08.28 |
| 8차 강의 노트 (스트리밍) (3) | 2025.08.28 |
| 7차 강의 노트 (페이지 캐싱) (0) | 2025.08.28 |
| 6차 강의 노트 (데이터 페칭) (0) | 2025.08.28 |