본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
16장 : 고급 주제

서버리스 함수 활용

Route Handler·서버 액션·Proxy와 Edge 런타임을 실행 위치와 제한에 맞게 선택합니다.

Next.js는 React 프레임워크를 넘어 SSR, SSG, API 처리까지 아우르는 풀스택 프레임워크로 발전했습니다.

특히 서버리스 함수(Serverless Functions)를 통해 별도 서버를 직접 운영하지 않고도 API 엔드포인트와 백엔드 로직을 구현할 수 있습니다.

이 함수들은 Vercel 같은 플랫폼에서 자동 배포/관리되므로 인프라 부담을 줄이면서 빠르게 기능을 확장할 수 있습니다.

이 절에서는 Route Handler, Server Actions, Proxy와 선택적 Edge 런타임의 실행 위치를 구분합니다.

먼저 서버 로직이 Route Handler, Server Action, Proxy 중 어디에 배치되는지 큰 그림을 잡습니다.


서버리스 함수란 무엇이며 왜 중요한가요?

서버리스 함수 (Serverless Functions)는 클라우드 공급자(AWS Lambda, Google Cloud Functions, Azure Functions 등)가 서버 인프라 관리를 전적으로 담당하고, 개발자는 코드만 작성해 배포하는 컴퓨팅 모델입니다.

코드는 이벤트(예: HTTP 요청, 데이터베이스 변경)에 의해 트리거될 때만 실행되며, 사용량에 따라 비용이 청구됩니다.

서버리스 함수의 주요 이점
  • 인프라 관리 부담 감소: 서버를 프로비저닝, 패치, 스케일링할 필요가 없습니다. 클라우드 공급자가 모든 인프라를 관리합니다.
  • 자동 스케일링: 트래픽 증가에 따라 자동으로 함수 인스턴스가 확장(스케일 아웃)되어 대규모 트래픽도 안정적으로 처리할 수 있습니다.
  • 비용 효율성: 코드가 실행될 때만 비용이 발생합니다(Pay-per-execution 모델). 유휴 시간에는 비용이 발생하지 않아 효율적입니다.
  • 빠른 배포: 코드 변경 사항을 빠르게 배포하고 적용할 수 있습니다.
  • 개발 생산성 향상: 백엔드 인프라 걱정 없이 비즈니스 로직 구현에 집중할 수 있습니다.

Next.js는 이러한 서버리스 함수의 이점을 애플리케이션에 쉽게 통합할 수 있도록 지원합니다.


Next.js에서 서버리스 함수 구현하기

App Router의 주요 서버 실행 단위는 Route Handler, Server ActionsProxy입니다.

Route Handler

Route Handler는 App Router에서 HTTP 엔드포인트를 만드는 기본 방식입니다.

src/app/api 아래의 route.ts 파일이 경로에 대응하며 HTTP 메서드 이름으로 처리 함수를 내보냅니다.

기본 Route Handler 예시
src/app/api/hello/route.ts
// GET 요청을 처리하는 Route Handler

import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const name = searchParams.get('name') || 'World';

  // JSON 응답 반환
  return NextResponse.json({ message: `Hello, ${name}!` });
}

// POST, PUT, DELETE 등 다른 HTTP 메서드도 동일한 방식으로 export 가능
export async function POST(request: Request) {
  const data = await request.json();
  return NextResponse.json({ received: data, status: 'success' }, { status: 200 });
}
특징
  • HTTP 요청(GET, POST 등)에 응답하는 RESTful API 엔드포인트를 쉽게 구축할 수 있습니다.
  • 클라이언트에서는 fetch로 이 엔드포인트를 호출할 수 있습니다.
  • Vercel에 배포 시 자동으로 서버리스 함수로 변환됩니다.
  • 데이터베이스 접근, 외부 API 호출, 인증 처리 등 다양한 백엔드 로직을 구현할 수 있습니다.

Server Actions (App Router Only)

Server Actions는 App Router에서 클라이언트의 폼 제출이나 이벤트를 서버 함수와 연결하는 기능입니다.

데이터 변경과 폼 처리를 구현할 때 입력 검증, 권한 확인, 캐시 재검증 경계를 함께 둡니다.

Server Actions 예시 (src/app/[locale]/add-todo/page.tsxactions.ts)
src/app/[locale]/add-todo/page.tsx
import { addTodo } from './actions'; // Server Action 임포트

export default async function AddTodoPage({
  params,
}: {
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;

  return (
    <form action={addTodo} style={{ margin: '50px', padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
      <input type="hidden" name="locale" value={locale} />
      <label htmlFor="todo" style={{ marginRight: '8px' }}>새 할 일</label>
      <input id="todo" type="text" name="todo" maxLength={100} required style={{ padding: '10px', marginRight: '10px' }} />
      <button type="submit" style={{ padding: '10px 15px', backgroundColor: '#007bff', color: 'white', border: 'none', borderRadius: '4px' }}>
        할 일 추가
      </button>
    </form>
  );
}
src/app/[locale]/add-todo/actions.ts (Server Action 정의)
'use server'; // 이 파일의 모든 함수가 서버에서 실행됨을 명시

import { revalidatePath } from 'next/cache'; // 데이터 갱신을 위해 Next.js 캐시 유틸리티 임포트

export async function addTodo(formData: FormData): Promise<void> {
  const value = formData.get('todo');
  const locale = formData.get('locale');

  if (
    typeof value !== 'string' ||
    typeof locale !== 'string' ||
    !['ko', 'en', 'ja'].includes(locale)
  ) return;

  const todo = value.trim();
  if (!todo || todo.length > 100) return;

  // 실제 데이터베이스에 할 일을 추가하는 로직 (예시)
  console.log(`서버에서 할 일 추가됨: ${todo}`);
  // await db.todos.create({ text: todo });

  // 특정 경로의 캐시를 무효화하여 최신 데이터를 가져오도록 강제
  revalidatePath(`/${locale}/add-todo`); // 현재 locale 페이지의 데이터와 UI 갱신
}

브라우저의 requiredmaxLength는 편의를 위한 1차 검증일 뿐 우회할 수 있습니다.

따라서 Server Action에서도 타입, 공백 제거 뒤 길이, 업무 규칙을 다시 확인해야 합니다.

특징
  • 클라이언트와 서버 간의 데이터 직렬화 및 통신을 Next.js가 자동으로 처리하여 개발 복잡성을 줄입니다.
  • 폼 제출, 버튼 클릭 등 UI 상호작용에 직접적으로 반응하는 서버 로직을 작성하기에 이상적입니다.
  • Next.js 캐시 API와 통합됩니다. 경로는 revalidatePath, 태그의 SWR 갱신은 revalidateTag(tag, 'max'), Server Action의 즉시 만료는 updateTag로 처리합니다.
  • Vercel에 배포 시 서버리스 함수로 변환됩니다.

Proxy와 Edge Runtime

Next.js 16은 요청 앞단의 파일 규칙을 middleware.ts에서 proxy.ts로 바꿨습니다.

Proxy는 Node.js 런타임에서 실행되므로 데이터베이스 드라이버와 Node.js API를 사용할 수 있습니다.

다만 모든 요청의 초입에서 실행될 수 있으므로 복잡한 조회와 긴 작업을 넣지 않습니다.

src/proxy.ts
import { NextResponse, type NextRequest } from 'next/server';

export function proxy(request: NextRequest) {
  const sessionCookie = request.cookies.get('session');

  const [, locale] = request.nextUrl.pathname.split('/');

  if (!sessionCookie) {
    return NextResponse.redirect(new URL(`/${locale}/login`, request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/:locale(ko|en|ja)/dashboard/:path*'],
};

Proxy 이름 변경은 단순한 파일명 교체가 아니라 요청 경계를 명시하는 변화입니다.

인증과 권한을 다룰 때는 직접 쿠키 값만 보는 예제보다 Auth.js 같은 인증 계층의 검증 결과를 사용합니다.

Edge Runtime이 필요한 Route Handler는 파일 상단에 runtime = 'edge'를 명시할 수 있습니다.

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

export const runtime = 'edge';

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

Edge Runtime 선택 기준 (Node.js 런타임과 비교)

Edge Runtime과 Node.js Runtime은 빠르냐 느리냐만으로 고르는 선택지가 아닙니다.

의존성, 데이터 접근, 실행 제한을 먼저 비교해야 합니다.

항목Edge RuntimeNode.js Runtime
최적 용도가벼운 지역 분산 응답Proxy, DB 접근, 파일 처리, 업무 로직
지연 시간매우 낮음 (엣지 실행)상대적으로 높을 수 있음 (리전 중심)
사용 가능한 APIWeb Standard 중심, Node 내장 모듈 제약Node 생태계 전반 사용 가능
실행 시간/메모리더 엄격한 제한 환경상대적으로 여유로운 환경
선택 순서
  1. 로직이 fs, 네이티브 모듈, 특정 Node 전용 SDK에 의존하는지 먼저 확인합니다.
  2. 의존성이 있다면 Node 런타임을 선택합니다.
  3. 의존성이 없고 Route Handler를 사용자 가까이 배치해야 할 근거가 있으면 Edge Runtime을 검토합니다.
  4. 트래픽 급증 구간에서 p95/p99 지연 시간을 비교 측정한 뒤 최종 확정합니다.

Edge Runtime Pitfall 체크리스트

  • Edge에서 동작하지 않는 라이브러리(Node 전용 모듈 의존)를 사전에 점검했는가?
  • Proxy와 Edge Route Handler의 런타임 차이를 확인했는가?
  • Edge/Node 런타임 혼용 시 환경 변수 및 비밀키 접근 정책을 분리했는가?
  • 실패 시 Node.js Route Handler로 우회할 fallback 경로를 준비했는가?

Edge를 선택할 때는 사용할 수 없는 Node API와 fallback 경로를 먼저 점검해야 합니다.


서버리스 함수 활용 전략 및 고려사항

  • 적절한 런타임 선택
    • Route Handler·Server Actions·Proxy: 기본 Node.js 런타임에서 데이터베이스 접근과 업무 로직을 처리합니다.
    • Edge Route Handler: 지역 분산 실행의 이점이 측정되고 Node.js 전용 의존성이 없는 응답에 한정합니다.
  • 환경 변수 관리: 민감한 정보(API 키, DB 연결 문자열)는 환경 변수로 관리하고, Vercel 대시보드나 .env 파일을 통해 안전하게 주입합니다.
  • 콜드 스타트(Cold Start) 이해: 서버리스 함수는 일정 시간 사용되지 않으면 콜드 상태가 됩니다. 첫 요청 시 컨테이너가 시작되어 지연이 생길 수 있으므로 중요한 API는 실제 배포 환경의 시작 시간을 측정합니다.
  • 상태 비저장(Stateless) 설계: 서버리스 함수는 상태를 유지하지 않는(stateless) 방식으로 설계해야 합니다. 사용자 세션 정보나 데이터는 데이터베이스, 캐시(Redis), 또는 외부 스토리지에 저장해야 합니다.
  • 로그 및 모니터링: Vercel 대시보드에서 서버리스 함수의 실행 로그와 성능 지표를 모니터링할 수 있습니다. 문제 발생 시 디버깅에 활용합니다.
  • 오류 처리: 서버리스 함수 내에서 발생하는 오류를 적절히 처리하고 로깅하여 안정성을 확보합니다.
  • 보안: Route Handler와 Server Actions의 입력에 인증, 인가와 유효성 검사를 적용해야 합니다.

위 전략을 배포 전 점검 기준으로 정리하면, 런타임 선택과 상태 저장 위치를 먼저 분리해서 볼 수 있습니다.

다음으로는 요청 성격에 따라 어떤 실행 위치가 맞는지 비교합니다.

Next.js의 서버리스 함수 기능은 화면 코드와 서버 로직을 같은 프로젝트 안에서 관리하게 해 줍니다.

Route Handler, Server Actions, Proxy와 선택적 Edge Route Handler는 호출 방식, 데이터 접근과 배포 제약이 다르므로 요청 성격에 맞게 선택해야 합니다.

마지막 비교표에서는 Route Handler, Server Actions, Proxy와 Edge Runtime의 선택 기준을 호출 방식과 실패 처리까지 함께 놓고 봅니다.

운영 단계에서는 런타임 경계, 캐시 전략, 통신 실패 대응을 분리해 관리해야 합니다.