작성
·
649
·
수정됨
0
// layout.tsx
import type { Metadata } from 'next';
import { getServerSession } from 'next-auth';
import { Nunito } from 'next/font/google';
import { authOptions } from './api/auth/[...nextauth]/route';
import ClientOnly from './components/ClientOnly';
import LoginModal from './components/modals/LoginModal';
import RegisterModal from './components/modals/RegisterModal';
import Navbar from './components/navbar/Navbar';
import RQProvider from './components/providers/RQProvider';
import ToasterProvider from './components/providers/ToasterProvider';
import './globals.css';
const font = Nunito({ subsets: ['latin'] });
export const metadata: Metadata = {
title: 'SMUING',
description: 'SMUING에 오신것을 환영합니다.'
};
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const session = await getServerSession(authOptions);
return (
<html lang="ko">
<body className={`${font.className} dark:bg-medium dark:text-slate-100`}>
<ClientOnly>
<RQProvider>
<ToasterProvider />
<RegisterModal />
<LoginModal />
<Navbar />
<div className="pb-20 pt-28">{children}</div>
</RQProvider>
</ClientOnly>
</body>
</html>
);
}
// page.tsx
import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query';
import Container from './components/Container';
import ListingContainer from './components/listings/ListingContainer';
import { getFilteredPosts } from './lib/getFilteredPosts';
type HomeProps = {
searchParams?: {
category?: string;
};
};
const Home: React.FC<HomeProps> = async ({ searchParams }) => {
const queryClient = new QueryClient();
const category = searchParams?.category || '';
// 서버에서 불러온 데이터를 클라이언트의 리액트 쿼리가 물려받음.(하이드레이트)
await queryClient.prefetchInfiniteQuery({
queryKey: ['posts', category],
queryFn: ({ pageParam = 1 }) => getFilteredPosts(category, { pageParam }), // searchParams 전달
// 커서 값
initialPageParam: 0
});
// hydrate라는 것은 서버에서 온 데이터를 클라이언트에서 그대로, 물려받는 것 이다.
const dehydratedState = dehydrate(queryClient);
return (
<HydrationBoundary state={dehydratedState}>
<Container>
<ListingContainer />
</Container>
</HydrationBoundary>
);
};
export default Home;
안녕하세요, 제로초님 강의를 수강하면서 현재 제가 진행하고있는 프로젝트에 Suspense를 도입해보면 좋을 것 같아 진행중입니다.layout.tsx에서 정의한 Header 컴포넌트만 먼저 보여주고, page.tsx에서 보여지는 가운데에 있는 ListingContainer 부분을 Skeleton UI 형식으로 보여주고 싶은게 제가 하고싶은 행동인데, 이 경우에는 Suspense를 어떻게 적용시켜야할지 감이 안와서 질문드립니다. ListingContainer를 ListingContainerSuspense 컴포넌트로 분리하여, searchParams와, 쿼리부분 코드를 옮겨도 제대로 동작하지 않아, 다른 좋은 접근 방식이 있을지 의견을 구하고자 질문을 남기게 되었습니다. 좋은강의 항상 감사합니다!
답변 2
0
안녕하세요 강사님 강사님의 답변을 읽어보던 중에 궁금한 것이 생겨서 여기에 따로 질문드립니다
어차피 suspense 쓰실거면 prefetchInfiniteQuery도 필요없습니다. useSuspenseInfiniteQuery만 쓰시면 됩니다.
라고 말씀해주셨는데 왜 서스펜스를 사용하면 prefetch가 필요없어지는지 알 수 있을까요?
그렇다면 혹시 강의에서 진행해주신 실습 내용이었던
// (afterLogin)\home\page.tsx
import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query';
import Post from '../_component/Post';
import PostForm from './_component/PostForm';
import Tab from './_component/Tab';
import TabProvider from './_component/TabProvider';
import styles from './home.module.css';
import { revalidatePath, revalidateTag } from 'next/cache';
import { getPostRecommends } from './_lib/getPostRecommends';
import PostRecommends from './_component/PostRecommends';
import TabDecider from './_component/TabDecider';
import { Suspense } from 'react';
import TabDeciderSuspense from './_component/TabDeciderSuspense';
import Loading from './loading';
const Home = async () => {
return (
<main className={styles.main}>
<TabProvider>
{/* 로딩이 필요 없는 애들은 Suspense 바깥으로 */}
<Tab />
<PostForm />
<Suspense fallback={<Loading />}>
<TabDeciderSuspense />
</Suspense>
</TabProvider>
</main>
);
};
export default Home;
import { Suspense } from 'react';
import TabDecider from './TabDecider';
import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query';
import { getPostRecommends } from '../_lib/getPostRecommends';
export default async function TabDeciderSuspense() {
const queryClient = new QueryClient();
// 추천 게시글 정보
await queryClient.prefetchInfiniteQuery({
queryKey: ['posts', 'recommends'],
queryFn: getPostRecommends,
initialPageParam: 0, //cursor값
});
const dehydreatedState = dehydrate(queryClient);
return (
<HydrationBoundary state={dehydreatedState}>
<TabDecider /> {/* <PostRecommends /> or <FollowingPosts /> */}
</HydrationBoundary>
);
}
이 부분에서는 suspense와 prefetchInfiniteQuery를 같이 사용하셨는데 이 부분은 강사님이 답글로 말씀 해주신 부분과 다른 맥락인가요?
여기서의 suspense 대상은 followingPosts였습니다. useSuspenseQuery로 바꾼 대상이요.
postRecommends는 suspense로 안 바꿨습니다.
아ㅠㅠ 조금 헷갈렸던 것이 강사님의
Suspense로 Streaming하여 최적화하기(feat. loading.tsx, error.tsx) 강의 에서 20분30초에
(prefetch한 것을 받아서 hydrate하는) PostRecommends.tsx 에서 useInfiniteQuery
를 useSuspenseInfiniteQuery
로 변경하셨더라구요 그리고 이 부분도 Suspense로 감싼 혜택을 볼 수 있다고 말씀하셨는데
깃허브에 올라간 코드를 보면 다시 useInfiniteQuery
로 사용되고 있네요
그러면 이 부분은 useInfiniteQuery
로 사용하는 것으로 이해하면 될까요?
서스펜스를 쓰는 이유가 나중에 데이터 불러오려고 쓰는건데 prefetch는 먼저 데이터를 가져오는 겁니다. 서로 반대되는 행동을 동시에 할 필요가 없죠