https://nextjs.org/docs/api-reference/data-fetching/get-static-paths#fallback-false
fallback의 기능은 여기 잘 나와 있습니다. 그냥 그대로 해석해 보겠습니다. 'fallback'은 getStaticPaths에서 리턴되지 않은 새로운 경로를 사용자로 하게끔 HTML을 만드는 시간동안 기다리게 합니다. 이는 SSR( blocking일 때 성능 증가 )인 경우와 똑같습니다. 그리고 이 Server-Side에서 Cahce된 HTML, JSON으로 인해 그 후로는 엄청 빠르게 페이지를 반환하는 원리입니다.
이제 이를 다른곳에 한번 활용해 보도록 하겠습니다. 이전에 만든 blog페이지를 가지고 해 보도록 하겠습니다.
fallback: blocking
/pages/blog/[slug].tsx
import { GetStaticProps, NextPage } from "next";
import { readdirSync } from "fs";
import matter from "gray-matter";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkHtml from "remark-html";
import Layout from "@components/layout";
const Post: NextPage<{ post: string; data: any }> = ({
post,
data,
}) => {
return (
<Layout title={data?.title} seoTitle={data?.title}>
<div
className="blog-post-content"
dangerouslySetInnerHTML={{ __html: post }}
/>
</Layout>
);
};
export default Post;
export function getStaticPaths() {
// const files = readdirSync("./posts").map((file) => {
// const [name, _] = file.split(".");
// return { params: { slug: name } };
// });
return {
paths: [],
fallback: "blocking",
};
}
export const getStaticProps: GetStaticProps = async (ctx) => {
const { content, data } = matter.read(
`./posts/${ctx.params?.slug}.md`
);
const { value } = await unified()
.use(remarkParse)
.use(remarkHtml)
.process(content);
return {
props: {
post: value,
data,
},
};
};
기존에 파일 시스템에서 하나하나 다 추출하는 방법보다는, 그냥 fallback을 blocking으로 넣어주어서 이전에 한것과 비슷한 방식으로 진행했습니다. 그러면 당연히 build타임에는 Static-HTML이 만들어 지지 않습니다.
이제 블로그 상세페이지에 접근해 보도록 하겠습니다.
일단 Next/link의 기능을 활용하여 싹다 prefetch를 진행했습니다. 그래서 Server-Side에서 싹다 StaticProps를 통해서 만들도록 했습니다.
그럼 다음과 같은 HTML파일이 생기고, 각 블로그 포스트에 접근 시, 매우 빠른 속도로 접근이 가능해 집니다.
추가로 04-wow.md라는 블로그 포스팅을 간단히 하나더 만들어 주겠습니다.
/posts/04-wow.md
---
title: wow
date: 2022.08.08
category: travel
---
# wow
Get fat2!~~
그리고 /blog/04-wow로 접근하게 되면 fallback을 'blocking'으로 주어졌으므로, 접속이 가능할 것이고, 그 즉시 바로 server폴더에 Static-HTML, JSON을 만들어, Cache할 것입니다.
정말 생상적인 NextJS가 아닐 수 없습니다. 나중에 동아리 홈페이지를 만들 떄, 이 기능을 사용하면 매우 좋을 거 같다는 생각을 합니다..
fallback: false
getStaticPaths로 반환된 경로가 없는 경로에 사용자가 접근했다면 404page를 부여줍니다. 간단히 보고 가겠습니다.
/pages/blog/[slug].tsx
...
return {
paths: [],
fallback: false,
};
}
export const getStaticProps: GetStaticProps = async (ctx) => {
const { content, data } = matter.read(
`./posts/${ctx.params?.slug}.md`
);
const { value } = await unified()
.use(remarkParse)
.use(remarkHtml)
.process(content);
return {
props: {
post: value,
data,
},
};
};
...
fallback: true
fallback이 true인 경우, 빌드 시 생성되지 않은 경로는 404페이지를 생성하지 않습니다. 대신 Next.js는 이러한 경로에 대한 첫 번쨰 요청에서 페이지의 "fallback"버전(isFallback)을 제공합니다. Google과 같은 웹 크롤러는 fallback서비스를 제공하지 않으며 대신 경로는 fallback: 'blocking'과 같이 작동합니다. 백그라운드에서 Next.js는 요청된 경로 HTML및 JSON을 정적으로 생성합니다.
fallback: true는 데이터에 의존하는 static페이지가 많은 경우에 유용합니다(예: 매우 큰 전자 상거래 사이트). 모든 제품 페이지를 미리 렌더링하려면 빌드 시간이 매우 오래 거리기 때문입니다.
/pages/products/[id].tsx
import type {
GetStaticPaths,
GetStaticProps,
NextPage,
} from "next";
import Layout from "@components/layout";
import Button from "@components/button";
import { useRouter } from "next/router";
import useSWR, { useSWRConfig } from "swr";
import Link from "next/link";
import { Product, User } from "@prisma/client";
import { Backdrop, CircularProgress } from "@mui/material";
import { useState } from "react";
import useMutation from "@libs/client/useMutation";
import { cls } from "@libs/client/utils";
import useUser from "@libs/client/useUser";
import Image from "next/image";
import client from "@libs/client/client";
interface ProductWithUser extends Product {
user: User;
}
interface ItemDetailResponse {
ok: boolean;
product: ProductWithUser;
relatedProducts: Product[];
isLiked: boolean;
}
const ItemDetail: NextPage<ItemDetailResponse> = ({
product,
relatedProducts,
isLiked,
}) => {
const { user, isLoading } = useUser();
const router = useRouter();
// const { mutate } = useSWRConfig();
const { data, mutate: boundMutate } =
useSWR<ItemDetailResponse>(
router.query.id ? `/api/products/${router.query.id}` : null
);
const [toggleFav] = useMutation(
`/api/products/${router.query.id}/fav`
);
const onFavClick = () => {
// SWR boundMutate를 활용해 캐시로 바로 UI에 업데이터 하고, Revalidate는 안하고
// 실제로 mutate를 DB에 주어서 ( toggleFav )로 값에 변화는 주어야 함.
toggleFav({});
if (!data) return;
boundMutate(
(prev) => prev && { ...prev, isLiked: !prev.isLiked },
false
);
// mutate(
// "/api/users/me",
// (prev: any) => {
// console.log(prev);
// return { ok: !prev.ok };
// },
// false
// );
};
if (router.isFallback) {
return (
<Layout title="Loading for you">
<span>Loading...</span>
</Layout>
);
}
return (
<Layout canGoBack seoTitle="Product Detail">
<Backdrop
sx={{
color: "#fff",
zIndex: (theme) => theme.zIndex.drawer + 1,
}}
open={data === undefined}
>
<CircularProgress color="inherit" />
</Backdrop>
<div className="px-4 py-3">
<div className="mb-8">
<div className="relative pb-64">
<Image
src={`https://imagedelivery.net/_SMYXsMOOEvTYhYAAKoRCQ/${product?.image}/public`}
className="bg-slate-300 object-cover"
alt=""
layout="fill"
/>
</div>
<div className="mt-1 flex items-center space-x-3 border-t border-b py-3">
<Image
width={48}
height={48}
src={`https://imagedelivery.net/_SMYXsMOOEvTYhYAAKoRCQ/${product?.user?.avatar}/avatar`}
className="h-12 w-12 rounded-full bg-slate-300"
alt=""
/>
<div>
<p className="text-sm font-medium text-gray-700">
{product?.user?.name}
</p>
<Link
href={`/users/profiles/${product?.user?.id}`}
>
<a className="cursor-pointer text-xs font-medium text-gray-500">
View profile →
</a>
</Link>
</div>
</div>
<div className="mt-5">
<h1 className="text-3xl font-bold text-gray-900">
{product?.name}
</h1>
<span className="mt-3 block text-3xl text-gray-900">
${product?.price}
</span>
<p className="my-6 text-base text-gray-700">
{product?.description}
</p>
<div className="flex items-center justify-between space-x-2">
<Button text="Talk to seller" large />
<button
onClick={onFavClick}
className={cls(
"flex items-center justify-center rounded-md p-3 hover:bg-gray-100",
isLiked
? "text-red-400 hover:text-red-500"
: "text-gray-400 hover:text-gray-500"
)}
>
{isLiked ? (
<svg
className="h-6 w-6"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z"
clipRule="evenodd"
></path>
</svg>
) : (
<svg
className="h-6 w-6 "
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
/>
</svg>
)}
</button>
</div>
</div>
</div>
<div>
<h2 className="text-2xl font-bold text-gray-900">
Similar items
</h2>
<div className="mt-6 grid grid-cols-2 gap-4">
{relatedProducts?.map(({ id, name, price }) => (
<Link href={`/products/${id}`} key={id}>
<a>
<div className="mb-4 h-56 w-full bg-slate-300" />
<h3 className=" -mb-1 text-gray-700">
{name}
</h3>
<span className="text-sm font-medium text-gray-900">
${price}
</span>
</a>
</Link>
))}
</div>
</div>
</div>
</Layout>
);
};
export const getStaticPaths: GetStaticPaths = () => {
return {
paths: [],
fallback: true,
};
};
export const getStaticProps: GetStaticProps = async (ctx) => {
if (!ctx?.params?.id) {
return {
props: {},
};
}
const product = await client.product.findUnique({
where: { id: +ctx.params.id.toString() },
include: {
user: {
select: {
id: true,
name: true,
avatar: true,
},
},
},
});
// 현재 상품의 이름을 공백을 기준으로 분리해
const terms = product?.name.split(" ").map((word) => ({
name: {
contains: word,
},
}));
const relatedProducts = await client.product.findMany({
where: {
OR: terms,
AND: {
id: {
not: product?.id,
},
},
},
});
const isLiked = false;
await new Promise((resolve) => setTimeout(resolve, 10000));
return {
props: {
product: JSON.parse(JSON.stringify(product)),
relatiedProducts: JSON.parse(
JSON.stringify(relatedProducts)
),
isLiked,
},
};
};
export default ItemDetail;
다음과 같이 router.isFallback을 활용하여, 처음 로딩 10초동안 유저에게 아무것도 안보여주는 대신 Fallback페이지를 보여줄 수 있습니다.
'Web > NextJs' 카테고리의 다른 글
[ Next.js - DeepDive ] - REACT18 - Suspense (0) | 2022.08.12 |
---|---|
[ Next.js - DeepDive ] - Data Fetching Reca (0) | 2022.08.12 |
[ Next.js - DeepDive ] - Blocking SSG (0) | 2022.08.12 |
[ Next.js - DeepDive ] - Incremental site regeneration - 2 ( On-Demand ) (0) | 2022.08.12 |
[ Next.js - DeepDive ] - Incremental site regeneration - 1 (0) | 2022.08.08 |