Build a Complete Profile Settings System with Supabase + Next.js (That Doesn’t Suck)

FMFrank Mendez·
Build a Complete Profile Settings System with Supabase + Next.js (That Doesn’t Suck)

Most profile pages are either too basic or unnecessarily complex. Here’s a clean, scalable approach using Supabase and Next.js that actually feels good to build—and use.

Let’s be honest: most “profile settings” pages are either an afterthought… or a Frankenstein monster of tabs, modals, and half-working features.

So I built one the way it should be done.

Clean. Scalable. Secure. No nonsense.

Here’s how I designed a complete profile system using Supabase + Next.js App Router—and why each decision matters.


The Goal

A single, intuitive page where users can:

  • Update personal info

  • Upload a profile photo

  • Manage social links

  • Change their password

  • Enable two-factor authentication (2FA)

All inside:

/dashboard/profile

No tabs. No confusion. Just scroll and update.

Simple wins.


🧱 The Layout: One Page, Five Sections

Instead of splitting everything into tabs (which users hate more than captchas), the page is structured as stacked cards:

  1. General Information

  2. Profile Photo

  3. Social Links

  4. Password

  5. Two-Factor Authentication

Each section has its own Save Changes button—so users don’t lose progress if something fails.

This is small UX detail, big impact.


👤 General Information (Keep It Flexible)

We store:

  • full_name

  • pronouns

  • bio

  • company

  • location

  • website

And here’s a key decision:

👉 Email is read-only

Why?

Because email changes are security-sensitive and should live in a dedicated flow—not casually inside a profile form.


🖼️ Avatar Upload (Done Right)

Stored in Supabase Storage:

avatars/{user_id}/avatar.{ext}

Constraints:

  • JPG, PNG, GIF

  • Max 2MB

Behavior:

  • Upload → replaces old file

  • Remove → deletes file + clears avatar_url

No versioning headaches. No orphaned files.

Just clean storage hygiene.


🔗 Social Links (No Overengineering)

Seven supported platforms:

  • Twitter/X

  • LinkedIn

  • GitHub

  • Instagram

  • Facebook

  • YouTube

  • TikTok

Each is just a URL field.

No fancy validation. No API calls.

Because let’s be real—users will paste whatever they want anyway.


🔐 Password Change (Secure but Not Painful)

Flow:

  1. Verify current password

  2. Validate:

    • New ≠ Current

    • New === Confirm

  3. Update via Supabase

supabase.auth.updateUser({ password })

Re-authentication step prevents session hijacking abuse.


🔒 Two-Factor Authentication (TOTP)

This is where things get serious.

We use Supabase MFA (TOTP only)—no SMS, no hardware keys.

Enable Flow

  1. User clicks "Enable 2FA"

  2. Supabase returns:

    • QR code

    • Manual secret

  3. User scans with apps like:

    • Google Authenticator

    • Authy

    • 1Password

  4. User enters 6-digit code

  5. Verify → done

Once enabled, login now requires a second step.


Disable Flow

Simple:

  • Confirm action

  • Call:

supabase.auth.mfa.unenroll()

Done.


🚧 The Important Part: Login Enforcement

This is where most people mess up.

After login:

  • Check MFA assurance level

  • If user has 2FA but hasn’t completed it → redirect

if (nextLevel === 'aal2' && currentLevel === 'aal1') {
  redirect('/auth/mfa')
}

This ensures:

👉 Password alone is never enough once 2FA is enabled

That’s real security—not checkbox security.


🧠 Database Design (Minimal but Scalable)

We extend the profiles table with:

  • bio, pronouns, company, location, website

  • all social links

And here’s the smart part:

👉 No 2FA columns in the database

Why?

Because Supabase Auth handles MFA state.

Don’t duplicate what your auth provider already manages.


🧩 Feature Architecture

Everything lives in a clean module:

features/profile/

With:

  • Server actions (updateProfile, updateAvatar, etc.)

  • Typed forms

  • Isolated components per section

And yes:

👉 All mutations use server actions

Cleaner than API routes. Less boilerplate. Better DX.


⚡ Why This Approach Works

Most implementations fail because they:

  • Mix auth logic with UI

  • Overcomplicate validation

  • Scatter features across multiple pages

  • Forget real-world edge cases

This one avoids all that by:

✔ Keeping everything modular
✔ Letting Supabase handle auth complexity
✔ Using simple, predictable UX patterns
✔ Designing for failure (per-section saves)


🚀 Final Thoughts

A profile system sounds boring… until you realize:

👉 It touches auth, storage, database, UX, and security all at once.

Do it wrong, and users feel it immediately.

Do it right, and nobody notices—which is exactly the point.


If you’re building a SaaS, dashboard, or any user-based app, this is one of those features worth doing properly.

Because nothing says “unfinished product” faster than a broken profile page.

💬 Leave a Comment

Want to join the conversation?