Chapter 10

部署:Vercel/Cloudflare Pages/Node.js

从开发到生产,掌握 SvelteKit Adapter 体系,将你的应用部署到各大平台

Adapter 体系

什么是 Adapter?

SvelteKit 的 Adapter(适配器)是构建输出的转换层——它将 SvelteKit 构建产物转换为特定平台能运行的格式。一套代码,通过更换 Adapter 即可部署到不同的运行环境:

adapter-auto
自动检测部署平台(Vercel、Netlify、Cloudflare Pages 等),在 CI 环境中自动选择合适的 adapter。本地开发默认输出 Node.js 格式。
adapter-vercel
专为 Vercel 优化,支持 Edge Functions、Serverless Functions、ISR 增量静态再生,自动选择最优渲染模式。
adapter-cloudflare
部署到 Cloudflare Pages + Workers,在全球 300+ 节点的边缘网络运行,延迟极低。
adapter-node
生成独立的 Node.js 服务器,可部署到任意支持 Node.js 的环境(VPS、Docker、Railway 等)。
adapter-static
生成纯静态 HTML/CSS/JS 文件,适合完全静态的站点(博客、文档),可部署到 GitHub Pages、Nginx 等。

Vercel 部署

一键部署

# 1. 安装 Vercel adapter(通常 adapter-auto 已包含)
npm install -D @sveltejs/adapter-vercel

# 2. 更新 svelte.config.js
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

export default {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter({
      // 每个路由的运行时配置
      runtime: 'edge',        // 'edge' | 'nodejs18.x' | 'nodejs20.x'
      regions: ['hkg1'],      // 香港节点,对中国用户更快
      split: true              // 每个路由独立的 Serverless Function
    })
  }
};
# 3. 安装 Vercel CLI 并部署
npm install -g vercel
vercel login
vercel --prod

# 或者连接 GitHub 仓库,每次 push 自动部署
# 在 vercel.com 中 import 你的仓库

Cloudflare Pages 部署

// svelte.config.js — Cloudflare adapter
import adapter from '@sveltejs/adapter-cloudflare';

export default {
  kit: {
    adapter: adapter({
      routes: {
        include: ['/*'],
        exclude: ['/static/*']
      }
    })
  }
};
# 使用 Wrangler CLI 部署
npm install -g wrangler
wrangler login
npm run build
wrangler pages deploy .svelte-kit/cloudflare

Node.js 自托管

// svelte.config.js — Node adapter
import adapter from '@sveltejs/adapter-node';

export default {
  kit: {
    adapter: adapter({
      out: 'build',          // 输出目录
      precompress: true,     // 预压缩 Brotli/Gzip
      envPrefix: 'APP_'      // 环境变量前缀
    })
  }
};
# 构建
npm run build

# 启动服务器
node build/index.js

# 配置端口和 host
PORT=3000 HOST=0.0.0.0 node build/index.js

Docker 部署

# Dockerfile
FROM node:20-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --production

COPY build ./build

EXPOSE 3000
ENV PORT=3000

CMD ["node", "build/index.js"]

环境变量

$env 模块

SvelteKit 提供了类型安全的环境变量访问方式,通过 $env 模块自动区分公开变量和私有变量:

// 私有环境变量(只在服务端可用)
// 在 +page.server.ts 或 +server.ts 中使用
import { DATABASE_URL, JWT_SECRET } from '$env/static/private';
import { env } from '$env/dynamic/private';  // 运行时动态读取

// 公开环境变量(客户端可访问,需要 PUBLIC_ 前缀)
import { PUBLIC_API_URL } from '$env/static/public';
import { env as publicEnv } from '$env/dynamic/public';
# .env(开发环境)
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
JWT_SECRET=your-secret-key

# 公开变量(PUBLIC_ 前缀)
PUBLIC_API_URL=https://api.example.com
PUBLIC_ANALYTICS_ID=UA-12345
不要将私密变量暴露给客户端

只有以 PUBLIC_ 前缀命名的变量才能在客户端使用。数据库密码、API 密钥等永远不要使用 PUBLIC_ 前缀或在 +page.ts(通用 load)中使用。

静态生成

// 对单个路由开启预渲染
export const prerender = true;

// 全局预渲染配置(svelte.config.js)
export default {
  kit: {
    adapter: adapter(),
    prerender: {
      handleHttpError: 'warn',  // 遇到错误时警告而不是失败
      crawl: true,               // 自动爬取 a[href] 链接
      entries: ['/', '/about']  // 手动指定预渲染入口
    }
  }
};

SvelteKit Adapter 的工作原理

什么是 Adapter?

SvelteKit 的核心是框架无关的——它不假设你的代码运行在 Node.js、Edge Runtime 还是浏览器。Adapter 是部署目标的"翻译器",将 SvelteKit 应用转化为目标平台能够运行的形式:

adapter-node(Node.js 服务器)
生成一个标准的 Node.js HTTP 服务器(Express 兼容)。适合自托管服务器、Docker 容器、VPS 部署。输出文件包含 handler.js(可集成到现有 Express 应用)和独立的 index.js 入口。
adapter-vercel(Vercel)
将每个路由转化为 Vercel 的 Serverless Function 或 Edge Function。自动利用 Vercel 的 ISR(增量静态再生成)特性。预渲染的页面部署到 CDN,动态路由部署为函数。
adapter-cloudflare(Cloudflare Pages)
将动态路由转化为 Cloudflare Workers(V8 隔离,不是 Node.js)。在边缘节点运行,全球 200+ 节点,延迟极低。限制:不能使用 Node.js 特定 API(fschild_process),需要使用 Web 标准 API。
adapter-static(纯静态)
将整个应用预渲染为静态 HTML/CSS/JS 文件,没有服务端代码。适合博客、文档网站、落地页。要求所有路由都能预渲染(不能有用户特定数据)。可以部署到任何静态文件服务器(GitHub Pages、Netlify、Nginx)。

环境变量的安全边界

SvelteKit 环境变量的访问控制:

服务端专用(安全,永不暴露给客户端):
  import { DATABASE_URL } from '$env/static/private'
  import { env } from '$env/dynamic/private'
  只能在 +page.server.ts, +layout.server.ts, +server.ts 中使用

客户端可用(PUBLIC_ 前缀,安全地暴露):
  import { PUBLIC_API_URL } from '$env/static/public'
  import { env } from '$env/dynamic/public'
  可以在所有文件(包括 +page.svelte)中使用

静态 vs 动态:
  $env/static/*  → 构建时替换(类似 #define),值内联到代码中
  $env/dynamic/* → 运行时读取(process.env),支持动态更新

安全保证:
  SvelteKit 会在构建时检测非 PUBLIC_ 变量是否被客户端代码引用
  如果检测到私密变量被暴露,构建会失败并报错

生产部署检查清单

部署前检查清单:

性能:
  □ 图片使用 <enhanced:img> 或外部 CDN(避免大文件内联 base64)
  □ 数据库查询有适当的索引
  □ 使用 depands() + invalidate() 而非 invalidateAll()(精确失效)
  □ {#each} 列表都有 key

安全:
  □ 所有敏感变量使用 $env/static/private(非 PUBLIC_)
  □ Form Actions 有输入验证(Zod Schema)
  □ API 端点有认证检查
  □ CSRF 保护未被关闭(默认开启)
  □ 数据库查询使用参数化(ORM 默认处理)

SEO(如果需要):
  □ 所有页面有 <svelte:head> 中的 title 和 description
  □ 动态路由实现了 prerender 或 SSR(非纯 CSR)
  □ 添加 sitemap.xml(通过 +server.ts 动态生成)

Docker 容器化部署

# 第一步:安装 adapter-node
npm install -D @sveltejs/adapter-node

# svelte.config.js
# import adapter from '@sveltejs/adapter-node';
# export default { kit: { adapter: adapter() } };

# 构建应用
npm run build
# Dockerfile(多阶段构建)

# 阶段1:构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --production  # 移除 devDependencies

# 阶段2:运行
FROM node:20-alpine AS runner
WORKDIR /app

# 只复制运行所需文件
COPY --from=builder /app/build ./build
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

# adapter-node 的构建产物监听 3000 端口
ENV PORT=3000
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget -q --spider http://localhost:3000/health || exit 1

CMD ["node", "build"]
# docker-compose.yml
version: '3.8'
services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
      - JWT_SECRET=super-secret-key
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 10s
      timeout: 5s
      retries: 5

CI/CD 自动部署(GitHub Actions)

# .github/workflows/deploy.yml
name: Deploy to Vercel

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run check      # TypeScript 类型检查
      - run: npm run test       # 单元测试(vitest)
      - run: npm run test:e2e   # E2E 测试(playwright)

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          JWT_SECRET: ${{ secrets.JWT_SECRET }}
      - name: Deploy to Vercel
        run: npx vercel --prod --token ${{ secrets.VERCEL_TOKEN }}
        env:
          VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
          VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}

性能监控与优化

Web Vitals
Google 定义的核心性能指标:LCP(最大内容绘制,<2.5s)、FID/INP(交互响应延迟,<100ms)、CLS(累积布局偏移,<0.1)。SvelteKit 的 SSR 对这些指标友好,服务端渲染的 HTML 直接显示,无需等待 JS。
Code Splitting
SvelteKit 自动按路由分割代码,每个页面只加载自己需要的 JS。不需要手动 dynamic import(),Vite 自动处理。
Preloading
SvelteKit 自动在用户鼠标悬停到链接时预加载目标页面的 JS 和数据(通过 prefetch),点击时页面即时切换,无等待感。
<!-- src/hooks.server.ts:添加性能监控钩子 -->
export const handle = async ({ event, resolve }) => {
  const start = Date.now();

  const response = await resolve(event);

  const duration = Date.now() - start;
  response.headers.set('Server-Timing', `total;dur=${duration}`);

  // 慢请求告警
  if (duration > 1000) {
    console.warn(`Slow request: ${event.url.pathname} took ${duration}ms`);
  }

  return response;
};

// src/app.html:添加 Web Vitals 监控
// 在 %sveltekit.body% 后添加:
// <script type="module">
//   import { onCLS, onINP, onLCP } from 'web-vitals';
//   function sendToAnalytics(metric) {
//     fetch('/api/vitals', { method: 'POST', body: JSON.stringify(metric) });
//   }
//   onCLS(sendToAnalytics);
//   onINP(sendToAnalytics);
//   onLCP(sendToAnalytics);
// </script>
本章小结 · 恭喜完成 Svelte 5 / SvelteKit 课程!