[Nextjs]SSG,SSR react-query로 Dynamic Routes에 적용
_app.tsx
import '../styles/globals.css';
import React from 'react';
import type { AppProps } from 'next/app';
import { Hydrate, QueryClient, QueryClientProvider } from 'react-query';
import { RecoilRoot } from 'recoil';
function MyApp({ Component, pageProps }: AppProps) {
const queryClient = React.useRef(new QueryClient());
//const [queryClient] = React.useState(() => new QueryClient())
return (
<QueryClientProvider client={queryClient.current}>
{/* <QueryClientProvider client={queryClient}> */}
<Hydrate state={pageProps.dehydratedState}>
<RecoilRoot>
<Component {...pageProps} />
</RecoilRoot>
</Hydrate>
</QueryClientProvider>
);
}
export default MyApp
[postId].tsx
import type { GetServerSideProps, GetStaticPaths, GetStaticProps, InferGetStaticPropsType, NextPage } from 'next'
import Image from 'next/image';
import Format from '../../layout/format';
import Author from '../../components/_child/author';
import Ralated from "../../components/_child/ralated"
import { ParsedUrlQuery } from 'querystring';
import { dehydrate, QueryClient, useQuery } from 'react-query';
import { useRouter } from 'next/router';
import Error from '../../components/_child/error';
import Spinner from '../../components/_child/spinner';
interface PostsProps {
id:Number;
title:string;
subtitle:string;
category:string;
img:string;
description:string;
published:string;
author:{
name:string;
img:string;
designation:string;
}
}
const page = () => {
const router = useRouter();
const { postId } = router.query;
//const { data, isLoading, isError } = useQuery(["post"], () => getPost(postId ? postId : 1));
const { data, isLoading, isError } = useQuery<PostsProps>(["post"], () => getPost(postId ? postId : 1));
//const {id, title, subtitle, description, category, img, published, author } = posts;
if (isLoading) {
return <Spinner></Spinner>;
}
if (isError) {
return <Error></Error>;
}
//const {id, title, subtitle, description, category, img, published, author } = data;
//const {id, title, subtitle, description, category, img, published, author }:PostsProps = data;
return (
<Format>
<section className=" container mx-auto md:px-2 py-16 lg:w-1/2">
<div className=" flex justify-center ">
{ data?.author ? <Author author={data?.author}></Author> : null }
</div>
<div className="post py-10">
<h1 className=" font-bold text-4xl text-center pb-5">{data?.title || null}</h1>
<p className=" text-gray-500 text-xl text-center">{data?.subtitle || null}</p>
<div className="py-10">
<Image src={data?.img || "/"} width={900} height = {600}></Image>
</div>
<div className="content text-gray-600 text-lg flex flex-col gap-4">
{data?.description || null}
</div>
</div>
<Ralated></Ralated>
</section>
</Format>
);
}
export default page;
interface IParams extends ParsedUrlQuery {
postId: string
}
const getPost = async (postId:string | string[] | number) => await (await fetch(`http://localhost:3000/api/posts/${postId}`)).json();
// export const getServerSideProps: GetServerSideProps = async (context) => {
// const queryClient = new QueryClient();
// const { postId } = context.params as IParams;
// await queryClient.prefetchQuery<PostsProps>(["post", postId], () => getPost(postId ? postId : 1));
// return {
// props: {
// dehydratedState: dehydrate(queryClient),
// }
// }
// }
// export const getServerSideProps: GetServerSideProps = async (context) => {
// const baseURL = "http://localhost:3000/api/posts/";
// const { postId } = context.params as IParams;
// const res = await fetch(`${baseURL}${postId}`);
// const posts: PostsProps = await res.json();
// return{
// props: {
// posts
// }
// }
// }
export const getStaticProps: GetStaticProps = async (context) => {
const queryClient = new QueryClient();
const { postId } = context.params as IParams;
await queryClient.prefetchQuery<PostsProps>(["post"], ()=> getPost(postId ? postId : 1));
return {
props: {
dehydratedState: dehydrate(queryClient),
}
}
}
export const getStaticPaths: GetStaticPaths = async () => {
const baseURL = "http://localhost:3000/api/posts/";
const res = await fetch(`${baseURL}`);
const posts: PostsProps[] = await res.json();
const paths = posts.map((value)=>{
return{
params:{
postId: value.id.toString()
}
}
})
return {
paths,
fallback:false,
};
}
// export const getStaticProps: GetStaticProps<{ posts: PostsProps }> = async (context) => {
// // export async function getStaticProps: GetStaticProps<{posts:PostsProps[]}>(){
// const baseURL = "http://localhost:3000/api/posts/";
// const { postId } = context.params as IParams;
// const res = await fetch(`${baseURL}${postId}`);
// const posts: PostsProps = await res.json();
// return{
// props: {
// posts
// }
// }
// }
// export const getStaticPaths: GetStaticPaths = async () => {
// const baseURL = "http://localhost:3000/api/posts/";
// const res = await fetch(`${baseURL}`);
// const posts: PostsProps[] = await res.json();
// const paths = posts.map((value)=>{
// return{
// params:{
// postId: value.id.toString()
// }
// }
// })
// return {
// paths,
// fallback:false,
// };
// }
오류 디버깅
error - unhandledRejection: TypeError: Cannot read properties of null (reading 'useRef')
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
Missing queryFn
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead
Unhandled Runtime Error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
오류 원인
1. 아래 빨간색 오류는 부모 컴포넌트인 page 컴포넌트 화살표 함수에다가 async 쳐 박아서 에러남;;
2. 부모에 해당하는 페이지 컴포넌트 page()에다가 async 쳐박은 이유 react-query에 useQuery 훅에 2번째 인자값 getPost함수가 await 넣으라고 오류가 떠서 넣어서
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
const getPost = async (postId:string | string[] | number) => await (await fetch(`http://localhost:3000/api/posts/${postId}`)).json();
PostId에 따라 다이나믹 라우터로 게시글 가져오는 fetch함수인데 요놈 호출할때 await 대신 ()=>로 바꿔서 처리함.
오류 코드
const page = async() => {
const router = useRouter();
const { postId } = router.query;
const { data, isLoading, isFetching } = useQuery<PostsProps>(["post", postId], await getPost(postId ? postId : 1));
...
...
수정코드
const page = () => {
const router = useRouter();
const { postId } = router.query;
const { data, isLoading, isFetching } = useQuery<PostsProps>(["post", postId], () => getPost(postId ? postId : 1));
[NextJs] React-Query로 ServrSide Rendering 구현하기
1. 무엇을 하려하는가? NextJS가 강력한 이유 중 하나는 SSR(server-side-Rendering)을 제공하기 때문이다. 서버 단에서 미리 데이터를 포함한 html파일을 렌더링한 후 프론트 쪽으로 보내주기 때문에 한 번
hojung-testbench.tistory.com
https://react-query-v3.tanstack.com/examples/nextjs
React Query Nextjs Example | TanStack Query Docs
An example showing how to implement Nextjs in React Query
tanstack.com
https://prateeksurana.me/blog/mastering-data-fetching-with-react-query-and-next-js/
Mastering data fetching with React Query and Next.js
Learn how React Query simplifies data fetching and caching for you and how it works in tandem with the Next.js pre-rendering methods
prateeksurana.me