개발을 하다 보면 캐시(Cache)라는 용어를 자주 접하게 된다.
브라우저 캐시, React Query 캐시, Router Cache, 서버 캐시, Redis 캐시처럼 이름도 많고 위치도 다르다.
그런데 그렇게 공부할수록 더 헷갈렸다.
이름은 다른데 도대체 어떤 데이터가 어디에 저장되는지, 그리고 언제 다시 사용되는지가 잘 보이지 않아서 그런 것 같다.
특히 Next.js의 캐싱 구조를 공부하면서 이런 생각이 들었다.
캐시를 종류별로 외우는 것보다, 데이터가 어디에 저장될 수 있는지로 보면 더 이해하기 쉽지 않을까?
그래서 이번 글에서는 캐시를 기술별로 설명하기보다, 데이터가 저장될 수 있는 위치를 기준으로 프론트엔드 캐시를 정리해보려고 한다.
게시글 목록 페이지를 조회한다고 가정해보자.
우리가 화면에서 보는 데이터는 단순히 DB에서 바로 브라우저로 오는 것이 아니다.
중간에는 여러 레이어가 존재하고, 각 레이어는 필요에 따라 데이터를 저장해두었다가 다시 사용할 수 있다.
Browser
↓
Frontend Server
↓
Backend
↓
DB
이 구조를 조금 더 자세히 보면 다음과 같다.

Browser
├─ HTTP Cache
├─ React Query Cache
└─ Router Cache
Frontend Server
├─ Request Memoization
└─ Frontend Server Cache
Backend
└─ Shared Cache (Redis)
DB
└─ Origin Data
캐시를 구분하고 이해하는 것은 결국 "이번 요청이 어디에서 멈추는가"를 이해하는 것에 가까운 것 같다.
브라우저에서 끝날 수도 있고, 프론트엔드 서버에서 끝날 수도 있고, 백엔드 캐시에서 끝날 수도 있다. 모든 캐시를 통과하면 그때 비로소 DB까지 요청이 내려간다.
처음에는 브라우저 캐시라고 하면 하나만 떠올렸다.
하지만 실제로는 브라우저 안에도 성격이 다른 여러 캐시가 존재한다.
| 캐시 | 저장 위치 | 저장 대상 | 관리 주체 |
|---|---|---|---|
| HTTP Cache | 브라우저 메모리/디스크 | HTTP 응답 | 브라우저 |
| React Query Cache | 브라우저 메모리 | API 데이터 | 애플리케이션 |
| Router Cache | 브라우저 메모리 | 페이지 이동 결과 | 프레임워크 |
HTTP Cache는 브라우저가 네트워크를 통해 받아온 리소스를 저장해두는 캐시다.
이미지, JS, CSS 같은 정적 파일뿐 아니라 조건에 따라 API 응답도 포함될 수 있다.
이 캐시는 React Query처럼 애플리케이션이 직접 관리하는 캐시가 아니라, 브라우저와 HTTP 규약이 관리하는 캐시에 가깝다.
React Query Cache는 API 응답 데이터를 브라우저 메모리에 저장하는 캐시다. 아마 프론트엔드에게 가장 익숙한 캐시일 것 같다.
같은 데이터를 다시 요청할 때 매번 네트워크를 보내는 대신, 이미 받아둔 데이터를 재사용할 수 있다.
🤔 HTTP Cache와 React Query Cache 둘 다 API 응답을 캐싱하네..?
API 요청을 캐싱하는 관점에서 HTTP Cache와 React Query Cache 모두 활용할 수 있다.
다만 동작하는 위치가 다르다.
React Query Cache는 애플리케이션 레벨에서 동작한다. 이미 보유한 데이터를 사용할 수 있다면 fetch 자체를 실행하지 않는다.
반면 HTTP Cache는 브라우저 레벨에서 동작한다. fetch가 실행되더라도, 브라우저가 이미 저장된 응답을 사용할 수 있다고 판단하면 실제 네트워크 요청을 생략하거나 최소화한다.
즉 React Query Cache는 애플리케이션이 데이터를 재사용하기 위한 캐시이고, HTTP Cache는 브라우저가 네트워크 요청을 최적화하기 위한 캐시라고 볼 수 있다.
두 캐시는 서로 대체 관계가 아니라, 같은 요청에서 함께 사용될 수도 있다.
Router Cache는 페이지 이동 결과를 저장하는 캐시다.
예를 들어 게시글 목록에서 상세 페이지로 이동했다가 다시 돌아왔을 때, 목록 화면이 즉시 복원되는 경험을 한 적이 있을 것이다.
이때는 데이터 자체를 다시 요청했다기보다, 이전 페이지의 렌더링 결과를 재사용했을 가능성이 높다.
React Query Cache가 데이터를 저장한다면, Router Cache는 페이지 이동 결과를 저장한다고 생각하면 이해하기 쉽다.
캐시는 브라우저에만 존재하는 것이 아니다.
Next.js 같은 서버 기반 프론트엔드 환경에서는 서버도 데이터를 다시 사용할 수 있다.
하나의 요청을 처리하는 과정에서 동일한 데이터를 여러 번 요청하는 경우가 있다.
Request Memoization은 이런 중복 요청을 줄여주는 역할을 한다.
이 캐시는 여러 사용자가 함께 쓰는 장기 캐시라기보다, 한 번의 렌더링 안에서 발생하는 중복 작업을 줄이는 최적화에 가깝다.
프론트엔드 서버도 여러 요청에 걸쳐 데이터를 재사용할 수 있다.
예를 들어 공지사항 목록이나 카테고리 정보처럼 자주 바뀌지 않는 데이터는 프론트엔드 서버가 일정 시간 동안 보관했다가 재사용할 수 있다.
다만 이 레이어는 프레임워크 버전과 설정에 따라 동작 방식이 달라질 수 있다.
그래서 이 글에서는 특정 프레임워크의 구현 세부사항보다는 "프론트엔드 서버도 데이터를 재사용할 수 있다"는 관점만 가져가려고 한다.
브라우저 캐시와 React Query Cache가 사용자 로컬에 존재한다면, 백엔드 캐시는 여러 사용자가 함께 사용하는 캐시다.
대표적으로 Redis가 있다.
예를 들어 A 사용자가 먼저 조회한 결과를 Redis에 저장해두면, B 사용자는 DB까지 내려가지 않고 같은 결과를 사용할 수 있다.
React Query Cache가 사용자 브라우저 안에만 머무는 캐시라면, Redis는 서버가 공유하는 캐시라고 볼 수 있다.
캐싱을 한다라고 했을 때, 가장 많이 이야기를 했던 부분이 React Query의 캐싱이어서, 가장 먼저 떠올랐고, 그 기준으로 생각을 많이 했었다.
하지만 실제로는 HTTP Cache, React Query Cache, Router Cache처럼 성격이 다른 캐시가 동시에 존재하고 있다.
셋 다 브라우저 안에 있지만 저장하는 대상과 사용하는 시점은 꽤 다르다.
가장 헷갈렸던 부분은 같은 게시글 목록 데이터가 한 곳에만 저장되는 것이 아니라는 점이었다.
상황에 따라 브라우저 메모리에도 있을 수 있고, 프론트엔드 서버에도 있을 수 있고, 백엔드 공용 캐시에도 있을 수 있다.
캐시를 공부하면서 가장 크게 바뀐 관점은 이것이었다.
중요한 건 캐시가 있다는 사실 자체가 아니라, 이번 요청이 어디에서 멈췄는지 보는 것이다.
브라우저에서 끝났는지, 프론트엔드 서버에서 끝났는지, 백엔드 캐시에서 끝났는지, 아니면 결국 DB까지 내려갔는지를 보는 것이 더 중요했다.
처음에는 캐시를 "데이터를 요청을 최적화하는 기술"이라고 생각했다.
하지만 정리하면서 느낀 건, 캐시는 오히려 "얼마나 오래된 데이터를 허용할 것인가"에 대한 관점도 중요하게 고려해야 한다는 것을 알게 되었다.
같은 데이터가 여러 레이어에 동시에 존재할 수 있고, 각 레이어는 서로 다른 기준으로 최신성을 관리한다. 그렇기 때문에 여러 캐시의 저장을 고려하다보면 반복해서 과거의 데이터를 조회하게 될 수 도 있다.
결국 캐시를 이해한다는 것은 저장 위치를 이해하는 것뿐 아니라, 각 레이어가 어떤 수준의 데이터 신선도를 보장하는지 의도를 가지고 설계하는 것이 중요하다는 것을 알 수 있었다.