Vite 项目部署平台适配实战:Vercel、Netlify、Cloudflare Pages 与自建方案

把 Vite 构建产物部署到不同平台的完整指南:静态托管平台的共性与差异、Vercel 与 Netlify 的配置约定、Cloudflare Pages 的边缘部署、对象存储加 CDN 的自建方案、自建 nginx 的缓存与回退、SPA 路由回退与 404 处理、环境变量注入与多环境构建、预览环境与灰度回滚,以及构建产物审计与高频陷阱。

引言

Vite 的构建产物本质是一堆纯静态文件——HTML、带内容 hash 的 JS 与 CSS、图片和字体。理论上「扔到任何能托管文件的地方」就能上线,但一进生产环境,平台之间的差异立刻暴露:路由回退由谁来做、环境变量在哪一步注入、缓存头如何设置、预览环境是否自带、回滚的成本有多高。

本文横向对比五类主流部署方案(Vercel、Netlify、Cloudflare Pages、对象存储加 CDN、自建 nginx),逐一说清各自的配置约定与适配要点,再深入 SPA 路由回退、环境变量注入、预览与灰度回滚、构建产物审计这些跨平台共通的工程问题,最后给出一份上线前的核对清单。

前置:构建产物与内容 hash、CI 流水线与自动部署。容器化方案见 Vite 容器化与 Docker 构建:多阶段构建、层缓存与镜像优化。


目录


1. 静态托管平台的共性与差异

1.1 所有平台都在做同一件事

无论哪家平台,本质都是「接收一份 dist 目录 → 全球分发 → 按规则设置响应头」,差异只体现在回退规则、环境变量注入时机、缓存头控制粒度三处。

共同能力:接收构建产物、边缘分发、为每次提交生成独立地址
差异能力:回退语法、变量注入时机、缓存头粒度、边缘函数、回滚方式

1.2 五类方案横向对比

方案回退配置环境变量预览环境缓存头控制
Vercelvercel.json rewrites平台面板注入自带vercel.json headers
Netlify_redirects 或 netlify.toml平台面板注入自带_headers 文件
Cloudflare Pages_redirects 文件平台面板注入自带_headers 文件
对象存储加 CDNCDN 回源规则构建期注入需自建构建期写入元数据
自建 nginxtry_files构建期或运行时需自建nginx 配置

1.3 选型建议

追求零运维、要预览环境 → Vercel / Netlify / Cloudflare Pages
已有云厂商对象存储与 CDN → 对象存储加 CDN(成本最低)
有合规、内网或特殊缓存需求 → 自建 nginx(控制力最强)

记忆:平台的差异只有三处——回退规则、环境变量注入时机、缓存头由谁决定;选型先看是否需要自带预览环境与回滚能力。


2. Vercel 部署配置与约定

2.1 vercel.json 关键配置

Vercel 会自动识别 Vite 项目(构建命令 vite build、输出目录 dist),但显式声明更稳妥:

{
  "buildCommand": "npm run build", "outputDirectory": "dist",
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}

rewrites 是内部重写(URL 不变、返回 index.html),不是 301 跳转——这正是 SPA 回退需要的行为。

2.2 静态资源与 HTML 的缓存头

Vercel 对 dist/assets/ 下的带 hash 文件默认长缓存,但 HTML 必须短缓存,可在 vercel.json 中显式声明:

{ "headers": [
  { "source": "/assets/(.*)", "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }] },
  { "source": "/index.html", "headers": [{ "key": "Cache-Control", "value": "no-cache" }] } ] }

2.3 环境变量与预览环境

在面板里配置的变量默认对 Production 生效;勾选 Preview 后,每个分支部署会自动带上对应值。客户端可见的变量必须以 VITE_ 开头,它们在构建期被静态替换,因此改动变量后必须重新部署。

记忆:Vercel 用 rewrites 做 SPA 回退(不是 redirects)、用 headers 分离 HTML 与 hash 资源的缓存策略——VITE_ 变量是构建期替换,改完必须重新部署。


3. Netlify 部署配置与重定向

3.1 netlify.toml 基础配置

[build]
  command = "npm run build"
  publish = "dist"
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

status = 200 是重写(rewrite),status = 301 才是跳转,SPA 回退必须用 200。

3.2 _redirects 与优先级

也可以在 public/_redirects 里写(构建时原样复制进 dist),规则从上到下匹配、第一条命中即生效,所以 API 代理必须写在通配回退之前:

/api/*  https://api.example.com/:splat  200
/*      /index.html                     200

3.3 缓存头与文件放置位置

# public/_headers(放在 public/ 下,构建时原样拷进 dist)
/assets/*   → Cache-Control: public, max-age=31536000, immutable
/index.html → Cache-Control: no-cache

记忆:Netlify 的 SPA 回退是 status = 200 的重写而非跳转,且规则顺序敏感——API 代理务必写在通配回退之前,_redirects 与 _headers 放在 public/ 下。


4. Cloudflare Pages 与边缘部署

4.1 Pages 的构建与产物约定

Cloudflare Pages 同样零配置识别 Vite:构建命令 npm run build、输出目录 dist。它的优势是边缘节点数量最多,静态资源天然就近分发。

构建设置:
  Framework preset  → Vite
  Build command     → npm run build
  Build output      → dist

4.2 _headers 与 _redirects

Cloudflare Pages 复用 Netlify 风格的两个文件,格式几乎一致:

# public/_headers
/assets/*  → Cache-Control: public, max-age=31536000, immutable
# public/_redirects
/*  /index.html  200

一个关键差异:Pages 默认把 404.html 作为自定义错误页,若你依赖 SPA 回退,务必确认 _redirects 生效且未被 404.html 覆盖。

记忆:Cloudflare Pages 复用 Netlify 风格的 _headers 与 _redirects,最大优势是边缘节点覆盖——但要注意 404.html 可能抢在 SPA 回退之前生效。


5. 对象存储加 CDN 的自建方案

5.1 上传与同步策略

把 dist 同步到对象存储,关键是只传变化的部分并先传资源后传 HTML:

# 1. 先同步带 hash 的静态资源(可长缓存、可并发)
aws s3 sync dist/assets s3://my-bucket/assets \
  --cache-control "public, max-age=31536000, immutable"
# 2. 再同步 HTML(短缓存)
aws s3 sync dist s3://my-bucket --exclude "assets/*" \
  --cache-control "no-cache" --delete

顺序很重要:若先传 HTML,用户可能在新 HTML 上线后、新 JS 就位前访问,导致 404。

5.2 缓存头必须在构建期决定

对象存储的缓存头是写入对象元数据的,不是请求时动态设置,想改策略只能重新上传:

带 hash 的资源 → immutable;HTML 与 sw.js → no-cache;上传脚本必须按类型区分 Cache-Control,无法像 nginx 那样改配置即刻生效

5.3 CDN 回源与 SPA 回退

CDN 侧要配置自定义错误响应,把 403/404 回退到 /index.html 并返回 200:

回源规则:源站 403 / 404 → 回退 /index.html 并把状态码改为 200
例外:路径以 /assets/ 开头 → 绝不回退,否则脚本 404 返回 HTML 会触发 MIME 报错

记忆:对象存储加 CDN 的两个铁律——先传资源后传 HTML、缓存头写死在元数据里;CDN 回退必须排除 /assets/ 路径,否则脚本 404 会变成 MIME 报错。


6. 自建 nginx 的缓存与回退配置

6.1 完整的 server 块

server {
    listen 80;
    root /var/www/app/dist;
    location / { try_files $uri $uri/ /index.html; }          # SPA 回退
    location /assets/ {                                        # 带 hash,长缓存
        expires 1y;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
    location = /index.html { add_header Cache-Control "no-cache"; }
}

6.2 带 hash 资源与 HTML 的缓存差异

这是整个部署体系的核心规律:文件名带内容 hash 的资源可以永久缓存,文件名不变的文件绝不能强缓存。

index-a1b2c3.js       → 内容变则名变 → immutable 安全
index.html            → 名字恒定 → 必须 no-cache,否则永远拿不到新版本
sw.js / manifest.json → 名字恒定 → 必须 no-cache

6.3 压缩与安全响应头

gzip on;  gzip_min_length 1024;
gzip_types text/css application/javascript application/json image/svg+xml;
add_header X-Content-Type-Options nosniff;    # 配合 SPA 回退兜底 MIME 嗅探
add_header X-Frame-Options SAMEORIGIN;
add_header Referrer-Policy strict-origin-when-cross-origin;

记忆:nginx 的两条主线是 try_files 做回退、按文件名是否带 hash 分档设缓存——HTML 与 sw.js 一律 no-cache,再加 nosniff 兜底 MIME 错误。


7. SPA 路由回退与 404 处理

7.1 为什么需要回退

前端路由(/dashboard/settings)在服务端并不存在对应文件。用户刷新或直接访问深层链接时,服务器必须把请求交给 index.html,由前端路由接管渲染。

用户访问 /dashboard/settings → 服务器无此文件
  → 回退返回 index.html(状态码 200)→ 前端路由解析路径并渲染

7.2 三种回退实现对照

场景实现
nginxtry_files 兜底 index.html
Vercelrewrites 规则指向 index.html
Netlify 与 Pages_redirects 中 /* /index.html 200

三者语义一致,都是「重写而非跳转」,URL 保持不变。

7.3 404 与真静态站的取舍

如果站点同时有静态预渲染页面(如博客)和 SPA 路由,不能无脑全量回退——否则真正不存在的 URL 也会返回 200,搜索引擎会收录大量重复页面。折中做法是按路由前缀精细化回退:

/app/*    /app/index.html   200
/blog/*   /blog/index.html  200
/*        /404.html         404

记忆:回退是「重写」不是「跳转」,URL 必须不变;混合站点要按路由前缀精细化回退,否则真 404 也会返回 200,污染搜索引擎收录。


8. 环境变量注入与多环境构建

8.1 Vite 的环境变量机制

Vite 只把 VITE_ 前缀的变量暴露给客户端,且是构建期静态替换(import.meta.env.VITE_API 会被直接替换成字面量),因此变量在构建时就被烧进产物,同一个产物无法在运行时切换后端地址:

// 构建产物里已无 process.env,只有被替换后的字符串
const api = import.meta.env.VITE_API_BASE

8.2 平台侧注入的差异

平台注入时机注意事项
Vercel构建期改后需重新部署
Netlify 与 Pages构建期均区分 Production 与 Preview 两套变量
对象存储加 CDN构建期由 CI 传入,最灵活

8.3 运行时配置的替代方案

若同一份产物要部署到多套环境(如私有化交付),把配置外置为运行时读取(代价是失去类型安全与 tree-shaking):

<!-- public/config.js,部署后可直接替换,无需重新构建 -->
<script>window.__APP_CONFIG__ = { apiBase: '/api' }</script>
const cfg = (window as any).__APP_CONFIG__ ?? { apiBase: import.meta.env.VITE_API_BASE }

记忆:VITE_ 变量是构建期烧进产物的,改值必须重新构建;同一产物要跨环境时,改用 window.__APP_CONFIG__ 这类运行时配置,但要以牺牲类型安全为代价。


9. 预览环境、分支部署与灰度回滚

9.1 预览环境与分支部署

三家平台都支持「每个 PR 一个独立 URL」,这是它们相对自建方案最大的效率优势:

main 分支    → 生产域名
feature 分支 → https://feature-xxx.preview.example.com
PR 创建时    → 自动构建 + 评论区贴出预览链接(评审即可点开真实产物)

9.2 灰度发布

静态站点的灰度通常是域名级或 CDN 规则级的,由于产物不可变,灰度切换本质上只是改流量分配规则,非常轻量:

方案一:新版本先部署到 /v2/ 子路径,用 CDN 规则把 5% 流量导向它
方案二:用边缘函数按 Cookie 或请求头分流;方案三:先在小流量域名验证再切主域名

9.3 回滚

静态站点的回滚极其简单——旧产物还在,把流量切回去即可:

vercel promote <previous-deployment-url>   # Vercel:提升上一次部署
# Netlify:面板选历史部署点 Publish;对象存储:切回上一版本目录前缀

前提是保留历史产物。若上传时用了 --delete 且未做版本目录,回滚就只能重新构建旧提交。

记忆:预览环境是平台方案的最大红利;静态站的灰度只是改流量分配、回滚只是切回旧产物——但前提是历史产物必须保留,别用 --delete 一把清空。


10. 构建产物审计与常见陷阱

10.1 上线前的产物审计

grep -o '/assets/[^"]*' dist/index.html          # HTML 引用的 hash 文件
ls dist/assets/*.map 2>/dev/null && echo "警告:sourcemap 未清理"
ls dist/_redirects dist/_headers dist/robots.txt; du -sh dist   # 约定文件与总体积

10.2 高频陷阱表

现象原因处理
刷新深层路由 404未配回退加 rewrites 或 try_files
发布后用户看不到新版HTML 被强缓存index.html 设 no-cache
资源 404 返回 HTMLCDN 回退未排除 assets回退规则排除静态目录
环境变量改了没生效构建期替换重新构建部署
回滚失败未保留历史产物版本目录或平台历史部署
首屏白屏先传 HTML 后传资源调整上传顺序
搜索收录重复页全量回退返回 200按路由前缀精细化回退

10.3 上线核对清单

□ 输出目录为 dist,SPA 回退规则为「重写」语义
□ HTML 与 sw.js 缓存头为 no-cache,带 hash 资源为 immutable
□ VITE_ 变量已在目标环境配置并重新构建
□ 预览环境可访问,且后端接口白名单已放开
□ sourcemap 未进入生产产物(或已限制访问)
□ 历史产物已保留,回滚路径已验证

记忆:部署翻车集中在「回退、缓存、变量、回滚」四件事——回退用重写、HTML 不缓存、变量改后重建、历史产物留好,八成的线上事故都能提前避免。


延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「vite」更多文章

  1. Vite 中的 3D 与 WebGL 工程化:Three.js、模型纹理压缩与渲染性能治理
  2. Vite 项目的 GraphQL 数据层:Apollo、urql、codegen 与缓存失效实战
  3. Vite 桌面应用实战:Electron 与 Tauri 的工程化落地