Universal Bridge is a framework- and runtime-agnostic protocol for mounting a same-origin control plane for local companion processes.
Frameworks and runtimes expose different dev-server integration points—plugins, rewrites, middleware, and server APIs. Without a shared layer, each tool needs a separate integration for every host.
Universal Bridge mounts that shared layer at /__universal/*, providing HTTP health/state/control routes, WebSocket events, an optional runtime WebSocket gateway, and API proxying. Framework-specific adapters translate it for each host environment.
Use Universal Bridge when you're building a developer tool—such as an overlay, panel, CLI companion, or local service—that needs to work across frameworks and server runtimes.
If you're using a tool that already includes Universal Bridge integration, configure that tool instead; you usually do not need to install Universal Bridge directly.
- Installation
- What it provides
- Use cases
- Quick start (Vite)
- Core concepts
- Integration and runtime surfaces
- Public API reference
- Configuration
- Protocol summary
- Usage examples
- Architecture overview
- Design caveats
- Compatibility
- Additional docs
npm i universal-bridgepnpm add universal-bridgeyarn add universal-bridgebun add universal-bridge- same-origin bridge endpoints under
/__universal/* - runtime lifecycle control (
start/restartrequirecommand;stopis idempotent) - runtime status and capability reporting for UIs/automation
- websocket event stream with protocol versioning
- optional same-origin runtime WebSocket gateway
/api/*passthrough proxy from host origin to runtime origin- framework/server/build-tool adapter surfaces plus a preset API for tool packages
Non-goals:
- no first-party UI or hosted service
- no requirement that end users import UniversalBridge directly when a tool already wraps it
- devtool overlays/sidebars/panels that should run across frameworks
- local CLIs/scripts that need runtime status and control via the same bridge
- internal developer platforms that expose same-origin local control APIs
// vite.config.ts
import { createUniversalVitePlugin } from "universal-bridge/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
createUniversalVitePlugin({
command: "node",
args: ["./scripts/dev-runtime.js"],
}),
],
});- Bridge path prefix: defaults to
/__universal. - Runtime helper: optional process manager for your tool runtime command.
- Protocol version: current bridge protocol is
2(universal.v2+jsonfor negotiated event sockets). - Preset API: recommended integration API for tool authors so users configure one entry point (
mytool().vite(),mytool().next(...), etc.).
| Surface | Import path |
|---|---|
| Vite-based dev servers (React, Vue, Solid, SvelteKit, Remix, TanStack Start, Vinext, etc.) | universal-bridge/vite |
| Next.js | universal-bridge/next |
| Nuxt | universal-bridge/nuxt |
| Astro | universal-bridge/astro |
| Angular CLI proxy flow | universal-bridge/angular/cli |
Bun.serve |
universal-bridge/bun |
| Node middleware + HTTP server | universal-bridge/node |
| Express middleware | universal-bridge/express |
| Fastify | universal-bridge/fastify |
| Hono on Node server | universal-bridge/hono |
| webpack-dev-server | universal-bridge/webpack |
| Rsbuild dev server | universal-bridge/rsbuild |
| Rspack dev server | universal-bridge/rspack |
| Docker Compose runtime helper for adapters that manage a process | universal-bridge/docker |
| API | Import |
|---|---|
createUniversalPreset |
universal-bridge/preset |
createUniversalClient |
universal-bridge/client |
createBridgeRuntimeStore |
universal-bridge/client |
createClientRuntimeContext |
universal-bridge/client-runtime |
createDockerComposeRuntime |
universal-bridge/docker |
attachUniversalToExpress |
universal-bridge/express |
startStandaloneUniversalBridgeServer |
universal-bridge |
createUniversalBridge / UniversalBridge |
universal-bridge |
The root package resolves to a browser-only client surface in browser builds. Import server APIs from universal-bridge in server code, and import browser APIs explicitly from universal-bridge/client when that makes the boundary clearer.
createUniversal*: build a plugin/module/integration instance.withUniversal*: wrap and return config.attachUniversalTo*: attach to an existing server.startUniversal*: start helper/standalone utilities.
For expanded API coverage (including lifecycle helpers, runtime-context utilities, and adapter-specific helper exports), see INTEGRATION_GUIDE.md.
Most adapter APIs accept shared bridge/runtime options.
| Option | Type | Default | Notes |
|---|---|---|---|
bridgePathPrefix |
string |
"/__universal" |
Normalized to stay rooted under /__universal. |
autoStart |
boolean |
true |
Auto-start runtime on state, proxy, events, and runtime WebSocket gateway paths. |
command |
string |
none | Required for managed runtime start/restart. |
args |
string[] |
[] |
Runtime command args. |
cwd |
string |
process.cwd() |
Runtime working directory. |
env |
Record<string, string | undefined> |
none | Extra runtime environment variables. |
host |
string |
"127.0.0.1" |
Runtime host binding. |
healthPath |
string |
"/api/version" |
Runtime health probe endpoint. |
startTimeoutMs |
number |
15000 |
Runtime startup timeout. |
runtimePortEnvVar |
string |
"UNIVERSAL_RUNTIME_PORT" |
Env var populated with selected runtime port. |
fallbackCommand |
string |
"universal dev" |
Returned in some runtime-control error payloads. |
eventHeartbeatIntervalMs |
number |
30000 |
WS heartbeat for stale client cleanup. |
runtimeWebSocketGateway |
{ path: string } |
none | Opt-in gateway at /runtime/ws to one managed runtime WebSocket path. |
instance |
{ id: string; label?: string } |
none | Optional instance metadata in bridge state/health. |
additionalRewriteSources |
string[] |
[] |
Extra path prefixes proxied directly to the runtime by adapters that support direct passthrough. |
identity(required):{ packageName: string; variant?: string }composition:"registry" | "local"instanceId: stable suffix for multiple preset instancesunsafeOverrides: advanced adapter identity overridesclient.entries: self-initializing browser modules loaded in development by the Vite, Next.js, Nuxt, and Astro preset adapters
With prefix /__universal (or /__universal/<namespaceId> for presets):
GET /healthGET /stateGET /runtime/statusPOST /runtime/startPOST /runtime/restartPOST /runtime/stopWS /eventsWS /runtime/ws(whenruntimeWebSocketGatewayis configured)ANY /api/*proxied to runtime/api/*
Behavior highlights:
- Route matching is query-safe.
GET /state,WS /events,WS /runtime/ws, and proxy requests may auto-start runtime whenautoStartis enabled.POST /runtime/stopis idempotent and disables auto-start, including gateway-triggered auto-start, until start/restart.WS /eventscarries bridge protocol events only.WS /runtime/wsforwards one configured runtime WebSocket path without parsing app messages; runtime close/error affects only the paired gateway socket.- Bridge-generated errors use
{ success: false, message, error }envelope. - Proxied
/api/*responses pass through status/headers/body (including non-2xx).
For normative protocol details, see PROTOCOL.md.
For adapter-specific snippets (Next keying, Bun/Node/Express/Fastify/Hono servers, Docker Compose runtime helpers, webpack/Rsbuild/Rspack, Astro/Nuxt, Angular CLI, and standalone bridge usage), see INTEGRATION_GUIDE.md.
import { createUniversalPreset } from "universal-bridge/preset";
export function myTool() {
return createUniversalPreset({
identity: { packageName: "mytool" },
client: {
entries: [{ module: "mytool/overlay" }],
},
command: "mytool",
args: ["dev"],
fallbackCommand: "mytool dev",
});
}Entries load once per page in development. Each can read its derived namespace
and bridge prefix with resolveClientRuntimeContext("mytool/overlay"), then
mount its own UI. See the integration guide for a complete example and
multiple-instance constraints.
// next.config.ts
import { myTool } from "mytool";
export default myTool().next({ reactStrictMode: true });// vite.config.ts
import { myTool } from "mytool";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [myTool().vite()],
});import { createUniversalClient } from "universal-bridge/client";
const client = createUniversalClient({ namespaceId: "mytool" });
const state = await client.getState();
const runtimeSocket = new WebSocket(
client.getRuntimeWebSocketUrl({ query: { session: "local" } }),
["mytool.v1"],
);
const unsubscribe = client.subscribeEvents((event) => {
if (event.type === "bridge-state") {
console.log(event.state.runtime.phase, event.state.revision);
} else {
console.error(event.error);
}
});
unsubscribe();High-level internals:
src/bridge/*: HTTP/WS routing, runtime control routes, proxying, event fanout.src/runtime/runtime-helper.ts: command lifecycle and health probing.src/adapters/*: framework/server/build-tool integration points.src/client/*: typed browser/Node client helpers.src/preset.ts+src/preset-registry.ts: preset composition and registry behavior.
For implementation details, see ARCHITECTURE.md.
- UniversalBridge does not ship a first-party UI.
- Runtime start/restart are unavailable when
commandis not configured. - The runtime WebSocket gateway is opt-in, proxies only its configured runtime path, and forwards text/binary frames opaquely.
/eventsis bridge-only; runtime/app WebSocket traffic belongs on/runtime/ws.- The bridge does not reconnect runtime WebSockets, replay application traffic, or interpret application messages.
bridgePathPrefixis normalized under/__universal.- Package is ESM-only (
"type": "module").
- Runtime targets: Node.js and Bun.
- Package format: ESM-only (
"type": "module").
INTEGRATION_GUIDE.md: end-to-end guide for tool authors, adapter cookbook, and expanded API coverage.PROTOCOL.md: normative bridge contract.ARCHITECTURE.md: internal architecture.EXAMPLE.md: Universal Overlay setup and verification.
Docs checks:
bun run docs:lint
bun run docs:check
