nuxt-nats
nuxt-nats is a Nuxt 4 / Nitro server-side module (config key nats) that owns the NATS JetStream connection lifecycle and gives you auto-imported server utilities for publishing, consuming, KV/Object Store, and the Synadia agent fabric. It wraps the modular @nats-io/* v3 client — you almost never call @nats-io/* directly in a nuxt-nats app.
It is server-only (Nitro): there are no browser composables (ADR-002). Publish and consume from server/ — plugins, API routes, and Nitro tasks.
What it gives you (auto-imported in server/)
| Utility |
Purpose |
useNats() / useJetStream() / useJetStreamManager() |
raw connection / JS client / JSM, when you need to drop down |
useJetStreamIfAvailable() |
non-throwing useJetStream() — returns null before the connection is ready (see SSR lifecycle) |
jsPublish(subject, data, opts?) |
durable JetStream publish, typed via NatsEvents, with msgId dedup + client retry |
corePublish(subject, payload) |
fire-and-forget core NATS publish (no PubAck) |
defineNatsConsumer(opts) |
register a durable pull consumer (worker) |
defineDeadLetterConsumer(opts) |
build a DLQ by consuming max-deliver / terminated advisories from a stream |
useEphemeralConsumer(opts) |
request-scoped ordered ephemeral consumer (SSE) |
useKV(bucket, opts?) / useObj(bucket, opts?) |
KV bucket / Object Store, cached per process |
defineNatsAgent(opts) / useAgents() |
host or call AI agents on the Synadia Agent Protocol |
Reference files
Read the one you need — don't load them all:
references/configuration.md — the full nats: module option reference, env-var overrides, the auth priority chain, TLS, JetStream domain/prefix, and the provisioning stance (provision). Read when setting up or debugging module config.
references/publishing.md — jsPublish vs corePublish, the NatsEvents typed-subject augmentation, msgId dedup, and publish retry. Read when producing events.
references/consumers.md — defineNatsConsumer, the NUXT_NATS_WORKERS gate, ack/nak/term handling, defineDeadLetterConsumer, and useEphemeralConsumer for SSE. Read when consuming.
references/kv-object.md — useKV / useObj, and their real gotchas (Object Store wants a ReadableStream, KV TTL units). Read for state/blob storage.
references/agents.md — defineNatsAgent / useAgents on the Synadia Agent Protocol. Read when building an agent; defer to the nats-agent-fabric skill for the wire protocol itself.
references/gotchas.md — the SSR/Nitro lifecycle race, reconnect-storm status semantics, Nitro externals, provisioning races, and testing with Testcontainers. Read this before shipping to production; it's where the non-obvious failures live.
Minimal setup
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-nats'],
nats: {
servers: ['nats://localhost:4222'], // override in prod via NUXT_NATS_SERVERS (all cluster nodes)
// auth is normally injected via env: NUXT_NATS_USER_JWT + NUXT_NATS_NKEY_SEED
streams: [ // DESCRIBE streams; do not let the app own them in prod
{ name: 'ORDERS', subjects: ['orders.>'], provision: 'never' },
],
},
})
// server/api/orders.post.ts — publish
export default defineEventHandler(async (event) => {
const body = await readBody(event)
await jsPublish('orders.created', body, { msgId: body.id }) // msgId => Nats-Msg-Id dedup
return { ok: true }
})
// server/plugins/order-worker.ts — consume (runs only when NUXT_NATS_WORKERS=true)
export default defineNitroPlugin(() => {
defineNatsConsumer({
stream: 'ORDERS',
durable: 'order-processor', // binds an EXISTING durable by default (IaC owns its config)
async handler(data, msg) {
await process(data)
// handler return = ack; throw = nak with backoff; see references/consumers.md
},
})
})
Core principles
- Server-side only. Publish/consume from
server/. There are no client composables; don't try to useNats() in a Vue component.
- The worker gate is deliberate. Consumers and agents run only when
NUXT_NATS_WORKERS=true. Serverless/edge deployments publish fine but must not run long-lived pull loops. The recommended topology is a serverless publisher + a separate persistent worker process. If your consumer "never fires", check this first.
provision: 'never' in production. A stream/consumer config is server-side state that belongs in IaC (an idempotent streams.sh or Helm), not in N racing app replicas. provision: 'startup' is for dev; 'update' races on rolling deploys. The streams: block in config is best read as documentation of a stream some other process owns.
defineNatsConsumer binds; it does not enforce. By default it binds an existing durable — filterSubjects/ackPolicy you pass are not sent to the broker unless the durable is being created (provision: 'startup'). The live durable's config wins; a drift test against a real broker is the only thing that catches a mismatch.
- NATS has no dead-letter queue.
maxDeliver = retry then stop, not "route to a DLQ". Use defineDeadLetterConsumer() (advisory capture) if you need one — see references/consumers.md. Never write a comment claiming redelivery auto-routes to dlq.*; it doesn't.
- Type your subjects. Augment
NatsEvents in a server/**/*.d.ts so every jsPublish is checked. See references/publishing.md.
- Respect the SSR lifecycle race. Nitro doesn't await async server plugins in order, so the connection may not be ready when a later plugin runs. Use
useJetStreamIfAvailable() / the module's readiness wait, never a bare useJetStream() at plugin top level. See references/gotchas.md.
1---2name: nuxt-nats3description: Use this skill whenever working in a Nuxt 4 / Nitro project that uses the nuxt-nats module to talk to NATS JetStream — configuring the module in nuxt.config (the `nats:` key), publishing events with jsPublish or corePublish, registering durable pull consumers with defineNatsConsumer, building a dead-letter consumer with defineDeadLetterConsumer, streaming to the browser with useEphemeralConsumer (SSE), using KV/Object Store via useKV/useObj, exposing or calling AI agents with defineNatsAgent/useAgents, typing subjects via the NatsEvents interface, or the NUXT_NATS_WORKERS worker gate. Trigger on any of those symbols, on the env vars NUXT_NATS_SERVERS / NUXT_NATS_USER_JWT / NUXT_NATS_NKEY_SEED / NUXT_NATS_WORKERS, or when a Nuxt app needs server-side NATS messaging. Use this even if the user just says "add NATS to my Nuxt app". For raw @nats-io/* client code outside Nuxt use jetstream-architecture; for server/cluster deployment use jetstream-deployment; for the Synadia agent protocol details use nats-agent-fab4---56# nuxt-nats78`nuxt-nats` is a **Nuxt 4 / Nitro server-side module** (config key `nats`) that owns the NATS JetStream connection lifecycle and gives you auto-imported server utilities for publishing, consuming, KV/Object Store, and the Synadia agent fabric. It wraps the modular `@nats-io/*` v3 client — you almost never call `@nats-io/*` directly in a nuxt-nats app.910It is **server-only** (Nitro): there are no browser composables (ADR-002). Publish and consume from `server/` — plugins, API routes, and Nitro tasks.1112## What it gives you (auto-imported in `server/`)1314| Utility | Purpose |15|---|---|16| `useNats()` / `useJetStream()` / `useJetStreamManager()` | raw connection / JS client / JSM, when you need to drop down |17| `useJetStreamIfAvailable()` | non-throwing `useJetStream()` — returns `null` before the connection is ready (see SSR lifecycle) |18| `jsPublish(subject, data, opts?)` | durable JetStream publish, typed via `NatsEvents`, with `msgId` dedup + client retry |19| `corePublish(subject, payload)` | fire-and-forget core NATS publish (no PubAck) |20| `defineNatsConsumer(opts)` | register a durable **pull** consumer (worker) |21| `defineDeadLetterConsumer(opts)` | build a DLQ by consuming max-deliver / terminated advisories from a stream |22| `useEphemeralConsumer(opts)` | request-scoped ordered ephemeral consumer (SSE) |23| `useKV(bucket, opts?)` / `useObj(bucket, opts?)` | KV bucket / Object Store, cached per process |24| `defineNatsAgent(opts)` / `useAgents()` | host or call AI agents on the Synadia Agent Protocol |2526## Reference files2728Read the one you need — don't load them all:2930- `references/configuration.md` — the full `nats:` module option reference, env-var overrides, the auth priority chain, TLS, JetStream domain/prefix, and the provisioning stance (`provision`). Read when setting up or debugging module config.31- `references/publishing.md` — `jsPublish` vs `corePublish`, the `NatsEvents` typed-subject augmentation, `msgId` dedup, and publish retry. Read when producing events.32- `references/consumers.md` — `defineNatsConsumer`, the `NUXT_NATS_WORKERS` gate, ack/nak/term handling, `defineDeadLetterConsumer`, and `useEphemeralConsumer` for SSE. Read when consuming.33- `references/kv-object.md` — `useKV` / `useObj`, and their real gotchas (Object Store wants a `ReadableStream`, KV TTL units). Read for state/blob storage.34- `references/agents.md` — `defineNatsAgent` / `useAgents` on the Synadia Agent Protocol. Read when building an agent; defer to the `nats-agent-fabric` skill for the wire protocol itself.35- `references/gotchas.md` — the SSR/Nitro lifecycle race, reconnect-storm status semantics, Nitro externals, provisioning races, and testing with Testcontainers. Read this before shipping to production; it's where the non-obvious failures live.3637## Minimal setup3839```ts40// nuxt.config.ts41export default defineNuxtConfig({42 modules: ['nuxt-nats'],43 nats: {44 servers: ['nats://localhost:4222'], // override in prod via NUXT_NATS_SERVERS (all cluster nodes)45 // auth is normally injected via env: NUXT_NATS_USER_JWT + NUXT_NATS_NKEY_SEED46 streams: [ // DESCRIBE streams; do not let the app own them in prod47 { name: 'ORDERS', subjects: ['orders.>'], provision: 'never' },48 ],49 },50})51```5253```ts54// server/api/orders.post.ts — publish55export default defineEventHandler(async (event) => {56 const body = await readBody(event)57 await jsPublish('orders.created', body, { msgId: body.id }) // msgId => Nats-Msg-Id dedup58 return { ok: true }59})60```6162```ts63// server/plugins/order-worker.ts — consume (runs only when NUXT_NATS_WORKERS=true)64export default defineNitroPlugin(() => {65 defineNatsConsumer({66 stream: 'ORDERS',67 durable: 'order-processor', // binds an EXISTING durable by default (IaC owns its config)68 async handler(data, msg) {69 await process(data)70 // handler return = ack; throw = nak with backoff; see references/consumers.md71 },72 })73})74```7576## Core principles7778- **Server-side only.** Publish/consume from `server/`. There are no client composables; don't try to `useNats()` in a Vue component.79- **The worker gate is deliberate.** Consumers and agents run **only when `NUXT_NATS_WORKERS=true`**. Serverless/edge deployments publish fine but must not run long-lived pull loops. The recommended topology is a serverless publisher + a separate persistent worker process. If your consumer "never fires", check this first.80- **`provision: 'never'` in production.** A stream/consumer config is server-side state that belongs in IaC (an idempotent `streams.sh` or Helm), not in N racing app replicas. `provision: 'startup'` is for dev; `'update'` races on rolling deploys. The `streams:` block in config is best read as *documentation* of a stream some other process owns.81- **`defineNatsConsumer` binds; it does not enforce.** By default it binds an existing durable — `filterSubjects`/`ackPolicy` you pass are **not** sent to the broker unless the durable is being created (`provision: 'startup'`). The live durable's config wins; a drift test against a real broker is the only thing that catches a mismatch.82- **NATS has no dead-letter queue.** `maxDeliver` = retry then *stop*, not "route to a DLQ". Use `defineDeadLetterConsumer()` (advisory capture) if you need one — see `references/consumers.md`. Never write a comment claiming redelivery auto-routes to `dlq.*`; it doesn't.83- **Type your subjects.** Augment `NatsEvents` in a `server/**/*.d.ts` so every `jsPublish` is checked. See `references/publishing.md`.84- **Respect the SSR lifecycle race.** Nitro doesn't await async server plugins in order, so the connection may not be ready when a later plugin runs. Use `useJetStreamIfAvailable()` / the module's readiness wait, never a bare `useJetStream()` at plugin top level. See `references/gotchas.md`.