[hydration] 브라우저 저장소와 Next.js의 렌더링 원리

이주희·2022년 4월 13일
4

React ♥️ Next.js

목록 보기
31/48

브라우저의 저장공간

브라우저에 데이터를 저장할 수 있는 공간을 알아보자!
개발자 도구 > Application에서 볼 수 있다.

1. Local Storage

  • 브라우저를 껐다가 켜도 남아있는다.
    ex) 비회원으로 장바구니 담기한 경우 저장

  • 보안상 중요한 데이터를 저장하는 것은 옳지 않다. console에 찍으면 다~ 나온다,,

2. Session Storage

  • 브라우저를 끄면 사라진다.

3. Cookies

  • 브라우저와 백엔드 간 연결 시 데이터 전달 통로가 될 수 있다.

  • 지정한 시간이 지나면 사라진다.

  • 백엔드 API와 통신할 때 같이 보낼 수 있다.

  • 만료 시간을 지정할 수 있다.

객체 형태로 저장한다.
여기에 accessToken을 저장해보자~ 새로고침 해도 로그인이 풀리지 않게!
⭐️ local storage에는 보안상 중요한 데이터를 저장하는 것이 옳지 않으므로, refreshToken을 배우면 다시 바꿀 것이다.

Local Storage와 Session Storage에는 텍스트만 저장할 수 있다.
👇🏻 객체를 담은 변수를 저장하면 아래와 같이 저장된다.

객체를 저장하고 싶으면, JSON.stringfy(객체)를 사용해야 한다.


Local Storage

accessToken을 Local Storage를 사용하지 않고, global state에만 담아서 사용하면
새로고침을 했을 때 accessToken은 날라가고 없다!

💡 Why?

global state도 변수에 불과하기 때문에 화면을 새로고침하면 브라우저가 프론트엔드로 다시 접속해서 소스파일을 다시 받아와서 다운로드하기 때문에 state, 변수 등에 담겨있는 값은 모두 초기화되어 비어있는 상태가 된다.

👉🏻 해결 방안

accessToken의 값이 초기화되지 않고 유지시키기 위해서 임시로 local storage에 담아주자!
(추후에는 refreshToken을 사용한다.)

1. Local Storage에 accessToken을 담는다.

accessToken이 있는(받아온) 페이지에서 진행한다.

    localStorage.setItem("accessToken", accessToken) // key, value

브라우저에서 Local Storage를 확인해보면 accessToken이 들어있다.

🚨 Error

fetch는 실패했다. Login User 데이터를 받아오지 못한다.

accessToken이 Local Storage에만 담겨있고, headers에 authorization(인증) 데이터 값으로 보내지지 않았기 때문이다.

지금 fetch 할 때 (headers에 담아서) API로 보내는 값은 이전과 마찬가지로 global state에 담겨있는 값이므로, 새로고침하면 역시나 초기화가 되어 값이 비게 된다.

👉🏻 headers에 authorization으로 보내는 데이터를 Local Storage에 담은 값으로 바꿔줘야 한다.
(Local Storage에 담겨있는 값은 새로고침을 해도 남아있다!)

2. Global State에 Local Storage의 값을 가져와서 넣어준다.

src/components/commons/apollo/index.tsx 👈🏻 _app.tsx에서 분리해낸 컴포넌트

  const [accessToken, setAccessToken] = useRecoilState(accessTokenState);
  const myLocalStorageAccessToken = localStorage.getItem("accessToken")
  setAccessToken(myLocalStorageAccessToken || "")

🚨 Error

다시 실행하면, localStorage를 읽을 수 없다는 에러가 나온다.


Next.js가 화면을 렌더링하는 원리

브라우저에 주소를 치고 접속하면,
프론트엔드 서버로 접속해서 html, css, js를 다운받아와서 먼저 그림을 그려준다.
그 다음에 2차적으로 useQuery 등이 실행되어서 백엔드가 데이터를 꺼내와서 2차적으로 화면에 데이터를 그려준다고 알고있었다.

사실 여기에 한단계가 더 있다!! Pre-rendering -> hydration

(1) Pre-rendering

브라우저에서 yarn dev로 접속을 하면
yarn dev(프론트엔드 서버) 프로그램 자체에서 html, css, js를 브라우저에 전달하기 전에 소스코드를 먼저 돌려본다.

사전에 먼저 한번 그려본다고 해서 프리렌더링이라고 한다.
이때 모든 것을 완벽하게 그리는 것은 아니다. 전체적인 구조만 그림을 그린다.
(useEffect 같은 mount 되고 나서 실행되는 것들은 빼고!
onClick, onChange 등의 바인딩도 빠져있다.)

(2) hydration

프리렌더링 이후에 html, css, js를 브라우저로 보내주면 브라우저에서 제대로 그림을 그리고, 여기서 그린 그림과 프리렌더링할때 그린 그림을 비교해서(diffing) 최종 완성형으로 업데이트 해준다.
diffing 이후 최종 완성형으로 업데이트 될 때 onClick, onChange 등이 반영된다.

diffing 이후에 최종 완성형으로 업데이트 해주는 과정을 hydration이라고 한다.
(hydration: onClick 등이 빠져있는 정적인 데이터에 물기를 줘서 움직일 수 있게 해준다는 의미)

Local Storage, Session Storage 등은 브라우저에 있는 것이다.
프리렌더링을 할 때는 (프론트엔드 서버에는) Local Storage라는 개념이 없다.

즉, 프론트엔드 서버에서 yarn dev해서 실행될 때(Pre-rendering)는
Local Storage라는 개념이 없어서 위에서 localStroage를 찾을 수 없다는 에러가 나온 것이다.

Pre-rendering 확인하기

아래 코드에서 window는 브라우저를 의미한다.
이 코드가 브라우저에서 실행될 경우에만 window가 존재한다.

  if(typeof window !== "undefined") {// 브라우저에서 실행되고 있을 때를 의미한다.
    console.log("여기는 브라우저다!")
  } else{ // 브라우저가 아닌 프론트엔드 서버에서 실행될 때를 의미한다.
    console.log("여기는 프론트엔드 서버이다!(yarn dev)")
  }

vsc 콘솔에는 프론트엔드가, 브라우저 콘솔에는 브라우저가 출력되어 나온다.

vsc console

browser console

해결 방법

3. localStorage가 Pre-rendering에서 실행되지 않도록 막는다.

src/components/commons/apollo/index.tsx 👈🏻 _app.tsx에서 분리해낸 컴포넌트

  useEffect(() => {
    const myLocalStorageAccessToken = localStorage.getItem("accessToken");
    setAccessToken(myLocalStorageAccessToken || "");
  }, []);

프리렌더링 당시 실행될 때만 문제가 되는 것이기 때문에
브라우저에서 실행될 때 localStorage.getItem을 하면 문제가 없다.

프리렌더링 때는 useEffect의 내용은 실행하지 않으므로,
useEffect에서 DOM이 Mount 되는 시점에 Local Storage에 접근해보자!!!
(프리렌더링 때는 마운트 전까지만 그린다.)


useEffect 말고 다른 방법을 써도 된당 👇🏻

 /* 첫번째 방법 -> deprecated */
   if (process.browser) {
     // 브라우저
   } else {
     // 프론트엔드 서버
   }

/* 두번째 방법 */
  if (typeof window !== "undefined") {
    // 브라우저
  } else {
    // 프론트엔드 서버
  }

/* 세번째 방법 */
  useEffect(() => {
    // 브라우저
  }, []);
profile
🍓e-juhee.tistory.com 👈🏻 이사중

0개의 댓글