React 19의 새로운 세계에는 두 개의 중요한 지시어(Directive)가 있었다. 'use client'는 파일 전체를 클라이언트 컴포넌트로 만드는 명확한 경계선 역할을 했다. 그리고 그 반대편에는, 훨씬 더 미묘하고 강력한 의미를 지닌 'use server'가 있었다.
처음 'use server'가 고안되었을 때, 그 역할은 단지 서버에서 실행될 단일 함수, 즉 서버 액션을 정의하는 것이었다.
// actions.js
async function createPost(formData) {
'use server'; // 이 함수는 서버에서 실행된다
// ...
}
하지만 개발이 진행되면서, 팀은 이 지시어가 단순한 함수 표시 이상의 역할을 해야 한다는 것을 깨달았다. 'use server'는 서버와 클라이언트 사이의 안전한 통로를 여는, 일종의 ‘게이트’ 역할을 해야 했다.
보안팀과의 협업 회의에서 중요한 문제가 제기되었다.
“클라이언트가 서버 함수를 직접 호출하는 것처럼 보이게 만드는 것은 매우 편리합니다. 하지만 만약 악의적인 사용자가 네트워크 요청을 조작해서, createPost 함수에 예상치 못한 데이터를 보내거나, 다른 함수를 호출하려고 시도하면 어떻게 방어할 거죠?”
그것은 RPC(Remote Procedure Call) 시스템이 가진 고질적인 보안 문제였다.
이 문제를 해결하기 위해, 'use server'는 단순한 표식을 넘어, React 빌드 시스템과 깊게 연동되는 핵심적인 장치가 되었다.
개발자가 'use server'가 포함된 파일을 저장하면, 빌드 시스템은 다음과 같은 작업을 수행했다.
-
서버 전용 코드 생성:
'use server'가 표시된 함수들은 클라이언트 번들에서 완전히 제거된다. 대신, 해당 함수를 가리키는 고유한 ID가 부여된 참조(reference) 객체만 남게 된다. 이로써 서버 함수의 소스 코드가 클라이언트에 절대 유출되지 않도록 보장했다. -
클라이언트-서버 경계 정의: 빌드 시스템은
'use server'를 기준으로, 클라이언트 코드와 서버 코드가 서로를 어떻게 호출하는지를 분석하여 명확한 경계, 즉 ‘서버-클라이언트 경계(Server-Client Boundary)’를 정의했다. 이 경계를 넘나드는 모든 데이터는 자동으로 직렬화 가능한 형태로 변환되었다. -
액션 엔드포인트 생성: 빌드 시스템은
'use server'로 정의된 모든 함수에 대해, 안전하게 호출될 수 있는 숨겨진 API 엔드포인트를 자동으로 생성했다. 클라이언트가 서버 액션을 호출하면, 실제로는 이 숨겨진 엔드포인트로 암호화된 요청을 보내는 것이었다.
결과적으로, 'use server'는 단순한 지시어가 아니라, 세 가지 중요한 역할을 동시에 수행하는 강력한 선언이 되었다.
- 개발자에게는: “이 함수는 서버에서 안전하게 실행됩니다”라는 약속.
- 번들러에게는: “이 함수의 코드는 클라이언트 번들에 포함시키지 마세요”라는 명령.
- React 런타임에게는: “이 함수 호출을 안전한 RPC 요청으로 변환하세요”라는 신호.
'use client'가 클라이언트 세계의 문을 여는 열쇠였다면, 'use server'는 서버 세계로 통하는 안전한 게이트를 만드는 설계도였다. 이 지시어 덕분에 개발자는 보안이나 번들링 같은 복잡한 내부 동작을 신경 쓸 필요 없이, 그저 선언 한 줄만으로 서버와 클라이언트의 경계를 자유롭게, 그리고 안전하게 넘나들 수 있게 되었다. 이 작은 문자열 뒤에는, 복잡성을 추상화하려는 React의 깊은 철학이 숨어 있었다.


