0b76a4b119
- Delete Mode1Test, Mode2Test, Mode3Test, Mode5Test (keep only Mode6Test) - Delete old documentation, Docker files, and k8s configs - Add comprehensive Svelte 5 reference library (svelte-reference/) - Update ModeSwitch orchestrator: - Fix port to 3005 (never use 3000/3001) - Add API endpoints for mode switching - Add bun-types and proper tsconfig - Clean up mode folder mappings
13 KiB
13 KiB
SvelteKit Deployment Guide
Overview
SvelteKit supports multiple deployment targets through adapters. Choose the right adapter for your infrastructure and requirements.
🔌 Available Adapters
Adapter Comparison
| Adapter | Platform | SSR | SSG | Edge | Serverless |
|---|---|---|---|---|---|
adapter-node |
Node.js | ✅ | ✅ | ❌ | ❌ |
adapter-auto |
Auto-detect | ✅ | ✅ | Varies | Varies |
adapter-vercel |
Vercel | ✅ | ✅ | ✅ | ✅ |
adapter-netlify |
Netlify | ✅ | ✅ | ✅ | ✅ |
adapter-cloudflare |
Cloudflare | ✅ | ✅ | ✅ | ✅ |
adapter-static |
Static hosts | ❌ | ✅ | ❌ | ❌ |
🖥️ Node.js Deployment (adapter-node)
Installation
npm install -D @sveltejs/adapter-node
Configuration
// svelte.config.js
import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
// Output directory
out: 'build',
// Precompress static assets with gzip/brotli
precompress: true,
// Environment variable configuration
envPrefix: 'APP_'
})
}
};
export default config;
Build & Run
# Build
npm run build
# Run with Node
node build/index.js
# Or with environment variables
PORT=3000 HOST=0.0.0.0 node build/index.js
Environment Variables
# Server configuration
PORT=3000 # Default: 3000
HOST=0.0.0.0 # Default: 0.0.0.0
ORIGIN=https://example.com # Required for form actions
BODY_SIZE_LIMIT=512K # Request body limit
Production Process Manager (PM2)
// ecosystem.config.cjs
module.exports = {
apps: [{
name: 'sveltekit-app',
script: './build/index.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000,
ORIGIN: 'https://example.com'
},
error_file: './logs/error.log',
out_file: './logs/output.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
}]
};
# Start with PM2
pm2 start ecosystem.config.cjs
# Save process list
pm2 save
# Setup startup script
pm2 startup
Systemd Service
# /etc/systemd/system/sveltekit-app.service
[Unit]
Description=SvelteKit Application
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/node build/index.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=ORIGIN=https://example.com
[Install]
WantedBy=multi-user.target
sudo systemctl enable sveltekit-app
sudo systemctl start sveltekit-app
Nginx Reverse Proxy
# /etc/nginx/sites-available/sveltekit-app
upstream sveltekit {
server 127.0.0.1:3000;
keepalive 64;
}
server {
listen 80;
server_name example.com;
# Redirect to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
# SSL certificates
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# SSL settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript;
# Static files with immutable cache
location /_app/immutable {
proxy_pass http://sveltekit;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Other static files
location /static {
proxy_pass http://sveltekit;
add_header Cache-Control "public, max-age=86400";
}
# Proxy to SvelteKit
location / {
proxy_pass http://sveltekit;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
🐳 Docker Deployment
Dockerfile
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci
# Copy source
COPY . .
# Build application
RUN npm run build
# Production stage
FROM node:20-alpine
WORKDIR /app
# Copy built application
COPY --from=builder /app/build ./build
COPY --from=builder /app/package*.json ./
# Install production dependencies only
RUN npm ci --omit=dev
# Set environment
ENV NODE_ENV=production
ENV PORT=3000
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# Run
CMD ["node", "build/index.js"]
docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- ORIGIN=https://example.com
- DATABASE_URL=postgresql://user:pass@db:5432/app
depends_on:
- db
- redis
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- app
restart: unless-stopped
volumes:
postgres_data:
redis_data:
📦 Static Site Generation (adapter-static)
Installation
npm install -D @sveltejs/adapter-static
Configuration
// svelte.config.js
import adapter from '@sveltejs/adapter-static';
const config = {
kit: {
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: '404.html', // or 'index.html' for SPA
precompress: true,
strict: true
}),
prerender: {
entries: ['*'], // Prerender all pages
handleHttpError: 'warn'
}
}
};
export default config;
Page Configuration for SSG
// src/routes/+layout.ts
export const prerender = true; // Prerender all pages
export const ssr = true; // Enable SSR during build
// src/routes/+page.ts
export const prerender = true; // Prerender this specific page
// For dynamic routes, export entries
export function entries() {
return [
{ slug: 'post-1' },
{ slug: 'post-2' },
{ slug: 'post-3' }
];
}
Deploy to GitHub Pages
# .github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
BASE_PATH: '/${{ github.event.repository.name }}'
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./build
☁️ Vercel Deployment
Configuration
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';
const config = {
kit: {
adapter: adapter({
runtime: 'nodejs20.x', // or 'edge'
regions: ['iad1'], // Deploy regions
split: true // Split into multiple functions
})
}
};
Per-Route Configuration
// src/routes/api/slow-endpoint/+server.ts
export const config = {
runtime: 'nodejs20.x',
maxDuration: 30 // Extend timeout
};
// src/routes/api/fast-endpoint/+server.ts
export const config = {
runtime: 'edge',
regions: ['iad1', 'sfo1', 'cdg1'] // Multi-region
};
vercel.json
{
"framework": "sveltekit",
"buildCommand": "npm run build",
"installCommand": "npm ci",
"headers": [
{
"source": "/_app/immutable/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
]
}
🌐 Cloudflare Pages
Configuration
// svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';
const config = {
kit: {
adapter: adapter({
routes: {
include: ['/*'],
exclude: ['<all>']
}
})
}
};
Platform Bindings
// src/app.d.ts
declare global {
namespace App {
interface Platform {
env: {
KV_NAMESPACE: KVNamespace;
D1_DATABASE: D1Database;
R2_BUCKET: R2Bucket;
};
context: {
waitUntil(promise: Promise<unknown>): void;
};
}
}
}
// src/routes/+page.server.ts
export async function load({ platform }) {
const value = await platform?.env.KV_NAMESPACE.get('key');
return { value };
}
🔒 Security Considerations
Environment Variables
// src/lib/server/env.ts
import { env } from '$env/dynamic/private';
import { building } from '$app/environment';
if (!building) {
// Validate required env vars at startup
const required = ['DATABASE_URL', 'SECRET_KEY'];
for (const key of required) {
if (!env[key]) {
throw new Error(`Missing required environment variable: ${key}`);
}
}
}
export const config = {
databaseUrl: env.DATABASE_URL,
secretKey: env.SECRET_KEY,
isProduction: env.NODE_ENV === 'production'
};
Security Headers
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
const response = await resolve(event);
// Security headers
response.headers.set('X-Frame-Options', 'SAMEORIGIN');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
);
response.headers.set(
'Permissions-Policy',
'camera=(), microphone=(), geolocation=()'
);
return response;
};
CORS Configuration
// src/routes/api/[...path]/+server.ts
import type { RequestHandler } from './$types';
const allowedOrigins = ['https://example.com', 'https://app.example.com'];
export const OPTIONS: RequestHandler = async ({ request }) => {
const origin = request.headers.get('origin');
if (origin && allowedOrigins.includes(origin)) {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400'
}
});
}
return new Response(null, { status: 403 });
};
📊 Health Checks & Monitoring
Health Endpoint
// src/routes/health/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async () => {
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
version: process.env.npm_package_version
};
// Add database check
try {
// await db.query('SELECT 1');
health.database = 'connected';
} catch {
health.database = 'disconnected';
health.status = 'degraded';
}
const statusCode = health.status === 'healthy' ? 200 : 503;
return json(health, { status: statusCode });
};
📋 Deployment Checklist
Pre-Deployment
- All tests passing
- Environment variables documented
- Build succeeds locally
- Security headers configured
- Error pages customized
- Logging configured
Production
- SSL/TLS enabled
- CDN configured
- Database backups scheduled
- Monitoring/alerting setup
- Rate limiting enabled
- Health checks configured
Post-Deployment
- Smoke tests passing
- Performance metrics acceptable
- Error tracking working
- Rollback plan documented