Astro Integration

Astro is an islands-first web framework built for content-heavy sites. @astrojs/bext is the Astro adapter that targets bext: you keep Astro's authoring experience, and bext provides the HTTP layer, cache, WAF, HTTP/2/3, and plugin ecosystem that every adapter otherwise reinvents.

Preview release. The adapter and the Rust-side framework runtime in crates/bext-framework-astro ship as part of bext's E6 milestone. See the ecosystem roadmap for what's landed and what's still on the follow-up list.

Install

bun add -d @astrojs/bext

Peer dependencies: astro ^4 or astro ^5.

Configure

In astro.config.mjs:




export default defineConfig({
  output: "server",
  adapter: bext({
    runtime: "bun",
  }),
});

The adapter registers itself in the astro:config:setup hook and calls setAdapter() in astro:config:done. Astro then builds with server output, producing dist/server/entry.mjs plus a dist/client directory of hashed assets.

Options

Option Type Default Description
runtime "node" | "bun" "bun" Preferred runtime for the SSR worker. Informational; bext decides.

Build

bun run build

The build emits:

dist/
  client/            hashed JS, CSS, fonts
  server/
    entry.mjs        SSR entry exporting { handler }
  bext-adapter.json  metadata for the bext runtime

bext-adapter.json is the contract between @astrojs/bext and crates/bext-framework-astro. It looks like:

{
  "adapter": "@astrojs/bext",
  "version": "0.1.0-preview",
  "runtime": "bun",
  "serverEntry": "server/entry.mjs",
  "clientDir": "client",
  "generatedAt": "2026-04-11T12:00:00.000Z"
}

Serve

Via bext.config.toml

[framework]
type = "astro"
build_dir = "dist"

[build]
script = "dist/server/entry.mjs"
watch_dirs = ["src"]
live_reload = true

Then:

bext serve

bext's vhost detects the project as Astro (on astro.config.* + astro in package.json), reads the build output, and dispatches requests to the Astro handler through the framework runtime adapter.

Via bext new

bext new my-astro-app --template @bext/starter-astro
cd my-astro-app
bun install
bun run build
bext serve

The starter ships with astro.config.mjs, a Base.astro layout, two pages (/ and /about), and the bext.config.toml plumbing pre-wired.

How it fits together

Three moving parts collaborate on every Astro request through bext:

  1. @astrojs/bext (this package) — Astro integration and adapter. Runs at build time. Produces dist/ and bext-adapter.json. 2. crates/bext-framework-astro — Rust FrameworkAdapter implementation. Runs at server-config time. Detects the project, invokes bunx astro build (or npx astro build), normalises the output, and vends a runtime adapter. 3. bext-server vhost — per-request. Routes incoming requests to the Astro runtime adapter, which in turn dispatches to the Node / Bun subprocess running dist/server/entry.mjs.

How it compares to @astrojs/node

@astrojs/node produces a standalone Node server you run yourself. @astrojs/bext skips the HTTP layer — bext provides that — and just emits the compiled SSR entry plus static assets. bext then adds TLS, HTTP/2/3, cache, WAF, compression, and the full plugin ecosystem without Astro needing to know about any of them.

If you currently run @astrojs/node behind nginx, @astrojs/bext replaces both layers in one move.

What works today

- File-system routing (src/pages/*)

- Dynamic routes with [param].astro

- API routes (src/pages/api/*)

- Content collections

- MDX (with @astrojs/mdx)

- Islands via client:load, client:visible, etc.

- Hybrid output (output: "hybrid")

- Prerendered pages (export const prerender = true)

- Static assets served from dist/client/

Current limitations

  • Subprocess dispatch is still TODO. For the E6 preview, the runtime adapter is a descriptor only — bext-framework-astro's handle_request returns a not-yet-wired error. The follow-up is to teach bext-server's vhost to dispatch to the Node / Bun subprocess that imports dist/server/entry.mjs, reusing the same subprocess plumbing bext already has for PHP / FastCGI. - No streaming responses yet. Astro's app.render() can return a streaming Response. The preview buffers those. Piping them through bext's response writer lands with the streaming FrameworkAdapter::render hook. - No edge runtime support. This adapter targets Node / Bun. Edge-style Workers-compatible output is out of scope for E6. - Image service isn't bridged yet. Astro's built-in image service works, but it doesn't go through bext's media pipeline for caching or transformation. Integration with bext's image pipeline is a follow-up. - No native Rust build. @astrojs/bext shells out to astro build — Astro's build is a Vite plugin set and rewriting it in Rust is out of scope for E6. A direct turbopack / bun pipeline is on the ecosystem roadmap.

Troubleshooting

  • astro build produced no dist/ directory — the build ran but didn't emit the expected layout. Double-check that astro.config.mjs has output: "server" and that the adapter is installed as a dev-dependency. - AstroRuntime::handle_request is not wired yet — you're seeing the E6 descriptor-only limitation. Build works, detection works, but per-request dispatch is scheduled for the follow-up. Until then, use @astrojs/node behind bext as a reverse proxy if you need production traffic right now. - Dependency mismatch with Astro v4 vs v5peerDependencies is set to "astro": "^4 || ^5". If your lockfile pins an earlier major, the integration hooks won't resolve — upgrade Astro or use an older adapter.

Related docs

- @bext/starter-astro — starter scaffold

  • bext framework overview — how adapters compose with bext's request pipeline - PRISM — bext's native React engine, another FrameworkAdapter implementation