Principes-Artis-Mechanicae / get-p-frontend

Get your People, Get your Projects!
https://beta-get-p.netlify.app/
7 stars 0 forks source link

Persist Storage 가 서버사이드에 존재하지 않아 hydration mismatch 발생하는 버그 해결 #18

Open toothlessdev opened 7 months ago

toothlessdev commented 7 months ago

resolve #17

✨ 구현한 기능

서버 사이드에서 Redux Persist 의 WebStorage API 의 부재로 사전렌더링시 오류 발생

  1. 서버 사이드 Storage 임시로 구현 및 서버사이드와 클라이언트 사이드에서 리턴되는 storage 분기
// /src/store/persist/persistStorage.ts
import createWebStorage from "redux-persist/lib/storage/createWebStorage";

const serverSideStorage = () => {
    return {
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        getItem: (_key: string): Promise<string> => {
            return new Promise((resolve) => {
                resolve("{}");
            });
        },
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        setItem: (_key: string, _item: string): Promise<void> => {
            return new Promise((resolve) => {
                resolve();
            });
        },
    };
};

export const persistStorage = typeof window !== "undefined" ? createWebStorage("local") : serverSideStorage();
  1. PersistGate 에서 typeof window === "undefined", 서버에서 실행시 loading props 가 아닌 children props 를 렌더링하도록 수정

    // /src/store/persist/PersistGate.ts
    if (typeof window === "undefined") return this.props.children;
    return this.state.bootstrapped ? this.props.children : this.props.loading;
  2. persistStore 가 자동으로 persist/PERSIST persist/REHYDRATE 액션을 디스패치 하지 않도록 manualPersist 설정

    // /src/store/store.ts
    export const persistor = persistStore(store, { manualPersist: true } as PersistorOptions);
  3. entry-client.ts 에서 hydration 이후 client 측에서 persist 액션 디스패치 하도록 설정

const root = ReactDOM.hydrateRoot(
    document.getElementById("app") as HTMLElement,
    <BrowserRouter>
        <App isClient={false} />
    </BrowserRouter>,
);

root.render(
    <BrowserRouter>
        <App isClient={true} />
    </BrowserRouter>,
);
export default function App({ isClient }: { isClient: boolean }) {
    useEffect(() => {
        if (isClient) persistor.persist();
    }, [isClient]);

📢 논의하고 싶은 내용

🎸 기타

toothlessdev commented 7 months ago

image

React Profiler 확인결과 hydrateRoot 호출로, 사전렌더링된 페이지에 하이드레이션이 발생하고,

image

PersistGate 에 의해 재렌더링이 발생하는데, 이는

image

State Changed : bootstrapped 즉, persistor.persist() 가 호출되어 클라이언트 사이드에서 WebStorage 로 부터 상태를 복구되고 복구가 완료되었다는 상태 (bootstrapped) 가 변경되며 발생하는 렌더링으로 정상적인 렌더링으로 보임