https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-nextjs/dashboard
인프런의 한 입 크기로 잘라먹는 Next.js 강의를 듣고 정리한 내용입니다.
페이지 라우터는 getServerSideProps, getStaticProps, getStaticPaths와 같은 서버측에서만 실행되는 함수를 이용하여 데이터를 패칭했다.
export default async function Page() {
  const response = await fetch("...");
 
  return <div>...</div>;
}
앱 라우터는 React Server Component가 도입되면서 컴포넌트를 비동기 함수로 만들 수 있다.
서버 컴포넌트는 서버에서만 실행되기 때문에 별도의 함수를 따로 사용할 필요없이 컴포넌트 내부에서 데이터 패칭 로직을 작성해도 아무론 문제가 발생하지 않는다.
따라서 데이터가 필요한 컴포넌트에서 직접 데이터를 요청하여 사용할 수 있다.
fetch 메서드를 활용해 불러온 데이터를 Next 서버에 보관하는 기능으로 영구적으로 데이터를 보관하거나, 특정 주기로 갱신 시킬 수 있어 불필요한 데이터 요청을 줄여 성능을 개선할 수 있다.
const response = await fetch(`~/api`, { cache: "force-cache" });
fetch 메서드에 두 번째 인수로 다양한 캐싱 옵션을 설정할 수 있다.
logging: {
  fetches: {
    fullUrl: true,
  },
},
데이터 패칭마다 로그를 출력하고 싶다면 next.config.mjs 파일에 logging 옵션을 작성해준다.

데이터 패칭의 결과를 캐싱하지 않는 옵션이다.
const response = await fetch("~/book", {
  cache: "no-store",
});

로그를 확인하면 오른쪽에 (cache skip)이 적혀있고 데이터 캐싱이 동작하지 않았음을 알려준다.
데이터 캐싱이 동작하지 않는 이유에 대해서 (cache: no-store) 옵션이 설정되어 있어서라고 알려준다.

캐시 옵션을 넣지 않으면 (auto-nocache)로 캐싱되지 않는 이유를 알려준다.
Next 14 버전까지의 캐시 옵션 기본값은 무조건 캐싱되는 것이었지만 15 버전 이후 부터는 캐싱되지 않는다.

MISS라는 판정을 내린다.SET)해준다.HIT) 반환하고 백엔드 서버에 요청을 보내지 않는다.
로그를 확인하면 (cache hit)으로 추가적인 데이터 요청이 발생하지 않았음을 알 수 있다.

캐싱된 데이터는 JSON 형태로 Next 서버에 보관되고 파일 탐색기에서 확인할 수 있다.

특정 시간을 주기로 캐시를 업데이트하는 ISR 방식과 유사하다.
MISS 판정을 내린 뒤 백엔드 서버에 데이터를 요청하여 저장한다.STALE 상태로 설정한 뒤 반환하고 백엔드 서버로 요청하여 데이터를 최신화 시킨다.review-${bookId}] } }const response = await fetch("~/book", {
  next: { tags: [`review-${bookId}`] },
});
데이터 패칭에 특정 태그를 붙일 수 있도록 해주는 옵션으로 태그를 통해서 데이터 캐시를 초기화하거나 재검증 시키도록 설정할 수 있는 옵션이다.
서버 액션이 성공적으로 종료가 됐을 때, 페이지 또는 서버 컴포넌트들을 다시 렌더링해서 사용자가 보고 있는 페이지의 데이터를 최신화하고 싶을 때 사용하는 방법이다.
/book/${bookId});revalidatePath 함수가 호출되면 Next 서버가 자동으로 인수로 전달한 경로의 페이지를 재검증한다.
페이지가 다시 렌더링되기 때문에 페이지 컴포넌트의 자식 컴포넌트들이 모두 리렌더링 되면서 컴포넌트에 있는 데이터 패칭 또한 다시 수행된다.
오직 서버측에서만 호출할 수 있는 메서드이기 때문에 서버 컴포넌트 내부 또는 서버 액션에서만 호출할 수 있다.

revalidatePath는 인자로 넘겨준 경로의 페이지를 전부 재검증하는 기능이기 때문에 페이지에 포함된 모든 캐시를 무효화 시킨다.
따라서 컴포넌트 내부에서 { cache: "force-cache" }로 설정한 데이터 패칭이 있더라도 데이터 캐시가 삭제된다.
데이터 캐시뿐만 아니라 페이지를 캐싱하는 풀 라우트 캐시까지 무효화시키고 새롭게 생성된 페이지를 풀 라우트 캐시에 저장하진 않는다.
따라서 새로 고침을 통해 다시 페이지에 접속하면 캐시를 사용할 수 없으므로 Next에서 동적 페이지를 만들 듯, 페이지를 새롭게 생성하여 브라우저에게 보내주게 되고 그때 풀 라우트 캐시에 업데이트된다.
풀 라우트 캐시에 저장되지 않기 때문에 다음 접속 요청 시에 비교적 느린 응답 속도를 보인다.
저장되지 않는 이유는 revalidate 요청 이후에 브라우저에서 페이지에 접속하게 됐을 때, 무조건 최신의 데이터를 보장하기 위해서다.
특정 경로의 모든 동적 페이지를 재검증하는 방식이다.
첫 번째 인수로 폴더 경로를 넣어주고, 두 번째 인수로 "page"를 넣어주면 /book/[id]라는 형태를 갖는 모든 동적 페이지가 전부 재검증 된다.
특정 레이아웃을 기준으로 해당하는 레이아웃을 갖는 모든 페이지들을 재검증하는 방식이다.
첫 번째 인수로 재검증 기준이 되는 layout.tsx 파일의 경로를 적고, 두 번째 인수로 문자열 "layout"을 전달하면 해당 레이아웃을 갖는 모든 페이지가 재검증된다.
루트 레이아웃을 넣어줌으로써 모든 페이지를 재검증할 수도 있다.
review-${bookId});태그값을 기준으로 데이터 캐시를 재검증하는 방식으로 인수로 특정 태그를 입력하면 특정 태그를 갖는 모든 데이터 캐시가 재검증된다.
앞서 본 revalidatePath는 경로에 해당하는 데이터 캐시를 모두 삭제하는 반면, revalidateTag는 태그값을 갖고 있는 패치 메서드의 데이터 캐시만 삭제해주기 때문에 효율적으로 재검증할 수 있다.

하나의 페이지를 이루고 있는 여러 컴포넌트에서 중복으로 발생한 요청을 캐싱해서 한 번만 요청하도록 데이터 패칭을 최적화해주는 기능이다.
접속 요청을 받은 페이지에 동일한 주소의 동일한 데이터를 불러오는 요청이 있고 캐싱 옵션이 no-store로 되어있을 경우 리퀘스트 메모이제이션이 자동으로 캐싱하여 한 번만 요청하여 캐싱한다.
주의할 점은 리퀘스트 메모이제이션은 하나의 페이지를 요청하는 동안에만 존재하는 캐시로 중복되는 API 요청을 방지하는 데에만 목적을 두고 있다.
따라서 렌더링이 종료되면 캐시가 소멸되어 다음 접속 요청에는 데이터를 다시 요청하기 때문에 데이터 캐시와는 다르다.
Next에서 리퀘스트 메모이제이션을 제공하는 이유는 서버 컴포넌트가 도입되면서 컴포넌트가 각각 필요한 데이터를 직접 패칭하는 방식으로 데이터 패칭이 진행되고 컴포넌트의 구조가 복잡해질 경우 동일한 데이터를 요청하는 경우가 발생하기 때문이다.

/a 페이지가 풀 라우트 캐시에 저장되는 페이지로 설정되었다면, 빌드 타임에 렌더링을 진행한다.
페이지에 필요한 데이터를 리퀘스트 메모이제이션이나 데이터 캐시 등의 기능을 거쳐서 렌더링이 완료된 결과를 풀 라우트 캐시라는 이름으로 저장한다.
빌드 타임 이후에 /a 페이지로 접속 요청이 들어오면 캐시가 HIT되어 빠른 속도로 처리한다.
풀 라우트 캐시는 SSG 방식과 유사하게 빌드 타임에 정적으로 페이지를 만들어 놓고 캐시에 보관한 다음, 브라우저에서 요청이 오면 캐시에 저장된 페이지를 응답하는 페이지 캐싱 기능이다.
Next 앱에 만든 모든 페이지는 자동으로 정적 페이지와 동적 페이지로 나뉘고 정적 페이지만 풀 라우트 캐시가 적용된다.
기본적으로 동적 페이지가 아니면 모두 정적 페이지가 된다.
페이지가 접속 요청을 받을 때마다 변화가 생기거나 데이터가 달라질 경우(캐시되지 않는 데이터 패칭을 사용하거나, 컴포넌트 내부에서 쿠키, 헤더, 쿼리스트링 등 동적 함수를 사용할 경우) 동적 페이지로 구분된다.
| 동적 함수 | 데이터 캐시 | 페이지 분류 | 
|---|---|---|
| O | X | 동적 페이지 | 
| O | O | 동적 페이지 | 
| X | X | 동적 페이지 | 
| X | O | 정적 페이지 | 
풀 라우트 캐시는 정적 페이지에 대해서만 적용되고 빌드 타임에 생성되기 때문에 빠른 속도로 응답할 수 있다.
따라서 대부분의 페이지를 정적 페이지로 작성하는 걸 권장한다.

풀 라우트 캐시된 페이지도 ISR처럼 Revalidate가 가능하다.
revalidate: 3으로 설정한 경우 빌드 타임에 페이지가 캐싱된다.
3초 이전에 들어온 접속 요청에 대해서 캐싱된 페이지를 보여주고 3초가 지난 이후에 STALE 표시를 한 뒤 캐싱된 페이지를 응답한 다음, 서버에 다시 데이터를 불러와 데이터 캐시를 SET한 뒤 페이지를 다시 렌더링하여 풀 라우트 캐시에 저장된 페이지를 최신화한다.
정적인 페이지는 빌드 타임에 만들어져 캐싱되기 때문에 최대한 활용하는 것이 좋다.
export default function Page({
  searchParams,
}: {
  searchParams: {
    q?: string;
  };
}) {
  const response = await fetch(
    `${process.env.NEXT_PUBLIC_API_SERVER_URL}/book/search?q=${q}`,
    { cache: "force-cache" }
  );
 
  ...
 
  return (
    <div>...</div>
  );
}
쿠키, 헤더, 쿼리 스트링을 사용하는 동적 함수를 포함한 컴포넌트는 동적 페이지로 분류된다.
동적 페이지로 분류되면 브라우저로 부터 접속 요청을 받을 때마다 다시 생성되겠지만 최대한 데이터 캐시를 활용하여 최적화할 수 있다.
동적 경로를 갖는 페이지가 있다면 기본적으로 동적 페이지로 분류된다.
generateStaticParams 함수는 빌드 타임에 어떠한 URL 파라미터가 존재할 수 있는지 알려줌으로써 동적 경로를 갖는 페이지를 정적 페이지로 빌드 타임에 생성되도록 해주는 함수다.
export function generateStaticParams(){
  return [{id: "1"}, {id: "2"}, {id: "3"}];
}
 
export default function Page({
  params,
}: {
  params: {id: string};
}) {
  const response = await fetch(`${process.env.NEXT_PUBLIC_API_SERVER_URL}/book/${params.id}`);
 
  ...
 
  return (
    <div>...</div>
  );
}
generateStaticParams 함수를 통해 정적인 URL 파라미터들을 담은 배열을 반환하면 빌드 타임에 Next 서버가 자동으로 읽어 반환값에 해당하는 페이지를 정적으로 생성한다.
빌드 결과를 확인해보면,

반환값으로 내보냈던 1, 2, 3에 해당하는 페이지가 빌드 타임에 생성된 걸 확인할 수 있다.
generateStaticParams 함수의 URL 파라미터 값은 문자열로만 반환해야 한다.
또한 페이지 컴포넌트 내부에 데이터 캐싱이 설정되지 않는 데이터 패칭이 존재해도 정적 페이지로 설정된다.
generateStaticParams 함수는 getStaticPaths 함수와 동일한 역할을 한다.
반환값으로 설정하지 않은 URL 파라미터로 접속하면 동적 페이지로 실시간으로 만들어진다.
if (!response.ok) {
  if (response.status === 404) {
    return notFound();
  }
  return <div>오류가 발생했습니다...</div>;
}
존재하지 않는 데이터일 경우 notFound()를 반환하여 404 페이지로 리다이렉트 시킬 수 있다.
export const dynamicParams = false;
dynamicParams 옵션을 false로 내보내주면 정적으로 설정한 URL 파라미터가 아닐 경우 모두 404 페이지로 리다이렉트한다.
풀 라우트 캐시를 적용하기 위해 정적 페이지로 변환하는 과정을 거치면서 페이지에 존재하는 컴포넌트들이 동적 함수를 사용하는지, 캐싱되지 않는 데이터 패칭을 하고 있지 않은지 검사해야 하는 번거로운 과정이 필요하다.
모든 컴포넌트들을 확인하지 않아도 강제로 동적, 정적 페이지로 설정하는 옵션이 라우트 세그먼트 옵션이다.
dynamicParams 옵션도 라우트 세그먼트 옵션 가운데 하나다.
라우트 세그먼트 옵션은 많은 옵션을 제공하지만 모두 사용되지 않고 있기 때문에 자주 사용되는 dynamic 옵션만 다뤄보겠다.
export const dynamic = "";
dynamic 옵션은 페이지의 유형을 강제로 동적, 정적으로 설정해주는 옵션으로 auto, force-dynamic, force-static, error라는 네 가지 옵션을 제공한다.
auto는 기본값으로 자동으로 동적, 정적인 페이지로 설정해주고 생략 가능하다.
export const dynamic = `force-static`;
 
export default function Page({
  searchParams,
}: {
  searchParams: {
    q?: string;
  };
}) {
  const response = await fetch(
    `${process.env.NEXT_PUBLIC_API_SERVER_URL}/book/search?q=${q}`,
    { cache: "force-cache" }
  );
 
  ...
 
  return (
    <div>...</div>
  );
}
만약 동적 페이지에 force-static 옵션을 주면 강제로 정적 페이지로 설정된다.
이때 페이지 내부에서 사용되는 쿼리 스트링 같은 동적 함수들은 undefined를 반환, 데이터 패칭은 no-store로 설정된다.
동적 함수를 undefined로 반환하도록 하기 때문에 쿼리 스트링을 사용하는 페이지의 경우 제대로 동작하지 않는다.
force-error는 force-static과 동일하게 정적 페이지로 설정해주지만 동적 함수나 캐싱되지 않는 데이터 패칭 등이 있다면 빌드 오류를 발생시킨다.
앱 라우터는 모든 컴포넌트들이 어떻게 동작하는지에 따라 동적, 정적 페이지로 자동으로 설정해주기 때문에 dynamic 옵션은 권장되지 않는다.
하지만 개발 중에 의도적으로 동적, 정적 페이지로 설정해야 되는 경우 dynamic 옵션을 적용한 다음에 나중에 고쳐나가는 식으로 진행할 수 있다.
클라이언트 라우터 캐시는 클라이언트의 브라우저에 저장되는 캐시로 페이지 이동을 효율적으로 진행하기 위해 페이지의 일부 데이터를 보관하는 기능이다.

~/ 경로의 인덱스 페이지는 정적 페이지, ~/search 경로의 페이지는 동적 페이지이고 루트 레이아웃을 같이 사용하고 있는 상황이라고 가정해보자.
인덱스 페이지로 요청을 보내면 풀 라우트 캐시에 저장된 페이지를 반환한다.
~/search 경로로 이동하면 동적 페이지이기 때문에 캐시는 SKIP되고 실시간으로 페이지를 생성해 반환한다.
클라이언트 라우터 캐시가 없다면 인덱스 페이지와 search 페이지에서 공통으로 사용하고 있는 루트 레이아웃을 중복으로 요청하게 된다.
자세하게 얘기하면 Next 서버는 브라우저에게 사전 렌더링된 HTML 파일, 클라이언트 컴포넌트들의 데이터를 포함하고 있는 JS Bundle, 서버 컴포넌트들의 데이터를 포함하고 있는 RSC Payload를 보낸다.
브라우저는 인덱스 페이지에서 search 페이지로 이동하는 과정에서 Next 서버로 부터 전달받은 RSC Payload에는 중복된 레이아웃이 있는 문제가 있다.
Next는 이러한 비효율을 줄이기 위해 브라우저에 클라이언트 라우터 캐시라는 새로운 캐시 공간을 추가해 서버로 부터 받는 RSC Payload 값들 중에 레이아웃에 해당하는 데이터만 따로 보관한다.
클라이언트 라우터 캐시는 따로 적용할 필요없이 자동 적용되어 있다.
클라이언트 라우터 캐시를 사용해도 새로 고침을 하거나 탭을 껐다가 다시 접속하는 경우에는 사라지기 때문에 동작하지 않는다.