인프런 "한 입 크기로 잘라먹는 Next.js" 수강
“빌드 타임에 서버가 특정 페이지의 렌더링 결과를 미리 캐싱해두는 기능”
Next.js의 서버는 페이지를 한 번 렌더링한 뒤,
그 결과물 전체를 “Full Route Cache” 라는 이름으로 저장한다.

이후 동일한 페이지의 요청이 들어오면, 다시 렌더링하지 않고 캐싱된 HTML 결과를 그래도 전달한다.
즉, 정적 페이지(SSG)의 방식과 유사하며 빠른 응답이 가능하다.

Next.js는 어떤 기능을 사용하느냐에 따라 자동으로 페이지를 구분한다.
“특정 페이지가 접속 요청을 받을 떄마다 매번 변화가 생기거나, 데이터가 달라지는 경우”
다음 중 하나라도 해당되면 동적 페이지로 분류된다.
Dynamic Page가 아니면 모두 Static Page가 된다. (기본값)
비교해보자
| 동적 함수 | 데이터 캐시 | 페이지 분류 |
|---|---|---|
| 🟢 | ❌ | Dynamic Page |
| 🟢 | 🟢 | Dynamic Page |
| ❌ | ❌ | Dynamic Page |
| ❌ | 🟢 | Static Page |
Full Route Cache와
revalidateFull Route Cache는
revalidate옵션과 함께 사용해
ISR(Incremental Static Regeneration) 방식처럼 동작할 수도 있다.

동작 순서
1. 지정한 시간이 지나면 Next 서버는 해당 캐시를 Stale(만료됨)로 표시
2. 먼저 기존 캐시를 그대로 응답
3. 그 뒤 서버에서 새로운 데이터를 fetch
4. 렌더링 후 캐시 업데이트(Full Route Cache 최신화)
useSearchParams 오류 해결현재
useSearchParams()라는 훅은 브라우저에서만 실행 가능하기 떄문에
사전 렌더링 과정에서 빌드할 수 없다는 오류가 발생한다.
"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(); // 문제 발생!
const [search, setSearch] = useState("");
이를 해결하기 위해서는 프로젝트 빌드 시 해당 파일이 빌드되지 않도록 설정해줘야 하며,
해당 컴포넌트를 사용하고 있는 부모 컴포넌트로 가서 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>}>
<Searchbar />
</Suspense>
{children}
</div>
);
}
Suspense란?
다음 주제에서 진행되지만 미리 살펴보자
비동기 로직이 포함된 컴포넌트를 "준비될 때까지 잠시 렌더링하지 않게 해주는 장치"이다.
fallback속성으로 로딩 상태 UI를 지정할 수 있다.- 여기서는
useSearchParams가 비동기 Hook이므로Suspense가 필요하다.
Suspense컴포넌트로 묶여있는 컴포넌트는 미완성 상태라 곧바로 렌더링하지 않으며, 대신fallback이라는props로 전달한 대체 UI가 로딩 UI로서 대신 렌더링된다.
(이떄, “미완성 상태”는 컴포넌트의 비동기 작업이 종료될 때까지)export default async function Page({ searchParams }: { searchParams: SearchParams }) { const { q } = await searchParams; return <div>Search Query: {q}</div>; }
Query String을 사용하는 search 페이지처럼, 페이지 자체는 캐시 불가하지만, fetch 단위로 캐싱이 가능하다.
export default async function Page({ searchParams }: { searchParams: Promise<{ q?: string }> }) {
const { q } = await searchParams;
const response = await fetch(`{process.env.NEXT_PUBLIC_API_SERVER_URL}/book?q=${q || ''}}`, {
cache: 'force-cache',
});
//...
URL 파라미터를 사용하는 book/[id] 빌드 타임에 어떤 경로가 존재할지 알 수 없기 때문에 기본적으로 Dynamic Page로 처리된다.
우리는 정적 경로로 지정해야 하니,
빌드 타임에 서버에게 해당 페이지가 어떤 경로를 가질 수 있는지 미리 알려주기 위해 generateStaticParams 함수를 생성해준다.
generateStaticParams함수는 "정적인 파라미터를 생성하는 함수"이다.
import { BookData } from '@/types';
import style from './page.module.css';
export function generateStaticParams() {
return [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }, { id: '5' }];
}
export default async function Page({ params }: { params: Promise<{ id: string | string[] }> }) {
//...
반환 값으로 어떤 URL 파라미터의 어떤 페이지가 빌드 타임에 존재하는지 알려줘야 한다.
⚠️ 주의할 점
id는 문자열(string) 로만 지정해야 함- generateStaticParams를 사용한 페이지는 무조건 정적 페이지로 설정되므로, 내부의
fetch도 자동으로 정적 캐시로 동작함. (데이터 캐싱이 설정되지 않은 패칭도 정적으로 설정되니 주의!)
< 이전 빌드 결과 >

< 이후 빌드 결과 >

generateStaticParams로 설정하지 않은 페이지도 렌더링 되는 이유는 무엇일까?
generateStaticParams로 지정하지 않은 페이지는
자동으로 Dynamic Page로 생성되어 Full Route Cache에 저장된다.
즉, 최초 접속 이후에는 캐시된 버전이 빠르게 반환된다.

🚫 지정되지 않은 경로는 Not Found로 처리하기
export const dynamicParams = false;이 설정은 generateStaticParams에 정의된 경로 외에는
모두 404 페이지로 이동한다.
요청 실패 시 모든 페이지에 노출할 공통 페이지를 만들어보자.
우선 navigation 의 notFound() 함수를 호출하여 API 로직에 아래와 같이 추가한다.
if (!response.ok) {
if (response.status === 404) {
notFound();
}
return <div>오류가 발생했습니다...</div>;
}
//...
그리고 app/not-found.tsx 파일을 추가하면 자동으로 렌더링된다.
export default function NotFound() {
return <div>404: NotFound</div>;
}
빌드하고 재실행하여 브라우저로 접속해서 확인해보자.

| 개념 | 설명 |
|---|---|
| Full Route Cache | 빌드 타임에 생성된 페이지 결과를 저장 |
| Dynamic Page | 요청마다 새 데이터로 렌더링 |
| Static Page | 캐싱된 HTML로 즉시 응답 |
| revalidate | 일정 시간 후 캐시 갱신 (ISR 유사) |
| generateStaticParams | 정적 경로를 빌드 타임에 미리 생성 |
| Suspense | 비동기 컴포넌트를 로딩 UI로 감싸는 장치 |