@lunora/nuxt
Nuxt module: single-worker composition (mounts /_lunora/* into Nitro) plus reactive-loader server helpers.
@lunora/nuxt runs Lunora and Nuxt as a single Cloudflare Worker. Instead
of Lunora owning the Cloudflare worker entry (the two-worker split other
adapters use), it is mounted inside Nitro: the module registers a server
route at /_lunora/** that forwards every Lunora RPC, WebSocket upgrade, and
admin request to your Lunora app in-process. One wrangler.jsonc, one deploy,
a same-origin client.
Install
pnpm add @lunora/nuxtnuxt (^4.0.0) is an optional peer dependency — the host app supplies the
Nuxt runtime. h3 is also an optional peer (^1.15.0); see
Deploy notes below.
Setup
Register the module and pick the Cloudflare Nitro preset:
export default defineNuxtConfig({
modules: ["@lunora/nuxt"],
nitro: { preset: "cloudflare_module" },
});Add exports.cloudflare.ts to the project root so the ShardDO Durable
Object class is exported from the emitted worker entry:
export { ShardDO } from "./lunora/server";lunora/server.ts is your built Lunora app (defineApp().build()) — its
default export is the worker (a fetch entrypoint), and it re-exports
ShardDO.
Options
Configurable under the lunora key in nuxt.config.ts.
| Option | Default | Description |
|---|---|---|
appEntry | ~/lunora/server | Module specifier of the Lunora app entry, aliased to the #lunora/app virtual. |
prefix | /_lunora | URL prefix the Lunora realtime plane is mounted at. |
How it works
- The route (
addServerHandlerat<prefix>/**): reconstructs a WebRequestfrom the H3 event, resolves the Cloudflareenv/ExecutionContextoff it (tolerating bothevent.context.cloudflareandevent.req.runtime.cloudflare), and forwards to your app'sfetch. A missing Cloudflare runtime answers a clear 500. - The
#lunora/appalias: points the route's worker import at your app entry (options.appEntry), forwarded into the Nitro server bundle vianuxt.options.alias. ShardDOrides to the worker entry through your rootexports.cloudflare.ts— thecloudflare_modulepreset appends its exports onto the generated worker.
Server data-loading — @lunora/nuxt/server
Re-exports the framework-neutral SSR contract from @lunora/client/ssr
(createServerClient, preloadQuery, serializePreloaded,
deserializePreloaded, preloadedQueryResult, getServerSession, plus the
ArgsOf / AuthLike / FunctionReference / HeadersSource / Preloaded /
ReturnOf / ServerClientOptions / ServerSession types) for the
reactive-loader handoff. Opens no WebSocket and touches no browser globals, so
it is safe to import from a Nitro server route.
import { createServerClient, preloadQuery } from "@lunora/nuxt/server";
import { api } from "./lunora/_generated/api";
// Per request — never reuse a client across requests (token leakage).
const client = createServerClient({ url: process.env.LUNORA_URL!, token });
const preloaded = await preloadQuery(client, api.posts.list, {});Hand the serializable preloaded value to a client component and seed it with
@lunora/vue's hydratePreloaded — Nuxt is Vue, so the
Vue adapter's composables (useQuery, useMutation, hydratePreloaded) are
the reactive layer here; @lunora/nuxt only owns the server-side composition.
Feature flags
@lunora/nuxt ships no flag composable of its own — since Nuxt is Vue, read
ctx.flags server-side (a Nitro route, a function, or a reactive loader) and
pass the resolved value down, or call useFlag / useFlags from
@lunora/vue directly in a component for live updates
over the WebSocket. Requires @lunora/flags wired in
lunora/flags.ts.
<script setup lang="ts">
import { useFlag } from "@lunora/vue";
// Live over the Lunora WS — holds `false` until the server resolves it.
const newHero = useFlag("homepage-hero", false);
</script>
<template>
<NewHero v-if="newHero" />
<ClassicHero v-else />
</template>Deploy notes
Verify before deploy. Single-worker composition rides on two Nitro behaviours that vary across versions — verify them on your pinned toolchain: (1)
WebSocket upgrade pass-through — the live feed needs Nitro to return your Lunora app's 101 Switching Protocols response (carrying its Cloudflare
webSocket) untouched; RPC (plain JSON) works regardless, so if live subscriptions never connect while RPC does, Nitro is normalising the upgrade response.
(2) The exports.cloudflare.ts hook — the cloudflare_module preset must append this file's exports onto the worker entry; if wrangler deploy fails
with "ShardDO class not exported", your Nitro version may use a different hook (nitro.cloudflare.additionalModules, or a rollupConfig output export).
The module warns when the file is missing but can't verify the hook fires.
h3 version. The peer range is ^1.15.0 (h3 v1 — what Nuxt 4 / Nitro 2 run at runtime). If you pin an explicit h3 dependency yourself, pin it to
^1.15.11 — npm's h3@2.0.0 is a deprecated stub with no code, exports, or type declarations (the real v2 line is still prerelease-only). The peer will
re-admit v2 once a stable h3 v2 actually ships.
See also
- @lunora/vue — the reactive layer (Nuxt is Vue):
useQuery/useMutation/hydratePreloaded - @lunora/astro — the other framework module composing single-worker deploys via
withLunora - @lunora/client — the framework-neutral SDK;
@lunora/nuxt/serverre-exports its/ssrcontract - @lunora/flags — feature flags surfaced through
ctx.flags/useFlag - Bring your framework — composition + adapter maturity