코드 출처 : https://github.com/Cluster-Taek/next14-boilerplate
import AuthProvider from './auth-provider';
import ModalProvider from './modal-provider';
import { QueryProvider } from './query-provider';
import SessionProvider from '@/contexts/session-provider';
interface ICoreProviderProps {
children?: React.ReactNode;
}
const CoreProvider = ({ children }: ICoreProviderProps) => {
return (
<SessionProvider>
<AuthProvider>
<QueryProvider>
<ModalProvider>{children}</ModalProvider>
</QueryProvider>
</AuthProvider>
</SessionProvider>
);
};
export default CoreProvider;
SessionProvider
'use client';
import { SessionProvider } from 'next-auth/react';
export default SessionProvider;
- 'use client';:
- Next.js의 App Directory에서 클라이언트 컴포넌트임을 명시.
- 이 파일이 클라이언트 환경에서만 실행되도록 함.
- SessionProvider 가져오기:
- NextAuth.js에서 제공하는 컨텍스트 컴포넌트.
- 세션 데이터를 컨텍스트로 공유하여 애플리케이션 전반에서 인증 상태를 확인하거나 사용자 정보를 사용할 수 있음.
- 내보내기:
- SessionProvider를 기본 내보내기로 제공.
- 이렇게 하면, 별도의 커스터마이징 없이 간단히 사용할 수 있음.
사용 목적
- 인증 상태 관리:
- 사용자가 로그인한 상태인지 확인.
- 세션 데이터를 통해 사용자 정보를 전역적으로 공유.
- 클라이언트 사이드:
- 이 코드는 클라이언트 환경에서 동작하며, 세션 데이터를 렌더링할 때 React 컴포넌트에서 직접 접근 가능.
장점
- 간소화:
- 별도의 세션 관리 로직을 작성하지 않고도 NextAuth.js와 통합 가능.
- 유연성:
- 세션 데이터는 useSession 훅으로 접근하여 필요한 정보를 쉽게 가져옴.
- SSR 및 CSR 호환:
- 서버에서 세션 데이터를 프리로드하여 빠른 초기 렌더링 제공.
- 클라이언트에서도 상태를 동기화.
AuthProvider
'use client';
import Spinner from '@/components/common/spinner';
import { Session } from 'next-auth';
import { useSession } from 'next-auth/react';
import { usePathname, useRouter } from 'next/navigation';
import React, { PropsWithChildren, createContext, useContext, useEffect } from 'react';
interface IAuthContext {
initialized: boolean;
session: Session;
}
export const AuthContext = createContext<IAuthContext | null>(null);
export function useAuth() {
const result = useContext(AuthContext);
if (!result?.initialized) {
throw new Error('Auth context must be used within a AuthProvider!');
}
return result;
}
const publicPageList = ['/login'];
const isPublicPage = (pathname: string) => {
return publicPageList.includes(pathname);
};
const AuthProvider = ({ children }: PropsWithChildren) => {
const router = useRouter();
const pathname = usePathname();
const { data: session, status } = useSession();
const loading = status === 'loading';
useEffect(() => {
if (loading) {
return;
}
if (session && isPublicPage(pathname)) {
router.push('/');
} else if (!session && !isPublicPage(pathname)) {
router.push('/login');
}
}, [loading, router, session, pathname]);
if (loading || (session && isPublicPage(pathname))) {
return <Spinner />;
}
if (isPublicPage(pathname)) {
return <>{children}</>;
}
if (!session?.user) {
return <Spinner />;
}
return <AuthContext.Provider value={{ initialized: true, session }}>{children}</AuthContext.Provider>;
};
export default React.memo(AuthProvider);
주요 동작
- 초기 로딩:
- next-auth가 세션 상태를 확인하는 동안 스피너가 표시됩니다.
- 페이지 접근 제한:
- 인증되지 않은 사용자가 비공개 페이지에 접근하면 로그인 페이지로 리다이렉트.
- 인증된 사용자가 로그인 페이지에 접근하면 홈('/')으로 리다이렉트.
- 인증 상태 제공:
- 인증이 완료되면 AuthContext.Provider가 세션 데이터를 자식 컴포넌트에 제공.
AuthContext와 useAuth Hook
export const AuthContext = createContext<IAuthContext | null>(null);
export function useAuth() {
const result = useContext(AuthContext);
if (!result?.initialized) {
throw new Error('Auth context must be used within a AuthProvider!');
}
return result;
}
- AuthContext:
- 인증 정보를 공유하기 위해 생성된 컨텍스트.
- IAuthContext 타입을 통해 initialized 상태와 session 정보를 포함.
- useAuth Hook:
- 컨텍스트 데이터를 쉽게 사용할 수 있도록 제공.
- AuthContext가 초기화되지 않은 상태에서 호출하면 에러를 던짐.
공개 페이지 확인 로직
const publicPageList = ['/login'];
const isPublicPage = (pathname: string) => {
return publicPageList.includes(pathname);
};
- publicPageList:
- 인증이 필요하지 않은 공개 페이지들의 경로 목록.
- 여기서는 '/login'만 포함되어 있음.
- isPublicPage 함수:
- 현재 경로(pathname)가 공개 페이지인지 확인.
인증 로직과 상태 관리
const AuthProvider = ({ children }: PropsWithChildren) => {
const router = useRouter();
const pathname = usePathname();
const { data: session, status } = useSession();
const loading = status === 'loading';
useEffect(() => {
if (loading) {
return;
}
if (session && isPublicPage(pathname)) {
router.push('/');
} else if (!session && !isPublicPage(pathname)) {
router.push('/login');
}
}, [loading, router, session, pathname]);
- useSession:
- next-auth의 훅으로 세션 데이터(session)와 상태(status)를 제공.
- status가 'loading'이면 세션 상태를 아직 확인 중인 상태.
- useEffect:
- 인증 상태에 따라 페이지를 리다이렉트.
- 동작:
- 세션이 있고 현재 페이지가 공개 페이지 → router.push('/').
- 세션이 없고 현재 페이지가 공개 페이지가 아님 → router.push('/login').
로딩 처리와 UI 렌더링
if (loading || (session && isPublicPage(pathname))) {
return <Spinner />;
}
if (isPublicPage(pathname)) {
return <>{children}</>;
}
if (!session?.user) {
return <Spinner />;
}
return <AuthContext.Provider value={{ initialized: true, session }}>{children}</AuthContext.Provider>;
- 로딩 중: 스피너(Spinner) 컴포넌트 렌더링.
- 공개 페이지 접근 중: 그대로 children을 렌더링.
- 세션이 없는 경우: 추가 확인을 위해 스피너 렌더링.
- 세션이 유효한 경우: AuthContext.Provider를 통해 컨텍스트 데이터 제공.
AuthContext 에서 export default React.memo(AuthProvider) 하는 이유
- AuthProvider 컴포넌트의 불필요한 재렌더링을 방지하여 성능을 최적화하기 위함입니다.
React.memo란?
- React.memo는 React의 고차 컴포넌트(HOC, Higher-Order Component)로, 컴포넌트가 동일한 props로 호출될 때 렌더링을 생략할 수 있도록 최적화를 제공합니다.
- 즉, 부모 컴포넌트가 재렌더링되더라도, React.memo로 감싼 자식 컴포넌트는 props가 변경되지 않았다면 재렌더링되지 않습니다.
왜 AuthProvider에 React.memo를 적용했을까?
- AuthProvider의 props가 변하지 않으면 재렌더링할 필요가 없음
- AuthProvider의 역할은 인증 상태를 관리하고, 컨텍스트를 제공하는 것입니다.
- 만약 children이나 AuthProvider 내부에서 사용하는 상태(useSession, usePathname 등)가 변하지 않았다면, 컴포넌트를 다시 렌더링할 이유가 없습니다.
- React.memo를 사용함으로써, props가 변하지 않는 한 재렌더링을 방지할 수 있습니다.
- 컨텍스트 재생성을 줄여 성능 최적화
- AuthProvider가 다시 렌더링되면, 내부의 AuthContext.Provider도 다시 생성됩니다.
- 이로 인해, useAuth로 컨텍스트를 사용하는 하위 컴포넌트들이 불필요하게 다시 렌더링될 수 있습니다.
- React.memo는 이러한 문제를 줄여줍니다.
- 애플리케이션 성능 최적화
- AuthProvider는 전역 상태와 인증 로직을 관리하기 때문에, 애플리케이션에서 자주 사용될 가능성이 큽니다.
- 불필요한 렌더링을 줄이면 성능이 크게 개선될 수 있습니다.
- children이 변경되지 않으면 렌더링 방지
- React.memo는 AuthProvider에 전달된 children을 props로 감시합니다.
- children이 변경되지 않으면 AuthProvider도 다시 렌더링되지 않으므로 효율적인 렌더링이 가능합니다.
'개발 공부 > React' 카테고리의 다른 글
| Next.js에서 레이아웃 구성 (3) / ModalProvider (1) | 2024.12.29 |
|---|---|
| Next.js에서 레이아웃 구성 (2) / QueryProvider (0) | 2024.12.26 |
| Next.js에서 레이아웃 구성하기 (0) | 2024.12.22 |
| Next.js의 app 디렉토리 구조와 파일 역할 (1) | 2024.12.21 |
| React createContext / useContext (0) | 2024.12.18 |