Server-Side Includes

<bext-include> is bext's composition primitive. It sits at the intersection of caching, streaming, auth, experimentation, and realtime to assemble pages from independent fragments — all at the server level, before the response leaves the edge.

<bext-include src="/__bext/nav" site="docs"></bext-include>

The server replaces this element with the resolved HTML before sending the response. Every integration — caching, auth, A/B tests, live updates — is a declarative attribute on the element.

bext also supports the industry-standard SSI (<!--#include virtual="..." -->) and ESI (<esi:include>) syntaxes, so existing nginx and CDN configurations migrate to bext with zero code changes and get bext's full feature set automatically.

Three syntaxes, one system

All three syntaxes parse into the same directive and flow through the same resolver pipeline:

<!-- bext-native, richest feature set -->
<bext-include src="/__bext/nav" site="docs"></bext-include>

<!-- nginx / Apache SSI -->
<!--#include virtual="/__bext/nav" -->

<!-- W3C ESI (Varnish, Akamai, Fastly) -->
<esi:include src="/__bext/nav" />

Use <bext-include> for new sites. Use SSI when migrating from nginx. Use ESI when migrating from a CDN.

What you can do with includes

Each capability is a single attribute on <bext-include>. Mix and match.

Cache with TTL and stale-while-revalidate

<bext-include src="/__bext/nav"
  ttl="5m" swr="1h" tags="chrome"
></bext-include>

The nav is rendered once, served from cache for 5 minutes, then served stale for up to an hour while revalidating in the background. Tagged chrome so you can purge all chrome fragments at once via the cache purge API. Backed by bext's tiered L0/L1/L2 cache.

Caching & TTL

Vary by dimension

<bext-include src="/partials/pricing-table"
  vary="role,locale,geo"
  ttl="10m"
></bext-include>

One element, separate cache entries per (role × locale × geo) combination. Admins in France see different prices than viewers in the US — and each variant is cached independently.

Vary-aware caching

Stream lazy includes

<bext-include src="/partials/recommendations"
  loading="lazy"
>Loading recommendations...</bext-include>

The page shell arrives immediately with a placeholder. The slow recommendations include resolves in the background and streams in as a later chunk. No blocking.

Streaming includes

Server-side auth gating

<bext-include src="/partials/admin-toolbar"
  auth="required" role="admin"
  vary="role" ttl="30m"
>
  <!-- fallback: hidden from non-admins entirely -->
</bext-include>

The admin toolbar is only rendered for authenticated admins. Non-admins see the fallback content (or nothing). No layout shift, no client-side flicker, no leaked markup.

Auth-gated includes

A/B experiments

<bext-include src="/partials/hero"
  experiment="hero-redesign"
></bext-include>

Assigns each user to a variant (weighted random, sticky cookie), rewrites src to the variant-specific source, and caches each arm independently.

Experiment variants

Live-updating fragments

<bext-include src="/partials/order-status"
  realtime="orders:123"
></bext-include>

SSR on first load; thereafter, a client-side SSE subscription re-fetches the fragment whenever the server publishes to the orders:123 channel. Dashboards and notification badges without a state management library.

Realtime includes

File-based partials

<bext-include src="/_partials/footer.html"></bext-include>

<!-- SSI-compatible -->
<!--#include virtual="/_partials/footer.html" -->

Static HTML files from the site's _partials/ directory. Mtime-based cache invalidation means editing the file busts the cache automatically.

File partials

Timeout, fallback, circuit breaking

<bext-include src="/api/live-scores"
  timeout="200ms"
  fallback="/_partials/cached-scores.html"
  onerror="continue"
></bext-include>

Give up after 200ms, try a fallback partial, and silently drop if everything fails. Plus a circuit breaker that skips calls to persistently failing sources.

Timeout & Fallback

Render components via V8

<bext-include src="./components/PricingTable.tsx"
  render="v8" props='{"plan":"pro"}'
></bext-include>

Compile and SSR a React component in bext's V8 isolate pool — no Bun process needed. Optional hydrate attribute bridges to the island system for client-side interactivity.

Component rendering

Full attribute reference

<bext-include
  src="/partials/example"

  ttl="5m" swr="1h" tags="chrome,nav"
  vary="role,locale,geo,device"
  loading="eager"

  auth="required" role="admin,editor"
  experiment="layout-test"

  realtime="channel-name"
  realtime-mode="replace"
  realtime-event="update"

  timeout="200ms"
  fallback="/cache/primary" fallback-2="/static/default.html"
  onerror="continue"

  render="v8" props='{"x":1}' hydrate
></bext-include>
Attribute Type Default Purpose
src path / URL required What to include
ttl duration none Cache lifetime (or adaptive)
swr duration none Stale-while-revalidate window
tags CSV none Cache tags for grouped purge
vary CSV none Cache vary dimensions
loading eager|lazy eager Inline vs streamed
auth required|anonymous none Authentication gate
role CSV none Role-based gate
experiment name none A/B experiment
realtime channel none Realtime subscription
realtime-mode replace|append|prepend replace Update behavior
realtime-event event name none Filter subscription to event type
timeout duration from config Resolution timeout
fallback src none Fallback source on failure
fallback-2 src none Second fallback
onerror continue|abort abort Error strategy
render v8|jsc|quickjs none Render engine
props JSON string null Props for rendered component
hydrate boolean false Wrap in island for hydration

How the resolver chain works

Every include — from any syntax — flows through the same pipeline:

Parse directive
  ↓
Security (depth, count, allow/deny)
  ↓
Cache (hit = return stored HTML)
  ↓
Auth gate (reject before resolution cost)
  ↓
Experiment (rewrite src to variant)
  ↓
Plugin resolvers (custom, pattern-matched)
  ↓
Built-in resolvers (/__bext/* sources)
  ↓
File resolver (/_partials/*)
  ↓
V8 renderer (render="...")
  ↓
Fallback resolver (fallback, onerror, inline)

Each resolver in the chain either returns content (stop) or passes to the next. This composition is how a single <bext-include> tag can be cached, auth-gated, vary'd by role, and stream in lazily — just by combining attributes.

Performance

- No per-request allocation when the HTML has no include tags — early bail via .contains().

- Stampede guard prevents thundering herd on cache misses.

- Adaptive TTL keeps popular includes cached longer (hit-count-based).

- Per-chunk compression on streaming lazy includes.

- Mtime cache on file partials means unchanged files never re-read from disk.

- Circuit breaker skips resolution for persistently failing sources.

Where to go next

- Caching & TTL — per-include cache lifecycle

- Vary-aware caching — dimension-based cache keys

- Auth-gated includes — server-side authorization

- Streaming includes — progressive delivery

- Experiment variants — A/B testing

- Realtime includes — live fragment updates

- File partials — filesystem-based includes

- Timeout & Fallback — resilience

- Component rendering — V8-backed SSR

- SSI & ESI compatibility — nginx and CDN migration

- Configuration referencebext.config.toml

- Plugin API — custom resolvers and hooks