Loading the catalog…
Loading the catalog…
시작 Next.js로 만든 사이트에서 DevTools의 Elements 탭을 열었는데, <body> 맨 아래에 script 태그가 수십 개 붙어 있었다. 정말 당황스러웠다; <script>self.__next_f.push([1,"e:I[622,[],\"IconMark\"]\n"])</script> <script>self.__next_f.push([1,"c:\"$7:metadata\"\n"])</script> <script>self.__next_f.push([1,"16:T225d,"])</script> <script>...</script> <script>...</script> <!-- 이하 수십 개 --> 직접 넣은 코드가 아니라서 뭔지 찾아봤다. 찾다 보니 App Router 와 Pages Router 가 어떻게 다른지까지 이어졌다. 이 글은 그 차이와 각각의 장점을 정리한 것이다. script 태그의 정체 self.__next_f.push(...) 는 App Router 가 넣는 RSC Payload 다. 서버 컴포넌트가 렌더링한 결과를 React가 읽을 수 있게 직렬화한 데이터다. e:I[622,[],"IconMark"] ← I : 클라이언트 컴포넌트 참조 c:"$7:metadata" ← 다른 줄을 가리키는 참조 16:T225d, ← T : 텍스트. 225d(16진수) = 8,797 바이트가 뒤에 온다 script 태그 안에 있지만 하는 일은 배열에 문자열을 넣는 것이다. 여러 개로 나뉜 건 Next 가 조각 단위로 내보내서 그렇다. 왜 필요한가 서버에서 만든 HTML은 문자열이다. 브라우저에서 React가 이 화면을 이어받으려면(hydration) DOM이 어떤 컴포넌트 트리에서 나왔는지 알아야 하는데, HTML만으로는 알 수 없다. 그래서 그 정보를 같이 보낸다. 크기 페이지 하나를 재봤다. 값 script 태그 49개 그중 __next_f 40개 __next_f 크기 (raw) 106 KB gzip 전송 기준 약 11 KB raw 로는 크지만 HTML에 이미 있는 내용이 한 번 더 나오는 거라 압축이 잘 된다. 문서 맨 아래에 있어서 화면을 그리는 것도 막지 않는다. Pages Router 에는 이 태그가 없다. 대신 <script id="__NEXT_DATA__"> 하나가 있다. 이 차이가 두 라우터의 동작 방식 차이에서 나온다. Pages Router 는 이렇게 동작한다 pages/ 폴더의 파일 하나가 페이지 하나다. 데이터는 페이지 파일의 전용 함수에서 가져와서 props 로 내려준다. // pages/products.tsx export async function getStaticProps() { const products = await getProducts() return { props: { products } } } export default function Page({ products }) { return ( <> <ProductTable products={products} /> <Footer /> </> ) } 여기서 Page , ProductTable , Footer 는 서버에서 한 번, 브라우저에서 한 번 실행된다. 서버에서 HTML을 만들고, 브라우저에서 같은 컴포넌트를 다시 실행해서 hydration 한다. 그래서 두 가지가 브라우저로 간다. 페이지에 넘긴 props ( __NEXT_DATA__ 에 JSON으로 들어간다) 페이지에 쓰인 모든 컴포넌트의 코드 Footer 처럼 클릭할 게 없는 컴포넌트도 코드가 내려간다. App Router 는 이렇게 동작한다 app/ 폴더를 쓰고, 컴포넌트는 기본이 서버 컴포넌트 다. 서버에서만 실행되고 브라우저에서는 다시 실행되지 않는다. // app/products/page.tsx — 서버 컴포넌트 export default async function Page() { const products = await getProducts() return ( <> <ProductTable products={products} /> <ProductFilter products={products} /> <Footer /> </> ) } 'use client' // 브라우저에서도 실행해야 하는 컴포넌트만 표시한다 export default function ProductFilter({ products }) { const [keyword, setKeyword] = useState('') // ... } 브라우저로 가는 것이 달라진다. 서버 컴포넌트( Page , ProductTable , Footer )는 코드가 가지 않는다. 렌더 결과만 RSC Payload 로 간다 클라이언트 컴포넌트( ProductFilter )는 코드가 가고, 넘겨받은 props 도 payload 에 실린다 처음에 본 script 태그가 이 payload 다. Pages Router 는 props 만 실으면 되지만 App Router 는 서버 컴포넌트의 렌더 결과를 실어야 해서 양이 더 많다. 대신 JS 파일이 작아진다. 차이 정리 Pages Router App Router 폴더 pages/ app/ 라우트 파일 pages/products.tsx app/products/page.tsx 컴포넌트 기본값 서버 + 브라우저 둘 다 실행 서버에서만 실행 브라우저로 가는 JS 페이지의 모든 컴포넌트 'use client' 붙인 것만 데이터 가져오기 getStaticProps · getServerSideProps 컴포넌트 안에서 await 공통 레이아웃 _app.tsx 하나 폴더마다 layout.tsx 중첩 로딩 처리 직접 구현 loading.tsx · Suspense 스트리밍 메타 태그 next/head metadata · generateMetadata 데이터 변경 API Route 를 만들어 호출 Server Actions HTML에 싣는 데이터 props ( __NEXT_DATA__ ) 렌더 결과 ( __next_f ) App Router 의 장점 1. 브라우저로 가는 JS 가 줄어든다 서버 컴포넌트는 코드가 내려가지 않는다. 약관, 푸터처럼 보여주기만 하는 부분이 많을수록 차이가 커진다. 서버 컴포넌트에서만 쓰는 라이브러리(마크다운 파서, 날짜 포맷터 등)도 번들에 들어가지 않는다. 2. 데이터를 쓰는 곳에서 가져온다 Pages Router 는 페이지 파일에서만 데이터를 가져올 수 있어서, 깊은 컴포넌트가 쓰는 데이터도 페이지에서 받아 props 로 계속 내려줘야 한다. App Router 는 필요한 컴포넌트가 직접 await 한다. async function ProductTable() { const products = await getProducts() return <table>...</table> } 3. 레이아웃을 중첩할 수 있다 폴더마다 layout.tsx 를 둘 수 있고, 페이지를 이동해도 공통 레이아웃은 다시 렌더링되지 않는다. Pages Router 에서는 _app.tsx 하나로 처리하거나 페이지마다 getLayout 같은 패턴을 직접 만들어야 했다. 4. 준비된 부분부터 보여준다 loading.tsx 나 <Suspense> 로 감싸면 느린 데이터를 기다리는 동안 나머지 화면을 먼저 보낸다. Pages Router 는 getServerSideProps 가 끝나야 페이지 전체가 나온다. 5. 새 기능이 여기에 들어온다 서버 컴포넌트, Server Actions 처럼 React 의 서버 쪽 기능은 App Router 에서만 쓸 수 있다. Next.js 문서도 새 프로젝트에는 App Router 를 권한다. Pages Router 의 장점 1. 구조가 단순하다 모든 컴포넌트가 같은 방식으로 동작한다. 서버 컴포넌트와 클라이언트 컴포넌트를 구분할 필요가 없고, useState 나 useEffect 를 어디서든 쓸 수 있다. App Router 는 경계를 계속 신경 써야 한다. 서버 컴포넌트에서는 훅과 이벤트 핸들러를 못 쓰고, 클라이언트 컴포넌트로 넘기는 props 는 직렬화가 되는 값이어야 한다(일반 함수는 넘길 수 없다). 2. 라이브러리가 그대로 동작한다 Context 를 쓰는 라이브러리(상태 관리, UI 키트, 스타일 라이브러리)는 App Router 에서 'use client' 로 감싸거나 Provider 를 따로 분리해야 한다. Pages Router 에서는 그런 작업이 없다. 3. 데이터 흐름이 눈에 보인다 데이터를 가져오는 곳이 페이지 파일의 함수 하나로 정해져 있어서, 이 페이지가 어떤 데이터를 쓰는지 한 곳에서 확인할 수 있다. App Router 는 캐싱과 재검증 규칙까지 알아야 해서 익히는 데 시간이 더 든다. 4. 여전히 지원된다 Pages Router 는 계속 지원되고, 한 프로젝트 안에서 app/ 과 pages/ 를 같이 쓸 수도 있다. 기존 프로젝트를 급하게 옮길 이유는 없다. 어떤 경우에 무엇을 쓰나 상황 선택 새 프로젝트 App Router 정적인 내용이 많은 사이트 (마케팅, 블로그, 문서) App Router. 서버 컴포넌트 비중이 커서 JS 가 많이 줄어든다 화면 대부분이 인터랙션인 앱 (대시보드, 에디터) 어느 쪽이든 큰 차이가 없다. 대부분 클라이언트 컴포넌트가 된다 잘 돌아가는 Pages Router 프로젝트 그대로 둔다. 필요하면 새 페이지만 app/ 에 만든다 Context 기반 라이브러리 의존이 큰 프로젝트 옮기기 전에 해당 라이브러리의 App Router 지원 여부를 먼저 확인한다 정리하면 script 태그 수십 개는 App Router 의 RSC Payload 다. gzip 으로는 작고, 그대로 둬도 된다 Pages Router 는 모든 컴포넌트를 브라우저에서 다시 실행한다. props 만 싣고 코드를 전부 보낸다 App Router 는 서버 컴포넌트를 브라우저에서 실행하지 않는다. 렌더 결과를 싣고 코드는 보내지 않는다 App Router 는 JS 가 줄고 데이터·레이아웃·로딩 처리가 편해진다. 대신 서버와 클라이언트의 경계를 관리해야 한다 Pages Router 는 단순하고 라이브러리 호환이 좋다. 대신 보여주기만 하는 컴포넌트도 코드가 내려간다 마무리 script 태그가 왜 많은지 찾아보다가 두 라우터의 동작 방식까지 보게 됐다. App Router 에서 HTML 에 script 가 많아 보이는 건 JS 파일에 있던 것이 HTML 쪽으로 온 결과였다. Elements 탭에 보이는 양과 실제 전송량은 차이가 컸다. 비슷한 걸 다시 보면 gzip 크기부터 확인하려고 한다. 🔗 Server and Client Components : https://nextjs.org/docs/app/getting-started/server-and-client-components 🔗 App Router : https://nextjs.org/docs/app 🔗 Pages Router : https://nextjs.org/docs/pages
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
Next.js로 script 태그가 40개 생겼다. 시작 Next.js로 만든 사이트에서 DevTools의 Elements 탭을 열었는데, 맨 아래에 script 태그가 수십 개 붙어 있었다. 정말 당황스러웠다; self.__next_f.push([1,"e:I[622,[],\"IconMark\"]\n"]) self.__next_f.push([1,"c:\"$7:metadata\"\n"]) self.__next_f.push([1,"16:T225d,"]) ... ... 직접 넣은 코드가 아니라서 뭔지 찾아봤다. 찾다 보니 App Router 와 Pages Router 가 어떻게 다른지까지 이어졌다. 이 글은 그 차이와 각각의 장점을 정리한 것이다. script 태그의 정체…
Open source