- Today
- Total
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- styled-component
- 오블완
- 리액트
- 오라클클라우드
- no-use-before-define
- 산업기능요원 훈련소
- resolved to branch.
- useformstatus
- 훈련소
- 자바스크립트 순수함수
- server action
- react life sycle
- angular
- react toast
- 훈련소 후기
- next.js toast
- 비동기 병렬처리
- sessionStorage
- 자바스크립트
- react
- 사회복무요원 훈련소
- The above error occurred in the
- 공익 훈련소
- localStorage
- svgr
- useRouter
- NextJS
- 리액트 라이프사이클
- query param
- 리액트 알림
아 그거 뭐였지
[Next.js] Next.js SSR 조림 그런데 이제 Tanstack/react-query를 곁들인... 본문
반갑다 여러분, 놀랍게도 블로그 주인장 죽지 않고 살아있었다.
마음 한편속에 '블로그 써야 되는데...'를 되새긴 지 어언 6개월, 블로그에서 녹물 나오기 전에 간신히 돌아왔다.
확실히 블로그를 안쓰니 알던 것도 금방 까먹고 알고 있어도 제대로 아는지 긴가민가하다. (언어능력이 떨어진 건 덤)
종종 나타나서 글을 써야겠다. 글쓰는 텀이 이 지경이니 어디 가서 블로그 쓴다고 얘기하기도 부끄럽다.
한창 미쳐있던 Compound-Component 패턴 적용기도 써야되고... Suspense도 써야되고...
쓸건 많은데 몸이 잘 따라주지않는다.
그래도 부끄럽지 않은 내 자신을 위해 열심히 키보드를 두들겨보겠다.
우리가 어떤 민족인가? 바로 빨리빨리의 민족이다.
빨리빨리의 민족답게 느린건 참을수가없다. 웹 페이지도 예외란 없다.
웹 페이지에 빙글빙글 로딩 스피너가 돌아가고 있는걸 보고있으면 답답해 미칠 지경이다.
이런 답답함을 조금이나마 해소해줄수 있는게 Server-Side-Rendering (이하 SSR) 인데
본 포스팅의 목적은 기술을 사용해보는 것이기 때문에 SSR을 깊게 설명하지는 않겠다.
대충 서버에서 데이터 먼저 받아서 뿌려주니까 CSR처럼 화면에서 데이터를 기다리지 않는다 정도만 알고있자.
Next.js를 사용하면 이 SSR을 사용할수있는데 page-router라면 getServerSideProps 함수로 구현이 가능하고
app-router라면 기본이 서버 컴포넌트이기 때문에 'use client'가 적용 되어있지 않은 컴포넌트라면 api 호출 때려버리면 된다.
//page-router
export const getServerSideProps = (async () => {
const res = await fetch('https://api.github.com/repos/vercel/next.js')
const result = await res.json()
return { props: { result } }
})
export default function Page({ result }) {
return <main> {result.data} </main>
}
// app-router
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
엥 그럼 끝난건가요?
프로젝트에 노잼 정적인 데이터만 있다면 저 방법으로도 충분하다. 특히 유저정보에 기반하여 내려주는 데이터가 아니라면 굳이 Tanstack/react-query 를 쓸 필요는 없다. 오히려 오버쿡이 될수있다. 그냥 fetch 함수에 next cache 옵션을 활용한다면 캐싱까지 야무지게 사용할수있다.
다만 Tanstack/react-query가 필요할때는 앞서 말한 유저정보에 기반하여 내려주는 데이터 일때 인데,
유저정보에 기반하여 내려주는 데이터의 대중적인 예시로는 프로필이나 장바구니가있다.
프로필정보를 캐싱해서 내려주면 사용자는 자신의 프로필이 아닌 다른 유저의 프로필을 볼 가능성이 높다.
장바구니도 마찬가지일 텐데, 서버캐싱을 사용해버리면 처음 요청했을때의 장바구니 데이터를 캐싱해서 모든 유저들은 한사람의 장바구니만을 바라봐야 한다. 운이 좋게 내 장바구니가 걸리면 모든 이에게 내 장바구니를 공유 할수있다. 설상가상으로 next cache 옵션이 force-cache라면 장바구니는 revalidate를 해주지않는이상 바뀌지 않을것이다.
(14버전 이전에는 next cache의 default 옵션이 force-cache였지만 14버전 부터는 no-store로 바뀌었다.)
물론 no-store옵션을 통해 해결할수는 있겠지만 그렇게 되면 캐시데이터를 사용하지 못하고 api 요청을 계속해서 날려야한다. 그냥 요청 날리면 안되나? 라고 생각할수도있겠지만, 요청 하나하나가 돈이다. 서버는 공짜로 돌아가지않는다. 서버 과부하는 덤이다.
그렇담 이븐하게 해결할 방법은 무엇일까?
바로 오늘 곁들일 Tanstack/react-query의 prefetch 이다.
라떼는 그냥 react-query였는데 v4부터였나 어느샌가 이름앞에 tanstack이 붙어서 더 길어졌다.
유저정보에 기반한 데이터는 캐싱하지 않고 서버에서 받아오되, (cache: no-store)
서버에서 받아온 데이터를 클라이언트에서 캐싱해서 사용한다면 서버에 대한 요청은 줄이면서 유저정보에 기반한 데이터들도 무사히 받아올수있을 것이다.
Tanstack/react-query는 prefetch라는 기능을 제공하는데,
해당 기능을 사용하면 서버사이드에서 데이터를 미리 fetch하고 클라이언트 데이터를 캐싱할수있다.
딱 우리가 원하는 기능이다.
Tanstack/react-query의 공식문서를 살펴보며 바로 사용법을 알아보자.
개발 환경은 next.js 14.2.13, react^18, app-router를 사용하였다.
※ next.js 14버전 이하를 사용한다면 fetch 의 cache 옵션을 no-store로 설정해주길 바란다. ※
Tanstack/react-query 설치
각자 프로젝트에서 사용중인 패키지매니저로 적절히 설치해주자.
// npm
npm i @tanstack/react-query
// pnpm
pnpm add @tanstack/react-query
// yarn
yarn add @tanstack/react-query
// bun
bun add @tanstack/react-query
Providers 세팅
root 폴더인 app 폴더 밑에 provider.tsx 파일과 get-query-client.ts 파일을 생성해준다.
provider.tsx 파일의 최상단에 'use client' 를 까먹지말자. provider.tsx가 이미 클라이언트 컴포넌트가 되었기때문에
get-query-client.ts 에는 굳이 안적어줘도된다.
// app/providers.tsx
'use client';
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { getQueryClient } from './get-query-client';
import type * as React from 'react';
export default function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient();
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools />
</QueryClientProvider>
);
}
// app/get-query-client.ts
import { QueryClient, defaultShouldDehydrateQuery, isServer } from '@tanstack/react-query';
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
dehydrate: {
shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === 'pending',
},
},
});
}
let browserQueryClient: QueryClient | undefined = undefined;
export function getQueryClient() {
if (isServer) {
return makeQueryClient();
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
}
getQueryClient 함수 코드에 중요한 부분이 있다. 바로 서버 요청 일때는 새로운 queryClient를 만든다는 점이다.
export function getQueryClient() {
if (isServer) {
return makeQueryClient();
} ....
}
해당 코드로 인해서 각 사용자는 독립된 queryClient를 사용할수있게 되고, 개별화된 정보를 받을수있는것이다.
이렇게 만들어진 providers를 최상위 layout.tsx 컴포넌트에 넣어주면된다.
// app/layout.tsx
import Providers from './providers'
export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
HydrationBoundary , prefetch
이제 세팅은 끝났다.
사용하고자 하는 서버 컴포넌트에서 prefetchQuery를 호출해서 데이터를 미리가져오고,
HydrationBoundary의 state props에 dehydrate시켜서 전달해주기만 하면 된다.
// app/posts/page.jsx
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from '@tanstack/react-query'
import Posts from './posts'
export default async function PostsPage() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: getPosts,
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<Posts />
</HydrationBoundary>
)
}
그럼 Posts 컴포넌트에서는 이미 prefetch되어 데이터가 캐싱되어있기때문에 queryKey가 바뀌지 않는 이상
별도의 api호출을 하지않고 캐싱된 데이터를 사용하게된다. 해당 공식문서의 예시에서는 queryKey를 static하게 넣었지만,
변수로 따로 분리해서 관리하는것이 휴먼에러도 줄이고 유지보수에 용이할것이다.
// app/posts/posts.jsx
'use client'
export default function Posts() {
const { data } = useQuery({
queryKey: ['posts'],
queryFn: () => getPosts(),
})
....
}
Tanstack/react-query를 사용하면 캐싱 외에도 props drilling을 해결할수있다는 이점이 있다.
만약 Tanstack/react-query를 사용하지 않고 서버 컴포넌트에서 fetch한 데이터만을 사용하려면 해당 데이터를
props로 자식 컴포넌트로 넘겨줘야한다. 이때 서버 컴포넌트는 대부분 상위 컴포넌트일 가능성이 높은데,
(비즈니스 로직이 들어가면 대부분 client 컴포넌트가 되기 때문에 대부분 상위에서 fetch)
해당 데이터가 필요한 컴포넌트까지 props로 계속 전달하다보면 가독성을 많이 해치게된다.
이때 Tanstack/react-query prefetch를 적절히 사용해준다면 데이터가 필요한 컴포넌트에서 쿼리해서 캐싱데이터를 사용하면 된다.
흐름을 한번 정리해보면서 이번 글을 마무리 해보려고 한다.
1. 서버 사이드에서 prefetchQuery를 사용해서 데이터를 미리 가져와서 캐시에 저장한다.
2. 클라이언트에서 사용할수있게 dehydrate 하여 HydrationBoundary 컴포넌트로 전달한다.
3. HydrationBoundary 컴포넌트는 클라이언트 컴포넌트가 실행 되기전에 react-query의 내부 캐시를 구성한다.
4. 클라이언트 컴포넌트에서 실행되면 useQuery를 호출하고 hydrate된 캐시를 확인하고 사용한다.
시작을 최강록 셰프님의 명언으로 했으니 마무리도 최강록 셰프님의 흑백요리사 명언으로 끝을내겠다.
긴글 읽어줘서 감사하며 다들 맛있는 코딩을 하기를 바란다.
참고문서
'Front-End' 카테고리의 다른 글
[Next.js] useFormStatus로 Server Action Loading 처리 해보기 (0) | 2024.11.27 |
---|---|
[React] React + Vite(^v4) 환경에서 svg 설정하기 (0) | 2024.03.17 |
[NextJS] 새로워진 Image태그를 알아보자 (image layout fill) (1) | 2023.10.17 |
[React] 리액트 렌더링 최적화에대해 알아보자 .with (useMemo, useCallback, React.mamo) (0) | 2023.06.04 |
[React] React Life-Sycle ( 클래스형, 함수형), 리액트 라이프 사이클 알아보기 (0) | 2023.05.14 |