본문으로 건너뛰기

18. 배포

SvelteKit 애플리케이션을 실제 서비스로 만들기 위해서는 적절한 배포 전략이 필요합니다. 다양한 플랫폼과 환경에 최적화된 어댑터 시스템을 통해 서버리스, 정적 호스팅, 컨테이너 등 원하는 방식으로 배포할 수 있습니다. 이 장에서는 SvelteKit의 어댑터 시스템을 이해하고, 주요 플랫폼별 배포 방법과 Docker를 활용한 컨테이너화까지 완전히 마스터해보겠습니다.


18.1 어댑터 이해하기

다양한 플랫폼 어댑터

SvelteKit의 어댑터는 빌드된 앱을 특정 배포 환경에 맞게 변환하는 플러그인입니다. 각 플랫폼의 고유한 요구사항과 최적화 방법을 처리하여 동일한 코드베이스로 다양한 환경에 배포할 수 있게 합니다. 프레임워크 정의 인프라(FDI) 개념을 통해 플랫폼이 애플리케이션의 요구사항을 자동으로 파악하고 최적화합니다.

공식 어댑터 목록

adapter-comparison.js
// 주요 공식 어댑터와 특징
const adapters = {
// 자동 어댑터 - 플랫폼 자동 감지
'@sveltejs/adapter-auto': {
pros: [
'자동 플랫폼 감지',
'설정 불필요',
'초보자 친화적',
],
cons: ['세부 설정 제한', '빌드 시간 증가'],
platforms: ['Vercel', 'Netlify', 'Cloudflare', 'Azure'],
},

// Vercel 어댑터
'@sveltejs/adapter-vercel': {
pros: ['엣지 함수 지원', 'ISR 지원', '자동 최적화'],
cons: ['Vercel 플랫폼 종속'],
features: ['스트리밍', '이미지 최적화', '분석'],
},

// Netlify 어댑터
'@sveltejs/adapter-netlify': {
pros: ['엣지 함수 지원', '자동 HTTPS', 'Forms 통합'],
cons: ['일부 Node.js API 제한'],
features: ['Split testing', 'Identity', 'Functions'],
},

// Node.js 어댑터
'@sveltejs/adapter-node': {
pros: [
'완전한 Node.js 지원',
'유연한 배포',
'PM2 호환',
],
cons: ['수동 서버 관리 필요'],
features: ['커스텀 서버', 'WebSocket', '파일 시스템'],
},

// 정적 어댑터
'@sveltejs/adapter-static': {
pros: ['무료 호스팅 가능', '빠른 로딩', 'CDN 친화적'],
cons: ['SSR 불가', '동적 기능 제한'],
features: ['GitHub Pages', 'Surge.sh', 'S3'],
},

// Cloudflare 어댑터
'@sveltejs/adapter-cloudflare': {
pros: ['전역 엣지 네트워크', 'Workers KV', '무료 티어'],
cons: ['Workers 런타임 제한'],
features: ['Durable Objects', 'R2 스토리지'],
},
};

어댑터 설정 방법

svelte.config.js
// svelte.config.js - 어댑터 설정
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/kit/vite';

/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),

kit: {
adapter: adapter({
// 어댑터별 옵션
// adapter-auto는 옵션을 받지 않음
}),

// 빌드 출력 설정
paths: {
base: process.env.BASE_PATH || '',
},

// 프리렌더링 설정
prerender: {
handleHttpError: 'warn',
handleMissingId: 'warn',
},

// CSP 설정
csp: {
mode: 'auto',
directives: {
'script-src': ['self'],
},
},
},
};

export default config;

정적 어댑터

정적 어댑터는 전체 애플리케이션을 HTML, CSS, JavaScript 파일로 프리렌더링합니다. 서버 사이드 렌더링이 필요 없는 블로그, 문서 사이트, 포트폴리오 등에 적합합니다. GitHub Pages, Netlify, Vercel 등 거의 모든 정적 호스팅 서비스에서 무료로 호스팅할 수 있습니다.

static-adapter-config.js
// svelte.config.js - 정적 어댑터 설정
import adapter from '@sveltejs/adapter-static';

export default {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: '404.html',
precompress: false,
strict: true,
}),

prerender: {
entries: ['*'],
handleHttpError: ({ path, referrer, message }) => {
// 404 에러 처리
if (message.includes('404')) {
console.warn(`404: ${path}`);
return;
}
throw new Error(message);
},
},
},
};
+layout.js
<!-- src/routes/+layout.js -->
<script>
// 모든 페이지를 프리렌더링
export const prerender = true;

// 정적 어댑터 사용 시 SSR 비활성화
export const ssr = false;

// 클라이언트 사이드 라우터 사용
export const csr = true;

// 트레일링 슬래시 설정
export const trailingSlash = 'always';
</script>
404.html
<!-- static/404.html - 폴백 페이지 -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<title>페이지를 찾을 수 없습니다</title>
<style>
body {
font-family: system-ui;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
}
.error {
text-align: center;
}
</style>
</head>
<body>
<div class="error">
<h1>404</h1>
<p>페이지를 찾을 수 없습니다</p>
<a href="/">홈으로 돌아가기</a>
</div>
</body>
</html>

Node 어댑터

Node 어댑터는 표준 Node.js 서버를 생성하여 VPS, Docker, AWS EC2 등 다양한 환경에서 실행할 수 있습니다. Express, Fastify 같은 프레임워크와 통합하거나 PM2로 프로세스를 관리할 수 있습니다. WebSocket, 파일 업로드, 데이터베이스 직접 연결 등 서버 사이드 기능을 완전히 활용할 수 있습니다.

node-adapter-config.js
// svelte.config.js - Node 어댑터 설정
import adapter from '@sveltejs/adapter-node';

export default {
kit: {
adapter: adapter({
out: 'build',
precompress: true,
envPrefix: 'APP_',
polyfill: true,
}),
},
};
custom-server.js
// server.js - Express와 통합
import { handler } from './build/handler.js';
import express from 'express';
import compression from 'compression';
import helmet from 'helmet';

const app = express();
const PORT = process.env.PORT || 3000;

// 보안 미들웨어
app.use(
helmet({
contentSecurityPolicy: false,
})
);

// 압축
app.use(compression());

// 정적 파일 서빙 (캐싱 설정)
app.use(
express.static('build/client', {
maxAge: '1y',
immutable: true,
})
);

// 헬스체크 엔드포인트
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: Date.now() });
});

// SvelteKit 핸들러
app.use(handler);

// Graceful shutdown
const server = app.listen(PORT, () => {
console.log(`서버가 포트 ${PORT}에서 실행 중입니다`);
});

process.on('SIGTERM', () => {
console.log('SIGTERM 신호 수신, 서버 종료 중...');
server.close(() => {
console.log('서버가 정상적으로 종료되었습니다');
process.exit(0);
});
});
ecosystem.config.js
// ecosystem.config.js - PM2 설정
module.exports = {
apps: [{
name: 'sveltekit-app',
script: './build/index.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
max_memory_restart: '500M',

// 무중단 재시작 설정
wait_ready: true,
listen_timeout: 3000,
kill_timeout: 5000
}]
};

18.2 주요 플랫폼 배포

Vercel 배포

Vercel은 SvelteKit의 공식 후원사로 자동 최적화, 엣지 함수, ISR 등 고급 기능을 제공합니다. Git 푸시만으로 자동 배포되며, 프리뷰 배포와 롤백 기능으로 안전한 배포 프로세스를 구축할 수 있습니다. 서버리스 아키텍처로 자동 스케일링되어 트래픽 급증에도 안정적으로 대응합니다.

vercel-config.js
// svelte.config.js - Vercel 어댑터 설정
import adapter from '@sveltejs/adapter-vercel';

export default {
kit: {
adapter: adapter({
runtime: 'nodejs20.x',
regions: ['iad1'],
maxDuration: 10,
memory: 1024,

// 엣지 함수 설정
edge: false,
split: false,

// 이미지 최적화
images: {
sizes: [640, 828, 1200, 1920],
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 300,
},
}),
},
};
+page.server.js
// src/routes/blog/[slug]/+page.server.js - ISR 설정
export const config = {
isr: {
expiration: 60,
bypassToken: process.env.BYPASS_TOKEN,
allowQuery: ['preview'],
},
};

export async function load({ params, setHeaders }) {
const post = await getPost(params.slug);

// 캐시 제어
setHeaders({
'cache-control': 'public, max-age=3600',
});

return {
post,
};
}
vercel.json
{
"rewrites": [
{
"source": "/api/:path*",
"destination": "https://api.example.com/:path*"
}
],
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
}
]
}
],
"functions": {
"src/routes/api/webhook/+server.js": {
"maxDuration": 30
}
}
}

Netlify 배포

Netlify는 강력한 빌드 시스템과 엣지 함수를 제공하는 정적 사이트 호스팅 플랫폼입니다. 폼 처리, Identity 서비스, Split Testing 등 추가 기능을 쉽게 통합할 수 있습니다. Deno 기반 엣지 함수로 사용자와 가까운 위치에서 SSR을 실행하여 성능을 최적화합니다.

netlify-config.js
// svelte.config.js - Netlify 어댑터 설정
import adapter from '@sveltejs/adapter-netlify';

export default {
kit: {
adapter: adapter({
edge: true,
split: false,
}),
},
};
netlify.toml
# netlify.toml - Netlify 설정
[build]
command = "npm run build"
publish = "build"

[build.environment]
NODE_VERSION = "20"

[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200

[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"

[functions]
directory = "netlify/functions"
node_bundler = "esbuild"

[[edge_functions]]
path = "/api/*"
function = "api-handler"
contact-form.svelte
<!-- src/routes/contact/+page.svelte - Netlify Forms -->
<script>
export const prerender = true;
</script>

<form
name="contact"
method="POST"
data-netlify="true"
data-netlify-honeypot="bot-field"
>
<input type="hidden" name="form-name" value="contact" />

<!-- 허니팟 필드 (봇 방지) -->
<p style="display: none">
<label>
이 필드를 채우지 마세요:
<input name="bot-field" />
</label>
</p>

<label>
이름:
<input type="text" name="name" required />
</label>

<label>
이메일:
<input type="email" name="email" required />
</label>

<label>
메시지:
<textarea name="message" required></textarea>
</label>

<button type="submit">전송</button>
</form>

AWS 배포

AWS는 다양한 서비스를 조합하여 확장 가능한 배포 환경을 구축할 수 있습니다. EC2 인스턴스, ECS 컨테이너, Lambda 함수 등 요구사항에 맞는 서비스를 선택할 수 있습니다. CloudFront CDN, RDS 데이터베이스, S3 스토리지 등과 통합하여 엔터프라이즈급 인프라를 구성할 수 있습니다.

aws-deployment.js
// AWS Lambda용 서버리스 핸들러
import { handler } from './build/handler.js';
import serverlessExpress from '@codegenie/serverless-express';

export const lambdaHandler = serverlessExpress({
app: handler,
});
serverless.yml
# serverless.yml - Serverless Framework 설정
service: sveltekit-app

provider:
name: aws
runtime: nodejs20.x
region: ap-northeast-2
stage: ${opt:stage, 'dev'}

environment:
NODE_ENV: production
DATABASE_URL: ${env:DATABASE_URL}

iam:
role:
statements:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource: arn:aws:s3:::${self:custom.bucket}/*

custom:
bucket: ${self:service}-${self:provider.stage}-assets

functions:
app:
handler: lambda.handler
events:
- http:
path: /
method: ANY
- http:
path: /{proxy+}
method: ANY
timeout: 30
memorySize: 1024

plugins:
- serverless-plugin-optimize
- serverless-offline

18.3 Docker와 컨테이너화

Dockerfile 작성

Docker를 사용하면 애플리케이션과 실행 환경을 하나의 이미지로 패키징할 수 있습니다. 멀티스테이지 빌드를 통해 최종 이미지 크기를 최소화하고 보안을 강화할 수 있습니다. 개발 환경과 프로덕션 환경의 일관성을 보장하여 "내 컴퓨터에서는 되는데" 문제를 해결합니다.

Dockerfile
# Dockerfile - 프로덕션용 멀티스테이지 빌드
# 빌드 스테이지
FROM node:20-alpine AS builder

WORKDIR /app

# 의존성 캐싱을 위해 package 파일만 먼저 복사
COPY package*.json ./
RUN npm ci --only=production

# 개발 의존성 설치 (빌드에 필요)
RUN npm ci

# 소스 코드 복사 및 빌드
COPY . .
RUN npm run build

# 프로덕션 의존성만 남기기
RUN npm prune --production

# 실행 스테이지
FROM node:20-alpine AS runner

WORKDIR /app

# 필요한 파일만 복사
COPY --from=builder /app/build build/
COPY --from=builder /app/package.json .
COPY --from=builder /app/node_modules node_modules/

# 비루트 사용자로 실행
USER node

# 환경 변수 설정
ENV NODE_ENV=production
ENV PORT=3000

EXPOSE 3000

# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => {r.statusCode === 200 ? process.exit(0) : process.exit(1)})"

CMD ["node", "build"]
Dockerfile.dev
# Dockerfile.dev - 개발용 컨테이너
FROM node:20-alpine

WORKDIR /app

# pnpm 설치 (더 빠른 패키지 관리)
RUN npm install -g pnpm

# 의존성 파일 복사
COPY package.json pnpm-lock.yaml ./

# 의존성 설치
RUN pnpm install

# 소스 코드는 볼륨으로 마운트
EXPOSE 5173
EXPOSE 24678

CMD ["pnpm", "dev", "--host", "0.0.0.0"]

최적화된 이미지 빌드

Docker 이미지를 최적화하면 배포 속도가 향상되고 스토리지 비용이 절감됩니다. 레이어 캐싱을 활용하고 불필요한 파일을 제외하여 이미지 크기를 최소화합니다. 보안 스캐닝과 취약점 패치를 통해 안전한 컨테이너 환경을 유지합니다.

.dockerignore
# .dockerignore - 빌드에서 제외할 파일
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.env.*
!.env.example
.vscode
.idea
*.swp
*.swo
.DS_Store
Thumbs.db
.svelte-kit
build
.vercel
.netlify
coverage
.nyc_output
*.log
docker-compose.yml
# docker-compose.yml - 개발 환경 구성
version: '3.8'

services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- '5173:5173'
- '24678:24678'
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- DATABASE_URL=postgres://user:pass@db:5432/myapp
depends_on:
- db
networks:
- app-network

db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=myapp
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- '5432:5432'
networks:
- app-network

volumes:
postgres_data:

networks:
app-network:
driver: bridge

컨테이너 오케스트레이션

프로덕션 환경에서는 Kubernetes나 Docker Swarm을 사용하여 컨테이너를 관리합니다. 자동 스케일링, 로드 밸런싱, 무중단 배포 등 고급 기능을 활용할 수 있습니다. 헬스체크와 모니터링을 통해 서비스의 안정성을 보장합니다.

k8s-deployment.yaml
# k8s-deployment.yaml - Kubernetes 배포 설정
apiVersion: apps/v1
kind: Deployment
metadata:
name: sveltekit-app
labels:
app: sveltekit
spec:
replicas: 3
selector:
matchLabels:
app: sveltekit
template:
metadata:
labels:
app: sveltekit
spec:
containers:
- name: app
image: myregistry/sveltekit-app:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: 'production'
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
resources:
requests:
memory: '256Mi'
cpu: '250m'
limits:
memory: '512Mi'
cpu: '500m'
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: sveltekit-service
spec:
selector:
app: sveltekit
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
deploy.sh
#!/bin/bash
# deploy.sh - 무중단 배포 스크립트

# 변수 설정
APP_NAME="sveltekit-app"
IMAGE_TAG=$(git rev-parse --short HEAD)
REGISTRY="myregistry"

# Docker 이미지 빌드
echo "이미지 빌드 중..."
docker build -t $REGISTRY/$APP_NAME:$IMAGE_TAG .
docker tag $REGISTRY/$APP_NAME:$IMAGE_TAG $REGISTRY/$APP_NAME:latest

# 레지스트리에 푸시
echo "이미지 푸시 중..."
docker push $REGISTRY/$APP_NAME:$IMAGE_TAG
docker push $REGISTRY/$APP_NAME:latest

# Kubernetes 배포
echo "Kubernetes 배포 중..."
kubectl set image deployment/$APP_NAME app=$REGISTRY/$APP_NAME:$IMAGE_TAG

# 배포 상태 확인
kubectl rollout status deployment/$APP_NAME

echo "배포 완료!"

정리

SvelteKit의 배포 시스템을 완전히 마스터했습니다! 이제 다음과 같은 핵심 개념들을 이해했습니다:

핵심 요약

  • 어댑터 시스템: 다양한 플랫폼에 최적화된 빌드를 생성하는 플러그인 시스템
  • 플랫폼별 배포: Vercel, Netlify, AWS 등 주요 플랫폼의 특징과 최적화 방법
  • Docker 컨테이너화: 멀티스테이지 빌드와 오케스트레이션을 통한 확장 가능한 배포

적절한 배포 방법 선택 가이드

프로젝트 유형권장 어댑터배포 플랫폼
정적 사이트adapter-staticGitHub Pages, Netlify
서버리스 앱adapter-vercelVercel, Netlify
풀스택 앱adapter-nodeAWS, Docker, VPS
엔터프라이즈adapter-nodeKubernetes, ECS

실무 활용 팁

  • 개발 초기에는 adapter-auto로 시작하고 프로덕션 전에 특정 어댑터로 전환
  • 정적 콘텐츠는 CDN을 통해 서빙하여 성능 최적화
  • Docker 이미지는 멀티스테이지 빌드로 크기 최소화
  • 환경 변수는 빌드 시점과 런타임을 구분하여 관리

다음 단계: 19장 "실무 패턴과 아키텍처"에서는 대규모 SvelteKit 애플리케이션을 위한 설계 패턴과 모범 사례를 알아보겠습니다. 확장 가능하고 유지보수가 쉬운 애플리케이션 구조를 설계해보세요!