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 !
Why Use Client-Side Route Protection?
- Simplified Implementation: A client-side solution enforces authentication checks directly in React components, no middleware required.
- Dynamic Auth State Handling: React instantly to user authentication changes for a seamless experience.
- 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
);
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}</>;
}
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>
</>
);
}
How It Works
- Initial Check: The PrivateRoute component checks for a valid session on mount.
- Real-Time Updates: The onAuthStateChange listener redirects users who log out while on a protected page.
- 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)