October 12, 2025
O. Wolfson
When I set out to build a reference implementation for Supabase authentication in Next.js 15, I knew the challenges that lay ahead. Having struggled with auth setup in previous projects, I wanted to create a step-by-step guide that any developer could follow—complete with test pages, comprehensive documentation, and real-world examples.
The result? A fully functional PWA with enterprise-grade authentication, meticulously documented across 7 phases.
🔗 Live Demo: https://simple-pwa-eta.vercel.app/
💻 Source Code: https://github.com/owolfdev/simple-pwa
Implementing authentication in modern web apps involves juggling multiple concerns:
Getting all these pieces to work together—especially in Next.js 15's new App Router paradigm—can be tricky. Most tutorials cover the basics but leave you hanging when it comes to production deployment or edge cases.
A complete reference implementation featuring:
Rather than dumping all the code at once and hoping it works, I broke the implementation into discrete, testable phases:
Install Supabase packages, configure environment variables, set up TypeScript paths. Each step is verified with a test page that shows exactly what's configured.
Test Page: /test-setup shows green checkmarks for each requirement.
Create three distinct Supabase clients:
Test Page: /test-supabase verifies each client can connect to Supabase.
Implement the critical middleware that refreshes auth sessions on every request. This is what keeps users logged in as they navigate your app.
typescriptexport async function middleware(request: NextRequest) {
return await updateSession(request);
}
Test Page: /test-middleware confirms middleware runs and sessions refresh.
Create the route structure:
/auth/login - Login page/auth/sign-up - Registration page/auth/confirm - Email confirmation handler/auth/error - Error display/auth/sign-up-success - Post-signup messagingTest Page: /test-routes provides one-click testing of each route.
Build the actual forms with full functionality:
LoginForm - Handles email/password authenticationSignUpForm - User registration with validationLogoutButton - Secure logoutAuthButton - Shows auth status in headerTest Page: /test-auth guides you through the complete signup → confirm → login flow.
Configure production settings in your Supabase dashboard:
/**)Test Page: /test-config provides step-by-step dashboard configuration instructions.
Implement real-world patterns:
Test Page: /test-integration with complete E2E checklist.
Next.js 15's App Router requires careful consideration of where auth checks happen:
Server Components (default):
typescriptimport { createClient } from "@/lib/supabase/server";
export default async function Page() {
const supabase = await createClient();
const { data } = await supabase.auth.getUser();
if (!data.user) {
redirect("/auth/login");
}
return <div>Protected content</div>;
}
Client Components (for interactive features):
typescript"use client";
import { createClient } from "@/lib/supabase/client";
export function MyComponent() {
const [user, setUser] = useState(null);
useEffect(() => {
const supabase = createClient();
supabase.auth.getUser().then(({ data }) => {
setUser(data.user);
});
}, []);
return user ? <div>Logged in!</div> : <div>Not logged in</div>;
}
The middleware is crucial for maintaining auth state:
typescriptexport async function middleware(request: NextRequest) {
let supabaseResponse = NextResponse.next({ request });
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
/* ... */
},
},
}
);
// CRITICAL: This refreshes the session
await supabase.auth.getClaims();
return supabaseResponse;
}
This runs on every request, keeping sessions alive and preventing random logouts.
Balancing PWA features with development experience:
typescriptexport default withPWA({
dest: "public",
register: true,
disable: process.env.NODE_ENV === "development",
})(nextConfig);
PWA is disabled in development to avoid service worker caching issues during development, but fully enabled in production builds.
The most common mistake: forgetting the /** wildcard in Supabase redirect URLs.
❌ Wrong:
https://your-app.vercel.app/
✅ Correct:
https://your-app.vercel.app/**
Without the wildcard, email confirmation links (which go to /auth/confirm) will fail.
Vercel gives you two URLs:
app-abc123-username.vercel.app (changes per deployment)your-app.vercel.app (permanent)Always use the permanent domain URL in your Supabase config.
Make sure your Vercel deployment has the same environment variables as local development:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYThese must be set in Vercel's project settings.
Best for pages that absolutely require authentication:
typescriptexport default async function ProtectedPage() {
const supabase = await createClient();
const { data } = await supabase.auth.getUser();
if (!data.user) {
redirect("/auth/login");
}
return <div>Secure content</div>;
}
Best for pages where you want to show a preview or teaser:
typescriptexport default async function DashboardPage() {
const supabase = await createClient();
const { data } = await supabase.auth.getUser();
return data.user ? (
<div>Full dashboard with data</div>
) : (
<div>
<p>Sign up to access your dashboard</p>
<Link href="/auth/sign-up">Sign up</Link>
</div>
);
}
Show different content based on auth status:
typescript{
user ? (
<div>
<h2>Welcome back, {user.email}!</h2>
<Link href="/dashboard">Go to Dashboard</Link>
</div>
) : (
<div>
<h2>Get started today</h2>
<Link href="/auth/sign-up">Sign Up Free</Link>
</div>
);
}
Each phase includes a dedicated test page that verifies:
/test-setup) - Dependencies and environment/test-supabase) - Client connections/test-middleware) - Session refresh/test-routes) - Page rendering/test-auth) - Login/logout functionality/test-config) - Dashboard setup/test-integration) - End-to-end flowThis test-driven approach ensures you catch issues early, before they compound into mysterious bugs.
During development, the PWA service worker was causing constant page refreshes due to a transpilation error (_async_to_generator is not defined).
Solution: Disable PWA in development mode:
typescriptdisable: process.env.NODE_ENV === "development";
The middleware must exclude static files to avoid unnecessary processing:
typescriptexport const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};
themeColor Moved in Next.js 15In Next.js 15, themeColor must be in a separate viewport export:
typescriptexport const viewport: Viewport = {
themeColor: "#ffffff",
};
Not in the metadata export like in previous versions.
Most auth tutorials give you the happy path. This implementation includes:
✅ Complete error handling - Every error state is handled
✅ Production deployment - Not just "works on localhost"
✅ Mobile responsive - Tested on all screen sizes
✅ PWA support - Installable and offline-capable
✅ Test infrastructure - Verify each step works
✅ Real-world patterns - Multiple auth patterns shown
✅ Comprehensive docs - Everything is documented
Visit https://simple-pwa-eta.vercel.app/ to:
bashgit clone https://github.com/owolfdev/simple-pwa.git
cd simple-pwa
npm install
# Add your Supabase credentials to .env.local
# NEXT_PUBLIC_SUPABASE_URL=your-url
# NEXT_PUBLIC_SUPABASE_ANON_KEY=your-key
npm run dev
Visit http://localhost:3000/docs for the complete implementation guide.
The entire codebase is structured as a reference implementation. You can:
Potential additions to this reference implementation:
All of these build on the foundation established in these 7 phases.
Building authentication doesn't have to be a mysterious black box. By breaking it into discrete phases, testing each step, and documenting thoroughly, we created a production-ready auth system that works reliably across development and production environments.
Whether you're building your first authenticated app or your tenth, having a reference implementation you can trust is invaluable. This project serves that purpose—a complete, tested, documented example of modern authentication in Next.js 15.
/test-setup, /test-supabase, etc.Next.js 15.5.4 - The latest version with App Router and React 19
Supabase - Open-source Firebase alternative with robust auth
TypeScript - Type safety for fewer runtime errors
Tailwind CSS v4 - Utility-first styling with the latest features
PWA Support - Installable, offline-capable web app
Built as an educational resource for developers implementing Supabase authentication in Next.js 15 Progressive Web Apps. All code is production-tested and ready to use as a template.
Questions or suggestions? Check out the GitHub repository or test the live implementation.
If you're using this as a template, here's your quick start:
npm install.env.local with your Supabase credentialsnpm run dev/test-setup to verify configuration/docsThat's it! You'll have working authentication in under an hour.
Happy coding! 🚀