← 블로그로 돌아가기
EngineeringBuild in PublicSEO

우리 Next.js 페이지는 Bing에게 너무 컸다. RSC 페이로드가 HTML의 58%

Next.js 페이지 세 개가 Bing에서 검색됨, 크롤링되지 않음에 멈췄다. App Router는 모든 바이트를 두 번 보낸다. 한 페이지를 48KB 줄이자 색인됐다.

2026년 10월 8일

우리 Next.js 페이지는 Bing에게 너무 컸다. RSC 페이로드가 HTML의 58%

Katto는 AI 영상 클리핑 도구입니다. 긴 영상을 건네면 가장 좋은 순간을 찾아 자막이 들어간 세로형 클립으로 잘라냅니다. 저는 Next.js의 App Router로, 혼자서, 공개적으로 만들고 있습니다. 이 글은 페이지를 쓰는 방식을 바꾼 측정과, 여러 사람 앞에서 확신을 담아 내놓았다가 틀린 것으로 드러난 숫자와, 108바이트짜리였던 제 가설에 관한 이야기입니다.

증상

Bing Webmaster Tools가 저희 페이지 몇 개를 「검색됨, 크롤링되지 않음」으로 표시했습니다. robots.txt로 막지 않았고, noindex도 아니고, 느리지도 않고, 리디렉션도 없었습니다. 발견한 뒤 그대로 두는 것입니다. Google은 색인하고 있었습니다. 사이트맵에도 들어 있었습니다. 누가 요청해도 200을 돌려줬습니다.

거절당한 페이지들에는 공통점이 하나 있었고, 그것은 내용이 아니었습니다.

경계, 그리고 우리가 그것을 얼마나 모르는가

Microsoft는 크롤러가 포기하는 크기를 공개하지 않습니다. 우리가 가진 것은 우리 자신의 페이지뿐입니다. Bing Webmaster Tools에서 읽은 상태와, 같은 날 bingbot 사용자 에이전트로 압축 없이 측정한 전송 크기입니다.

선 너머에 페이지 한 장이 있다고 법칙이 되지는 않습니다. 하지만 이 목록에는 나머지 전부보다 가치 있는 관측이 하나 있습니다. 같은 URL이 두 번 등장하기 때문입니다. /compare/ai-video-clippers는 172,295바이트를 전송했고 몇 주 동안 「검색됨, 크롤링되지 않음」에 멈춰 있었습니다. 약 124KB로 줄였습니다. 오늘은 127,951바이트로 색인되어 있습니다. 같은 URL, 같은 템플릿, 같은 종류의 내용인데, 너무 컸다가 충분히 작아진 것입니다.

일주일 전의 저라면 다른 숫자를 말했을 테고, 그것은 틀렸을 겁니다. 상태 판독 세 건과 우리 페이지 크기 분포의 눈에 띄는 빈틈이 있었고, 저는 거기서 125KB 부근의 경계를 읽어냈습니다. 그중 두 페이지는 그 뒤 128KB를 조금 밑도는 크기로 색인됐습니다. 우리 사이트가 실제로 말할 수 있는 것은, 선이 128KB와 140KB 사이에 있고 그 너머는 여전히 정확히 페이지 한 장 너비라는 것뿐입니다.

숫자를 찾아 오셨다면 정직한 답은 「직접 재보세요」입니다. 제가 방어할 수 있는 주장은 더 좁고, 어차피 더 쓸모 있습니다. 검색됨에서 멈춘 페이지는 다시 쓰기 전에 무게를 재볼 가치가 있고, 줄인 뒤에 크롤링되기 시작한 페이지야말로 진짜로 의미 있는 유일한 증거입니다.

소스 파일의 무게는 아무것도 말해주지 않는다

여기서부터가 React Server Components에 고유한 이야기입니다. App Router는 렌더링된 트리를 같은 HTML 문서 안에 self.__next_f.push(...) 스크립트 태그의 연속으로 직렬화합니다. 클라이언트 측 내비게이션이 왕복 없이 이어지도록 하기 위해서입니다. 이 페이로드는 크롤러가 건너뛸 수 있는 별도 요청이 아닙니다. 문서 안에 있습니다.

어느 페이지에서 측정한 결과: 전송 125,981바이트 중 73,571바이트가 그 페이로드였습니다. 문서의 58퍼센트가, 나머지 42퍼센트가 이미 말하고 있는 것의 직렬화된 사본입니다.

그러니 24행 반복문 안에 한 번 쓴 유틸리티 클래스는 24번 나가는 게 아닙니다. 48번 나갑니다.

알아차리는 데 가장 오래 걸린 부분

이것은 페이지를 두 배로 만드는 게 아닙니다. 페이지가 이미 지니고 있는 표현 하나하나를 두 배로 만듭니다.

우리 로드맵은 각 항목을 일부러 두 번 그렸습니다. 한 번은 보이는 본문으로, 한 번은 JSON-LD의 ItemList 블록 안에. 기계 독자가 이름과 날짜와 상태를 해석해야 할 산문이 아니라 데이터로 받게 하려는 것이었습니다. 합리적입니다. 그다음 한 항목의 설명문이 전송 문서에 몇 번 나오는지 세어봤더니 네 번이었습니다.

표현이 둘, 각각이 두 배. 같은 문장의 사본 넷. 세어볼 생각을 한 번도 하지 않았던 것입니다.

해법은 압축이 아니었습니다. JSON-LD가 들고 있던 description은 독자가 서른 줄 아래에서 보는 바로 그 문장을, 같은 언어로 그대로 복사한 것이었습니다. 얻는 것은 없고 사본 넷 중 둘을 먹고 있었습니다. 지우면 블록에는 이름과 날짜와 상태가 남습니다. 산문이 기계에 명시하지 않는 것이 바로 그것입니다. 이 페이지는 121,196바이트에서 115,350바이트가 됐고 정보는 하나도 빠지지 않았습니다. 두 숫자 모두 같은 빌드의, 그 한 가지 변경 전과 후입니다. 전후 비교가 의미를 갖는 방식은 이것뿐입니다.

여기서 습관을 하나만 가져가신다면 이것입니다. 무엇을 줄이려 하기 전에, 내 콘텐츠의 한 문장이 전송 문서에 몇 번 나오는지 세어보십시오. 섹션의 무게를 재서는 답이 나오지 않는 질문에 답을 줍니다.

더 큰 두 개의 지렛대

반복되는 클래스 속성을 스타일시트 규칙으로. Tailwind 유틸리티는 쓰기에 놀랍도록 싸고, 모든 요소에 두 번씩 실려 나갑니다. 반복이 많은 것들을 @apply 규칙으로 옮기자 요금 색인이 172,295바이트에서 약 142,000바이트가 됐습니다. 그것이 마크업의 약 51퍼센트였습니다.

비대화형 블록을 직렬화 트리 밖으로. 정확히 말하면, 이게 중요합니다. 이들은 Server Components라서 클라이언트에서 하이드레이트되지 않습니다. 비용은 하이드레이션이 아니라 직렬화입니다. 클라이언트 측 내비게이션이 왕복 없이 트리를 재구성할 수 있도록 모든 요소와 prop과 키가 플라이트 페이로드에 기록됩니다. 클라이언트 동작이 전혀 없는 정적 표도 그 값을 치르고, 제약이 문서 크기라면 그것이 React 요소로 남아 있을 이유가 없습니다. 지금은 그런 블록을 같은 데이터로 HTML 문자열을 만들어 dangerouslySetInnerHTML로 넣습니다. 이스케이프가 데이터 출처에 좌우되지 않도록, 보간되는 값은 만드는 시점에 모두 이스케이프합니다. 요금 색인에서 18KB 더. 로드맵의 54행 목록은 같은 방식으로 157,308에서 119,031이 됐습니다.

통하지 않은 것

그 전의 저는 문제가 컴포넌트 경계라고 확신했습니다. 목록 안의 next/link 컴포넌트 쉰네 개가 각각 직렬화된 이름과 props와 키를 페이로드에 더하고 있다고요. 쉰네 개를 모두 평범한 앵커로 바꿨습니다.

157,200바이트 중 108바이트를 아꼈습니다.

Link는 쌉니다. 그것이 감싸고 있는 내용이 비쌉니다. 전후를 측정하지 않았다면 저는 그 가설로 하루를 컴포넌트 고쳐 쓰는 데 썼을 테고, 이 부정적 결과가 그날 가장 쓸모 있는 숫자였습니다.

바닥이 있고, 낮지 않다

가장 가벼운 실제 페이지인 /contact는 제목 하나와 짧은 양식과 질문 몇 개만 담고도 46,111바이트를 전송합니다. 나머지는 내비게이션, 푸터, 분석, 폰트입니다. 이 숫자 아래로는 페이지 단위로 가져갈 것이 없습니다. 그때부터는 페이지 작업이 아니라 외피 작업입니다. 바닥을 알면 언제 멈출지 알 수 있습니다.

번역이 쓰는 것은 바이트지 단어가 아니다

그 로드맵을 열 개 언어로 옮겼습니다. 같은 마크업, 같은 구조, 같은 정보입니다. 영어 페이지는 116,239바이트, 프랑스어는 119,838, 일본어는 122,155, 힌디어는 135,379바이트를 전송합니다.

데바나가리는 UTF-8에서 글자당 3바이트를 쓰고 라틴 문자는 1바이트를 씁니다. 그리고 페이로드가 그것을 한 번 더 치릅니다. 페이지 수준의 어떤 기법도 여기엔 손대지 못합니다. 같은 마크업, 같은 정보, 19킬로바이트 차이입니다.

흥미로운 쪽은 힌디어 페이지입니다. 135,379바이트는 우리가 결론 낼 수 없는 구간의 한복판이기 때문입니다. Bing이 이것을 처리하는지 여부는 우리가 할 수 있는 가장 싼 실험이고, URL 검사 한 번이면 끝납니다.

여기에는 한때 그 초과분이 그리 중요하지 않다고 주장하는 문단이 있었습니다. 초과가 문서 끝에 떨어지고, 그 페이지의 끝은 어차피 제목이 영어로 남는 목록이라는 논리였습니다. 잘라냈습니다. 크롤러가 예산을 다 쓸 때까지 읽다가 멈춘다고 전제하기 때문입니다. 제가 실제로 관측한 모든 것은 어떤 크기를 넘는 페이지는 처리되지 않는다는 것뿐입니다. 문서가 통째로 거절된다면 순서는 아무것도 사주지 않고, 저는 어느 쪽 증거도 갖고 있지 않습니다. 내 페이지에 관한 편안한 이론이고, 그런 것일수록 먼저 지울 가치가 있습니다.

이것은 수리가 아니라 예산이다

172,295바이트에서 124,202바이트로 줄였던 요금 색인은 오늘 127,951바이트입니다. 도구가 늘고, 열이 늘고, 번역이 열 개 늘었기 때문입니다. 여전히 색인되어 있고 그게 핵심이며, 동시에 선에서 몇 킬로바이트 안쪽까지 되돌아왔습니다. 잘못된 것은 없습니다. 그저 지켜보지 않은 채 예산을 다시 썼을 뿐입니다.

이것이 진짜 결론입니다. 자기 출력을 직렬화하는 프레임워크에서 페이지 무게는 한 번 하고 끝나는 청소가 아닙니다. 누군가 좋은 일을 할 때마다 올라가는 숫자이고, 유일한 방어는 소스가 아니라 전송된 문서를, 한 번에 한 가지 변경씩 측정하는 것입니다.

직접 재는 방법

관련 글

영상을 바이럴 클립으로 만들 준비가 되셨나요?

Katto는 긴 영상을 자동으로 자르고 자막을 넣고 리프레임해 숏폼 콘텐츠로 만들어 줍니다.

Katto 무료로 사용해보기 →