■ 문제 상황: "수정했는데, 수정 전 값이 보임"
Next.js 14에서 등록/수정 페이지를 개발하던 중 이상한 현상을 만났다.
선물을 등록하거나 수정한 뒤,
다시 수정 페이지(/경로/update)에 들어가면 수정 전 값이 그대로 보이는 문제가 발생했다.
심지어 데이터는 분명히 DB에 잘 저장되어 있었고, API도 문제없었음.
■ 구성: 서버 컴포넌트(RSC)
해당 페이지는 **Next.js 13 이후 구조(App Router)**에서 작성된 서버 컴포넌트였고, 다음과 같았다
// /app/update/page.tsx
import GiftForm from '@/components/GiftForm';
import { getLatestGiftData } from '@/lib/server/getLatestGiftData';
export const dynamic = 'force-dynamic';
export const revalidate = 0;
export default async function UpdateGiftPage() {
const latest = await getLatestGiftData();
if (!latest) {
return <div>데이터가 없습니다.</div>;
}
return (
<GiftForm/>
);
}
그런데 이상하게도, 페이지를 이동해서 다시 들어가면 최신 데이터를 받아오지 않고 수정 전 데이터가 계속 보였다.
■ 원인: RSC는 클라이언트 이동 시 캐시된 HTML을 재사용함
Next.js의 **RSC(Server Component)**는 사용자가 페이지를 이동할 때, 서버로부터 매번 fresh한 데이터를 가져오지 않는다.
심지어 위 코드처럼 dynamic = 'force-dynamic', revalidate = 0을 설정했어도 서버 측 렌더링을 강제로 실행하지만, 브라우저는 여전히 캐시된 HTML을 사용할 수 있다.
즉, 사용자가 "수정 페이지 → 다른 페이지 → 수정 페이지"로 다시 들어올 때 클라이언트 내 캐시된 HTML을 재사용하는 것이다.
■ 해결 방법
1. 최선의 방법: router.refresh()를 통한 강제 새로고침
router.refresh()는 서버 컴포넌트 전체를 새로 불러오도록 강제한다.
나는 이 방법을 아래처럼 컴포넌트로 분리해서 적용했다:
// components/common/AutoRefreshWrapper.tsx
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function AutoRefreshWrapper() {
const router = useRouter();
useEffect(() => {
router.refresh(); // 페이지 마운트 직후 서버 컴포넌트 리패치
}, []);
return null;
}
그리고 page.tsx에서는 이렇게 사용했다
return (
<>
<AutoRefreshWrapper />
<GiftForm />
</>
);
이렇게 하니 페이지에 들어갈 때마다 자동으로 fresh 데이터가 로드되었다.
2. 대체 방법: 쿼리 스트링 추가로 캐시 무효화
실제로 "/경로/upload"가 아니라 "/경로/upload?query=a" 이런식으로 쿼리 스트리밍을 추가 하면 캐시 데이터를 사용하지 않고 새로운 데이터를 받아온다. 따라서 해당 경로를 router 하는 코드에서는 아래와 같이 쿼리 스트링을 추가해준다.
router.push(`/admin/gift/update?ts=${Date.now()}`);
쿼리 파라미터는 URL이 달라지므로 Next.js는 다른 경로로 인식하고 캐시를 무시한다.
단점은 URL이 길어지고, ?ts=xxx와 같은 파라미터를 관리해야 하는 점이다.
■ Next.js의 캐싱 가이드
Next.js는 성능 최적화를 위해 서버, 클라이언트, 경로 단위의 캐싱 전략을 제공한다.
이번 문제도 이런 기본 전략을 이해하지 않으면 예기치 못한 상황이 생기기 쉽다.
공식 문서: Next.js Caching Guide
'React > 프론트엔트 개념 정리' 카테고리의 다른 글
| Next.js에서 RSC 방식이란?(feat.서버, 클라이언트 컴포넌트) (0) | 2025.10.27 |
|---|---|
| JWT – Stateless 인증의 핵심 (0) | 2025.10.22 |
| 클로저(Closure)란? – React에서 stale 값이 생기는 이유 (0) | 2025.10.17 |
| Vue가 다시 뜨는 이유 (0) | 2025.10.14 |