본문 바로가기

카테고리 없음

[Next.js] 서버 액션(Server Action)

반응형

들어가며

웹 서비스를 만들다 보면 아주 자연스러운 흐름을 기대하게 된다.

사용자가 버튼을 클릭하면 데이터가 저장되고, 저장된 결과가 반영된 최신 목록이 바로 다시 보이기를 말이다.

 

즉, 우리가 원하는 흐름은 대부분 다음과 같다.

버튼 클릭 → 서버로 데이터 전송 → 목록 갱신

 

기존 방식에서는 이 흐름을 구현하기 위해
REST API 설계부터 요청/응답 처리, 상태 관리까지 여러 단계를 거쳐야 했다.

 

하지만 Next.js App Router에서는 서버 액션(Server Action)을 활용하면 이 과정을 훨씬 단순하게 구성할 수 있다.

 

이번 글에서는 서버 액션이 무엇인지, 그리고 왜 이렇게 간단하게 동작하는지 정리해 보려고 한다.


내용

#1. 서버 액션(Action)이란?

서버 액션은 폼 제출이나 데이터 변경과 같은 작업을 처리하기 위해 서버에서 실행되는 비동기 함수다.

 

조금 더 쉽게 말하면, 클라이언트에서 직접 실행 요청을 보낼 수 있는 서버 함수라고 보면 된다.

예제를 하나 보자.

 

서버 액션 예제
// src/app/test/page.tsx

export default function Page() {
  const addReviewAction = async (formData: FormData) => {
    "use server";

    const content = formData.get("content");
    const reviewer = formData.get("reviewer");

    console.log({ content, reviewer });
  };

  return (
    <form action={addReviewAction}>
      <input type="text" name="content" />
      <input type="text" name="reviewer" />
      <button type="submit">작성하기</button>
    </form>
  );
}

 

사용자가 리뷰 내용을 입력하고 작성하기 버튼을 누르면 브라우저는 서버로 데이터를 전달하고
서버 액션이 실행되면서 로그가 출력된다.

서버 액션 실행 예시

 

이처럼 코드가 굉장히 단순한 걸 확인할 수 있다.


#2. 전통적인 방식과의 차이

전통적인 방식으로 동일한 기능을 만든다면 보통 다음과 같은 과정이 필요하다.

  • REST API 설계
  • 엔드포인트 구현
  • 요청/응답 처리
  • 상태 코드 관리
  • 에러 핸들링
  • 클라이언트 fetch 로직 작성

하지만 서버 액션을 사용하면 서버에서 실행할 함수 하나만 정의하면 된다.

따라서 API를 직접 만들지 않아도 서버 로직을 호출할 수 있는 구조라고 볼 수 있다.

 

코드가 간결해지는 것은 물론이고, 서버에서만 실행되는 함수이므로 브라우저에 로직이 노출되지 않는다는 장점도 있다.

 

그렇다면 클라이언트는 서버에 선언되어 정의된 함수를 실행할 수 있는 것일까?


#3. 클라이언트가 서버 함수를 호출할 수 있는 이유

서버 액션은 그 이름에서도 알 수 있듯이 서버에서만 존재하는 함수이다.

때문에 클라이언트는 서버 액션 함수를 호출할 수 없는 게 정상이라 생각할 수 있다.

 

그럼에도 불구하고 호출할 수 있는 이유는 바로 서버 액션 ID가 있기 때문이다.

 

Next.js는 빌드 과정에서 "use server"가 붙은 함수를 컴파일하면서

각 서버 액션에 고유한 식별자를 생성한다.

 

그리고 클라이언트에서는 함수 자체가 아니라 이 서버 액션 ID를 Header에 담아서 요청을 보내게 된다.

서버 액션의 동작 원리
Next-Action 항목에 담시는 서버 액션 ID

 

정리하면, 클라이언트가 함수를 직접 호출하는 것이 아니라

서버에게 "이 액션을 실행해줘"라고 요청하는 구조이다.

 

이러한 모든 과정은 Next.js가 내부적으로 추상화해서 자동으로 처리하는 것들이다.

덕분에 개발자들은 복잡한 HTTP 요청 코드를 직접 작성하지 않고 "일반 함수처럼" 서버 액션을 선언해

호출하는 것으로 서버의 비동기 함수를 실행할 수 있다.


#4. 주의사항

[ 1. 서버 액션은 어디까지나 함수 ]

예제를 보면 form 태그 안에서 사용하고 있어서 form 태그 안에서만 사용할 수 있는 것처럼 보일 수 있다.

 

하지만 서버 액션의 본질은 "함수"이다.

별도의 파일에 서버 액션을 정의하고 클라이언트 컴포넌트에서 직접 호출도 가능하다.

서버 액션 함수
// src/app/test/add-review.action.ts

"use server"

export const addReviewAction = async (formData: FormData) => {
  const content = formData.get("content");
  const reviewer = formData.get("reviewer");

  console.log({ content, reviewer });
};
서버 액션을 호출하는 비동기 함수
// src/app/test/page.tsx

"use client";

import { useRef } from "react";
import { addReviewAction } from "./add-review.action";

export default function Page() {
  const contentRef = useRef<HTMLInputElement>(null);
  const reviewerRef = useRef<HTMLInputElement>(null);

  const onClickAddReview = async () => {
    if (!contentRef.current || !reviewerRef.current) return;

    const content = contentRef.current.value;
    const reviewer = reviewerRef.current.value;

    const formData = new FormData();
    formData.set("content", content);
    formData.set("reviewer", reviewer);

    await addReviewAction(formData);
  };

  return (
    <div>
      <input ref={contentRef} type="text" name="content" />
      <input ref={reviewerRef} type="text" name="reviewer" />
      <button onClick={onClickAddReview}>작성하기</button>
    </div>
  );
}

 

이처럼 버튼 클릭 이벤트에서도 서버 액션을 실행할 수 있다.

 

[ 2. 반드시 async 선언 ]

서버 액션은 반드시 async 함수여야 한다.

async 제거
// src/app/test/add-review.action.ts

"use server"

export const addReviewAction = (formData: FormData) => {
  // !! 오류 발생 !!
};

async 제거 시 오류 발생

 

상당히 친절하게 서버 콘솔에서 오류를 확인할 수 있는데 여기에는 특별한 이유는 없다.

단지 Next.js 내부 설계가 비동기 기반이기 때문에 반드시 async 키워드를 사용해야 한다.


마치며

Next.js 서버 액션은 서버로 보내는 데이터 변경 요청을

함수처럼 표현할 수 있다는 점에서 흥미로운 것 같다.

 

기존에는 데이터를 변경하면

  • API 호출
  • 상태 갱신
  • 다시 목록을 불러오기

이러한 흐름을 직접 구성해야 했다.

 

하지만 서버 액션을 사용하면 데이터 변경 자체를 서버 함수로 표현할 수 있기 때문에

구조가 훨씬 직관적으로 보이기 시작한다.

 

여기서 더 재밌는 개념이 하나 더 있는데 바로 revalidatePath이다.

데이터가 변경된 이후 어떤 화면을 다시 갱신할 것인지 결정하는 역할을 하며,

캐시를 무효화하여 다음 요청 시 최신 데이터로 다시 렌더링되도록 만드는 방식으로 동작한다.

 

다음 글에서는 revalidatePath를 중심으로

서버 액션과 함께 사용하는 리뷰 작성 예제를 정리해 보려고 한다.

반응형