[React]
- [React] vite React설치
- Project initial setting
- Hooks && Library
- [React] Javascript 파일을 Typescript Import해보자
- [React] react-router-dom 알아보고 사용하자
- [React] child 컴포넌트에서 함수호출하기
- [React] useCallback
- [React] framer-motion 알아보고 사용하자
- [React] Context API
- [React] react lazy 지연로딩 예제
- [React] react에서 캐싱처리를 위한 react queryd와 indexDB 비교분석
- [React] 스타일 라이브러리 styled-components stitches 비교
- [React] React Query < useQuery fetchQuery useMutation > 사용법
- [React] 프론트 에러추적 도구 Sentry 사용해보기
- [React] React 19 주요 변경점
- [React] DOM이란 무엇이고? 가상 DOM이란 무엇인가?
- [React] preact 알아보기
- [React] Sentry 심층 가이드
- [React] stagewise 사용법
- [React] useEffectEvent로 해결하는 useEffect의 고질적 문제
- [React] React 렌더링 원리 & Next.js 완전 정복
- [React] React Native + WebView로 앱 껍데기 만들어보기
- [React] Activity, Compiler, use cache — React 19.2 & Next.js 16
탭 전환 시 입력값이 날아가는 문제, 테마 바꿀 때마다 소켓이 재연결되는 문제, URL만 바뀌는데 서버 컴포넌트가 계속 다시 도는 문제. 2025~2026 React / Next.js가 이 고질적인 패턴들을 프레임워크 레벨에서 풀어주고 있다.

이미 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을 안 쓰는데도 리렌더 */}
</>
);
}

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는 유지하고, 최신 값만 읽기
테마를 소켓 메시지에 넣고 싶은데, theme을 useEffect 의존성에 넣으면 테마 변경마다 재연결된다. 빼면 연결은 유지되지만 항상 초기 테마를 보낸다.
1
2
3
4
5
6
7
useEffect(() => {
const socket = connect();
socket.on("ready", () => {
socket.send({ theme }); // theme을 deps에 넣느냐 vs 빼느냐
});
return () => socket.disconnect();
}, [theme]);

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: 캐시가 완전히 끝나는 시간
프리셋은 '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-app이 AGENTS.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.config의 agentRules: false.
캐시 모델이 자주 바뀌는 프레임워크일수록, 이 파일이 있는 프로젝트와 없는 프로젝트의 에이전트 코드 품질 차이가 크다.
6. Next.js MCP — 돌아가는 서버를 에이전트가 보게 하기
MCP의 정식 이름은 Model Context Protocol이다.
Next.js 16+는 개발 서버에 /_next/mcp 엔드포인트를 연다. 에이전트는 파일만 읽는 게 아니라 실제 라우트, 로그, 빌드/런타임 에러, Server Action을 조회할 수 있다.

공식 패키지는 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가 진짜 구독인지만 설계하면 된다.