Skip to content
DocspackagesDocumentation

@lunora/nuxt

Nuxt module: single-worker composition (mounts /_lunora/* into Nitro) plus reactive-loader server helpers.

PackagesNuxt

@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 a worker.ts wrapper at the project root and point wrangler.jsonc's main at it, so the ShardDO Durable Object class is exported from the deployed worker. Nitro's cloudflare_module output exports only the SSR handler, so main must point at the wrapper, not the raw .output/server/index.mjs, or wrangler deploy fails on the missing DO:

worker.ts
export { default } from "./.output/server/index.mjs";
export { ShardDO } from "./lunora/server";
wrangler.jsonc
{ "main": "worker.ts" }

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 deployed worker through your root worker.ts wrapper (wrangler.jsonc's main), which re-exports Nitro's SSR handler and ShardDO; Nitro's cloudflare_module output exports only the SSR handler.

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, so 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 worker.ts wrapper: wrangler.jsonc's main must point at a root worker.ts that re-exports Nitro's SSR handler and ShardDO (Nitro's cloudflare_module output exports only the SSR handler); if wrangler deploy fails with "ShardDO class not exported", check that main points at the wrapper and that it re-exports ShardDO. The module warns when worker.ts is missing but can't verify wrangler's main points at it.

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.