Full Route Cache 풀 라우트 캐시
풀라우트 캐시가 진행되는 방식은 다음과 같다.
fetch api
를 통해 데이터가 있는지 찾는다.skip
하여 백엔드에 요청한다.풀 라우트 캐시
한다.👍 한 번 렌더링 해두면 재접속 시 빠른 로딩 속도로 데이터를 불러올 수 있어 효율적인 방식이다.
어떤 기능을 사용하느냐에 따라 자동으로 나뉘는데 풀 라우트 캐시가 적용되는 건 Static Page 즉, 정적 페이지이다.
“특정 페이지가 접속 요청을 받을 때마다 매번 변화가 생기거나, 데이터가 달라질 경우” 설정된다.
아래 조건을 만족하지 않으면 모두 Static Page로 기본값으로 설정된다.
🚨 서버 컴포넌트만 해당! 클라이언트 컴포넌트는 페이지 유형에 영향 미치지 않음!!!!
1. 캐시되지 않는 Data Fetching을 사용할 경우
async function Comp(){
const res = await fetch("...", { cache: "no-store"});
return <div>"캐시되지 않은 페이지"</div>;
}
2. 동적 함수(cookie
, header
, queryString
)을 사용하는 컴포넌트가 있을 때
// 1. cookies
import { cookies } from "next/headers";
async function Comp(){
const cookieStore = cookies();
cosnt theme = cookieStore.get("theme");
return <div>동적 함수 cookies 페이지</div>
}
// 2. headers
import { headers } from "next/headers";
async function Comp(){
const headersList = cookies();
cosnt authorization = headersList.get("authorization");
return <div>동적 함수 headers 페이지</div>
}
// 3. queryString
async function Page({searchParams} : {searchParams : { q : string } }){
const q = searchParams.q;
return <div>동적 함수 searchParams 페이지</div>
Static Page : 동적함수 NO! 데이터 캐시 YES! → 풀 라우트 캐시
Dynamic Page : 동적함수가 하나라도 있거나, 둘다 없을 경우
무조건 Static Page가 좋은 것은 아니다! 되도록이면 적용하면 좋지만 항상 Dynamic Page가 안티패턴이거나 느린 것은 아니다.
Static Page에도 revalidate
를 적용할 수 있다. 서버가 가동한 이후에 revalidate
로 설정한 시간이 지나면 데이터 캐시가 상하게 된다. 그렇다는 뜻은 Page에 있는 데이터도 상한 데이터라는 것!
이럴 때는 데이터 캐시 뿐만 아니라 Page 자체도 새로 업데이트 해줘야한다.
stale
데이터를 먼저 보여준다.revalidate data
를 다시 SET
해서 데이터 캐시에 담는다.revalidate
로 업데이트 된 데이터를 SET
하여 보여준다."use client";
import { useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import style from "./serachbar.module.css";
export default function Searchbar() {
const router = useRouter();
const searchParams = useSearchParams(); //🚨build 타임에는 검색어가 없어서 build 오류 발생!
const [search, setSearch] = useState("");
const q = searchParams.get("q");
useEffect(() => {
setSearch(q || "");
}, [q]);
//... 생략
yarn build
를 해서 실행시켜보면 아래와 같은 오류가 발생하게 된다.
오류 문구 : Generating static pages (0/6) [ ] ⨯ useSearchParams() should be wrapped in a suspense boundary at page "/search".
해석 : 정적페이지에서의 useSearchParams
는 suspense
경계 안에 감싸져야만 한다.
해결방법 : 이 useSearchParams
가 사용되고 있는 페이지를 렌더링 시 완전히 배제되도록 하면 된다. → Suspense
사용
import { ReactNode, Suspense } from "react";
import Searchbar from "../../components/searchbar";
export default function Layout({ children }: { children: ReactNode }) {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
{/* ✅ useSearchParams가 사용된 페이지를 Suspense 리액트 내장함수로 감싸주기 */}
<Searchbar /> {/* useSearchParams 사용된 컴포넌트 */}
</Suspense>
{children}
</div>
);
}
Suspense
라는 리액트 내장 컴포넌트를 사용해서 useSearchParams
가 사용된 컴포넌트를 감싸주면 Searchbar 컴포넌트는 build
시 완전히 배제되어 오류를 막을 수 있다.
현재 page들 중에서 Dynamic page 일 필요가 없는 Home 페이지를 Static page로 바꿔보려고 한다.
앞에서 설명했듯이 되도록 Static page로 만드는 것이 효율적이기 때문에! Home 에 있는 모든 코드들을 수정작업해볼 것이다.
1. root의 layout.tsx
import "./globals.css";
import Link from "next/link";
import style from "./layout.module.css";
import { BookData } from "@/types";
async function Footer() {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/book`,
{ cache: "force-cache" } // 🚧 매번 새로운 데이터를 가져올 필요없으므로 cache 수정
);
if (!response.ok) {
return <footer>제작 @winterlood</footer>;
}
const books: BookData[] = await response.json();
const bookCount = books.length;
return (
<footer>
<div>제작 @winterlood</div>
<div>{bookCount}개의 도서가 등록되어 있습니다.</div>
</footer>
);
}
export default function RootLayout({ // 🚧 RootLayout 컴포넌트에는 동적함수 없음! -> Footer check 하기
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<div className={style.container}>
<header>
<Link href={"/"}>📚 ONEBITE BOOKS</Link>
</header>
<main>{children}</main>
<Footer />
</div>
</body>
</html>
);
}
2. with-searchbar 폴더의 layout.tsx
import { ReactNode, Suspense } from "react";
import Searchbar from "../../components/searchbar";
export default function Layout({ children }: { children: ReactNode }) {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<Searchbar />
</Suspense>
{children}
</div>
);
}
앞에서 이미 수정해준 부분이기도 하고 fetch 함수도 없기 때문에 수정할 부분 없음!
3. with-searchbar 폴더의 page.tsx
import BookItem from "@/components/book-item";
import style from "./page.module.css";
import { BookData } from "@/types";
async function AllBooks() {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/book`,
{ cache: "force-cache" } // 🚧 항상 새로운 데이터 불러올 필요 없으므로 SSG 렌더링 적용
);
if (!response.ok) {
return <div>오류가 발생했습니다.</div>;
}
const allBooks: BookData[] = await response.json();
return (
<div>
{allBooks.map((book) => (
<BookItem key={book.id} {...book} />
))}
</div>
);
}
async function RecoBooks() {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/book/random`,
{
next: { // 🚧 SGR렌더링 방식 -> Static 렌더링 중 하나이므로 변경 필요없음
revalidate: 3,
},
}
);
if (!response.ok) {
return <div>오류가 발생했습니다.</div>;
}
const recoBooks: BookData[] = await response.json();
return (
<div>
{recoBooks.map((book) => (
<BookItem key={book.id} {...book} />
))}
</div>
);
}
export default function Home() {
return (
<div className={style.container}>
<section>
<h3>지금 추천하는 도서</h3>
**<RecoBooks />** {/* check! */}
</section>
<section>
<h3>등록된 모든 도서</h3>
**<AllBooks />** {/* check! */}
</section>
</div>
);
}
이렇게 수정한 후 다시 yarn build
해보면 아래 사진처럼 터미널을 확인할 수 있다.
yarn build
하면 생기는 .next
폴더를 들어가보면 html
이 생성된 것도 같이 볼 수 있다.
.next > server > index.html
파일이 생성되었고 yarn start
를 해보면 굉장히 빠른 속도로 렌더링이 되는 걸 확인할 수 있다.
(* 비슷한 맥락으로 not found 페이지도 Static page 이라는 것 확인!)
✅ 풀 라우트 페이지에 저장되는 Static Page라고 할지라도 데이터 fetching
에 revalidate
옵션이 적용되어 있다면 그 시간마다 새로운 데이터가 업데이트 될 수 있다는 것도 함께 알아두도록 하자!
generateStaticParams?
: getStaticPath
의 app router 버전이다.Home 컴포넌트와 달리 params
같은 쿼리스트링을
사용한 컴포넌트 페이지는 어떻게 Static page로 만들 수 있을까?
간단명료하게 말하자면 이런 경우에는 Home 컴포넌트처럼 Static page로 만드는 걸 포기해야한다.
하지만 렌더링 속도를 최대한 빠르게 불러올 수 있는 방법이 있는데 그 중 generateStaticParams
를 활용해 최적화 해보는 작업을 해보았다.
export default async function Page({
params,
}: {
params: { id: string | string[] }; // Dynamic page
}) {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/book/${params.id}`
);
if (!response.ok) {
if (response.status === 404) {
notFound(); // 자동으로 redirect
}
return <div>오류가 발생했습니다.</div>;
}
const book = await response.json();
const { id, title, subTitle, description, author, publisher, coverImgUrl } =
book;
// return 이하 코드 생략
}
현재 params: { id: string | string[] }
부분으로 인해 이 컴포넌트는 Dynamic page이다.
사용자의 데이터를 받아와서 결과를 보여주는 부분이라 이 컴포넌트는 앞 예시처럼 Static page로 바꿀 수 없는 상황이다.
generateStaticParams 사용한 코드
export function generateStaticParams() {
//정적인 파라미터를 생성하는 함수
return [{ id: "1" }, { id: "2" }, { id: "3" }];
// Static으로 build 타임에 완성이 되어서 풀라우트!
}
generateStaticParams()
함수를 사용해서 build
타임에 어떤 도서들이 있는지 미리 알려주도록 하는 코드를 작성하면 렌더링 시간을 조금 줄일 수 있다.
이 말은 즉슨 정적인 파라미터를 생성하는 함수를 만들어주는 과정이다!
✅ Network 탭으로 결과확인
처음 5번 인덱스로 들어갔을 때는 60ms
정도 걸렸지만 다시 새로고침 후 확인 하면
6ms
로 굉장히 빠르게 데이터를 불러오고 있다는 걸 확인할 수 있다.
![]() | ![]() |
---|
.next
폴더를 다시 확인했을 때도 전 후 차이를 확인할 수 있다.
캐시 어렵,,