How to Seamlessly Combine Next.js with Supabase Auth
If you’ve built a React front‑end with Next.js and are looking for a hassle‑free way to add sign‑up, login, and password‑reset flows, Supabase Auth is a solid choice. The combination lets you keep most of the heavy lifting on the server while still enjoying the static‑site generation and API routes that make Next.js popular. Below is a step‑by‑step guide that walks you through the entire process, from project setup to protecting pages on both the client and the server.
Why Pair Next.js with Supabase Auth?
Next.js already provides a clean separation between server‑side code (via pages/api or the new app directory) and client‑side React components. Supabase, on the other hand, offers a PostgreSQL‑backed authentication layer that mirrors Firebase’s ease of use but with open‑source flexibility. When you bind the two together you get:
- Instant email‑magic‑link or OAuth logins without writing custom back‑end logic.
- Server‑side rendering (SSR) that can read the session before the page reaches the browser, improving SEO and perceived performance.
- A single source of truth for user data, because Supabase stores the auth session in its own JWT‑based cookies.
All of this works out of the box once you configure a few environment variables.
Prerequisites
Before diving in, make sure you have the following:
- Node.js ≥ 16 installed locally.
- A fresh Next.js project (you can create one with
npx create-next-app@latest). - A Supabase account; the free tier is more than enough for a prototype.
- Basic familiarity with React hooks and async/await.
If any of these items are missing, pause the guide and get them sorted first—otherwise you’ll run into avoidable errors later.
Setting Up the Supabase Project
Log into the Supabase dashboard and hit “New Project.” After the database spins up, navigate to the “Authentication” tab and enable the providers you need (email, Google, GitHub, etc.). You’ll also want to copy the API URL and anon public key; they’ll become environment variables in your Next.js app.
Optionally, tweak the “Email templates” section if you prefer a custom look for magic‑link emails. Supabase lets you add your own logo, change the sender name, and even set a redirect URL after a successful login.
Installing Packages in Your Next.js App
From the root of your Next.js project run:
npm install @supabase/supabase-js @supabase/auth-helpers-nextjsThe first library is the core JavaScript client, while the second provides helpers for Next.js‑specific tasks like SSR session handling and route protection.
Configuring the Auth Provider
Create a file called lib/supabaseClient.js (or .ts if you use TypeScript) with the following content:
import { createClient } from '@supabase/supabase-js';export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);
Next, add the environment variables to .env.local:
NEXT_PUBLIC_SUPABASE_URL=your-project-url.supabase.coNEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
Because these variables are prefixed with NEXT_PUBLIC_, they’ll be exposed to the browser—exactly what the client‑side auth flow needs.
Protecting Routes and Server‑Side Rendering
One of the biggest advantages of the Supabase auth helpers is the ability to fetch the user session during SSR. In a page that should only be visible to logged‑in users, add a getServerSideProps function:
import { supabase } from '@/lib/supabaseClient';import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs';
export const getServerSideProps = async (ctx) => {
const supabaseServerClient = createServerSupabaseClient(ctx);
const { data: { session } } = await supabaseServerClient.auth.getSession();
if (!session) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
return { props: { user: session.user } };
};
This snippet checks for a valid session; if none exists, the visitor is redirected to /login. Because the check runs on the server, the protected page never even renders for unauthenticated users, which is good for both security and SEO.
Handling Sessions on the Client
On the client side you’ll likely need to show the current user's name, log out, or trigger a sign‑in flow. The useUser hook from @supabase/auth-helpers-react makes this painless:
import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react';export default function Profile() {
const user = useUser();
const supabase = useSupabaseClient();
if (!user) return null; // or a loading spinner
const handleLogout = async () => {
await supabase.auth.signOut();
// Optionally redirect after logout
};
return (
<div>
<h2>Welcome, {user.email}</h2>
<button onClick={handleLogout}>Log out</button>
</div>
);
}
The hook automatically syncs with the server‑side session, so you won’t see flickering UI where the user appears logged out for a split second.
Creating a Simple Login Page
A minimal login component can rely on Supabase’s built‑in magic‑link flow:
import { useState } from 'react';import { supabase } from '@/lib/supabaseClient';
export default function Login() {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const handleLogin = async (e) => {
e.preventDefault();
const { error } = await supabase.auth.signInWithOtp({ email });
if (error) setMessage(error.message);
else setMessage('Check your inbox for the login link.');
};
return (
<form onSubmit={handleLogin}>
<input
type="email"
placeholder="you@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<button type="submit">Send Magic Link</button>
<p>{message}</p>
</form>
);
}
Because Supabase stores the JWT in an HttpOnly cookie, you don’t need to manage tokens in localStorage—a common source of XSS vulnerabilities.
Common Pitfalls and Tips
- Cookie domain mismatches. If you’re developing locally with
localhostand later deploy to a custom domain, remember to update theAuth > Settings > Site URLin the Supabase dashboard; otherwise redirects will fail. - SSR caching. When using
next/cacheor any CDN, ensure that pages withgetServerSidePropsare not cached for anonymous users, as this could serve a protected page to the wrong visitor. - Refresh token handling. Supabase automatically refreshes the JWT, but if you manually read
session.access_tokenyou should also listen foronAuthStateChangeto keep the client state up to date. - Environment variable leakage. Only the
NEXT_PUBLIC_prefixed keys are exposed to the browser. Keep your service_role key in a separate.env.productionfile and never commit it.
FAQ
Can I use Supabase Auth with Next.js middleware?
Yes. The @supabase/auth-helpers-nextjs package includes a supabaseMiddleware you can add to middleware.ts. It checks the request’s cookie and redirects unauthenticated users before they reach the page.
Do I need a separate database for user profiles?
Supabase stores basic user metadata (email, provider, timestamps) automatically. For richer profiles—like avatars or preferences—you can create a profiles table and link it to the auth.users ID using a foreign key.
Is server‑side rendering compatible with static site generation (SSG)?
Pages that truly depend on a logged‑in user must use getServerSideProps or the new app directory’s fetch with cache: "no-store". Purely static pages can still be generated with getStaticProps, but they won’t have access to the session at build time.
How do I log out across multiple tabs?
Supabase writes the session cookie as HttpOnly; when you call signOut(), the cookie is cleared for the current tab. To sync the logout across tabs, listen for the storage event on window and trigger a client‑side redirect if the session disappears.