Next.js에서 웹 스토리지를 사용할 때 발생하는 문제

서동경·2023년 10월 14일
0

troubleshooting

목록 보기
10/13
post-thumbnail

Next.js에서 클라이언트 측에 상태를 저장하기 위해 웹 스토리지를 사용할 때 발생한 문제를 정리해보았다.

Error 1. window is not Fonund

Next.js 환경에서 세션 스토리지에 시크릿 코드를 저장하고 관리하기 위해 atoms.ts를 아래와 같이 작성하였다.

import { atom } from 'recoil';
import { recoilPersist } from 'recoil-persist';

const { persistAtom } = recoilPersist({
    key: 'sessionStorage',
    storage: window.sessionStorage,
});

export const isAcceptedAtom = atom<boolean>({
    key: 'isAcceptedAtom',
    default: false,
    effects_UNSTABLE: [persistAtom],
});

그러나 빌드 시 아래와 같은 에러가 출력되었다.

ReferenceError: window(document, sessionStorage, localStorage ...) is not defined

Next.js의 SSR 구조는 서버에서 미리 렌더링할 데이터를 만들어서 정적으로 내려줘야하는데, 페이지가 처음 렌더링하는 과정에서는 window, document 또는 웹 스토리지가 없다.

sessionStorage를 undefined로 선언해서 서버에서 window.sessionStorage 객체를 로드하지 않도록 처리해주기 위해 atoms.ts에 아래 코드를 추가해준다.

import { atom } from 'recoil';
import { recoilPersist } from 'recoil-persist';

// 해당 코드 추가
const sessionStorage = typeof window !== 'undefined' ? window.sessionStorage : undefined;

const { persistAtom } = recoilPersist({
    key: 'sessionStorage',
    storage: window.sessionStorage,
});

export const isAcceptedAtom = atom<boolean>({
    key: 'isAcceptedAtom',
    default: false,
    effects_UNSTABLE: [persistAtom],
});

그럼 정상적으로 빌드되는 것을 확인할 수 있다.

Error 2. Recoil에서 상태를 읽어올 때 발생하는 Hydration 에러

Hydrate

SSR시 서버에서 넘겨받는 정적인 HTML을 받으면, 클라이언트가 JS를 통해 interactive한 HTML로 바꾸는 과정

Next.js에서 자주 등장하는 Hydration 에러는 서버 측 렌더링(SSR)과 클라이언트 측 렌더링(CSR)간의 데이터 불일치로 인해 발생한다. Next.js 13에서 'use client'를 통해 CSR을 할 때 겪을 수 있는 문제이다.

먼저 우리는 Error 1을 해결하기 위해 atom에 window.sessionStorage가 아니라 undefinded를 넣어주었다.

그리고 CSR을 하는 클라이언트 컴포넌트에서 atom을 불러오는 경우를 생각해보자. 서버 사이드 렌더링으로 값을 넘겨줄 때는 atom 값이 저장되는 storage가 undefined로 설정된 상태이지만, 클라이언트 컴포넌트에서는 sessionStoreage로 설정된다. 여기서 불일치가 발생하게 되고 Hydration 오류가 발생하게 된다.

따라서 아래와 같이, 클라이언트 컴포넌트의 return문에서 불일치가 발생하는 atom 값을 바로 사용하는 것이 아니라, useState와 useEffect를 통해 atom에 변경이 생겼을 때 비로소 해당 값을 새로운 상태로 저장하여 이 값을 사용해야 한다.

const [isAccepted, setClientIsAccepted] = useState(null);
const [recoilIsAccepted, setIsAccepted] = useRecoilState(isAcceptedAtom);

useEffect(() => {
    setClientIsAccepted(recoilIsAccepted);
}, [recoilIsAccepted]);

const handleFormSubmit = (e: any) => {
    if (secretCode === 'SECRETCODE') {
        setIsAccepted(true);
    }
};

// 그리고 return 문에서 isAccepted를 통해 사용자 권한을 식별하면 된다!

참고 자료

profile
개발 공부💪🏼

0개의 댓글