Home

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.

Note: If you are using the pages 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

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, session }) {
  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, session }}>
      <>{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 session={session}>
          <SupabaseListener serverAccessToken={session?.access_token} />
          {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.