27. SvelteKit: Деплой
🚀 SvelteKit Деплой: От Localhost до Продакшна
Заголовок раздела «🚀 SvelteKit Деплой: От Localhost до Продакшна»Привет! 👋 Написать приложение — это полдела. Нужно ещё его задеплоить! SvelteKit с его системой адаптеров делает это удивительно гибким. Один и тот же код можно задеплоить на Vercel, Netlify, Cloudflare Workers, обычный VPS или даже как статический сайт. Выбирай что хочешь!
🔌 Адаптеры: ключевая концепция
Заголовок раздела «🔌 Адаптеры: ключевая концепция»Адаптер — это пакет npm, который трансформирует собранное SvelteKit приложение в формат, понятный конкретной платформе.
npm run build ↓[SvelteKit компиляция] ↓[Адаптер обрабатывает вывод] ↓Готовый артефакт для платформы🤖 adapter-auto: умный выбор
Заголовок раздела «🤖 adapter-auto: умный выбор»adapter-auto — это дефолтный адаптер при создании проекта. Он автоматически определяет платформу по переменным окружения:
// svelte.config.js с adapter-autoimport adapter from '@sveltejs/adapter-auto';import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */const config = { preprocess: vitePreprocess(), kit: { adapter: adapter(), },};
export default config;Что он делает:
VERCELenv → использует adapter-vercelNETLIFYenv → использует adapter-netlifyCF_PAGESenv → использует adapter-cloudflare-pages- Ничего нет → падает с ошибкой (нужен явный адаптер)
🟢 adapter-node: деплой на VPS/Docker
Заголовок раздела «🟢 adapter-node: деплой на VPS/Docker»npm install -D @sveltejs/adapter-nodeimport adapter from '@sveltejs/adapter-node';
const config = { kit: { adapter: adapter({ out: 'build', // Папка вывода precompress: false, // Предсжатие gzip/brotli envPrefix: '', // Префикс для env переменных }), },};Dockerfile для Node.js:
FROM node:20-alpine AS builder
WORKDIR /appCOPY package*.json ./RUN npm ci
COPY . .RUN npm run build
# Production образ (минимальный):FROM node:20-alpine AS runner
WORKDIR /app
# Только нужные файлы:COPY --from=builder /app/build ./buildCOPY --from=builder /app/package*.json ./
# Устанавливаем только prod зависимости:RUN npm ci --only=production
# Открываем порт:EXPOSE 3000
# Запускаем:CMD ["node", "build/index.js"]docker-compose.yml:
version: '3.8'services: app: build: . ports: - "3000:3000" environment: - PORT=3000 - ORIGIN=https://myapp.com - DATABASE_URL=postgresql://db:5432/myapp depends_on: - db restart: unless-stopped
db: image: postgres:16-alpine environment: POSTGRES_PASSWORD: secret POSTGRES_DB: myapp volumes: - pgdata:/var/lib/postgresql/data
volumes: pgdata:PM2 для управления процессом:
# Глобальная установка PM2:npm install -g pm2
# ecosystem.config.js:module.exports = { apps: [{ name: 'sveltekit-app', script: './build/index.js', instances: 'max', // По одному на каждое ядро CPU exec_mode: 'cluster', // Режим кластера env_production: { NODE_ENV: 'production', PORT: 3000, ORIGIN: 'https://myapp.com', }, }],};
# Запуск:pm2 start ecosystem.config.js --env production
# Автостарт после перезагрузки:pm2 startuppm2 save▲ adapter-vercel: нулевая конфигурация
Заголовок раздела «▲ adapter-vercel: нулевая конфигурация»npm install -D @sveltejs/adapter-vercelimport adapter from '@sveltejs/adapter-vercel';
const config = { kit: { adapter: adapter({ runtime: 'nodejs20.x', // Node.js версия regions: ['fra1'], // Регион (Frankfurt) // Edge Functions вместо Node.js: // runtime: 'edge', }), },};Per-route конфигурация на Vercel:
// src/routes/api/fast/+server.ts — Edge Functionexport const config = { runtime: 'edge', // Этот route на Edge regions: ['iad1', 'fra1'], // Регионы};
// src/routes/heavy/+page.server.ts — обычный serverlessexport const config = { runtime: 'nodejs20.x', maxDuration: 60, // Максимум 60 секунд};ISR (Incremental Static Regeneration) на Vercel:
export const config = { isr: { expiration: 60, // Ревалидировать через 60 секунд // bypassToken: process.env.BYPASS_TOKEN, // Для on-demand ISR },};🌊 adapter-netlify
Заголовок раздела «🌊 adapter-netlify»npm install -D @sveltejs/adapter-netlifyimport adapter from '@sveltejs/adapter-netlify';
const config = { kit: { adapter: adapter({ edge: false, // Использовать Netlify Functions (не Edge) split: false, // Один бандл для всех функций }), },};netlify.toml:
[build] command = "npm run build" publish = "build"
[build.environment] NODE_VERSION = "20"
# Редиректы:[[redirects]] from = "/old-path" to = "/new-path" status = 301
# Заголовки безопасности:[[headers]] for = "/*" [headers.values] X-Frame-Options = "DENY" X-Content-Type-Options = "nosniff"🔶 adapter-cloudflare: Workers и Pages
Заголовок раздела «🔶 adapter-cloudflare: Workers и Pages»npm install -D @sveltejs/adapter-cloudflare// svelte.config.js для Cloudflareimport adapter from '@sveltejs/adapter-cloudflare';
const config = { kit: { adapter: adapter({ routes: { include: ['/*'], exclude: ['<all>'], // Статические файлы напрямую }, }), },};Доступ к Cloudflare Workers API:
// src/app.d.ts — типы для Cloudflare platformdeclare global { namespace App { interface Platform { env: { // KV Storage: CACHE: KVNamespace; // D1 Database: DB: D1Database; // R2 Storage: ASSETS: R2Bucket; // Дополнительные Workers Services: AI: Ai; }; context: ExecutionContext; caches: CacheStorage & { default: Cache }; } }}// src/routes/api/cached/+server.ts — использование KV Cacheimport type { RequestHandler } from './$types';import { json } from '@sveltejs/kit';
export const GET: RequestHandler = async ({ platform, params }) => { const { env } = platform!; const cacheKey = `product:${params.id}`;
// Проверяем KV кэш: const cached = await env.CACHE.get(cacheKey); if (cached) return json(JSON.parse(cached));
// Запрашиваем из D1: const result = await env.DB .prepare('SELECT * FROM products WHERE id = ?') .bind(params.id) .first();
// Сохраняем в KV на 5 минут: await env.CACHE.put(cacheKey, JSON.stringify(result), { expirationTtl: 300, });
return json(result);};wrangler.toml для конфигурации:
name = "my-sveltekit-app"main = "./build/index.js"compatibility_date = "2024-01-01"pages_build_output_dir = ".svelte-kit/cloudflare"
[[kv_namespaces]]binding = "CACHE"id = "xxxxxxxxxx"
[[d1_databases]]binding = "DB"database_name = "my-database"database_id = "xxxxxxxxxx"📄 adapter-static: чистый SSG
Заголовок раздела «📄 adapter-static: чистый SSG»npm install -D @sveltejs/adapter-staticimport adapter from '@sveltejs/adapter-static';
const config = { kit: { adapter: adapter({ pages: 'build', // Куда кладём HTML assets: 'build', // Куда кладём статику fallback: '404.html', // SPA fallback (или 'index.html' для SPA) precompress: true, // Gzip + Brotli предсжатие strict: true, // Ошибка если страница не предрендерена }), prerender: { entries: ['*'], // Предрендеривать все страницы }, },};Настройка предрендеринга:
import type { EntryGenerator, PageLoad } from './$types';
// Генерируем список всех слагов для предрендеринга:export const entries: EntryGenerator = async () => { const posts = await getAllPosts(); return posts.map((post) => ({ slug: post.slug }));};
export const prerender = true;
export const load: PageLoad = async ({ params }) => { const post = await getPost(params.slug); return { post };};// Отключить предрендеринг для динамических страниц:export const prerender = false;export const ssr = false; // SPA режим для динамических страниц🌍 Переменные окружения: полный гайд
Заголовок раздела «🌍 Переменные окружения: полный гайд»Модуль Тип Доступен где──────────────────────────────────────────────────────────────────$env/static/public Билд-тайм Сервер + Клиент (PUBLIC_ prefix)$env/static/private Билд-тайм Только сервер$env/dynamic/public Runtime Сервер + Клиент (PUBLIC_ prefix)$env/dynamic/private Runtime Только сервер// Примеры использования:import { PUBLIC_API_URL, PUBLIC_STRIPE_KEY } from '$env/static/public';import { DATABASE_URL, SECRET_KEY, STRIPE_SECRET } from '$env/static/private';import { env as dynamicPrivate } from '$env/dynamic/private';
// static — вшивается в бандл при сборке (оптимальнее):const apiUrl = PUBLIC_API_URL; // 'https://api.myapp.com'
// dynamic — читается при каждом запросе (нужно для runtime конфигурации):const dbUrl = dynamicPrivate.DATABASE_URL;
// ⚠️ Нельзя использовать $env/static/private в +page.svelte или +page.ts!// Только в +page.server.ts, +layout.server.ts, +server.ts, hooks.server.tsТипизация переменных окружения:
declare global { namespace NodeJS { interface ProcessEnv { DATABASE_URL: string; SECRET_KEY: string; PUBLIC_API_URL: string; } }}⚙️ ORIGIN: критически важная переменная
Заголовок раздела «⚙️ ORIGIN: критически важная переменная»# Обязательно для Node.js деплоя!# ORIGIN указывает откуда приходят запросы — нужен для CSRF защиты
# В production:ORIGIN=https://myapp.com
# При деплое через Docker/PM2 обязательно задать:PORT=3000ORIGIN=https://myapp.com// Если ORIGIN не задан — SvelteKit падает с ошибкой для Form Actions// Это защищает от CSRF атак📊 Предрендеринг: конфигурация
Заголовок раздела «📊 Предрендеринг: конфигурация»// svelte.config.js — глобальные настройки предрендерингаconst config = { kit: { prerender: { // Что делать при HTTP ошибках во время предрендеринга: handleHttpError: ({ path, referrer, message }) => { if (path.includes('/api/')) return; // Игнорируем ошибки API throw new Error(message); },
// Что делать при отсутствии якоря в ссылке: handleMissingId: 'warn', // 'ignore' | 'warn' | 'error'
// Точки входа для предрендеринга: entries: ['*'], // Все страницы // или ['/', '/about', '/contact']
// Параллельность предрендеринга: concurrency: 4,
// Предотвратить краулинг: crawl: true, }, },};🔧 Полный CI/CD пайплайн
Заголовок раздела «🔧 Полный CI/CD пайплайн»name: Deploy to Production
on: push: branches: [main]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '20' } - run: npm ci - run: npm run check # TypeScript проверка - run: npm run test # Тесты - run: npm run lint # Линтер
build-and-deploy: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '20' }
- run: npm ci - run: npm run build env: DATABASE_URL: ${{ secrets.DATABASE_URL }} PUBLIC_API_URL: ${{ vars.PUBLIC_API_URL }}
- name: Deploy to Server run: | rsync -avz --delete ./build user@server:/app/build ssh user@server "pm2 reload sveltekit-app"