Supabase Auth with Next.js app directory
The Next.js Auth Helpers package configures Supabase Auth to store the user's session in a cookie, rather than localStorage
. This makes the users's session available server-side - in Server Components and Route Handlers - and is automatically sent along with any requests to Supabase.
The
app
directory in Next.js is still in beta and expected to change. For examples using thepages
directory check out Auth Helpers in Next.js.
To learn more about Supabase and the Next.js 13 app directory, check out this playlist.
Install the Next.js helper library#
npm install @supabase/auth-helpers-nextjs
Next.js Server Components and the
app
directory are experimental and likely to change.
Set up environment variables#
Retrieve your project's URL and anon key from your API settings in the dashboard, and create a .env.local
file with the following environment variables:
1NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL 2NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
Configure Middleware#
Middleware runs immediately before each route in rendered. Next.js only provides read access to headers and cookies in Server Components and Route Handlers, however, Supabase needs to be able to set cookies and headers to refresh expired access tokens. Therefore, you must call the getSession
function in middleware.js
in order to use a Supabase client in Server Components or Route Handlers.
Create a new middleware.js
file in the root of your project and populate with the following:
import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
export async function middleware(req) {
const res = NextResponse.next()
const supabase = createMiddlewareSupabaseClient({ req, res })
await supabase.auth.getSession()
return res
}
Supabase Provider#
All Client Components need to share a single instance of the Supabase client. We can wrap our application in a <SupabaseProvider />
and use React Context to create a global Supabase instance.
Create a new file at /app/supabase-provider.jsx
and populate with the following:
'use client'
import { createContext, useContext, useEffect, useState } from 'react'
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { useRouter } from 'next/navigation'
const Context = createContext(undefined)
export default function SupabaseProvider({ children }) {
const [supabase] = useState(() => createBrowserSupabaseClient())
const router = useRouter()
useEffect(() => {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange(() => {
router.refresh()
})
return () => {
subscription.unsubscribe()
}
}, [router, supabase])
return (
<Context.Provider value={{ supabase }}>
<>{children}</>
</Context.Provider>
)
}
export const useSupabase = () => {
const context = useContext(Context)
if (context === undefined) {
throw new Error('useSupabase must be used inside SupabaseProvider')
}
return context
}
Modify layout.jsx
to wrap the application with the <SupabaseProvider>
component:
import './globals.css'
import SupabaseProvider from './supabase-provider'
export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<SupabaseProvider>{children}</SupabaseProvider>
</body>
</html>
)
}
Now any of our Client Components can use the useSupabase
hook to ensure they are using the same instance of a Supabase client.
Creating a Supabase Client#
Client Components#
While Server Components are great for data fetching, we still need to use Supabase client-side for authentication and realtime subscriptions.
As mentioned above, it is important that all Client Components share a single instance of the Supabase client. We can use the useSupabase
hook we created above to ensure this is the case.
'use client'
import { useState } from 'react'
import { useSupabase } from './supabase-provider'
export default function NewPost() {
const [content, setContent] = useState('')
const { supabase } = useSupabase()
const handleSave = async () => {
const { data } = await supabase.from('posts').insert({ content }).select()
}
return (
<>
<input onChange={(e) => setContent(e.target.value)} value={content} />
<button onClick={handleSave}>Save</button>
</>
)
}
check out this example for making the user's session available to all Client Components.
Server Components#
In order to use Supabase in Server Components, you need to have implemented the middleware.ts
steps above 👆
import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { headers, cookies } from 'next/headers'
// do not cache this page
export const revalidate = 0
export default async function ServerComponent() {
const supabase = createServerComponentSupabaseClient({
headers,
cookies,
})
const { data } = await supabase.from('posts').select('*')
return <pre>{JSON.stringify(data, null, 2)}</pre>
}
check out this example for redirecting unauthenticated users - protected pages.
Route Handlers#
In order to use Supabase in Route Handlers, you need to have implemented the middleware.ts
steps above 👆
import { createRouteHandlerSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import { headers, cookies } from 'next/headers'
// do not cache this page
export const revalidate = 0
export async function GET() {
const supabase = createRouteHandlerSupabaseClient({
headers,
cookies,
})
const { data } = await supabase.from('posts').select('*')
return NextResponse.json(data)
}
Check out this repo for a full example including authentication, realtime and protected pages.