[Next.js] 6. 페이지 캐싱

2025. 3. 27. 00:02·🌃 Next.js/🔜 next.js-study

🔖 풀 라우트 캐시

Full Route Cache : Next 서버측에서 빌드 타임에 특정 페이지의 렌더링 결과를 캐싱하는 기능.

 빌드 타임에 페이지를 생성하고, -> 렌더링 결과를 Next.js 서버의 캐시에 저장
이후 브라우저에서 해당 페이지 요청이 들어오면, 새롭게 페이지를 생성하지 않고 캐시된 페이지를 즉시 반환한다.

 

 

 

- 사진처럼 마지막 단계에서 `페이지 생성 결과`를 저장한다.

- next 서버가 가동되었을때, 같은 페이지 요청이 들어오면 Full Router Cache에 저장된 페이지를 전달 ( 다시렌더링x)))

 

 

> 이런 풀 라우트 캐시 동작 방식은, Page Router에서 빌드 타임에 페이지를 생성하고, 미리 저장해두었다가 요청 시 반환하는 SSG(정적 사이트 생성) 방식과 매우 유사하다. 그러니까 이 풀 라우트 캐시는 브라우저에 요청이 들어왔을 때 캐시에 저장된 페이지를 그대로 응답해주는 페이지 캐싱 기능이라고 생각하면 된다.

 

✅ 자동 캐싱 조건

다음 조건이면 Next.js가 자동으로 캐싱해요:

  • `export const dynamic = "force-static"` 이거나 기본값(auto)이고
  • 요청에 따라 바뀌지 않는 정적인 페이지 (블로그 글, 상세페이지 등)

즉, 동적이 아니고, 요청마다 결과가 달라지지 않는다면 캐싱 대상이 될 수 있다.

⚠️ 주의할 점

  • 로그인 사용자마다 다르게 보여야 하는 페이지는 캐싱되면 안 되기 때문에, Next.js가 자동으로 캐시를 끄기도 함
  • `export const dynamic = "force-dynamic"`으로 설정하면 캐싱이 꺼진다.

📍Dynamic Page로 설정되는 기준📍
: 특정 페이지가 접속 요청을 받을 때마다 매번 변화가 생기거나, 데이터가 달라지는 경우.

캐시되지 않은 Data Fetching을 사용할 경우. ( `cache: "no-store"`로 설정된 fetch 요청.)))
데이터가 매번 새롭게 불러와지는 경우  = 동적 함수(쿠키, 헤더, 쿼리스트링)을 사용하는 컴포넌트가 있을 때.
-> 이외에는 다 Static Page로 자동으로 설정된다.

 

 

- Static page는 풀라우트 캐시를 사용하기 때문에 빠르게 동작한다.

- 풀라우트캐시도 revalidate가 적용된다.

 

❗Dynamic Page가 항상 안티패턴은 아니다


풀 라우트 캐시 사용이 가능한 Static Page를 사용하면 훨씬 더 빠른 페이지 응답이 가능하기 때문에 더 권장되는 방식이지만 그렇다고 해서 Dynamic Page 사용이 좋지 않은 방식이란 건 아니다.

Dynamic Page는 비효율적이거나 느리다고만 볼 수 없다. 다음과 같은 상황에서는 Dynamic Page를 필요로 한다.

- 인증이 필요한 페이지
     쿠키 데이터를 조회해 사용자를 인증.
- 검색 결과 페이지
   쿼리스트링 값을 기반으로 데이터가 달라져야 하는 경우.

 


또한, Dynamic Page는 풀 라우트 캐시는 적용되지 않지만, 리퀘스트 메모이제이션과 데이터 캐시는 동일하게 활용할 수 있다.
이를 통해 API 요청을 최적화하거나 데이터를 캐싱해 성능을 개선할 수 있다.

Static Page는 풀 라우트 캐시를 통해 빠른 응답 속도를 제공하기 때문에 가능한 한 적극적으로 활용하는 것이 좋다.
그러나 Dynamic Page도 필요에 따라 사용해야 한다.
예를 들어, 동적 값이나 실시간 데이터가 필수적인 경우 Dynamic Page가 더 적합하다.

🔖풀 라우트 캐시와 Revalidate

풀 라우트 캐시는 정적 페이지에만 적용되지만, 정적 페이지도 특정 Revalidate 주기로 캐싱 데이터를 갱신할 수 있다.


페이지를 구성하는 컴포넌트들 중에

` fetch,revalidate:3 `이렇게 하나의 데이터 페칭 요청이라도 Revalidate옵션이 존재한다면



-> 현재 설정된 Revalidate타임에 따라서 데이터 캐시 뿐만 아니라, 페이지 캐시 즉 풀 라우트 캐시 또한 함께 업데이트 
이는 Page Router에서 사용되던 ISR(증분 정적 재생성) 방식과 동일


> 예를 들면 A라는 Static Page가 빌드 타임에 생성이 될 때 3s간격으로 Revalidate돼야 데이터 페칭이 존재하게 된다면 데이터 캐시에 보관이 되는 데이터 페칭의 결과 뿐만 아니라, 풀 라우트 캐시에 저장이 되는 페이지의 렌더링 결과 또한 revalidate 타임으로 설정된 3s주기로 계속해서 다시 업데이트 된다.

 

 

풀 라우트 캐시의 장점
1. 빠른 응답 속도

빌드 타임에 미리 생성된 페이지를 캐시에서 즉시 반환.
서버 부하를 줄이고, 사용자 경험(UX)을 향상.
2. 효율적인 데이터 관리

데이터 캐시와 연동하여, 데이터를 효율적으로 재활용.
특정 Revalidate 주기로 데이터와 페이지를 동기화.
3. 개발 유연성

Static Page와 Dynamic Page의 분리를 통해 각 페이지에 맞는 최적화를 적용.

 

 

결론 

다음과 같은 경우에 풀 라우트 캐시를 적극적으로 활용하면 좋다.

자주 변경되지 않는 콘텐츠.
빠른 응답 속도가 요구되는 페이지.
동적 데이터가 필요하지 않은 페이지.

🔖 Dynamic 페이지를 Static 페이지로 전환하여 풀라우트 캐시 적용

1️⃣문제 상황

 

쿼리스트링과 빌드타임 문제

- useSearchParams()와 같은 동적 데이터 관련 훅은 브라우저 환경에서만 동작하며, 빌드타임에서는 값을 알 수 없기 때문에 에러 발생 가능

- 예를 들어, Seacrh 컴포넌트에서 `useSearchParams()`를 호출하면, 빌드타임에는 쿼리스트링(?검색어) 값이 존재하지 않기 때문에 오류발생

 

// src/components/searchbar.tsx

"use client";

import { useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";

export default function Searchbar() {
  const router = useRouter();
  const searchParams = useSearchParams();
  const [search, setSearch] = useState("");

  const q = searchParams.get("q");

  useEffect(() => {
    setSearch(q || "");
  }, [q]);

  const onSubmit = () => {
    if (!search || q === search) return;
    router.push(`/search?q=${search}`);
  };

  return (
    <div>
      <input
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        onKeyDown={(e) => e.key === "Enter" && onSubmit()}
      />
      <button onClick={onSubmit}>검색</button>
    </div>
  );
}

 

 

2️⃣ 문제해결: 클라이언트 컴포넌트로 분리

 

React의 Suspense로 클라이언트 컴포넌트 분리

- useSearhParam 훅을 사용하고 있는 Searchbar 컴포넌트를 오직 클라이언트 측에서만 실행이 되도록 -> 사전 렌더링 과정에서는 완전히 배제되도록 설정해주면 됨

- > React의 Suspense 컴포넌트를 사용해ㅓ Searchbar와 같은 클라이언트 컴포넌트를 빌드타임에서 제외하고, 

     브라우저 환경에서만 렌더링 되도록 설정한다.

 

// src/app/(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>
  );
}

 

 

Suspense의 동작 원리

suspense로 컴포넌트 감싸주게 되면 이제부터 이 컴포넌트는 사전 렌더링 과정에서는 배제되고, 

오직 클라이언트 측에서만 렌더링 되는 컴포넌트로서 설정된다.



-fallback :  비동기 작업이 완료될때까지 대체 UI를 렌더링

-동작방식:  서버에서 사전 렌더링시 Searhbar는 렌더링 되지 않고, fallback UI만 렌더링됨.

- 클라이언트 환경: useSearchParams가 쿼리스트링을 불러오는 이런 컴포넌트의 비동기 작업이 종료된 후 ( 컴포넌트가 브라우저에 마운트 되었을때) 에야 컴포넌트가 완전히 렌더링



3️⃣ Dynamic 페이지의 원인 분석 및 수정

Next.js에서는 Dynamic 페이지가 캐싱되지 않는데이터 패칭이나, 동적함수 사용으로 인해 설정된다.

모든 페이지를 static페이지로 만들수는 없고 

특정 컴포넌트에 대해 캐시옵션을 통해 static 하게 만들어도 되는 이유를 이해하고 적용해야한다.

 

현재 빌드 결과를 살펴보면, 지금 이 Next 앱에는 index, not-found, book, search페이지가 존재한다.

그리고 각 페이지의 유형은 빌드 결과의 경로 앞에 표시된 기호를 통해 확인할 수 있다.

- index 페이지: f(function 기호)가 붙어 있어 Dynamic 페이지로 설정되어 있다.
- not-found 페이지: 빈 동그라미가 붙어 있어 Static 페이지로 설정되어 있다.
- book, search 페이지: f 기호가 붙어 있어 index 페이지와 마찬가지로 Dynamic 페이지로 설정되어 있다.

 

 

1. Index 페이지는 force-cache와 Suspense 설정으로 Static 페이지로 전환되었다. (index페이지 앞에 빈 동그라미 기호가 붙어서 static페이지로서 설정이 된 걸 확인할 수 있다.)

2. .next/server/app/index.html 파일을 확인하면, Static 페이지로 캐싱된 HTML 파일을 볼 수 있는데, index페이지가 build타임에 미리 생성이 완료되어서 서버 측에 캐싱이 된 것이다. 그리고 이때 이런 기능을 바로 "풀 라우트 캐시"라고 부른다.

 

 

5️⃣ 결론

 

Dynamic → Static 전환 요약

1. force-cache를 통해 데이터 페칭 결과를 캐싱.
2. Suspense로 클라이언트 컴포넌트를 빌드 타임에 서버 렌더링에서 제외.
3. Revalidate 옵션을 사용해 정적 페이지도 특정 주기로 갱신 가능.

 

풀 라우트 캐시의 이점

1.정적 HTML 파일을 빌드 타임에 생성하여 빠른 응답 속도 제공.
2. Revalidate 옵션과 결합해 동적 데이터도 효과적으로 관리 가능.

🔖동적 페이지 Static 페이지로 전환해보자

search 페이지 분석 및 최적화

 

mport BookItem from "@/components/book-item";
import { BookData } from "@/types";

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>
  );
}

 

 문제점: Static 페이지 전환 불가


이 페이지는 searchParams라는 쿼리스트링 보관하는 props를 받아 검색 결과를 가져오는 동적 함수를 사용하기 때문에 Dynamic페이지로서 존재하는데 이 페이지는 Static 페이지로 설정할 수 없다. 
Static 페이지는 빌드 타임에 완전히 렌더링되지만, 쿼리스트링은 런타임에 브라우저에서 전달되므로 빌드 타임에는 존재하지 않는 값이기 때문이다.


해결 방안: 데이터 캐시 활용


Static 페이지로 전환할 수 없어서 풀 라우트 캐시는 포기해야 하지만, 데이터를 효율적으로 관리하기 위해 데이터 캐시를 활용하여 검색 속도를 개선할 수 있다.

fetch 메서드의 cache: "force-cache" 옵션을 설정하면, 브라우저로부터 접속 요청을 받았을 때 페이지는 계속해서 다시 생성이 되겠지만 이미 검색된 데이터는 캐시에서 빠르게 반환되어 응답 속도가 빨라진다.

정리하자면, search페이지는 index페이지와는 다르게 쿼리스트링 같은 동적인 값에 의존하고 있다. 그렇기 때문에 static페이지로는 설정할 수 없지만 데이터 캐시를 활용하는 쪽으로 최적화를 해줄 수 있다.

 

 const response = await fetch(`${process.env.NEXT_PUBLIC_API_SERVER_URL}/book/search?q=${searchParams.q}`, {
    cache: "force-cache",
  });

 

book 페이지 분석 및 Static 설정

// src/app/book/[id]/page.tsx

import { notFound } from "next/navigation";
import style from "./page.module.css";

export function generateStaticParams() {
  return [{ id: "1" }, { id: "2" }, { id: "3" }];
}

export default async function Page({
  params,
}: {
  params: { id: string | string[] };
}) {
  const response = await fetch(`${process.env.NEXT_PUBLIC_API_SERVER_URL}/book/${params.id}`);
  if (!response.ok) {
    return <div>오류가 발생했습니다...</div>;
  }

  const book = await response.json();
  return (
    <div className={style.container}>
      <div
        className={style.cover_img_container}
        style={{ backgroundImage: `url('${book.coverImgUrl}')` }}
      >
        <img src={book.coverImgUrl} />
      </div>
      <div className={style.title}>{book.title}</div>
      <div className={style.author}>{book.author} | {book.publisher}</div>
      <div className={style.description}>{book.description}</div>
    </div>
  );
}

 

문제점: 기본적으로 Dynamic 페이지


이 페이지는 지금 id라는 URL 파라미터를 갖는 여러개의 동적 경로에 대응하는 페이지이다. 하지만 현재 코드에서는 URL에 동적으로 전달되는 id 값이 무엇인지 알 수 없다.따라서 Next.js는 이 페이지를 Dynamic 페이지로 설정한다.
Dynamic 페이지는 요청이 들어올 때마다 실시간으로 서버에서 데이터를 가져와 페이지를 생성하므로, 정적으로 캐싱된 페이지에 비해 응답 속도가 느리다.

해결 방안: generateStaticParams 함수 활용


generateStaticParams는 빌드 타임에 생성될 URL 파라미터를 명시하여 Static 페이지로 설정하는 함수로, 빌드 타임에 Next서버가 book페이지에 어떠한 경로들이 올 수 있는지 그래서 어떠한 URL 파라미터들이 존재할 수 있는지 알려준다.

쉽게 말하면 이 book페이지에 어떤 도서 데이터들이 빌드 타임에 만들어져야 하는지 먼저 알려주는 것이다. 아래와 같이 설정하면, id가 1, 2, 3인 도서의 페이지를 빌드 타임에 미리 생성할 수 있다.

export function generateStaticParams() {
  return [{ id: "1" }, { id: "2" }, { id: "3" }];
}


동작 방식


1. Next.js는 빌드 타임에 generateStaticParams에서 반환된 URL 파라미터(id: 1, 2, 3)를 읽는다.
2. 각 파라미터 값에 해당하는 페이지를 미리 렌더링하여 Static 페이지로 생성한다. (ex: /book/1,/book/2,/book/3)
3. 생성된 페이지는 풀 라우트 캐시에 저장된다.
4. 정적으로 설정되지 않은 /book/4 같은 경로는 Dynamic 페이지로 동작하며, 요청 시 실시간으로 생성된다.

 

실시간 Dynamic 페이지의 동작

 

만약 /book/4 경로로 접속하면 어떻게 될까?

- /book/4는 generateStaticParams에 포함되지 않았으므로, 빌드 타임에 정적으로 생성되지 않는다.
- 따라서 Dynamic 페이지로 동작하여, 서버에서 데이터를 가져온 후 실시간으로 페이지를 생성한다.
- 생성된 페이지는 풀 라우트 캐시에 저장된다.

 

🛠 Dynamic 페이지의 실시간 생성


1, 처음 /book/4에 접속하면 실시간 데이터 페칭을 통해 페이지를 생성한다.
2. 따라서 Dynamic 페이지로 동작하여, 서버에서 데이터를 가져온 후 실시간으로 페이지를 생성한다.
3. 다음 요청부터는 캐시에 저장된 페이지를 반환하므로 더 빠른 응답 속도로 제공한다. (풀 라우트 캐시에 저장)

 

Dynamic 페이지 제한

 

generateStaticParams에서 정적으로 설정한 경로 외의 URL을 허용하지 않으려면 dynamicParams 값을 false로 설정한다.

 

 

 export const dynamicParams = false;

 

그럼 Next 서버가 이 페이지를 생성할 때 자동으로 dynamicParams 변수의 값을 확인해서 false로 내보내졌으니까 지금 이 페이지의 URL 파라미터는 다이나믹 하면 안 되겠구나 하고 generateStaticParams로 부터 내보내진 /book/1, /book/2, /book/3 이외의 요청에는 모두 404 페이지로 리다이렉트된다.

 

 

 

 

 

 

 

 

 

https://velog.io/@codns1223/Nextjs-Full-Route-Cache-%ED%92%80-%EB%9D%BC%EC%9A%B0%ED%8A%B8-%EC%BA%90%EC%8B%9C

https://velog.io/@realeun243/https://velog.io/@codns1223/Nextjs-dhecyijvDay-13-%EB%8D%B0%EC%9D%B4%ED%84%B0%ED%8C%A8%EC%B9%AD-SSR

https://velog.io/@codns1223/Nextjs-dhecyijv

'🌃 Next.js > 🔜 next.js-study' 카테고리의 다른 글

[Next.js] 5. 데이터패칭  (0) 2025.03.25
[Next.js] 4. 페이지 라우팅 설정하기  (0) 2025.03.18
'🌃 Next.js/🔜 next.js-study' 카테고리의 다른 글
  • [Next.js] 5. 데이터패칭
  • [Next.js] 4. 페이지 라우팅 설정하기
gprorogpfus
gprorogpfus
:- >
  • gprorogpfus
    gpfusdldks
    gprorogpfus
  • 전체
    오늘
    어제
    • 분류 전체보기 (55)
      • 🎭JavaScript (2)
      • 👚 CSS (1)
      • ⚛️ React (13)
      • 🌃 Next.js (5)
        • 🔜 next.js-study (3)
      • 🥏TypeScript (10)
      • 🏴알고리즘 (2)
      • 🌴트러블슈팅 (3)
      • ⛲ 프로젝트 (6)
        • 👖gproro-shop-app (8)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Redux
    JavaScript
    GIT
    react
    TypeScript
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
gprorogpfus
[Next.js] 6. 페이지 캐싱
상단으로

티스토리툴바