서버리스 함수 활용
Route Handler·서버 액션·Proxy와 Edge 런타임을 실행 위치와 제한에 맞게 선택합니다.
Next.js는 React 프레임워크를 넘어 SSR, SSG, API 처리까지 아우르는 풀스택 프레임워크로 발전했습니다.
특히 서버리스 함수(Serverless Functions)를 통해 별도 서버를 직접 운영하지 않고도 API 엔드포인트와 백엔드 로직을 구현할 수 있습니다.
이 함수들은 Vercel 같은 플랫폼에서 자동 배포/관리되므로 인프라 부담을 줄이면서 빠르게 기능을 확장할 수 있습니다.
이 절에서는 Route Handler, Server Actions, Proxy와 선택적 Edge 런타임의 실행 위치를 구분합니다.
서버리스 함수란 무엇이며 왜 중요한가요?
서버리스 함수 (Serverless Functions)는 클라우드 공급자(AWS Lambda, Google Cloud Functions, Azure Functions 등)가 서버 인프라 관리를 전적으로 담당하고, 개발자는 코드만 작성해 배포하는 컴퓨팅 모델입니다.
코드는 이벤트(예: HTTP 요청, 데이터베이스 변경)에 의해 트리거될 때만 실행되며, 사용량에 따라 비용이 청구됩니다.
서버리스 함수의 주요 이점- 인프라 관리 부담 감소: 서버를 프로비저닝, 패치, 스케일링할 필요가 없습니다. 클라우드 공급자가 모든 인프라를 관리합니다.
- 자동 스케일링: 플랫폼이 트래픽에 맞춰 인스턴스를 조정합니다. 동시 실행 한도와 연결하는 데이터베이스의 용량도 함께 고려합니다.
- 사용량 기반 과금: 호출 수, CPU, 메모리 등 플랫폼의 과금 단위를 확인합니다. Vercel Fluid compute는 I/O 대기 중 CPU 과금은 멈추지만, 처리 중인 요청에 할당된 메모리는 계속 계산합니다.
- 빠른 배포: 코드 변경 사항을 빠르게 배포하고 적용할 수 있습니다.
- 개발 생산성 향상: 백엔드 인프라 걱정 없이 비즈니스 로직 구현에 집중할 수 있습니다.
Next.js는 이러한 서버리스 함수의 이점을 애플리케이션에 쉽게 통합할 수 있도록 지원합니다.
Next.js에서 서버리스 함수 구현하기
App Router의 주요 서버 실행 단위는 Route Handler, Server Actions와 Proxy입니다. 이들은 Next.js의 기능이며, 실제로 함수에 배포할지 직접 운영하는 Node.js 서버에서 실행할지는 호스팅 방식에 따라 달라집니다.
Route Handler
Route Handler는 App Router에서 HTTP 엔드포인트를 만드는 기본 방식입니다.
app의 라우트 세그먼트에 두는 route.ts가 HTTP 메서드 이름으로 처리 함수를 내보냅니다. 다음처럼 src/app/api 아래에 둘 수 있지만 api 디렉터리만 가능한 것은 아닙니다.
// 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 });
}이 POST 예제는 JSON 입력을 돌려주는 최소 응답입니다. 잘못된 JSON, 인증과 업무 입력 검증은 아직 처리하지 않습니다.
특징- HTTP 요청(GET, POST 등)에 응답하는 RESTful API 엔드포인트를 쉽게 구축할 수 있습니다.
- 클라이언트에서는
fetch로 이 엔드포인트를 호출할 수 있습니다. - 요청 시 처리하는 Route Handler는 Vercel Functions로 실행할 수 있습니다. 빌드 때 확정할 수 있는 정적
GET결과는 정적 산출물이 될 수도 있습니다. - 데이터베이스 접근, 외부 API 호출, 인증 처리 등 다양한 백엔드 로직을 구현할 수 있습니다.
Server Actions (App Router Only)
Server Actions는 App Router에서 클라이언트의 폼 제출이나 이벤트를 서버 함수와 연결하는 기능입니다.
데이터 변경과 폼 처리를 구현할 때 입력 검증, 권한 확인, 캐시 재검증 경계를 함께 둡니다.
Server Actions 예시 (src/app/[locale]/add-todo/page.tsx와 actions.ts)
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>
);
}'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 갱신
}예제의 데이터베이스 저장 코드는 주석이며 실제로는 입력을 검사하고 로그를 남긴 뒤 경로를 재검증합니다. revalidatePath는 데이터 저장을 대신하지 않습니다.
브라우저의 required와 maxLength는 편의를 위한 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를 사용할 수 있습니다.
다만 모든 요청의 초입에서 실행될 수 있으므로 복잡한 조회와 긴 작업을 넣지 않습니다.
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 이름 변경은 단순한 파일명 교체가 아니라 요청 경계를 명시하는 변화입니다.
이 Proxy는 session 쿠키의 존재만 확인하므로 빈 값이나 위조된 값도 검증하지 못합니다. 실제 인증·권한 검사는 Route Handler와 Server Action 등 데이터 처리 지점에서도 수행해야 합니다.
Edge Runtime이 필요한 Route Handler는 파일 상단에 runtime = 'edge'를 명시할 수 있습니다.
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은 빠르냐 느리냐만으로 고르는 선택지가 아닙니다.
의존성, 데이터 접근, 실행 제한을 먼저 비교해야 합니다.
배포 위치나 성능 이름보다 실제 사용하는 API와 플랫폼 지원을 먼저 확인합니다.
| 기준 | Node.js | Edge |
|---|---|---|
| 사용 가능한 API | Node.js API와 호환 패키지 | Web API 중심 · Node.js API 일부 미지원 |
| 데이터 접근 | 일반 Node.js DB 드라이버·SDK 검토 | Edge 호환 드라이버·HTTP API 필요 |
| 실행 위치와 한도 | 플랫폼의 지역·시간·메모리 설정 | 플랫폼의 지역·시간·메모리 설정 |
- 사용 가능한 API
- Node.js: Node.js API와 호환 패키지Edge: Web API 중심 · Node.js API 일부 미지원
- 데이터 접근
- Node.js: 일반 Node.js DB 드라이버·SDK 검토Edge: Edge 호환 드라이버·HTTP API 필요
- 실행 위치와 한도
- Node.js: 플랫폼의 지역·시간·메모리 설정Edge: 플랫폼의 지역·시간·메모리 설정
Next.js 16의 Proxy는 기본 Node.js 런타임입니다. 사용자와 가깝게 실행돼도 데이터 저장소가 멀면 전체 응답이 빨라진다고 보장할 수 없습니다.
- 로직이
fs, 네이티브 모듈, 특정 Node 전용 SDK에 의존하는지 먼저 확인합니다. - 의존성이 있다면 Node 런타임을 선택합니다.
- 의존성이 없고 Route Handler를 사용자 가까이 배치해야 할 근거가 있으면 Edge Runtime을 검토합니다.
- 트래픽 급증 구간에서 p95/p99 지연 시간을 비교 측정한 뒤 최종 확정합니다.
선택한 런타임의 환경 변수·비밀키 접근 방식과 오류 응답·로그도 확인합니다.
서버리스 함수 활용 전략 및 고려사항
-
적절한 런타임 선택
- Route Handler·Server Actions·Proxy: 기본 Node.js를 기준으로 의존성을 검토합니다. Server Action은 사용하는 페이지·레이아웃의 런타임을 따릅니다.
- Edge Route Handler: 지역 분산 실행의 이점이 측정되고 Node.js 전용 의존성이 없는 응답에 한정합니다.
- 환경 변수 관리: 민감한 정보(API 키, DB 연결 문자열)는 환경 변수로 관리하고, Vercel 대시보드나
.env파일을 통해 안전하게 주입합니다. - 콜드 스타트(Cold Start) 이해: 서버리스 함수는 일정 시간 사용되지 않으면 콜드 상태가 됩니다. 첫 요청 시 컨테이너가 시작되어 지연이 생길 수 있으므로 중요한 API는 실제 배포 환경의 시작 시간을 측정합니다.
- 상태 비저장(Stateless) 설계: 함수 인스턴스가 재사용되면 메모리 값도 남을 수 있지만 다음 요청이 같은 인스턴스에 도달한다고 보장할 수 없습니다. 지속돼야 하는 세션과 데이터는 외부 저장소에 둡니다.
- 로그 및 모니터링: Vercel 대시보드에서 서버리스 함수의 실행 로그와 성능 지표를 모니터링할 수 있습니다. 문제 발생 시 디버깅에 활용합니다.
- 오류 처리: 서버리스 함수 내에서 발생하는 오류를 적절히 처리하고 로깅하여 안정성을 확보합니다.
- 보안: Route Handler와 Server Actions의 입력에 인증, 인가와 유효성 검사를 적용해야 합니다.