애드센스BlogSEO

애드센스 심사를 올리기 전에 내 사이트에서 찾아낸 것들

2026.08.03

애드센스 심사 전에 사이트를 항목별로 점검하는 체크리스트 일러스트

애드센스 심사를 올리려고 사이트를 처음부터 끝까지 훑었다. 글 수도 충분하고 정책 페이지도 있으니 그냥 신청하면 되겠다고 생각했는데, 훑어보니 네 가지가 걸렸다. 그중 하나는 몇 달째 배포돼 있던 버그였다.

1. 동작하지 않는 광고 태그가 글 45편에 실려 있었다

라이브 사이트의 블로그 글 HTML을 그대로 받아서 광고 태그를 찾아봤다.

curl -s https://joowonkoh.com/blog/dev/2026040204 | grep -o 'data-ad-client="[^"]*"'

돌아온 값이 이랬다.

data-ad-client="ca-pub-XXXXXXXXXXXXXXXX"

XXXXXXXXXXXXXXXX. 컴포넌트를 만들 때 자리만 잡아두려고 넣은 값인데, 그대로 배포돼서 색인된 글 45편 전부에 실려 나가고 있었다. 슬롯 ID도 마찬가지로 1234567890이었다.

동작하지 않는 광고 태그는 광고를 안 띄우는 것보다 나쁘다. 빈 <ins> 껍데기가 글마다 남고, 심사자가 그 자리를 보면 깨진 광고 영역으로 읽힌다. 애드센스가 "사이트가 준비되지 않았다"고 판단할 근거를 스스로 만들어 준 셈이다.

원인은 단순했다. 컴포넌트에 기본값을 박아 두고, 나중에 채운다고 생각하고 배포한 것이다. 문제는 "나중에"가 오지 않아도 아무 에러가 나지 않는다는 점이다. 빌드도 통과하고 페이지도 잘 뜨니까 잊어버린다.

2. 광고 단위 ID는 승인 후에만 만들 수 있다

고치려고 대시보드에 들어가서 알게 된 게 이거다. 애드센스에서 다루는 ID가 두 종류인데 성격이 완전히 다르다.

애드센스에서 쓰는 두 가지 ID
퍼블리셔 ID
광고 단위 ID
형태
ca-pub-으로 시작하는 16자리
10자리 숫자
언제 받나
가입하면 바로
승인된 뒤에 직접 만들어야
어디에 쓰나
head 스크립트, ads.txt
각 광고 자리의 data-ad-slot
심사에 필요한가
필요하다
필요 없다

승인 전에는 슬롯 ID를 채워 넣을 방법이 아예 없다. 광고 단위를 만드는 메뉴 자체가 승인될 때까지 잠겨 있다. 심사에 필요한 건 <head>에 들어가는 스크립트 한 줄뿐이다.

그래서 결론은 "심사 기간에는 광고 자리를 아예 그리지 않는다"였다. 값이 없으면 안 그리게 만드는 쪽으로 갔다.

// src/lib/adsense.ts
export const ADSENSE_CLIENT = "ca-pub-…";           // ads.txt와 같은 값
export const ADSENSE_SLOT = process.env.NEXT_PUBLIC_ADSENSE_SLOT || null;
export default function AdSense() {
  // 슬롯이 없으면 아무것도 그리지 않는다
  if (!ADSENSE_SLOT) return null;
  return <ins className="adsbygoogle" data-ad-client={ADSENSE_CLIENT} data-ad-slot={ADSENSE_SLOT}  />;
}

이렇게 두면 승인 뒤에 할 일이 환경변수 한 줄 추가로 끝난다. 무엇보다 값을 잊어도 가짜 태그가 나가지 않는다. 자리를 잡아두는 기본값이 왜 위험한지 이번에 배웠다. 기본값은 "없음"이어야 하고, 없으면 조용히 실패하는 게 아니라 아무것도 하지 않아야 한다.

3. 캔버스 페이지의 본문이 350자였다

내 사이트에는 플레이그라운드라는 코너가 있고, 웹 미니게임이 아홉 개 들어 있다. 문제는 이 페이지들이 캔버스 하나로만 되어 있다는 것이다. 페이지에 실제로 들어 있는 텍스트를 세 봤다.

curl -s https://joowonkoh.com/playground/ddong-mallang \
  | python3 -c "
import sys, re
h = sys.stdin.read()
h = re.sub(r'<script.*?</script>', '', h, flags=re.S)
print(len(re.sub(r'\s+', '', re.sub(r'<[^>]+>', ' ', h))))"

355자. 헤더와 푸터를 빼면 사실상 제목뿐이다. 사람이 봐도 이게 뭘 하는 건지, 어떻게 노는 건지 알 수 없다. 검색엔진 입장에서는 더 그렇다.

애드센스 반려 사유에서 자주 보이는 "가치 없는 콘텐츠"가 이런 페이지를 말한다. 아홉 개면 사이트맵 70개 중 9개라 비율도 무시할 수 없었다.

4. 도움말을 붙이는 방법이 세 가지인데, 두 개는 틀렸다

각 게임에 "무엇인지 / 어떻게 노는지 / 어떻게 만들었는지"를 적은 도움말을 붙이기로 했다. 여기서 구현 방식이 갈렸다.

도움말을 접어두는 세 가지 방법
1
useState로 여닫기닫힌 동안에는 문서에 아무것도 없다. 얇은 페이지 문제가 그대로 남는다
2
hidden으로 숨기기텍스트는 HTML에 있지만 사용자에게 안 보인다. 숨긴 텍스트로 취급될 수 있어 더 위험하다
3
details로 접기접혀 있어도 내용이 HTML에 그대로 있고, 누르면 정말 열린다. 둘 중 어느 문제도 없다

1번은 흔한 선택인데 목적을 못 이룬다. 리액트 상태로 조건부 렌더링하면 닫혀 있는 동안 그 텍스트는 문서에 존재하지 않는다. 도움말을 붙였는데 페이지는 여전히 350자다.

2번은 더 나쁘다. 사용자에게 안 보이는 텍스트를 크롤러에게만 먹이는 형태가 되어, 검색엔진 쪽에서 숨긴 텍스트로 볼 여지가 있다. 얻으려던 것보다 잃을 게 크다.

그래서 <details>를 썼다.

<details className="absolute top-3 right-3">
  <summary>도움말</summary>
  <div>…무엇인지, 어떻게 노는지, 어떻게 만들었는지…</div>
</details>

접힘 영역은 오래된 표준 패턴이고, 검색엔진도 접힌 내용을 정상적으로 색인한다. 자바스크립트가 없어도 열리고, 키보드와 스크린리더 지원이 공짜로 따라온다. 리액트 상태를 하나도 안 늘리고 끝났다.

결과로 페이지당 본문이 350자에서 480~790자가 됐다. 여전히 글이 많은 페이지는 아니지만, 이제 처음 들어온 사람이 뭘 하는 건지 읽을 수 있다. 그게 원래 목적이었고 색인은 부수 효과다.

5. 내비에서 감춘 페이지가 검색에는 열려 있었다

마지막으로 하나 더 찾았다. 아직 다듬는 중이라 내비게이션 목록에서 빼둔 페이지가 여럿 있었는데, 그 페이지들이 사이트맵에는 그대로 들어 있었다.

목록에 없으면서 색인은 되는 페이지는 고아 페이지가 된다. 검색으로 들어온 사람은 그 페이지만 보고, 사이트 안에서 다시 찾아갈 길이 없다. 링크 하나 없이 떠 있는 페이지가 여섯 개였다.

감추기로 마음먹었다면 세 곳을 다 막아야 한다는 걸 이번에 정리했다.

페이지를 감출 때 막아야 하는 세 곳
막는 곳
효과
안 막으면
목록·내비게이션
사이트 안에서 안 보인다
그냥 노출된다
metadata robots noindex
검색 결과에 안 뜬다
주소를 알아내면 색인된다
sitemap.xml 제외
주소 자체를 안 알려준다
사이트맵으로 직접 알려주게 된다

목록에서만 빼고 나머지를 안 막으면, 감췄다고 생각하는 페이지를 스스로 사이트맵에 적어서 제출하고 있는 셈이다.

정리하며

네 가지를 고치고 나서 다시 확인했다. 빌드 결과물에서 data-ad-client가 전부 사라졌고, 게임 페이지에는 읽을 게 생겼고, 감춘 페이지는 사이트맵에서 빠졌다.

돌아보면 세 개는 같은 실수였다. "나중에 채운다"고 두고 배포한 값, "일단 목록에서만 빼자"고 미룬 처리, "도움말은 나중에 쓰자"고 넘긴 설명. 전부 에러를 내지 않아서 잊혔다. 빌드가 통과하는 미완성은 완성으로 착각하기 쉽다.

심사에 통과하든 아니든 사이트는 이 편이 낫다. 광고 태그를 정리한 건 광고 때문이 아니고, 도움말을 붙인 것도 색인 때문이 아니다. 라이브에 나가 있는 걸 실제로 열어서 확인해 본 게 이번에 얻은 것 중 제일 크다.

같이 읽으면 좋은 글