Skip to content
QueryWeave

Nuxt

Framework

A module plus a request-scoped runtime plugin for server rendering.

Package
@queryweave/nuxt
Runtime
Nuxt server and client
Depends on
@queryweave/core, @queryweave/vue, @queryweave/vue-router
Requires
nuxt, vue, vue-router
InstallQuick examplePackage: @queryweave/nuxt

The Nuxt package does two separable things: it configures your application at build time, and it creates one adapter per Vue application instance at runtime. Those live in different entry points because they run at different times.

Terminal window
pnpm add @queryweave/core @queryweave/vue @queryweave/nuxt
nuxt.config.ts
export default defineNuxtConfig({
modules: ["@queryweave/nuxt"],
});

Requires Nuxt 4.0 or newer on the 4.x line. The module declares the same range as the package’s peer dependency.

app/pages/products.vue
<script setup lang="ts">
import { defineQueryModel, param } from "@queryweave/core";
const products = defineQueryModel({
search: param.text().optional(),
page: param.integer({ min: 1 }).default(1),
});
// useQueryModel is auto-imported by the module.
const filters = useQueryModel(products);
const search = filters.field("search", { navigation: "replace" });
</script>
<template>
<input v-model="search" type="search" />
<p>Page {{ filters.values.page }}</p>
</template>

No adapter is provided by hand. The module registers a plugin that installs one for you, and the Vue binding finds it through injection.

nuxt.config.ts
export default defineNuxtConfig({
modules: ["@queryweave/nuxt"],
queryweave: {
autoImports: true, // register useQueryModel and provideQueryAdapter — default true
enabled: true, // register the runtime plugin at all — default true
},
});

Setting enabled: false disables the module entirely, which is useful when you want to install an adapter yourself.

This is the part that matters for server rendering.

Nuxt creates a fresh Vue application for every server-rendered request. The QueryWeave plugin creates its adapter inside that application’s setup and installs it on that instance:

// packages/nuxt/src/runtime/plugin.ts, in essence
const adapter = createNuxtQueryAdapter(nuxtApp.$router, { server: import.meta.server });
installQueryAdapter(nuxtApp.vueApp, adapter);

Nothing is stored at module scope. A server handling two requests concurrently therefore has two adapters, each bound to its own router, and neither can observe the other.

On the server, update() and the other transitions resolve with outcome: "refused" and an Error as reason rather than moving a router whose response is already being written. Reading works as usual. A server render that must change the URL redirects with navigateTo().

The adapter is backed by Vue Router, which Nuxt initializes from the request URL on the server and from location on the client. Both sides therefore decode the same query with the same model, and the first client render matches the server’s.

There is no state-transfer payload for query state, and none is needed: the URL is the transfer. The exception is a model with an asynchronous refinement: its first snapshot is pending on both sides. Await filters.settled() in the component — for example at the top level of <script setup>, which Nuxt wraps in Suspense — when the server render must carry the settled value.

Nuxt mode names the three parts that make hydration safe: the SSR initial query, the Vue Router runtime, and per-request state.

History1 / 1

Products

6 matching

  • Edge runtime handbookactive$59
  • Node.js request toolkitactive$39
  • Nuxt deployment guidearchived$19
  • SSR initial query
  • Vue Router runtime
  • hydration-safe state

One adapter per Vue application instance, which on the server means one per request.

Type a query, press Enter.

Typed state

{
  "page": 1,
  "sort": "created_at",
  "status": "all"
}

Canonical URL

Valid
/products

    For applications that want to wire things themselves:

    import { createNuxtQueryAdapter, installQueryAdapter } from "@queryweave/nuxt/runtime";
    import type { Router } from "vue-router";
    export default defineNuxtPlugin((nuxtApp) => {
    const adapter = createNuxtQueryAdapter(nuxtApp.$router as Router, { server: import.meta.server });
    installQueryAdapter(nuxtApp.vueApp, adapter);
    });

    installQueryAdapter accepts any QueryAdapter, so an application can substitute its own — a memory adapter in a test, for example — without leaving the module.

    The plugin also exposes the adapter as $queryWeaveAdapter on the Nuxt app, for code that needs it outside a component.

    • useQueryModel outside a component still finds the adapter wherever Nuxt provides an injection context — a plugin, route middleware, or a useAsyncData handler. Somewhere it does not, pass $queryWeaveAdapter or a runtime explicitly.
    • A page that unmounts does not take the adapter’s route watcher with it. The next page’s binding keeps receiving navigation.
    • Static generation decodes the query at request time in the browser; a pre-rendered page has no query of its own.
    • Disabling auto-imports means importing useQueryModel from @queryweave/vue yourself; the behavior is identical.
    • isolatedDeclarations is off for this package alone, because the Nuxt module type cannot be expressed without it. This is recorded in ADR 0007.