Vue 3 项目在开发阶段通常运行在 localhost 的随机端口上,比如 Vite 默认使用 5173 端口。如果需要让手机通过移动网络访问这个开发服务器,或者把本地环境提供给外部测试人员,仅靠 localhost 显然无法满足。Serveo 是一个基于 SSH 反向隧道的免费内网穿透服务,它允许开发者执行一条简单的 ssh 命令就把本机端口暴露到公网。本文将以 Vue 3 + Vite 的工作流为例,讲解如何将 Serveo 集成到工程化配置中,并分析这种方案的优势与局限。

Serveo 的核心思路并不复杂:它把 SSH 协议中的远程端口转发能力做成了一个公共的隧道服务。开发者不需要自己准备一台公网服务器,只要本机能够访问 serveo.net 的 22 端口,就可以把本地端口反向注册到对方的服务器上,从而获得一个可公开访问的域名。对于 Vue 3 工程化开发来说,这意味着可以在不改变现有 Vite 配置的情况下,快速获得一个临时分享地址。
Serveo 与 SSH 内网穿透的工作机制
要理解 Serveo,首先要弄清楚 SSH 反向隧道的基本原理。传统 SSH 本地端口转发使用 -L 参数,把远程主机的某个端口映射到本地;而远程端口转发使用 -R 参数,作用方向正好相反,它会把本地端口注册到远程 SSH 服务器上,让远程服务器监听一个指定端口,并把所有到达该端口的请求通过 SSH 加密通道转发回本地。Serveo 正是利用了 -R 参数,只不过它专门提供了一个对公众开放的 SSH 服务器,并且把远程端口固定为 80 或 443。
在 Vue 3 项目根目录下,只需打开终端执行以下命令,就可以把本地 Vite 开发服务器暴露出去:
ssh -R 80:localhost:5173 serveo.net
命令执行成功后,终端会返回一个类似 https://xxxx.serveo.net 的公网地址。访问这个地址,请求会先到达 serveo.net 的 80 端口,再通过 SSH 隧道转发到本机的 5173 端口,从而实现内网穿透。这里需要注意,命令中的 localhost:5173 指的是发起 SSH 连接的那台机器,也就是运行 Vue 3 开发服务器的主机,因此不需要额外设置 Vite 的 server.host 为 0.0.0.0。
如果希望获得一个固定的子域名,可以在远程端口前加上自定义前缀,例如:
ssh -R myvueapp:80:localhost:5173 serveo.net
这样会生成 https://myvueapp.serveo.net。不过免费服务的子域名并不保证绝对唯一,如果前缀已经被占用,连接可能会失败,此时需要更换其他前缀。在实际团队协作中,建议使用随机子域名或约定一个不常见的命名规则,以降低冲突概率。
在 Vue 3 项目中工程化接入 Serveo
手动执行一条 SSH 命令虽然简单,但在团队协作和重复开发中很容易忘记参数细节,也不利于统一维护。更合理的做法是把 Serveo 命令封装进项目的 package.json 脚本中,并结合环境变量实现可配置化。这样开发者只需要运行一条 npm 命令,就能同时启动本地开发服务器和公网隧道。
首先在 package.json 中添加两个脚本:一个负责启动 Vite,另一个负责建立 Serveo 隧道。如果希望同时运行,可以引入 concurrently 这个并行执行工具。示例如下:
{
"scripts": {
"dev": "vite",
"serveo": "ssh -R 80:localhost:5173 serveo.net",
"dev:public": "concurrently \"vite\" \"npm run serveo\""
},
"devDependencies": {
"concurrently": "^8.2.2"
}
}
运行 npm run dev:public 后,Vite 会在本机 5173 端口启动,同时 Serveo 隧道也会连接到 serveo.net。开发者在终端中可以同时看到 Vite 的本地地址和 Serveo 返回的公网地址。这种方式把两个独立进程的管理成本降到了最低,也避免了每次手动输入冗长命令。
为了让端口和子域名更具灵活性,可以借助环境变量和 Node 脚本来动态生成 SSH 参数。以下是一个简单的 scripts/serveo.js 脚本,它会读取 PORT 和 SUBDOMAIN 两个环境变量,并调用系统 SSH 客户端:
const { spawn } = require('child_process');
const port = process.env.PORT || 5173;
const subdomain = process.env.SUBDOMAIN || '';
const remote = subdomain ? `${subdomain}:80:localhost:${port}` : `80:localhost:${port}`;
const child = spawn('ssh', ['-R', remote, 'serveo.net'], { stdio: 'inherit' });
child.on('exit', (code) => {
console.log(`Serveo tunnel exited with code ${code}`);
});
然后在 package.json 中修改脚本为 node scripts/serveo.js,并可通过 SUBDOMAIN=myapp npm run serveo 来指定子域名。如果使用的是 Windows 系统,建议安装 cross-env 来设置环境变量,以避免不同 shell 之间的语法差异。这样一套工程化配置完成后,任何新加入项目的开发者都可以直接运行脚本,无需额外了解 SSH 转发的细节。
此外,为了避免每次连接都需要手动输入密码,可以提前配置 SSH 密钥认证。虽然 serveo.net 作为公共服务并不强制要求密钥,但使用密钥可以提升连接体验,也便于在 CI/CD 或自动化脚本中无感运行。对于团队内部,也可以进一步封装成 Vite 插件,在开发服务器启动后自动建立隧道,但考虑到 Serveo 的临时性,使用并行脚本通常已经足够。
实战注意事项与替代方案
将本地 Vue 3 开发服务器通过 Serveo 暴露到公网,虽然方便,但也会带来一定的安全风险。开发服务器通常没有身份认证和访问控制,任何拿到公网地址的人都可以访问页面,甚至可能触发 HMR 接口或查看源码映射。因此,不建议在包含敏感数据或内部业务逻辑的项目中长期使用固定子域名。对于临时演示,尽量使用随机地址,并在演示结束后立即关闭隧道。如果必须在受控环境下使用,可以考虑在 Vite 中增加简单的访问口令中间件,或者只暴露静态构建产物,而不是开发服务器。
稳定性是另一个需要关注的问题。Serveo 是一个免费公共服务,没有明确的可用性保障,偶尔会出现连接中断或子域名无法分配的情况。对于重要的对外演示,如果网络条件允许,可以考虑自建 SSH 服务器并使用相同的 -R 参数,这样隧道质量和域名都由自己掌控。如果只是本地真机调试,其实也可以直接让手机和电脑处于同一局域网,使用 vite --host 0.0.0.0 配合本机 IP 访问,不需要公网隧道。
除了 Serveo,常见的同类工具还包括 ngrok、cloudflared 和 localtunnel。它们各有特点,下面通过表格进行简单对比:
| 工具 | 是否需要注册 | 免费额度 | 主要特点 |
|---|---|---|---|
| Serveo | 不需要 | 完全免费但无保障 | 基于 SSH,无需安装客户端 |
| ngrok | 需要 | 有限免费 | 功能丰富,提供 Web 面板和请求重放 |
| cloudflared | 需要 Cloudflare 账号 | 免费 | 借助 Cloudflare 网络,延迟较低 |
| localtunnel | 不需要 | 免费 | 基于 Node,安装简单,但域名随机且不稳定 |
在实际排查连接问题时,可以先检查本机是否能够正常访问 serveo.net 的 22 端口。某些公司网络或防火墙会禁止出站 SSH,此时需要更换网络环境或使用 443 端口的替代服务。如果隧道建立成功但访问公网地址出现 502,通常是因为本地 Vite 服务尚未启动或端口号写错。建议在启动隧道前,先确认 localhost:5173 能正常返回页面。为了保持长连接稳定,还可以在 SSH 命令中加入 -o ServerAliveInterval=60 参数,让客户端定期发送心跳包,防止被中间设备断开。
总体来看,Serveo 在 Vue 3 工程化中的定位更偏向于轻量级、临时性的内网穿透方案。它不需要安装额外客户端,也不需要注册账号,适合快速分享本地预览、调试远程回调等场景。但如果项目需要长期稳定的公网访问、完整的访问日志或团队权限管理,那么使用 ngrok、cloudflared 或者自建隧道服务会更合适。在工程化实践中,关键是提前评估使用场景,把隧道命令和环境配置固化到脚本中,而不是每一次都手动敲命令。