JavaScript 검색엔진 최적화

검색엔진이 JavaScript에 의존하는 콘텐츠를 크롤링하고 렌더링하고 색인할 수 있게 하는 방법을 다룹니다. 실제 링크, 콘텐츠 일치, 지연 로딩, 무한 스크롤, 소프트 404, 테스트를 설명합니다.

이 페이지의 근거 신호 1개

JavaScript SEO는 검색엔진이 JavaScript에 의존하는 콘텐츠를 크롤링하고 렌더링하고 색인할 수 있는지를 다룹니다. 실제 앵커 링크를 사용하고 JS/CSS를 차단하지 말며, 중요한 콘텐츠에는 서버 측 렌더링이나 사전 렌더링을 우선하고, 원시 HTML과 렌더링된 DOM의 차이 및 무한 스크롤 병합 문제를 점검하세요.

TL;DR — Google은 JavaScript를 실행할 수 있으므로 “Google이 JS를 읽을 수 있는가?”는 잘못된 질문입니다. 실제 실패 유형은 동등성(원시 DOM과 렌더링된 DOM), 상호작용(Google은 스크롤하거나 클릭하지 않음), 상태(렌더러는 상태를 유지하지 않음), 타이밍입니다. 링크는 실제 <a href> 앵커로 만들고 JS/CSS를 차단하지 마세요. 상호작용이 아니라 viewport 진입 시 지연 로드하고, 클라이언트 측 404에는 실제 상태를 반환해야 합니다. 원시 HTML의 noindex 때문에 JavaScript가 이를 제거하기 전에 렌더링이 생략될 수도 있습니다. 실제로 처리된 지시문만 함께 판단하고 원시 또는 렌더링 버전이 항상 우선한다고 가정하지 마세요. 특히 무한 스크롤에서는 긴 렌더 viewport가 로더를 작동시켜 두 URL을 하나의 색인 페이지로 합칠 수 있습니다. 렌더러 내부 구조와 렌더링 모드 선택은 렌더링을 참고하세요.

Google이 JavaScript를 읽을 수 있는가? 그렇지만 핵심 질문은 아닙니다

Google은 JavaScript를 실행할 수 있습니다. 실제 위험은 동등성, 상호작용, 상태, 타이밍입니다. 출처: /technical-seo/javascript-seo/

크롤링, 렌더링, 색인의 세 단계가 왼쪽에서 오른쪽으로 이어집니다. 렌더링 단계에서 네 가지 실패 유형이 갈라집니다. 동등성은 렌더링된 DOM이 예상과 다를 수 있는 문제, 상호작용은 콘텐츠에 스크롤이나 클릭이 필요한 문제, 상태는 렌더러가 지우는 쿠키나 저장소에 의존하는 문제, 타이밍은 느린 JavaScript 뒤에서 콘텐츠가 지연되는 문제입니다.

© Patrick Stox LLC · CC BY 4.0 ·

Google은 JavaScript 앱을 세 단계로 처리합니다. “Google processes JavaScript web apps in three main phases: 1. Crawling 2. Rendering 3. Indexing.” (번역) 「Google은 JavaScript 웹 앱을 세 가지 주요 단계, 즉 1. 크롤링 2. 렌더링 3. 색인으로 처리합니다.」 가운데 단계에서는 최신 헤드리스 Chrome에서 JS를 실행해 색인할 DOM을 만듭니다. “Rendering is important because websites often rely on JavaScript to bring content to the page, and without rendering Google might not see that content.” (번역) 「웹사이트는 페이지에 콘텐츠를 표시하기 위해 JavaScript에 의존하는 경우가 많고, 렌더링하지 않으면 Google이 그 콘텐츠를 보지 못할 수 있으므로 렌더링이 중요합니다.」

이 주장에 대한 근거 Google processes JavaScript web apps in three main phases: crawling, rendering, and indexing; without rendering, Google might not see JavaScript-provided content. 범위: Google Search's processing of JavaScript pages; successful rendering does not guarantee indexing or ranking. 신뢰도: 높음 · 검증일: Google Search Central: Understand the JavaScript SEO basics

따라서 Google은 JavaScript를 실행할 수 있습니다. 실용적인 질문은 더 구체적입니다.

  • 동등성 — 렌더링된 DOM에 예상한 내용이 실제로 들어 있는가?
  • 상호작용 — Google이 하지 않을 스크롤이나 클릭이 필요한 요소가 있는가?
  • 상태 — 상태 없는 렌더러가 지우는 쿠키나 localStorage에 의존하는가?
  • 타이밍 — 중요한 콘텐츠가 느리거나 늦게 실행되는 JavaScript 뒤로 밀려 있는가?

타이밍 측면에서 Google은 200을 반환한 크롤링 페이지를 렌더링 대기열에 넣으며, “the page may stay on this queue for a few seconds, but it can take longer than that.” (번역) 「페이지가 이 대기열에 몇 초 동안 머물 수 있지만 그보다 더 오래 걸릴 수도 있습니다.」 공개된 고정 지연 시간이나 제한 시간은 없습니다. 200이 아닌 상태를 반환하거나 처음부터 noindex 지시문이 있는 페이지는 JavaScript가 이를 바꾸기를 기다리지 않고 렌더링 대기열을 건너뛸 수 있습니다.

이 주장에 대한 근거 Google queues crawled pages with a 200 response for rendering; the page may stay in that queue for a few seconds but can take longer, with no published fixed delay or timeout, and non-200 or initial noindex responses may skip rendering. 범위: Google Search's documented render-queue behavior; not a guaranteed or universal timing figure. 신뢰도: 높음 · 검증일: Google Search Central: Understand the JavaScript SEO basics

Web Rendering Service, 무상태성, 캐싱, ‘두 번의 색인 단계’ 오해 등 Google의 렌더링 메커니즘은 렌더링 페이지에서 다룹니다. 여기서는 실무 문제와 해결책에 집중합니다.

링크는 실제 <a href> 앵커여야 합니다

가장 흔한 JS SEO 오류입니다. “Google can only discover your links if they are <a> HTML elements with an href attribute.” (번역) 「Google은 링크가 href 속성이 있는 <a> HTML 요소일 때만 그 링크를 발견할 수 있습니다.」 onclick 핸들러가 달린 클릭 가능한 <div>는 Google에게 링크로 보이지 않습니다. 따라서 Google이 따라가지 않으며, 그 링크에 발견을 의존하는 페이지는 크롤링되지 않을 수 있습니다. JavaScript로 링크를 삽입하는 것은 괜찮지만 렌더링된 DOM에서 실제 <a href> 앵커가 되어야 합니다. 다만 렌더링된 앵커는 JavaScript 실행 후 파싱됩니다. DOM에 나타나면 발견 가능하다는 뜻일 뿐, URL의 크롤링·색인이나 원시 HTML 링크와 동일한 취급을 보장하지는 않습니다.

이 주장에 대한 근거 Google can reliably discover links only when they are HTML anchor elements with an href attribute. 범위: Link discovery by Google Search; this does not claim that every discovered URL will be crawled or indexed. 신뢰도: 높음 · 검증일: Google Search Central: Make your links crawlable

지연 로드 및 상호작용이 필요한 콘텐츠

렌더러는 호기심 많은 사용자처럼 행동하지 않습니다. “Google Search does not interact with your page.” (번역) 「Google 검색은 페이지와 상호작용하지 않습니다.」 스크롤, 클릭, hover를 하지 않으므로 이런 이벤트 뒤에서만 불러오는 콘텐츠는 보이지 않습니다.

Google은 사용자 행동이 아니라 콘텐츠가 viewport에 들어올 때 불러오라고 안내합니다. “make sure that your lazy-loading implementation loads all relevant content whenever it is visible in the viewport,” (번역) 「지연 로딩 구현이 관련 콘텐츠가 viewport에 보일 때마다 모두 불러오도록 하세요.」 또한 “don’t add lazy-loading to content that is likely to be immediately visible when a user opens a page.” (번역) 「사용자가 페이지를 열자마자 보일 가능성이 높은 콘텐츠에는 지연 로딩을 추가하지 마세요.」 IntersectionObserver 또는 이미지의 네이티브 loading="lazy"를 사용하고, 스크롤이나 클릭 핸들러는 사용하지 않아야 정상적인 렌더링 과정에서 콘텐츠가 로드됩니다.

이 주장에 대한 근거 Google Search does not interact with a page, so lazy-loaded content should load when it becomes visible in the viewport rather than requiring user interaction. 범위: Google Search's documented rendering behavior for lazy-loaded content; other crawlers can behave differently. 신뢰도: 높음 · 검증일: Google Search Central: Fix lazy-loaded content

무한 스크롤: 두 페이지가 하나로 색인될 때

Google의 긴 렌더 viewport가 무한 스크롤 로더를 작동시켜 두 페이지를 하나의 색인 URL로 합칠 수 있습니다. 출처: /technical-seo/javascript-seo/

일반 브라우저 viewport는 첫 페이지 뒤에서 멈추지만 Google의 렌더 viewport는 훨씬 길게 확장됩니다. 실제 사용자 스크롤 없이 무한 스크롤 트리거에 닿아 로더를 실행하고 다음 페이지를 같은 DOM에 추가합니다. 그러면 Google이 두 페이지 콘텐츠를 한 URL로 색인합니다.

© Patrick Stox LLC · CC BY 4.0 ·

거의 아무도 설명하지 않지만 이 문제만으로도 한 절을 할애할 가치가 있습니다.

Googlebot은 일반적인 브라우저 창보다 훨씬 긴 viewport에서 렌더링할 수 있습니다. Google은 정확한 렌더 viewport 크기를 공개하지 않고 그 크기도 바뀔 수 있으므로 특정 수치를 전제로 설계하지 말고 자체 구현을 테스트해야 합니다. 핵심은 작동 방식입니다. 무한 스크롤 로더가 스크롤 위치나 viewport 높이를 기준으로 실행된다면 예상보다 긴 렌더 viewport가 렌더링 도중 로더를 작동시킬 수 있습니다. 그러면 다음 글이나 제품 페이지의 콘텐츠가 같은 DOM에 추가됩니다. 서로 다른 두 URL의 콘텐츠가 함께 렌더링되고 Google이 이를 한 페이지로 색인할 수 있습니다. 내 경험상 “occasionally, two pages get indexed as one” (번역) 「때때로 두 페이지가 하나로 색인됩니다.」 ‘색인되지 않음’으로 보고된 페이지가 실제로는 다른 페이지, 대개 피드의 이전 글 일부로 색인된 사례가 있었습니다. 이유는 “when Google resized the viewport to be longer … it triggered the infinite scroll and loaded another article in when it was rendering.” (번역) 「Google이 viewport를 더 길게 조정했을 때 … 무한 스크롤이 작동해 렌더링 도중 다른 글을 불러왔기 때문입니다.」 특정 크기를 가정하거나 안전하다고 단정하지 말고, URL Inspection의 렌더링된 HTML에서 예상 지점에서 끝나야 하는 페이지가 실제로 그렇게 끝나는지 확인하세요.

이를 제대로 처리하려면 두 단계가 필요합니다.

먼저 무한 스크롤을 검색 친화적으로 만드세요. 무한 스크롤 아래에 페이지네이션 방식의 로딩을 지원합니다. 각 묶음에는 “its own persistent, unique URL,” (번역) 「지속적으로 유지되는 고유 URL」이 있어야 하고, URL을 불러올 때마다 콘텐츠가 같아야 하며, ?date=yesterday 같은 상대 매개변수는 피해야 합니다. 또한 “link sequentially to the individual URLs so that search engines can discover the URLs in a paginated set,” (번역) 「검색엔진이 페이지로 나뉜 집합의 URL을 발견할 수 있도록 각 URL을 순서대로 링크」하고, 스크롤로 새 묶음을 불러올 때는 “update the displayed URL using the History API.” (번역) 「History API를 사용해 표시된 URL을 업데이트」해야 합니다. 실제 <a href> 페이지네이션 링크와 고유 URL을 사용하세요. Google은 fragment 식별자를 무시하므로 페이지 번호에 “don’t use URL fragment identifiers” (번역) 「URL fragment 식별자(# 뒤 부분)를 사용하지 마세요.」 내 JavaScript SEO 가이드에서 말했듯, “if you have an infinite scroll setup, I still recommend a paginated page version so that Google can still crawl properly.” (번역) 「무한 스크롤을 사용하더라도 Google이 계속 제대로 크롤링할 수 있게 페이지네이션 버전을 권장합니다.」

오류가 있는 로더가 실제로 페이지를 합치고 있다면 가장 빠른 해결책은 단순합니다. “block the JavaScript file that handles the infinite scrolling so the functionality can’t trigger.” (번역) 「기능이 작동하지 않도록 무한 스크롤을 처리하는 JavaScript 파일을 차단하세요.」 렌더링 중 로더가 실행되지 않으면 다음 페이지 콘텐츠를 추가할 수 없고 각 URL은 다시 자기 콘텐츠만 렌더링합니다.

클라이언트 측 라우팅 이후의 소프트 404

SPA는 HTTP 상태 코드를 바꾸지 않고 콘텐츠를 교체할 수 있으므로 ‘찾을 수 없음’ 화면도 200을 반환할 수 있습니다. Google은 반환된 콘텐츠를 평가한 뒤 이 응답을 소프트 404로 분류할 수 있지만 정적 fetch만으로는 Google이 실제로 그렇게 분류했음을 증명할 수 없습니다. 해결책은 두 가지입니다. History API로 이동하고, 실제로 찾을 수 없는 상태라면 진짜 404를 반환하는 URL로 보내거나 noindex 태그를 추가합니다. 라우팅을 URL fragment에 의존하지 마세요. “the AJAX-crawling scheme has been deprecated since 2015, so you can’t rely on URL fragments to work with Googlebot.” (번역) 「AJAX 크롤링 방식은 2015년에 폐기되었으므로 URL fragment가 Googlebot에서 작동할 것이라고 기대할 수 없습니다.」

클라이언트 측 redirect에도 같은 증거 문제가 있습니다. JavaScript가 실행되기 전 초기 응답은 200으로 남을 수 있습니다. Google은 서버 측 또는 meta refresh redirect가 불가능할 때만 JavaScript redirect를 대안으로 지원합니다. 감사에서는 정적 상태와 관찰한 렌더링 후 이동을 별도로 보고하세요. HTTP 상태를 다시 쓰거나 렌더링 중 URL 변화 모두를 redirect라고 부르면 안 됩니다.

robots.txt에서 JavaScript 또는 CSS를 차단하지 마세요

Google은 차단된 파일이나 차단된 페이지의 JavaScript를 렌더링하지 않습니다. bundle이나 그 파일이 있는 /_next/, /static/, /assets/ 디렉터리를 robots.txt에서 차단하면 렌더링 전체가 깨질 수 있습니다. Google은 shell만 가져오고 스크립트를 실행하지 못해 빈 페이지를 색인하게 됩니다. URL Inspection의 페이지 리소스에서 차단된 항목을 확인하세요.

DOM 동등성과 처리 단계별 robots 지시문

원시 HTML(페이지 소스 보기)과 렌더링된 HTML(URL Inspection)을 비교합니다. 렌더링된 HTML에만 있는 콘텐츠도 렌더링에 성공하면 색인될 수 있습니다. 둘 다에 없으면 Google에도 존재하지 않습니다.

Google이 직접 문서화한 처리 순서 위험이 하나 있습니다. “When Google encounters the noindex tag, it may skip rendering and JavaScript execution, which means using JavaScript to change or remove the robots meta tag from noindex may not work as expected.” (번역) 「Google이 noindex 태그를 발견하면 렌더링과 JavaScript 실행을 건너뛸 수 있습니다. 따라서 JavaScript로 robots meta 태그의 noindex를 변경하거나 제거하는 방식은 예상대로 작동하지 않을 수 있습니다.」 원시 HTML이 처음부터 noindex를 내보내면 Google은 그 지시문에 따라 처리하고 이를 제거할 스크립트를 아예 실행하지 않을 수 있습니다.

이를 ‘항상 렌더링 버전이 우선한다’는 규칙으로 일반화하면 안 됩니다. 조정 방식은 필드마다 다릅니다.

필드원시/렌더링 비교로 확인할 수 있는 것
주요 콘텐츠와 링크렌더링에 성공하면 Google은 렌더링 과정에서 생성된 콘텐츠와 실제 <a href> 링크를 사용할 수 있습니다. 원시 HTML에 미리 있으면 렌더링 의존도가 낮아집니다.
제목과 설명Google은 JavaScript로 설정한 metadata를 처리할 수 있지만 제목 링크와 snippet은 여러 소스에서 선택됩니다. 두 상태를 모두 제시하고 렌더링된 값, 첫 값 또는 마지막 값이 보장된다고 주장하지 마세요.
Robots 지시문원시 noindex 때문에 Google이 렌더링을 건너뛰면 JavaScript로 제거한 결과를 보지 못할 수 있습니다. 나중에 제한을 추가했다고 해서 이전 제한이 취소되었다는 증거가 되지는 않습니다.
CanonicalGoogle의 JavaScript 안내는 소스에 한 값을 설정한 뒤 JavaScript로 바꾸지 말라고 합니다. 한 가지 방법만 쓰고 렌더링된 head에 선언이 하나인지 확인하세요.
HTTP 상태와 redirectJavaScript는 이미 받은 응답 상태를 바꿀 수 없습니다. 정적 상태와 관찰된 렌더링 후 이동을 별개의 사실로 기록하세요.

따라서 감사자는 source, rendered, response header, 관찰된 검색 상태를 하나의 ‘유효 값’으로 합치지 말고 서로 구분해야 합니다.

콘텐츠를 DOM에 넣는 렌더링 모드를 선택하세요

JS SEO 위험의 대부분은 HTML을 어떻게 만드는지에서 생깁니다. SSR, 정적/사전 렌더링, hydration은 콘텐츠를 DOM에 넣거나 빠르게 추가하므로 검색 노출이 렌더러 성공에 의존하는 정도를 줄입니다. 완전한 클라이언트 측 렌더링은 매번 제시간에 올바르게 렌더링되는지에 더 크게 의존합니다. Dynamic rendering은 동급 선택지가 아니라 우회책입니다. 2025년 12월에 마지막으로 업데이트된 Google 안내에서도 장기 해법이 아닌 우회책으로 설명하며, 대신 서버 측 렌더링, 정적 렌더링 또는 hydration을 권장합니다. 내 JavaScript SEO 가이드 표현으로는 “any kind of SSR, static rendering, and prerendering setup is going to be fine for search engines.” (번역) 「어떤 형태든 SSR, 정적 렌더링, 사전 렌더링 구성은 검색엔진에 적합합니다.」 CSR, SSR, SSG, hydration, ISR, edge, streaming, dynamic rendering의 전체 비교표는 렌더링 페이지에 있습니다. Google은 별도로 서버 측 렌더링이나 사전 렌더링이 사용자와 크롤러에게 좋은 방법이라고 설명합니다. 이 주장에 대한 근거 Google describes server-side rendering or pre-rendering as a good idea because it makes a website faster for users and crawlers. 범위: Google Search guidance for JavaScript sites; the source does not prescribe one framework or guarantee indexing. 신뢰도: 높음 · 검증일: Google Search Central: Understand the JavaScript SEO basics

테스트 방법

Search Console의 URL Inspection이 기준입니다. 실시간 테스트를 실행한 뒤 렌더링된 HTML, 스크린샷, 페이지 리소스/콘솔 메시지를 확인해 무엇을 불러왔고 무엇이 실패했는지 살펴보세요. Rich Results Test로 렌더링된 HTML을 빠르게 확인할 수도 있습니다. 대규모 사이트에서는 JavaScript를 실행하는 크롤러(Ahrefs Site Audit, JavaScript 렌더링 모드의 Screaming Frog)를 사용해 사이트 전체의 원시 HTML과 렌더링 결과를 비교합니다.

렌더링 차이는 점수가 아니라 진단 자료입니다. JavaScript 실행 후에만 나타나거나 렌더링 뒤 사라지는 핵심 요소를 조사하세요.

예시 페이지에서 title은 원시 HTML과 렌더링된 HTML에 각각 1개, heading은 원시 18개와 렌더링 후 19개, 내부 링크는 원시 42개와 렌더링 후 71개, 제품 설명은 원시 0개와 렌더링 후 12개, canonical 태그는 원시 6개와 렌더링 후 1개입니다. 이 수치는 설명을 위한 가상 데이터입니다.

JavaScript는 SEO에 나쁘거나 악한 것이 아닙니다. 많은 SEO 실무자에게 익숙한 방식과 다를 뿐입니다. 개발자와 협업하고 중요한 콘텐츠를 DOM에 넣은 다음, 추측이 아니라 Google 도구로 실제 렌더링 결과를 판단하세요.

다음 단계: JavaScript SEO 클러스터

이 허브는 전체 지도입니다. 아래 각 주제에는 별도의 심층 가이드가 있습니다.

렌더링과 아키텍처

  • 헤드리스 CMS SEO — 분리된 frontend가 크롤링, 렌더링, metadata, sitemap, canonical 태그에 미치는 영향, 렌더링 모드 선택, 헤드리스 환경의 고유 실패 유형을 설명합니다.

프레임워크별 가이드

  • React SEO — CSR 우선 React의 색인 위험, Google의 React 앱 렌더링, React Router와 History API, meta 태그용 react-helmet-async, Next.js를 선택할 시점.
  • Angular SEO — Angular의 SPA 기본값, @angular/ssr(Angular Universal 후속), 내장 Title 및 Meta 서비스, 사전 렌더링, 최신 Angular의 incremental hydration.
  • Next.js SEO — Pages Router와 App Router, Metadata API, next/image와 CWV, ISR 타이밍과 Googlebot, sitemap, 흔한 Next.js SEO 실수.
  • Nuxt SEO — 기본 SSR, useSeoMeta(), Nuxt 렌더링 모드, @nuxtjs/seo 모듈 생태계, 색인 가능성 측면에서 일반 Vue와의 비교.
  • Vue SEO — Vue 3의 CSR 기본값과 크롤러에 미치는 영향, createWebHistory(), @unhead/vue, meta-framework 없이 쓸 수 있는 사전 렌더링, Nuxt가 적합한 시점.
  • Svelte SEO — Svelte와 SvelteKit, SvelteKit의 기본 SSR, <svelte:head>, adapter-static + ssr: false 함정, adapter 선택, AI 크롤러에 미치는 영향.
  • Astro SEO — 기본 zero-JS, islands architecture, @astrojs/sitemap, astro:assets, View Transitions와 History API, Server Islands fallback 동작, Astro의 Core Web Vitals 장점.

전문가 메모 추가

전문가 인용문 고정

새로운 사람인가요? 미등록 프로필을 다음 위치에서 /admin/experts/ → 전문가 인용문 고정 먼저 만드세요.