• Что бы вступить в ряды "Принятый кодер" Вам нужно:
    Написать 10 полезных сообщений или тем и Получить 10 симпатий.
    Для того кто не хочет терять время,может пожертвовать средства для поддержки сервеса, и вступить в ряды VIP на месяц, дополнительная информация в лс.

  • Пользаватели которые будут спамить, уходят в бан без предупреждения. Спам сообщения определяется администрацией и модератором.

  • Гость, Что бы Вы хотели увидеть на нашем Форуме? Изложить свои идеи и пожелания по улучшению форума Вы можете поделиться с нами здесь. ----> Перейдите сюда
  • Все пользователи не прошедшие проверку электронной почты будут заблокированы. Все вопросы с разблокировкой обращайтесь по адресу электронной почте : info@guardianelinks.com . Не пришло сообщение о проверке или о сбросе также сообщите нам.

Managing Supabase Auth State Across Server & Client Components in Next.js

Lomanu4 Оффлайн

Lomanu4

Команда форума
Администратор
Регистрация
1 Мар 2015
Сообщения
1,481
Баллы
155
This article intends to save you 10+ hours of your valuable time, effort, and ‘developer’ pain, which I think is an entirely different kind of pain

In your Next.js project, you'll need a way to keep UI elements like your Navbar (a Client component) in sync with Supabase Auth state

You might be thinking - React Context to the rescue.

But wait!


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

states:

React context is not supported in Server Components, making them only applicable to Client Components.
Your Navbar is a Client component, seems all good.

But,

Supabase Server-Side Auth recommends access to Auth User data, using only supabase.auth.getUser() in Server Components.

Example from Supabase Official Guide →

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.




// app/private/page.tsx

import { redirect } from 'next/navigation'

import { createClient } from '@/utils/supabase/server'

export default async function PrivatePage() {
const supabase = await createClient()

const { data, error } = await supabase.auth.getUser()
if (error || !data?.user) {
redirect('/login')
}

return <p>Hello {data.user.email}</p>
}

This creates a disconnect between how you access Auth data on the server versus the client.

So, how can your Client components (like Navbar) & Server components be in sync with the authentication state?

Let's find out.

The Flow Works Like This:

  1. Root layout fetches the initial user state server-side
  2. This data initializes the AuthProvider
  3. Navbar and other client components access auth state via the context
  4. When auth changes (login/logout), the context updates all components
  5. Server Components independently verify auth status on each request
1. Server-Side Authentication Layer


First, create a reliable server-side authentication layer:


// utils/auth.ts
import { createClient } from '@/utils/supabase/server';
import { User } from '@supabase/supabase-js';

export async function getUser(): Promise<User | null> {
const supabase = await createClient();
const { data, error } = await supabase.auth.getUser();

if (error || !data?.user) {
return null;
}

return data.user;
}
2. Layout-Based Authentication Sharing


Use Next.js layouts to fetch Auth data from server side once and pass it down:


// app/layout.tsx
import { getUser } from '@/utils/auth';
import Navbar from '@/components/Navbar';
import { AuthProvider } from '@/components/AuthProvider';

export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const user = await getUser();

return (
<html lang="en">
<body>
<AuthProvider initialUser={user}>
<Navbar />
{children}
</AuthProvider>
</body>
</html>
);
}
3. Client-Side Auth Provider


Create a React Context to watch for Auth change & provide Auth context to all Client components:


// components/AuthProvider.tsx
"use client";

import { createContext, useState, useEffect, useContext, ReactNode } from 'react';
import { User } from '@supabase/supabase-js';
import { createClient } from '@/utils/supabase/client';

type AuthContextType = {
user: User | null;
isLoading: boolean;
};

const AuthContext = createContext<AuthContextType>({
user: null,
isLoading: true,
});

export const useAuth = () => useContext(AuthContext);

export function AuthProvider({
children,
initialUser
}: {
children: ReactNode;
initialUser: User | null;
}) {
const [user, setUser] = useState<User | null>(initialUser);
const [isLoading, setIsLoading] = useState(false);
const supabase = createClient();

useEffect(() => {
// Initialize with SSR data
setUser(initialUser);
setIsLoading(false);

// Listen for auth changes on the client
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
setUser(session?.user || null);
}
);

return () => subscription.unsubscribe();
}, [initialUser]);

return (
<AuthContext.Provider value={{ user, isLoading }}>
{children}
</AuthContext.Provider>
);
}
4. Client-Side Auth Synchronization


For your client components (like Navbar), in addition to consuming Auth Context from AuthProvider, create a mechanism to sync with auth changes:


// components/Navbar.tsx
"use client";

import { useAuth } from '@/components/AuthProvider';
import { signOut } from "app/auth/actions";
import Link from 'next/link';

export default function Navbar() {
const { user } = useAuth();
const supabase = createClient();

const handleSignOut = async () => {
await supabase.auth.signOut();
};

return (
<nav className="p-4 flex justify-between items-center bg-gray-100">
<Link href="/" className="font-bold text-lg">My App</Link>

<div>
{user ? (
<div className="flex items-center gap-4">
<span>Hello, {user.email}</span>
<form action={signOut} className="w-full">
<button
type="submit"
className="w-full text-left"
>
Logout
</button>
</form>
</div>
) : (
<Link
href="/login"
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Sign In
</Link>
)}
</div>
</nav>
);
}
4. Auth State for Server Components


For your server components, use the Supabase recommended pattern. For example:


// app/profile/page.tsx
import { redirect } from 'next/navigation';
import { getUser } from '@/utils/auth';
import ProfileDetails from '@/components/ProfileDetails';

export default async function ProfilePage() {
const user = await getUser();

if (!user) {
redirect('/login');
}

// Fetch additional user data from Supabase if needed
// This can include profile data beyond the auth user

return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">Profile</h1>
<ProfileDetails user={user} />
</div>
);
}

// 6. Client Component that receives user data from parent
// components/ProfileDetails.tsx
"use client";

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

export default function ProfileDetails({ user }: { user: User }) {
return (
<div className="bg-white p-6 rounded shadow">
<h2 className="text-xl mb-4">Your Profile</h2>
<p><strong>Email:</strong> {user.email}</p>
<p><strong>User ID:</strong> {user.id}</p>
<p><strong>Last Sign In:</strong> {new Date(user.last_sign_in_at || '').toLocaleString()}</p>
</div>
);
}
Benefits of This Approach

  • Server Components: Can access user data directly via getUser()
  • Client Components: Get real-time auth state via the context hook (useAuth())
  • No Duplication: Auth logic is centralized and consistent
  • Performance: Server-side verification for protected routes
  • Seamless UX: UI stays in sync with auth state

This approach gives you the best of both worlds - server-side protection for routes and data access while maintaining a reactive UI that responds to authentication changes


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

 
Вверх Снизу