Skip to content

Integration with nuxt

There is no official nuxt integration package. However, vue-query and vue-trpc can be integrated into nuxt with some simple plugins provided here. You might need to tweak the plugin to fit your application need.

A full example is available at example-nuxt directory.

See also h3 adapter for the server adapter with h3/nitro.

1. Install required packages

WARNING

trpc-vue-query is designed for trpc v11. See support matrix in overview for details

bash
npm install -S @trpc-vue-query/client @tanstack/vue-query @trpc/client@next @trpc/server@next
bash
pnpm install -S @trpc-vue-query/client @tanstack/vue-query @trpc/client@next @trpc/server@next
bash
yarn add @trpc-vue-query/client @tanstack/vue-query @trpc/client@next @trpc/server@next

2. Create trpc-vue-query hooks

This will provide the interface for calling trpc procedures which we will show later.

This file can be placed under utils/trpc.ts so it is available via auto import.

ts
import { createTRPCVue } from "@trpc-vue-query/client";
// Tweak the import to point to server router export, similar to how react integration works
import type { AppRouter } from "../server/trpc/routers";

export const trpc = createTRPCVue<AppRouter>({
  // Set serverPrefetch to true for nuxt. This hint nuxt to fetch during SSR
  serverPrefetch: true,
});

3. Create vue-query and vue-trpc plugin

ts
// Setup vue-query. Adapted from tanstack-query docs:
// https://tanstack.com/query/latest/docs/framework/vue/guides/ssr

import {
  VueQueryPlugin,
  QueryClient,
  hydrate,
  dehydrate,
  type DehydratedState,
  type VueQueryPluginOptions,
} from "@tanstack/vue-query";

export default defineNuxtPlugin({
  name: "vue-query",
  setup(nuxt) {
    const vueQueryState = useState<DehydratedState | null>("vue-query");

    // Modify your Vue Query global settings here
    const queryClient = new QueryClient({
      defaultOptions: { queries: { staleTime: 5000 } },
    });
    const options: VueQueryPluginOptions = { queryClient };

    nuxt.vueApp.use(VueQueryPlugin, options);

    if (import.meta.server) {
      nuxt.hooks.hook("app:rendered", () => {
        vueQueryState.value = dehydrate(queryClient);
      });
    }

    if (import.meta.client) {
      hydrate(queryClient, vueQueryState.value);
    }
  },
});
ts
import { TRPCUntypedClient, httpBatchLink } from "@trpc/client";
import { trpc } from "../utils/trpc";

export default defineNuxtPlugin({
  name: "vue-trpc",
  dependsOn: ["vue-query"],
  setup(nuxt) {
    // Fetch the request headers on plugin initialization. This will return
    // empty object in client and should always return the same object on SSR
    // (because each SSR invoke is rendering a page/route/HTTP request).
    //
    // If we were to call `useRequestHeaders` on every trpc request, it might fall
    // because the call is not in a nuxt context.
    const headers = useRequestHeaders();

    nuxt.vueApp.use(trpc, {
      client: new TRPCUntypedClient({
        links: [
          httpBatchLink({
            url: "/api/trpc",
            headers,
            // Simple fetch polyfill for trpc
            async fetch(input, init) {
              const resp = await $fetch.raw(input.toString(), init as any);

              return {
                ...resp,
                json: () => Promise.resolve(resp._data),
              };
            },
          }),
        ],
      }),
    });
  },
});

4. Fetch data

You can then use trpc in your components to fetch data. The trpc call should be handled during SSR.

vue
<script setup lang="ts">
const userQuery = trpc.getUser.useQuery({ id: "id_bilbo" });
const userCreator = trpc.createUser.useMutation();
</script>

<template>
  <p>{{ userQuery.data?.name }}</p>
  <button @click="userCreator.mutate({ name: 'Frodo' })">Create Frodo</button>
</template>