
상황
나는 page router를 좋아하지만, 이번에는 여러 사정이 있어서 app router를 쓰게 되었다. 이왕 쓰게 된 김에 이런 저런 기능을 몽땅 활용할 생각이었다. 가장 기대하던 기능은 데이터 캐싱이었는데, 가능한 많은 걸 캐싱해두면 랜더링 완료 시점을 한참 일찍 앞당길 수 있을 거란 기대가 있었다.
나는 여러 캐싱 기능을 테스트해보기 위해 아래와 같은 코드를 작성했다. 백엔드에서 랜덤한 기술 질문을 하나 가져오고, ─ 원래라면 revalidate: 10 을 넣었겠지만 ─ 캐시에 TTL 없이 보관하는 설정을 시험해보고자 cache: "force-cache"로 지정해 fetch 요청을 캐싱했다. 이렇게 하면 빌드 시점 이후에는 동일한 요청에 대해 항상 캐시된 데이터를 반환하게 되며, 의도한 대로라면 서버의 부하를 줄이고 클라이언트가 빠르게 응답을 받아 페이지 렌더링도 빨라져야 했다.
const question = await fetch("http://localhost:3000/api/tech-question", {
cache: "force-cache",
});
if (question.ok) {
const data = await question.json()
console.log(data)
} else {
console.log(question.statusText)
}문제
아래와 같이 next.config.ts에 logging 설정을 해주면 요청을 보낼 때마다 해당 요청이 캐싱되는지 아닌지 여부를 터미널에서 확인할 수 있다. 그런데 cache hit으로 떠야할 로그가 몇 번을 시도해도 cache skip으로 뜨는 게 아닌가!
const nextConfig: NextConfig = {
/* config options here */
logging: {
fetches: {
fullUrl: true,
},
}
};
cache: "force-cache"를 포함해 다양한 옵션을 테스트해보았지만 cache: "no-store"를 포함해 캐시되지 말아야 할 경우에는 캐시가 되지 않고, 캐시가 되어야 할 경우에도 캐시가 되지 않았다. 그런데 놀랍게도 build 후 start를 하니 캐시가 되어야 할 경우에는 캐싱 되기 시작했다. 그래서 처음에는 단순히 dev 모드에 버그가 있는 게 아닐까 미루어 짐작했다.
하지만 오늘 다른 개발자분이 동일한 node와 next 버전에서 동일한 테스트를 진행했고, dev 모드에서도 문제 없이 캐시가 캐싱되는 걸 확인할 수 있었다. 결국 원인은 다른 데 있는 것이었다.
실마리는 아주 우연히 찾아왔다. 맥을 아예 재시동한 후 dev 모드를 켜자 데이터 캐시가 제대로 작동하기 시작했다. 이상 없이 캐싱되는 것을 확인하고자 크롬 개발자 도구를 열었는데, 그 순간부터 응답 로그에 다시 "cache skip"이 나타났다. 도무지 납득이 되지 않아 여러 번 요청을 반복했지만 결과는 같았다. 개발자 도구가 열려있으면 캐시가 스킵되고, 닫혀있으면 제대로 캐싱되었다. 혹시나 하여 다른 브라우저에서도 테스트해보았지만 개발자 도구가 열려있던 닫혀있던 상관 없이 언제나 캐시가 제대로 캐싱되었다.
내 의심을 확인하기 위해 이전에 테스트에 도움을 주셨던 분께 한 번 더 도움을 요청했다. 하지만 그분은 크롬 개발자 도구를 연 상태에서도 문제 없이 캐싱이 동작한다고 말했다. 그렇다면 '크롬 개발자 도구'가 문제였던 게 아니라 '내 크롬 개발자 도구'에 문제가 있던 것이다.
해결
react developer tools가 문제일 거라 추측했지만, 원인은 전혀 다른 곳에 있었다. 나는 랜더링 속도를 확인하기 위해 "Disable cache" 옵션을 사용해 응답 결과가 캐싱되는 것을 막아두었는데, 이것이 원인이었다. 해당 옵션을 끄고 다시 테스트해보니, 기대하던 대로 캐시가 정상적으로 작동하기 시작했다.

더 읽어보기
2026.03.01
렌더링 전략 정리
리액트와 Next.js에서 렌더링 전략은 단순한 옵션 선택이 아니다. 이는 서비스의 초기 로딩 속도, 서버 비용, 캐싱 전략, SEO 노출, 개발 복잡도까지 동시에 좌우하는 아키텍처 결정이다. 프로젝트 규모가 커질수록 “어디에서 HTML을 생성하는가”, “언제 자바스크립트를 실행하는가”…
2025.07.15
앱 라우터를 싫어하게 된 몇 가지 이유
지금까지 진행한 대부분의 프로젝트에서는 Next.js의 페이지 라우터를 사용해왔지만, 이번에는 처음으로 앱 라우터(App Router)를 도입해보았다. 특별한 이유가 있었던 것은 아니고, 그저 기술적인 호기심 때문이었다. 서버 수준에서 처리되는 fetch cache나 서버 컴포넌트 개념…
2025.05.26
3. SharedWorker로 멀티탭 소켓을 하나로 묶기
같은 서비스를 10개의 탭으로 열었을 때 서버에 소켓 연결도 10개가 생긴다면, 문제는 실시간 기능 자체가 아니라 브라우저 컨텍스트마다 연결을 새로 만드는 구조에 있을 수 있다. 채팅, 알림, 협업 상태처럼 페이지가 열려 있는 동안 유지되는 연결은 탭 수만큼 늘어날 때 서버와 클라이언트…
2025.05.26
2. Dedicated Worker를 Next.js에서 안전하게 쓰기
Worker를 만들 수 있다는 사실보다 더 중요한 것은, Next.js가 서버와 브라우저를 오가는 동안 그 Worker 생성 코드가 언제 평가되는지 통제하는 일이다. Dedicated Worker는 하나의 호출자와 연결되는 구조라서 이미지 처리나 대용량 데이터 변환처럼 특정 화면의 작업…
2026.06.01
React Server Components를 위한 컴포넌트 아키텍처
이 포스트는 Vercel의 Next.js 팀 소속 개발자 Aurora Scharff가 자신의 블로그에 올린 Component Architecture for React Server Components 게시글을 번역한 것이다. 번역하는 과정에서 다소 의역이 있을 수 있으며, 일부 번역에는…
2026.05.26
차트는 멈췄는데 윈도우가 움직인다
상황 어느날 서비스를 살펴보시던 팀장님께서 이런 말씀을 slack에 남기셨다. 진호님, 예측 차트에서 zoom을 계속하면 어느 순간 라인 차트가 아니라 단일 스캐터 차트처럼 보이는 데, 이거 수정하면 좋을 거 같아요. 어느정도 zoom을 하면 그 이후로는 zoom이 안 되도록 할 수 없…
2026.05.21
피자가게로 이해하는 디자인 패턴
에이든 피자는 처음부터 복잡한 시스템을 만들 생각이 없었다. 처음에는 메뉴 몇 개만 만들면 됐다. 그런데 손님은 커스텀 주문을 넣기 시작했고, 주방은 상태를 나눠야 했고, 결제와 배달앱과 알림이 하나씩 붙었다. 코드도 가게를 닮는다. 장사가 잘될수록 이상하게 더 쉽게 망가진다. 디자인…
2026.05.21
7. Decorator — 토핑 추가할 때마다 클래스를 새로 만들 수 없다
에이든 피자에서 주문서를 객체로 만들자 취소와 재주문은 한결 편해졌다. 그런데 주문이 편해지자 손님들도 한결 편해졌다. 편해진 손님은 더 많은 요구를 한다. "치즈 추가요", "올리브도 추가요", "소스 많이요", "조금 더 바삭하게 구워주세요" 같은 요청이 주문대 위로 쌓이기 시작했다…
댓글
댓글을 불러오는 중...