Mudasir Fayaz

How I Built a Fullstack Authentication System with Next.js, Supabase, and Tailwind

AuthorMudasir Fayaz
October 8, 2025
Next.js Authentication with Supabase

Authentication is one of the first real challenges every fullstack developer faces. When I started building my Next.js project, I wanted a solution that was secure, easy to maintain, andbeautifully integrated with modern UI frameworks. That’s how I ended up using Next.js for the frontend,Supabase for authentication and backend services, andTailwind CSS for styling.

Why This Stack?

- Next.js offers hybrid rendering (SSR + SSG), API routes, and seamless deployment.
- Supabase provides an open-source backend-as-a-service with authentication, database, and storage built-in.
- Tailwind CSS ensures rapid UI development with a consistent design system.

“The beauty of this stack lies in its simplicity — clean architecture, minimal setup, and full control.”

Step 1: Setting Up the Project

Start with a fresh Next.js app using TypeScript and install Tailwind CSS:

npx create-next-app@latest auth-system --typescript
cd auth-system
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Then configure your tailwind.config.ts and include Tailwind directives in your global CSS file.

Step 2: Connecting Supabase

Create a new project on Supabaseand get your API keys. Then, install the Supabase client:

npm install @supabase/supabase-js

Create a supabaseClient.ts file to initialize your connection:

// lib/supabaseClient.ts
import { createClient } from '@supabase/supabase-js';

export const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

Now Supabase is ready to handle signups, logins, and session tracking.

Step 3: Building the Auth Form

Let’s create a simple login and signup form using Tailwind CSS:

// components/AuthForm.tsx
'use client';
import { useState } from 'react';
import { supabase } from '@/lib/supabaseClient';

export default function AuthForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [loading, setLoading] = useState(false);

  async function handleLogin() {
    setLoading(true);
    const { error } = await supabase.auth.signInWithPassword({ email, password });
    if (error) alert(error.message);
    setLoading(false);
  }

  async function handleSignup() {
    setLoading(true);
    const { error } = await supabase.auth.signUp({ email, password });
    if (error) alert(error.message);
    setLoading(false);
  }

  return (
    <div className="max-w-sm mx-auto mt-12 bg-white p-6 rounded-xl shadow-md">
      <h2 className="text-2xl font-semibold text-gray-800 mb-4 text-center">Welcome</h2>
      <input
        type="email"
        placeholder="Email"
        className="w-full border rounded-md px-3 py-2 mb-3 focus:ring-2 focus:ring-blue-500"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        type="password"
        placeholder="Password"
        className="w-full border rounded-md px-3 py-2 mb-3 focus:ring-2 focus:ring-blue-500"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button
        onClick={handleLogin}
        disabled={loading}
        className="w-full bg-blue-600 text-white py-2 rounded-md hover:bg-blue-700 transition mb-2"
      >
        {loading ? 'Loading...' : 'Login'}
      </button>
      <button
        onClick={handleSignup}
        disabled={loading}
        className="w-full bg-gray-100 text-gray-800 py-2 rounded-md hover:bg-gray-200 transition"
      >
        Sign Up
      </button>
    </div>
  );
}

With just a few lines, you have a fully functional authentication form connected to Supabase. You can now add email verification and password reset with minimal setup.

Step 4: Handling Sessions and Protected Routes

Supabase automatically manages sessions with JWTs. You can use Next.js middleware to protect private pages:

// middleware.ts
import { NextResponse } from 'next/server';
import { supabase } from '@/lib/supabaseClient';

export async function middleware(req: Request) {
  const { data } = await supabase.auth.getSession();
  if (!data.session) {
    return NextResponse.redirect(new URL('/login', req.url));
  }
  return NextResponse.next();
}

This ensures that only logged-in users can access protected routes like dashboards or profiles.

Step 5: Styling with Tailwind

Tailwind CSS makes it easy to design responsive, minimal UI components. You can add animations, themes, or transitions effortlessly — keeping the UX clean and consistent.

Conclusion

Building a fullstack authentication system with Next.js, Supabase, and Tailwind CSS is surprisingly simple yet powerful. Supabase handles all the heavy lifting — from user management to sessions — while Next.js and Tailwind make the experience fast and beautiful.

This stack is perfect for startups, side projects, and even production apps that need secure, scalable authentication without building a backend from scratch.

MUDASIR