왜 구글이 내 React 사이트를 색인하지 않을까
거의 언제나, 크롤러가 받는 페이지와 당신이 보는 페이지가 다르기 때문입니다. 브라우저에서 그리는 React 앱은 거의 비어 있는 HTML 껍데기를 보내고 그 안을 자바스크립트로 채웁니다. 구글이 그 자바스크립트를 실행하기는 하지만, 크롤링하는 순간이 아니라 나중 단계에서 실행합니다. 첫 단계에서 정해지는 것들 — 이 페이지가 중복인지, 어떤 URL이 표준인지, 어떤 언어 버전이 해당하는지 — 은 모두 그 껍데기 위에서 정해집니다. 그러니 무언가를 고치기 전에, 브라우저가 그려낸 화면이 아니라 서버가 실제로 돌려주는 원본 HTML을 읽으세요.
크롤러가 받는 페이지는 당신이 보는 페이지가 아니다
운영 중인 사이트를 열고 검사가 아니라 소스 보기를 고르세요. 검사는 자바스크립트가 실행된 뒤의 DOM을 보여주지만, 소스 보기는 서버가 실제로 보낸 바이트를 보여줍니다. 클라이언트에서 그리는 React 앱이라면 그 바이트는 대개 제목 하나, link 태그 몇 개, 비어 있는 컨테이너 요소 하나, 그리고 스크립트 번들입니다. 크롤러가 가장 먼저 읽는 문서가 바로 이것이고, 만든 지 얼마 안 된 사이트라면 한동안 이것만 읽히는 유일한 버전일 수도 있습니다.
구글은 자바스크립트를 단계로 나누어 처리한다고 분명히 밝히고 있습니다. URL을 크롤링하고, 렌더링 대기열에 페이지를 넣고, 돌아온 결과를 색인합니다 — 그리고 이 렌더링은 즉시가 아니라 미뤄집니다. 여기서 두 가지 결과가 나옵니다. 하나는 명백합니다. 번들이 실행되어야만 존재하는 내용은 색인이 늦어지고, 아예 안 될 수도 있습니다. 다른 하나는 저절로 풀리지 않아서 더 큰 손해를 냅니다. 오직 첫 응답에서만 읽히는 신호들이 있고, 나중에 아무리 렌더링해도 그것들은 되살아나지 않습니다.
가장 분명한 사례가 표준 태그입니다. 구글 자신의 안내는 표준 링크 요소가 문서의 헤드 안에 있어야 한다고 말하며, 언어 버전을 이어주는 hreflang 표기도 마찬가지입니다. 프레임워크가 나중에 — 본문 쪽에, 혹은 헤드를 이미 내보낸 뒤에 — 끼워 넣는 태그는, 판단이 내려진 시점에는 없던 태그입니다. 브라우저가 어차피 적용하니 화면상으로는 멀쩡해 보입니다. 다만 당신이 생각을 바꾸려던 유일한 독자에게는 너무 늦게 도착했을 뿐입니다.
구글이 실제로 무엇을 받았는지 확인하기
이십 분이면 되고, 무언가를 다시 쓰기 전에 논쟁을 끝내 줍니다. 미리보기가 아니라 운영 URL을 대상으로 하세요.
- 원본 응답을 읽는다. curl로 페이지를 받아 오거나 소스 보기를 씁니다. 돌아온 결과에 두 가지를 물으세요. 상위 노출을 노리는 그 문구가 이 텍스트 안에 들어 있는가. 표준 태그가 문서 아래쪽이 아니라 head 요소 안에 있는가. 둘 중 하나라도 아니라면 원인은 이미 찾은 셈입니다.
- 서치 콘솔에서는 실시간 테스트가 아니라 크롤링된 페이지를 읽는다. URL 검사는 둘 다 보여줍니다. 실시간 테스트는 그 자리에서 새로 가져온 결과이고, 크롤링된 HTML은 지난번에 실제로 저장된 것입니다. 둘이 어긋난다면 크롤링된 쪽을 믿으세요. 그 어긋남 자체가 발견입니다.
- 하위 페이지 URL을 직접 요청한다. 클라이언트 라우터는 서버가 제공하지 않는 경로를 만들어낼 수 있습니다. 하위 페이지를 새 탭에서 열거나 curl로 받아, 홈으로 리디렉션하거나 아무것도 없다고 말하면서도 성공을 반환하는 대신 그 페이지 자신의 내용을 돌려주는지 확인하세요.
- 호스트를 셋이 아니라 하나만 제공하는지 확인한다. www가 붙은 형태, 붙지 않은 형태, 그리고 암호화되지 않은 형태를 각각 요청하세요. 셋 중 둘은 나머지 하나로 리디렉션되어야 합니다. 둘 이상이 사이트 전체를 제공한다면, 같은 사이트를 두 주소에 올려놓고 어느 쪽을 고를지 구글에 맡긴 것입니다.
우리 자신의 점검을 피해 숨어 있던 결함
우리는 AgentCeres — AI Growth Officer를 agentceres.com에서 React 프레임워크 위에 운영하는데, 이 문제의 한 형태 때문에 몇 달치 색인을 잃었습니다. 우리가 돌린 모든 점검이 정상이라고 답했기 때문입니다. 전부 적어 둘 가치가 있습니다. 각 단계가 성공처럼 보이는 방식으로 실패했으니까요.
서치 콘솔은 우리 페이지의 상당수를 사용자가 선택한 표준 URL이 없는 중복으로 보고했습니다. 신뢰할 만한 표준 태그를 찾지 못해 스스로 하나를 골랐다는 뜻입니다. 태그는 있었습니다. 응답의 약 40킬로바이트 지점에 있었죠. 프레임워크가 동적으로 렌더링하는 페이지에서는 메타데이터를 스트리밍으로 보내는데, head 요소는 맨 위에서 약 1.5킬로바이트 지점에서 이미 닫혀 있었습니다. 브라우저는 모두 그 태그를 적용했습니다. 첫 크롤링만이 그것을 보지 못했습니다.
그 페이지들이 애초에 동적이었던 이유가 두 번째 결함입니다. 빌드 시점에 미리 생성된다고 믿었던 열두 개 경로가 조용히 그 대상에서 빠져 있었습니다. 공용 헤더 컴포넌트가 요청 헤더를 읽는 번역 헬퍼를 호출했고, 요청 헤더를 읽는 것만으로 페이지는 동적이 됩니다. 빌드 자신의 경로 표는 여전히 그것들을 정적으로 찍고 있었습니다. 생성된 HTML 파일 목록에는 하나도 없었습니다. 요약과 산출물이 서로 어긋났고, 우리는 요약을 읽고 있었습니다.
가져갈 만한 것은 세 번째입니다. 메타데이터를 스트리밍하는 프레임워크는 예전처럼 기다렸다 보내주는 크롤러 목록을 따로 두는 경우가 많고, 우리 것도 그 목록을 기본으로 갖고 있었습니다. Googlebot은 거기에 없었습니다. 실시간 테스트 버튼 뒤에서 도는 구글의 검사 도구는 있었습니다. 그러니까 우리 작업을 확인하려고 누르던 그 버튼은 매번 멀쩡한 버전을 받아왔고, 일반 크롤링은 계속 망가진 버전을 받고 있었던 겁니다. 검증 도구가 당신의 스택 안에서 특별 취급을 받는다면, 그것은 검증이 아닙니다.
값싼 각주 두 개. 우리 www 호스트는 리디렉션 없이 사이트 전체를 제공하고 있었고, 구글은 그것을 두 번째 사본으로 색인해 사이트맵에 올린 주소를 낮춰 두고 있었습니다. 미들웨어 두 줄로 끝났습니다. 그리고 크롤러별 설정에 손이 간다면, 오리진 앞의 CDN은 대개 누가 요청했는지 보지 않고 캐시한다는 점을 기억하세요. 캐시에 올라간 버전이 다음 크롤러가 받는 버전입니다. CDN 뒤에서는 특정 크롤러만 다르게 대하는 규칙에 기댈 수 없습니다.
효과 순으로, 무엇을 바꿀 것인가
- 찾히길 바라는 페이지는 미리 생성하세요. 마케팅 페이지, 문서, 낯선 사람이 검색할 만한 모든 것은 빌드 시점에 HTML을 만들어 두세요. 정적 출력은 문제 전체를 비껴갑니다. 첫 단계에 이미 본문과 태그가 다 들어 있기 때문입니다.
- 정말로 동적이어야 하는 것만 서버에서 렌더링하세요. 요청에 따라 달라지는 페이지도 완전한 HTML을 돌려줄 수 있습니다. 중요한 것은 미리 계산했는지가 아니라 첫 응답이 완성되어 있는지입니다.
- 제목, 설명, 표준, 언어 태그를 그 첫 응답의 헤드에 두세요. 프레임워크가 그것들을 미룰 수 있다면 막는 설정을 찾고, 렌더링된 DOM이 아니라 원본 HTML을 읽어 고쳐졌는지 확인하세요. 사람들이 이미 했다고 믿는 단계가 바로 여기입니다.
- 모든 페이지에 진짜 URL과 그것을 가리키는 진짜 링크를 주세요. 프래그먼트나 클릭 핸들러 뒤에 있는 경로는 크롤링되지 않습니다. 크롤러는 href를 가진 앵커 요소를 따라가므로, 버튼으로 만든 내비게이션은 구글이 걸어갈 수 없는 내비게이션입니다.
- 호스트를 하나 고르고 나머지는 리디렉션하세요. 그런 다음 고른 형태만 담은 사이트맵을 제출해, 당신의 선언들끼리 서로 어긋나지 않게 하세요.
이 중 어느 것도 순위 전략이 아닙니다. 전략의 전제 조건이고, 다시는 신경 쓰지 않기 위해 반나절에 끝낼 가치가 있습니다. 페이지가 실제로 읽히기 시작하면, 성과를 좌우하는 질문은 멀쩡한 페이지인데도 유입이 없는 이유와 첫 방문자가 실제로 어디서 오는지입니다. 이 파이프라인을 아예 직접 갖고 싶지 않다면, 정적 사이트 생성기와 Git 기반 편집기의 조합이 구조적으로 렌더링 문제를 피해 갑니다. 배포되는 것이 이미 완성된 HTML이니까요.
FAQ
- 구글은 자바스크립트 사이트를 색인하기는 하나요?
- 합니다. Googlebot은 자바스크립트를 실행하고 페이지가 최종적으로 어떤 모습이 되는지를 색인합니다. 다만 크롤링 시점이 아니라 미뤄진 단계에서 하기 때문에, 번들이 실행되어야만 존재하는 것은 이미 HTML에 있는 내용보다 늦게, 그리고 덜 확실하게 색인됩니다. 첫 응답에서만 읽히는 부분 — 표준 태그와 hreflang 표기 같은 것 — 은 렌더링으로 되살아나지 않습니다. 클라이언트 렌더링은 절대적인 장벽이 아니라 지연에 위험이 더해진 것으로 보세요.
- 이걸 고치려면 서버 사이드 렌더링이 필요한가요?
- 대개는 아닙니다. 낯선 사람이 검색할 만한 페이지에는 정적 사전 생성으로 충분하고, 서버 렌더링보다 단순하고 운영 비용도 적습니다. 서버 렌더링은 내용이 정말로 누가 요청했는지에 따라 달라지는 페이지에 남겨 두세요. 중요한 기준은 어떤 기법을 고르느냐가 아니라 첫 응답이 이미 완성되어 있느냐입니다. 사전 생성과 서버 렌더링은 그것을 만족하고, 클라이언트 렌더링은 만족하지 않습니다.
- 서치 콘솔 실시간 테스트는 멀쩡한데 왜 색인이 안 되나요?
- 실시간 테스트는 그 자리에서 수행하는 요청과 렌더링이고, 당신의 스택이 일반 크롤링과는 다른 응답을 그쪽에 줄 수 있기 때문입니다. 우리가 그랬습니다. 기다렸다 메타데이터를 받는 크롤러 기본 목록에 구글의 검사 도구는 있었지만 Googlebot은 없었으니까요. 같은 보고서 안에 저장된 크롤링 HTML을 대신 읽으세요. 둘이 다를 때 색인되는 쪽은 크롤링된 사본이고, 실시간 테스트는 다른 누구에게도 제공되지 않는 페이지 이야기를 하고 있는 것입니다.
Want this done for you?
AgentCeres is a managed AI marketing team — specialists draft the work, you approve what ships. 14-day free trial, from $39/month.