문제 설명
React 애플리케이션에서 서버 사이드 렌더링(SSR)을 사용하지 않을 때, 즉 next.js를 사용하기 전에는 초기 페이지 로드 시 데이터가 없어서 화면이 깜빡이는 현상이 발생할 수 있습니다. 예를 들어, 로그인된 사용자의 정보를 표시할 때, 초기 렌더링에서 데이터가 없으면 "없습니다"라고 표시되었다가, 데이터가 로드되면 "사용자 이메일"로 변경됩니다. 이러한 화면 깜빡임은 사용자 경험을 저하시킬 수 있습니다.
해결 방법
이 문제를 해결하기 위해, 서버 사이드에서 초기 데이터를 가져와 클라이언트로 전달하고, 이를 통해 초기 렌더링 시에 데이터를 사용하여 화면 깜빡임을 방지할 수 있습니다. 이를 위해 Next.js의 서버 사이드 렌더링 기능과 컨텍스트 API를 사용합니다.
구현 단계
1. AuthContext 설정
먼저, 사용자 인증 정보를 관리할 AuthContext를 설정합니다. 여기서는 useAuth 훅과 AuthProvider 컴포넌트를 정의합니다.
"use client";
import { User } from "@supabase/supabase-js";
import {
createContext,
PropsWithChildren,
useContext,
useEffect,
useState,
} from "react";
type AuthContextValue = {
isInitialized: boolean;
isLoggedIn: boolean;
me: User | null;
logIn: (email: string, password: string) => void;
logOut: () => void;
signUp: (email: string, password: string) => void;
};
const initialValue: AuthContextValue = {
isInitialized: false,
isLoggedIn: false,
me: null,
logIn: () => {},
logOut: () => {},
signUp: () => {},
};
const AuthContext = createContext<AuthContextValue>(initialValue);
export const useAuth = () => useContext(AuthContext);
interface AuthProviderProps {
initialIsInitialized: boolean;
initialMe: User | null;
}
export function AuthProvider({
initialIsInitialized,
initialMe,
children,
}: PropsWithChildren<AuthProviderProps>) {
const [isInitialized, setIsInitialized] =
useState<AuthContextValue["isInitialized"]>(initialIsInitialized);
const [me, setMe] = useState<AuthContextValue["me"]>(initialMe);
const isLoggedIn = !!me;
const logIn: AuthContextValue["logIn"] = async (email, password) => {
if (me) return alert("이미 로그인 되어 있어요");
if (!email || !password) return alert("이메일, 비밀번호 모두 채워 주세요.");
const data = { email, password };
const response = await fetch("http://localhost:3000/api/auth/log-in", {
method: "POST",
body: JSON.stringify(data),
});
const user = await response.json();
setMe(user);
};
const logOut = async () => {
if (!me) return alert("로그인하고 눌러주세요");
await fetch("http://localhost:3000/api/auth/log-out", { method: "DELETE" });
setMe(null);
};
const signUp: AuthContextValue["signUp"] = async (email, password) => {
if (me) return alert("이미 로그인 되어 있어요");
if (!email || !password) return alert("이메일, 비밀번호 모두 채워 주세요.");
const data = { email, password };
const response = await fetch("http://localhost:3000/api/auth/sign-up", {
method: "POST",
body: JSON.stringify(data),
});
const user = await response.json();
setMe(user);
};
useEffect(() => {
fetch("http://localhost:3000/api/auth/me").then(async (response) => {
if (response.status === 200) {
const {
data: { user },
} = await response.json();
setMe(user);
}
setIsInitialized(true);
});
}, []);
const value: AuthContextValue = {
isInitialized,
isLoggedIn,
me,
logIn,
logOut,
signUp,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
2. 서버 사이드에서 데이터 가져오기
ProviderLayout 컴포넌트를 통해 서버 사이드에서 초기 데이터를 가져옵니다.
import { AuthProvider } from "@/contexts/auth.context/auth.context";
import { createClient } from "@/supabase/server";
import React from "react";
async function ProvidersLayout({ children }: { children: React.ReactNode }) {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
const isInitialized = true;
const me = user || null;
return (
<AuthProvider initialIsInitialized={isInitialized} initialMe={me}>
{children}
</AuthProvider>
);
}
export default ProvidersLayout;
3. 홈 페이지 컴포넌트
홈 페이지 컴포넌트에서 useAuth 훅을 사용하여 인증 상태를 관리합니다.
"use client";
import { useAuth } from "@/contexts/auth.context/auth.context";
import { useState } from "react";
export default function Home() {
const { me, logIn, logOut, signUp } = useAuth();
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const handleClickLogIn = async () => logIn(email, password);
const handleClickLogOut = async () => logOut();
const handleClickSignUp = async () => signUp(email, password);
return (
<main className="h-screen flex flex-col items-center justify-center">
<h1>현재 로그인한 유저는</h1>
{me ? me.email + "입니다" : "없습니다"}
<hr className="my-10 w-full border-black" />
<input
type="text"
className="input"
placeholder="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="text"
className="input"
placeholder="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button className="button" onClick={handleClickLogIn}>
로그인하기
</button>
<hr className="my-10 w-full border-black" />
<button className="button" onClick={handleClickLogOut}>
로그아웃하기
</button>
<hr className="my-10 w-full border-black" />
<input
type="text"
className="input"
placeholder="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="text"
className="input"
placeholder="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button className="button mt-3" onClick={handleClickSignUp}>
회원가입하기
</button>
</main>
);
}
결론
이렇게 서버 사이드 렌더링을 통해 초기 데이터를 미리 가져와서 화면 깜빡임 현상을 제거할 수 있습니다. 이를 통해 사용자 경험을 개선할 수 있습니다.
'TIL' 카테고리의 다른 글
TIL - Next.js와 Zustand를 사용한 상태 관리 및 SSR 하이드레이션 (0) | 2024.07.10 |
---|---|
TIL- 시스템 설계와 Many-to-Many 관계 적용 (0) | 2024.07.09 |
TIL - 소셜 로그인 기능 추가 도중 트러블 슈팅 (0) | 2024.07.06 |
TIL - 리액트 훅과 가상 DOM의 상호작용 (0) | 2024.07.05 |
TIL - 무한 스크롤 데이터 요청 중 깜빡임 제거 (0) | 2024.07.04 |