[React] Activity, Compiler, use cache — React 19.2 & Next.js 16 실전 정리

탭 상태 유지부터 서버 캐시, AI 에이전트 지침까지

2026년 08월 27일 · lim.Chuck

[React]

  1. [React] vite React설치
  2. Project initial setting
  3. Hooks && Library
    1. [React] Javascript 파일을 Typescript Import해보자
    2. [React] react-router-dom 알아보고 사용하자
    3. [React] child 컴포넌트에서 함수호출하기
    4. [React] useCallback
    5. [React] framer-motion 알아보고 사용하자
    6. [React] Context API
    7. [React] react lazy 지연로딩 예제
    8. [React] react에서 캐싱처리를 위한 react queryd와 indexDB 비교분석
    9. [React] 스타일 라이브러리 styled-components stitches 비교
    10. [React] React Query < useQuery fetchQuery useMutation > 사용법
  4. [React] 프론트 에러추적 도구 Sentry 사용해보기
  5. [React] React 19 주요 변경점
  6. [React] DOM이란 무엇이고? 가상 DOM이란 무엇인가?
  7. [React] preact 알아보기
  8. [React] Sentry 심층 가이드
  9. [React] stagewise 사용법
  10. [React] useEffectEvent로 해결하는 useEffect의 고질적 문제
  11. [React] React 렌더링 원리 & Next.js 완전 정복
  12. [React] React Native + WebView로 앱 껍데기 만들어보기
  13. [React] Activity, Compiler, use cache — React 19.2 & Next.js 16

탭 전환 시 입력값이 날아가는 문제, 테마 바꿀 때마다 소켓이 재연결되는 문제, URL만 바뀌는데 서버 컴포넌트가 계속 다시 도는 문제. 2025~2026 React / Next.js가 이 고질적인 패턴들을 프레임워크 레벨에서 풀어주고 있다.

React 19.2와 Next.js 16의 다섯 가지 핵심 기능


이미 React 19 주요 변경점에서 use, useActionState, useOptimistic을 다뤘고, useEffectEvent도 따로 정리했다. 이 글은 그 이후 실제로 쓰기 시작한 React 19.2 + Next.js 16 쪽을 중심으로 정리한다.

참고: React 19.2 릴리즈, React Compiler 1.0, Next.js 16.2 AI


1. Activity — 언마운트되지만 상태는 남긴다

댓글/답글 탭처럼 조건부 렌더링을 하면, 탭을 바꿀 때 컴포넌트가 사라지고 입력값이 초기화된다.

1
2
3
{
  activeTab === "reply" && <ReplyEditor />;
}

예전에는 display: none으로 DOM을 남겨 상태를 유지했다. 문제는 숨겨진 컴포넌트의 useEffect(소켓, 타이머, 구독)가 계속 살아 있다는 점이다.

탭을 숨기는 세 가지 방식의 상태·사이드이펙트 비교

상태 유지와 사이드 이펙트 정리를 동시에 만족하는 건 세 번째 방식뿐이다. React 19.2의 <Activity>는 이 중간 지점을 공식 API로 제공한다.

1
2
3
4
5
import { Activity } from "react";

<Activity mode={activeTab === "reply" ? "visible" : "hidden"}>
  <ReplyEditor />
</Activity>;
mode 화면 Effect 상태
visible 보여주고 업데이트 정상 처리 mount 유지
hidden 숨김, 업데이트는 유휴 시간에 unmount (사이드이펙트 정리) 유지

DOM/상태는 남기고, 사이드 이펙트는 끈다. 탭 UI, 뒤로가기 시 입력값 복원, 다음에 갈 화면을 백그라운드에서 미리 준비하는 용도에 잘 맞다.

1
2
3
4
5
6
7
8
9
10
11
12
function Tabs({ active }: { active: "write" | "preview" }) {
  return (
    <>
      <Activity mode={active === "write" ? "visible" : "hidden"}>
        <Editor />
      </Activity>
      <Activity mode={active === "preview" ? "visible" : "hidden"}>
        <Preview />
      </Activity>
    </>
  );
}

앞으로 mode가 더 늘어날 예정이다. 지금은 visible / hidden만 안정적으로 쓰면 된다.


2. React Compiler 1.0 — memo 지옥에서 벗어나기

부모에서 테마만 바꿔도, 테마를 안 쓰는 자식까지 리렌더링되는 건 익숙한 그림이다.

1
2
3
4
5
6
7
8
9
function App() {
  const [theme, setTheme] = useState<"light" | "dark">("light");
  return (
    <>
      <ThemeToggle theme={theme} onChange={setTheme} />
      <StaticSidebar /> {/* theme을 안 쓰는데도 리렌더 */}
    </>
  );
}

Compiler 적용 전후 리렌더링 범위 비교

React.memo + useCallback + useMemo로 막을 수 있지만, 코드가 최적화 때문에 읽기 어려워진다.

React Compiler 1.0은 2025년 10월 안정화됐다. 빌드 타임에 안전한 지점에 메모이제이션을 삽입한다. React 19가 기본 타깃이고, 17/18은 react-compiler-runtime으로 우회 가능하다.

Next.js에서는 설정 한 줄이면 된다.

1
2
3
4
5
6
// next.config.ts
const nextConfig = {
  reactCompiler: true,
};

export default nextConfig;

적용 후 DevTools에서 테마만 바꿀 때 StaticSidebar가 안 도는 걸 확인하는 게 제일 빠르다.

주의할 점:

  • React 규칙을 어기는 코드(렌더 중 부작용 등)는 컴파일러가 예상과 다르게 메모할 수 있다.
  • useEffect 의존성이 참조 동일성에 기대고 있으면, 메모 결과가 바뀌며 effect가 더/덜 돌 수 있다. 이 케이스가 바로 다음 섹션(useEffectEvent)과 맞물린다.
  • 버전은 --save-exact로 고정하는 편이 안전하다.

수동 memo를 전부 지울 필요는 없다. Compiler가 커버하는 일반 경로 + 사람이 손대는 핫패스 정도로 역할을 나누면 된다.


3. useEffectEvent — Effect는 유지하고, 최신 값만 읽기

테마를 소켓 메시지에 넣고 싶은데, themeuseEffect 의존성에 넣으면 테마 변경마다 재연결된다. 빼면 연결은 유지되지만 항상 초기 테마를 보낸다.

1
2
3
4
5
6
7
useEffect(() => {
  const socket = connect();
  socket.on("ready", () => {
    socket.send({ theme }); // theme을 deps에 넣느냐 vs 빼느냐
  });
  return () => socket.disconnect();
}, [theme]);

의존성 배열에 theme을 넣을 때와 뺄 때, 그리고 useEffectEvent 비교

React 19.2의 useEffectEvent는 “Effect에서 발생하는 이벤트”를 분리한다. 항상 최신 props/state를 보고, Effect 의존성에는 넣지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useEffect, useEffectEvent } from "react";

function ChatRoom({ roomId, theme }: { roomId: string; theme: string }) {
  const onConnected = useEffectEvent(() => {
    showNotification("Connected!", theme);
  });

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on("connected", () => onConnected());
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // theme은 여기 없음
}

공식 가이드의 핵심은 lint를 잠재우려고 전부 감싸지 말라는 것이다. “외부 시스템 이벤트 콜백”에만 쓰는 게 맞다.

더 긴 예제는 useEffectEvent 글을 보면 된다. ESLint는 eslint-plugin-react-hooks 최신(v6+)이 Effect Event를 deps에 넣으라고 잔소리하지 않는다.


4. "use cache" — 서버 컴포넌트를 캐시 단위로 자르기

URL 파라미터만 바뀌는 페이지에서, 사실 안 바뀌는 서버 컴포넌트까지 매번 다시 실행되는 경우가 있다. Next.js 16의 Cache Components는 이 경계를 "use cache"로 명시한다.

1
2
3
4
5
6
// next.config.ts
const nextConfig = {
  cacheComponents: true,
};

export default nextConfig;
1
2
3
4
5
6
7
8
9
10
import { cacheLife, cacheTag } from "next/cache";

async function Sidebar() {
  "use cache";
  cacheLife("hours");
  cacheTag("sidebar");

  const data = await fetchSidebar();
  return <nav>{/* ... */}</nav>;
}

cacheLife의 stale, revalidate, expire 구간 타임라인

cacheLife 프로필은 대략 이렇게 이해하면 된다.

  • stale: 클라이언트가 서버를 안 보고 써도 되는 시간
  • revalidate: 서버에서 백그라운드 갱신을 시작하는 시간
  • expire: 캐시가 완전히 끝나는 시간

프리셋은 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'max' 가 있고, 커스텀 오브젝트로 세 값을 직접 줄 수도 있다. 무효화는 cacheTag + revalidateTag.

개발 모드에서는 체감이 안 날 수 있다. 프로덕션 빌드 후 로그가 사라지고 TTFB가 빨라지는지로 확인하는 게 맞다.

캐시가 만료되면 서버 작업이 다시 돌고, 그때 로그가 다시 찍히는 게 정상이다.

RSC 쪽에서 캐시 수명이 끝나면 fetch를 끊고 싶을 때는 React 19.2의 cacheSignal()fetch signal에 넘기면 된다.


5. AGENTS.md / CLAUDE.md — 에이전트가 설치 버전 문서를 보게 하기

Next.js 16.2부터 create-next-appAGENTS.md를 만들고, 패키지 안에 버전과 맞는 문서가 node_modules/next/dist/docs/에 들어간다. 16.3+는 next dev가 에이전트 환경을 감지하면 파일을 자동 upsert 한다.

핵심은 한 줄이다. 학습 데이터가 아니라, 지금 설치된 Next 문서를 읽어라.

에이전트가 학습 데이터 대신 설치된 버전의 문서를 읽게 하는 구조

1
2
3
4
5
6
7
8
<!-- BEGIN:nextjs-agent-rules -->

# Next.js: ALWAYS read docs before coding

Before any Next.js work, find and read the relevant doc in `node_modules/next/dist/docs/`.
Your training data is outdated — the docs are the source of truth.

<!-- END:nextjs-agent-rules -->

CLAUDE.md는 보통 @AGENTS.md만 넣는다. 빈 폴더에 프로젝트를 만들 때 이미 있는 AGENTS.md와 충돌하지 않게 관리 블록(BEGIN:nextjs-agent-rules) 밖은 보존된다. 끄려면 next.configagentRules: false.

캐시 모델이 자주 바뀌는 프레임워크일수록, 이 파일이 있는 프로젝트와 없는 프로젝트의 에이전트 코드 품질 차이가 크다.


6. Next.js MCP — 돌아가는 서버를 에이전트가 보게 하기

MCP의 정식 이름은 Model Context Protocol이다.

Next.js 16+는 개발 서버에 /_next/mcp 엔드포인트를 연다. 에이전트는 파일만 읽는 게 아니라 실제 라우트, 로그, 빌드/런타임 에러, Server Action을 조회할 수 있다.

코딩 에이전트가 next-devtools-mcp를 거쳐 dev 서버의 런타임 정보를 읽는 구조

공식 패키지는 next-devtools-mcp다.

1
2
3
4
5
6
7
8
{
  "mcpServers": {
    "next-devtools": {
      "command": "npx",
      "args": ["-y", "next-devtools-mcp@latest"]
    }
  }
}

Cursor / Claude Code / Codex 모두 .mcp.json(또는 클라이언트별 설정)에 넣고, dev 서버를 띄운 뒤 에이전트에게 “MCP로 이 라우트 렌더링 방식 분석해줘”라고 명시하면 정확도가 올라간다.

가능한 일:

  • 현재 에러·로그 확인
  • 페이지가 캐시되는지, 어떤 컴포넌트 트리인지 확인
  • 문서 게이트웨이로 node_modules/next/dist/docs/ 읽기

프로덕션에 이 엔드포인트를 열어두면 안 된다. 로컬/개발 전용이다.


7. 같이 보면 좋은 2026 트렌드

앞에서 다룬 기능과 한 사이클로 이어지는 것들만 골랐다. 이미 있는 글과 겹치는 건 링크로만 둔다.

Actions + 낙관적 UI

폼/뮤테이션은 Server Action + useActionState + useOptimistic + useFormStatus 조합이 사실상 표준이다. 로딩 플래그와 롤백을 직접 짜던 자리를 React가 가져갔다. 자세한 코드는 React 19 글.

View Transitions

React 19.2 시점에 useId 접두사가 _r_로 바뀐 이유 중 하나가 View Transition 이름을 유효하게 만들기 위해서다. 라우트 전환을 JS 애니메이션 라이브러리 없이 GPU 쪽 transition으로 넘기는 방향이다. Next.js에서는 실험 플래그 + <ViewTransition> 조합으로 붙는 중이다. Activity로 상태를 남긴 채 화면만 바꾸고, View Transition으로 그 전환을 부드럽게 만드는 그림이 잘 맞는다.

Performance Tracks

Chrome Performance 패널에 React Scheduler / Components 트랙이 생긴다. Compiler 적용 전후, Activity hidden 트리의 업데이트 지연을 숫자로 보고 싶을 때 쓴다.

Partial Pre-rendering

정적 껍데기를 먼저 뿌리고(prerender), 나중에 resume으로 동적 구간을 채운다. Next의 PPR / Cache Components와 같은 축이다. "use cache"는 그 축 위에서 어떤 서버 작업이 캐시 키인가를 코드로 드러내는 장치다.

클라이언트 데이터는 여전히 TanStack Query

RSC가 초기 데이터를 가져와도, 클라이언트 캐시·재시도·무한스크롤은 Query가 여전히 강하다. 서버 캐시(use cache)와 클라이언트 캐시(Query)를 한 방에 합치지 않는 게 2026년 실무 감각에 가깝다.


한 줄로 정리

문제 쓰지 말 것 쓸 것
탭 전환 시 입력값 초기화 조건부 언마운트만 <Activity>
숨긴 UI의 소켓/타이머 생존 display: none Activity hidden
테마 변경 시 무관한 리렌더 memo 범벅 React Compiler
Effect 재실행 vs stale 값 deps 빼기 / ref 수동 useEffectEvent
안 바뀌는 서버 컴포넌트 재실행 페이지 전체 dynamic "use cache" + cacheLife
에이전트가 구버전 API를 씀 학습 데이터만 신뢰 AGENTS.md + bundled docs
에이전트가 런타임을 모름 파일만 읽고 추측 next-devtools-mcp

2026년 React는 “어떻게 메모하지 / 어떻게 캐시하지 / 어떻게 Effect deps를 짜지”를 사람이 덜 고민하게 옮기는 중이다. 대신 우리는 어디까지 Activity로 숨길지, 무엇을 캐시 태그로 자를지, 어떤 Effect가 진짜 구독인지만 설계하면 된다.