


Request Memoization에서 캐시가 저장되는 원리를 배웠지만, 사실은 1단계 더 설명해야하는 부분이 존재한다.
Next.js는 빌드 타임에 데이터 호출, 캐싱 등의 작업을 마치고 페이지의 생성 결과를 풀 라우트 캐시Full Route Cache에 저장하게 된다.
실제 접속 요청이 들어오게 되면,풀 라우트 캐시Full Route Cache에 저장된 생성 결과를 HIT해준다. (미리 완성된 결과를 반환해준다는 면에서 SSG와 결과가 흡사하다.)

해당 페이지가 어떤 기능을 사용하느냐에 따라서, Next에서 자동으로 정적/동적 여부를 구분한다.
Server Component에만 해당된다. Cilent Component는 Next가 알아서 페이지 유형을 구분하지 않는다.

동적Dynamic Page
-> 페이지가 접속 요청을 받을 때 마다 변화가 생기거나 데이터가 달라질 경우.
정적Static Page
-> 동적Dynamic Page이 아닌 모든 페이지들.
그리고 Full Route Cache는 정적 페이지에서만 동작한다.
Next에서는 되도록이면 Full Route Cache가 동작하는 정적 페이지로 구성하는 것을 추천한다.
-> 물론, 동적 페이지를 반드시 사용해야하는 경우들이 있으니 상황에 따라 잘 구분하면 된다. (동적 페이지에서도 데이터 캐시나 리퀘스트 메모이제이션은 동작하기 때문.)
Full Route Cache도 revalidate가 가능하다.
여기도 마찬가지로 지정 시간이 지난 뒤의 요청은, 우선 유효하지 않은 저장값을 반환한 다음에 서버에 요청을 보내서 데이터를 업데이트하게 된다.

useSearchParams()는 쿼리스트링의 값을 가져오기 위해 사용하는 Hook.
그런데 빌드 타임에는 쿼리스트링이라는 것이 존재할 수가 없다. 따라서 빌드 타임에서 에러가 발생하게 된다.
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>
);
}
React.js의 내장 컴포넌트 Suspense를 사용하면 된다. (미결, 미완성이라는 뜻)
Suspense는 사전 렌더링 과정에서 배제되고, 오직 클라이언트에서만 렌더링 되도록 설정한다.
Suspense는 사전 렌더링 과정에서 '미완성' 상태로 간주한다. fallback 옵션에서 지정된 임시 UI만이 렌더링되고, Suspense 아래 배치된 컴포넌트에 필요한 요소들이 모두 갖추어진 뒤에 정식 렌더링을 실행하게 된다.
학습 프로젝트 코드 기준으로 설명하면, Searchbar 컴포넌트에 필요한 쿼리스트링이 클라이언트에서 불러와지기 전까지 Suspense에 의해 렌더링이 배제된다.
async function Footer() {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/book`
);
(...)
}
async function Footer() {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/book`,
{ cache: "force-cache" }
);
(...)
}
이런 경우라면 데이터 캐시를 사용해주기만 해도 페이지가 정적으로 전환된다.
다만, 무작정 데이터 캐시를 사용하는 것도 좋은 개발 방법은 아니다. 데이터 캐시가 필요한 상황인지를 파악할 것!
export default async function Page({
searchParams,
}: {
searchParams: {
q?: string;
};
}) {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/book/search?q=${searchParams.q}`,
{ cache: "force-cache" }
);
if (!response.ok) {
return <div>오류가 발생했습니다...</div>;
}
const books: BookData[] = await response.json();
return (
<div>
{books.map((book) => (
<BookItem key={book.id} {...book} />
))}
</div>
);
}
이 페이지의 경우, searchParams라는 동적 함수가 포함되어 있고 이를 대체할 방법이 없기 때문에 정적 페이지로 만드는 것은 불가능하다.
다만 여기는 동일한 검색 결과를 캐시하여 페이지를 최적화할 수는 있다.
export default async function Page({
params,
}: {
params: { id: string | string[] };
}) {
(...)
}
특정 책의 id 값을 받아서 데이터를 조회하여 렌더링하는 상세 페이지.
이 페이지 또한 동적 함수를 대체할 방법은 없다.
다만, 여기서의 동적 함수는 어떤 id값이 올지 모른다는 전제조건에서 실행되는 것.
그렇다면 generateStaticParams() 메소드를 이용하여, 해당 페이지에 들어올 수 있는 데이터들을 사전에 알려주는 방법으로 정적 페이지화를 실현할 수 있다.

Next에서는 generateStaticParams()가 존재한다면, 이 정적 URL 파라미터를 이용해서 빌드 타임에 페이지들을 생성할 수 있게 된다.
generateStaticParams() 사용시에는 값을 반드시 문자열만 사용해야 하며, generateStaticParams()을 사용하게 되면 해당 페이지 컴포넌트는 데이터 캐시를 사용하지 않는 페칭이 있다라도 해도 정적 페이지로 간주된다.
generateStaticParams()에서 제공하지 않는 값에 해당하는 페이지는 동적 페이지로 생성된다. 이렇게 생성된 페이지는 동적이긴 하지만 Full Route Cache에 저장된다.
if (!response.ok) {
if (response.status === 404) {
notFound();
}
return <div>오류가 발생했습니다...</div>;
}
generateStaticParams()에 없는 값에 해당하는 페이지가 동적으로 생성이 되긴 하지만, 아예 존재하지 않는 값은 당연히 에러가 발생하게 된다.
따라서 이런 부분에 대해서는 따로 예외처리를 해주는게 좋다.
export const dynamicParams = false;
