본문으로 건너뛰기

안동민 개발노트

본문 시작

서버리스 함수 활용

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 중 어디에 배치되는지 큰 그림을 잡습니다.

서버리스 함수는 요청을 작은 실행 단위로 나눈다

Next.js의 API Route, Server Action, Edge Function은 실행 위치와 제약이 다르므로 요청 성격에 맞춰 골라야 한다.

  1. Request
    HTTP / Form / Proxy

    짧은 이벤트가 진입

  2. Function
    짧은 로직

    DB/API/cache 호출

  3. Result
    Response / cache sync

    Response.json 또는 redirect를 반환하고 변경 뒤에는 재검증 함수를 명시적으로 호출

  4. Limit
    cold start/time limit

    긴 작업은 분리


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

서버리스 함수 (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, 무거운 처리는 Node가 맡는다

런타임 선택은 성능 취향이 아니라 의존성, 데이터 접근, 실행 제한을 먼저 확인하는 결정이다.

  1. Edge Runtime

    적합: 인증 기준, 리다이렉트, 지역 기반 분기, A/B 테스트 부적합: fs, 네이티브 모듈, 무거운 DB SDK

  2. Node.js Runtime

    적합: DB 접근, 파일 처리, 복잡한 비즈니스 로직 주의: 리전 위치와 콜드 스타트 지연

질문Edge로 가는 경우Node로 가는 경우
Node 전용 API가 필요한가아니오, Web API만 사용예, fs/crypto/SDK 필요
응답 전에 초고속 분기가 필요한가예, 요청 초입에서 판단아니오, 데이터 처리 중심
비밀키와 DB 연결이 무거운가가벼운 토큰 검증풀 연결, 트랜잭션 필요
항목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 경로를 먼저 점검해야 합니다.

Edge Runtime Pitfall 체크리스트

Edge Runtime은 전 세계 가까운 위치에서 실행되는 대신 사용할 수 있는 API와 실행 시간이 더 엄격하다.

  1. dependency
    Node 전용 모듈

    fs, net, 일부 DB 드라이버, 네이티브 의존 패키지는 Edge에서 실패할 수 있다.

  2. latency
    과한 미들웨어

    모든 요청 초입에서 무거운 연산을 하면 TTFB가 나빠진다.

  3. secret
    환경 변수 정책

    Edge와 Node의 비밀키 접근 범위와 노출 가능성을 분리해 본다.

  4. fallback
    Node 우회 경로

    Edge에서 처리할 수 없는 경우 Node API Route로 넘기는 기준을 둔다.


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

  • 적절한 런타임 선택
    • 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의 입력에 인증, 인가와 유효성 검사를 적용해야 합니다.

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

서버리스 함수는 런타임과 상태 저장 위치를 먼저 분리해 본다

함수 자체는 짧게 실행되고 사라진다. 상태, 비밀키, 로그, 실패 처리는 외부 기준으로 설계해야 한다.

  1. 입력 검증

    요청 body와 권한을 함수 시작 지점에서 확인한다.

  2. 짧은 실행

    긴 작업은 queue나 background job으로 분리한다.

  3. 명시적 실패

    timeout, retry, fallback 응답을 API 계약에 포함한다.

점검좋은 기준나쁜 신호확인 위치
RuntimeEdge/Node 선택 이유 명확Node SDK를 Edge에 배치route config
StateDB/Redis/Storage에 보관메모리 전역 변수 의존data layer
Secrets서버 전용 env로 제한클라이언트 번들 노출env prefix
Observability로그, latency, error rate 추적실패 재현 불가Vercel logs

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

요청이 가벼운 분기인지, 데이터 처리인지에 따라 실행 위치가 달라진다

서버리스 선택은 “서버에서 돈다”가 아니라 요청의 성격을 보고 위치를 고르는 일이다.

요청 성격추천 위치이유주의점
로그인 전 redirectProxy가장 앞에서 빠르게 분기무거운 DB 조회 금지
폼 제출과 데이터 변경Server Action경로는 revalidatePath, 즉시 tag는 updateTag, SWR tag는 revalidateTag(tag, 'max')권한, CSRF, 입력 검증
외부 API 프록시API Route비밀키를 서버에 숨김rate limit, timeout
무거운 DB 트랜잭션Node RuntimeNode SDK와 연결 관리콜드 스타트와 리전

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

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

API Routes, Server Actions, Edge Functions는 같은 문제가 아니다

세 방식은 모두 서버에서 실행되지만 호출 방식, 데이터 경계, 배포 제약이 다르다.

  1. API Routes

    좋음: 외부 클라이언트도 호출하는 HTTP API 주의: 인증과 rate limit이 빠지기 쉬움

  2. Server Actions

    좋음: 폼 제출, 데이터 변경, revalidatePath/updateTag 호출 주의: SWR tag는 revalidateTag(tag, 'max'), 클라이언트 입력은 검증

  3. Edge Functions

    좋음: 인증 초입, redirect, geo 분기 주의: Node API와 무거운 SDK 제약

질문선택확인할 것
브라우저 밖에서도 호출해야 하나API RouteHTTP method, auth, schema
폼 액션과 UI 갱신이 붙어 있나Server Action권한, revalidatePath/updateTag, stale 허용 시 revalidateTag(tag, 'max')
요청 초입에서 빠르게 분기하나Edge Function지원 API, TTFB, fallback

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

선택 기준은 호출 방식, 데이터 접근, 지연 시간, 실패 처리다

기능 이름보다 요청이 실제로 무엇을 해야 하는지가 런타임 선택을 결정한다.

선택지호출 방식데이터 접근실패 처리
API Routefetch/외부 HTTP서버 비밀키, DB 가능status code와 JSON 계약
Server Actionform/action 호출DB 변경 + revalidatePath/updateTag, SWR은 revalidateTag(tag, 'max')폼 오류, optimistic UI 조정
Edge Function요청 초입 자동 실행가벼운 토큰/헤더 중심Node route로 fallback
Node RuntimeAPI/Action 내부 런타임Node SDK와 트랜잭션timeout, retry, log

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

서버리스 운영 기준은 런타임 경계, 캐시, 실패 응답을 분리하는 것이다

함수는 짧게 실행되지만 사용자는 느린 응답, 오래된 데이터, 실패 메시지를 경험한다.

  1. boundary
    런타임 경계

    boundary Edge와 Node의 의존성, 비밀키, 실행 시간을 분리한다.

  2. cache
    캐시 전략

    cache GET 응답, revalidate, stale 데이터 표시를 요청별로 정한다.

  3. failure
    실패 응답

    failure timeout, 권한 오류, 외부 API 실패를 같은 JSON 계약으로 돌려준다.

운영 질문좋은 답나쁜 신호
어디서 실행되는가route별 runtime 명시Edge에서 Node SDK 사용
얼마나 오래 저장되는가cache/revalidate 정책 문서화사용자별 응답 cache-first
실패를 어떻게 알리는가표준 오류 코드와 retry 안내무한 로딩 또는 빈 응답