How to Add Supabase Auth in Next.js
DS
Darshan Suthar

How to Add Supabase Auth in Next.js

Authentication is foundational infrastructure in any serious web application. In this guide, you’ll implement Supabase Authentication in a Next.js App Router project with a clean, scalable architecture suitable for production.

We will cover:

  • Project setup

  • Supabase client configuration (browser + server)

  • Auth provider pattern

  • Sign up / Sign in / Sign out

  • Protected routes (server + middleware)

  • Session handling in App Router

1. Create a Supabase Project

  1. Go to Supabase and create a new project.

  2. Navigate to Settings → API

  3. Copy:

  • Project URL

  • anon public key

  1. Enable authentication providers (Email/Password is enabled by default).


2. Install Dependencies

npm install @supabase/supabase-js @supabase/ssr

We use:

  • @supabase/supabase-js → Core client

  • @supabase/ssr → Proper session handling in App Router (server components)


3. Environment Variables

Create .env.local:

NEXT_PUBLIC_SUPABASE_URL=your_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key

Never expose the service_role key to the browser.


4. Create Supabase Clients (Production Pattern)

We create two clients:

  • Browser client

  • Server client (for RSC + route handlers)

/lib/supabase/client.js

import { createBrowserClient } from "@supabase/ssr";

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
  );
}

/lib/supabase/server.js

import { cookies } from "next/headers";
import { createServerClient } from "@supabase/ssr";

export function createClient() {
  const cookieStore = cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
    {
      cookies: {
        get: (name) => cookieStore.get(name)?.value,
        set: (name, value, options) =>
          cookieStore.set({ name, value, ...options }),
        remove: (name, options) =>
          cookieStore.set({ name, value: "", ...options }),
      },
    }
  );
}

This ensures sessions work correctly in:

  • Server Components

  • Route Handlers

  • Middleware


5. Create Auth Context Provider

We manage auth state globally.

/context/AuthProvider.jsx

"use client";

import { createContext, useContext, useEffect, useState } from "react";
import { createClient } from "@/lib/supabase/client";

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const supabase = createClient();
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const getUser = async () => {
      const { data } = await supabase.auth.getUser();
      setUser(data.user);
      setLoading(false);
    };

    getUser();

    const { data: listener } = supabase.auth.onAuthStateChange(
      (_, session) => {
        setUser(session?.user ?? null);
      }
    );

    return () => listener.subscription.unsubscribe();
  }, []);

  return (
    <AuthContext.Provider value={{ user, loading, supabase }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => useContext(AuthContext);

6. Wrap Root Layout

/app/layout.js

import { AuthProvider } from "@/context/AuthProvider";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <AuthProvider>{children}</AuthProvider>
      </body>
    </html>
  );
}

7. Create Sign Up Page (Tailwind Responsive)

/app/signup/page.jsx

"use client";

import { useState } from "react";
import { useAuth } from "@/context/AuthProvider";
import { useRouter } from "next/navigation";

export default function SignupPage() {
  const { supabase } = useAuth();
  const router = useRouter();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSignup = async (e) => {
    e.preventDefault();

    const { error } = await supabase.auth.signUp({
      email,
      password,
    });

    if (!error) router.push("/dashboard");
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-100 px-4">
      <form onSubmit={handleSignup} className="w-full max-w-md bg-white p-6 rounded-xl shadow-lg space-y-4">
        <h1 className="text-2xl font-semibold text-center">Create Account</h1>
        <input type="email" placeholder="Email" className="w-full border p-3 rounded-lg" onChange={(e) => setEmail(e.target.value)} required />
        <input type="password" placeholder="Password" className="w-full border p-3 rounded-lg" onChange={(e) => setPassword(e.target.value)} required />
        <button type="submit" className="w-full bg-black text-white py-3 rounded-lg hover:opacity-90 transition">Sign Up</button>
      </form>
    </div>
  );
}

Fully responsive:

  • Mobile: full width

  • Tablet/Desktop: centered card


8. Protect a Route (Server Component)

/app/dashboard/page.jsx

import { redirect } from "next/navigation";
import { createClient } from "@/lib/supabase/server";

export default async function Dashboard() {
  const supabase = createClient();
  const { data } = await supabase.auth.getUser();

  if (!data.user) redirect("/login");

  return <div className="p-6">Welcome {data.user.email}</div>;
}

This is the correct production way to protect App Router pages.


9. Add Logout Button

"use client";

import { useAuth } from "@/context/AuthProvider";

export default function LogoutButton() {
  const { supabase } = useAuth();

  const handleLogout = async () => {
    await supabase.auth.signOut();
    window.location.href = "/";
  };

  return (
    <button onClick={handleLogout} className="px-4 py-2 bg-red-500 text-white rounded-lg">
      Logout
    </button>
  );
}

10. Middleware Protection (Optional Global Protection)

Create middleware.js in root:

import { createMiddlewareClient } from "@supabase/ssr";
import { NextResponse } from "next/server";

export async function middleware(req) {
  const res = NextResponse.next();
  const supabase = createMiddlewareClient({ req, res });

  const { data } = await supabase.auth.getUser();

  if (!data.user && req.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", req.url));
  }

  return res;
}

/app
  /login
  /signup
  /dashboard
/lib/supabase
  client.js
  server.js
/context
  AuthProvider.jsx
middleware.js

Production Best Practices

  • Never expose service_role key

  • Always protect sensitive pages on the server

  • Use middleware for global protection

  • Enable email confirmation in Supabase

  • Use Row Level Security (RLS) in database

  • Add rate limiting for auth routes

#Supabase Auth in Next.js
DS

Darshan Suthar

JavaScript Developer (Next.js & React Native)

I build accessible and high-performance modern web applications. Passionate about open source, UI/UX design, and sharing knowledge through writing.