Skip to content
art of loving
Go back

Next.js, Vercel, Supabase 아키텍처 분석

Updated:
Edit page

배포에 성공하기는 쉬웠다. 무수한 가이드가 인터넷에 있고 AI가 비약적인 발전을 거듭하는 시대니까. 하지만 알고 쓰지 않으면 잘 쓴 건지, 의도한 바에 일치하는지 점검할 수 없다. 그래서 글을 남겨둔다.

1. PaaS vs Baas

매번 헷갈리는 Iaas / Pass / BaaS, / Saas. 다시 한 번 정리해보자.

분류의미관리 대상예시
IaaSInfrastructure as a ServiceOS, 런타임, 앱, 데이터 모두AWS EC2, GCP, Compute Engine
PaasPlatform as a Service앱 코드 & 데이터Vercel, Netlify, Heroku
BaasBackend as a suevice프론트엔드 로직만Supabase, Firebase
SaaSSoftware as a Service설정만Notion, Slack

추상화 수준이 높아질수록, 그러니까 IaaS → PaaS → Baas → SaaS 순서로 내려갈 수록 플랫폼은 더 많은 것을 대신 처리하고 개발자가 직접 관리해야 하는 범위는 좁아진다. 다른 말로 플랫폼에 대한 의존도가 높아진다 라고 말하기도 한다. 이 트레이드오프, 즉, 하나를 얻으려면 다른 하나를 포기해야 하는 관계를 이해하는 것이 서비스 선택의 출발점이다.

2. Severless 배포

2-1. 정적 자산이 전세계에 배포되는 방법

Vercel 이나 NetlifyNext.js 프로젝트를 푸시하면 내부적으로 일어나는 과정이 있다.

(1) Git push → CI/CD 파이프라인 트리거

(2) 빌드 서버에서 next build 실행

(3) 결과물 분류 :

  • /public.next/static → 정적 자산 (HTML, CSS, JS, 이미지)

  • API Routes / Server Components → 서버리스 함수 (Lambda-like)

(4) 정적 자산 → CDN (Content Delivery Network) 엣지 노드 전체에 복제

(5) 서버리스 함수 → 요청이 들어올 때마다 실행 (cold start 발생 가능)

keyword: 콜드스타트 (cold start)

서버리스 함수는 일정 시간 요청이 없으면 실행환경 또는 컨테이너가 내려간다. 새 요청이 들어오면 환경을 재부팅하는데 수백ms에서 수초까지 걸린다. Vercel Edge FunctionsV8 isolate 기반인데 코드스타터가 매우 짧지만 Node.js 런타임 기반 함수는 상대적으로 길다.

CDN (Content Delivery Network)

전 세계 여러 지역에 분산된 서버 노드들의 네트워크. 사용자가 어떤 사이트에 접속하면 미국 원본 서버까지 가지 않고 가장 가까운 곳의 엣지 노드에서 정적 파일을 받아온다.

VercelEdge Network를 통해 100개 이상의 리전에 자산을 배포한다. Cash-Control 헤더를 잘 설정하면 앳지 케시 히트율을 올릴 수 있고 결과적으로 원본 서버 부하의 속도를 줄이기 때문에 응답 속도가 빨라진다.

keyword: Cache-Control 헤더

HTTP 응답 헤더. 브라우저와 CDN이 자원을 얼마나, 어디에, 어떤 방식으로 저장할 지 정의하는 지침서.

keyword: 엣지 캐시 히트율 Edge Cache Hit Rate

전체 요청 중 원본 서버(origin)까지 가지 않고 CDN 엣지 노드에서 즉시 응답을 처리한 비율. 동일한 자원에 대해 캐시 키를 정규화하고 적절한 유효 기간을 설정, 히트율을 높이는 것이 인프라 비용 절감과 속도 개선의 핵심.

2-2. 서버리스 함수 (Serverless Function)

Next.jsAPI RoutesServer ActionsVercel / Netlify에서 서버리스 함수로 실행되는데, 이것은 전통적인 의미, 즉 항상 켜져있는 서버와는 다르다.

구분

전통 서버

서버리스 함수

실행방식

항상 대기중

요청이 들어올 때만 실행

비용

고정

  • 서버 운영 시간

종량제

  • 실행 횟수 * 실행 시간

상태 State

메모리에 유지 OK

요청마다 새 인스턴스 생성, 무상태 state (Statless) 구조

콜드 스타트

없음

있음

  • 첫 요청 시 지연

스케일링

수동 또는 오토스케일링 필요

자동

  • 프레픽에 따라 인스턴스 복제

3. Managed 서비스

(1) OS 업데이트 및 보안 패치

(2} PostgreSQL 버전 업그레이드

(3) 백업 스케줄링 (cron + pg_dump 또는 WAL 아카이빙

(4) 장애 발생 시 복구 절차

(5) 연결 풀림 (pg_bouncer 등)

(6) SSL/TIS 설정

(7) 방화벽 및 인카운드 규칙 설정

(8) 모니터링 (CPU, 메모리 ,디스크, 커넥션 수)

소규모 프로젝트에서 이러한 진행은 비효율적이다. 모든 것을 관리해야 하기 때문이다.

3-2. Supabase가 추상화하는 것들

Supabase 는 GpostgreSQL을 기반으로 하면서도 운영 부담을 플랫폼 수준에서 처리해준다. 개발자가 직접 다루는 레이어는 스키마 설계와 쿼리 뿐이다.

Supabase 제공 기능

(1) PostgreSQL : managed

(2) Auth : JWT 기반, OAuth 포함

(3) Storage : 파일 업로드 → S3 호환 버킷

(4) Realtime : 변경 감지 → WebSocket으로 브로드캐스트

(5) Edge Functions : Deno 기반 서버리스

(6) PostgREST : REST API 자동 생성

keyword: WebSocket 브로드캐스트 Broadcast

특정 클라이언트로부터 전송된 메시지를 현재 연결된 모든 클라이언트에게 동시에 전송하는 이벤트 전파 방식. 단순 일대일 통신이 아니라 Pub/Sub(발행/구독) 패턴을 서버 레이어에서 추상화하여 대규모 사용자에게 실시간 상태를 동기화할 때 사용.

keyword: S3 호환 버킷 (compatible API)

AWS S3가 객체 스토리지 시장의 표준이 되면서 대다수의 스토리지 서비스 (Cloudflare R2, MinlO 등)가 S3와 동일한 RESTful API규격을 따름. aws-sdk 라이브러리를 그대로 사용하면서 엔드포인트 URL만 바꾸면 서비스 이동 가능.

keyword: Edge Functions

전 세계에 분산된 엣지 노드에서 실행되는 서버리스 함수. 기존 Lambda(Node.js) 보다 가벼운 V8 엔진을 사용.

keyword: PostgREST

PostgreSQL 데이터베이스 스키마를 읽어 표준 RESTful API를 자동으로 생성해주는 웹 서버. 단순 CRUD 코드를 작성할 필요를 없애고 GET /users?id=eq.1 쿼리를 SQL로 자동 변환. RLS와 결헙하여 백엔드 로직 없이도 DB레벨에서 보안 및 권한 제어 수행. 복잡한 비즈니스 로직이 없는 서비스라면 이것만으로 별도 백엔드 서버 없이 풀스택 개발 가능.

3-3. Row Level Security

RLS라고 약칭한다. 테이블의 각 행에 대한 접근 권한을 정책으로 정의하는데, Supabase가 클라이언트에서 직접 DB에 쿼리를 날릴 수 있는 구조PostgREST를 제공하기 때문에 발생할 수 있는 보안 취약점을 방지한다.

RLS를 설정하지 않으면 anon키 (공개) 로도 전체 데이터를 조회할 수 있기 때문에 Supabase 프로젝트 초기 세팅 시 놓치지 않고 필수로 해주어야 함

4. 객체 스토리지 (Object Storage)

4-1. 객체 스토리지란

전통적인 파일 시스템 (디렉터리 구조)와 달리 key-value쌍으로 저장하여, 각 파일(객체)가 가진 고유한 식별자(key)를 가지고 HTTP URL로 직접 접근이 가능한 방식.

장점

  • 파일 수와 용량에 거의 제한 없이 확장 가능

  • 파일에 HTTP URL 로 직접 접근 가능 → CDN 연동 용이

  • 내부적으로 데이터를 여러 노드에 복제 → 고가용성

  • 서버 로컬 스토리지 대비 저렴한 가격

4-2. S3 vs Cloudflare R2

AWS S3는 업로드 Ingress 비용이 무료지만 다운로드 Egress(데이터 전송 비용)이 발생해서 내 프로젝트에 적합하지 않았음.

이미지나 동영상처럼 용량이 큰 파일을 자주 서빙하는 서비스라면 Egress 비용이 전체 인프라 비용에서 상당한 비중을 차지한다. Cloudflare R2는 이 비용을 제거하기 때문에 선 택했다.

대신 R2는 저장 용량과 클래스에 따른 작업 양에 따라 금액이 부과된다. 프로젝트에 적합하게 설계해야 한다.

5. 인바운드 규칙

Netlify/Vercel 계열 프로젝트와 달리 Node.js 백엔드를 EC2에 올리려면 보안그룹(Security Group)과 인바운드/아운바운드 규칙을 직접 설정 해주어야 한다.

5-1. 보안그룹 (Security Group)

AWS의 보안그룹은 EC2 인스턴스에 대한 가상의 방화벽이다. 어떤 IP에서 어떤 포트로 들어오는 트래픽을 허용할지, 어떤 포트로 나가는 트래픽을 허용할지 정의한다.

보안그룹은 화이트리스트 (허용 목록 규정) 방식. 명시적으로 허용하지 않은 트래픽은 모두 차단된다.

5-2. 일반적인 Node.js + Socket.io 서버의 인바운드 규칙 구성

유형

프로토콜

포트 범위

소스

SSH

TCP

22

내 IP

  • 관리 접속용

HTTP

TCP

80

0.0.0.0/0

  • 모든 IP

HTTPS

TCP

443

0.0.0.0/0

  • 모든 IP

Custom TCP

TCP

3001

0.0.0.0/0

  • Node 앱 포트

포트 22 (SSH): EC2 인스턴스에 터미널로 접속하는 포트. 0.0.0.0/0(모든 IP 허용)으로 열어두면 브루트포스 공격에 노출된다. 가능하면 본인의 IP만 허용하거나 AWS Systems Manager Session Manager를 사용해 SSH 포트 자체를 닫는 것이 권장된다.

포트 80/443 (HTTP/HTTPS): 웹 요청을 받는 표준 포트. Node.js 앱이 직접 80 포트를 열기보다는 Nginx를 앞단에 두고 리버스 프록시 구성을 하는 것이 일반적이다.

Socket.io 포트: 기본적으로 HTTP 프로토콜 위에서 동작하며(초기 핸드셰이크), 이후 WebSocket으로 업그레이드된다. 별도 포트를 사용한다면 해당 포트를 인바운드 규칙에 추가해야 한다. Nginx로 리버스 프록시를 구성하면 80/443을 통해 처리할 수 있다.

이 부분부터는 이해가 어려워서 Gemini랑 같이 봤다

5-3. Nginx 리버스 프록시 구성 개념

mermaid 로 쓰기

[인터넷] ↓ :443 (HTTPS) [Nginx] ← SSL 종료(SSL Termination) 처리 ↓ 내부 포트 프록시 [Node.js 앱 :3001] ↕ [Socket.io WebSocket 연결]

# /etc/nginx/sites-available/myapp
server {
    listen 443 ssl;
    server_name api.yourdomain.com;

    # SSL 인증서 경로 (Let's Encrypt 등)
    ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;

        # WebSocket 업그레이드를 위한 헤더 설정
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }
}

proxy_set_header UpgradeConnection "upgrade의 Socket.io가 없으면 WebSocket 업그레이드가 실패하고 폴링(polling) 방식으로 폴백된다. CORS 에러와 함께 연결이 불안정해지는 원인이 되기도 한다.

Nginx 리버스 프록시

사용자가 직접 3001 포트 Node.js로 접속하게 하지 않고 Nginx 라는 문지기 앞에 세우는 이유

(1) SSL Termination

암호화된 통신 HTTPS를 해독하는 작업은 연산량이 많다. 이걸 Node.js가 직접하면 메인이 되는 비즈니스 로직 처리가 느려짐. 고성능인 Nginx가 암호화를 풀어서 Node.js에게는 가벼운 HTTP로 전달해주는 구조

(2) 보안 (추상화)

실제 서비스가 돌아가는 포트를 외부에 직접 노출하지 않으면 공격자는 Nginx (80/443)만 볼 수 있기 때문에 내부 구조를 알 수 없다

(3) 로드 밸런싱 및 정적 파일 서빙

나중에 서버가 커져서 Node.js 앱을 여러 개 띄울 때 Nginx가 트래픽을 골고루 나눠주는 역할을 하게 됩

keyword: 프록시 Proxy

대리인이라는 뜻의 단어. “누구를” 대신하느냐에 따라 이름이 바뀜.

  • 포워드 프록시 Forward proxy

    • 클라이언트가 외부 인터넷에 접속할 때 정체를 숨기거나 특정 사이트 접근을 제한하기 위해

  • 리버스 프록시

    • 외부 사용자가 내 서버에 직접 접근하는 걸 막고 중간에서 요청을 대신 받아 내부 서버로 전달

→ 실제 백엔드 서버의 IP와 포트 노출 X => 보안 용이

HTTPS 암호화 해제 작업을 프록시가 전담해 백엔드 서버의 부하를 줄임

→ 자주 요청되는 정적 자원을 프록시가 저장해두고 즉시 응답 → 캐싱

keyword: 로드 밸런싱 Load Balancing

부하(Load)를 균형있게 나누는 것. 서비스 규모가 커져서 서버 한 대 single instance로는 감당이 안 될 때 똑같은 서버를 여러 대 띄우고 트래픽을 분산시킴.

  • 동작: 리버스 프록시로 동작하는 Nginx가 요청 → 뒤에 있는 여러 대의 Node.js 서버 중 하나를 골라 요청을 던짐

  • 주요 알고리즘

(1) Round Robin: 순서대로 한번씩 돌아가며 전달

(2) Least Connections: 연결된 사용자가 가장 적은 서버로 전달

(3) IP Hash: 특정 IP는 항상 특정 서버로 연결

인프라 위에서 돌아가는 소프트웨어만 들여다보다가 직접 해보니 그만큼 기반이 되는 인프라도 중요하다는 걸 알게 되었다. 옳거나 그른 스택이 아니라 서비스의 요구사항, 운영리소스에 맞게 조율해야 하는데, 너무 막연히 종류를 알고만 있었어서 애를 좀 먹었다. 다음 번에 할 때는 더 잘 할 수 있겠지?


Edit page
Share this post on:

Previous Post
01. 네트워크 알아보기
Next Post
[Docker] `ADD` or `COPY`