기록

Next.js의 Static rendering, Dynamic rendering, Cache

als982001 2023. 7. 4. 11:11

 Next.js에서 프로젝트를 빌드하고 실행할 수 있는 두 가지 명령어가 있다.

  • npm run build: Next.js 애플리케이션을 번들링, 최적화 등을 거쳐 빌드한다.
  • npm run start: 빌드한 결과물을 바탕으로, 실제 서비스할 애플리케이션을 실행한다.

이 때, Next.js에서 npm run build를 실행할 경우 콘솔에서 확인할 수 있는 게 있다.

Route의 왼쪽에 ο와 λ 표시가 있다. 우선, ο는 static rendering을 의미한다. Static rendering이란 간단히 말하자면 페이지를 미리 렌더링해서 클라이언트에게 보내주는 것이다. 그렇기에 항상 같은 내용이 보이게 된다. 그리고 λ는 dynamic rendering을 의미하며, 이는 요청이 들어올 때마다 렌더링하는 것이다. 그렇기에 저 route로 들어갈 때마다 변경점이 반영되어 보이게 된다. 

 예를 들어, 위의 /write는 글을 작성하는 페이지로 static rendering이다. 글을 작성하는 페이지는 크게 달라질 것이 없기에 문제가 없어 보인다. 그리고 /detail과 /edit은 글의 상세 정보를 보여주는 페이지와 글을 수정하는 페이지로 둘 다 dynamic rendering이다. 이 역시 문제가 없어 보인다. 왜냐하면 글이라는 것이 언제 수정될지 모르기 때문이다. 하지만 글의 목록을 보여주는 /list가 static rendering이다. 글이 언제 추가되고 삭제될지 모르기에 이는 문제가 있어 보인다. 실제로 npm run start를 통해 list 페이지를 확인해보았다.

분명 글을 작성하였는데 작성한 글이 보이지 않는다. 이를 어떻게든 해결해야 할 것 같다. 그리고 다행히, 이를 해결하기 위한 두 가지 방법이 있다.

 

1. 강제로 dynamic rendering으로 바꾸기

 

 'dynamic'이라는 예약된 변수명이 있다. 이를 통해 강제로 static rendering 혹은 dynamic rendering으로 바꿀 수 있다. 예를 들어, /list의 page.js에 아래 코드를 추가할 경우, /list는 dynamic rendering이 된다. (그리고 "force-static"으로 설정할 경우 static rendering이 된다.)

import { connectDB } from "@/utils/database";
import ListItem from "./ListItem";

export const dynamic = "force-dynamic";	// <= 추가한 코드

export default async function List() {
  let db = (await connectDB).db("forum");
  let result = await db.collection("post").find().toArray();
  result = result.map((item) => {
    item._id = item._id.toString();
    return item;
  });

  return (
    <div className="list-bg">
      <ListItem result={result} />
    </div>
  );
}

npm run build 결과

코드를 추가한 후, npm run build를 한 결과를 보면, /list가 dynamic rendering으로 바뀐 것을 확인할 수 있다. 그리고 이 상태에서 글을 올릴 경우, 제대로 새로운 글이 보이는 것을 확인할 수 있다. 지금같이 규모가 작을 때는 상관이 없지만, dynamic rendering 페이지들이 많을 경우 유저가 그 페이지를 방문할 때마다 다시 페이지를 그려야 하기에 서버 부담이 심해질 수 있다. 

 

2. 캐싱 기능 이용

 캐싱은 이전에 요청된 내용을 저장하고, 동일한 요청이 발생할 때 저장된 내용을 사용하여 서버로부터 데이터를 다시 가져오지 않아도 되게 하는 기능이다. 이를 통해 서버의 부담을 조금이라도 줄일 수 있다.

 

2-1. GET 요청 결과 캐싱

await fetch('/URL', { cache: 'force-cache' });
await fetch('/URL', { cache: 'no-store' });
await fetch('/URL', { next: { revalidate: 60 } });

 첫 번째 코드처럼 { cache: 'force-cache' }를 추가할 경우, 결과를 캐싱한다. 이를 통해 응답을 기다릴 필요 없이 저장된 데이터를 가져오게 된다. (사실 'force-cache'가 디폴트 값이라고 한다.)

 두 번째 코드의 { cache: 'no-store' }는 캐싱 기능을 이용하지 않을 때 적용할 수 있는 옵션이다. 그렇기에 fetch할 때마다 서버로 요청하여 데이터를 새로 가져온다. 이는 실시간으로 데이터를 받아오는 것이 중요할 때 이용할 수 있다.

 마지막 옵션인 { next: { revalidate: 60 } }초 단위의 작성된 숫자만큼 캐싱 결과를 보존하고 그 시간이 지날 경우 새로 요청하여 받은 값을 캐싱한다. 위의 예시에서는 60초마다 새로 요청할 것이다.

 

2-2. 페이지 단위 캐싱

 'revalidate'라는 변수가 있다. 이 변수에 초 단위의 숫자를 넣어서 그 페이지를 원하는 시간만큼 캐싱할 수 있다. 예를 들어 아래처럼 10을 설정할 경우, 10초마다 캐싱한다.

import { connectDB } from "@/utils/database";
import ListItem from "./ListItem";

export const revalidate = 10;

export default async function List() {
  let db = (await connectDB).db("forum");
  let result = await db.collection("post").find().toArray();
  result = result.map((item) => {
    item._id = item._id.toString();
    return item;
  });

  return (
    <div className="list-bg">
      <ListItem result={result} />
    </div>
  );
}

npm run build의 결과를 보면 알 수 있듯, static rendering이다. 하지만 밑의 움짤에서 확인할 수 있듯, 글을 작성한 후 어느 정도 시간이 지나고 새로고침을 하니 목록에 글이 추가되는 것을 확인할 수 있다.