設定並使用 portless 來建立具名本地開發伺服器網址(例如 https://myapp.localhost 取代 http://localhost:3000)。適用於將 portless 整合到專案、設定開發伺服器名稱、設定本地代理、使用 .localhost 網域,或排解連接埠/代理問題。
Portless
用穩定的具名 .localhost 網址取代連接埠號碼。適用於人類與 AI 代理。
為什麼要用 portless
- 連接埠衝突:兩個專案預設使用相同連接埠時出現
EADDRINUSE - 記不住連接埠:哪個 app 在 3001 還是 8080?
- 重新整理顯示錯誤的 app:停止一個伺服器,啟動另一個在同一個連接埠,舊的分頁顯示錯誤內容
- Monorepo 放大效應:每個問題隨著 repo 中的服務數量而擴大
- AI 代理測試錯誤的連接埠:AI 代理猜錯或寫死錯誤的連接埠
- Cookie/儲存空間衝突:cookie 在
localhost上跨 app 混用;連接埠變動時 localStorage 遺失 - 設定中寫死連接埠:CORS 允許清單、OAuth 重新導向、
.env檔案在連接埠變更時失效 - 與團隊分享網址:「那個在哪個連接埠?」變成 Slack 問題
- 瀏覽器歷史紀錄沒用:
localhost:3000的歷史是不同專案的混合
安裝
建議全域安裝,或作為專案的開發依賴。不要使用 npx 或 pnpm dlx 來一次性執行。
# 全域安裝(隨處可用)
npm install -g portless
# 或每個專案的開發依賴
npm install -D portless
若安裝為專案依賴,請透過 package.json 腳本或 npx portless 來呼叫(因為套件在本機,npx 不會下載任何東西)。
快速開始
# 全域安裝(或加入 -D 到專案)
npm install -g portless
# 執行你的 app(自動啟動 HTTPS 代理在連接埠 443)
portless run next dev
# -> https://<project>.localhost
# 或使用明確的名稱
portless myapp next dev
# -> https://myapp.localhost
當你執行 app 時,代理會自動啟動。你也可以用 portless proxy start 明確啟動它。自動啟動會重複使用最近一次代理執行時的設定(連接埠、TLS、TLD),因此重新啟動或重開機不會默默恢復預設值。明確的環境變數永遠優先。
在非互動環境(沒有 TTY,或 CI=1)中,portless 會退出並顯示描述性錯誤,而不是提示。像 turborepo 這樣的工作執行器應預先啟動代理。
整合模式
零設定(建議)
單純的 portless 開箱即用。它會透過代理執行 package.json 中的 "dev" 腳本,並從套件名稱、git 根目錄或目錄推斷 app 名稱:
portless # -> 執行 "dev" 腳本,https://<project>.localhost
pnpm dev # -> 不使用 portless 時正常運作,單純 "next dev"
使用選用的 portless.json 來覆蓋預設值(名稱、腳本、連接埠):
{ "name": "myapp" }
portless # -> 執行 "dev" 腳本,https://myapp.localhost
Monorepo
在 repo 根目錄放一個 portless.json。Portless 會從 pnpm-workspace.yaml 或 package.json 中的 "workspaces" 欄位(npm、yarn、bun)發現套件:
{
"apps": {
"apps/web": { "name": "myapp" },
"apps/api": { "name": "api.myapp" }
}
}
portless # 從 repo 根目錄:啟動所有有 "dev" 腳本的套件
cd apps/web && portless # 只啟動一個套件
portless --script start # 執行 "start" 而非 "dev"
apps 對應表是選用的,僅提供名稱覆蓋。未列出的套件會自動發現並使用推斷的名稱。
沒有 apps 對應表時,主機名稱遵循 <package>.<project>.localhost 格式。專案名稱來自最常見的 npm scope(例如 @myorg/web 和 @myorg/api 產生 myorg),若無則使用工作區根目錄名稱。如果套件的短名稱與專案名稱相同,則使用單純的 <project>.localhost。
Turborepo
對於 turborepo 專案,將 portless 作為 dev 腳本,並將實際指令放在另一個腳本中:
{
"scripts": { "dev": "portless", "dev:app": "next dev" },
"portless": { "name": "myapp", "script": "dev:app" }
}
pnpm dev 執行 turbo,turbo 在每個套件中執行 portless。Portless 會偵測套件管理器並透過代理執行 pnpm run dev:app。
package.json 腳本
你仍然可以直接在腳本中使用 portless:
{
"scripts": {
"dev": "portless run next dev"
}
}
當你執行 app 時,代理會自動啟動。或者明確啟動:portless proxy start。
多 app 設定與子網域
portless myapp next dev # https://myapp.localhost
portless api.myapp pnpm start # https://api.myapp.localhost
portless docs.myapp next dev # https://docs.myapp.localhost
預設情況下,只有明確註冊的子網域會被路由(嚴格模式)。使用 --wildcard 啟動代理,允許任何已註冊路由的子網域回退到該 app(例如 tenant1.myapp.localhost 路由到 myapp app)。完全符合永遠優先於萬用字元。
Git worktrees
portless run 會自動偵測 git worktrees。在連結的 worktree 中,分支名稱會作為子網域前綴,讓每個 worktree 都有獨特的網址:
# 主要 worktree(無前綴)
portless run next dev # -> https://myapp.localhost
# 在分支 "fix-ui" 上的連結 worktree
portless run next dev # -> https://fix-ui.myapp.localhost
無需修改設定。將 portless run 放入 package.json 一次,即可在所有 worktree 中運作。
繞過 portless
設定 PORTLESS=0 可直接執行指令而不透過代理:
PORTLESS=0 pnpm dev # 繞過代理,使用預設連接埠
運作原理
portless proxy start在背景啟動一個 HTTPS 反向代理,監聽連接埠 443。在 macOS/Linux 上自動使用 sudo 提升權限;若無法使用 sudo 則回退到連接埠 1355。使用--no-tls可在連接埠 80 上使用純 HTTP。可透過-p/--port或PORTLESS_PORT環境變數設定。當你執行 app 時,代理也會自動啟動。portless <name> <cmd>透過PORT環境變數分配一個隨機的空閒連接埠(4000-4999),並向代理註冊該 app。- 瀏覽器連線到
https://<name>.localhost;代理將請求轉發到 app 的分配連接埠。
在 LAN 模式之外,代理及其 HTTP 重新導向監聽器只綁定到 IPv4 和 IPv6 的 loopback 位址 127.0.0.1 和 ::1。它們不接受來自 LAN、VPN 或其他網路介面的連線。
.localhost 網域在 Chrome、Firefox 和 Edge 中原生解析到 127.0.0.1。Safari 依賴系統 DNS 解析器,在某些設定下可能無法處理 .localhost 子網域。如有需要,執行 portless hosts sync 將條目加入 /etc/hosts。
使用 portless proxy start --tld localhost --tld test 可讓同一個代理在多個 TLD 下提供相同的 app 名稱。PORTLESS_URL 使用第一個設定的 TLD。PORTLESS_TLD 接受相同的逗號分隔格式,例如 PORTLESS_TLD=localhost,test。
大多數框架(Next.js、Express、Nuxt 等)會自動讀取 PORT 環境變數。對於忽略 PORT 的框架(Vite、VitePlus、Astro、React Router、Angular、Expo、React Native),portless 會自動注入正確的 --port 旗標,並在需要時注入對應的 --host CLI 旗標。
狀態目錄
Portless 將其狀態(路由、PID 檔案、連接埠檔案)儲存在 ~/.portless。當代理以 sudo 執行時,這仍然是呼叫使用者的家目錄,因此非特權的 app 和代理可以共享路由註冊。可透過 PORTLESS_STATE_DIR 環境變數覆蓋。
環境變數
| 變數 | 說明 |
|---|---|
PORTLESS_PORT |
覆蓋預設代理連接埠(預設:HTTPS 為 443,無 HTTPS 為 80) |
PORTLESS_APP_PORT |
為 app 使用固定連接埠(跳過自動分配) |
PORTLESS_HTTPS |
HTTPS 預設開啟;設為 0 可停用(等同於 --no-tls) |
PORTLESS_LAN |
設為 1 可永遠啟用 LAN 模式(自動偵測 LAN IP) |
PORTLESS_LAN_IP |
為 LAN 模式指定特定的 LAN IP |
PORTLESS_TLD |
使用一個或多個 TLD(例如 localhost,test) |
PORTLESS_WILDCARD |
設為 1 允許未註冊的子網域回退到父路由 |
PORTLESS_SYNC_HOSTS |
設為 0 停用自動同步 /etc/hosts(預設開啟) |
PORTLESS_TAILSCALE |
設為 1 在 Tailscale 網路上分享 app(等同於 --tailscale) |
PORTLESS_FUNNEL |
設為 1 透過 Tailscale Funnel 公開分享 app(等同於 --funnel) |
PORTLESS_NGROK |
設為 1 透過 ngrok 公開分享 app(等同於 --ngrok) |
PORTLESS_STATE_DIR |
覆蓋狀態目錄 |
PORTLESS=0 |
繞過代理,直接執行指令 |
HTTP/2 + HTTPS
HTTPS 搭配 HTTP/2 預設啟用(對於有許多檔案的開發伺服器,可加快頁面載入速度)。WebSocket 可透過 HTTP/1.1(Upgrade)和 HTTP/2(RFC 8441 擴展 CONNECT)運作,因此開發伺服器的 HMR 可透過代理正常運作。首次執行會產生一個本地 CA 並將其加入系統信任儲存區。之後不會再有提示或瀏覽器警告。
portless proxy start --cert ./c.pem --key ./k.pem # 使用自訂憑證
portless proxy start --no-tls # 停用 HTTPS(純 HTTP)
portless trust # 稍後將 CA 加入信任儲存區
在 Linux 上,portless trust 支援 Debian/Ubuntu、Arch、Fedora/RHEL/CentOS 和 openSUSE(透過 update-ca-certificates 或 update-ca-trust)。在 Windows 上,它使用 certutil 將 CA 加入系統信任儲存區。在 WSL 上,它會同時更新 Linux 信任儲存區和 Windows 目前使用者的根憑證儲存區,讓 Windows 瀏覽器信任 portless HTTPS 憑證。
LAN 模式
portless proxy start --lan
portless proxy start --lan --https
portless proxy start --lan --ip 192.168.1.42
--lan 明確將代理綁定到 IPv4 和 IPv6 的未指定位址 0.0.0.0 和 ::,並透過 mDNS 公告 <name>.local 主機名稱,讓同一 Wi-Fi 上的裝置可以存取你的 app。Portless 會自動偵測你的 LAN IP 並自動跟隨網路變更,但你可以使用 --ip <address> 或 PORTLESS_LAN_IP 環境變數指定特定位址。設定 PORTLESS_LAN=1 可讓代理每次啟動時預設進入 LAN 模式。
Portless 透過 proxy.lan 記住 LAN 模式,因此如果你停止 LAN 代理後再次啟動,它會保持在 LAN 模式。所有代理設定(連接埠、TLS、TLD、LAN)都會被持久化,並在自動啟動時重複使用,除非被明確的旗標或環境變數覆蓋。使用 PORTLESS_LAN=0 可單次切換回 .localhost 模式。如果已有代理正在執行且具有不同的明確 LAN/TLS/TLD 設定,portless 會發出警告並要求你先停止它。
LAN 模式依賴 portless 啟動的系統 mDNS 輔助程式:macOS 包含 dns-sd,而 Linux 使用 avahi-utils 中的 avahi-publish-address(透過 sudo apt install avahi-utils 或你的發行版工具安裝)。
-
Next.js:將你的
.local主機名稱加入allowedDevOrigins:// next.config.js module.exports = { allowedDevOrigins: ["myapp.local", "*.myapp.local"], }; -
Expo / React Native:portless 總是注入
--port。React Native 也會獲得--host 127.0.0.1。Expo 在非 LAN 模式下獲得--host localhost,但在 LAN 模式下,portless 讓 Metro 保持其預設的 LAN 主機行為,而不是強制使用--host或HOST。
Tailscale 分享
使用 --tailscale 與 Tailscale 網路上的團隊成員分享開發伺服器,或使用 --funnel 公開到網際網路:
portless myapp --tailscale next dev
# -> https://myapp.localhost (本地)
# -> https://devbox.yourteam.ts.net (tailnet)
portless myapp --funnel next dev
# -> https://myapp.localhost (本地)
# -> https://devbox.yourteam.ts.net (公開網際網路)
必須先啟用 Tailscale HTTPS 憑證,--tailscale 或 --funnel 才能註冊 HTTPS 網址。Funnel 也必須為 tailnet 和節點啟用,--funnel 才能註冊公開網址。如果缺少任一設定,portless 會在啟動子程序前退出。
每個 --tailscale app 都掛載在自己的 Tailscale HTTPS 連接埠(443,然後 8443、8444 等)上,因此不需要框架的 basePath 設定。設定 PORTLESS_TAILSCALE=1 可讓每個 app 預設分享。portless list 會顯示本地和 tailnet 網址。Tailscale serve 註冊會在 app 退出時清除。需要安裝並連線 tailscale CLI,且啟用 Tailscale HTTPS 憑證。
ngrok 分享
使用 --ngrok 將開發伺服器公開到網際網路:
portless myapp --ngrok next dev
# -> https://myapp.localhost (本地)
# -> https://abc123.ngrok.app (公開網際網路)
設定 PORTLESS_NGROK=1 可在 portless 執行 app 時預設啟用 ngrok。portless list 會顯示本地和 ngrok 網址。ngrok 隧道會在 app 退出時清除。需要安裝 ngrok CLI 並使用 ngrok config add-authtoken <token> 進行驗證。
作業系統啟動服務
當使用者希望代理在重新開機後自動啟動時,使用 service 指令:
portless service install
portless service install --lan
portless service install --wildcard
PORTLESS_STATE_DIR=~/.portless-lan PORTLESS_LAN=1 portless service install
portless service status
portless service uninstall
除非提供安裝選項或 PORTLESS_* 環境變數,否則服務使用 portless 預設值:HTTPS 在連接埠 443 上,使用 .localhost 名稱。service install 接受代理選項,包括 --port、--no-tls、--lan、--ip、--tld、--wildcard、--cert 和 --key。使用 --state-dir <path> 或 PORTLESS_STATE_DIR=<path> 選擇服務狀態和日誌的寫入位置。
選擇的服務設定會寫入 launchd、systemd 或 Task Scheduler,並在重新開機後重複使用。portless service status 會報告已安裝的連接埠、HTTPS 模式、TLD、LAN 模式、萬用字元模式和狀態目錄。macOS 和 Linux 安裝一個 root 擁有的服務,以便連接埠 443 可以在開機時綁定。Windows 安裝一個以 SYSTEM 身分執行的 Task Scheduler 啟動工作。安裝和移除可能需要管理員權限。portless clean 會自動移除服務。
CLI 參考
| 指令 | 說明 |
|---|---|
portless |
透過代理執行 dev 腳本 |
portless |
從 monorepo 根目錄:執行所有工作區套件 |
portless --script <name> |
執行特定的 package.json 腳本(預設:dev) |
portless run [cmd] [args...] |
從專案推斷名稱,透過代理執行(自動啟動) |
portless run --name <name> <cmd> |
覆蓋推斷的基礎名稱(worktree 前綴仍會套用) |
portless <name> <cmd> [args...] |
在 https://<name>.localhost 執行 app(自動啟動代理) |
portless get <name> |
印出服務的網址(用於跨服務串接) |
portless get <name> --no-worktree |
印出網址但不含 worktree 前綴 |
portless list |
顯示活躍路由 |
portless doctor |
檢查代理、路由、DNS、CA 信任和 LAN 先決條件 |
portless trust |
將本地 CA 加入系統信任儲存區(用於 HTTPS) |
portless clean |
移除狀態、CA 信任條目和 /etc/hosts 區塊 |
portless prune |
終止來自崩潰 session 的孤立開發伺服器 |
portless prune --force |
使用 SIGKILL 而非 SIGTERM 終止孤立程序 |
portless proxy start |
啟動 HTTPS 代理作為背景服務(連接埠 443,自動提升權限) |
portless proxy start --no-tls |
啟動但不使用 HTTPS(純 HTTP 在連接埠 80) |
portless proxy start --lan |
以 LAN 模式啟動(mDNS .local,自動跟隨 LAN IP 變更) |
portless proxy start -p <number> |
在自訂連接埠上啟動代理 |
portless proxy start --tld test |
使用 .test 取代 .localhost |
portless proxy start --tld localhost --tld test |
從同一個代理提供兩個 TLD |
portless proxy start --foreground |
在前景啟動代理(用於除錯) |
portless proxy start --wildcard |
允許未註冊的子網域回退到父路由 |
portless proxy stop |
停止代理 |
portless service install |
在作業系統啟動時啟動 HTTPS 代理 |
portless service install --lan |
以 LAN 模式安裝服務 |
portless service install --wildcard |
在啟動服務中持久化萬用字元路由 |
portless service status |
顯示服務和代理狀態 |
portless service uninstall |
移除啟動服務 |
portless alias <name> <port> |
註冊靜態路由(例如用於 Docker 容器) |
portless alias <name> <port> --force |
覆蓋現有路由 |
portless alias --remove <name> |
移除靜態路由 |
portless hosts sync |
將路由加入 /etc/hosts(修正 Safari) |
portless hosts clean |
從 /etc/hosts 移除 portless 條目 |
portless <name> --app-port <n> <cmd> |
為 app 使用固定連接埠而非自動分配 |
portless <name> --tailscale <cmd> |
在 Tailscale 網路上分享 app(tailnet) |
portless <name> --funnel <cmd> |
透過 Tailscale Funnel 公開分享 app |
portless <name> --ngrok <cmd> |
透過 ngrok 公開分享 app |
portless <name> --force <cmd> |
終止現有程序並接管其路由 |
portless --name <name> <cmd> |
強制使用 <name> 作為 app 名稱(繞過子指令分派) |
portless <name> -- <cmd> [args...] |
停止旗標解析;-- 之後的所有內容都傳遞給子程序 |
portless --help / -h |
顯示說明 |
portless run --help |
顯示子指令的說明(也適用於:alias、hosts、clean) |
portless --version / -v |
顯示版本 |
保留名稱: run、get、alias、hosts、list、doctor、trust、clean、prune、proxy 和 service 是子指令,不能直接用作 app 名稱。使用 portless run <cmd> 來推斷名稱,或使用 portless --name <name> <cmd> 強制使用任何名稱(包括保留名稱)。
portless.json
選用設定檔。Portless 會在目前目錄中尋找它。
| 欄位 | 類型 | 預設值 | 說明 |
|---|---|---|---|
name |
string | 從 package.json 推斷 | 基礎 app 名稱(worktree 前綴仍會套用) |
script |
string | "dev" |
要執行的 package.json 腳本名稱 |
appPort |
number | 自動分配 | 子程序的固定連接埠 |
proxy |
boolean | 自動偵測 | 是否透過代理路由(false 用於任務) |
apps |
object | 工作區套件的覆蓋,鍵為相對路徑 | |
turbo |
boolean | true |
設為 false 以使用直接啟動而非 turborepo |
每個 apps 條目具有相同的形狀(name、script、appPort、proxy)。當 apps 存在時,頂層欄位僅在單一 app 模式下套用。
package.json 中的 "portless" 鍵
你可以將 "portless" 鍵加入 package.json,而不是使用獨立的 portless.json。字串值是設定名稱的簡寫:
{ "portless": "myapp" }
物件支援所有每個 app 的欄位(name、script、appPort、proxy):
{ "portless": { "name": "myapp", "script": "dev:app" } }
優先順序(最近的優先):CLI 旗標 > package.json "portless" 鍵 > portless.json app 條目 > 預設值。
疑難排解
執行診斷
當本地路由或 HTTPS 行為看起來不對時,請先使用 portless doctor。它是唯讀的,會檢查 Node.js、狀態目錄權限、代理存活狀態、路由條目、主機名稱解析、本地 CA 信任和 LAN 模式先決條件。
代理未執行
當你使用 portless <name> <cmd> 執行 app 時,代理會自動啟動。如果它沒有啟動(例如連接埠衝突),請手動啟動:
portless proxy start
連接埠已被佔用
另一個程序已綁定到代理連接埠。請先停止它,或使用不同的連接埠:
portless proxy start -p 8080
框架不遵守 PORT
對於忽略 PORT 環境變數的框架,portless 會自動注入正確的 --port 旗標,並在需要時注入對應的 --host 旗標:Vite、VitePlus(vp)、Astro、React Router、Angular、Expo 和 React Native。SvelteKit 內部使用 Vite,會自動處理。
對於其他不讀取 PORT 的框架,請手動傳遞連接埠:
- Webpack Dev Server:使用
--port $PORT - 自訂伺服器:讀取
process.env.PORT並監聽它
權限錯誤
預設連接埠(HTTP 為 80,HTTPS 為 443)在 macOS 和 Linux 上需要 sudo。Portless 會在需要時自動使用 sudo 提升權限。如果無法使用 sudo,它會回退到連接埠 1355(不需要 sudo)。在 Windows 上,不需要提升權限。
portless proxy start --https # 自動使用 sudo 提升權限以使用連接埠 443
portless proxy start -p 1355 --https # 不需要 sudo(網址包含 :1355)
portless proxy stop # 停止(如果以 sudo 啟動,請使用 sudo)
Safari 找不到 .localhost 網址
Safari 依賴系統 DNS 解析器來解析 .localhost 子網域,這在某些 macOS 設定上可能無法解析。Chrome、Firefox 和 Edge 有內建處理。
修正方式:
portless hosts sync # 將目前路由加入 /etc/hosts
portless hosts clean # 稍後移除條目
預設會自動同步 /etc/hosts 中的路由主機名稱。設定 PORTLESS_SYNC_HOSTS=0 可停用。
瀏覽器在使用 --https 時顯示憑證警告
本地 CA 可能尚未被信任。執行:
portless trust
這會將 portless 本地 CA 加入你的系統信任儲存區。之後,重新啟動瀏覽器。
從機器移除 portless
portless clean
如有需要,會停止代理,從信任儲存區移除 portless CA(當 portless 加入它時),刪除狀態目錄下的已知檔案,並移除 portless 的 /etc/hosts 區塊。在 macOS/Linux 上可能需要 sudo。如果信任儲存區移除失敗,portless 會保留其 CA 憑證和金鑰,以便稍後的 portless clean 可以安全地重試。
代理迴圈(508 Loop Detected)
如果你的開發伺服器將請求代理到另一個 portless app(例如 Vite 將 /api 代理到 api.myapp.localhost),代理必須改寫 Host 標頭。否則,portless 會將請求路由回原始 app,造成無限迴圈。
修正方式:在代理設定中設定 changeOrigin: true(Vite、webpack-dev-server 等):
// vite.config.ts
proxy: {
"/api": {
target: "https://api.myapp.localhost",
changeOrigin: true,
ws: true,
},
}
Portless 會自動在子程序中設定 NODE_EXTRA_CA_CERTS,讓 Node.js 信任 portless CA。如果你在 portless 之外執行獨立的 Node.js 程序,請手動指向 CA:NODE_EXTRA_CA_CERTS=~/.portless/ca.pem。或者,使用 --no-tls 進行純 HTTP。
Tailscale 無法運作
如果 --tailscale 或 --funnel 失敗:
tailscale status # 檢查是否已連線
tailscale up # 連線到你的 tailnet
需要安裝 Tailscale CLI(https://tailscale.com/download)並在 PATH 中。
ngrok 無法運作
如果 --ngrok 失敗:
ngrok version # 檢查是否已安裝
ngrok config add-authtoken <token> # 設定驗證
需要安裝 ngrok CLI(https://ngrok.com/download)並在 PATH 中。
系統需求
- Node.js 24+
- macOS、Linux 或 Windows
openssl(用於--https憑證產生;macOS 和大多數 Linux 發行版已內建;在 Windows 上,透過winget install -e --id ShiningLight.OpenSSL.Dev安裝,或使用 Git for Windows 附帶的版本)tailscaleCLI(選用,用於--tailscale和--funnel)ngrokCLI(選用,用於--ngrok)






