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
657 lines
13 KiB
Markdown
657 lines
13 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
npm install -D @sveltejs/adapter-node
|
|
```
|
|
|
|
### Configuration
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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)
|
|
|
|
```javascript
|
|
// 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'
|
|
}]
|
|
};
|
|
```
|
|
|
|
```bash
|
|
# Start with PM2
|
|
pm2 start ecosystem.config.cjs
|
|
|
|
# Save process list
|
|
pm2 save
|
|
|
|
# Setup startup script
|
|
pm2 startup
|
|
```
|
|
|
|
### Systemd Service
|
|
|
|
```ini
|
|
# /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
|
|
```
|
|
|
|
```bash
|
|
sudo systemctl enable sveltekit-app
|
|
sudo systemctl start sveltekit-app
|
|
```
|
|
|
|
### Nginx Reverse Proxy
|
|
|
|
```nginx
|
|
# /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
|
|
|
|
```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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```bash
|
|
npm install -D @sveltejs/adapter-static
|
|
```
|
|
|
|
### Configuration
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```yaml
|
|
# .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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```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
|
|
|
|
```javascript
|
|
// svelte.config.js
|
|
import adapter from '@sveltejs/adapter-cloudflare';
|
|
|
|
const config = {
|
|
kit: {
|
|
adapter: adapter({
|
|
routes: {
|
|
include: ['/*'],
|
|
exclude: ['<all>']
|
|
}
|
|
})
|
|
}
|
|
};
|
|
```
|
|
|
|
### Platform Bindings
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|