서버 액션과 라우트 핸들러
2025.04.04 기술 토론#1

알아볼 것...
- 서버액션
- 점진적 향상
- 타입 유지의 간편함(?)
- 라우트핸들러
- 공용API: CORS 에러 여부
다만 서버 액션의 경우, Next13부터 등장하여 14버전에서 안정화가 되었다.
비동기 함수를 클라이언트 컴포넌트에서 사용 가능하게 해준다는 점에서 아주 강력한 Next.js의 무기가 아닐까?
왜냐하면 서버액션을 정의하는 방법은 파일 최상단에 use server 지시어만 넣어주면 되기 때문!
내부 로직에 의해 늘 POST 요청으로 변환된다.
get, post, patch, delete 등 restful한 요청이 필요할 때 좋다.
미들웨어, 쿠키, 헤더 직접 제어 가능
보통 서버의 응답값을 NextResponse.json()을 통해 파싱하므로, 그 중간에 원하는 대로 response를 정제할 수도 있다.
Request, Response 객체를 직접 다룰 수 있어, 쿠키 설정, 헤더 파싱, 미들웨어 통합 등이 용이하다.
라우트 핸들러(Route Handlers)를 사용해 내부적인 API Endpoint를 만드는 것의 장점은
- API를 외부 서비스나 클라이언트가 직접 호출 가능
서버 액션은 Next.js 내부에서만 사용 가능한 반면,
라우트 핸들러는 API 엔드포인트이므로 외부 서비스에서도 호출 가능하다.
(예: Webhook 처리, 모바일 앱에서 호출 등)
- 업로드·스트리밍 등 특수한 요청 처리
예: FormData 업로드, 동영상 스트리밍 등 Raw Request Body를 직접 처리해야 할 경우
라우트 핸들러가 유일한 방법이라고..
- 로직 재사용 측면에서 유리
라우트 핸들러로 만든 API는 프론트엔드, 모바일, 서버 등 다양한 곳에서 공통으로 재사용 가능
엔드포인트가 뭔데요?
API가 두 시스템(어플리케이션)이 상호작용할 수 있게 하는 프로토콜의 총집합이라면, ENDPOINT는 API가 서버에서 리소스에 접근할 수 있도록 가능하게 하는 URL이다.
클라이언트가 서버에 데이터를 요청하거나 보낼 수 있는 주소(URL)라고 생각하면 된다.

예시로 위 Cat Facts API는 Breeds, Facts라는 API endpoints를 가지고 있다.
요청을 하려면 BaseURL에 선택한 엔드포인트를 추가하면 된다. => https://catfact.ninja/facts

2개의 엔드포인트를 가진 Cat Facts API를 예시로 이해해 보았다.

서버 컴포넌트에서 그냥 비동기함수로 호출하였을 때는 GET 요청으로 처리됨.
그럼 서버액션으로 바꾸면 이것도 POST로 요청이 될까?!
아니었다.

게시글 상세 데이터를 불러오는 파일에 지시어를 넣고, 다시 실행시켜 보았으나

띠용? 이게 왜 아직도 GET? 했던 나...
그렇다, 서버액션의 POST 요청으로의 변환은 클라이언트 컴포넌트에 한한 것.
생각해보면 서버액션은 캐싱을 하지 않는다는 것이 클라이언트 컴포넌트에서 내부적 변환에 의해 서버에 POST로 요청하기 때문에 그런 것일까 생각이 들었는데...
하지만 서버에서 사용한다면 GET으로, 비동기함수 로직의 의도와 동일하게 서버에 요청이 들어가니까 캐싱이 되는 것일까?
하지만 서버에서 실행하는 비동기 함수나 서버액션이나 어짜피 서버에서 실행한다면 굳이 선언할 필요가 없지 않을까? 하는 궁금증이 생겼다.
🤔 서버 액션이 캐싱되지 않는 이유
: 클라이언트에서 호출한 서버 액션은
POST형태로 요청되므로, Next.js 내부에서 캐싱되지 않는다.
🤔 서버 컴포넌트에서 쓸 건데, 서버 액션?
: 그럴 필요 없다. 비동기 함수 직접 호출이 되는데 뭐하러 함?
=> 따라서 서버 액션의 필요성은 클라이언트 컴포넌트에서 비동기 함수를 사용하는 것에 있음.
여전히 아리송하지만 기술 토론을 통해서 새로운 궁금증이 생겼고 해당 내용들을 좀 더 찾아보고 정리할 필요성을 느꼈다.
기존에는 Supabase 요청을 서버 액션을 통해 처리하도록 구현했다.
하지만 현재 서버 컴포넌트에서만 데이터를 불러오고 있기 때문에, 서버액션으로 구현할 필요성이 없음을 깨달았다.
이를 일반 비동기 함수로 변경.
=> use server 지시어만 삭제했다!
잠깐, 클라이언트 컴포넌트에서 사용하려면?
👾 서버용
createClient는 브라우저 환경에서는 작동하지 않는다.
클라이언트 컴포넌트에서는 서버용이 아닌, 클라이언트 전용createClient를 사용해야 한다.

선언해 둔 use server 지시어는 Next.js가 자동으로 해당 함수를 서버로 serialize하여 전송하고, 서버에서 실행되도록 처리해주기 때문.
그래서 탠스택쿼리를 사용해 클라이언트 컴포넌트에서 서버액션을 실행시키더라도, 서버용 createClient를 통해 supabase에 요청을 보낼 수 있는 것. 어짜피 서버에서 처리되기 때문이다.
queryFn 내부에서 서버 액션을 호출하는 경우
→ 서버 액션 자체는 서버에서 실행되므로
→ 서버용 createClient를 사용해도 괜찮음
위와 같은 이해를 바탕으로 GPT에게 요청한 아키텍쳐 다이어그램.
image generated by OpenAI.

Client부에서는 TanStack을 통해 queryFn을 실행한다.
그럼 내부 서버에서 server client를 통해 Supabase에 통신을 시작하고..
그렇게 돌아온 response를 다시 전달하고..
이후 TanStack이 돌아온 응답값을 받아서 반환값을 업데이트한다.
const { data, isLoading } = useQuery({... 와 같이 useQuery를 사용했을 때,
돌아오는 값인 data가 서버 응답이 오기 전까지는 undefined의 상태였다가 응답값으로 바뀌면서 state 변경에 따른 리렌더링이 발생한다!