Tutorial
30 min read
33 views

Modern Developer Workflows & Advanced Infrastructure Integrations

Discover why standard HTTP proxies drop Next.js 15+ Server Actions and Vite WebSocket HMR connections, and how to maintain sub-millisecond hot-reloading over remote tunnels.

IT
InstaTunnel Team
Published by the InstaTunnel team | Editorial policy
Modern Developer Workflows & Advanced Infrastructure Integrations

Quick answer

Tunneling Vite 6 & Next.js Server Actions: Fix HMR: webhook testing answer

For local webhook testing, run your app locally, expose it with a public HTTPS tunnel, and paste the stable callback URL into the provider dashboard.

How do I test webhooks on localhost?

Start your local server, open a public HTTPS tunnel to that port, configure the provider webhook URL, and inspect events in your local logs.

Why does a stable webhook URL matter?

Stable URLs prevent provider dashboards from needing manual callback updates every time you restart a tunnel.

Modern full-stack architecture balances remote cloud runtimes with instantaneous feedback loops on developer machines. Achieving rapid iteration—whether in Next.js frontend applications or distributed microservices—requires routing live traffic between cloud boundaries and local developer environments.

This guide provides technical strategies for proxying local dev servers, forwarding telemetry spans, routing multi-tenant webhooks, and attaching cloud previews to ephemeral database instances.

1. Hot-Reload Across Borders: Tunneling Vite 6 and Next.js Server Actions over HMR-Aware Proxies

Traditional HTTP reverse proxies (like default Nginx or basic SSH tunnels) break stateful web applications because they evaluate traffic statelessly. Modern development tools like Vite 6 and Next.js 15+ rely on bidirectional, low-latency connections to sync client states, compile server-side modules, and process Server Actions.

+-------------------------------------------------------------------------------+
|                             PUBLIC CLOUD TUNNEL                               |
|                     https://dev-tenant.app.example.com                        |
+-------------------------------------------------------------------------------+
                                       |
                                       | TLS Terminal & WSS Upgrade
                                       v
+-------------------------------------------------------------------------------+
|                       HMR-AWARE REVERSE TUNNEL PROXY                          |
|  - Host Header Rewriting (Origin Verification)                                |
|  - HTTP/1.1 Upgrade Handling (101 Switching Protocols)                        |
|  - Keep-Alive Pings & Dynamic Client Port Mapping                             |
+-------------------------------------------------------------------------------+
                                       |
            +--------------------------+--------------------------+
            | (gRPC / Multiplexed TCP)                            | (HTTP/2 Server Actions)
            v                                                     v
+-----------------------+                             +-----------------------+
|  Vite Dev Server      |                             | Next.js App Router    |
|  (Localhost:5173)     |                             | (Localhost:3000)      |
|                       |                             |                       |
| - HMR WebSocket Path: |                             | - CSRF Allowed Host:  |
|   /_vite_hmr          |                             |   dev-tenant.example  |
+-----------------------+                             +-----------------------+

Why Traditional Proxies Break HMR and Server Actions

  1. WebSocket Framing Dropped: Standard HTTP proxies treat incoming requests as short-lived request/response pairs. When Vite attempts an HTTP/1.1 Upgrade: websocket exchange to establish its Hot Module Replacement (HMR) channel, stateless proxies fail to maintain the persistent TCP socket. This forces Vite into continuous fallback reloads.
  2. Next.js 15+ Host Header & CSRF Checks: Next.js Server Actions execute via HTTP POST requests using hidden endpoint IDs. To prevent Cross-Site Request Forgery (CSRF) attacks, Next.js inspects the incoming Origin and Host headers. If a tunnel rewrites Host: localhost:3000 without matching the public tunnel URL, Next.js blocks the invocation.
  3. Mismatched WebSocket Ports: Vite injects a client-side runtime script into the browser that attempts to open a WebSocket connection back to the HMR port. If the browser connects to [https://app.example.com](https://app.example.com) (port 443) but Vite tells the client to open a socket to ws://localhost:5173, cross-origin browser security blocks the connection.

Preservation Architecture for Vite 6

To maintain sub-millisecond HMR over public tunnels (such as Cloudflare Tunnels, frp, or ngrok), configure Vite’s internal WebSocket server to decouple its internal listening port from its public client-facing port.

// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    host: '0.0.0.0',
    port: 5173,
    strictPort: true,
    hmr: {
      // The public hostname exposed by your tunnel
      host: 'vite-tunnel.dev.example.com',
      // Force client to use HTTPS/WSS port instead of internal 5173
      clientPort: 443,
      protocol: 'wss',
      path: '/_vite_hmr',
    },
    // Prevent HMR connection dropouts due to host mismatch
    cors: true,
  },
});

Next.js 15+ Server Action Tunnel Configuration

For Next.js 15, update next.config.ts to allow invocation of Server Actions through public dev tunnels.

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  experimental: {
    serverActions: {
      // Whitelist tunnel origins to pass CSRF origin checks
      allowedOrigins: [
        'next-tunnel.dev.example.com',
        '*.tunnel.dev.example.com',
      ],
    },
  },
};

export default nextConfig;

2. Routing Distributed Traces from Cloud Staging to Your Local Jaeger UI

When debugging microservices in staging environments, tracing an asynchronous request through multiple services is vital. However, sending telemetry data to a central telemetry storage backend creates noise and makes isolation difficult.

Using OpenTelemetry (OTLP) collectors, backend engineers can proxy telemetry streams down to a local Jaeger UI instance without polluting remote storage.

+-----------------------------------------------------------------------------------+
|                              CLOUD STAGING CLUSTER                                |
|                                                                                   |
|  +---------------------+      +---------------------+      +-------------------+  |
|  |  Auth Microservice  | ---> | Order Microservice  | ---> | Payment Service   |  |
|  +---------------------+      +---------------------+      +-------------------+  |
|                                          |                                        |
|                                          | OTLP / gRPC (Port 4317)                |
|                                          v                                        |
|                      +---------------------------------------+                    |
|                      |   Cloud Staging OTLP Collector     |                    |
|                      +---------------------------------------+                    |
+------------------------------------------|----------------------------------------+
                                           |
                                           | Routing via Tailscale/SSH Reverse Tunnel
                                           v
+-----------------------------------------------------------------------------------+
|                             LOCAL DEVELOPER WORKSTATION                           |
|                                                                                   |
|  +-----------------------------------------------------------------------------+  |
|  | Local OpenTelemetry Collector                                               |  |
|  | - Receives: OTLP / gRPC on 127.0.0.1:4317                                    |  |
|  | - Filter: attributes["x-developer-id"] == "dev-alice"                       |  |
|  +-----------------------------------------------------------------------------+  |
|                                          |                                        |
|                                          v OTLP / gRPC (Insecure)                 |
|  +-----------------------------------------------------------------------------+  |
|  | Local Jaeger UI Container                                                   |  |
|  | - Ingestion Port: 4317                                                      |  |
|  | - Web UI: http://localhost:16686                                          |  |
|  +-----------------------------------------------------------------------------+  |
+-----------------------------------------------------------------------------------+

The Trace Hijacking Problem

Sending trace spans directly to cloud stores like Datadog, Honeycomb, or a shared AWS OpenSearch instance presents two distinct issues:

  • Index Contamination: Debugging logs and malformed payloads clutter production and staging dashboards.
  • Latency & Rate Limits: Exporting high-volume spans over internet bounds increases cloud ingestion costs and risks exceeding vendor rate limits.

Architecture: Selective OTLP Reverse Routing

Engineers can selectively isolate and forward traces using contextual headers (such as x-developer-id: dev-alice) processed by an OpenTelemetry Collector routing connector.

Step 1: Staging OpenTelemetry Collector Configuration

Configure the staging OTLP collector to evaluate incoming trace attributes and route tagged spans through a reverse gRPC pipeline back to a local machine.

# otel-collector-staging.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  batch:
    timeout: 1s
    send_batch_size: 256

exporters:
  # Standard cloud storage destination
  otlp/staging_backend:
    endpoint: "tempo-staging.internal.net:4317"
    tls:
      insecure: true

  # Reverse tunnel destination pointing to the developer machine
  otlp/dev_alice_local:
    endpoint: "host.docker.internal:14317"
    tls:
      insecure: true

connector:
  routing:
    default_exporters: [otlp/staging_backend]
    from_attribute: "x-developer-id"
    table:
      - value: "dev-alice"
        exporters: [otlp/dev_alice_local]

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [routing]

Step 2: Establish the Reverse Tunnel and Local Ingestion

Establish a reverse SSH tunnel from your developer workstation to the staging gateway, mapping port 14317 on the staging node to port 4317 locally.

# Establish reverse SSH gRPC tunnel
ssh -R 14317:localhost:4317 developer@staging-gateway.internal.net -N

Run a lightweight local Jaeger container (configured with native OTLP ingestion):

docker run -d --name jaeger-local \
  -e COLLECTOR_OTLP_ENABLED=true \
  -p 16686:16686 \
  -p 4317:4317 \
  jaegertracing/all-in-one:latest

When an HTTP request containing the header x-developer-id: dev-alice enters the staging environment, its entire trace tree branches off and streams directly to your local Jaeger UI at http://localhost:16686.

3. Testing Local Stripe Connect Webhooks with Dynamic Multi-Tenant Subdomains

Testing multi-tenant SaaS platforms requires isolating events for distinct organizations. When building multi-tenant architectures using Stripe Connect (where a platform manages multiple connected accounts), debugging webhooks through a single endpoint hides dynamic tenant resolution issues.

                                  STRIPE CLOUD
                                        |
                 +----------------------+----------------------+
                 | Connect Webhook Event                       |
                 | Account: acct_tenant_A                      | Account: acct_tenant_B
                 v                                             v
  +------------------------------+             +------------------------------+
  | https://tenant-a.tunnel.dev  |             | https://tenant-b.tunnel.dev  |
  +------------------------------+             +------------------------------+
                 |                                             |
                 +----------------------+----------------------+
                                        |
                                        v
                        +-------------------------------+
                        | WILDCARD INGRESS PROXY        |
                        | *.tunnel.dev -> localhost:8080|
                        +-------------------------------+
                                        |
                                        v
                        +-------------------------------+
                        | LOCAL MULTI-TENANT ROUTER     |
                        | Extracts Host header          |
                        | Maps Tenant A vs Tenant B     |
                        +-------------------------------+

Multi-Tenant Webhook Routing Setup

Using a wildcard reverse proxy (such as frp or caddy), point *.tunnel.yourdomain.dev to your local application router.

1. Caddy Reverse Proxy Configuration (Local Ingress Router)

# Caddyfile - Multi-tenant Wildcard Local Proxy
*.tunnel.dev.localhost {
    tls internal
    
    @tenant_a header_regexp host Host ^([a-zA-Z0-9-]+)\.tunnel\.dev\.localhost$
    
    handle {
        reverse_proxy localhost:3000 {
            header_up Host {http.request.host}
            header_up X-Forwarded-Host {http.request.host}
        }
    }
}

2. Advanced Multi-Tenant Webhook Forwarder script

Instead of running individual stripe listen commands per tenant, use this Node.js multi-tenant CLI proxy script. It listens to Stripe events and dynamically forwards them to the corresponding local subdomain based on the event’s account ID.

// scripts/stripe-multitenant-proxy.ts
import Stripe from 'stripe';
import axios from 'axios';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2026-01-28',
});

// Map Stripe Connected Account IDs to Local Subdomains
const TENANT_MAPPING: Record<string, string> = {
  'acct_1N01AAA000000000': 'acme',
  'acct_1N02BBB000000000': 'globex',
};

async function handleWebhookEvent(event: Stripe.Event) {
  const connectedAccountId = event.account;
  const tenantSlug = connectedAccountId ? TENANT_MAPPING[connectedAccountId] : 'app';

  if (!tenantSlug) {
    console.warn(`[WARN] Unmapped account ID: ${connectedAccountId}`);
    return;
  }

  const targetUrl = `http://${tenantSlug}.tunnel.dev.localhost:3000/api/webhooks/stripe`;

  try {
    console.log(`[FORWARD] Event ${event.type} -> ${targetUrl}`);
    await axios.post(targetUrl, event, {
      headers: {
        'Stripe-Signature': 'simulated_local_sig',
        'Content-Type': 'application/json',
        'Host': `${tenantSlug}.tunnel.dev.localhost`,
      },
    });
  } catch (err: any) {
    console.error(`[ERROR] Failed to deliver webhook: ${err.message}`);
  }
}

4. Exposing Local Supabase and Postgres Instances to Cloud Vercel/Netlify Preview Deployments

Feature branch workflows automatically spin up ephemeral frontend preview deployments on platforms like Vercel or Netlify. However, providing isolated backend database state for every PR preview can be difficult to manage.

Using Layer 4 TCP reverse tunneling (such as ngrok tcp, bore, or frp), you can safely expose your local PostgreSQL or Dockerized Supabase instance directly to cloud preview deployments.

+-----------------------------------------------------------------------------+
|                        VERCEL / NETLIFY PREVIEW PR #42                      |
|                     https://app-git-feature-pr42.vercel.app                 |
|                                                                             |
|   DATABASE_URL = postgresql://postgres:pass@tcp.tunnel.dev:19432/postgres   |
+-----------------------------------------------------------------------------+
                                       |
                                       | Encrypted TCP (Port 19432)
                                       v
+-----------------------------------------------------------------------------+
|                            TCP REVERSE PROXY                                |
|  - Pass-through TLS / Pure L4 TCP Pipeline                                  |
+-----------------------------------------------------------------------------+
                                       |
                                       | Secure Reverse Tunnel
                                       v
+-----------------------------------------------------------------------------+
|                        LOCAL DEVELOPER WORKSTATION                          |
|                                                                             |
|  +-----------------------------------------------------------------------+  |
|  | Local Postgres Firewall / PgBouncer Proxy                             |  |
|  | - Listens on 127.0.0.1:5432                                           |  |
|  | - Restricts SSL / Validates Session Limits                            |  |
|  +-----------------------------------------------------------------------+  |
|                                      |                                      |
|                                      v                                      |
|  +-----------------------------------------------------------------------+  |
|  | Dockerized Supabase Instance                                          |  |
|  | - PostgreSQL Engine (Port 5432)                                       |  |
|  | - PostgREST API (Port 54321)                                          |  |
|  | - GoTrue Auth System (Port 9999)                                      |  |
|  +-----------------------------------------------------------------------+  |
+-----------------------------------------------------------------------------+

Protocol Comparison: Layer 4 (TCP) vs Layer 7 (HTTP)

  • HTTP Proxies (Layer 7): Unsuitable for database engines. They expect plain HTTP/WebSockets, break wire protocols like Postgres StartupMessage, and drop non-HTTP connections.
  • TCP Tunnels (Layer 4): Forward raw TCP packets at the transport layer, keeping wire protocols intact.

Exposing Supabase Local Stack via FRP (Fast Reverse Proxy)

To tunnel your local Supabase stack, set up frp to expose local PostgreSQL (port 5432) and PostgREST (port 54321) over L4 TCP.

Server Configuration (frps.ini on Remote Gateway)

[common]
bind_port = 7000
vhost_extra_db_port = 19432

Client Configuration (frpc.ini on Developer Workstation)

[common]
server_addr = gateway.yourdomain.dev
server_port = 7000

[supabase-postgres-tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 54322
remote_port = 19432

[supabase-postgrest-http]
type = http
local_ip = 127.0.0.1
local_port = 54321
custom_domains = api-pr42.tunnel.yourdomain.dev

Automation via GitHub Actions & Vercel API

Automate your pull request workflows by dynamically setting environment variables on ephemeral preview deployments using a GitHub Action.

# .github/workflows/preview-environment.yml
name: Preview Environment Database Injector

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  attach-local-db:
    runs-on: ubuntu-latest
    steps:
      - name: Inject Dynamic DB Tunnel URL to Vercel Preview
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
          VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
        run: |
          PR_NUM=${{ github.event.number }}
          # Construct dynamic database URL pointing to the developer's assigned TCP tunnel port
          TUNNEL_DB_URL="postgresql://postgres:postgres@gateway.yourdomain.dev:19432/postgres?sslmode=require"

          # Assign database connection string to Vercel Preview deployment environment
          curl -X POST "https://api.vercel.com/v10/projects/${VERCEL_PROJECT_ID}/env" \
            -H "Authorization: Bearer ${VERCEL_TOKEN}" \
            -H "Content-Type: application/json" \
            -d '{
              "key": "DATABASE_URL",
              "value": "'"${TUNNEL_DB_URL}"'",
              "type": "encrypted",
              "target": ["preview"],
              "gitBranch": "${{ github.head_ref }}"
            }'

Architectural Summary: Reverse Proxies & Integration Patterns

Pattern Protocol Layer Best Used For Key Edge Cases & Caveats
HMR Proxying Layer 7 (HTTP/WSS) Vite 6 / Next.js 15+ local hot reloading across remote tunnels Requires allowedOrigins whitelist in Next.js and explicitly overriding clientPort in Vite.
OTLP Trace Routing Layer 7 (gRPC / HTTP/2) Isolating microservice trace spans locally via OpenTelemetry Requires header-propagation middleware (x-developer-id) across service boundaries.
Dynamic Subdomains Layer 7 (HTTP/SNI) Multi-tenant SaaS webhook & checkout route simulation Requires wildcard SSL certificates (*.domain.dev) or local CA authority installation.
L4 Database Reverse Tunnels Layer 4 (Pure TCP) Linking Vercel/Netlify cloud preview environments to local databases High connection concurrency can exhaust local database pools; use PgBouncer locally.

Continue from this article into the most relevant product guides and workflows.

Related Topics

#vite 6 hmr#nextjs server actions#websocket proxy#hmr over proxy#hot module replacement#vite tunnel#nextjs 15 tunneling#localhost tunnel#reverse proxy hmr#websocket drops proxy#nextjs server actions debug#local developer environment#vite dev server proxy#hmr websocket reconnect#server action proxy#tunnel localhost#local dev tunneling#remote hmr debugging#web sockets over reverse proxy#sub millisecond hmr#vite HMR configuration#nextjs server actions deployment#developer workflow optimization#modern frontend frameworks#local dev environment tools#web development proxies#tunneling tools for developers#nextjs preview local database#remote webhook debugging#local dev setup#web development workflow#fullstack local debugging#software development tools#devops local environment#local proxy configuration#developer networking tools#frontend developer environment#backend developer environment#fullstack developer tools#real time hot reload#continuous development workflow#local dev server port forwarding#local port tunnel#secure localhost exposure#web development productivity#server action execution#websocket connection handling#developer tooling 2026#remote development server#web dev tunneling solutions

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles