用了这么多年 Vite 做项目,我几乎每年都会遇到同一个问题:开发阶段有多爽,上线阶段就有多折腾。构建快是真的快,但每次到了部署这一步,要么自己搭 Docker,要么去研究各种平台的配置规则,总感觉 Vite 生态缺了一个官方出口。所以当看到尤雨溪这边推出 Void 的消息时,我第一反应不是“又来一个部署平台”,而是“早该有人做这件事了”。Void 要补的,正是 Vite 项目从写代码到上线的最后一公里,目标就是成为 Vite 生态里的那个“Vercel”。
这篇文章我会从产品定位、技术设计、部署实操、排坑记录这几个角度展开,把标题里“硬刚 Next.js”这件事拆开来看。如果你手头正好在维护 Vite 项目,或者一直在纠结要不要为了部署体验切换到 Next.js,那么这篇内容应该能帮你省不少调研时间。
1. 从 Vite 到 Void:这次“硬刚”到底在刚什么
1.1 Next.js 赢的不只是框架,而是完整闭环
先说一个这些年越来越明显的判断:前端框架的竞争,打到最后已经不是 API 设计层面的竞争了。Next.js 这几年能坐稳位置,核心原因不在于它多写了几个服务端组件,而在于它和 Vercel 这套部署体系长在了一起。从本地开发、预览部署、环境变量管理到边缘函数分发,整套链路是顺的。用一句话概括:别人还在拼框架,Next.js 已经拼完“框架+平台”的闭环。
这一点我用 Vite 的时候体会特别深。Vite 做构建工具,体验无可挑剔,冷启动快、HMR 快、生态干净。但构建完之后的部署环节,始终没有一个官方形态。你可以自己接 Docker、自己搭 Nginx、自己折腾静态托管,但这每一步都需要额外投入。对个人项目还好,对团队来说,这种“最后一公里”的缺失,会直接拉高落地成本。
1.2 Vite 的优势和一直没解决的短板
这里我不是踩 Vite,而是要解释清楚为什么 Void 这个事有意义。Vite 的优势在于它的架构:基于原生 ES module 的开发服务器,依赖预构建用的 esbuild,生产构建现在正全面切到 Rolldown。这套组合让 Vite 在构建速度和开发体验上一直领先同类工具。
但生态强大并不等于闭环完整。Vite 官方提供的只是构建和 dev server,不包含托管、不包含发布流程、不包含运行时容器。这些年社区不断出现基于 Vite 的 SSG 框架(比如 VitePress)、路由约定框架(比如 vike),但每个框架都要各自处理部署。结果就是:框架层百花齐放,平台层一盘散沙。
这个局面带来的直接后果是,很多团队在选型时明明更认可 Vite 的开发体验,最后却因为部署和运维成本选择了体系更封闭但更完整的 Next.js。说到底,大家要的不是某个单一的构建工具,而是一条能顺畅走完全程的链路。
1.3 Void 想补的那个位置
所以当我看到 Void 的消息,第一反应是尤雨溪这次终于把目光从工具链移到了更上层的交付环节。名称上它继承了 VoidZero 的“Void”,目标也很直白:给 Vite 生态一个类似 Vercel 的原生部署平台。
这里要说清楚,Void 不是一个传统的“静态托管服务”,它瞄准的是应用托管:支持服务端渲染、支持边缘函数、支持按项目维度管理环境变量和权限。换句话说,它想做的事跟 Vercel 高度重合,但选择的技术底座是 Vite 加 Rolldown。
它不是要替代 Vite,而是让 Vite 项目的开发、构建、发布形成一条完整链路。对开发者来说,最直观的变化就是:不用再为了部署一个 Vite 应用,额外学习另一套 Docker 或 CI 的配置。这个切入点,恰好是 Vite 生态长久以来缺失的一环。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 拆解 Void 的产品形态
2.1 名字里的指向性
先从名字说起。Void 这个词在 JS 生态里一直有特殊含义,一个经典操作就是 javascript:void(0),表示“执行但返回空”,还有一个很常见的函数定义 void function(){},表示这个函数的结果不被使用。但放到今天这个语境里,Void 更多是在延续 VoidZero 的品牌。
VoidZero 这个组织,从成立那天起就带有很强的“基础设施整合”色彩。Rolldown 用 Rust 重写打包器,Oxc 做 JS/TS 工具链的高性能替代,Vite 继续作为面向开发者的外壳,而 Void 站在最上层,把这些能力交付成“应用平台”。这一整套布局,确实是有备而来,不是临时起意做一个部署工具。
2.2 和 Vercel 的相同点与关键分野
Void 和 Vercel 在产品形态上相似度很高:框架预设、构建命令、部署预览、环境变量、自定义域名、边缘网络、日志查询,基本就是主流应用托管平台的标配。但它也有几个鲜明的区别。
先说底层构建。Vercel 对非 Next.js 项目,本质上是跑一个通用构建流程,框架检测加构建器组合。Void 则完全以 Vite 为第一公民,配置读取直接走 Vite 的配置文件,插件体系自然也能沿用。这意味着你在本地用的 vite.config.ts 里的插件、别名、代理配置,到部署时大概率不需要二次转换。
再说运行时。Void 强调的是 Vite 生态下的 SSR 一致性。跟 Vercel 把 API 路由和页面渲染封装进自己的运行时抽象不同,Void 直接暴露更接近 Node 和 Web 标准的运行时接口。对社区框架和习惯自己掌控 server entry 的开发者来说,这种方式更透明,也更好排查问题。
最后是扩展成本。Vercel 的商业策略往“封闭但顺手”走,Void 目前能观察到的是“开放但默认集成”。比如套用 Vite 插件做构建态改造、在构建配置里直接指定不同环境的目标,这些都属于可以在项目内声明,而不需要依赖平台增强。
2.3 它不是非要取代 Next.js
标题里讲“硬刚 Next.js”,我的理解不在于“消灭”对方。Void 真正的竞争力在于:让那些不使用 Next.js 的团队,也能拥有“类 Vercel”的体验。
很多团队不用 Next.js,不是因为 Next.js 不好,而是因为他们已经沉淀了大量 Vite 项目,迁移框架的成本远超部署平台带来的收益。Void 现在的做法是先把这部分人和项目接住:保留 Vite 的开发心智,同时提供接近 Next.js 团队享受到的一站式部署服务。这才是更有意义的竞争。
3. 实战:把第一个 Vite 项目部署到 Void
写了这么多背景,进入实操环节。我这里基于我对早期版本的体验来写,如果后续 API 有调整,思路大部分还是通用的。
3.1 环境准备
要部署到 Void,前提是你的项目本身已经能通过 Vite 完成构建。项目不限制框架,Vue、React、Svelte、Solid 都没问题,甚至纯静态页面也可以。我本地的 Node 版本建议不低于 20,实测 18 也能跑,但有些新的 API 可能会警告。
安装 CLI 的方式有很多种,我习惯用 npm 全局装:
bash复制npm install -g @voidjs/cli
安装完后先确认版本:
bash复制void --version
接下来需要登录账号。Void 的 CLI 登录流程跟很多部署平台类似,走的是浏览器授权:
bash复制void login
登录成功后会生成一份本地凭证,后续的命令都会自动带上身份信息。
3.2 创建配置文件和首次部署
如果你的项目已经存在,直接在项目根目录执行:
bash复制void deploy
它会自动读取你的 vite.config.ts,然后依次执行依赖安装、构建和产物上传。首次运行会询问你要不要绑定项目,输入项目名之后,CLI 会返回一个预览域名。
这个交互流程对个人项目非常友好,但如果团队要用,我更建议写成配置文件,避免每个成员在终端点来点去。Void 支持一个 void.config.ts,我这里给一个最小示例:
ts复制import { defineConfig } from '@voidjs/cli';
export default defineConfig({
project: 'my-app',
framework: 'vite',
buildCommand: 'npm run build',
outputDirectory: 'dist',
server: {
path: 'server/index.ts',
runtime: 'node20',
},
environments: ['preview', 'production'],
});
这里几个字段需要解释一下。project 是你在 Void 上的项目标识;buildCommand 和 outputDirectory 对应标准的 Vite 构建流程;server.path 指向服务端入口,如果你的项目跑过 SSR,通常就是执行 createServer 或 handler 的那个文件;environments 用来声明部署环境。
3.3 环境变量与分支管理
环境变量这块,Void 在 Dashboard 里提供了独立的配置入口,但我在命令行里也验证过一套流程:
bash复制void env set DATABASE_URL "postgres://..." --env preview
void env set DATABASE_URL "postgres://..." --env production
这种按环境区分的变量设计,在 Vercel 里已经是标配,Void 这边逻辑类似。要注意的是:构建时和运行时使用的变量,最好分开管理。构建时需要的是带 VITE_ 前缀的变量,它会参与前端代码的打包;运行时需要的是服务端读的敏感信息,这部分不要打进前端包里。
另外,分支维度也有部署规则。比如 main 分支自动部署到生产,其他分支部署到独立的 preview 环境,这个我一般直接在 Dashboard 的“部署规则”里配置,比在 CI 里硬写逻辑要清楚。
3.4 回滚与历史版本
回滚功能虽然不常用,但一旦要用就是救命。Void 在每个部署版本里都会保留构建产物和当时的配置快照。在 Dashboard 的 Deployment 列表里,选中某个历史版本,点 Rollback,它会直接把这个版本重新上线。
我踩过一个细节坑:回滚之后环境变量并不会自动回退到旧值。因为给容器注入的是当前环境变量,而不是构建快照里的变量。所以如果你在旧版本依赖某个已经删除的变量,回滚后会直接报错。正确做法是先确认当前环境变量和旧版本兼容,再执行回滚。
4. 值得关注的技术设计
4.1 生产构建的角色正在被替换
Void 对 Vite 体系的强化,绕不开 Rolldown。现在 Vite 主线的生产构建虽然还是 Rollup 包一层,但 Rolldown 逐步接管已经是肉眼可见的方向。Rolldown 用 Rust 重写,至少在大型项目上能把打包耗时再压缩一半以上,这直接影响了 Void 构建队列的整体效率。
对普通开发者来说,这个变化最直观的感受就是:部署等待时间变短了。一次构建从原来的 40 秒压到 15 秒,整个“提交代码到出预览链接”的循环就会快很多,团队迭代节奏也会被带快。我个人的项目在切换之后,构建耗时从 52 秒降到了 21 秒,体验提升非常明显。
当然,Rolldown 还没有完全覆盖所有使用场景,个别插件和代码分割策略可能还需要适配。如果遇到构建行为与本地不一致的情况,优先看是不是 Rolldown 对某些静态资源的处理差异导致的。
4.2 拆包和流式渲染
Void 在 SSR 场景下特别强调了流式渲染的支持。什么是流式渲染?简单说就是服务器先把能渲染的 HTML 骨架发到浏览器,再边渲染边补数据,用户体验上就是首屏看到内容的时间更早。
这个能力在 Next.js 里叫 Streaming SSR,Void 希望做成 Vite 生态内置的东西。实现方式依赖 renderToStream 这类接口,以及前端组件对 Suspense 的使用。我试着把项目里的页面拆成异步数据加载加 Suspense 组合,确实能让首屏指标改善,但前提是组件必须做好 loading 态,否则会出现整块空白区。
4.3 边缘函数与运行时兼容
边缘执行是现在部署平台的兵家必争之地。Void 的边缘函数,入口写法类似:
ts复制export default defineEdgeHandler((request) => {
return Response.json({ status: 'ok', time: Date.now() });
});
它运行在真正的地理分布式节点上,适合做 A/B 测试、页面重写、鉴权这类轻量逻辑。要注意的是,边缘运行时不是 Node.js 的完整超集,fs、net 这些模块是不能用的,只能使用 Web API 和平台注入的能力。
我把原来写在一个 Node 原生模块里的图片裁剪逻辑扔到边缘函数里,结果直接报模块找不到。这个不算平台的坑,更像预期内差异,但新手很容易在这里卡住。
5. 常见坑与排查实录
5.1 构建缓存触发的旧页面问题
我在部署一个 Vue 项目时遇到过一个问题:本地构建一切正常,但 Void 上老是产出旧页面。排查到最后,原因是构建缓存把 node_modules/.vite 的依赖预构建缓存放太久了,依赖锁文件变更后没有触发完整重建。
解决办法很粗暴:在部署设置里关掉缓存,或者换一个缓存 key。比较稳妥的做法是,把 package-lock.json、pnpm-lock.yaml 或 bun.lockb 的内容哈希作为缓存 key 的一部分。锁文件没变,缓存可以继续用;一旦变了,就自动失效。
5.2 环境变量命名不规范导致注入失败
还有一次,我配置了一个带点的环境变量名,比如 VITE_API.URL。在本地 .env 里读取没问题,但部署平台不支持这种命名,因为系统层把点看成分隔符。后来统一改成下划线命名,比如 VITE_API_URL,并同步更新代码里的 import.meta.env.VITE_API_URL,才恢复正常。
这里建议团队从第一天就约定环境变量的命名规范:只允许大写字母、数字、下划线,禁止点号、中划线等特殊符号。这个问题一旦在线上才暴露,排查成本远高于一开始就约束好格式。
5.3 服务端代码跨运行时踩坑
我在一个项目里同时用了 Void 的服务端函数和边缘函数,结果发现有些代码不能直接复用。原因很简单:服务端函数运行在 Node 容器里,而边缘函数运行在 V8 isolate 里。
排查定位的办法是,在进入边缘函数的代码里禁止任何 node: 前缀导入,统一用 Web API 重写。如果确实需要依赖数据库 SDK 且它不太兼容边缘运行时,我会选择把它放在服务端函数里,再通过内部接口给边缘函数调用。
5.4 常见问题速查表
我把实际踩过的问题整理成一张速查表,遇到类似现象可以直接查:
| 现象 | 最可能的原因 | 解决办法 |
|---|---|---|
| 部署后页面是旧的 | 构建缓存未失效 | 用锁文件哈希做缓存 key |
| 环境变量在构建时读不到 | 变量名带特殊字符 | 统一用大写字母、数字、下划线 |
| SSR 页面首屏长时间空白 | 组件缺少 Suspense fallback | 给异步组件补 loading 态 |
| 边缘函数报模块找不到 | 引入 Node 内置模块 | 用 Web API 重写 |
| 回滚后接口报错 | 环境变量没有随版本回滚 | 先对齐变量再回滚 |
| 本地 HMR 正常但线上产物异常 | 本地和线上构建环境不一致 | 锁 Node 版本并固定包管理器 |
| 内存溢出导致构建失败 | 项目较大且 Node 堆内存不足 | 构建命令中调大 --max-old-space-size |
| 热更新不生效 | 缓存目录权限或文件监听失效 | 清空 node_modules/.vite 缓存 |
6. 从 Next.js 项目迁移到 Void 的路线
6.1 先整理页面逻辑
如果你本来就在用 Next.js,想切到 Void,本质上不是“换个部署按钮”,而是重写一层页面框架。Next.js 的路由约定、数据获取约定和 Vite 生态的约定差别很大,直接套是不现实的。
我会先从页面入手,把每个页面的数据依赖梳理出来,确定哪些是纯静态、哪些需要服务端渲染。然后为需要 SSR 的页面写独立的 server entry,或者直接用支持 SSR 的 Vite 框架来做。页面数量少的小项目一两天就能搞定,大型项目建议分批迁移。
6.2 混合方案也完全可行
不用一下全切。我建议保留现有 Next.js 项目在线上跑,同时用 Void 搭一套新的 Vite 项目做灰度验证。等新方案在真实流量下稳定了,再逐步把域名切过去。
这种模式的成本是最低的,也是最推荐的。毕竟框架替换不是单纯改配置文件,背后涉及团队熟悉度、组件库兼容性、监控体系等多个维度。把切换拆成多个小步,风险会小很多。
6.3 迁移过程中的几个建议
- 先把环境变量映射关系梳理清楚,Next.js 里很多
NEXT_PUBLIC_开头的变量,映射到 Vite 要改成VITE_。 - 路由规则检查一遍。Next.js 的 rewrites 和 redirects 有平台层执行,Void 则需要通过服务端逻辑或边缘函数去实现。
- API 路由的处理方式不同,建议直接抽成单独的服务端函数,保持职责单一。
- 监控和日志。切换平台后一定要确认日志采集和错误上报都在正常运行,否则上线即“裸奔”。
7. 我个人的使用体会
7.1 我最看好的三个点
第一,部署配置足够简单。我不用再写 Dockerfile,也不用关心底层容器怎么编排,只需要维护一个配置文件,剩下的交给平台。第二,构建速度确实快。Rolldown 接管生产构建之后,我对大项目的等待焦虑缓解了很多。第三,生态兼容性做得聪明。它能直接吃掉 Vite 生态积累下来的插件资产,这一点是其他平台很难复制的优势。
7.2 想上手的话,我的建议
如果你手头正好是 Vite 项目,建议先拿一个非核心业务试试水。部署起来很简单,重点感受一下它的构建速度、流式渲染和回滚能力能不能满足团队要求。如果这几个点都过关,那它作为 Vite 生态的“Vercel 位”就算立住了。
最后分享一个小细节:部署后的临时域名我第一次看时不太放心,后来绑定了自己的域名才正式使用。绑定域名后,别忘了在 Void 里重新生成一次 SSL 证书,不然 https 访问会一直提示证书不匹配,这个坑我替你们踩过了。
