Vercel 서버리스 함수란?

현우
  1. API
  2. 프론트엔드
Vercel 서버리스 함수는 서버를 직접 관리하지 않고, 백엔드 로직을 실행할 수 있는 기능이에요. 기본적으로 상태가 없는 Stateless 하며, 각 요청은 독립적으로 처리되어요. 함수는 HTTP 요청이 들어올 때만 실행이 되고, 유후 상태에서는 비용이 발생되지 않아요. 또, 트래픽에 따라 자동으로 인스턴스가 늘어나는 구조를 가지게 됩니다.

/api 폴더 아키텍쳐로 서버리스 함수 구현해보기

비교적 간단하게 사용을 할 수 있는데, 아래와 같은 예제로 구성을 할 수 있어요.
서버리스 함수는 서버가 없어도 호출할 수 있는 구조이기에, 단순한 정적 사이트에서도 API 호출을 테스트하거나 간단한 서버 로직을 구현하는데 활용 할 수 있어요. 프론트엔드 중심의 프로젝트에서 별도의 백엔드 환경 없이도 빠르게 데이터를 처리할 수 있다는 것이 강점인 것이죠.
/api
  └── hello.ts       → GET /api/hello
  └── users/
        └── [id].ts  → GET /api/users/123 (동적 라우트)
// api/hello.ts
// 만약 동적으로 하고 싶다면 api/post/[id].ts 형식으로 지정하면 된다.
import type { VercelRequest, VercelResponse } from '@vercel/node';
 
export default function handler(req: VercelRequest, res: VercelResponse) {
  res.status(200).json({ message: 'Hello!' });
}
// app.ts
const response = await fetch('/api/hello');
const data = await response.json();
console.log(data.message);

vercel.json으로 서버리스 함수 구현해보기

/api 폴더 아키텍쳐가 아닌 vercel.json 으로 서버리스 함수를 명시적으로 구현을 하는 방법도 존재해요. 만약 서버 자원이 아닌, 서버에서 html 을 받아 html 의 일부를 파싱하고, 수정하는 서버리스 함수를 만든다고 가정을 해볼게요.
// server/server.cjs
// 아키텍쳐 구조는 분명 express.js의 자율적인 구조
 
const app = express();
 
// 프로젝트 루트 경로에서 dist 폴더 제공
const distPath = path.resolve(__dirname, "../dist"); // 절대 경로 사용
 
app.use(express.static(distPath));
 
app.get("/space/join/:id", async (req, res) => {
  const encryptedId = req.params.id;
  const filePath = path.join(distPath, "index.html"); // dist 경로에 있는 index.html 사용
  let html;
...
(이하 코드 생략)
// vercel.json
{
  "version": 2, // 1은 deprecated라서 2버젼이 고정이라고 생각하면 된다.
  "builds": [
    {
      "src": "server/server.cjs",
      "use": "@vercel/node"
    } // Express 서버를 서버리스 함수로 변환한다,
  ],
  "routes": [
    {
      "src": "/space/join/(.*)",
      "dest": "/server/server.cjs"
    }, // 이 경로로 오는 요청만 서버리스 함수로 보낸다.
  ]
}

Vercel 서버리스 함수, 편하고 마냥 좋은거 아닌가요?

마냥 좋지만은 않아요. 콜드스타트 방식이면서 Vercel 서버리스 함수는 실행에 제약을 가지고 있는데, 기본은 10초 · Pro 플랜은 60초의 최대 요청 시간을 제공해요. 메모리는 최대 1GB이기 때문에 주의해서 사용을 해야합니다.

Vercel 서버리스 함수의 동작 원리

일반 서버와 서버리스 함수를 비교해보면 아래와 같은 동작 원리를 가지게 되는 것을 볼 수 있어요.
[일반 서버]
서버 시작 → 계속 대기 중 → 요청 처리 → 계속 대기 중...
(24시간 프로세스가 살아있음)
 
[서버리스]
요청 들어옴 → 함수 생성 → 요청 처리 → 함수 종료
(요청이 없으면 아무것도 없음)
요청이 들어올 때만 함수를 생성해서 요청을 처리한다는 것은 "콜드 스타트" 라는 방식이에요.
💬
콜드 스타트와 웜 스타트?
[콜드 스타트] 첫 요청 or 오래 쉰 후
컨테이너 생성 + 코드 로드 + 실행 → 느림 (수백ms ~ 수초)
 
[웜 스타트] 연속 요청
이미 떠있는 컨테이너 재사용 → 빠름
위에서 확인한 콜드스타트의 개념을 차용해서 보면 Vercel 서버리스 함수는 아래와 같은 내부적인 세부 동작을 가지게 돼요.
1. 사용자가 /space/join/abc 요청
          ↓
2. Vercel 엣지 네트워크가 요청 수신
          ↓
3. routes 규칙 확인 → server.cjs 매핑됨
          ↓
4. AWS Lambda 위에서 컨테이너 생성 (콜드 스타트)
          ↓
5. server.cjs 코드 로드 & 실행
          ↓
6. 응답 반환
          ↓
7. 컨테이너 대기 (일정 시간 후 소멸)
또 각각의 Vercel 서버리스 함수는 각 요청 시 마다 개별적이고 독립적으로 움직이게 됩니다.
요청 A ─→ [컨테이너 1] 처리 후 종료
요청 B ─→ [컨테이너 2] 처리 후 종료
요청 C ─→ [컨테이너 1] 재사용 (웜 스타트)
悠悠自適
유유자적한 블로그 주인장 김현우입니다.
오늘도 안녕하신가요?
Subscribe
1
👍
현우
Vercel 서버리스 함수가 Express를 서버리스로 변환하는 방식
레이어 내부 코드에는 app.listen(3000)이 존재하지만,
Vercel 서버리스 함수는 이를 무시하고 내부적으로 아래와 같이 처리를 하게 됩니다.
(사실상 app.listen 은 사용하지 않기 때문에 지워도 무방한 코드라고 봐도 됨)
HTTP 요청
    ↓
Vercel 런타임이 req, res 객체 생성
    ↓
app(req, res) 직접 호출  ← listen() 대신 이걸 씀
    ↓
Express 라우터가 처리
    ↓
res.send() / res.json() 로 응답
See latest comments