What works
An honest rundown of what DemoTape handles, what it handles automatically, and where the edges are today.
Works out of the box
Multi-port apps
Frontend on 3000, API on 8001, auth service on 4000 — DemoTape detects all running ports and routes them through a single public URL. No reverse proxy config needed.
Quick sharing
Run demotape, get a link, share it. Your reviewer sees the live app. You can update code while they're using it.
Session replay
Full visual replay of the reviewer's session — every click, scroll, page transition, and DOM change. Powered by rrweb.
Network request capture
Failed fetch() and XMLHttpRequest calls (4xx, 5xx, network errors) are captured and shown in the session timeline. You see the URL, status code, and timing.
Console recording
All console output (log, info, warn, error, debug) is captured with timestamps. Stack traces from errors are included.
Cookie rewriting
Cookies with Domain=localhost are rewritten to work under the public tunnel URL. Other attributes (Path, Secure, HttpOnly, SameSite) are preserved. Your auth flow works without changes. See the cookie-session and auth-flow examples.
HTTPS
The public URL is always HTTPS. If your local server uses HTTPS (e.g., self-signed certs), DemoTape proxies to it correctly.
Location header rewriting
Redirect responses with Location headers pointing to localhost are rewritten to the public URL. Server-side redirects work as expected. See the location-rewrite example.
WebSocket proxying
WebSocket connections are proxied through the tunnel. Hot module reload (HMR), live updates, and real-time features work. See the realtime example.
Absolute URL rewriting
URLs like http://localhost:3000/api in HTML, JSON, and JavaScript responses are rewritten to the public tunnel URL. Client-side fetch(), XMLHttpRequest, WebSocket, and EventSource calls are intercepted and rewritten at runtime.
Response decompression
Gzip, Deflate, and Brotli responses are transparently decompressed for URL rewriting.
Viewer comments
Reviewers can leave timestamped comments through the overlay. Comments appear in your session replay at the exact moment they were written.
Framework support
DemoTape auto-detects these frameworks and their default ports:
| Framework | Detection |
|---|---|
| Next.js | /_next/ marker, next.config.* |
| Vite | /@vite/client marker, vite.config.* |
| Nuxt | /_nuxt/ marker, nuxt.config.* |
| Create React App | /sockjs-node marker |
| Astro | /__astro marker, astro.config.* |
| SvelteKit | Svelte markers, svelte.config.* |
| Angular | angular.json |
| Storybook | Storybook markers |
| Rails | Gemfile |
| Django | manage.py |
| Laravel | composer.json |
Any HTTP server on a local port works — the framework detection just improves auto-selection. See working examples for Next.js, SvelteKit, Nuxt, and more.
Platform support
| OS | Architecture |
|---|---|
| macOS | Intel (x64), Apple Silicon (arm64) |
| Linux | x64, arm64 |
| Windows | x64, arm64 |
Current limitations
These are things DemoTape doesn't handle yet or by design.
OAuth and fixed redirect URLs
OAuth flows that use a hardcoded redirect URL (e.g., http://localhost:3000/callback) won't work through the tunnel, because the provider redirects back to localhost instead of the public URL. You'll need to update the redirect URL in your OAuth provider settings to match the shared URL.
Cross-origin iframes
Session recording cannot capture content inside cross-origin iframes (e.g., embedded Stripe checkout, third-party widgets). The iframe appears in the replay but its contents are blank.
Event sampling
Mouse movements are sampled at 50ms intervals, scroll events at 150ms, and input events are deduplicated to the latest value. This is sufficient for replay but may miss very fast intermediate states.
Binary responses
URL rewriting only applies to text-like responses (HTML, JSON, JavaScript, XML, SVG). Binary responses (images, fonts, PDFs) are passed through unchanged.
Apps that check domain in code
If your app has server-side logic that checks req.hostname and rejects non-localhost requests, you'll need to adjust that check. The proxy sends x-forwarded-host with the public hostname.