DEV Community

frontcodelover
frontcodelover

Posted on

Securing Client-Side Routes in Next.js with Supabase

Ensuring that only authenticated users can access certain pages or features is crucial in web applications. This article explains how to protect routes in Next.js using Supabase authentication with a fully client-side approach. We'll avoid middleware and focus on simplicity and dynamic updates. You Shall not pass !


Image description

Why Use Client-Side Route Protection?

  1. Simplified Implementation: A client-side solution enforces authentication checks directly in React components, no middleware required.
  2. Dynamic Auth State Handling: React instantly to user authentication changes for a seamless experience.
  3. Flexibility: Perfect for Single Page Applications (SPAs) or dynamic rendering.

Setting Up Supabase

First, initialize Supabase in a supabase.js file:

'use client';

import { createClient } from '@supabase/supabase-js';

if (!process.env.NEXT_PUBLIC_SUPABASE_URL || !process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
  throw new Error('Missing Supabase environment variables');
}

export const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);
Enter fullscreen mode Exit fullscreen mode

This ensures the Supabase client is available for authentication operations.

Creating the PrivateRoute Component

The PrivateRoute component ensures only authenticated users can access the wrapped content. It checks for a valid session on mount and listens for authentication state changes.

'use client';

import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { supabase } from '@/lib/supabase';

export default function PrivateRoute({ children }: { children: React.ReactNode }) {
  const [isLoading, setIsLoading] = useState(true);
  const router = useRouter();

  useEffect(() => {
    const checkAuth = async () => {
      try {
        const { data: { session } } = await supabase.auth.getSession();
        if (!session) {
          router.replace('/signin');
          return;
        }
        setIsLoading(false);
      } catch (error) {
        console.error('Error verifying authentication:', error);
        router.replace('/signin');
      }
    };

    checkAuth();

    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange((_event, session) => {
      if (!session) {
        router.replace('/signin');
      }
    });

    return () => {
      if (subscription) {
        subscription.unsubscribe();
      }
    };
  }, [router]);

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return <>{children}</>;
}
Enter fullscreen mode Exit fullscreen mode

Key Features

  • Session Validation: Checks for a valid session on mount using supabase.auth.getSession().
  • Auth State Listener: Automatically redirects users who log out or whose session expires.
  • Graceful Loading: Displays a loading state while verifying authentication.

Applying the PrivateRoute Wrapper

To use PrivateRoute, wrap the protected content in your layout or pages.

Creating a Layout Component

A layout ensures that the authentication check is applied to all nested pages:

'use client';

import PrivateRoute from '@/components/PrivateRoute';

export default function PrivateLayout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <PrivateRoute>
        {children}
      </PrivateRoute>
    </>
  );
}

Enter fullscreen mode Exit fullscreen mode

How It Works

  1. Initial Check: The PrivateRoute component checks for a valid session on mount.
  2. Real-Time Updates: The onAuthStateChange listener redirects users who log out while on a protected page.
  3. Reusable Structure: The layout structure simplifies extending protection to other app sections.

Benefits of This Approach

  • Real-Time Protection: Auth state changes are instantly reflected.
  • Component-Based: No need for additional middleware or server-side setup.
  • Client-Focused: Ideal for SPAs or apps relying on client-side rendering.

Conclusion

Using a client-side approach with Next.js and Supabase, you can implement robust route protection while maintaining a dynamic user experience. This method leverages Supabase's session management and Next.js's client components to enforce access control effectively. Give it a try and keep your app secure! 🚀

Follow me on
X (twitter)
Linkedin

Top comments (0)