서버와의 약속, Actions

402025년 09월 24일4

useOptimistic 훅은 클라이언트에서 서버로 데이터를 보내는 동안의 사용자 경험을 극적으로 개선했다. 하지만 이 과정의 전제, 즉 ‘클라이언트가 서버에 데이터를 보내는 방식’ 자체는 여전히 전통적인 fetch API 호출에 머물러 있었다.

서버 컴포넌트가 ‘읽기(read)’의 패러다임을 바꿨다면, 이제는 ‘쓰기(write)’의 패러다임을 바꿀 차례였다.

앤드류 클라크는 현재의 데이터 제출 방식이 가진 근본적인 문제점을 지적했다.

“우리는 지금 두 개의 다른 언어로 이야기하고 있습니다. 클라이언트 컴포넌트는 fetch('/api/posts', ...) 같은 HTTP 요청을 보내고, 서버는 API 라우터에서 그 요청을 받아 해석한 뒤 데이터베이스 작업을 수행합니다. 이 과정에는 항상 번거로운 직렬화(serialization)와 파싱(parsing) 과정이 끼어들고, 개발자는 클라이언트와 서버 양쪽에 비슷한 로직을 중복해서 작성해야만 합니다.”

그의 말대로였다. 클라이언트에서는 폼 데이터를 모아 JSON으로 만들고, 서버에서는 그 JSON을 받아 유효성을 검사하는 코드가 프로젝트마다 반복되었다.

“서버 컴포넌트는 우리에게 새로운 가능성을 보여줬습니다. 서버 환경의 함수를 직접 호출하는 듯한 경험 말이죠. 만약… 데이터 ‘쓰기’ 작업도 그렇게 할 수 있다면 어떨까요?”

그의 질문은 새로운 개념의 등장을 예고했다. 바로 ‘서버 액션(Server Actions)’이었다.

서버 액션의 아이디어는 놀랍도록 대담했다.

서버에, 오직 서버에서만 실행되도록 설계된 특별한 함수를 정의하는 것이다. 이 함수는 데이터베이스를 수정하거나, 이메일을 보내는 등 서버 사이드의 작업을 수행한다.

// actions.js (서버 파일)
'use server'; // <--- 이 함수는 서버에서만 실행된다는 약속

export async function createPost(formData) {
  const title = formData.get('title');
  const content = formData.get('content');
  
  // 데이터베이스에 게시물 생성
  await db.post.create({ data: { title, content } });

  // 필요하다면 페이지를 갱신하도록 지시
  revalidatePath('/posts');
}

그리고 가장 혁신적인 부분은, 클라이언트 컴포넌트가 이 서버 함수를 마치 로컬 함수처럼 직접 import해서 호출할 수 있다는 점이었다.

// PostForm.client.js
'use client';

import { createPost } from './actions'; // 서버 함수를 직접 임포트

export function PostForm() {
  const handleSubmit = async (formData) => {
    await createPost(formData);
  };

  // ... 폼 렌더링
}

클라이언트 개발자는 더 이상 API 엔드포인트 주소나 HTTP 메서드, 헤더 같은 것들을 신경 쓸 필요가 없었다. 그저 createPost라는, 타입까지 완벽하게 추론되는 함수를 호출하기만 하면 그만이었다.

“어떻게 이게 가능한 거죠?” 한 엔지니어가 물었다. “클라이언트가 어떻게 서버 함수를 직접 호출합니까?”

“직접 호출하는 게 아니야.” 앤드류가 설명했다. “컴파일러(번들러)가 이 import 구문을 보고, createPost 함수 호출을 특별한 fetch 요청으로 자동으로 변환해주는 거지. 개발자의 눈에는 함수 호출처럼 보이지만, 실제로는 React가 정해진 규약에 따라 서버와 통신하는 RPC(Remote Procedure Call)가 일어나는 거야.”

이것이 바로 서버 액션의 본질이었다.

개발자에게는 타입이 안전한 함수 호출의 경험을 제공하고, 그 복잡한 네트워크 통신 과정은 React가 알아서 처리해주는 것.

서버와 클라이언트를 연결하던 위태로운 fetch API라는 다리가, 이제는 ‘액션(Action)’이라는 이름의 견고하고 안전한 순간이동 게이트로 대체되고 있었다. 개발자는 더 이상 두 개의 다른 세계를 오가며 번역하는 수고를 할 필요가 없었다. 그저 서버와 ‘함수’라는 이름으로 약속하고, 그 약속을 호출하기만 하면 됐다. 이 새로운 약속은 웹 개발의 ‘쓰기’ 작업을 이전과는 비교할 수 없을 정도로 단순하고 강력하게 만들 준비를 하고 있었다.