Site index
Arrow keys to navigateEnter to open / Esc to close
Writing index
Field note / Frontend engineering

How I built a fullstack authentication system with Next.js, Supabase, and Tailwind

Email sign-in, session handling, and protected routes in a Next.js App Router app, built on Supabase auth with a clean Tailwind interface.

1 min readnext.jssupabaseauthfullstack
On this page
How I built a fullstack authentication system with Next.js, Supabase, and Tailwind

Authentication is the first real fullstack feature most projects need. Here is the shape that has served me well: Supabase for identity, Next.js for routing, and a thin server boundary.

Create the client once

Create a single browser client and reuse it. Creating a new one per render causes subtle session bugs.

import { createClient } from "@supabase/supabase-js";

let client: ReturnType<typeof createClient> | null = null;

export function getSupabase() {
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
  const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
  if (!url || !key) return null;
  client ??= createClient(url, key);
  return client;
}

Returning null when the environment is missing means the UI can degrade gracefully instead of crashing.

Keep secrets on the server

The anon key is public by design, protected by row level security. Service keys are not.

  • Never import a service key into a client component
  • Enforce access with row level security, not with UI logic
  • Treat the client as untrusted at all times

Protect routes at the edge

A redirect in a layout is the simplest reliable guard.

export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
  const session = await getSession();
  if (!session) redirect("/login");
  return <>{children}</>;
}

The form should feel instant

Authentication UX lives or dies on feedback. Disable the button, show a pending state, and announce the result.

<button disabled={isPending} aria-busy={isPending}>
  {isPending ? "Signing in..." : "Sign in"}
</button>

Never tell the user whether an email exists. Generic errors protect your users from enumeration.

Checklist

  1. One client, created lazily
  2. Secrets only on the server
  3. Row level security as the real boundary
  4. Route guards in layouts
  5. Optimistic, accessible feedback on every action

Continue reading

Related articles

Modal interface

Accent system

Choose a palette. The selection is saved on this device.

Accent color themes

28 palettes available

Modal interface

A note worth keeping

A randomly selected thought from the inspiration archive.