Files
Job-Info/svelte-reference/10-DEPLOYMENT.md
T
aewing 0b76a4b119 Cleanup: Remove old modes, add Svelte 5 reference library, update orchestrator
- 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
2026-01-21 03:56:01 +00:00

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