Web Development Developer Tools 1 vues

Stop Memorizing Port Numbers! Portless Gives You HTTPS .localhost URLs

B
Bright Coding
Auteur
Stop Memorizing Port Numbers! Portless Gives You HTTPS .localhost URLs

You're three hours into a complex debugging session. Your terminal is a graveyard of localhost:3000, localhost:3001, localhost:8080 tabs. You just accidentally refreshed the wrong port and wiped your carefully reproduced state. Again. Or worse—you're pair programming and mutter "it's on localhost thirty... no, thirty-oh-one... wait, which service is this?"

Sound familiar? Port numbers are a cognitive tax that every developer pays, every single day. They're arbitrary, unstable, and fundamentally hostile to human memory. We invented DNS to solve this problem on the internet. So why are we still typing localhost:3000 in 2024?

Enter portless—a deceptively simple tool from Vercel Labs that replaces port numbers with stable, named .localhost URLs. Not localhost:3000. https://myapp.localhost. With HTTPS. With HTTP/2. With zero configuration. And it's about to become the most addictive tool in your development workflow.

What is Portless?

Portless is a local development proxy that eliminates port numbers entirely, replacing them with human-readable, stable .localhost URLs. Created by Vercel Labs—the experimental arm of the company behind Next.js↗ Bright Coding Blog, Turborepo, and the Vercel platform—portless represents a fundamental rethinking of how local development environments should work.

The tool sits between your browser and your development servers, acting as a transparent reverse proxy. When you run portless myapp next dev, it automatically assigns an ephemeral port to your Next.js application (somewhere in the 4000-4999 range), starts an HTTPS proxy on port 443, and routes https://myapp.localhost to your app. The browser never sees a port number. You never type one. The URL is stable across restarts, reboots, and even different machines.

Why it's trending now: The developer experience movement has reached a tipping point. Tools like Next.js, Vite, and Turborepo have dramatically improved how we build applications, but the local development infrastructure has remained stuck in the 1990s. Portless addresses this gap with surgical precision. It's particularly resonating with teams running micro-frontends, monorepos, or any setup where multiple services run simultaneously. The addition of Tailscale integration for secure sharing and LAN mode for mobile device testing has pushed it from "nice-to-have" to "how did I live without this?"

The project's tagline—"For humans and agents"—hints at a deeper vision. As AI coding assistants become ubiquitous, stable, predictable URLs become infrastructure that both humans and automated agents can rely on. No more parsing console output to find "Local: http://localhost:5173/".

Key Features That Make Portless Irresistible

Zero-Configuration HTTPS with Auto-Trusted Certificates Portless generates a local Certificate Authority on first run and automatically adds it to your system trust store. The result? Real HTTPS on https://myapp.localhost with zero browser warnings. No more mkcert setup, no more clicking through "Your connection is not private" screens. On Linux, it supports Debian/Ubuntu, Arch, Fedora/RHEL/CentOS, and openSUSE automatically.

HTTP/2 by Default Here's where it gets technically interesting. Browsers limit HTTP/1.1 to 6 concurrent connections per host. Modern dev servers—especially Vite and Nuxt—serve many unbundled files, creating a bottleneck that manifests as slow initial loads. Portless enables HTTP/2 multiplexing by default, collapsing all requests into a single connection. The performance difference on complex applications is genuinely noticeable.

Framework-Aware Port Injection Most frameworks (Next.js, Express, Nuxt) respect the PORT environment variable. But some—Vite, Astro, React↗ Bright Coding Blog Router, Angular, Expo, React Native—stubbornly ignore it. Portless doesn't just fail; it auto-injects the correct --port flag and, when needed, --host flags. This isn't documented in most frameworks' "getting started" guides, and discovering it manually costs hours.

Intelligent Monorepo Discovery Place one portless.json at your repository root, and portless discovers all workspace packages from pnpm-workspace.yaml or package.json workspaces. Run portless from the root, and all your services start with sensible names. The naming convention (<package>.<project>.localhost) eliminates collisions without configuration.

Git Worktree Auto-Detection Working with git worktrees? Portless automatically prepends branch names as subdomains. Your fix-ui branch becomes https://fix-ui.myapp.localhost—no manual configuration, no port collisions between worktrees, no --force flags.

Persistent Proxy State The proxy remembers its configuration (port, TLS, TLD, LAN mode) across restarts. Reboot your machine, run portless again, and your URLs are identical. Explicit environment variables always override, giving you predictable behavior when you need it.

Real-World Use Cases Where Portless Shines

Micro-Frontend Development↗ Bright Coding Blog

Running a shell app with three micro-frontends and two APIs? Without portless, you're managing localhost:3000 through localhost:3004, creating a mental map that evaporates after lunch. With portless: https://shell.localhost, https://catalog.localhost, https://cart.localhost, https://api.localhost, https://legacy.localhost. Your browser history becomes documentation.

Mobile Development with HTTPS Requirements

Modern web features—Service Workers, Geolocation, Camera access, Payment Request API—require secure contexts. Testing on actual devices means tunneling or complex certificate setup. Portless's LAN mode advertises services via mDNS as <name>.local, reachable from any device on your network. Your iPhone can hit https://myapp.local with valid HTTPS, no configuration.

Monorepo Service Orchestration

In a Turborepo with 8 packages, pnpm dev traditionally starts a wall of ports. Portless transforms this into a coherent URL scheme. The apps configuration lets you explicitly map paths to names, or auto-discover from package names. New team members understand the architecture by looking at their browser tabs.

Secure Collaboration with Tailscale

Need to share your in-progress feature with a teammate? portless myapp --tailscale next dev creates both a local URL and a Tailscale tailnet URL (https://devbox.yourteam.ts.net). No ngrok, no public exposure, no expiration timers. For public demos, --funnel exposes via Tailscale Funnel with a single flag.

API-to-Frontend Proxying Without Configuration Hell

When your Vite dev server proxies to your API, portless handles the Host header rewriting automatically. It even detects misconfigurations that would cause infinite loops and responds with 508 Loop Detected—saving you from the silent failure that has consumed countless debugging hours.

Step-by-Step Installation & Setup Guide

Global Installation (Recommended)

Global installation ensures consistent behavior across all your projects and prevents version drift between team members:

npm install -g portless

Why global? Portless is pre-1.0, and per-project installations risk different contributors running incompatible versions. The state directory format can change between releases, potentially requiring re-running portless trust.

Project-Level Installation

If you prefer explicit dependencies:

npm install -D portless

First Run: Trust the Certificate Authority

On first execution, portless generates a local CA and prompts for trust installation:

portless myapp next dev
# Follow the system prompt to trust the certificate

If you skip the prompt, trust later with:

portless trust

Basic Usage Patterns

Simplest form (infers name from package.json):

portless        # Runs "dev" script, creates https://<project>.localhost

Explicit name and command:

portless myapp next dev
# -> https://myapp.localhost

With package.json script integration:

{
  "scripts": {
    "dev": "portless run next dev"
  }
}

Or with portless.json for cleaner scripts:

{ "name": "myapp" }
{
  "scripts": {
    "dev": "next dev"
  }
}

Then simply:

portless        # -> https://myapp.localhost

Monorepo Configuration

Create portless.json at repository root:

{
  "apps": {
    "apps/web": { "name": "myapp" },
    "apps/api": { "name": "api.myapp" }
  }
}

Run from root to start all workspace packages with "dev" scripts, or cd into individual packages.

OS Startup Service (Optional)

For URLs available immediately after reboot:

portless service install    # macOS/Linux: root service on 443; Windows: SYSTEM task
portless service status     # Verify operation
portless service uninstall  # Remove when needed

Real Code Examples from the Repository

Let's examine actual patterns from the portless documentation, with detailed explanations of what's happening under the hood.

Example 1: Basic Transformation

The core value proposition, expressed as a diff:

- "dev": "next dev"                  # http://localhost:3000
+ "dev": "portless run next dev"     # https://myapp.localhost

What's happening here? The original Next.js dev script binds to localhost:3000 with HTTP. The portless-wrapped version intercepts the command, assigns an ephemeral port (4000-4999) via the PORT environment variable, starts the HTTPS proxy on 443 if not running, and registers the route. Next.js respects PORT automatically, so no framework changes are needed. The result is a stable, memorable URL with production-like HTTPS and HTTP/2.

Example 2: Turborepo Integration

For teams using Turborepo, portless integrates without modifying turbo.json:

{
  "scripts": {
    "dev": "portless",
    "dev:app": "next dev"
  },
  "portless": { "name": "myapp", "script": "dev:app" }
}

Deep dive: Turbo runs each package's dev script. Here, that script invokes portless (no arguments), which reads the "portless" configuration object. It detects name: "myapp" and script: "dev:app", then runs pnpm run dev:app (or yarn/bun/npm as appropriate) through the proxy. The magic is in portless's package manager detection—it doesn't hardcode pnpm, it inspects your project. People without portless installed can still run pnpm run dev:app directly, maintaining backward compatibility.

Example 3: package.json Shorthand and Full Configuration

The "portless" key in package.json supports progressive disclosure—from simple to complex:

{
  "name": "@myorg/web",
  "portless": "myapp"
}

String shorthand sets just the name. For full control:

{
  "name": "@myorg/web",
  "portless": { "name": "myapp", "script": "dev:app" }
}

Precedence rules matter here: The package.json "portless" key overrides portless.json app entries, but CLI flags (--name, --script) override everything. This lets you commit sensible defaults while allowing temporary overrides for experimentation.

Example 4: Vite Proxy Configuration for API Routing

When proxying between portless apps, the Host header must be rewritten. Here's the Vite configuration:

// vite.config.ts
export default {
  server: {
    proxy: {
      "/api": {
        target: "https://api.myapp.localhost",
        changeOrigin: true,  // Critical: rewrites Host header to target
        ws: true,            // Enable WebSocket proxying
      },
    },
  },
}

Why changeOrigin: true is essential: Without this, Vite preserves the original Host: myapp.localhost header when proxying to api.myapp.localhost. Portless receives a request for api.myapp.localhost with a mismatched Host header, routes it back to the frontend app, creating an infinite loop. Portless detects this and returns 508 Loop Detected, but fixing the configuration eliminates the problem entirely. The ws: true enables WebSocket forwarding for hot module replacement and real-time APIs.

Example 5: Next.js with LAN Mode

For mobile testing with Next.js in LAN mode:

// next.config.js
module.exports = {
  allowedDevOrigins: ["myapp.local", "*.myapp.local"],
}

Next.js 14+ restricts dev origins for security. LAN mode advertises myapp.local via mDNS, but Next.js must explicitly allow this origin. The wildcard *.myapp.local covers git worktree subdomains automatically.

Advanced Usage & Best Practices

Environment Variable Overrides Set persistent defaults in your shell profile:

export PORTLESS_LAN=1        # Default to LAN mode
export PORTLESS_TAILSCALE=1  # Auto-share on tailnet

Subdomain Wildcards for Multi-Tenant Testing

portless proxy start --wildcard

This allows tenant1.myapp.localhost, tenant2.myapp.localhost to route to the same myapp service without explicit registration—essential for testing multi-tenant SaaS applications.

Custom Certificates for Corporate Environments

portless proxy start --cert ./corp-cert.pem --key ./corp-key.pem

Disabling Portless Temporarily

PORTLESS=0 pnpm dev    # Bypasses proxy, uses framework defaults

Cleaning Up Completely

portless clean    # Removes all state, CA, hosts entries

Safari-Specific DNS Handling

Safari's system DNS resolver sometimes fails with .localhost subdomains:

portless hosts sync    # Add routes to /etc/hosts
portless hosts clean   # Remove when done

Auto-sync is enabled by default; set PORTLESS_SYNC_HOSTS=0 to disable.

Comparison with Alternatives

Feature Portless ngrok localtunnel Caddy + mkcert
Local-only URLs ✅ .localhost ❌ Public URLs ❌ Public URLs ✅ Manual setup
Auto HTTPS trust ✅ Built-in ✅ Built-in ❌ HTTP only ⚠️ Manual mkcert
HTTP/2 default ✅ Yes ✅ Yes ❌ No ✅ Yes
Framework port injection ✅ Auto-detect ❌ Manual ❌ Manual ❌ Manual
Monorepo awareness ✅ Native ❌ No ❌ No ❌ No
Git worktree support ✅ Auto ❌ No ❌ No ❌ No
Tailscale integration ✅ Native ❌ No ❌ No ❌ No
LAN/mDNS mode ✅ Built-in ❌ No ❌ No ⚠️ Manual Avahi
OS startup service ✅ Built-in ❌ No ❌ No ⚠️ Manual systemd
Free for team use ✅ Yes ⚠️ Limited ✅ Yes ✅ Yes
Configuration required Minimal Minimal Minimal Significant

The verdict: ngrok excels at public tunneling but exposes your dev server to the internet. localtunnel is simpler but lacks HTTPS and features. Caddy is powerful but requires substantial configuration. Portless occupies a unique niche: local-first development with production-like infrastructure, then optional secure sharing when needed.

FAQ

Is portless free to use? Yes, portless is open-source under the Vercel Labs organization. Tailscale integration requires a Tailscale account (free tier available).

Does portless work with Docker↗ Bright Coding Blog? Use portless alias <name> <port> to register static routes for Docker containers. Portless routes traffic to the specified port without managing the process.

Will this conflict with my existing localhost:3000 workflow? No—portless assigns ephemeral ports (4000-4999) to child processes. Your existing ports remain available. Set PORTLESS=0 to bypass entirely.

How does portless handle port 443 permissions? On first run, portless auto-elevates with sudo on macOS/Linux to bind port 443. The OS startup service runs as root. Custom ports (-p 8080) avoid elevation entirely.

Can I use my own domain instead of .localhost? Yes—--tld test creates myapp.test. The proxy syncs /etc/hosts for resolution. Avoid .dev (HSTS-forced HTTPS) and .local (mDNS conflicts).

What happens if the proxy crashes? Run portless prune to kill orphaned dev servers from crashed sessions. portless proxy stop && portless proxy start restarts cleanly.

Is Windows fully supported? Yes, with OS startup via Task Scheduler and certificate trust via certutil. Some LAN mode features depend on Windows mDNS support.

Conclusion

Port numbers are a relic that modern development has outgrown. They're arbitrary, unmemorable, and create friction at every turn—from onboarding new team members to debugging complex service interactions to testing on real devices.

Portless eliminates this friction with a tool that feels like it should have existed years ago. The HTTPS-by-default, HTTP/2-by-default, zero-configuration approach means you're not just getting prettier URLs—you're getting a development environment that more closely mirrors production infrastructure.

The monorepo awareness, git worktree auto-detection, and Tailscale integration show thoughtful attention to real developer workflows, not just demo scenarios. This isn't a tool that solves one problem and creates three new ones.

My recommendation? Install it globally today. Wrap your dev script. Experience the momentary confusion when you instinctively reach for a port number, then the lasting relief when you realize you'll never need to again. Your browser history, your teammates, and your future self will thank you.

Ready to stop memorizing port numbers? Grab portless from github.com/vercel-labs/portless and never look back.


Have you tried portless? What local development pain points would you like to see solved next? Drop your thoughts below.

Commentaires 0

Aucun commentaire pour l'instant. Soyez le premier à réagir !

Laisser un commentaire