Nuxt
Set up Typebase with Nuxt, covering the client, Tanstack Query, composables, and authentication.
Last updated on
Typebase provides two client types for Nuxt. createRouterClient is a simple promise-based client, best for useLazyAsyncData and server-side calls. createTanstackQueryClient is a Tanstack Query integration, best for components that need caching, loading states, and automatic refetching.
Both clients are set up as Nuxt plugins and injected into the app via provide, making them available anywhere via useNuxtApp().
Pick the one that fits your use case, or use both in the same project.
The examples below show one common way to integrate Typebase with Nuxt. They're a starting point, not a requirement. Typebase doesn't impose any project structure, so feel free to adapt the patterns to fit your app's conventions or pick only what's useful.
Simple client
Setup
nuxt.config.ts
Expose your Typebase server URL as a public runtime config value so it's available on both server and client:
export default defineNuxtConfig({
runtimeConfig: {
public: {
TYPEBASE_APP_URL: process.env.TYPEBASE_APP_URL_DEV || process.env.TYPEBASE_APP_URL,
},
},
});Plugin
Create a plugin that sets up the client and injects it into the app:
import { createRouterClient } from 'typebase-io/client';
import type { Router } from '~/utils/typebase/_generated/server';
export default defineNuxtPlugin(() => {
const event = useRequestEvent();
const runtimeConfig = useRuntimeConfig();
const client = createRouterClient<Router>({
url: runtimeConfig.public.TYPEBASE_APP_URL,
headers: event?.headers,
});
return {
provide: { client },
};
});useRequestEvent() gives you the incoming H3 request on the server. Passing its headers to the client forwards cookies on SSR requests so they reach your Typebase server.
That's it. $client is now available anywhere via useNuxtApp(), fully typed based on the .input() and .output() you defined on each action.
Examples
Fetching data
<script setup lang="ts">
const { $client } = useNuxtApp();
const { data: todos } = useLazyAsyncData('todos', () => $client.queries.todos.getMany());
</script>
<template>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.value }}
</li>
</ul>
</template>Mutating data
<script setup lang="ts">
const { $client } = useNuxtApp();
const value = ref('');
const addTodo = async (e: SubmitEvent) => {
e.preventDefault();
await $client.mutations.todos.create({ value: value.value });
await refreshNuxtData('todos');
value.value = '';
};
</script>
<template>
<form @submit="addTodo">
<input v-model="value" placeholder="What needs to be done?" />
<button type="submit">Add</button>
</form>
</template>Tanstack Query client
Setup
nuxt.config.ts
Same as the simple client: expose your server URL as a public runtime config value.
export default defineNuxtConfig({
runtimeConfig: {
public: {
TYPEBASE_APP_URL: process.env.TYPEBASE_APP_URL_DEV || process.env.TYPEBASE_APP_URL,
},
},
});Vue Query plugin
Set up VueQueryPlugin with server-side dehydration and client-side hydration so SSR-prefetched data is passed to the browser without an extra round trip:
import { dehydrate, hydrate, QueryClient, VueQueryPlugin, type DehydratedState, type VueQueryPluginOptions } from '@tanstack/vue-query';
export default defineNuxtPlugin((nuxt) => {
const vueQueryState = useState<DehydratedState | null>('vue-query');
const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 5000 } },
});
nuxt.vueApp.use(VueQueryPlugin, { queryClient } satisfies VueQueryPluginOptions);
if (import.meta.server) {
nuxt.hooks.hook('app:rendered', () => {
vueQueryState.value = dehydrate(queryClient);
});
}
if (import.meta.client) {
nuxt.hooks.hook('app:created', () => {
hydrate(queryClient, vueQueryState.value);
});
}
});Typebase plugin
import { createTanstackQueryClient } from 'typebase-io/client';
import type { Router } from '~/utils/typebase/_generated/server';
export default defineNuxtPlugin(() => {
const event = useRequestEvent();
const runtimeConfig = useRuntimeConfig();
const client = createTanstackQueryClient<Router>({
url: runtimeConfig.public.TYPEBASE_APP_URL,
headers: event?.headers,
});
return {
provide: { client },
};
});Querying data
Use onServerPrefetch with suspense() to prefetch data on the server so it's included in the SSR response:
<script setup lang="ts">
import { useQuery } from '@tanstack/vue-query';
const { $client } = useNuxtApp();
const { data: todos, suspense } = useQuery($client.queries.todos.getMany.queryOptions());
onServerPrefetch(async () => {
await suspense();
});
</script>
<template>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.value }}
</li>
</ul>
</template>Mutating data
<script setup lang="ts">
import { useMutation, useQueryClient } from '@tanstack/vue-query';
const { $client } = useNuxtApp();
const queryClient = useQueryClient();
const value = ref('');
const { mutate, isPending } = useMutation(
$client.mutations.todos.create.mutationOptions({
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: $client.queries.todos.getMany.key(),
});
value.value = '';
},
})
);
</script>
<template>
<form @submit.prevent="mutate({ value })">
<input v-model="value" :disabled="isPending" placeholder="What needs to be done?" />
<button type="submit" :disabled="isPending">Add</button>
</form>
</template>Authentication
Authentication requires an auth.ts file in your Typebase project. See the Authentication guide to set it up.
Setup
Auth plugin
Create an auth plugin that injects $auth into the app:
import { createAuthClient } from 'typebase-io/client/auth/vue';
export default defineNuxtPlugin(() => {
const authClient = createAuthClient();
return {
provide: { auth: authClient },
};
});The better-auth client docs are the full reference for everything $auth can do.
Proxy setup
When your Typebase server is deployed on a different domain than your Nuxt app (which is the typical setup), you need a server middleware that proxies auth and RPC requests through your app's domain. This is required for secure, HttpOnly cookies to work correctly.
better-auth does support cookies across different domains, but it is less secure and may have problems on Safari due to third-party cookie restrictions. The proxy approach is strongly recommended.
Proxied requests hop through your Nuxt host before reaching the Typebase server, so they add one extra network hop. In practice this is negligible compared to the cost of the underlying RPC call, especially when both hosts are deployed to the same edge network.
Create a server middleware at server/middleware/typebase.ts:
import { proxyToTypebase } from 'typebase-io/client/auth/nuxt';
export default defineEventHandler(async (event) => {
if (!event.path.startsWith('/api/auth') && !event.path.startsWith('/rpc')) {
return;
}
const runtimeConfig = useRuntimeConfig();
return proxyToTypebase(event, runtimeConfig.public.TYPEBASE_APP_URL);
});This forwards all /api/auth requests (auth sign-in, sign-up, sign-out) and all /rpc requests (your actions) through the Nuxt server, ensuring cookies are set on your app's domain.
Update your client
With the proxy in place, update your client plugin to route browser requests through the proxy by pointing to the current origin on the client:
Simple client:
import { createRouterClient } from 'typebase-io/client';
import type { Router } from '~/utils/typebase/_generated/server';
export default defineNuxtPlugin(() => {
const event = useRequestEvent();
const runtimeConfig = useRuntimeConfig();
const client = createRouterClient<Router>({
url: import.meta.client ? window.location.origin : runtimeConfig.public.TYPEBASE_APP_URL,
headers: event?.headers,
});
return {
provide: { client },
};
});Tanstack Query client:
import { createTanstackQueryClient } from 'typebase-io/client';
import type { Router } from '~/utils/typebase/_generated/server';
export default defineNuxtPlugin(() => {
const event = useRequestEvent();
const runtimeConfig = useRuntimeConfig();
const client = createTanstackQueryClient<Router>({
url: import.meta.client ? window.location.origin : runtimeConfig.public.TYPEBASE_APP_URL,
headers: event?.headers,
});
return {
provide: { client },
};
});If you're using the Tanstack Query client, the app/plugins/tanstack-query.ts plugin from the Tanstack Query setup section is
still required alongside this one.
When running in the browser, the client points to the same origin so requests go through the proxy automatically. On the server, headers: event?.headers forwards the session cookie directly to the Typebase server.
Examples
Sign up / Sign in
<script setup lang="ts">
const { $auth } = useNuxtApp();
const name = ref('');
const email = ref('');
const password = ref('');
const signIn = async () => {
await $auth.signIn.email({ email: email.value, password: password.value });
reloadNuxtApp({ path: '/' });
};
const signUp = async () => {
await $auth.signUp.email({ name: name.value, email: email.value, password: password.value });
reloadNuxtApp({ path: '/' });
};
</script>
<template>
<form>
<input v-model="name" type="text" placeholder="Name" />
<input v-model="email" type="email" placeholder="Email" />
<input v-model="password" type="password" placeholder="Password" />
<button type="button" @click="signIn">Sign in</button>
<button type="button" @click="signUp">Sign up</button>
</form>
</template>Sign out
<script setup lang="ts">
const { $auth } = useNuxtApp();
const signOut = async () => {
await $auth.signOut();
reloadNuxtApp({ path: '/auth' });
};
</script>
<template>
<button @click="signOut">Sign out</button>
</template>Get session
<script setup lang="ts">
const { $auth } = useNuxtApp();
const session = $auth.useSession();
</script>
<template>
<p v-if="session.data">Welcome, {{ session.data.user.name }}!</p>
<p v-else>Not signed in</p>
</template>Protect routes with middleware
export default defineNuxtRouteMiddleware(async (to) => {
const { $auth } = useNuxtApp();
const { data: session } = await $auth.useSession(useFetch);
if (!session.value && to.path !== '/auth') {
return navigateTo('/auth');
}
if (session.value && to.path === '/auth') {
return navigateTo('/');
}
});How routing works
The Router type is auto-generated by npx typebase-io-cli codegen and mirrors your actions/ folder structure:
typebase/actions/
queries/
todos.ts → $client.queries.todos.*
mutations/
todos.ts → $client.mutations.todos.*Each exported action from your files becomes a callable method on the client.
Environment variables
After running npx typebase-io-cli deploy, the CLI saves your server URL to .env. The key depends on the target (TYPEBASE_APP_URL for prod, TYPEBASE_APP_URL_DEV for dev) so both can coexist. Expose it in nuxt.config.ts under runtimeConfig.public so it's available on both server and client:
export default defineNuxtConfig({
runtimeConfig: {
public: {
TYPEBASE_APP_URL: process.env.TYPEBASE_APP_URL,
},
},
});When developing locally, set TYPEBASE_APP_URL to whichever target your frontend should hit, typically the dev deployment:
export default defineNuxtConfig({
runtimeConfig: {
public: {
TYPEBASE_APP_URL: process.env.TYPEBASE_APP_URL_DEV,
},
},
});API Reference
createRouterClient
import { createRouterClient } from 'typebase-io/client';Creates a simple promise-based client. Takes the same options as an oRPC RPCLink. Returns a fully typed client that mirrors your actions/ folder structure.
createTanstackQueryClient
import { createTanstackQueryClient } from 'typebase-io/client';Creates a Tanstack Query-wrapped client. Returns utilities (queryOptions, mutationOptions) for use with useQuery, useMutation, and more. Accepts the same options as createRouterClient plus an optional second argument for Tanstack Query configuration.
createAuthClient
import { createAuthClient } from 'typebase-io/client/auth/vue';Re-exports better-auth's createAuthClient from better-auth/vue. Returns an authClient with methods like signUp.email, signIn.email, signOut, and useSession. The better-auth client docs are the full reference.
getServerAuthCookie
import { getServerAuthCookie } from 'typebase-io/client/auth/nuxt';Reads the current session cookie from the incoming H3 event on the server and returns it as a headers object. Takes the H3 event as its argument. Used to forward the session when making server-side calls to your Typebase server.
getServerSession
import { getServerSession } from 'typebase-io/client/auth/nuxt';Fetches the current user's session from the server. Takes the H3 event and your Typebase server URL. Can be called in server routes, server middleware, and useAsyncData callbacks. Returns the session object or null if the user is not authenticated.
proxyToTypebase
import { proxyToTypebase } from 'typebase-io/client/auth/nuxt';Forwards an incoming H3 event to your Typebase server. Used in server/middleware/typebase.ts to proxy auth and RPC requests through your Nuxt app's domain, enabling secure HttpOnly cookies.