portless

portless

熱門

設定並使用 portless 來建立具名本地開發伺服器網址(例如 https://myapp.localhost 取代 http://localhost:3000)。適用於將 portless 整合到專案、設定開發伺服器名稱、設定本地代理、使用 .localhost 網域,或排解連接埠/代理問題。

1萬星標
329分支
更新於 2026/7/18
SKILL.md
唯讀
名稱
portless
描述

設定並使用 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 的歷史是不同專案的混合

安裝

建議全域安裝,或作為專案的開發依賴。不要使用 npxpnpm 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.yamlpackage.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   # 繞過代理,使用預設連接埠

運作原理

  1. portless proxy start 在背景啟動一個 HTTPS 反向代理,監聽連接埠 443。在 macOS/Linux 上自動使用 sudo 提升權限;若無法使用 sudo 則回退到連接埠 1355。使用 --no-tls 可在連接埠 80 上使用純 HTTP。可透過 -p / --portPORTLESS_PORT 環境變數設定。當你執行 app 時,代理也會自動啟動。
  2. portless <name> <cmd> 透過 PORT 環境變數分配一個隨機的空閒連接埠(4000-4999),並向代理註冊該 app。
  3. 瀏覽器連線到 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-certificatesupdate-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 主機行為,而不是強制使用 --hostHOST

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 顯示版本

保留名稱: rungetaliashostslistdoctortrustcleanpruneproxyservice 是子指令,不能直接用作 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 條目具有相同的形狀(namescriptappPortproxy)。當 apps 存在時,頂層欄位僅在單一 app 模式下套用。

package.json 中的 "portless" 鍵

你可以將 "portless" 鍵加入 package.json,而不是使用獨立的 portless.json。字串值是設定名稱的簡寫:

{ "portless": "myapp" }

物件支援所有每個 app 的欄位(namescriptappPortproxy):

{ "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 旗標:ViteVitePlusvp)、AstroReact RouterAngularExpoReact 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 附帶的版本)
  • tailscale CLI(選用,用於 --tailscale--funnel
  • ngrok CLI(選用,用於 --ngrok