The Architectural Shift: Client-Side vs. Server-Side Auth
When you build a standard React application using Vite, your authentication logic usually lives entirely in the browser. You likely have a useAuth hook, a Context Provider, and your tokens (JWTs) are stored in localStorage or sessionStorage.
Transitioning to Next.js changes the game. You are no longer just managing client-side state; you are managing sessions across the Client, the Server (SSR), and Middleware. If you don't adjust your auth patterns during migration, you'll end up with "flickering" UI where protected content shows for a split second before the client-side script realizes the user is logged out.
In this guide, we will look at how to migrate three of the most popular auth patterns from a Vite environment to the Next.js App Router.
1. Supabase: Moving from supabase-js to SSR Helpers
In a Vite app, you usually initialize the Supabase client once and export it. In Next.js, you need to create the client dynamically to handle cookies correctly across the server and client.
The Vite Pattern (Client-only)
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(URL, ANON_KEY);
The Next.js Pattern (Server-Client Sync)
With Next.js, you should use @supabase/ssr. You need to define a client for Server Components and another for Client Components. The key difference is the Cookie Store.
// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export function createClient() {
const cookieStore = cookies();
return createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
cookies: {
getAll() { return cookieStore.getAll() },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options))
},
},
});
}
By migrating your auth to the server, you can perform database queries directly in your page components without needing an internal API route.
2. Clerk: The Seamless Transition
Clerk is arguably the easiest to migrate because their SDK is built with Next.js in mind. In a Vite app, you wrap your app in <ClerkProvider>. In Next.js, you do the same in your layout.tsx, but you also gain access to middleware.ts for edge-level protection.
Protecting Routes
In Vite, you likely used a <ProtectedRoute> component that checked user.isLoading. In Next.js, use the middleware to prevent the page from even starting to render if the session is invalid:
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isPublicRoute = createRouteMatcher(['/sign-in(.*)', '/sign-up(.*)', '/']);
export default clerkMiddleware((auth, request) => {
if (!isPublicRoute(request)) {
auth().protect();
}
});
3. Auth.js (NextAuth): The Standard for Custom Backends
If you were using a custom JWT implementation in Vite (e.g., storing a token in a cookie manually), you should migrate to Auth.js. It handles the heavy lifting of OIDC, OAuth, and database sessions.
While rewriting these patterns manually is a great way to learn the nuances of the App Router, developers looking to speed up the transition can use ViteToNext.AI to automatically restructure their project files and component logic for the Next.js environment.
Key Auth.js Implementation
// auth.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [GitHub],
});
In your components, you can now check the session like this:
// Page.tsx (Server Component)
import { auth } from "./auth";
export default async function Page() {
const session = await auth();
if (!session) return <div>Please log in</div>;
return <div>Welcome {session.user?.name}</div>;
}
Handling Protected Redirects
One common pitfall when migrating is using window.location for redirects. In Next.js, you should use the redirect() function from next/navigation.
-
Server Components: Use
redirect('/login'). -
Client Components: Use
useRouter()androuter.push('/login'). -
Middleware: Return a
NextResponse.redirect().
Conclusion
Migrating auth from Vite to Next.js is not just about moving code; it is about shifting your mindset from "fetching data after the page loads" to "validating the user before the page even renders." Whether you choose Supabase, Clerk, or Auth.js, the goal is always to leverage the server for better security and faster perceived performance.
Further reading on automated migration strategies: ViteToNext.AI
Top comments (0)