PackagesNuxt

@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/nuxt

nuxt (^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:

nuxt.config.ts
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:

exports.cloudflare.ts
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.

OptionDefaultDescription
appEntry~/lunora/serverModule specifier of the Lunora app entry, aliased to the #lunora/app virtual.
prefix/_lunoraURL prefix the Lunora realtime plane is mounted at.

How it works

  • The route (addServerHandler at <prefix>/**): reconstructs a Web Request from the H3 event, resolves the Cloudflare env/ExecutionContext off it (tolerating both event.context.cloudflare and event.req.runtime.cloudflare), and forwards to your app's fetch. A missing Cloudflare runtime answers a clear 500.
  • The #lunora/app alias: points the route's worker import at your app entry (options.appEntry), forwarded into the Nitro server bundle via nuxt.options.alias.
  • ShardDO rides to the worker entry through your root exports.cloudflare.ts — the cloudflare_module preset 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/server re-exports its /ssr contract
  • @lunora/flags — feature flags surfaced through ctx.flags / useFlag
  • Bring your framework — composition + adapter maturity