Braid

Many apps. One page. One accessibility tree.

The gateway· The realm· The console
The problem, before any jargon

Four teams work on one website.

Billing owns the invoices page. Payments owns a widget. Everybody wants to deploy on their own schedule, without a release train.

That is the entire requirement. Every architecture below is an attempt at it, and each one breaks somewhere specific.

Where the obvious answers break

Four approaches. Four different failures.

ApproachWhat breaksWhy
One big app one deploy for everyone Their broken build is your broken build. Upgrading React becomes a company-wide project.
iframes the box never opens Cannot lay out with the page. A modal inside cannot cover the page. Screen readers see separate documents. Each reloads its own framework.
Module Federation one global scope Two copies of React fight. One team's polyfill patches a prototype and breaks another team's code — on a Tuesday, with no error pointing at the cause.
Web components one registry, one realm customElements.define is global and one-shot per tag name, so two versions of the same widget cannot coexist in a document. And the code still runs in the page's single JavaScript context.
The scorecard

Separate repos are not the point. These four are.

No fate-sharing

One team's bad deploy, slow endpoint, or uncaught exception does not take the page down with it. The blast radius stops at their box.

Independent dependency graphs

Billing upgrades React when Billing is ready. There is no shared version to negotiate and no company-wide migration to schedule.

Loose coupling

No build-time contract between teams. The integration is a name and a URL, resolved at runtime — not an import resolved at compile time.

Independent cadence

Ship on your own schedule. No release train, no coordinated cutover, no waiting for the slowest team in the group.

These are properties of the running system, not of the repository layout. Four repos that must deploy together are still one application — a distributed monolith wearing a micro-frontend's clothes.

The idea the architecture follows from

Split where the code runs
from where the DOM lives.

Code runs here

Each app's JavaScript executes in its own hidden, isolated JavaScript context. Own window, own globals, own module registry.

DOM lives here

Its rendered HTML sits in the main page, in normal layout flow, in one accessibility tree.

Concretely: <fragment-slot> owns a shadow root in the host page, and inside the realm the fragment's document is a facade over it. Members that touch the DOM are virtualized onto that shadow root; the rest pass through to the realm's own document.

Six terms to know

Fragment

One independently deployed app. An ordinary web app on an ordinary server — usually containing no Braid code at all.

Host / shell

The app that owns the page: layout, navigation, and the places fragments appear.

Slot

<fragment-slot> — the element in the host's markup that says a fragment goes here.

Realm

The isolated JS context a fragment runs in. In practice: a hidden, same-origin <iframe>.

Shadow root

Where the fragment's DOM actually lands, inside the slot. Styles do not leak either way.

Piercing

The gateway writing a fragment's HTML into the page server-side, before the browser sees it.

The part that makes people squint

Iframes, supercharged.

Normal iframeBraid realm
Where the fragment's JS runsin the iframein the iframe
Where the fragment's DOM appearsin the iframein the host page
Participates in page layoutnoyes
Separate accessibility treeyesno — one tree

The iframe is used purely as a second JavaScript world, and its own document is nearly empty. A Worker has no DOM and cannot run synchronously with the page, so there is no third option.

What the fragment's document actually resolves to

One call, three places.

1 · Host page DOM

<fragment-slot name="billing">
  #shadow-root
    <braid-document>
      <button id="pay">Pay</button>

2 · The document facade

querySelector  virtualized
   → the shadow root

cookie         native
   → the realm's own doc

3 · Fragment JS, in the realm

// billing app, unmodified
document
  .querySelector('#pay')
  .addEventListener(
    'click', pay);

The app never learns any of this happened. Members that touch the DOM are virtualized onto the slot's shadow root; the rest pass through native to the realm's own document. Anything unaudited passes through too, and logs a diagnostic in development rather than failing silently.

How a fragment gets started

Two adapters ship today.

compat — for an application

The default, and the path most teams take. An app you already have — Angular, React, whatever it is — composed with zero code changes. Configuration only.

Inside its realm it gets a convincing document, window, location, and history, all wired to its shadow root and its route.

custom-element — for a component

Contract mode: no document facade, no window patches, no emulation of anything. The element is created inside the realm, so it upgrades against that realm's own definition, then moves into the host's DOM — adoption keeps its class and behaviour.

It needs no HTML at all. A widget that ships only a script is a complete fragment.

Why the second one exists at all: custom element registries are per window. Because the element is defined and created inside its own realm, two fragments can register the same tag name — or different majors of the same component library — and never collide. That is the web-components row from earlier, answered. It also gets a real props and events channel instead of an emulated one.

First paint without a waterfall

The server writes the fragment's HTML in before the browser sees it.

Without piercing

HTML arrives → JavaScript boots → slot fetches the fragment → fragment appears. The user watches an empty box.

With piercing

The gateway fetches the host's HTML and the fragment's at the same time, splicing the second into the first as it streams past.

The markup is wrapped in a declarative shadow root — HTML that tells the browser "make this a shadow root as you parse it". When the slot's JavaScript starts, it finds the DOM already correct and adopts it rather than fetching anything.

This is not an SSR-only feature. A server-rendered fragment arrives already painted. A SPA fragment's shell arrives in the same response and boots in the realm — no separate document round trip either way. The requirement sits on the host, which must render server-side so there is a stream to splice into; no fragment needs SSR at all.

curl the page and it is right there.

Two guarantees, not two intentions

Born inert, and host purity.

Born inert

Fragment markup lands in the host's DOM — so if it could execute, it would execute in the host's world. Before it arrives: every <script> is retyped inert, inline handlers stripped, meta refresh defanged.

Scripts are re-activated inside the realm, where they were always meant to run.

Host purity

Braid never modifies globals or prototypes on the host page. Even compat's heavy emulation is confined to the fragment's own realm.

There is a test suite whose entire job is to fail if Node.prototype or history.pushState is touched on the host.

The alternative is the failure everyone has lived: a library patched a global, and three months later an unrelated feature broke with no trail leading back.

One navigation to /billing/invoices · server side

Before any JavaScript runs.

1
The browser asks for the page.
GET /billing/invoices — the gateway is middleware in front of the host, so it sees this first. It checks the registry: does any fragment declare it appears on this URL?
2
It fetches two things at once.
The host app and billing's server, in parallel. Nothing is serialized behind anything else. The fragment is asked for the page's own path — a fragment that renders a screen wants to know which screen.
3
It splices them together as they stream.
Watching for <fragment-slot name="billing">, it injects billing's HTML as a declarative shadow root — after defusing it.

The browser receives one HTML response containing both apps' markup, and paints it.

One navigation to /billing/invoices · client side

Then a normal app, making normal requests.

4
The slot wakes up and adopts.
It finds content already pierced in — no fetch, no re-render, no flash. (Not pierced, e.g. a client-side navigation? It fetches /__braid/doc/… instead.)
5
The realm boots.
GET /__braid/realm/billing/billing/invoices — a nearly empty stub carrying three things: protocol version, adapter name, and a <base> pointing into billing's asset namespace.
6
The adapter takes over.
Installs the emulation and replaceStates the realm's URL so the router sees its own route. Only then are the inert scripts run — until the facade exists there is no correct document for them to see.
7
The fragment asks for its own things.
Because of that <base>: /__braid/frag/billing/main.js. The gateway strips the prefix and forwards to billing's server, which sees the paths it would serve standalone. It never learns it is embedded.

Once, and not everything. Each node is guarded, so a script re-inserted into the DOM does not run twice. Non-JS types — application/json, ld+json — are data blocks: they stay in the host DOM, unexecuted. And realms share no module registry, so a tag the host and a fragment both include (Adobe, your telemetry SDK) loads once per realm.

One reserved prefix, so it can never collide
/__braid/ frag / billing / main.js │ │ │ └── the path on the fragment's own server │ │ └─────────── which fragment (its manifest id) │ └───────────────── what kind of request └────────────────────────── reserved namespace
/__braid/realm/:id/:routeThe iframe stub. Identical for every user, varies on nothing. max-age=3600 with a year of stale-while-revalidate: the hour bounds how fast an adapter or protocol change reaches clients, the SWR year means nobody ever waits on the revalidation.
/__braid/doc/:id/:routeThe fragment's prepared HTML, when nothing was pierced in: a client-side navigation (no document request reached the gateway), a slot created after load, or a pierce that failed or timed out.
/__braid/frag/:id/*Everything the fragment's own code asks for, forwarded verbatim with the prefix stripped.
/__braid/registryPaginated JSON list of fragments, filtered by who is asking. Off unless configured.

Why realm and frag are separate: a stub varies on nothing; assets vary by user, cookie, and encoding. One prefix for both would force the safest thing in the system and the least safe thing to share cache rules.

Two kinds of fragment

A screen, or a widget.

Bound — the default

A fragment that is a screen. It lives at certain routes, follows host navigation, and is fetched at the page's own path.

On /billing/invoices, the gateway asks billing's server for /billing/invoices.

Unbound

A fragment that is a widget on the page — horizontally integrated rather than owning a route. A notifications panel, a sidebar, a global search box. It appears on many pages, and its content lives at one fixed address.

// manifest { "id": "notifications", "bound": false, "src": "/panel", "pierce": ["/", "/*"] } <!-- the host names the same path, so client and server agree --> <fragment-slot name="notifications" src="/panel"></fragment-slot>
Blast radius

What happens when a fragment is down, or just slow.

Down

The page still renders. The slot is marked data-braid-fallback and the client tries to boot the fragment itself — a transient failure self-heals rather than becoming a broken page.

A manifest can opt into a visible error instead, when a missing section is worse than a visible failure.

Slow

Each fragment has a timeoutMs budget. Past it the gateway stops waiting and degrades to the same fallback. One slow widget cannot hold the whole page.

Measured in the POC: a 2s widget against a 400ms budget delayed the page by 407ms, not 2s.

Concurrent identical fragment fetches also collapse into one — not a cache, so no TTL and no dependence on which instance a request lands on.

Day two

The registry, and what it can tell you.

Topology

Routes, the fragments they compose, the origins serving them. Routes carrying more than one fragment are highlighted, because that is where the coupling is.

Findings

Overlapping pierce patterns, incomplete manifests, empty access rules — surfaced at publish time rather than in production.

Snapshots

A pinned HEAD over published registry snapshots. Rolling back is re-pointing it, and nothing else.

Fragments never import each other — so the honest graph isn't dependencies. It's who shares a page, and who shares a deploy.

Getting started · step 1 of 3

Mount the gateway in front of your host.

import { createGateway } from '@braidlabs/gateway'; import { toNodeMiddleware } from '@braidlabs/gateway/node'; const gateway = createGateway({ mode: 'production', registry: [{ id: 'billing', endpoint: 'https://billing.internal.company.com', pierce: ['/billing', '/billing/*'], timeoutMs: 1200, }], }); // first, so it sees the request before the host does app.use(toNodeMiddleware(gateway)); app.use(hostAppRouter);
Getting started · steps 2 and 3

Name the slot. Then change nothing else.

<!-- 2. in the host's template --> <div class="billing-container"> <h1>Account & Invoicing</h1> <fragment-slot name="billing"></fragment-slot> </div>
# 3. the billing team's repo — an ordinary build, an ordinary server npm run build npm run serve -- --port 4201

No bundler plugins. No shared-dependency negotiation. No webpack configuration. Step 3 is not a Braid step at all.

What goes wrong, by symptom

The list we publish.

SymptomCauseFix
Content flickers, then re-renders. Two realms per slot. The host framework discarded the server-rendered DOM and rebuilt it, so the new slot booted a second realm. hydration, both bootstraps
A widget is an empty shell, or 404s, on every page. It is bound when it should not be, so the gateway asks its server for the page's path. bound: false + src
Fragment CSS 404s; content renders unstyled. Its relative URLs resolved against the host page — the response never went through the re-rooting transform. fetch via /__braid/doc/
A routed fragment hijacks the host URL. A compat realm is a real iframe, so its boot navigation joins the joint session history. own the route
A fragment vanishes on some page loads. A shared cache served a composed page to a request that wanted the plain shell. never edge-cache pierced pages
At the edge

No custom cache keys. No Vary. Just URLs.

/__braid/frag/:id/*Aggressively. The bulk of your traffic. Immutable if the fragment's build hashes filenames — the id is in the path, so two fragments' assets can never collide.
/__braid/realm/:id/*Aggressively. Tiny and identical for a given URL. Caching it removes a round trip from every fragment boot.
/__braid/doc/:id/*Follow the fragment's own headers. That team's call, and it often reflects a signed-in user.
a pierced pageNever at the edge. The gateway marks composed pages private, so a correct shared cache refuses them even if your shell said public.

Where it runs: the gateway is a fetch handler, and streaming is the requirement that eliminates platforms. Cloudflare Workers is the best fit; ECS/Fargate, Cloud Run and Container Apps are all fine. Lambda@Edge: avoid — no response streaming, so you keep composition and lose the reason for it. CloudFront Functions: cannot — no network access at all.

Where to go next

Four documents, in the order that helps.

braid-explained.mdThe vocabulary and the page-load walkthrough this talk followed.
braid-poc.mdA running example: three frameworks and a server-rendered widget on one page, with the commands to start it.
braid-failure-modes.mdWhat goes wrong in practice, by symptom. Read this before debugging anything.
braid-from-module-federation.mdIf you already know Module Federation, start here instead.

Speaker notes

← → navigate · N notes · O overview · F fullscreen · P handout