How it works

DemoTape runs a local proxy between your app and a public tunnel. In PR mode, it also detects your git context, looks for an existing pull request, finds or starts your app, and prepares a reviewer-ready handoff before recording the session.

PR mode preflight

When you enable PR mode, the CLI tries to make the flow feel obvious on first run:

  • Git context detection — checks whether you are in a git repo and identifies the current branch and remote
  • PR lookup — if GitHub CLI is available and the remote is GitHub, DemoTape looks for an existing PR for the current branch
  • Startup detection — looks for common scripts like npm run dev, pnpm dev, yarn dev, or bun run dev
  • Startup caching — saves the startup command per project directory so later runs skip the prompt

Port detection

When you run demotape without an explicit port, the CLI probes common dev ports. PR mode uses the same detection path.

3000, 3001, 5173, 5174, 8080, 8081, 8000, 4200, 4000, 5000, 6006 ...

Each port is scored based on:

  • TCP reachability — is something listening?
  • HTTP response — does it return HTML?
  • Framework markers — Next.js (/_next/), Vite (/@vite/client), Nuxt, CRA, Astro, Svelte, etc.
  • Repo hints — config files like next.config.js, vite.config.ts, port numbers in package.json scripts

If there's a clear winner, it's selected automatically. If multiple ports score similarly, you're prompted to choose which service reviewers should use.

Multi-port routing

If your app runs a frontend on one port and an API on another, DemoTape detects both and routes them through a single public URL. See the react-vite-node and docker-compose examples.

The main port (your frontend) is served at the root URL. Secondary ports like APIs or backend services are routed through the same public URL automatically.

All localhost URLs in your app's responses are automatically rewritten to the public URL. This includes:

  • URLs in HTML, JSON, and JavaScript responses
  • fetch() and XMLHttpRequest calls (intercepted at runtime)
  • WebSocket and EventSource connections

What gets rewritten

Absolute URLs

Any http://localhost:PORT or http://127.0.0.1:PORT URL found in a text response body is rewritten to the public tunnel URL. WebSocket schemes (ws://) are rewritten to wss://.

Set-Cookie headers

Cookie Domain attributes pointing to localhost are stripped so the cookie works under the public domain. All other cookie attributes (Path, Secure, HttpOnly, SameSite) are preserved.

Location headers

Redirect Location headers pointing to localhost are rewritten to the public URL. Path and query strings are preserved.

All rewriting can be selectively disabled with CLI flags: --no-rewrite-cookies, --no-rewrite-absolute-urls, and --no-rewrite-location. See demotape --help for details.

Response decompression

Gzip, Deflate, and Brotli responses are decompressed before rewriting, then served uncompressed. This allows URL rewriting in compressed responses.

HTTPS

The public URL is always HTTPS. If your local dev server runs on HTTPS (e.g., with a self-signed cert), DemoTape proxies to it correctly. The tunnel handles TLS termination for the public side.

Session recording

DemoTape injects a small script before </body> in every HTML response. This script:

  • Records the DOM using rrweb — captures a full visual replay of the page
  • Captures console outputlog, info, warn, error, debug, plus stack traces
  • Captures network requestsfetch() and XMLHttpRequest calls with URL, status code, and timing
  • Tracks page navigationpushState, replaceState, popstate, hash changes, link clicks, form submissions, plus a 500ms polling fallback

Recording happens in the background with no visible impact on your app's performance.

Viewer overlay

The injected script also renders a small overlay UI for reviewers with:

  • A live indicator showing the session is being recorded
  • A countdown timer for the remaining share duration
  • A comment button — viewers can leave timestamped notes with their name
  • An abuse report button

Tunnel

DemoTape uses an encrypted tunnel to expose your local app to the internet. Authentication is verified on both sides — when you start sharing and when a viewer connects — before any traffic is forwarded.

Your app never needs to be deployed or publicly accessible — everything runs through the tunnel.