# 네이버 서치어드바이저 소유확인과 GA4 연결, 정적 블로그에서 하는 법

> 네이버 서치어드바이저 소유확인 두 가지 방식의 차이, Cloudflare Pages에서 HTML 파일 방식이 막히는 이유, Astro 블로그에 GA4 gtag를 설치하고 수집까지 검증하는 과정을 이 블로그에 직접 적용하며 정리했습니다.

- 발행: 2026-08-01
- 주제: GEO · AI 검색 최적화
- 발행처: 매드업 마케팅사업본부 AI 마케팅 개발실
- 웹 원문: https://madobi.madup.com/blog/naver-search-advisor-ga4-astro/

## 세 줄 요약

- 네이버 서치어드바이저 소유확인은 HTML 파일 업로드와 메타 태그 두 방식이 있는데, Cloudflare Pages처럼 .html 요청을 308 리다이렉트로 바꾸는 호스팅에서는 메타 태그 방식이 확실하다.
- Astro 컴포넌트 안의 script는 기본적으로 번들러가 가져가 처리하므로, GA4 gtag 스니펫은 is:inline 지시자를 붙여 원문 그대로 출력해야 정상 동작한다.
- 설치가 끝났다는 판단은 페이지 소스가 아니라 수집 요청으로 한다. 브라우저에서 google-analytics.com/g/collect 요청에 자신의 측정 ID(tid)와 page_view 이벤트가 실리는지 확인하면 된다.

---
블로그를 열고 한 달쯤 지나 구글 서치콘솔은 붙어 있는데, 네이버에서는 검색이 안 되고
방문자가 몇 명인지도 모르는 상태라는 걸 알았습니다. 이 블로그 이야기입니다. 그래서
네이버 서치어드바이저 소유확인과 GA4 설치를 직접 진행했고, 그 과정에서 함정을 두 개
밟았습니다. 하나는 호스팅의 리다이렉트가 네이버 확인 파일을 가로채는 문제, 하나는
Astro가 분석 스크립트를 번들러로 가져가 버리는 문제입니다. 같은 조합(정적 사이트
생성기 + Cloudflare Pages)을 쓰는 분들이 그대로 따라 할 수 있게 순서대로 정리합니다.

## 네이버 서치어드바이저 소유확인 방법 두 가지

서치어드바이저(searchadvisor.naver.com)에 사이트를 등록하면 소유확인부터 요구합니다.
방법은 두 가지입니다. 네이버가 발급한 HTML 확인 파일을 사이트 루트에 업로드하거나,
발급된 메타 태그를 홈페이지 head에 넣거나. 네이버 화면에는 HTML 파일 업로드 방식을
권장한다고 안내돼 있습니다.

HTML 파일 방식은 `naver`로 시작하는 확인 파일을 내려받아
`https://내도메인/naver확인코드.html` 주소로 접근되게 올리는 방식입니다. 메타 태그
방식은 아래 한 줄을 head에 넣습니다.

```html
<meta name="naver-site-verification" content="발급받은 확인 코드" />
```

구글 서치콘솔의 `google-site-verification` 메타 태그와 같은 원리라서, 서치콘솔을
메타 태그로 통과해 봤다면 낯설지 않습니다. 어느 쪽이든 적용하고 배포까지 마친 뒤
서치어드바이저의 소유확인 버튼을 눌러야 판정이 납니다.

<figure style="margin:1.75rem 0;overflow-x:auto;">
<svg viewBox="0 0 720 260" role="img" aria-label="네이버 소유확인 두 방식 비교. 메타 태그 방식: 홈페이지 head에 한 줄 추가, 배포 후 200 OK로 바로 판정, 리다이렉트 영향 없음. HTML 파일 방식: 확인 파일을 루트에 업로드, Cloudflare Pages에서는 .html 요청이 308 리다이렉트로 바뀌어 검증봇이 따라가지 못하면 실패." style="width:100%;min-width:600px;height:auto;font-family:inherit;">
  <g>
    <rect x="6" y="8" width="348" height="238" rx="12" fill="none" stroke="#3e4a78" stroke-width="1.5"/>
    <text x="24" y="38" fill="#2a2d3e" font-size="15" font-weight="700">메타 태그 방식</text>
    <rect x="24" y="56" width="300" height="40" rx="8" fill="#3e4a78" opacity="0.08"/>
    <text x="38" y="80" fill="#2a2d3e" font-size="12">홈 head에 naver-site-verification 한 줄</text>
    <text x="170" y="118" fill="#6b6e7b" font-size="14">↓ 배포</text>
    <rect x="24" y="132" width="300" height="40" rx="8" fill="#3e4a78" opacity="0.08"/>
    <text x="38" y="156" fill="#2a2d3e" font-size="12">홈페이지 요청 → HTTP 200 + 태그 노출</text>
    <text x="170" y="194" fill="#6b6e7b" font-size="14">↓</text>
    <text x="24" y="226" fill="#3e4a78" font-size="13" font-weight="700">소유확인 통과 — 리다이렉트 무관</text>
  </g>
  <g>
    <rect x="366" y="8" width="348" height="238" rx="12" fill="none" stroke="#c7303b" stroke-width="1.5"/>
    <text x="384" y="38" fill="#2a2d3e" font-size="15" font-weight="700">HTML 파일 방식 (Pages 함정)</text>
    <rect x="384" y="56" width="300" height="40" rx="8" fill="#c7303b" opacity="0.07"/>
    <text x="398" y="80" fill="#2a2d3e" font-size="12">naver확인코드.html 루트 업로드</text>
    <text x="530" y="118" fill="#6b6e7b" font-size="14">↓ 봇이 .html 요청</text>
    <rect x="384" y="132" width="300" height="40" rx="8" fill="#c7303b" opacity="0.07"/>
    <text x="398" y="156" fill="#c7303b" font-size="12" font-weight="700">308 리다이렉트 → /naver확인코드</text>
    <text x="530" y="194" fill="#6b6e7b" font-size="14">↓ 안 따라가면</text>
    <text x="384" y="226" fill="#c7303b" font-size="13" font-weight="700">소유확인 실패 위험</text>
  </g>
</svg>
<figcaption>같은 확인 코드라도 도달 경로가 다릅니다. 리다이렉트가 낀 호스팅에서는 메타 태그가 안전합니다.</figcaption>
</figure>

## Cloudflare Pages에서 HTML 파일 방식이 막히는 이유

이 블로그는 Cloudflare Pages에서 서빙됩니다. 확인 파일을 루트에 올리고 해당 주소를
호출해 보니 파일 내용 대신 HTTP 308 응답이 돌아왔습니다. Pages가 주소를 깔끔하게
유지하려고 `/파일명.html` 요청을 확장자 없는 `/파일명`으로 리다이렉트하기
때문입니다. 리다이렉트를 따라가면 내용은 정상적으로 나옵니다. 문제는 검증봇이
따라가 줄지를 사이트 쪽에서 통제할 수 없다는 점입니다.

네이버가 확인하는 주소는 `.html`이 붙은 원래 주소입니다. 봇이 308을 따라가면
통과하고, 안 따라가면 실패합니다. 결과를 운에 맡기는 셈이라, 저는 확인 파일을
올려둔 채로 판정은 메타 태그 방식으로 받는 쪽을 택했습니다. 메타 태그는 홈페이지
자체에 붙어 있어 리다이렉트가 개입할 틈이 없습니다.

정적 사이트 생성기의 공통 레이아웃에 넣으면 모든 페이지에 함께 들어갑니다. Astro라면
이런 모양입니다.

```astro
<!-- src/layouts/Base.astro의 head 안 -->
<meta name="google-site-verification" content="서치콘솔 확인 코드" />
<meta name="naver-site-verification" content="서치어드바이저 확인 코드" />
```

<img src="/madobi/search.png" alt="돋보기를 든 매도비" width="120" height="120" class="sticker" style="display:block;margin:2rem auto 0;" />

## Astro에 GA4 gtag 설치하기, is:inline이 필요한 이유

GA4 쪽은 관리 화면에서 웹 스트림을 만들면 측정 ID(G-로 시작)와 함께 gtag.js
스니펫을 발급해 줍니다. 이 스니펫을 그대로 붙이면 되는데, Astro에서는 한 가지를
더 챙겨야 합니다. Astro는 컴포넌트 안의 `<script>`를 기본적으로 자기 번들 시스템으로
가져가 모듈로 처리합니다. 최적화에는 좋은 동작이지만, 발급받은 스니펫이 원문 그대로
출력된다는 보장이 사라집니다. 외부 스크립트 로더와 전역 함수 선언에 기대는 gtag
같은 코드는 `is:inline` 지시자를 붙여 손대지 말라고 알려줘야 합니다.

```astro
<!-- Google tag (gtag.js) -->
<script is:inline async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script is:inline>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX');
</script>
```

<figure style="margin:1.75rem 0;overflow-x:auto;">
<svg viewBox="0 0 720 250" role="img" aria-label="Astro script 처리 비교. is:inline 없이 넣으면 번들러가 스크립트를 모듈로 가져가 처리해 스니펫 원형이 깨질 수 있고 gtag 수집이 실패할 수 있다. is:inline을 붙이면 스니펫이 head에 원문 그대로 출력되어 로더와 전역 dataLayer가 정상 동작한다." style="width:100%;min-width:600px;height:auto;font-family:inherit;">
  <g>
    <rect x="6" y="8" width="348" height="228" rx="12" fill="none" stroke="#c7303b" stroke-width="1.5"/>
    <text x="24" y="38" fill="#2a2d3e" font-size="15" font-weight="700">&lt;script&gt; 만 넣으면</text>
    <rect x="24" y="58" width="300" height="38" rx="8" fill="#c7303b" opacity="0.07"/>
    <text x="38" y="82" fill="#2a2d3e" font-size="12">번들러가 모듈로 가져가 가공</text>
    <text x="170" y="120" fill="#6b6e7b" font-size="14">↓</text>
    <rect x="24" y="134" width="300" height="38" rx="8" fill="#c7303b" opacity="0.07"/>
    <text x="38" y="158" fill="#2a2d3e" font-size="12">스니펫 원형 보장 안 됨</text>
    <text x="24" y="212" fill="#c7303b" font-size="13" font-weight="700">수집 실패 가능</text>
  </g>
  <g>
    <rect x="366" y="8" width="348" height="228" rx="12" fill="none" stroke="#3e4a78" stroke-width="1.5"/>
    <text x="384" y="38" fill="#2a2d3e" font-size="15" font-weight="700">&lt;script is:inline&gt;</text>
    <rect x="384" y="58" width="300" height="38" rx="8" fill="#3e4a78" opacity="0.08"/>
    <text x="398" y="82" fill="#2a2d3e" font-size="12">head에 원문 그대로 출력</text>
    <text x="530" y="120" fill="#6b6e7b" font-size="14">↓</text>
    <rect x="384" y="134" width="300" height="38" rx="8" fill="#3e4a78" opacity="0.08"/>
    <text x="398" y="158" fill="#2a2d3e" font-size="12">로더 로드 + 전역 dataLayer 생성</text>
    <text x="384" y="212" fill="#3e4a78" font-size="13" font-weight="700">page_view 수집 시작</text>
  </g>
</svg>
<figcaption>발급 스니펫을 붙일 때는 is:inline 두 글자가 성패를 가릅니다.</figcaption>
</figure>

정적 사이트라는 점도 잊기 쉬운 부분입니다. 태그는 빌드 시점에 HTML로 구워지므로,
레이아웃에 태그를 넣었어도 다시 빌드해서 배포해야 실제 사이트에 반영됩니다. 소스는
고쳤는데 소유확인이 계속 실패한다면 배포가 안 나간 경우가 흔합니다.

## GA4 수집을 확인하는 방법, 소스 보기로는 모자란 이유

페이지 소스에 스니펫이 보이는 것과 수집이 되는 것은 다른 이야기입니다. 스니펫이
있어도 로더가 차단되거나 측정 ID가 틀리면 데이터는 쌓이지 않습니다. 판정 기준은
수집 요청 자체로 잡는 게 정확합니다.

브라우저 개발자 도구의 네트워크 탭을 열고 사이트에 접속해 두 가지를 확인합니다.
`googletagmanager.com/gtag/js?id=G-...` 로더가 200으로 로드되는지, 그리고
`google-analytics.com/g/collect` 요청이 나가면서 파라미터에 자신의 측정 ID(`tid`)와
`page_view` 이벤트가 실리는지. 이 블로그에서는 배포 직후 헤드리스 브라우저로 접속해
`tid=G-...`가 담긴 collect 요청이 나가는 것까지 보고 설치 완료로 판정했습니다.
GA4 관리 화면의 실시간 보고서에서 접속자가 잡히는 것으로 이중 확인하면 됩니다.

<figure style="margin:1.75rem 0;overflow-x:auto;">
<svg viewBox="0 0 720 150" role="img" aria-label="GA4 수집 검증 흐름. 1단계 사이트 접속, 2단계 gtag/js 로더가 200으로 로드, 3단계 google-analytics.com/g/collect 요청에 tid 측정 ID와 page_view 이벤트 확인, 4단계 GA4 실시간 보고서에서 접속 확인." style="width:100%;min-width:600px;height:auto;font-family:inherit;">
  <rect x="6" y="40" width="158" height="64" rx="10" fill="#3e4a78" opacity="0.08"/>
  <text x="22" y="66" fill="#2a2d3e" font-size="12.5" font-weight="700">① 사이트 접속</text>
  <text x="22" y="86" fill="#6b6e7b" font-size="11">네트워크 탭 열고</text>
  <text x="172" y="78" fill="#6b6e7b" font-size="14">→</text>
  <rect x="190" y="40" width="158" height="64" rx="10" fill="#3e4a78" opacity="0.08"/>
  <text x="206" y="66" fill="#2a2d3e" font-size="12.5" font-weight="700">② gtag/js 로드</text>
  <text x="206" y="86" fill="#6b6e7b" font-size="11">HTTP 200 확인</text>
  <text x="356" y="78" fill="#6b6e7b" font-size="14">→</text>
  <rect x="374" y="40" width="170" height="64" rx="10" fill="#f5be4b" opacity="0.22"/>
  <text x="390" y="66" fill="#2a2d3e" font-size="12.5" font-weight="700">③ /g/collect 요청</text>
  <text x="390" y="86" fill="#6b6e7b" font-size="11">tid=G-... + page_view</text>
  <text x="552" y="78" fill="#6b6e7b" font-size="14">→</text>
  <rect x="570" y="40" width="144" height="64" rx="10" fill="#3e4a78" opacity="0.08"/>
  <text x="586" y="66" fill="#2a2d3e" font-size="12.5" font-weight="700">④ 실시간 보고서</text>
  <text x="586" y="86" fill="#6b6e7b" font-size="11">접속자 잡히면 완료</text>
</svg>
<figcaption>③이 핵심입니다. collect 요청이 나가야 데이터가 쌓입니다.</figcaption>
</figure>

<img src="/madobi/chart.png" alt="차트를 확인하는 매도비" width="120" height="120" class="sticker tilt-l" style="display:block;margin:2rem auto 0;" />

## 소유확인 다음, 사이트맵과 RSS 제출

소유확인이 통과되면 서치어드바이저의 요청 메뉴가 열립니다. 여기서 사이트맵과 RSS를
제출합니다. 사이트맵은 사이트 전체 구조를 알려주고, RSS는 새 글을 알려줍니다.
네이버는 RSS 쪽 반영이 빠른 편이라 블로그라면 둘 다 넣는 쪽이 좋습니다.

Astro는 공식 사이트맵 통합(@astrojs/sitemap)을 켜두면 빌드마다
`/sitemap-index.xml`을 자동 생성합니다. robots.txt에도 사이트맵 주소를 선언해 두면
서치어드바이저에 제출하기 전에도 크롤러가 스스로 찾을 수 있습니다.

```txt
# robots.txt
Sitemap: https://내도메인/sitemap-index.xml
```

이 블로그 기준으로 제출한 값은 사이트맵 `sitemap-index.xml`, RSS `rss.xml` 두
개였습니다. 제출 후 반영까지는 시간이 걸리므로, 수집 현황은 서치어드바이저의 리포트
메뉴에서 며칠 두고 확인하면 됩니다.

## 순서 요약

같은 구성을 만들려면 이 순서입니다. 서치어드바이저에 사이트 등록, 메타 태그 발급받아
공통 레이아웃 head에 추가, GA4 웹 스트림 만들어 gtag 스니펫을 is:inline으로 추가,
빌드와 배포, 소유확인 버튼 클릭, collect 요청으로 GA4 수집 검증, 사이트맵과 RSS
제출. 함정은 두 곳입니다. 호스팅이 `.html`을 리다이렉트하면 소유확인은 메타 태그로,
Astro에서 분석 스크립트는 반드시 is:inline으로. 🐹