라이브 스코어 플랫폼이 프로덕션에서 간헐적으로 먹통이 됐다. 다섯 번 새로고침하면 네 번은 되고, 한 번은 안 된다. 이런 류의 버그가 가장 잔인한 이유는 재현이 안 되는 척 위장하기 때문이다. dev.to에 공유된 Ahmed Ali의 Flacron Gamezone 사례는 바로 이 상황에서 시작된다.
원인은 단순했다. Next.js App Router는 fetch()를 기본적으로 캐싱한다—요청 단위가 아니라 서버 인스턴스 수명 단위로. 라이브 경기 데이터를 가져오는 apiGet() 헬퍼가 cache: "no-store"를 명시하지 않았고, 첫 요청의 응답이 이후 요청에 그대로 내려갔다. 실시간 스코어 플랫폼에서 캐시된 데이터는 단순한 버그가 아니라 제품 자체가 거짓말을 하는 상황이다.
그런데 cache: "no-store" 하나 추가한다고 끝나지 않았다. 두 번째 문제가 레이어드돼 있었다. 서버 컴포넌트가 URL의 searchParams(?page=2, ?status=live)를 읽어 페이지네이션 데이터를 패칭하는 구조였는데, App Router는 이 동적 의존성을 정적 분석 단계에서 깔끔하게 처리하지 못했다. Dynamic server usage 에러가 바로 여기서 나왔다. 두 이슈가 서로를 증폭시키며 간헐적 실패를 만들었다.
최종 해법은 데이터 소유권의 이동이었다. 서버 컴포넌트는 레이아웃 셸만 담당하고, 페이지네이션·필터 상태와 데이터 패칭은 클라이언트 컴포넌트(useState + useEffect)로 옮겼다. 이 결정이 'RSC의 이점을 포기하는 것'처럼 느껴질 수 있지만, Ahmed는 명확하게 반박한다. 서버 컴포넌트는 요청 시점에 확정된 데이터, 사용자 인터랙션에 의존하지 않는 데이터, 캐싱이 적절한 데이터에 맞다. 유저 주도의 페이지네이션과 필터는 그 어느 조건도 충족하지 않는다. 클라이언트 패칭은 폴백이 아니라, 이 기능에 정확히 맞는 도구다.
이 사례가 기술적 디버깅에 그친다면 흥미로운 포스트모템으로 끝난다. 그런데 이 버그 뒤에 더 큰 질문이 숨어 있다. AI 도구가 이 문제를 대신 잡아줄 수 있었을까? 그리고 잡아줄 수 있다면, 개발자가 설계해야 할 것은 무엇인가?
dev.to의 또 다른 글—"How to enjoy programming in a world of AI"—은 역설적인 통찰을 던진다. 저자는 Claude Code와 DeepSeek을 번갈아 쓰면서 발견했다. Claude는 실수가 적지만, 그만큼 개발자가 '구경꾼'이 된다. DeepSeek은 실수가 잦지만, 개발자가 오히려 능동적 파트너로 복귀한다. 더 강한 AI가 반드시 더 나은 개발 경험을 만들지 않는다는 것이다. AI가 잡아주는 버그와 개발자가 직접 설계해야 하는 판단 사이의 경계를 어디에 그을 것인가—그 감각 자체를 잃으면 개발자는 도구의 결과를 검증하는 능력도 함께 잃는다.
세 번째 관점은 복잡성 흡수 원칙에서 온다. "The Hidden Contract of Mastery"는 프로듀서-컨슈머 계약을 이렇게 정의한다. 프로듀서의 일은 복잡성을 흡수해서 컨슈머에게 단순함을 전달하는 것. 3D 프린트 검사 CLI에 --clahe 플래그를 문서화하는 것은 복잡성을 사용자에게 전가하는 것이고, 조명 조건을 자동 감지해서 내부에서 처리하는 것이 진짜 흡수다. 사용자는 printsight photo.jpg만 입력하면 된다.
Next.js 캐시 문제에 이 렌즈를 얹으면 구조가 명확해진다. cache: "no-store" 하나를 빠뜨린 것은 단순 실수지만, 그 뒤에 있는 진짜 판단은 이것이다. 어떤 데이터는 서버에서 캐싱하고, 어떤 데이터는 클라이언트에서 신선하게 패칭해야 하는가. 이 경계를 설계하는 일은 AI에게 위임할 수 없다. App Router의 캐싱 모델이 무엇을 가정하는지, 우리 제품의 데이터 특성이 그 가정과 어디서 충돌하는지—이 두 가지를 연결하는 판단은 도메인과 프레임워크를 동시에 이해하는 사람만 내릴 수 있다. AI는 cache: "no-store" 추가를 제안할 수 있지만, 페이지네이션 상태 전체를 클라이언트로 이동시켜야 한다는 결론은 기능의 성격과 프레임워크의 렌더링 모델을 함께 읽어야 나온다.
이것이 캐시 설계에서 개발자가 끝까지 쥐어야 할 판단 영역이다. AI는 useEffect로 클라이언트 패칭 코드를 빠르게 생성할 수 있다. 하지만 그 코드를 어느 컴포넌트 경계에 배치할 것인지, 서버의 캐싱 기본값이 이 기능의 사용자 경험과 어떻게 충돌하는지를 묻는 질문은 여전히 개발자의 몫이다. 복잡성을 흡수하는 것—프레임워크의 의도를 읽고, 데이터의 성격을 분류하고, 적절한 경계에 로직을 배치하는 것—은 AI가 대체하는 영역이 아니라 AI를 제대로 쓰기 위해 개발자가 반드시 유지해야 할 근육이다.
App Router가 정교해질수록, 그리고 AI가 코드를 더 빠르게 생성할수록 역설적으로 이 설계 판단의 비중은 커진다. 프레임워크가 더 많은 것을 '알아서' 처리하려 할수록, 그 기본값이 우리 제품과 맞지 않는 지점을 감지하는 능력이 더 중요해진다. Flacron Gamezone의 캐시 버그는 기술적으로는 두 줄짜리 수정이었지만, 그 수정에 도달하는 길은 프레임워크의 렌더링 모델 전체를 읽는 일이었다. AI가 코드를 쓰는 시대일수록, 그 모델을 읽는 판단력은 개발자의 손에 남아야 한다.