RSS듀오랩스
Next.js

Next.js 라우트 핸들러와 서버 함수 구분

작성자
듀오랩스 대표·6분 읽기

앱 라우터에서 서버로 무언가를 보내는 방법이 둘입니다. app/api/.../route.ts 에 라우트 핸들러를 만들거나, "use server" 를 붙인 서버 함수를 부르는 것입니다. 폼 하나를 붙일 때마다 어느 쪽을 쓸지 정해야 하는데, 「둘 다 서버에서 도니 아무거나」로 두면 나중에 갈립니다.

라우트 핸들러는 예전 API 라우트의 새 이름이라는 생각

Pages 라우터를 쓰던 사람은 pages/apiapp/api 로 옮겨 온 것으로 이해하기 쉽습니다. 라우트 핸들러 문서도 그 관계를 인정합니다. 라우트 핸들러는 app 디렉터리 안에서만 쓸 수 있고 Pages 의 API 라우트에 해당하므로 둘을 함께 쓸 필요가 없다고 적습니다.

다만 앱 라우터에는 없던 선택지가 하나 늘었습니다. 서버 함수입니다. 화면에서 데이터를 바꾸는 일에는 굳이 엔드포인트를 만들지 않아도 됩니다. 그래서 질문이 「API 를 어떻게 만드나」가 아니라 **「이 동작에 공개된 주소가 필요한가」**로 바뀝니다.

주소가 필요한 일과 필요 없는 일

라우트 핸들러는 웹 표준의 RequestResponse주소 하나를 만드는 일입니다. 문서의 정의도 그렇습니다. 그래서 다음 경우에 맞습니다.

  • 외부 서비스가 부르는 웹훅 수신 (결제 대행사, 발급 대행사, 메신저 플랫폼)
  • HTML 이 아닌 응답을 내보내는 곳 (RSS, 사이트맵, CSV 내보내기, 이미지)
  • 모바일 앱이나 다른 서비스가 쓰는 공개 API
  • 헬스 체크처럼 사람이 아니라 기계가 부르는 주소

서버 함수는 반대입니다. 우리 화면에서만 부르고, 주소가 노출될 이유가 없는 동작입니다. 저장, 삭제, 상태 변경이 여기 해당합니다. 서버 함수에도 실제로는 요청이 오가지만 그 주소는 프레임워크가 관리합니다.

그래서 저는 질문을 이렇게 씁니다. 우리 화면 말고 다른 무언가가 이 동작을 불러야 하는가. 그렇다면 라우트 핸들러, 아니라면 서버 함수입니다.

조회는 컴포넌트에서, 변경은 서버 함수에서

한 가지를 더 가려야 합니다. 화면에 데이터를 보여 주는 일은 둘 중 어느 쪽도 아닐 때가 많습니다. 서버 컴포넌트가 렌더링하면서 직접 읽으면 되기 때문입니다.

app/api/products/route.ts 를 만들고 서버 컴포넌트에서 fetch("/api/products") 로 부르는 코드를 종종 봅니다. 같은 서버 안에서 자기 자신에게 HTTP 요청을 한 번 더 보내는 구조입니다. 서버 컴포넌트에서 데이터베이스나 라이브러리를 직접 부르면 그 왕복이 사라집니다.

React 문서도 서버 함수를 데이터 조회에 권하지 않는다고 적습니다. 프레임워크가 서버 함수를 한 번에 하나씩 처리하고 결과를 캐시하지 않기 때문입니다. 정리하면 이렇습니다.

하는 일 자리
화면에 보여 줄 데이터 읽기 서버 컴포넌트에서 직접
우리 화면에서 데이터 바꾸기 서버 함수
외부가 부르는 주소 라우트 핸들러

라우트 핸들러를 만들면 따라오는 것

주소를 만든다는 것은 누구나 부를 수 있는 입구를 하나 여는 일입니다. 서버 함수와 달리 주소를 우리가 정하므로 찾기도 쉽습니다. 그래서 라우트 핸들러에는 최소한 다음이 따라와야 합니다.

첫째, 호출자 확인. 웹훅이라면 서명 검증, 내부 전용 주소라면 토큰 검사입니다. 이 블로그의 캐시 무효화 엔드포인트도 헤더의 토큰을 상수 시간 비교로 확인하고, 틀리면 401 을 돌려줍니다.

둘째, 입력 검증. 본문과 쿼리 문자열은 모두 호출자가 정한 값입니다. 서버 함수의 인자와 같은 취급이 필요합니다.

셋째, 응답 형식과 상태 코드. 라우트 핸들러는 우리가 Response 를 직접 만들므로 상태 코드와 헤더를 정확히 정할 수 있습니다. 이것은 부담이 아니라 장점입니다. 캐시 헤더가 필요한 응답, 예를 들어 사이트맵이나 피드를 라우트 핸들러로 두면 신선도를 직접 제어할 수 있습니다.

프레임워크 밖에서 부르는 쪽을 생각하기

Next.js 문서는 이 패턴을 「Backend for Frontend」라고 부릅니다. HTML 이 아닌 콘텐츠를 내보내는 공개 엔드포인트를 만들고 데이터 소스에 접근하는 구조입니다.

제 기준으로는 여기에 버전 관리의 부담이 붙습니다. 라우트 핸들러의 주소와 응답 모양은 한 번 밖으로 나가면 우리 화면만 고쳐서는 바꿀 수 없습니다. 웹훅을 등록해 둔 외부 서비스, 모바일 앱, 고객사의 스크립트가 그 모양에 묶입니다. 서버 함수는 우리 코드 안에서만 쓰이므로 이름과 인자를 자유롭게 바꿀 수 있습니다.

그래서 새 동작을 만들 때는 서버 함수로 시작하고, 밖에서 부를 필요가 생기는 순간 라우트 핸들러를 추가하는 순서가 안전하다고 봅니다. 반대 순서, 즉 일단 API 부터 만들어 두고 우리 화면이 그것을 부르는 구조는 왕복과 버전 부담을 처음부터 떠안습니다.

여기까지가 확실한 부분

라우트 핸들러가 app 디렉터리 전용이고 Pages 의 API 라우트에 대응한다는 점, 웹 표준 Request·Response 를 쓴다는 점, Backend for Frontend 패턴에 대한 문서의 설명은 Next.js 16 문서 기준입니다. 서버 함수를 데이터 조회에 권하지 않는다는 설명은 react.dev 문서 기준입니다. 「서버 함수로 시작하고 필요할 때 라우트 핸들러를 추가한다」는 순서는 제 판단이고, 외부 연동이 먼저 정해진 프로젝트라면 반대가 맞을 수 있습니다.

마지막 수정:

공유하실 때는 출처(Duolabs)와 원문 주소를 표시해 주세요.