라우터 핸들러와 서버 액션

김현준·2024년 12월 20일

넥스트JS

목록 보기
4/8

Next.js 15 (App Router 기반)에서는 서버 기능을 처리하는 두 가지 방식이 존재한다.

  • 라우터 핸들러 (Route Handler)
  • 서버 액션 (Server Action)

이 둘은 모두 서버에서 실행되지만 목적과 사용 방식이 다르다.


라우터 핸들러 (Route Handler)

개념

  • 서버 API 엔드포인트를 정의하는 방식
  • pages/api 대신 app/api/**/route.ts에서 작성
  • REST API처럼 GET, POST 등의 요청 처리 가능

파일 구조 예시

/app
  /api
    /hello
      route.ts

코드 예시

// app/api/hello/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json({ message: 'Hello from Route Handler!' });
}

특징

  • App Router 전용
  • GET, POST, PUT, DELETE, PATCH 등 HTTP 메서드 대응
  • fetch('/api/hello') 방식으로 클라이언트에서 호출
  • React 컴포넌트 트리 바깥의 완전한 서버 코드
  • 클라이언트 번들에 포함되지 않음 → 민감 정보 노출 우려 없음
  • NextRequest, NextResponse로 요청/응답 처리

그럼 Server Component도 서버에서 실행되는데, 뭐가 다를까?

use client 지시어가 없는 Server Component도 기본적으로 서버에서 실행된다.
하지만 Route Handler는 React와 완전히 격리된 순수 서버 함수로, 다음과 같은 차별점이 있다:

항목Server ComponentRoute Handler
위치React 렌더링 트리 내부React 바깥 (app/api)
클라이언트 번들 포함 여부❌ (Hydration 없음)✅ 아예 번들 대상 아님
민감 정보 안전성비교적 안전구조적으로 완전 차단
인증 처리쿠키 접근 시 제약 있음NextRequest에서 완전한 접근 가능
API 설계어려움 (fetch나 mutate로 연결 필요)직관적 REST API 가능

즉, Route Handler는 API 서버처럼 동작하는 "완전한 서버 전용 코드"로 보안, 역할 분리, 유지보수 면에서 더 명확한 이점이 있다.

언제 사용할까?

  • 외부 API 연동, DB 저장 등 네트워크 요청이 필요할 때
  • 로그인, 웹훅, 결제 콜백 등 REST API가 필요할 때
  • 클라이언트에서 직접 fetch() 호출할 때
  • 민감한 비즈니스 로직/토큰/비밀키 등 보안성 높은 작업이 필요할 때

서버 액션 (Server Action)

개념

  • use server로 선언된 서버에서 실행되는 함수
  • 클라이언트 컴포넌트 안에서도 직접 호출 가능
  • 별도의 API 라우트 없이 폼과 직접 연결

구조 예시

/app/login/page.tsx
/app/login/actions.ts

코드 예시

서버 함수:

// app/login/actions.ts
'use server';

export async function handleLogin(formData: FormData) {
  const email = formData.get('email');
  const password = formData.get('password');
  return { errors: ['Invalid email or password'] };
}

클라이언트에서 호출:

// app/login/page.tsx
'use client';

import { handleLogin } from './actions';

export default function Login() {
  return (
    <form action={handleLogin}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      <button type="submit">로그인</button>
    </form>
  );
}

특징

  • form action={서버함수} 패턴으로 간단하게 제출 처리
  • 클라이언트에서 직접 호출 가능하지만 실행은 서버에서
  • 별도의 API 작성 없이 서버 로직만 만들면 됨
  • useFormState와 함께 상태 기반 UI도 구현 가능

서버 액션 요청 바디 크기 제한을 늘려주는 설정

// next.config.ts
experimental: {
  serverActions: {
    bodySizeLimit: '20mb',
  },
}

서버 액션은 POST 요청처럼 동작하고, 전송되는 데이터(body) 크기가 기본 1MB 제한됨
→ 이미지/FormData 업로드 시 반드시 이 설정 필요


라우터 핸들러 vs 서버 액션 비교

항목Route HandlerServer Action
위치app/api/**/route.ts컴포넌트 내부 or lib/actions/**
호출 방식fetch()form action={함수} 또는 직접 호출
주 사용 목적API 라우트, 외부 요청내부 서버 처리, 폼 제출
브라우저 번들❌ 클라이언트 포함 안 됨❌ 클라이언트 포함 안 됨
외부 백엔드 연동적합가능하나 덜 직관적
보안 민감 작업안전더 직관적, 쿠키 접근 등 쉬움

서버 액션은 외부 백엔드(Spring 등) 협업에 유용할까?

대부분 안 쓴다 또는 최소한만 쓴다

  • 이유: 서버 액션은 Next.js 내부에서 직접 DB나 쿠키를 다루는 구조에 적합
  • Spring 백엔드처럼 이미 서버가 있다면, 그냥 클라이언트에서 fetch로 요청 보내는 게 훨씬 간단함
// 굳이 서버 액션으로 외부 백엔드 호출할 필요는 없다
'use server';

export async function loginAction(formData: FormData) {
  const name = formData.get('name');
  const password = formData.get('password');

  const res = await fetch('http://localhost:8080/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name, password }),
  });

  return res.json();
}

🧾 이런 경우엔 그냥 useMutation(fetch)로 처리하자.


언제 어떤 걸 써야 할까?

상황추천 방식
외부 API 요청, DB 저장 등 REST API 필요Route Handler
클라이언트에서 fetch로 직접 호출Route Handler
간단한 폼 제출 처리Server Action
쿠키, 세션 조작 등 보안 민감 작업Server Action
백엔드 없이 Next.js만 사용하는 경우Server Action

결론

  • Route Handler: 전통적인 API 서버 느낌
    fetch()로 요청하고, Next.js가 API 서버 역할을 함
    → React와 완전히 분리된 독립 서버 코드

  • Server Action: 서버 코드가 프론트 코드 안에 있는 느낌
    → 코드가 간결해지고, 폼과 직결되어 사용하기 편리함
    간단한 내부 처리나 Form과의 통합에 강점

profile
기록하자

0개의 댓글