Next.js에서 ISR과 React Query를 연동하는 방법에 대해서 알아보려합니다
그전에 우선 렌더링 방식에 대한 간단한 설명부터 하겠습니다
렌더링 방식
CSR(Client Side Rendering)
웹페이지의 내용을 클라이언트에서 렌더링하는 방식임
렌더링의 책임을 사용자에게 전가하는 것
화면 로딩이 사용자 눈에 보여서 사용자 경험을 감소시키는 단점이 있음
SSR(Server Side Rendering)
웹페이지의 내용을 서버 측에서 렌더링한 후 완성된 HTML 파일을 클라이언트에 전달함
프론트엔드 서버에게 렌더링의 책임을 전가하는 것
사용자가 웹에 접속할때마다 새로운 페이지를 생성해줌
항상 최신 정보를 유지해야한다면 좋은 방식
페이지가 즉시 보이지만 서버가 부하를 감당해야한다는 단점이 있음
근데 성능상 이슈가 있으며 화면깜빡임 현상이 있음
SSG(Static Site Generation)
Build Time HTML 데이터를 받아서 → 미리 파일을 만들고 → 이걸 접속한 사용자에게 보여주는 방식
즉, 빌드 타임에 웹페이지를 미리 렌더링하여 정적 파일로 생성하는 방식입니다
이미 만들어진 페이지를 여러 사람들에게 보여주는거라 서버부담이 적고 응답속도가 빠릅니다
빠른 로딩 속도와 SEO 향상도 가져갈수 있습니다
하지만 매 페이지마다 빌드를 수행하기 때문에 마케팅 페이지 블로그 글 등 한번 만들면 변화가 별로 없는거에 사용됨
getStaticProps() 메소드를 사용해 데이터를 불러오고, 이를 페이지 컴포넌트의 props로 전달해주면 됩니다
그러므로 API와 같은 요청은 빌드 타이밍에서 처리해줘야합니다
ISR(incremental Static Regeneration)
정적생성으로 만들어놓은 페이지이지만 업데이트가 가능한 방식
정적생성의 장점 + 단점 보완한 방식으로 SSG에서 간단한 옵션 추가로 사용가능
빌드시점에 페이지 생성(SSG와 동일)
일정 시간 지난 후 페이지 새로 생성
최신 버전의 페이지를 비동기적으로 생성함
아래는 공식문서에서 제공하는 가이드입니다
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// We'll pre-render only these paths at build time.
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return { paths, fallback: 'blocking' }
}
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every 10 seconds
revalidate: 10, // In seconds
}
}
export default Blog
데이터 페칭
- getStaticPath
- 유저의 요청마다 fetch할 필요가 없는 고정된 내용의 페이지를 렌더링할때 사용됨
- getStaticProps
- 어떤 페이지를 미리 static으로 빌드할지 정하는 api, 동적 라우팅할 떄 적합함
- getServerSideProps
- 자주 변경되는 페이지를 렌더링할때 사용된다
- fetch해야할 데이터가 있을때 사용한다
- 서버에 부담을 주기 때문에 꼭 필요한 곳에서만 사용한다
Tanstack-Query
위에서 ISR를 적용하는 방식에 대해 설명했는데, 이번에는 React-Query를 사용해서 ISR과 연동하는 방법에 대해 알아보려합니다
먼저 React-query는 Tanstack-Query로 명칭이 변경됬습니다
아래는 추천하는 방식인 Dehydration 방식으로 ISR를 적용하는 방법입니다
initialData 방법이 세팅수요도 적고 간단하나 자손 컴포넌트까지 data를 props drilling 해야한다는 번거로움이 존재하고,
Hydration 방법은 queryClient로 간단히 접근 가능하며 기존 queryKey 값을 활용할 수 있다는 장점이 있어 2번을 권장하는 것이다.
여기서 hydration은 pre-rendering과 관련이 있다 각 페이지의 HTML을 미리 생성하고 생성된 HTML은 해당 페이지에 필요한 자바스크립트 코드와 연결하고 브라우저에 의해 페이지가 로드되면 자바스크립트 코드가 실행되어 유저와 인터랙트할 수 있는 방법이다 hydration은 두가지로 나뉘는데 SSG와 SSR로 나뉘게 된다
Dehydration
공식문서
Overview | TanStack Query Docs
우선 hydartion에 개념부터 알아보자면
SSR에서 정적인 페이지(HTML)를 렌더링하고
→ JS가 모두 로드되면 HTML에 이벤트 핸들러를 붙혀
→ 동적인 페이지를 만드는 과정 자체를 hydration이라고 말합니다
dehydrate는 나중에 hydrate로 공급할 수 있는 cache에 대한 고정된 표현을 생성하며, hydrate는 이전에 dehydrate된 state를 cache에 추가합니다
또한 React-query는 Next.js 서버에서 여러 개의 query를 prefetch하고 그 query들을 queryClient에 dehydrate하는 것을 지원합니다
서버는 페이지 로드시 즉시 사용할 수 있는 마크업을 미리 렌더링하는 것을 뜻합니다
InitialData
initialData로 넘겨주게되면 빠른 설정이 가능하지만 props drilling 등 몇가지 문제가 생기게 된다
같은 query가 여러 곳에서 호출된다면 모두 initialData를 세팅해야한다
query가 서버에서 fetch된 정확한 시점을 모르기에 페이지가 로드된 시점을 기준으로 dataUpdatedAt을 설정해서 refetch가 된다
export async function getStaticProps() {
const posts = await getPosts()
return { props: { posts } }
}
function Posts(props) {
const { data } = useQuery(['posts'], getPosts, { initialData: props.posts })
// ...
}
Hydration
getStaticProps, getServerSideProps 함수에서 prefetch를 통해 쿼리 데이터를 미리 요청한다
그리고 결과값이 담긴 queryClient를 dehydrate한 뒤 page.props에 dehydratedState로 내려주는 방법이 있다
// _app.jsx
import { Hydrate, QueryClient, QueryClientProvider } from '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>
)
}
hydration시 참조를 위한 pageProps.dehydratedState를 넘겨준다
queryClient는 lifeCycle 주기당 인스턴스가 1번만 생성되도록 App 외부, state, 혹은 ref 등으로 저장한다
// /components/Component.jsx
// pages/posts.jsx
import { dehydrate, QueryClient, useQuery } from '@tanstack/react-query';
export async function getStaticProps() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery(['posts'], getPosts)
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
}
function Component() {
const { data } = useQuery(['posts'], getPosts)
}
queryClient 인스턴스를 생성한 뒤 여기에 prefetchQuery 메소드로 쿼리를 refetch 한다
query를 캐싱하기 위해 dehydrate 처리를 한 queryClient를 dehydratedState로 추가한다
참조
https://abangpa1ace.tistory.com/267
[React Query] (5) SSR
🧐 서론 지난 포스팅까지 진행하며, React Query를 사용해서 데이터를 다루는 주요 문법들을 정리하였다. 프로젝트를 마이그레이션하며 적용하던 중, Next.js 세팅인 점을 상기했으며 당연히 React Que
abangpa1ace.tistory.com
https://www.howdy-mj.me/next/hydrate
Next.js의 렌더링 과정(Hydrate) 알아보기
누군가 나에게 Next.js를 쓰는 이유를 물어본다면, 가장 먼저 SSR 때문이라고 대답할 것 같다. Next.js 공식 홈페이지에서도 가장 먼저 강조하고 있는 것이 'hybrid static & server rendering'인 것처럼 말이다
www.howdy-mj.me
'프론트엔드 > Next.js' 카테고리의 다른 글
Next.js build시 Image Optimization 설정 에러 (0) | 2023.05.16 |
---|---|
Nextron(eletron) IPC 통신 (0) | 2023.05.08 |
Next.js SSG에 대해서 알아보자 (0) | 2023.04.26 |
Next.js에서 미디어쿼리 (반응형) 적용하기 (0) | 2023.04.19 |
Next/image가 외부 이미지를 import 해오지 못하는 문제 (0) | 2022.12.29 |