Skip to main content
RapidDev - Software Development Agency
supabase-tutorial

How to Use Supabase with Nuxt.js

To use Supabase with Nuxt.js, install the @nuxtjs/supabase module and add your Supabase URL and anon key to nuxt.config.ts. The module provides useSupabaseClient and useSupabaseUser composables that work in both server and client contexts. Use these composables in your pages and components to perform CRUD operations, handle authentication, and listen for real-time updates without manual client initialization.

What you'll learn

  • How to install and configure the @nuxtjs/supabase module in a Nuxt 3 project
  • How to use useSupabaseClient and useSupabaseUser composables for data and auth
  • How to perform CRUD operations and handle authentication in Nuxt pages
  • How to protect routes with Nuxt middleware and Supabase auth
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read15-20 minNuxt 3+, @nuxtjs/supabase v1+, @supabase/supabase-js v2+March 2026RapidDev Engineering Team
TL;DR

To use Supabase with Nuxt.js, install the @nuxtjs/supabase module and add your Supabase URL and anon key to nuxt.config.ts. The module provides useSupabaseClient and useSupabaseUser composables that work in both server and client contexts. Use these composables in your pages and components to perform CRUD operations, handle authentication, and listen for real-time updates without manual client initialization.

Setting Up Supabase in a Nuxt.js Application

Nuxt.js has an official Supabase module that handles client initialization, cookie-based session management for SSR, and provides composables for data fetching and auth. This tutorial walks through installing the module, configuring it, building a data-fetching page, implementing email/password auth, and protecting routes with middleware.

Prerequisites

  • A Nuxt 3 project created with npx nuxi init
  • A Supabase project with at least one table
  • Node.js 18+ installed
  • SUPABASE_URL and SUPABASE_KEY from the Supabase Dashboard (Settings > API)

Step-by-step guide

1

Install the @nuxtjs/supabase module

The @nuxtjs/supabase module is the official Nuxt integration for Supabase. It auto-configures the Supabase client with cookie-based session storage for SSR, provides Vue composables, and handles auth redirects. Install it as a dependency and add it to the modules array in nuxt.config.ts.

typescript
1# Install the module
2npm install @nuxtjs/supabase
3
4# nuxt.config.ts
5export default defineNuxtConfig({
6 modules: ['@nuxtjs/supabase'],
7 supabase: {
8 redirectOptions: {
9 login: '/login',
10 callback: '/confirm',
11 exclude: ['/', '/about'],
12 },
13 },
14})

Expected result: The Supabase module is installed and configured. The Supabase client is automatically available via composables in all components and pages.

2

Add environment variables for Supabase credentials

Create a .env file in the project root with your Supabase URL and anon key. The @nuxtjs/supabase module reads SUPABASE_URL and SUPABASE_KEY automatically — you do not need the NUXT_PUBLIC_ prefix for these specific variables. Find these values in the Supabase Dashboard under Settings > API.

typescript
1# .env
2SUPABASE_URL=https://your-project.supabase.co
3SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Expected result: Environment variables are loaded by the Supabase module. The client connects to your Supabase project.

3

Fetch and display data using useSupabaseClient

Use the useSupabaseClient composable to get a typed Supabase client in any component or page. Combine it with Nuxt's useAsyncData for SSR-compatible data fetching. The data is fetched on the server during SSR and hydrated on the client, providing fast initial loads. Remember that the table must have RLS policies that allow the current user (or anon role) to read the data.

typescript
1<script setup lang="ts">
2const client = useSupabaseClient()
3
4const { data: posts, error } = await useAsyncData('posts', async () => {
5 const { data, error } = await client
6 .from('posts')
7 .select('id, title, content, created_at')
8 .eq('published', true)
9 .order('created_at', { ascending: false })
10
11 if (error) throw error
12 return data
13})
14</script>
15
16<template>
17 <div>
18 <h1>Blog Posts</h1>
19 <p v-if="error">Failed to load posts.</p>
20 <article v-for="post in posts" :key="post.id">
21 <h2>{{ post.title }}</h2>
22 <p>{{ post.content }}</p>
23 </article>
24 </div>
25</template>

Expected result: The page renders a list of published posts fetched from Supabase. Data is loaded during SSR for fast initial page loads.

4

Implement email/password authentication

Use useSupabaseClient to call auth methods and useSupabaseUser to reactively track the current user. The module handles session persistence via cookies automatically. Build a login form that calls signInWithPassword and a signup form that calls signUp. The useSupabaseUser composable returns a reactive ref that updates when the auth state changes.

typescript
1<script setup lang="ts">
2const client = useSupabaseClient()
3const user = useSupabaseUser()
4const email = ref('')
5const password = ref('')
6const errorMsg = ref('')
7
8async function handleLogin() {
9 const { error } = await client.auth.signInWithPassword({
10 email: email.value,
11 password: password.value,
12 })
13 if (error) {
14 errorMsg.value = error.message
15 } else {
16 navigateTo('/dashboard')
17 }
18}
19
20async function handleSignup() {
21 const { error } = await client.auth.signUp({
22 email: email.value,
23 password: password.value,
24 })
25 if (error) {
26 errorMsg.value = error.message
27 } else {
28 navigateTo('/confirm')
29 }
30}
31
32async function handleLogout() {
33 await client.auth.signOut()
34 navigateTo('/login')
35}
36</script>
37
38<template>
39 <div v-if="user">
40 <p>Logged in as {{ user.email }}</p>
41 <button @click="handleLogout">Log out</button>
42 </div>
43 <form v-else @submit.prevent="handleLogin">
44 <input v-model="email" type="email" placeholder="Email" required />
45 <input v-model="password" type="password" placeholder="Password" required />
46 <p v-if="errorMsg" class="text-red-500">{{ errorMsg }}</p>
47 <button type="submit">Log in</button>
48 <button type="button" @click="handleSignup">Sign up</button>
49 </form>
50</template>

Expected result: Users can sign up, log in, and log out. The UI reactively updates to show the authenticated state.

5

Insert data with RLS protection

When inserting data into Supabase from a Nuxt component, the request uses the authenticated user's JWT. RLS policies on the table determine whether the insert is allowed. Always include the user's ID as the owner field so the RLS policy can verify the user owns the record they are creating.

typescript
1<script setup lang="ts">
2const client = useSupabaseClient()
3const user = useSupabaseUser()
4const title = ref('')
5const content = ref('')
6
7async function createPost() {
8 if (!user.value) return
9
10 const { data, error } = await client
11 .from('posts')
12 .insert({
13 title: title.value,
14 content: content.value,
15 author_id: user.value.id,
16 published: false,
17 })
18 .select()
19
20 if (error) {
21 console.error('Insert failed:', error.message)
22 } else {
23 navigateTo(`/posts/${data[0].id}`)
24 }
25}
26</script>

Expected result: Authenticated users can create new posts. The RLS policy ensures they can only set their own user ID as the author.

6

Add route middleware for custom auth guards

While the module's redirectOptions handle basic auth guards, you may need custom logic for specific routes. Create Nuxt middleware files that check auth state and redirect based on user roles or other conditions. Use defineNuxtRouteMiddleware and apply it to specific pages.

typescript
1// middleware/auth.ts
2export default defineNuxtRouteMiddleware(async (to) => {
3 const user = useSupabaseUser()
4
5 if (!user.value) {
6 return navigateTo('/login')
7 }
8})
9
10// middleware/admin.ts
11export default defineNuxtRouteMiddleware(async (to) => {
12 const user = useSupabaseUser()
13 const client = useSupabaseClient()
14
15 if (!user.value) {
16 return navigateTo('/login')
17 }
18
19 // Check admin role from profiles table
20 const { data } = await client
21 .from('profiles')
22 .select('role')
23 .eq('id', user.value.id)
24 .single()
25
26 if (data?.role !== 'admin') {
27 return navigateTo('/unauthorized')
28 }
29})
30
31// pages/admin.vue
32<script setup lang="ts">
33definePageMeta({
34 middleware: 'admin',
35})
36</script>

Expected result: Protected pages redirect unauthenticated users to login and non-admin users to an unauthorized page.

Complete working example

pages/posts/index.vue
1<script setup lang="ts">
2// Complete Nuxt page: List posts + create new post
3// Requires: @nuxtjs/supabase module configured
4// Table: public.posts with RLS enabled
5
6const client = useSupabaseClient()
7const user = useSupabaseUser()
8const title = ref('')
9const content = ref('')
10const creating = ref(false)
11
12// Fetch published posts (SSR-compatible)
13const { data: posts, refresh } = await useAsyncData('posts', async () => {
14 const { data, error } = await client
15 .from('posts')
16 .select('id, title, content, created_at')
17 .eq('published', true)
18 .order('created_at', { ascending: false })
19 .limit(20)
20
21 if (error) throw error
22 return data
23})
24
25// Create a new post (authenticated users only)
26async function createPost() {
27 if (!user.value) return
28 creating.value = true
29
30 const { error } = await client
31 .from('posts')
32 .insert({
33 title: title.value,
34 content: content.value,
35 author_id: user.value.id,
36 published: true,
37 })
38
39 creating.value = false
40 if (!error) {
41 title.value = ''
42 content.value = ''
43 await refresh()
44 }
45}
46</script>
47
48<template>
49 <div class="max-w-2xl mx-auto p-6">
50 <h1 class="text-3xl font-bold mb-6">Posts</h1>
51
52 <form v-if="user" @submit.prevent="createPost" class="mb-8 space-y-4">
53 <input v-model="title" placeholder="Post title" required
54 class="w-full p-2 border rounded" />
55 <textarea v-model="content" placeholder="Write your post..."
56 class="w-full p-2 border rounded h-32" />
57 <button type="submit" :disabled="creating"
58 class="px-4 py-2 bg-blue-600 text-white rounded">
59 {{ creating ? 'Creating...' : 'Create Post' }}
60 </button>
61 </form>
62
63 <article v-for="post in posts" :key="post.id" class="mb-6 p-4 border rounded">
64 <h2 class="text-xl font-semibold">{{ post.title }}</h2>
65 <p class="mt-2 text-gray-600">{{ post.content }}</p>
66 <time class="text-sm text-gray-400">{{ new Date(post.created_at).toLocaleDateString() }}</time>
67 </article>
68 </div>
69</template>

Common mistakes when using Supabase with Nuxt.js

Why it's a problem: Using NUXT_PUBLIC_SUPABASE_URL instead of SUPABASE_URL, which the module does not read automatically

How to avoid: The @nuxtjs/supabase module expects SUPABASE_URL and SUPABASE_KEY as environment variable names. These are configured specifically for the module and do not need the NUXT_PUBLIC_ prefix.

Why it's a problem: Calling useSupabaseClient outside of setup() or a composable, where it is not available

How to avoid: Vue composables can only be called during the setup phase. If you need the client in a utility function, pass it as a parameter or use the Nuxt plugin system.

Why it's a problem: Forgetting RLS policies on the table, causing queries to return empty arrays without errors

How to avoid: Always create RLS policies for every operation you need. Check the Supabase Dashboard for tables without policies. Empty results from a query with no error usually means an RLS policy is missing.

Best practices

  • Use useAsyncData with unique keys for SSR-compatible data fetching that avoids duplicate requests
  • Rely on the module's redirectOptions for basic auth guards and use custom middleware only for complex logic
  • Always use the anon key (SUPABASE_KEY) for the module configuration — never the service role key
  • Create RLS policies that match your application's access patterns before writing frontend queries
  • Use useSupabaseUser() to reactively track auth state instead of manually calling getUser() on every page
  • Handle loading and error states in templates to provide feedback when data fetching fails
  • Set up the email confirmation callback route at /confirm to handle signUp redirects

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have a Nuxt 3 project and want to add Supabase for authentication and data. Walk me through installing @nuxtjs/supabase, configuring it, fetching data in a page with useAsyncData, and implementing email/password login with useSupabaseClient.

Supabase Prompt

Set up @nuxtjs/supabase in my Nuxt 3 project. Configure redirectOptions to protect all routes except / and /about. Create a posts page that fetches published posts using useAsyncData and allows authenticated users to create new posts.

Frequently asked questions

Does @nuxtjs/supabase handle SSR session management automatically?

Yes, the module uses cookie-based session storage that works in both server and client contexts. Sessions are automatically shared between server-rendered pages and client-side navigation without any manual configuration.

Can I use Supabase Realtime with the Nuxt module?

Yes. Use useSupabaseClient() to get the client and subscribe to channels with .channel().on('postgres_changes', ...).subscribe(). Set up the subscription in onMounted and clean up in onUnmounted to avoid memory leaks.

How do I generate TypeScript types for my Supabase tables?

Run supabase gen types typescript --project-id your-project-ref > types/database.ts, then pass the generated type to useSupabaseClient<Database>() for type-safe queries.

Why does useSupabaseUser return null on the server during SSR?

This usually means the session cookie is not being sent with the request. Ensure you are using the latest version of @nuxtjs/supabase and that your auth callback route is set up correctly at /confirm.

Can I use the Nuxt Supabase module with Nuxt 2?

No, @nuxtjs/supabase is designed for Nuxt 3 only. For Nuxt 2, manually initialize the Supabase client using @supabase/supabase-js in a Nuxt plugin.

Can RapidDev help integrate Supabase with my Nuxt application?

Yes, RapidDev can set up the full Supabase integration for your Nuxt app, including auth flows, data fetching patterns, real-time subscriptions, and deployment configuration.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.