무엇
<script>는 인라인 또는 외부 스크립트를 문서에 추가한다. React 19는 조건이 맞는 외부 스크립트를 자동으로 중복 제거하고 문서 <head>로 옮긴다.
외부 스크립트는 src로, 인라인 코드는 자식으로 준다.
<script async src="analytics.js" />
무슨 일이 일어나나
React 19의 핵심은 컴포넌트 트리 어디서 렌더하든 스크립트를 알아서 정리한다는 점이다. 다만 특별 처리는 조건부다.
외부 스크립트에 src와 async={true}를 함께 주면 React가 특별 취급한다. src가 같은 스크립트는 여러 컴포넌트가 렌더해도 DOM에 하나만 들어가고(중복 제거), 그 요소는 <head>로 올라간다(hoisting). 컴포넌트 트리 깊은 곳에 <script>를 써도 실제로는 문서 머리에서 한 번만 로드된다는 뜻이다.
인라인 스크립트나 async가 아닌 스크립트는 특별 처리를 받지 않는다. 중복 제거도, <head>로 옮기는 것도 없이 렌더된 자리에 그대로 남아 삽입될 때 실행된다.
주의할 점 두 가지. 첫 렌더 뒤 바뀐 script prop은 무시된다(개발 모드에서 경고). 그리고 컴포넌트가 언마운트돼도 스크립트는 DOM에 남을 수 있다 — 스크립트는 한 번만 실행되므로 실질적 영향은 없다.
사용법
외부 스크립트는 async src로 두면 트리 어디에 있어도 한 번만 로드된다.
export function Map({ lat, long }: { lat: number; long: number }) {
return (
<>
<script async src="map-api.js" onLoad={() => console.log('로드됨')} />
<div id="map" data-lat={lat} data-long={long} />
</>
)
}
인라인 스크립트는 자식으로 코드를 넣는다. 이쪽은 중복 제거·hoisting 대상이 아니다.
<script>{`ga('send', 'pageview')`}</script>
실무 예시
지도, 결제 위젯처럼 외부 스크립트가 필요한 컴포넌트를 여러 화면에서 써도, async src로 두면 중복 로드 걱정이 없다.
export function PaymentWidget() {
return (
<>
<script async src="https://js.example.com/pay.js" />
<div id="pay-container" />
</>
)
}
왜 중요한가
예전에는 서드파티 스크립트를 넣으려면 useEffect로 직접 <script>를 만들어 붙이고, 중복 삽입을 손으로 막아야 했다. React 19는 이걸 컴포넌트 안에 <script async src>로 선언만 하면 되도록 바꿨다. 지도·분석·결제 위젯처럼 외부 리소스에 기대는 컴포넌트를 만들 때 실질적으로 쓸모가 크다. 단, 특별 처리는 async 외부 스크립트에만 걸린다는 조건을 기억해야 한다.
Reference