-
Next.js 렌더링 방식 및 react-query를 이용한 hydrateFrontend/NextJS 2023. 5. 1. 21:41
1. next.js가 SSR을 수행하는 방식
웹서버에서 html+css+js를 다운받아 렌딩할 때 두 가지 방식이 있다. CSR과 SSR.
이 둘의 차이는 이전 포스팅에서 확인할 수 있다.
https://lulu-developmentlog.tistory.com/187
next.js는 기본적으로 SSR를 기반으로 한다.
하지만 SSR은 처음 및 초기 로딩속도는 빠르지만 페이지를 넘길 때마다 중복되는 데이터를 서버측에서 다시 불러올 수 있으며, 페이지를 이동할 때마다 서버에 요청하므로 과부하의 위험성이 크다.
Next.js는 이러한 단점을 SSR과 CSR의 하이브리드 렌더링 방식을 사용하여 해결합니다.
즉, SSR을 기반으로 첫 페이지를 로드하지만 이후에는 React에서 CSR을 이용하는 방식입니다.
순서는 아래와 같다.
1. 페이지는 서버가 그립니다. pages/안에 폴더를 만들면 해당 라우팅의 페이지들은 서버에서 먼저 로드한다.
(이 때, SSR의 장점인 SEO를 내용을 담은 컴포넌트를 서버가 그리는 페이지 안에 넣는다.<- 검색 엔진 최적화를 위해)
2. 페이지가 그려진 이후 페이지 내부에서 동적인 데이터 패칭 (axios, swr)은 CSR의 방식을 따른다.
이 때 그려지는 페이지가 로드된 이후에 클라이언트 측에서 다시 렌더되어 불러와지므로 SEO에 걸리지 않는다.
만약 페이지 로드 이후, 클라이언트 측에서 다시 렌더되어 불러지지 않고, 서버에 요청과 동시에 데이터가 패칭되길 원한다면 next.js가 제공하는 pre-rendering이 가능한 방식 (getInitialProps, getServerSideProps getStaticProps, getStaticPath)을 사용해야 한다.
참고 : https://lulu-developmentlog.tistory.com/276
이 과정을 코드를 도입하여 Next.js 렌더링 과정을 정리하면 아래와 같다.
- Next server가 get요청을 받는다.
- 요청에 맞는 Pages를 Pages 폴더 하위에서 찾는다.
- _app.tsx의 getInitialProps가 있다면 실행한다.
- Page Component의 getInitialProps가 있다면 실행하고 pageProps를 받아온다.
- _document.tsx의 getInitialProps가 있다면 실행하고 pageProps를 받아온다.
- 모든 props들을 구성하고 _app.js> page Component 순서로 렌더링한다.
- 모든 Content를 구성하고 _document.js를 실행하여 html형태로 출력한다.
앞선 루트를 통해 첫 페이지는 SSR 방식으로 하고 이후 페이지 이동시엔 CSR로 이뤄진다.
위와 같은 하이브리드 렌더링 방식을 활용하기 위해 여러 상태 관리 라이브러리를 사용할 수 있는데, 오늘은 React-query를 이용한 방법을 소개하고자 한다.
React-query가 왜 특히 next.js와 궁합이 좋은지는 다음 번 포스팅에 소개하기로 하겠다!
우선, React-query를 통해 SSR을 구현하는 방식에는 크게 두 개가 있다.
첫 번째는 InitialData
즉, SSR 메서드로 불러온 응답을 React Query 기본값으로 넣어주는 방법이다.
getStaticProps나 getServerSideProps에서 원하는 API를 요청하여 받은 응답을 react-query의 InitialData로 넣어주는 방법이다.
이와 같은 방법은 아래 코드에서도 볼 수 있 듯, 여러 컴포넌트에서 React Query 사용 시 해당 컴포넌트까지 props drilling이 필연적으로 생기기에 비효율적이다.
export async function getStaticProps() { const posts = await getPosts() return { props: { posts } } } function Posts(props) { const { data } = useQuery({ queryKey: ['posts'], queryFn: getPosts, initialData: props.posts, }) // ... }
그래서 react-query에서는 Hydration방식을 많이 쓴다!
hydration
수화시키다, 수분을 채워넣다, 수분을 유지시키다...즉, 서버에서 정적으로 제공하는 HTML에 수분을 보충(동적인 상태로 변화)하는 과정
Server Side 단에서 정적 페이지와 번들링된 JS 파일을 클라이언트에게 보낸 뒤, 클라이언트 단에서 HTML 코드와 React JS 코드를 서로 매칭 시키는 과정
그래서 SSR 내에서 prefetch를 통해 쿼리를 불러온 뒤, queryClient에서 degydrate한 상태값으로 페이지에 전달한다.
그리고 React가 번들링된 자바스크립트 코드들을 클라이언트에 전송하여 js 코드를 통해 웹 화면을 렌더링 하며 클릭과 같은 이벤트 리스너들을 적용시키는 즉, 수분을 채워넣는 일련의 Hydrate 과정이 일어난다. 그리고 우리는 dehydrate시킨 값을 hydrate시키는 것이므로 클라이언트에 rehydrate 시킨다고 표현한다.
next.js에서는 내부적으로 ReactDom.hydrate()를 사용하고 있어 React에서 SSR 적용을 위한 Webpack 및 Babel 설정을 따로하지 않아도 된다.
👉 dehydrate & hydrate & Rehydrate 도식화
1. 서버는 완성된 HTML을 내려준다.
2. Dehydrate는 수분을 없앤다, 즉 동적인 것을 정적으로 만든다. (ReactDOMServer 사용)
3. JS가 실행되면서 React가 HTML과 store를 동적인 리액트 컴포넌트 트리와 store로 변환하는 과정이 발생 (hydrate)
4. 이 때, hydrate가 일어나면 화면이 한번 더 렌더되는 현상 발생
여기에서 React Query는 next.js 서버에서 여러 개의 query를 prefetch하고 그 query들을 queryClient에 dehydrate하는 것을 지원한다. 즉 서버는 페이지 로드 시, JS를 사용할 수 있게되면 이러한 query들을 쉽게 업데이트하거나 hydrate할 수 있다.
import { QueryClientProvider, Hydrate } from '@tanstack/react-query'; import type { AppProps } from 'next/app'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { useState } from 'react'; import queryClientInstance from '../lib/queryClient'; export default function App({ Component, pageProps }: AppProps) { const [queryClient] = useState(queryClientInstance); return ( <QueryClientProvider client={queryClient}> <Hydrate state={pageProps.dehydratedState}> <Component {...pageProps} /> <ReactQueryDevtools initialIsOpen={true} /> </Hydrate> </QueryClientProvider> );
참고 : https://velog.io/@pjh1011409/React-Query-HydrationSSR
https://nextjs.org/docs/basic-features/data-fetching/overview
'Frontend > NextJS' 카테고리의 다른 글
next.js getInitialProps, getServerSideProps, getStaticProps, getStaticPath, (0) 2023.05.01 Next.js _app, _documnet (0) 2023.05.01 nextJS 왜 쓰는가 (0) 2023.01.19