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

Moving a frontend application from a Single Page Application (SPA) architecture—typically built with Vite—to a Server-Side Rendered (SSR) framework like Next.js is a significant architectural shift. While the UI components often stay the same, the way you handle authentication changes fundamentally.

In a Vite app, auth is usually client-side only: you fetch a JWT, store it in localStorage or a cookie, and use a React Context provider to manage the session. In Next.js, auth happens at the edge, on the server, and in the browser.

This guide explores how to migrate the three most popular auth providers—Supabase, Clerk, and Auth.js—while avoiding common pitfalls.

The Core Difference: Client-Side vs. Server-Side Auth

In a Vite environment, your auth logic looks like this:

  1. User logs in.
  2. API returns a token.
  3. Token is stored in localStorage.
  4. Every useEffect or useQuery sends this token in the header.

In Next.js, this is an anti-pattern. Because Next.js renders content on the server (SSR) or during build time (SSG), the server needs to know who the user is before the page reaches the browser. This requires HttpOnly Cookies.

1. Migrating Supabase Auth

If you are using Supabase in Vite, you likely use the @supabase/supabase-js client. In Next.js, you need to switch to @supabase/auth-helpers-nextjs or the newer @supabase/ssr package.

The Vite Way (Client Only):

// Initializing in Vite
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(URL, KEY);
Enter fullscreen mode Exit fullscreen mode

The Next.js Way (Server-Side Logic):

You must create a client that can access cookies. In your middleware.ts, you’ll need to refresh the session so the user stays logged in across SSR requests.

import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function updateSession(request: NextRequest) {
  let response = NextResponse.next({ request })

  const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
    cookies: {
      get(name: string) { return request.cookies.get(name)?.value },
      set(name: string, value: string, options: CookieOptions) {
        response.cookies.set({ name, value, ...options })
      },
    },
  })
  await supabase.auth.getUser()
  return response
}
Enter fullscreen mode Exit fullscreen mode

2. Migrating Clerk

Clerk is arguably the easiest to migrate because they handle the heavy lifting of session synchronization between the client and server.

In Vite, you wrap your app in <ClerkProvider>. In Next.js, the pattern is similar, but you gain access to auth() for Server Components and useAuth() for Client Components.

Key Migration Step: Move your environment variables from VITE_CLERK_PUBLISHABLE_KEY to NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY. Then, implement the clerkMiddleware() to protect your routes at the infrastructure level rather than relying on useEffect redirects, which cause layout shifts.

3. The Transition to Auth.js (formerly NextAuth)

If you were using a custom backend or Firebase with Vite, many developers choose to switch to Auth.js when moving to Next.js. Auth.js is built specifically for the Next.js ecosystem.

Unlike Vite where you might have a useAuth hook talking to a /login endpoint, Auth.js provides a unified configuration file (auth.ts) that handles OAuth providers, credentials, and session management automatically.

Handling the Migration Complexity

Rewriting your entire authentication layer, handling middleware, and refactoring every API call to be server-aware is time-consuming. If you are looking to speed up the transition, using a tool like ViteToNext.AI can help automate the structural conversion of your Vite project into a Next.js App Router format, allowing you to focus purely on the auth logic.

Common Pitfalls to Avoid

1. The "Window is not defined" Error

In Vite, you can check localStorage.getItem('token') anywhere. In Next.js, this will crash your build during SSR. Always wrap client-only auth checks in a useEffect or use the 'use client' directive sparingly.

2. Protecting Routes

In Vite, you protect routes in your router (like React Router). In Next.js, it is best practice to protect routes in middleware.ts. This prevents the browser from downloading the page code entirely if the user is unauthorized, which is a significant security and performance boost.

3. Data Fetching

In Vite: client -> fetch -> backend.
In Next.js: Server Component -> database.
If you are logged in, the Server Component can fetch data directly from your DB or Supabase using the server-side session, eliminating the need for an intermediate API route in many cases.

Conclusion

Migrating auth from Vite to Next.js isn't just about changing libraries; it's about changing your mindset from "browser-first" to "server-first." By leveraging Middleware and HttpOnly cookies via tools like Clerk or Supabase SSR, you create a more secure and faster experience for your users.

Further reading: How to automate your migration with ViteToNext.AI

Top comments (0)