castle.log
GitHub

Spring Boot와 Next.js 캐시 전략

2026년 05월 21일  3개월 전
Next.js
Spring
캐시
캐시 계층브라우저Next.js fetch / ISRSpring @CacheableDB화면 재검증서비스 결과 저장두 계층을 따로 보지 않으면, 백엔드 캐시를 비워도 화면은 그대로다
Next.js와 Spring은 서로 다른 것을 캐시한다. 무효화도 따로 해야 한다.

Spring Boot와 Next.js 모두 캐시를 다루지만, 같은 단어를 쓰더라도 위치와 동작 방식은 꽤 다르다. Spring Boot는 주로 서비스 로직 결과를 저장하고, Next.js는 fetch 결과나 렌더링 결과를 재검증하는 쪽에 가깝다.

둘을 함께 쓰는 서비스에서는 어느 계층에서 무엇을 캐시하는지 분리해서 봐야 한다. 그렇지 않으면 백엔드 캐시를 비웠는데 화면은 그대로이거나, 화면 재검증은 됐는데 API 응답은 이전 값을 주는 식으로 헷갈리기 쉽다.

먼저 정리한 차이

  • Spring Boot는 주로 @Cacheable, @CacheEvict, @CachePut 같은 캐시 추상화를 통해 서비스 로직 결과를 캐시합니다.
  • Spring Boot에서 TTL(만료 시간), eviction 정책, 저장 위치는 보통 Redis, Caffeine 같은 캐시 구현체가 담당합니다.
  • Next.js는 Spring처럼 서비스 메서드 결과를 캐시하는 구조라기보다, 데이터 fetch 결과, 서버 렌더링 결과, 라우트 결과를 프레임워크 차원에서 캐시합니다.
  • Next.js의 시간 설정은 일반적인 캐시 TTL보다는 재검증(revalidation) 주기에 가깝습니다.
  • Spring Boot에서 Swagger로 캐시 초기화 API를 만들 수 있듯, Next.js도 Route Handler로 관리용 엔드포인트를 만들어 revalidatePath, revalidateTag로 캐시 무효화를 구현할 수 있습니다.

1. Spring Boot의 캐시

어디에 쓰는가

Spring Boot는 보통 비즈니스 로직 결과를 캐시하는 데 사용합니다.

예를 들어, 같은 상품 조회 로직이 반복 호출될 때 매번 DB를 조회하지 않고 캐시에 저장된 값을 재사용할 수 있습니다.

자주 쓰는 어노테이션

  • @Cacheable: 캐시에 값이 없으면 실행 후 저장, 있으면 캐시 반환
  • @CacheEvict: 캐시 제거
  • @CachePut: 메서드는 실행하고 결과를 캐시에 갱신

예시 코드

@Service
public class ProductService {

    @Cacheable(value = "products", key = "#id")
    public Product getProduct(Long id) {
        return productRepository.findById(id).orElseThrow();
    }

    @CacheEvict(value = "products", key = "#id")
    public void evictProduct(Long id) {
    }
}

특징

  • 서비스 메서드 중심으로 캐시를 적용합니다.
  • 캐시 로직을 비즈니스 코드와 비교적 분리해서 다룰 수 있습니다.
  • Redis, Caffeine, Ehcache 같은 구현체와 함께 자주 사용됩니다.

2. Spring Boot에서 만료 시간은 어디서 잡나?

Spring Boot는 캐시 기능을 제공하지만, TTL 자체를 Spring이 직접 관리하는 구조는 아닙니다.

역할을 나누면 아래에 가깝습니다.

  • Spring: 캐시 추상화 제공
  • Redis/Caffeine: 실제 저장, TTL, eviction 담당

예를 들면

  • @Cacheable("products")
  • 실제 "products" 캐시에 몇 분 동안 저장할지는 Redis 또는 Caffeine 설정에서 결정

운영 기준

즉, Spring Boot에서는 보통:

  • 캐시를 어디에 저장할지
  • 몇 초/몇 분 뒤 만료시킬지
  • 최대 몇 개까지 저장할지

이런 정책을 캐시 구현체 설정에서 잡습니다.


3. Next.js의 캐시

어디에 쓰는가

Next.js는 Spring처럼 서비스 메서드 호출 결과를 캐시하기보다, 다음을 캐시합니다.

  • fetch()로 가져온 데이터 결과
  • 서버에서 렌더링된 페이지 결과
  • 라우트 이동에 필요한 일부 클라이언트 캐시

즉, 캐시의 중심이 서비스 함수가 아니라 페이지/데이터/렌더링 결과에 가깝습니다.

예시 코드

const data = await fetch("https://api.example.com/products", {
  next: { revalidate: 3600 },
});

이 코드는 3600초 단위로 재검증되도록 설정한 예시입니다.

특징

  • 데이터 fetch와 렌더링 결과를 프레임워크 차원에서 캐시합니다.
  • 페이지 단위, 데이터 단위로 캐시를 다루는 경우가 많습니다.
  • 전통적인 백엔드 캐시보다는 정적 생성 + 재검증 개념이 강합니다.

4. Next.js에서 시간 지정은 가능한가?

가능합니다.
다만 Spring의 TTL과 완전히 같은 개념은 아니고, 재검증 주기에 가깝습니다.

예시

export const revalidate = 3600;

또는

const data = await fetch("https://api.example.com/products", {
  next: { revalidate: 3600 },
});

의미

  • 3600초 동안은 기존 캐시 결과를 사용
  • 그 이후에는 다시 검증해서 새 결과를 생성

즉, Next.js는 보통 **"만료되면 삭제"**보다
**"시간이 지나면 다시 만들어라"**에 더 가깝습니다.


5. 한번 서버에 올린 HTML은 계속 그대로인가?

항상 그렇지는 않습니다.

경우 1. 진짜 정적 파일

public/ 아래에 둔 파일은 일반적인 정적 파일입니다.

예:

  • public/test.html
  • public/logo.png

이런 파일은 보통 다시 배포해야 변경됩니다.

경우 2. Next.js가 생성한 페이지

이건 설정에 따라 달라집니다.

  • 완전한 static export라면 다시 빌드/배포해야 반영됩니다.
  • ISR 또는 revalidate를 쓰고 있으면, 재배포 없이도 다시 생성될 수 있습니다.

즉, Next.js에서 보이는 HTML이 전부 같은 성격은 아닙니다.


6. Spring Boot와 Next.js 캐시의 차이

항목Spring BootNext.js
캐시 대상서비스 메서드 결과fetch 결과, 렌더링 결과, 라우트 결과
중심 위치백엔드 서비스 로직프레임워크 렌더링/데이터 계층
시간 설정Redis/Caffeine 등 구현체 TTLrevalidate 기반 재검증
캐시 제거@CacheEvictrevalidatePath, revalidateTag
대표 사용 목적DB/API 호출 감소페이지 성능 최적화, 정적/동적 렌더링 최적화

7. Spring Boot의 Swagger 캐시 초기화 API처럼 Next.js도 가능한가?

가능합니다.

Spring Boot에서는 종종 Swagger에서 호출할 수 있도록 관리용 API를 열어 캐시를 초기화합니다.

예를 들어:

@RestController
@RequestMapping("/admin/cache")
public class CacheAdminController {

    @DeleteMapping("/products")
    public ResponseEntity<?> clearProductsCache() {
        return ResponseEntity.ok().build();
    }
}

Next.js도 비슷하게 Route Handler를 만들어 관리용 엔드포인트를 만들 수 있습니다.


8. Next.js에서 캐시 초기화 API 만들기

path 기준 무효화

// app/api/admin/cache/clear/route.ts
import { revalidatePath } from "next/cache";
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const auth = req.headers.get("x-admin-secret");

  if (auth !== process.env.ADMIN_CACHE_SECRET) {
    return NextResponse.json({ message: "unauthorized" }, { status: 401 });
  }

  revalidatePath("/products");

  return NextResponse.json({
    ok: true,
    message: "/products cache marked for revalidation",
  });
}

tag 기준 무효화

// app/api/admin/cache/clear/route.ts
import { revalidateTag } from "next/cache";
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const auth = req.headers.get("x-admin-secret");

  if (auth !== process.env.ADMIN_CACHE_SECRET) {
    return NextResponse.json({ message: "unauthorized" }, { status: 401 });
  }

  revalidateTag("products", "max");

  return NextResponse.json({
    ok: true,
    message: "products tag marked for revalidation",
  });
}

9. Spring의 @CacheEvict와 Next.js 대응

Spring

@CacheEvict(value = "products", allEntries = true)
public void clearProductsCache() {
}

Next.js

revalidateTag("products", "max");

또는 페이지 단위면:

revalidatePath("/products");

차이점

Spring의 @CacheEvict는 보통 캐시 엔트리를 제거하는 성격이 강합니다.

반면 Next.js의 revalidatePath, revalidateTag
보통 다음 요청 시 다시 검증/생성 대상으로 표시하는 개념에 더 가깝습니다.

즉, 완전히 같은 동작은 아니지만, 운영 관점에서는 충분히 비슷한 역할을 합니다.


10. 실무적으로 어떻게 이해하면 좋은가?

Spring Boot가 더 잘 맞는 상황

  • 서비스 로직 결과를 캐시하고 싶을 때
  • DB 조회, 외부 API 응답을 백엔드 레벨에서 줄이고 싶을 때
  • Redis 같은 중앙 캐시 저장소를 운영하고 있을 때

Next.js가 더 잘 맞는 상황

  • 페이지 응답 속도를 높이고 싶을 때
  • 서버 렌더링 결과를 효율적으로 재사용하고 싶을 때
  • 정적 생성과 동적 생성 사이를 유연하게 운영하고 싶을 때

같이 쓰는 경우

실무에서는 둘 중 하나만 쓰는 것이 아니라, 같이 쓰는 경우도 많습니다.

예:

  • Spring Boot: DB 조회 결과를 Redis에 캐시
  • Next.js: 페이지 fetch 결과와 렌더링 결과를 재검증 기반으로 캐시

즉, 백엔드 캐시 + 프론트/SSR 캐시를 함께 가져가는 구조도 충분히 가능합니다.


11. 정리

Spring Boot와 Next.js는 둘 다 캐시를 다루지만, 접근 방식이 다릅니다.

  • Spring Boot는 서비스 로직 결과 캐시
  • Next.js는 데이터/렌더링 결과 캐시

또한 시간 설정도 차이가 있습니다.

  • Spring Boot: 구현체가 TTL 관리
  • Next.js: revalidate 기반 재검증

그리고 캐시 초기화도 둘 다 가능합니다.

  • Spring Boot: @CacheEvict, 관리용 API
  • Next.js: Route Handler + revalidatePath, revalidateTag

즉, Next.js에 캐시가 없는 것이 아니라, Spring과 캐시의 위치와 동작 방식이 다르다고 이해하는 것이 가장 정확하다. 두 계층을 같이 쓸 때는 캐시 키, 재검증 시점, 강제 무효화 경로를 함께 문서화해두는 편이 운영에서 덜 흔들린다.