Spring Boot와 Next.js 모두 캐시를 다루지만, 같은 단어를 쓰더라도 위치와 동작 방식은 꽤 다르다. Spring Boot는 주로 서비스 로직 결과를 저장하고, Next.js는 fetch 결과나 렌더링 결과를 재검증하는 쪽에 가깝다.
둘을 함께 쓰는 서비스에서는 어느 계층에서 무엇을 캐시하는지 분리해서 봐야 한다. 그렇지 않으면 백엔드 캐시를 비웠는데 화면은 그대로이거나, 화면 재검증은 됐는데 API 응답은 이전 값을 주는 식으로 헷갈리기 쉽다.
@Cacheable, @CacheEvict, @CachePut 같은 캐시 추상화를 통해 서비스 로직 결과를 캐시합니다.revalidatePath, revalidateTag로 캐시 무효화를 구현할 수 있습니다.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) {
}
}
Spring Boot는 캐시 기능을 제공하지만, TTL 자체를 Spring이 직접 관리하는 구조는 아닙니다.
역할을 나누면 아래에 가깝습니다.
@Cacheable("products")"products" 캐시에 몇 분 동안 저장할지는 Redis 또는 Caffeine 설정에서 결정즉, Spring Boot에서는 보통:
이런 정책을 캐시 구현체 설정에서 잡습니다.
Next.js는 Spring처럼 서비스 메서드 호출 결과를 캐시하기보다, 다음을 캐시합니다.
fetch()로 가져온 데이터 결과즉, 캐시의 중심이 서비스 함수가 아니라 페이지/데이터/렌더링 결과에 가깝습니다.
const data = await fetch("https://api.example.com/products", {
next: { revalidate: 3600 },
});
이 코드는 3600초 단위로 재검증되도록 설정한 예시입니다.
가능합니다.
다만 Spring의 TTL과 완전히 같은 개념은 아니고, 재검증 주기에 가깝습니다.
export const revalidate = 3600;
또는
const data = await fetch("https://api.example.com/products", {
next: { revalidate: 3600 },
});
즉, Next.js는 보통 **"만료되면 삭제"**보다
**"시간이 지나면 다시 만들어라"**에 더 가깝습니다.
항상 그렇지는 않습니다.
public/ 아래에 둔 파일은 일반적인 정적 파일입니다.
예:
public/test.htmlpublic/logo.png이런 파일은 보통 다시 배포해야 변경됩니다.
이건 설정에 따라 달라집니다.
revalidate를 쓰고 있으면, 재배포 없이도 다시 생성될 수 있습니다.즉, Next.js에서 보이는 HTML이 전부 같은 성격은 아닙니다.
| 항목 | Spring Boot | Next.js |
|---|---|---|
| 캐시 대상 | 서비스 메서드 결과 | fetch 결과, 렌더링 결과, 라우트 결과 |
| 중심 위치 | 백엔드 서비스 로직 | 프레임워크 렌더링/데이터 계층 |
| 시간 설정 | Redis/Caffeine 등 구현체 TTL | revalidate 기반 재검증 |
| 캐시 제거 | @CacheEvict | revalidatePath, revalidateTag |
| 대표 사용 목적 | DB/API 호출 감소 | 페이지 성능 최적화, 정적/동적 렌더링 최적화 |
가능합니다.
Spring Boot에서는 종종 Swagger에서 호출할 수 있도록 관리용 API를 열어 캐시를 초기화합니다.
예를 들어:
@RestController
@RequestMapping("/admin/cache")
public class CacheAdminController {
@DeleteMapping("/products")
public ResponseEntity<?> clearProductsCache() {
return ResponseEntity.ok().build();
}
}
Next.js도 비슷하게 Route Handler를 만들어 관리용 엔드포인트를 만들 수 있습니다.
// 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",
});
}
// 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",
});
}
@CacheEvict와 Next.js 대응@CacheEvict(value = "products", allEntries = true)
public void clearProductsCache() {
}
revalidateTag("products", "max");
또는 페이지 단위면:
revalidatePath("/products");
Spring의 @CacheEvict는 보통 캐시 엔트리를 제거하는 성격이 강합니다.
반면 Next.js의 revalidatePath, revalidateTag는
보통 다음 요청 시 다시 검증/생성 대상으로 표시하는 개념에 더 가깝습니다.
즉, 완전히 같은 동작은 아니지만, 운영 관점에서는 충분히 비슷한 역할을 합니다.
실무에서는 둘 중 하나만 쓰는 것이 아니라, 같이 쓰는 경우도 많습니다.
예:
즉, 백엔드 캐시 + 프론트/SSR 캐시를 함께 가져가는 구조도 충분히 가능합니다.
Spring Boot와 Next.js는 둘 다 캐시를 다루지만, 접근 방식이 다릅니다.
또한 시간 설정도 차이가 있습니다.
revalidate 기반 재검증그리고 캐시 초기화도 둘 다 가능합니다.
@CacheEvict, 관리용 APIrevalidatePath, revalidateTag즉, Next.js에 캐시가 없는 것이 아니라, Spring과 캐시의 위치와 동작 방식이 다르다고 이해하는 것이 가장 정확하다. 두 계층을 같이 쓸 때는 캐시 키, 재검증 시점, 강제 무효화 경로를 함께 문서화해두는 편이 운영에서 덜 흔들린다.