React 19.3이 9월 9일 npm에 올라왔다. 한국 시각으로는 10일 새벽이다. 작년에 실험 API로 공개된 <ViewTransition>과 Fragment ref가 정식이 됐고, react-dom에 SSR을 건너뛰는 browser()가 생겼다. 19.2 쓰는 프로젝트를 올려도 되는지, ViewTransition 쓰려고 canary 깔아둔 걸 걷어내도 되는지가 먼저 궁금할 것이다.

먼저 밝혀둘 것. 실제 앱을 올린 게 아니라 빈 폴더에 19.2와 19.3을 각각 깔아 export를 비교하고, Node에서 renderToString만 돌려봤다. 브라우저 애니메이션 캡처는 없다.
특히 유의해야 할 점은 공식 문서 어디에도 "breaking change 없음"이라는 문장이 없다는 것이다. 블로그·CHANGELOG·GitHub 릴리스에 파괴적 변경 항목이 안 적혀 있을 뿐이다. 직접 비교해 보니 export가 3개 늘어난 것 말고는 같았다. 마이너 버전이라는 말은 그 정도 뜻으로 받으면 된다.
19.2와 19.3의 export 차이
같은 스크립트를 두 폴더에서 돌렸다. Node 22, npm 10 기준이고 경로는 ~/tmp/로 바꿨다.
# 19.2와 19.3을 각각 빈 폴더에 설치하고 export 목록을 비교한다
mkdir react193 && cd react193 && npm init -y && npm i react@19.3 react-dom@19.3
node -e "console.log(Object.keys(require('react')).sort().join('\n'))" > r193.txt
# (react192 폴더에서 같은 방식으로 r192.txt 생성 후)
diff r192.txt r193.txt
diff 결과다.
8a9
> ViewTransition
11a13
> addTransitionType
react-dom 쪽은 browser 하나다. 19.2.8에서 셋 다 undefined였고 19.3.0에서 symbol, function, function으로 잡힌다. 설치는 "added 3 packages", peer 경고 없이 1초에 끝났다. 다만 react-dom@19.3.0의 peer가 react ^19.3.0이라 둘을 같이 올려야 한다. 공식 명령은 npm install react@latest react-dom@latest다.
ViewTransition은 Transition 안에서만
레퍼런스 문장 그대로다. setState는 즉시 반영되고 ViewTransition을 켜지 않는다. startTransition, <Suspense>, useDeferredValue로 감싼 업데이트만 애니메이션을 탄다. 애니메이션 종류는 enter, exit, update, share 넷이고, 원인을 구분하고 싶으면 addTransitionType을 같은 Transition 안에서 부른다.
// react에서 바로 가져온다. 타입을 붙이면 CSS에서 :active-view-transition-type(...)으로 나눌 수 있다
import { ViewTransition, addTransitionType, startTransition } from 'react';
function go(path) {
startTransition(() => {
addTransitionType('navigation-forward'); // 이 Transition의 원인을 표시
navigate(path);
});
}
// DOM 노드 바로 위에 두어야 enter/exit이 잡힌다. div로 한 번 더 감싸면 안 된다
<ViewTransition><Page /></ViewTransition>
주의점이 몇 개 있다. 같은 name은 앱 전체에서 한 번에 하나만 마운트돼야 하고, 중간에 flushSync가 끼면 Transition을 건너뛴다. prefers-reduced-motion은 React가 대신 꺼주지 않으니 CSS 미디어쿼리로 직접 처리해야 한다. 지금은 DOM에서만 동작하고 React Native는 작업 중이라고 적혀 있다.
브라우저는 MDN 호환 데이터 기준으로 startViewTransition이 Chrome 111, Safari 18, Firefox 144부터다. addTransitionType이 기대는 transition type은 Chrome 125, Safari 18.2, Firefox 147부터라 한 단계 더 높다. Next.js 가이드는 지원 안 하는 브라우저에서는 앱은 정상 동작하고 애니메이션만 안 나온다고 썼다.
browser()가 SSR에서 뱉는 것
useEffect로 mounted 플래그를 세우던 "클라이언트 전용 컴포넌트" 패턴을 대체하는 API다. 서버에서는 Suspense를 걸고 클라이언트에서는 걸지 않는다. Node에서 실제 출력을 봤다.
// 서버 렌더에서 use(browser())가 내는 HTML을 본다. Suspense 밖에서 쓰면 서버 렌더가 실패한다
const { browser } = require('react-dom');
function ClientOnly() { React.use(browser()); return React.createElement('p', null, 'client only'); }
// <Suspense fallback={<p>loading</p>}><ClientOnly/></Suspense> 를 renderToString에 넘긴다
출력엔 fallback인 <p>loading</p>과 함께 data-msg="Switched to client rendering because a component requested it."가 붙은 <template>이 나온다. 브라우저에서 하이드레이션되면 use(browser())는 그냥 undefined를 돌려주고 컴포넌트가 이어서 렌더된다. 19.2.8에서 같은 코드를 돌리면 "browser is not a function" 메시지와 함께 서버 렌더가 실패하고 fallback으로 떨어진다. 서버 컴포넌트에서는 못 쓰고 클라이언트 컴포넌트에서만 부른다.
Fragment ref와 나머지
<Fragment ref={ref}>를 쓰면 FragmentInstance가 오고, 1단계 DOM 자식에 대해 addEventListener, observeUsing, scrollIntoView 같은 메서드를 쓸 수 있다. focus는 중첩 자식까지 깊이 우선으로 옮긴다. 축약형 <>에는 ref를 못 붙이니 Fragment를 import해야 한다. renderToString으로 찍어보면 래퍼 태그가 없다. 이벤트 위임용 div 하나 지우자고 마크업 건드리던 일이 줄어든다.
나머지는 짧게. Trusted Types 값을 문자열로 강제 변환하지 않게 됐고, 서버 컴포넌트에서 'use client' 모듈의 Context를 바로 렌더할 수 있다. Transition이 서로 얽히지 않고 따로 렌더돼 느린 하나가 나머지를 붙잡지 않는다. useDeferredValue가 옛 값에 고착되던 버그, 모바일 Safari의 ViewTransition 크래시도 고쳤다.
canary 붙이고 쓰던 건 임시 번호판 달고 다니는 차와 같다. 굴러가긴 하는데 언제 바뀔지 몰라 마음이 안 놓인다. 19.3은 그 번호판을 정식으로 바꿔준 릴리스다. Next.js App Router는 원래 canary를 번들해 설정 없이 됐으니, 손댈 건 Vite나 직접 SSR 하는 쪽이다. 우리가 할 일은 npm ls react로 19.2인지 확인하고 @latest로 올린 뒤 flushSync 쓰는 곳부터 훑어보는 것이다.
참고
- React Blog, React 19.3 (2026-09-09) https://react.dev/blog/2026/09/09/react-19-3
- React Reference, ViewTransition https://react.dev/reference/react/ViewTransition
- React Reference, browser https://react.dev/reference/react-dom/browser
- GitHub, react v19.3.0 https://github.com/facebook/react/releases/tag/v19.3.0
- Next.js Docs, View Transitions https://nextjs.org/docs/app/guides/view-transitions
'개발 > 프론트엔드' 카테고리의 다른 글
| 🛡️ Next.js 16.4.1 보안 업데이트 · 내 앱 해당 조건표 (0) | 2026.10.28 |
|---|---|
| 🟦 TypeScript 7.1 베타 · es2026 타깃과 달라진 점 (0) | 2026.10.27 |
| 🧯 Sentry 에러 로그 자세히 남기기 · v11은 기본 수집이 늘었다 (0) | 2026.10.23 |
| ⚡ Next.js prefetch false 언제 쓰나 · 기본값과 서버 부하 (0) | 2026.10.23 |
| 📱 Shopify가 React Native 버리고 네이티브로 돌아간 이유 (0) | 2026.09.11 |