React Query - Hydration(SSR)

박정호·2023년 1월 24일
13

React Query

목록 보기
13/14
post-thumbnail

🚀 Start

이번에 내가 알아가고, 공부해보고 싶은 내용들을 프로젝트에 구현시켜보며 꼭 알고가면 좋을 것 같은 부분이 있었다.

아직은 Next.js을 이용한 SSR방식의 흐름 및 개발방식이 익숙하지 않고 Next.js에서의 React Query의 사용은 해본적이 없어서 공부하게 되었다.

CSR은 초기 로딩 속도 저하, SEO 한계, 느린 인터넷 속도에선 유저가 빈 화면만 보게 되는 유저 경험의 약점이 있고, SSR은 느린 페이지 전환과 서버 부하가 많다는 문제가 있다. 이러한 CSR, SSR 한계점들을 개선하기 위해 SSR with Hydration기법 등장했다.

Hydration은 첫 페이지는 SSR 방식으로 하고 이후 페이지 이동시엔 CSR로 이뤄진다.


⭐️ React Query는 SSR을 구현하는 방식에는 두가지가 있다.

initialData

: SSR 메서드로 불러온 응답을 React Query 기본값으로 넣어주는 방법

getStaticPropsgetServerSideProps 에서 원하는 API 를 요청하고 그에 대한 응답을 page 에 props 로 내려주고 그 값을 react-queryinitialData 로 넣어주는 방법이다.

initialData를 통한 방법은 데이터를 명시해주면 되기 때문에 구현이 간단하지만, 여러 컴포넌트에서 Reacy Query 사용시 해당 컴포넌트까지 props drilling을 주의해야하며, 같은 응답을 원하는 query가 여러개인 경우 initialData을 넘겨줘야하기 때문에 비효율적이다.

export async function getStaticProps() {
  const posts = await getPosts()
  return { props: { posts } }
}

function Posts(props) {
  const { data } = useQuery({
    queryKey: ['posts'],
    queryFn: getPosts,
    initialData: props.posts,
  })

  // ...
}

Hydration

: SSR 내에서 prefetch를 통해 쿼리를 불러온 뒤, queryClient에서 dehydrate한 상태값으로 페이지에 전달

  • getStaticPropsgetServerSideProps에서 prefetch을 통해 데이터를 요청하고, 해당 cache를 dehydrate해서 클라이언트에 그것을 rehydrate하는 것.
  • Hydration을 통한 방법은 SSR 방식의 개발시 원하는 쿼리를 prefetch하고 해당 쿼리를 사용하는 컴포넌트에서는 동일한 키로 useQuery만 호출하면 된다.
// _app.jsx
import {
  Hydrate,
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'

export default function MyApp({ Component, pageProps }) {
  const [queryClient] = React.useState(() => new QueryClient())

  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={pageProps.dehydratedState}>
        <Component {...pageProps} />
      </Hydrate>
    </QueryClientProvider>
  )
}

💡 참고하자!
👉 React Query - hydration 공식문서
👉 React Query - SSR



🧐 Hydration?

아니 일단 Hydration이 뭔데?

: Hydrate : 수화시키다, 수분을 유지시키다.


개발자 관점에서의 Hydrate

  • 정적인 메마른 HTML에 수분을 보충(동적인 상태로 변화)하는 과정
  • Server Side 단에서 렌더링된 정적 페이지와 번들링된 JS파일을 클라이언트에게 보낸 뒤, 클라이언트단에서 HTML 코드와 React인 JS코드를 서로 매칭 시키는 과정

Next.js 을 사용하는 핵심 이유

CSR은 첫페이지에 빈 HTML을 가져와서 JS파일을 해석하여 화면을 구성하기 때문에 JS파일이 로드될 때까지 빈화면을 보여줘 사용자 경험이 나쁘며, 초기 HTML 파일이 비어있기 때문에 봇이 데이터 수집에 어려움이 따라 (SSR에 비해) SEO가 좋지 않다.

반면 SSR 프레임워크인 Next.js는 Pre-Rendering을 통해서 페이지를 미리 렌더링하여 HTML을 가져온 다음 번들링 된 JS파일을 가져오기 때문에 사용자와 검색 엔진 크롤러에게 바로 렌더링된 페이지를 전달할 수 있다.

하지만, 이때 사용자가 받은 화면은 빈화면이 아닐뿐 단순한 웹화면만 보여주는 HTML이고 JS요소는 하나도 없는 상태이기 때문에 단순 클릭 같은 이벤트 리스너도 DOM요소에 적용되지 않은 상태이다.

👍 그래서 이때 JS 코드들이 이전에 보내진 HTLM DOM 위에서 한번 더 렌더링 하게 되어 필요로 하던 요소들이 채워지며 수분을 공급한다는 뜻으로 Hydrate이라는 용어를 사용하는 것이다.

요약

  • React.js : Html과 JS파일을 한꺼번에 보내고 클라이언트가 js 코드를 통해 웹 화면을 렌더링

  • Next.js : Pre-Rendering된 웹 페이지를 클라이언트에게먼저 보내고, React가 번들링된 자바스크립트 코드들을 클라이언트에게 전송함.


render() / hydrate()

React의 큰 특징 중 하나는 Virtual DOM이다. 이를 통해 DOM 변경사항이 있을시 Virtual DOM과 비교해 바뀐 부분만 실제 DOM에 적용한다.

따라서, ReactDOM.render()를 통해 매번 새로운 웹 페이지를 구성할 DOM을 생성하는 것이 아니라 이전에 렌더링 되었던 컴포넌트라면 새로 렌더링되는 것이 아니라 업데이트만 시켜준다. (효율적인 업데이트를 위해 React의 DOM diffing 알고리즘을 사용)

ReactDOM.hydrate()의 경우 render()와 같지만, HTML 내용이 ReactDOMServer에 의해 렌더링된 컨테이너를 Hydrate하는데 사용된다.


확실히 알아야할 것!

위에서 본 ReactDOM.hydrate(), ReactDOMServer 등은 React에서 SSR 적용을 위한 렌더링 설정으로, Webpack 및 Babel 등 여러가지 설정들을 수반해야 한다. 따라서, SSR 적용을 더욱 쉽게 적용하기 위해 사용하는 것이 Next.js 프레임워크인 것이다.

Next.js에서 내부적으로 ReactDOM.hydrate()을 사용하고 있기 때문에, hydrate 과정에 대해 중요하지 않게 생각하지 않을 수 있지만 Next.js의 렌더링 과정을 아는 것이 SSR을 이해하는데 큰 도움이 될 것이다.

💡 dehydrate & hydrate & Rehydrate

1️⃣ 서버가 완성된 HTML을 내려준다.

  • Dehydrate는 수분을 없앤다라는 뜻. 즉 동적인 것을 정적으로 만드는 행위.
    (ReactDOMServer 사용)

2️⃣ JS가 실행되면서 리액트가 정적인 HTML과 store를 동적인 리액트 컴포넌트 트리와 store로 변환하는 과정이 발생 (hydrate)

3️⃣ 이때 hydrate가 일어나면 화면이 한번 더 렌더되는 현상이 발생

  • 그래서 SSR의 경우 ReactDOM의 hydrate 메소드를 사용하는 것.

👉 Next.js의 렌더링 과정(Hydrate) 알아보기

💡 React-DOM

왜 우리는 react-dom을 설치할까?

리액트는 순수 자바스크립트로 컴포넌트를 만들어 브라우저를 구성한다. 그리고 브라우저는 HTML, CSS, 순수 자바스크립트 이 세가지만 이해하며 이용할 수 있다.

하지만 리액트는 순수 자바스크립트가 아닌 JSX라는 JS 확장 문법을 사용한다. 따라서 Babel을 통해 순수 자바스크립트로 변환해야 브라우저가 이해할 수 있다.

그리고 우리가 생성한 컴포넌트를 HTML과 연결하는 작업을 해줘야 하는데, 이를 react-dom이 해결해준다.

ReactDom.render(element, container[, callback])
: 제공된 container에 element를 렌더하고 component에 대한 참조를 반환
ReactDOM.render(
	<React.StrictMode>
  		<App />
  	</React.StrictMode>,
  	document.getElementById('root')
);


👍 Next.js + React Query

본론으로 돌아오면, React Query는 서버에서 데이터를 prefetch하여 queryClient에 전달하는 방법을 지원한다.

React Query는 Next.js 서버에서 여러 개의 query를 prefetch하고 그 query들을 queryClient에 dehydrate하는 것을 지원한다. 즉, 서버는 페이지 로드 시 즉시 사용할 수 있는 마크업을 미리 렌더링할 수 있으며, JS를 사용할 수 있게 되면 React Query는 라이브러리 자체의 기능으로 이러한 query들을 업그레이드하거나 hydrate할 수 있다.

// .app.js
import { Hydrate } from "react-query/hydration";

const App = ({ Component, pageProps }) => {
  ...

  return (
    
      <QueryClientProvider client={getClient()}>
        <Hydrate state={pageProps.dehydratedState}>
          <Loading />
          <Component {...pageProps} />
        </Hydrate>
        <ReactQueryDevtools />
      </QueryClientProvider>
  );
};


🔗 Reference
👉 [React Query] Next.js + React Query로 무한 스크롤과 SSR 구현하기
👉 [Next.js] React Query로 SSR 구현하기
👉 리액트의 hydration이란?

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글