@lunora/astro
Astro integration: single-worker composition plus reactive-loader server helpers.
@lunora/astro owns two server-side seams for running Lunora inside an Astro app on Cloudflare. Astro is multi-framework at the UI layer, so this package is not a reactive runtime — reactivity comes from whichever island adapter you hydrate with (@lunora/react, @lunora/solid, @lunora/svelte, @lunora/vue).
- Single-worker composition —
withLunorawraps the Worker@astrojs/cloudflareemits so Lunora realtime (/_lunora/rpc,/_lunora/ws,/_lunora/admin/*) is mounted inside it. One worker, one deploy. - Reactive-loader server helpers —
@lunora/astro/serverre-exports the framework-neutral SSR contract (createServerClient,preloadQuery,serializePreloaded,getServerSession) for use in.astrofrontmatter and server endpoints.
Install
pnpm add @lunora/astroastro is an optional peer dependency (^6.0.0) — the host app provides it.
Composition
Add the lunora integration to astro.config, then wrap the Cloudflare adapter's SSR handler in src/worker.ts.
Register the integration:
import cloudflare from "@astrojs/cloudflare";
import { defineConfig } from "astro/config";
import { lunora } from "@lunora/astro";
export default defineConfig({
output: "server",
adapter: cloudflare(),
integrations: [lunora()],
});Compose the worker. On Astro 6 / @astrojs/cloudflare v13 the adapter no longer emits a dist/_worker.js bundle — import handle (the adapter's built-in SSR fetch function) and wrap it:
import { handle } from "@astrojs/cloudflare/handler";
import { withLunora } from "@lunora/astro";
// `SHARD` lives on `env` (per request), so pass an `(env) => options` factory.
export default withLunora(
(request, env, ctx) => handle(request, env, ctx),
(env) => ({ shardDO: env.SHARD }),
);Point wrangler at the composed entry:
{
"main": "src/worker.ts",
}withLunora reserves /_lunora/rpc, /_lunora/ws, /_lunora/admin/* (plus any auth/explicit routes) for Lunora; everything else delegates to Astro's SSR handler. The two dispatch flows share one worker but never collide, and an Astro render that throws is isolated as a plain 500.
withLunora
withLunora(host, optionsInput);host— Astro's emitted Cloudflare handler, either a barefetchfunction or a{ fetch }object (ascheduledon the object is preserved when Lunora configures no cron surface).optionsInput— Lunora worker options minushttpRouter, or an(env) => optionsfactory so per-request bindings likeenv.SHARDwire in. Returns a{ fetch, scheduled, serverQuery }worker.
withLunora is the Astro-named alias for the shared withFrameworkWorker from @lunora/runtime — the same composer behind @lunora/svelte/worker and
@lunora/vue/worker.
lunora(options)
The astro.config integration object ({ name, hooks }). The only option is serverEntry (default "src/worker.ts"), the module that calls withLunora and is the composed worker's export default.
Server helpers
@lunora/astro/server opens no WebSocket and touches no browser globals, so it is safe to import from .astro frontmatter or a server endpoint. Build a request-scoped client, preload a query, then serialize the token for an island.
---
import { createServerClient, preloadQuery, serializePreloaded } from "@lunora/astro/server";
import { api } from "../lunora/_generated/api";
const client = createServerClient({ url: Astro.url.origin + "/_lunora/rpc" });
const preloaded = await preloadQuery(client, api.messages.list, {});
---
<my-island data-preloaded={serializePreloaded(preloaded)}></my-island>On the client, deserialize and hand the token to your island adapter's hydratePreloaded for the SSR-seed-to-live handoff:
import { hydratePreloaded } from "@lunora/react";
import { deserializePreloaded } from "@lunora/astro/server";
const preloaded = deserializePreloaded(el.dataset.preloaded);
const initial = hydratePreloaded(preloaded); // seeds the first paint, then goes liveRe-exported from @lunora/astro/server:
| Export | Use |
|---|---|
createServerClient | Build a request-scoped client ({ url, token?, fetch? }). One per request. |
preloadQuery | Run a query server-side → a serializable Preloaded token. |
preloadedQueryResult | Read the value out of a Preloaded token. |
serializePreloaded | Serialize a Preloaded token to a string for an island attribute. |
deserializePreloaded | Parse a serialized token back on the client. |
getServerSession | Resolve the current session from a headers source (Request/Headers) + an auth instance. |
Create a createServerClient per request rather than sharing a module-level instance — the bearer token is request-scoped, and a shared client would leak
one request's auth into another.
Related
@lunora/runtime— the Worker runtimewithLunoracomposes with.@lunora/client— the browser SDK;@lunora/astro/serverre-exports its/ssrcontract.@lunora/react,@lunora/solid,@lunora/svelte,@lunora/vue— island adapters that shiphydratePreloaded.