전 게시글에서 마쳤어야 했는데 너무 긴 내용을 담은 것 같고 애매하게 섹션 3의 1강의만 남아서 .5 게시글을 작성하게 되었습니다. 다음 섹션부턴 백엔드 개발자와 협업한다는 전제하로 들어가기 때문에 이번 글은 가볍게 확인하기 좋은 것 같습니다~
넥스트 로딩과 에러
자동적으로 넥스트는 page.tsx 가 로딩 중일 땐 loading.tsx 를 보여주고, 에러가 발생했을 땐 error.tsx 를 보여주게 설계되어 있습니다. 이 기능은 기본적으로 리액트의 와 를 활용한 것으로 귀찮았던 과정을 줄일 수 있게 되었습니다.
https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming
공식 페이지에서도 보여주듯 loading.tsx 의 내부는 위처럼 구현되었다고 합니다.
이렇게 넥스트 상 계층구조를 미리 구현하여 따로 개발자가 구현하지 않아도 되게끔 파일 구조로 변경시킨 것입니다.
loading.tsx
그렇다면 넥스트에서 로딩 페이지가 보여지는 경우는 언제인가?
- 넥스트의 서버 컴포넌트에서 데이터 패칭하는 경우 -> 로딩O
- 리액트 Lazy가 적용되어 있는 경우 -> 로딩O
- 리액트 18에서 나온 'use' 훅의 경우 -> 로딩O
- 해당 use 훅은 Promise 를 넣어줄 수 있으며, Promise를 넣으면 resolve 되기 전까지 기다려주게 됨
- 또한 use 훅은 컨텍스트도 넣어줄 수 있는데, useContext와 유사하다고 함
가장 먼저 접속하는 /home 페이지에서 로딩창이 뜨지 않는 이유는 서버 사이드 렌더링 특성상 가장 처음 보여지는 페이지를 미리 읽어오기 때문이고, /explore 이나 다른 페이지에서 가장 먼저 접속해서 로딩 컴포넌트를 만들어준 페이지로 이동하면 로딩창이 뜨게 되는 것입니다.
다만 이 로딩창은 /home 페이지의 로딩을 의미하는 것으로, Post 쪽 로딩을 위해선 저번 글에서 구현했던 무한 스크롤링 부분에서 사용해준 useQuery 훅에서 제공하는 isPending 과 isLoading 을 활용해서 직접 구현해줘야 합니다.
- isPending
- 맨 처음 어떤 데이터도 없을 때, 대기 중인 경우 true
- isLoading
- isFetching && isPending
- 처음 데이터를 가져올 때 true가 됨
- isFetching
- 데이터를 불러오게 될 때 true
- queryFn이 호출될 때마다 true가 됨
if (isPending) {
return (
<div style={{ display: "flex", justifyContent: "center" }}>
<svg
className={styles.loader}
height="100%"
viewBox="0 0 32 32"
width={40}
>
<circle
cx="16"
cy="16"
fill="none"
r="14"
strokeWidth="4"
style={{ stroke: "rgb(29, 155, 240)", opacity: 0.2 }}
></circle>
<circle
cx="16"
cy="16"
fill="none"
r="14"
strokeWidth="4"
style={{
stroke: "rgb(29, 155, 240)",
strokeDasharray: 80,
strokeDashoffset: 60,
}}
></circle>
</svg>
</div>
);
}
위처럼 useQuery 에서 isPending 중일 때 로딩창을 설정할 수 있습니다.
다만 주의할 점은 prefetchInfiniteQuery 를 사용하여 서버에서 가져온 정보들은 로딩창을 구현할 수 없습니다. 해당 함수는 애초에 로딩되지 않게끔 미리 서버에서 정보를 가져오는 기능을 하기 때문입니다.
즉 서버 사이드 렌더링이 필요한 경우, 로딩창은 포기해야하는 것입니다. 검색엔진 최적화를 위해 SSR를 활용하는 경우가 많은데, 로딩 화면을 가져가 버리면 의미가 없어지기 때문입니다. 이 경우 메타데이터 등에 넣어주는 방식도 있으니 고려해봐야 합니다.
헷갈릴 법한데 정리하자면,
- 서버 컴포넌트에서 로딩/에러를 구현하려면 -> loading.tsx / error.tsx
- 클라이언트 컴포넌트에서 로딩/에러를 구현하려면 -> useQuery 등의 리액트 쿼리의 훅에서 지원하는 isPending / isError 값을 활용해주기
다만 useSuspenseQuery/useSuspenseInfiniteQuery 를 사용하기 위해 직접 최적화를 위한 Suspense 를 구현해줄 수 있습니다. 그렇게 되면, 클라이언트 측에 isPending 값을 활용하여 매번 로딩창을 구현하지 않아도 되게끔 처리가 가능해집니다. 위의 두 훅은 기존의 useQuery, useInfiniteQuery의 Suspense 가 감싸진 기능을 추가한 것 뿐이므로 사용법은 동일합니다.
import TabDecider from "@/app/(afterLogin)/home/_component/TabDecider";
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";
import getPostRecommend from "@/app/(afterLogin)/home/_lib/getPostRecommends";
export default async function TabDeciderSuspense() {
const queryClient = new QueryClient();
await queryClient.prefetchInfiniteQuery({
queryKey: ["posts", "recommends"],
queryFn: getPostRecommend,
initialPageParam: 0,
});
const dehydratedState = dehydrate(queryClient);
return (
<HydrationBoundary state={dehydratedState}>
<TabDecider />
</HydrationBoundary>
);
}
리액트 쿼리의 HydrationBoundary 가 필요한 영역이 불필요한 부분을 제외하고 좁아지게 되었습니다.
export default async function Home() {
return (
<main className={style.main}>
<TabProvider>
<Tab />
<PostForm />
<Suspense fallback={<Loading />}>
<TabDeciderSuspense />
</Suspense>
</TabProvider>
</main>
);
}
위처럼 미리 보여줘도 상관없는 Tab과 PostForm은 Suspense 외부에 두고, TabDeciderSuspense 컴포넌트를 따로 구현하고, 그 위에 Suspense 로 감싸줌을 알 수 있습니다. 이제 로딩이 될 때 fallback에 담긴 컴포넌트가 보여질 것입니다.
'Web Study > Next.js' 카테고리의 다른 글
Next.js로 SNS x.com 클론코딩하기 - 10 (1) | 2024.02.12 |
---|---|
Next.js로 SNS x.com 클론코딩하기 - 9 (1) | 2024.02.09 |
Next.js로 SNS x.com 클론코딩하기 - 8 (2) | 2024.02.07 |
Next.js로 SNS x.com 클론코딩하기 - 7 (2) | 2024.02.02 |
Next.js로 SNS x.com 클론코딩하기 - 6 (2) | 2024.01.30 |