DEV Community

Digital dev
Digital dev

Posted on

Migrating Auth from Vite to Next.js: Supabase, Clerk, and Auth.js Patterns That Actually Work

Introduction

Transitioning from a Client-Side Rendered (CSR) Vite application to a Server-Side Rendered (SSR) Next.js application is a rite of passage for many scaling React projects. While the UI components often transfer easily, authentication is usually the biggest hurdle.

In a Vite app, auth typically lives in localStorage or memory, managed by a client-side provider. In Next.js, auth needs to work across the server (Server Components, Middleware) and the client. This shift requires a fundamental change in how you handle sessions and cookies. This guide explores the migration patterns for the three most popular auth solutions: Supabase, Clerk, and Auth.js.

1. The Supabase Strategy: Moving to Cookies

In a Vite setup, you likely used supabase.auth.getSession() inside a useEffect. In Next.js, this approach causes "layout shift" because the server doesn't know the user's state during the initial render.

The Vite Pattern (Client-only)

// App.tsx
useEffect(() => {
  const session = supabase.auth.session();
  setUser(session?.user);
}, []);
Enter fullscreen mode Exit fullscreen mode

The Next.js Pattern (SSR)

To migrate, you must implement the ssr package. You'll need to create a server-side client that interacts with Next.js cookies.

  1. Server Client: Use @supabase/ssr to create a client in utils/supabase/server.ts that can read/write cookies.
  2. Middleware: Use Middleware to refresh the user's session before the page loads. This ensures user is available in Server Components.

If you find the structural changes between CSR and SSR architectures overwhelming, tools like ViteToNext.AI can help automate the boilerplate migration of your Vite project into a Next.js App Router structure, allowing you to focus on logic rather than configuration.

2. The Clerk Strategy: From Provider to Middleware

Clerk is perhaps the easiest to migrate because it handles the heavy lifting of cookie management, but the architecture still changes. In Vite, you wrapped your app in <ClerkProvider>. In Next.js, the provider is still there, but the Middleware becomes the source of truth.

Migration Steps:

  • Update the Wrapper: Move your <ClerkProvider> to the root layout.tsx.
  • Protect Routes: Instead of checking if (!user) inside your components, use clerkMiddleware() in middleware.ts. This prevents the client from even downloading the JS for protected routes if the user isn't authenticated.
// middleware.ts
import { clerkMiddleware } from "@clerk/nextjs/server";

export default clerkMiddleware();

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};
Enter fullscreen mode Exit fullscreen mode

3. The Auth.js (NextAuth) Pattern

If you were using a custom backend or a library like react-auth-kit in Vite, you are likely moving toward Auth.js. The biggest shift here is moving from fetching a /me endpoint from your backend to using the auth() function in Server Components.

The Client-Side Trap

Avoid the temptation to use useSession() everywhere. In Next.js, useSession() triggers an extra fetch request. Instead, fetch the session on the server:

// dashboard/page.tsx
import { auth } from "@/auth";

export default async function Page() {
  const session = await auth();
  if (!session) return <div>Access Denied</div>;

  return <h1>Welcome {session.user.name}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

Key Architectural Differences to Remember

1. Token Storage

In Vite, you might have stored a JWT in localStorage. In Next.js, this is a security risk and won't work for SSR. You must move to HttpOnly Cookies. All three libraries mentioned above handle this by default in their Next.js SDKs.

2. The Loading State

In Vite, you usually show a spinner while the client-side JS boots up and checks the session. In Next.js, the server knows the auth status before the HTML is sent. This means you can eliminate "Auth Flash" (where the login button shows for a split second before the profile icon appears).

3. Route Protection

  • Vite: Handled via <ProtectedRoute> components and React Router guards.
  • Next.js: Handled via middleware.ts (global) or server-side redirects in layout.tsx or page.tsx (granular).

Conclusion

Migrating auth isn't just about changing libraries; it's about changing how data flows. By moving your auth logic to the server using Middleware and Server Components, you improve both the security and the user experience of your application. Whether you choose the seamless integration of Clerk, the database-centric approach of Supabase, or the flexibility of Auth.js, the key is to embrace the server-first mindset.

Further reading: Automate your transition from Vite to Next.js

Top comments (0)