고양이hyebin
Next.js 서버의 문제를 캐싱으로 해결하기
September 22, 2025

Next.js 서버의 문제점

JavaScript는 싱글 스레드이기에 한번에 하나의 일을 처리할 수 있다.

요청 1 → 처리 중... (다른 요청들 대기)
요청 2 → 대기 중...
요청 3 → 대기 중...
요청 4 → 대기 중...

Node.js는 기본적으로 단일 스레드에서 실행하기 때문에 서버 사이드 렌더링(SSR) 시 요청이 순차적으로 처리하게 된다. 이때 동시 요청이 많으면 병목 현상 발생하여 하나의 요청 처리가 오래 걸리면 다른 요청들이 모두 대기해야 한다.

이는 응답 시간 지연과 서버 과부하로 사용자 경험 저하되는 큰 문제가 되고 운영서버에서 발생할 경우 큰 이슈이다. next.js가 생각보다 적은 양의 트래픽으로도 무너질 수 있다는 것을 주의해야한다.

우리는 Next.js가 최신 기술로 인식되어 쉽게 사용하지만 SSR이 관리하는 방향에 대해서 고민이 필요하고, 프레임워크를 고를때 이를 간과해서는 안된다 .

2. 캐싱 전략 해결책

캐싱이란?

이전에 계산한 결과를 저장해두고, 같은 요청이 오면 저장된 결과를 바로 반환하는 기술 → 모든 사용자가 같은 캐시된 페이지를 받는다.

캐싱의 효과

캐싱 없음:
요청 → 데이터베이스 조회 → 계산 → 렌더링 → 응답 (느림)

캐싱 있음:
요청 → 캐시 확인 → 저장된 결과 반환 (빠름)

3. Next.js 캐싱 방법들

방법 1: fetch를 이용한 데이터 캐싱

// 무조건 적으로 캐싱
const response = await fetch('https://api.example.com/posts', {
  next: { revalidate: 'force-cache' }
});
// 60초 동안 캐싱
const response = await fetch('https://api.example.com/posts', {
  next: { revalidate: 60 }
});

동작 과정:

  1. 1.첫 번째 요청: API 호출 → 결과 캐싱
  2. 2.60초 내 요청: 캐시된 데이터 즉시 반환
  3. 3.60초 후 요청: 캐시 갱신

방법 2: 페이지 레벨 캐싱 (ISR)

// next app router
// 1시간 동안 페이지 전체 캐싱
export const revalidate = 3600;

export default async function Page() {
  const posts = await fetch('https://api.example.com/posts');
  return <div>포스트 목록</div>;
}

ISR (Incremental Static Regeneration) 동작:

  1. 1.첫 방문자: 페이지 생성 → 캐싱
  2. 2.1시간 내 방문자들: 캐시된 페이지 즉시 제공
  3. 3.1시간 후 첫 방문자: 기존 페이지 제공 + 백그라운드에서 새 페이지 생성
  4. 4.새 페이지 준비 완료: 이후 방문자들에게 새 페이지 제공

방법 3: 태그 기반 캐싱

// 데이터에 태그 붙이기
const posts = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] }
});

// 필요할 때 특정 태그만 캐시 무효화
import { revalidateTag } from 'next/cache';

export async function createPost() {
  // 새 포스트 생성 로직
  revalidateTag('posts'); // 'posts' 태그 캐시만 새로고침
}

Next.js ISR 캐시는 어디에 저장될까?

Next.js의 ISR(Incremental Static Regeneration)을 사용할 때 캐시가 정확히 어디에 저장되는지, 그리고 서버가 여러 개일 때 어떤 문제가 발생할 수 있는지 알아보겠습니다.

캐시는 "파일 시스템"에 저장된다

ISR을 사용하면 캐시는 서버가 실행되는 환경의 파일 시스템에 저장됩니다. 메모리가 아닌 실제 파일로 생성되죠.

확인하는 방법

ISR 코드로 빌드 후 다음 경로를 확인해보세요:

.next/server/app/${pagename}.html

이 파일을 열어보면 ISR로 생성된 HTML이 미리 그려져 있는 것을 확인할 수 있습니다. 서버 실행 후 캐시가 갱신되면, 이 파일의 내용도 변경된 데이터로 업데이트됩니다.

멀티 Pod 환경에서의 문제점

여러 개의 서버 인스턴스(Pod)가 실행되는 환경에서는 각각의 서버가 독립적인 파일 시스템을 가집니다.

시나리오 예시

2개의 Pod가 존재하는 경우:

Pod A → server A running → .next/server/app/${pagename}.html
Pod B → server B running → .next/server/app/${pagename}.html

각 Pod마다 별도의 캐시 파일이 생성됩니다. n개의 Pod가 있다면 캐시도 n개 존재하게 되죠.

문제 상황: 지속적인 Cache Miss

다음과 같은 조건에서 문제가 발생할 수 있습니다:

  • revalidate: 60 설정
  • 사용자가 30초마다 방문
  • 트래픽이 로드밸런싱으로 순차 분산

타임라인:

0~30초   → 사용자 A 방문 → Pod A cache miss (Pod A에서 캐시 생성)
31~60초  → 사용자 B 방문 → Pod B cache miss (Pod B에서 캐시 생성)
61~120초 → 사용자 C 방문 → Pod A cache miss (Pod A에서 다시 캐시 생성)
...

결과적으로 계속해서 cache miss가 발생하는 상황이 벌어집니다.

해결방안: Redis를 활용한 통합 캐시 관리

이런 문제를 해결하기 위해 Redis 캐싱 서버를 도입할 수 있습니다.

Redis 도입 후의 플로우

0~30초  → 사용자 A 방문 → Pod A → Redis cache miss (캐시 생성)
31~60초 → 사용자 B 방문 → Pod B → Redis cache hit ✅
61초 후  → 사용자 C 방문 → Pod A → Redis cache hit ✅

이제 60초마다 한 번씩만 캐시가 생성되어 원하는 대로 동작합니다.

정리

  • Next.js ISR 캐시는 파일 시스템에 저장됩니다
  • 멀티 Pod 환경에서는 각 Pod마다 독립적인 캐시가 생성됩니다
  • 로드밸런싱 환경에서는 지속적인 cache miss 문제가 발생할 수 있습니다
  • Redis 같은 외부 캐시 저장소를 사용하여 문제를 해결할 수 있습니다

참고 영상

https://www.youtube.com/watch?v=5SojnABKBqA