출처: https://youtu.be/xiMZZoNhLOI 길이: 20:56 · 채널: 프롱트 다운로드 자막: 한국어 자동자막 영상 유형: A형 — VS Code 데모·공식 문서 화면이 이어지는 프레임워크 신기능 튜토리얼 캡처 참고: 아래 이미지는 영상에서 직접 추출한 프레임이며, 각 시점의 자막과 화면을 대조해 선별했다.

한눈에 보는 요약

  • React 19.2의 <Activity> 컴포넌트로 컴포넌트를 언마운트하면서도 상태를 유지하는 방법을 데모로 보여준다. 숨긴 동안 사이드 이펙트까지 차단된다.
  • React Compiler를 켜면 memo·useCallback·useMemo를 손으로 붙이지 않아도 자동 메모이제이션이 적용된다. Next.js에서는 Rust로 새로 작성된 구현이 실험 플래그 하나로 반영된다.
  • useEffectEvent(React 19.2)는 이펙트 안에서 최신 상태값을 참조해야 하는 골치 아픈 디펜던시 문제를 깔끔하게 해결한다.
  • 서버 컴포넌트 캐싱 — 'use cache' 지시어와 cacheLife(시간), cacheTag(무효화 키)로 서버 데이터를 캐시한다. 프로덕션 빌드에서만 동작하고, 캐시 적중 시 로그가 아예 찍히지 않을 정도로 반응이 빨라진다.
  • Next.js 신버전은 설치 시 AGENTS.md/CLAUDE.md에 에이전트용 가이드(node_modules 내부 문서 참조 지침)를 자동 생성해 AI 코딩 에이전트가 프레임워크 규칙대로 개발하도록 돕는다.
  • Vercel 공식 Next DevTools MCP를 코덱스에 연결하면 에이전트가 떠 있는 Next.js 서버의 렌더링 방식 등 내부 상태를 API처럼 조회할 수 있다.

장면별 상세 설명

[0:25] 1. <Activity> 없이는 생기는 문제 — 탭을 돌아오면 입력값이 사라진다

장면 1 — Activity 데모 화면

오른쪽 화면에 어떤 값을 입력하고 다른 탭으로 이동했다가 다시 돌아오면 값이 사라진다. 컴포넌트 자체가 언마운트되었기 때문이다. 값을 유지하려면 전역 상태를 두는 등 번잡한 일을 해야 한다. 지금 앱은 활성 탭이 “리플라이”일 때만 리플라이 에디터 컴포넌트를 렌더링하도록 되어 있어서 그렇다.

CSS display 속성으로 흉내 낼 수도 있다 — 활성 탭이 리플라이면 block, 아니면 none. 이러면 컴포넌트가 계속 유지되니 값은 남는다. 하지만 이 방식에는 함정이 있다. 화면을 보고 있지 않아도 리플라이 에디터가 useEffect로 실행했던 코드(예: 10초 뒤 서버로 답변을 전송)가 계속 실행될 수 있다. 보이지 않는 컴포넌트의 사이드 이펙트가 계속 도는 것이다.

[1:42] 2. <Activity mode> — 언마운트처럼 동작, 상태만 유지

장면 2 — Activity 컴포넌트 코드

React의 <Activity> 컴포넌트가 해결책이다. 컴포넌트는 언마운트됐지만 상태는 유지하는 패턴을 제공하며, CSS 클래스가 아니라 mode prop으로 제어한다. modevisiblehidden 두 값 — display: block/none과 달리 히든 상태에서는 컴포넌트가 실제로 정리(cleanup)된다.

<Activity mode={activeTab === 'reply' ? 'visible' : 'hidden'}>
  <ReplyEditor />
</Activity>

데모에서 답변을 입력해 두고 다른 탭을 갔다가 돌아오면 값이 유지된다. 동시에 콘솔 로그를 보면 왔다 갔다 할 때마다 컴포넌트가 렌더링됐다가 언마운트 시 실행하도록 넣어 둔 정리 코드가 계속 도는 것도 확인할 수 있다. 즉, 사이드 이펙트는 막으면서 값을 유지하는 좋은 방법이다. React 19.2부터 적용된 기능이다.

[2:48] 3. 부모 리렌더링에 덩달아 도는 정적 컴포넌트

장면 3 — 테마 변경 시 정적 컴포넌트 리렌더링 콘솔

새 데모 화면은 테마 버튼을 누르면 배경색이 바뀌는데, 콘솔을 띄워 놓으면 테마를 바꿀 때마다 정적(static) 컴포넌트까지 계속 렌더링되는 것이 보인다. 원인은 부모의 상태가 변경될 때 자식이 덩달아 리렌더링되는 전형적인 문제다.

[4:12] 4. React Compiler 설정 — memo/useCallback/useMemo를 손으로 쓰지 않는다

장면 4 — next.config.ts 편집

전통적 해법은 React.memo로 정적 컴포넌트를 감싸는 것. 그러면 테마가 바뀌어도 자식 함수가 호출조차 되지 않는다. 하지만 useCallback·useMemo 같은 최적화 API를 어디까지 적용해야 하는지 끝없이 고민하게 만들고 코드가 번잡해진다.

React Compiler를 쓰면 이런 대상을 컴파일러가 찾아서 알아서 적용해 준다. Next.js는 JS 버전 컴파일러 대신 Rust로 새로 만든 구현을 쓰며, Turbopack에 함께 반영되어 있어서 설정은 실험 플래그 하나면 충분하다(Vite나 Babel 환경은 설정이 조금 다르다).

// next.config.ts
const config: NextConfig = {
  experimental: {
    reactCompiler: true,
  },
};

[5:08] 5. 개발자 도구의 Memo 배지 — 자동 적용 확인

장면 5 — Memo 배지가 찍힌 컴포넌트

설정 후 데모에서는 memo 코드를 모두 지웠음에도 테마 버튼을 눌러도 정적 컴포넌트가 다시 렌더링되지 않는다. 개발자 도구의 컴포넌트 탭에서 해당 컴포넌트를 열면 Memo 배지가 붙은 것을 볼 수 있다. React Compiler가 자동으로 메모이제이션을 적용한 결과다.

[5:52] 6. useEffect 디펜던시 문제 — 테마를 바꿀 때마다 소켓이 재연결된다

장면 6 — 테마 변경마다 반복되는 소켓 연결/정리 로그

메시지를 주고받는 소켓 통신 코드가 있는 데모. 테마 바꾸기를 누를 때마다 콘솔에 “소켓 연결 정리” 로그가 찍힌다 — 소켓 통신이 매번 다시 일어나고 있는 것이다. 원인은 useEffect의 디펜던시 배열에 theme이 들어 있기 때문. 소켓으로 메시지를 보낼 때 테마를 함께 보내는 (엉뚱하지만) 코드가 있어서 테마가 디펜던시에 들어갔다.

테마를 디펜던시에서 지우면 재연결은 멈추지만, 이번엔 또 다른 문제가 생긴다.

[6:42] 7. stale closure 버그 — 이펙트가 옛날 테마값만 기억한다

장면 7 — 라이트 테마가 고정되어 나가는 메시지

테마를 디펜던시에서 빼고 메시지를 보내면 현재 라이트 테마가 잘 전송된다. 그런데 테마를 다크로 바꾼 뒤 다시 보내면? 이펙트가 참조하는 theme은 최신값이 아니라 생성 당시 스코프의 값이라 여전히 라이트가 나간다. 이펙트 안에서 쓰는 값은 선택이 아니라 반드시 디펜던시에 넣어야 하는 이유가 바로 이것 — 리액트의 특징이다.

[7:58] 8. useEffectEvent로 해결 — 이펙트 안에서 최신값 읽기

장면 8 — useEffectEvent 적용 코드

React 19.2에 나온 useEffectEvent가 이 문제의 해법이다. 메시지 전송 로직을 이벤트 함수로 분리하고, 이펙트 안에서는 그 함수만 호출한다.

const onMessage = useEffectEvent((payload: { message: string }) => {
  sendMessage({ ...payload, theme }); // 항상 최신 theme을 참조
});
 
useEffect(() => {
  socket.onmessage = (e) => onMessage(e.data);
}, []); // theme이 디펜던시에 없어도 된다

원래는 이펙트가 생성될 당시의 스코프를 따라가 예전 값을 기억하지만, useEffectEvent 안에서는 최신 상태값을 참조할 수 있다. 데모에서도 라이트 테마로 보내면 “light”, 다크로 바꾼 뒤 보내면 “dark”가 바로 나온다. 꽤 유용한 기능이니 같은 문제에 부딪히면 활용해 보면 좋다.

[9:12] 9. 캐시 데모 — 같은 결과를 계속 재계산하는 서버 컴포넌트

장면 9 — 클릭마다 반복 찍히는 재계산 로그

민서로 보기/준호로 보기를 클릭하면 URL 파라미터가 바뀌고 내용이 렌더링된다(일부러 딜레이를 줬다). 클릭할 때마다 콘솔에 계산 로그가 찍히는데, 이 컴포넌트는 정적인 컴포넌트다. 중간에 Promise로 지연을 시키고 waiting: 10을 항상 똑같이 반환한다. 오른쪽 시각 표시만 바뀔 뿐 재사용 가능한 데이터인데도 매번 실행되고 있다 — 캐시의 대상이다.

[10:22] 10. 'use cache' + cacheLife/cacheTag

장면 10 — use cache 지시어 추가

Next.js의 Cache Components 기능으로 캐시한다. 먼저 next.config.ts에서 활성화하고, 서버 컴포넌트 함수에 'use cache' 지시어를 붙인다.

캐시는 결국 “얼마나, 언제까지” 시간을 정하는 게 중요하다. TanStack Query를 써본 사람이라면 익숙한 고민이다. 여기서 쓰는 두 도우미가 있다:

  • cacheLife('minutes') — 캐시 수명(프리셋). 자세한 정보를 직접 넣을 수도 있다.
  • cacheTag('team-summary') — 캐시를 무효화할 때 필요한 키값.
import { cacheLife, cacheTag } from 'next/cache';
 
async function getTeamSummary() {
  'use cache';
  cacheLife('minutes');
  cacheTag('team-summary');
 
  console.log('[cache-demo] 팀 현황 계산');
  await new Promise((resolve) => setTimeout(resolve, 500));
  return { waiting: 12, updatedAt: new Date().toLocaleTimeString('ko-KR') };
}

[11:52] 11. 프로덕션 빌드에서 확인 — 캐시 적중 시 로그가 안 찍힌다

장면 11 — pnpm build/start로 프로덕션 실행

주의할 점은 캐시가 프로덕션 모드에서만 동작한다는 것. 그래서 pnpm buildpnpm start로 실행해서 localhost:3000에서 확인한다. 이건 서버 컴포넌트라 로그가 서버 터미널에 찍히고, 브라우저 콘솔에 뜨는 로그는 서버가 디버깅 편의를 위해 힌트를 주는 것이다.

프로덕션에서 민서↔준호를 누르면 반응도 빠르고 캐시 덕분에 재계산 로그가 더 이상 찍히지 않으면서도 데이터는 정상적으로 바뀐다.

[12:55] 12. cacheLife 프리셋 — staleTime / revalidate / expire

장면 12 — cacheLife 프리셋 문서 표

Next.js 문서의 캐시 프로필 표를 보면 각 프리셋은 세 가지 시간으로 구성된다.

항목의미
stale (staleTime)클라이언트 캐시를 신선하다고 보는 시간 — 이 안에서는 서버로 요청조차 가지 않는다
revalidate서버가 재검증하는 주기 — 캐시를 먼저 쓰고 백그라운드에서 업데이트(SWR 방식)
expire요청이 없어 일정 시간이 지나면 캐시를 버리고 즉시 새 값을 응답하는 시점

정리하면, 서버 컴포넌트를 캐시하고 싶을 때 'use cache'를 써서 간편하게 캐시하고 빠른 응답을 얻을 수 있다.

[14:02] 13. 캐시 만료 후에는? — 재계산이 다시 도는 모습

장면 13 — cacheLife 만료 후 재계산 로그

시간이 지나고 나서 다시 클릭하면 “팀 현황 계산” 로그가 찍히면서 반응도 약간 늦어진다. 캐시 수명이 만료됐기 때문이다. 빠른 반응이 중요한 화면, 특히 서버에서 오래 걸리는 작업은 캐시를 잘 쓰면 효과적으로 처리할 수 있다.

[15:20] 14. 코덱스로 todo 앱 만들기 — Next.js가 에이전트 지침 파일을 생성한다

장면 14 — Codex에서 todoweb 앱 생성 중

여기서부터는 OpenAI 코덱스(Codex) 데모. 빈 디렉토리(기본 AGENTS.md만 있는)에 create-next-app으로 스타일 없는 간단한 todo 웹앱을 만들어 본다. 프롬프트에서 눈여겨볼 지시는 하나 — 설치 중 AGENTS.md 파일이 겹치면 overwrite하지 말고 추가해 달라는 것.

최근 Next.js에는 “Next를 잘 쓰기 위한 에이전트 정보”를 만들어 주는 기능이 있다. 설치가 끝나자 CLAUDE.mdAGENTS.md가 생성됐고, AGENTS.md를 열면 위에는 사용자가 만든 지침, 아래에는 Next.js가 추가한 에이전트 룰이 붙어 있다. 내용은 “정확한 최신 가이드는 node_modules/next/dist/docs를 참조하라”는 식으로, 아키텍처·App Router 작성법 등 굉장히 자세한 문서들을 포함해서 개발하게 된다. Next.js 16.2부터 적용된 기능으로, 앞으로는 이런 지침 기반으로 꼼꼼하게 개발될 확률이 높아졌다고 이해하면 된다.

[17:12] 15. Next.js devtool MCP 소개

장면 15 — Next.js devtool MCP 타이틀 카드

마지막 주제는 Vercel이 공식 지원하는 Next DevTools MCP(github.com/vercel/next-devtools-mcp). 에이전트에게 “이 프로젝트 렌더링 방식이 뭐야?”라고 물으면, 에이전트는 코드만 보고 어디를 봐야 할지 헤멜 수 있다. 반면 MCP는 현재 떠 있는 Next.js 서버를 기반으로 그 안에서 동작 중인 방식들을 API처럼 접근해서 보여준다.

[18:32] 16. 코덱스에 MCP 설치하기

장면 16 — README의 에이전트별 설정 안내와 codex 명령 실행

README에는 Claude, Codex, Cursor, Gemini, Google Antigravity, VS Code/Copilot, Warp 등 에이전트별 설정 방법이 각각 정리되어 있다. 코덱스는 명령어 한 줄로 추가한다:

codex mcp add next-devtools -- npx next-devtools-mcp@latest

코덱스 앱을 종료했다가 다시 열면 MCP 항목에 “next-devtools”가 보인다.

[19:52] 17. MCP 동작 확인과 마무리

장면 17 — todo 앱 데모와 MCP 사용 확인 대화

todo 앱 서버를 띄운 뒤 에이전트에게 “todo의 기본 렌더링 방식을 SSR, 내부 동작으로 간단히 유추해 달라”고 요청한다. 그리고 “MCP 사용했니?”라고 물으면 처음엔 “아니요, 쓰지 않았다”고 답한다. 이번에는 “todo 추가될 때 Next DevTools MCP로 Next.js에서 렌더링되는 방식을 클라이언트-서버 관계로 확인해 줘”라고 명시적으로 도구를 지정하자 이번엔 MCP를 사용했다고 확인해 준다.

로그 분석이나 오류 원인 추적처럼 정확한 결과가 필요할 때는 MCP를 명시적으로 호출해서 확인하면 된다. Vercel 공식 MCP로 Next.js를 분석하거나 원인을 찾을 때 참고하면 좋겠다는 것으로 영상을 마무리한다.

부록 — 실전 체크리스트

  • 탭 전환 시 입력값이 사라지는 화면이 있으면 <Activity mode="visible" | "hidden"> 검토 — 상태 유지 + 히든 중 사이드 이펙트 차단 (React 19.2+)
  • memo·useCallback·useMemo 남발로 번잡해진 컴포넌트가 있으면 React Compiler 도입 검토 — Next.js는 experimental: { reactCompiler: true } 한 줄
  • useEffect 안에서 상태를 읽어야 하는데 디펜던시 넣기 애매하면 useEffectEvent로 전송 로직을 분리
  • 비싼 서버 컴포넌트 계산에는 'use cache' + cacheLife(프리셋) + cacheTag('키') — 단, 프로덕션 빌드에서만 동작하므로 build && start로 검증
  • 캐시 무효화가 필요하면 cacheTag 키를 설계부터 붙여 둔다
  • create-next-app 실행 시 AGENTS.md 겹침에 대비해 overwrite 금지 지시 — Next.js가 에이전트 가이드를 자동 생성한다 (16.2+)
  • AI 에이전트로 Next.js를 분석할 때는 vercel/next-devtools-mcp 연결 — 필요할 때 “MCP 써서 확인해 줘”라고 명시적으로 호출

원문 인용 모음

  • [2:20] “액티비티를 잘 쓰면 컴퍼넌트는 불필요하게 존재하지 않고, 사이드 이펙트로 뭔가 실행되는 걸 막으면서도 값을 유지할 수 있는 좋은 방법이니까 19.2부터 아마 적용이 됐을 거예요.”
  • [8:01] “쉽게 얘기하면 뭐냐? useEffectEvent는 최신의 값을 참조를 할 수가 있습니다. 원래는 생성할 당시의 스코프를 따라서 리액트가 관리를 하는데… 최신 상태값을 참조해서 useEffect 안에서도 그 값을 사용할 수 있는 방법이 이렇게 나왔죠.”
  • [10:26] “캐시라는 거는 결국에는 시간 정하는 게 되게 중요하잖아요. … 어디서 캐시를 해야 얼만큼 캐시를 해야 되고 언제 갱신해야 되는지.”
  • [11:18] “이게 이제 프로덕션 모드에서만이 캐시가 동작한다고 해요.”
  • [13:37] “‘use cache’를 써 가지고 간편하게 서버에 있는 컴포넌트를 캐시해서 빠른 응답을 얻을 수가 있다.”
  • [17:01] “앞으로 개발할 때는 이런 지침을 기반으로 꼼꼼하게 개발이 될 확률이 높아졌다라고 이해하시면 좋을 것 같습니다.”