命令行工具
vercel-labs/portless avatar
vercel-labs/portless

portless:用固定域名替代本地端口号,开发体验的一次减法

将端口号替换为稳定的、命名的本地 URL。对于人类和特工来说。

12,449 个 Star413 个 ForkTypeScriptApache-2.0

秒懂

它是什么?
portless 把 localhost:3000 这类端口地址换成 myapp.localhost 这样的固定域名,并自动处理 HTTPS 和代理。它对单仓库和 turborepo 有专门支持,但 pre-1.0 的状态格式变更需要留意。
适合谁用?
适合被端口号困扰的前端开发者,尤其是使用 Next.js、Vite 或 Astro 且经常同时启动多个服务的人。单仓库场景下,portless 的自动发现和 turborepo 集成能省掉不少手工配置。
能商用吗?
可以。Apache-2.0 是宽松许可证:你可以使用、修改并销售基于它的软件,只需保留版权和许可证声明。
还在维护吗?
在维护。仓库最近一次提交在 1 天前。
用什么语言写的?
主要是 TypeScript(依据 GitHub 的语言统计)。

以上回答依据项目的 GitHub 数据(最近同步于 2026年9月15日)和我们的分析,不构成法律意见。

开源项目深度解析

端口号是本地开发的隐性税

端口号是本地开发的隐性税,每个跑在前端的开发者都经历过端口冲突:3000 被占用,换 3001,再换 3002。浏览器地址栏里记不住这些数字,分享给同事时还要强调端口号。portless 把 http://localhost:3000 变成 https://myapp.localhost。固定域名可以写进环境变量、代理配置和文档里,不用每次启动都确认端口。项目描述里特意提到「for humans and agents」,意思是 AI 编程代理也能从固定地址中受益,它们不需要解析动态端口。

代理、端口注入与 CA 信任的三角关系

portless 的机制分三层。第一层是代理,它监听 443 端口,把 myapp.localhost 的请求转发到子进程。第二层是端口分配,子进程通过 PORT 环境变量拿到 4000 到 4999 之间的随机端口,大多数框架会自动遵守。第三层是参数注入,针对 Vite、Astro 这类忽略 PORT 的框架,portless 会识别出 dev、serve、preview 等命令,自动追加 --port 和 --host 参数。注入不是无脑的,vite build 这类非服务命令会被跳过,复合命令(含 &&、|、;)和带 env 前缀的脚本也会被放弃。文档明确说这类脚本「keep their own port, so set it in the script yourself」,这是设计边界,不是缺陷。

首次运行:CA 生成与 sudo 提权

HTTPS 默认开启,附带 HTTP/2。首次运行 portless 会生成一个本地 CA,把它加入系统信任列表,然后绑定 443 端口。在 macOS 和 Linux 上,绑定 443 需要 root 权限,portless 会自动用 sudo 提权。这里有个值得注意的细节:当代理以 sudo 运行时,它会把 ~/.portless 路径解析回普通用户的家目录,这样代理和未提权的应用进程能共享同一份路由注册表。如果你不想走 HTTPS,可以用 --no-tls 切回明文 HTTP。非交互环境下(没有 TTY 或 CI=1),portless 不会弹提示,而是直接报错退出,这对 turborepo 这类任务运行器很友好,失败会立刻暴露。

配置入口:portless.json 与 package.json 的取舍

零配置就能跑,portless 会自动推断应用名,来源顺序是 package.json 的 name、git 根目录、当前目录。需要覆盖时有两个入口。portless.json 支持完整字段,包括 name、script、appPort、proxy,以及面向单仓库的 apps 映射。package.json 里的 "portless" 键是另一种写法,字符串是 name 的简写,对象则支持全部字段。优先级从高到低是 CLI 参数、package.json 的 portless 键、portless.json。单仓库场景下,根目录放一个 portless.json 就能覆盖所有 workspace 包,hostname 遵循 <package>.<project>.localhost 的约定,项目名取 workspace 里最常见的 npm scope。如果包的短名恰好等于项目名,会得到裸的 <project>.localhost,避免重复。

turborepo 集成:包装 dev 脚本而不是改 turbo.json

portless 对 turborepo 的支持方式比较巧妙,它不改 turbo.json,而是让 portless 本身成为 dev 脚本。具体做法是把真实命令挪到 dev:app,dev 脚本只写 portless,portless 读配置后自动检测包管理器,运行 pnpm run dev:app 或对应的 yarn、npm 命令。这样做的收益是,没装 portless 的同事可以直接跑 pnpm run dev:app,完全绕开代理。文档还提到,portless 会复用最近一次代理运行的配置,包括端口、TLS 和 TLD 设置,重启后不会悄悄退回默认值。显式设置的环境变量如 PORTLESS_PORT、PORTLESS_HTTPS 始终优先于缓存配置。

pre-1.0 的现实:状态格式可能变化

portless 的版本号停在 v0.15.6,文档自己承认这是 pre-1.0 软件。两个具体的风险点被写进了 README。第一,按项目安装时,不同贡献者可能跑不同版本,行为差异难以预料。第二,状态目录格式可能在小版本之间变化,升级后可能需要重新运行 portless trust 来重建 CA 信任。这意味着团队如果采用这个工具,最好统一全局安装版本,或者把版本号锁死在 package.json 里。代理在 sudo 下运行时,状态路径会解析到普通用户的家目录,这个设计避免了权限分裂,但也意味着清除状态时要找到正确的目录。

对比方案:mkcert 加 hosts 手工配置

portless 不是唯一解决本地 HTTPS 和命名域名的工具。mkcert 是更底层的方案,它只负责生成并信任本地 CA,然后你得自己把域名写进 /etc/hosts,再让代理软件(如 Caddy 或 nginx)转发到具体端口。这个方案灵活,但每一步都要手工维护,新增一个服务就要改一次 hosts 和代理配置。portless 把 CA 生成、hosts 映射、端口分配和参数注入打包成一个命令,代价是你得接受它对脚本的自动分类逻辑。如果某个脚本 portless 无法识别,它就直接放弃,不会尝试猜测。相比之下,mkcert 路线没有这个限制,但也没有自动注入端口参数的能力,Vite 这类框架你得自己处理 --port 标志。

编辑结论

适合被端口号困扰的前端开发者,尤其是使用 Next.js、Vite 或 Astro 且经常同时启动多个服务的人。单仓库场景下,portless 的自动发现和 turborepo 集成能省掉不少手工配置。不适合需要精确控制子进程端口、或者脚本里充满复合命令和 env 前缀的团队,这类情况 portless 会主动放弃注入,你得自己在脚本里写死端口。项目仍处于 pre-1.0,安装前先确认团队是否接受版本漂移,升级后若遇到路由失效,按文档重新运行 portless trust 即可。

官方来源

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
社区笔记

社区笔记