애드센스 사이트 목록에서 상태 칸이 '준비됨'으로 바뀌어 있었다. 옆의 Ads.txt 칸도 '승인됨'이었다.
심사를 기다리는 동안은 승인만 나면 나머지는 스위치를 켜는 일이라고 생각했다. 1편에서 수익을 보는 화면을 만들었고, 2편에서 광고 단위 이름만 규칙대로 지으면 위치마다 토글 하나로 켜지게 해 두었다. 준비는 끝나 있었다.
그런데 관리자 화면의 토글 여섯 개와 애드센스 콘솔의 자동 광고 스위치들을 나란히 띄워 놓고 보니, 질문이 바뀌어 있었다. 무엇을 켤지가 아니라 무엇을 끌지였다.
승인 직후 블로그에 나가던 것
승인이 났을 때 공개 페이지에 나가는 광고 관련 코드는 <head>의 스크립트 한 줄뿐이었다. 위치별 광고 단위는 아직 하나도 켜지 않았으니, 본문 어디에도 <ins class="adsbygoogle">는 없었다. 이 상태에서 화면에 광고가 뜬다면 그건 전부 애드센스의 자동 광고가 스스로 자리를 골라 넣은 것이다.
스크립트를 넣을지는 레이아웃에서 한 번 정한다.
@if (AdSense.ShouldLoadScript && !meta.SuppressAds)
{
@* 구글 애드센스 (공개 영역 전용 — 관리자 레이아웃에는 넣지 않음) *@
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=@AdSense.ClientId" crossorigin="anonymous"></script>
}
ShouldLoadScript는 게시자 ID가 설정되어 있고, 관리자 화면의 '애드센스 스크립트 · 자동광고' 토글이 켜져 있거나 위치별 광고가 하나라도 켜져 있을 때 참이 된다. 심사 기간에는 이 토글 하나로 스크립트만 내보냈다. 심사에도, 자동 광고에도 이 스크립트가 필요하기 때문이다.
조건의 뒷부분 !meta.SuppressAds는 광고를 내지 않을 페이지를 거른다. 여기에 걸리는 페이지는 세 종류다. 없는 주소로 들어와 보는 404 화면, 410·400 같은 상태 코드 화면, 그리고 관리자 글 편집의 미리보기다. 앞의 둘은 콘텐츠가 없는 화면이라 정책상 광고를 두면 안 된다. 미리보기는 공개 레이아웃을 빌려 쓰지만 방문자의 화면이 아니다. 500 오류 화면은 레이아웃 자체를 쓰지 않는 독립 페이지라 처음부터 스크립트가 없고, 관리자 화면은 별도 레이아웃이라 이 블록이 아예 없다.
위치별 광고를 그리는 부분 뷰 _AdUnit도 같은 표시를 한 번 더 보고, 걸리면 slot을 비워 아무것도 그리지 않는다. 스크립트는 빠졌는데 광고 자리만 남아 빈 칸이 생기는 일을 막는다.
여기까지는 승인 전에 만들어 둔 것이다. 승인 뒤에 새로 정해야 했던 건 이 바깥, 광고가 실제로 들어가는 자리였다.
여섯 자리 중 세 자리
위치별 광고 자리는 여섯 곳이다. 본문 상단, 본문 중간, 본문 하단, 사이드바, 목록 상단, 목록 하단. 처음에는 본문 상단·본문 하단·사이드바·목록 하단 네 곳을 켜고 본문 중간과 목록 상단만 껐다. 그 뒤 본문 상단도 껐다. 이유는 아래에 따로 적는다.
본문 중간을 끈 이유는 이 블로그 글의 모양에 있다. 글 상세의 순서는 이렇다.
@if (Model.Series != null)
{
@await Html.PartialAsync("_SeriesBox", Model.Series)
}
@await Html.PartialAsync("_AdUnit", AdPositions.PostTop)
<div class="post-content prose ...">
@if (AdSense.GetSlot(AdPositions.PostMiddle) != null
&& AdPlacement.TrySplitBeforeMiddleH2(post.ContentHtml, out var contentBefore, out var contentAfter))
{
@Html.Raw(contentBefore)
@await Html.PartialAsync("_AdUnit", AdPositions.PostMiddle)
@Html.Raw(contentAfter)
}
...
중간 광고는 본문의 <h2> 중 가운데 것 바로 앞에 들어간다. 그런데 요즘 이 블로그에 올라가는 글은 대부분 연재이고, 연재 글은 한 편 안에서 이야기가 이어진다. 문제가 드러나는 장면, 원인을 파고드는 부분, 해결하는 코드가 소제목을 경계로 이어지는데, 가운데 소제목은 대개 긴장이 가장 높은 지점이다. 거기서 광고가 화면 하나를 차지하고 나면 읽던 흐름이 끊긴다.
광고 자리마다 높이를 미리 잡아 두는 것도 이 판단에 영향을 줬다. 광고가 늦게 떠서 본문이 밀려 내려가는 일을 막으려고, .ad-unit에는 250px(넓은 화면 280px)의 최소 높이와 위아래 여백을 준다. 밀림은 없어지는 대신, 본문 한가운데에 위아래 여백까지 합쳐 300px이 넘는 블록이 생긴다. 상단과 하단은 본문 바깥이라 이 높이가 거슬리지 않지만, 중간은 읽는 사람이 반드시 지나가야 하는 자리다.
목록 상단은 첫인상 때문에 껐다. 홈이나 카테고리에 들어왔을 때 제일 먼저 보여야 하는 건 글 제목이다. 목록 위에 광고가 먼저 있으면, 이 블로그가 무엇을 쓰는 곳인지 알기도 전에 광고부터 보게 된다. 목록 하단은 목록을 끝까지 내린 사람만 만나는 자리라 남겼다.
남긴 세 곳은 공통점이 있다. 읽기가 끝난 자리이거나, 읽는 동선 바깥이다. 본문 하단은 다 읽은 뒤다. 사이드바는 카테고리와 최근 글 아래에 있어서 데스크톱에서는 본문 옆으로 비켜나 있고, 모바일에서는 본문 아래로 내려간다.
자동 광고의 스위치들
위치별 광고는 내가 정한 자리에만 들어간다. 자동 광고는 다르다. 애드센스가 페이지를 읽고 형식별로 알아서 자리를 고른다. 형식은 크게 오버레이(전면 광고, 앵커, 사이드 레일), 인페이지(본문 배너, 멀티플렉스), 의도 기반 형식으로 나뉘고, 형식마다 켜고 끌 수 있다.
처음에는 사이드 레일과 멀티플렉스 두 가지를 남겼다. 사이드 레일은 넓은 화면에서 본문 양옆의 빈 여백에 세로로 붙는다. 본문 폭이 정해져 있어 큰 모니터에서는 양옆이 비니, 그 여백을 쓰면 본문을 건드리지 않을 거라고 봤다. 멀티플렉스는 여러 광고를 카드 목록처럼 묶어 보여 주는 형식이고, 주로 본문이 끝난 아래쪽에 들어간다.
끈 것은 네 가지다. 전면 광고는 페이지를 옮길 때 화면 전체를 덮는다. 연재 글을 다음 편으로 넘기는 순간 전면 광고가 끼어들면, 위치별 광고에서 중간 자리를 끈 이유가 그대로 무너진다. 본문 배너는 자동 광고가 본문 문단 사이에 직접 끼워 넣는 형식이다. 내가 끈 본문 중간 광고를 애드센스가 다른 이름으로 다시 켜는 셈이라 같이 껐다. 의도 기반 형식도 본문 안에 광고 요소를 더하는 쪽이라 껐다.
마지막까지 고민한 건 앵커였다. 앵커는 화면 아래쪽에 붙어 스크롤해도 따라다니는 광고다. 켜 두고 데스크톱에서 열어 보니 화면 아래를 생각보다 크게 가렸다. 본문을 읽는 내내 바닥 한 줄이 광고에 깔려 있었다. 이 계정의 설정 화면에는 앵커를 데스크톱과 모바일로 나눠 켜는 옵션이 없었고, 크기를 조절하는 옵션도 없었다.
크기를 직접 줄이는 방법이 떠오르기는 한다. 앵커는 결국 페이지에 삽입되는 요소이니, CSS로 높이를 눌러 버리면 된다. 하지만 이건 하지 않았다. 애드센스 광고 코드나 광고가 그려지는 모양을 게시자가 바꾸는 건 정책 위반이다. 광고가 덜 보이게 만드는 조작은 계정 문제로 이어질 수 있고, 같은 계정을 쓰는 다른 블로그까지 함께 영향을 받는다. 앵커는 껐다.
직접 만든 하단 고정 광고
앵커를 끄고 나니 하나가 더 떠올랐다. 바닥에 붙는 광고가 문제가 아니라 크기가 문제라면, 하단 고정 영역을 내가 직접 만들고 그 안에 디스플레이 광고 단위를 넣으면 되지 않을까. 높이는 내가 정하고, 데스크톱에서는 안 보이게 미디어 쿼리로 숨기면 된다. 2편의 위치 구조에 일곱 번째 자리를 더하면 된다.
여기서 멈췄다. 애드센스 정책에는 광고를 스크롤해도 따라다니게 고정하는 배치에 대한 제한이 있는데, 애드센스 광고 단위를 직접 만든 고정 영역에 넣는 경우가 어디까지 허용되는지 확실한 근거를 찾지 못했다. 근거가 불확실한 상태에서 수익을 위해 정책의 경계를 시험할 이유가 없었다. 계정 하나에 두 블로그가 묶여 있으니 더 그랬다. 이 아이디어는 보류했다.
미리보기가 뜨지 않았다
자동 광고 설정 화면에는 '광고 설정 미리보기'가 있다. 사이트를 애드센스 화면 안에 띄워 놓고, 자동 광고가 어디에 들어갈지 보여 주고, 특정 영역을 자동 광고에서 빼도록 지정할 수 있는 기능이다. 사이드바 근처에는 넣지 말라는 식의 세밀한 조정을 하려면 이 화면이 필요하다.
미리보기를 열면 사이트가 그려져야 할 자리에 로드 실패만 떴다. 원인은 블로그 자신이 보내는 응답 헤더였다.
// 보안 응답 헤더 (HTTPS 강제·HSTS 는 IIS web.config 의 URL Rewrite 가 담당)
app.Use(async (context, next) =>
{
var headers = context.Response.Headers;
headers["X-Content-Type-Options"] = "nosniff";
headers["X-Frame-Options"] = "SAMEORIGIN";
headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
await next();
});
X-Frame-Options: SAMEORIGIN은 브라우저에게 이 페이지를 같은 출처의 페이지 안에서만 <iframe>으로 띄우라고 말한다. 애드센스 화면은 다른 출처이니 브라우저가 거절한다. 미리보기는 정확히 이 헤더가 막으라고 만든 일을 하고 있었던 셈이다.
이 헤더를 둔 이유는 클릭재킹이다. 다른 사이트가 내 페이지를 투명한 iframe으로 덮어 깔고, 그 위에 다른 버튼을 그려서 사용자가 모르게 내 페이지의 버튼을 누르게 만드는 공격이다. 공개 글 화면만 보면 누를 만한 게 별로 없다. 하지만 이 헤더는 모든 응답에 붙고, 같은 앱 안에 글을 저장하고 삭제하고 설정을 바꾸는 관리자 화면이 있다.
풀 방법은 있었다. X-Frame-Options를 빼고 CSP의 frame-ancestors로 바꾸면, 자기 출처에 더해 애드센스 화면의 출처만 골라서 허용할 수 있다. 공개 페이지에만 이 완화를 적용하고 관리자 경로는 그대로 두는 식으로 범위를 좁힐 수도 있다.
그래도 풀지 않았다. 얻는 것은 자동 광고에서 특정 영역을 빼는 기능 하나다. 그런데 지금 켜 둔 자동 광고는 멀티플렉스 하나뿐이고, 본문이 끝난 바깥에 붙는다. 본문 사이로 들어오는 형식은 이미 다 껐으니, 영역을 빼서 보호할 곳이 거의 남지 않았다. 남은 이익에 비해, 보안 헤더를 광고 도구 하나의 사정에 맞춰 바꾸는 쪽이 더 무거웠다. 경로별로 헤더를 달리 내기 시작하면 새 화면마다 어느 쪽인지 따져야 한다는 부담도 있었다.
대신 페이지 단위의 제외는 애드센스 콘솔의 '제외된 페이지'로 했다. 미리보기 없이도 주소로 지정할 수 있다. 소개, 개인정보처리방침, 문의 세 페이지를 자동 광고에서 뺐다. 세 페이지 모두 광고가 아니라 신뢰를 위해 있는 페이지다. 개인정보를 어떻게 다루는지 읽는 화면 옆에 광고가 따라붙는 건 어색하다. 자동 광고 설정은 애드센스 사이트 단위로 걸리기 때문에, 같은 계정을 쓰는 다른 블로그의 같은 페이지들도 함께 지정했다.
그래도 많았다
구성을 정하고 실제 글을 다시 열어 보니 광고가 많았다. 세어 보면 데스크톱에서 글 한 편에 여섯 개까지 나올 수 있었다. 본문 상단, 본문 하단, 사이드바에 사이드 레일 둘, 그리고 멀티플렉스. 특히 오른쪽은 사이드바 광고와 사이드 레일이 나란히 서서, 화면 양옆에 세로 광고가 세 줄 겹쳤다. 하나하나는 "본문 바깥"이라는 기준을 지켰는데, 모아 놓으니 본문보다 광고가 먼저 눈에 들어왔다.
그래서 두 개를 더 껐다. 본문 상단은 제목 바로 아래라, 글을 열자마자 광고부터 보게 되는 자리였다. 사이드 레일은 사이드바 광고와 겹쳤다. 자리마다 따로 따진 기준만으로는 부족했고, 한 화면에 모였을 때의 합계를 따로 봐야 했다.
다시 설정 화면을 열었다가 하나를 더 발견했다. 자동 광고 형식이 처음 정한 것과 반대로 켜져 있었다. 끄기로 한 본문 배너와 모바일 전면 광고가 켜져 있고, 남기기로 한 멀티플렉스가 꺼져 있었다. 미리보기가 뜨지 않는 화면에서 체크 상자만 보고 고르다 보니 생긴 일이다. 모바일에서 광고가 유독 많게 느껴진 이유도 이 두 형식이었다. 지금은 위치별 광고 세 곳(본문 하단, 사이드바, 목록 하단)과 자동 광고 멀티플렉스 하나만 남아, 글 한 편에 나오는 광고는 세 개다.
자동 최적화를 끈 이유
자동 광고 설정 맨 아래에는 자동 최적화가 있다. 켜 두면 애드센스가 실적을 보고 광고 형식과 노출을 스스로 조정한다. 이것도 껐다.
방문자가 많지 않은 지금은 실적이라는 근거 자체가 얇다. 그리고 자동 최적화의 기준은 수익이다. 수익을 기준으로 보면, 내가 꺼 둔 전면 광고나 앵커는 다시 켜야 할 후보가 된다. 위에서 하나씩 이유를 붙여 끈 것들이 내가 모르는 사이에 돌아올 수 있다는 뜻이다. 지금의 구성은 읽는 흐름을 지키려고 정한 것이라, 그 판단이 그대로 유지되는 쪽을 골랐다.
다만 이 결정들을 영구적인 것으로 보지는 않는다. 앵커는 방문자가 늘면 다시 켜서 수익과 체감을 비교해 볼 생각이고, 자동 최적화도 그때 다시 볼 것이다. 1편에서 만든 수익 화면이 그 비교를 위한 자리가 될 것이다.
광고는 승인을 받는 순간 붙는 게 아니었다. 승인은 광고를 내도 된다는 허락일 뿐이고, 어디에 낼지는 여전히 글을 쓰는 사람이 정해야 했다. 결국 이번에 한 일의 대부분은 무언가를 만드는 일이 아니라 끄는 일이었다.
다음 업데이트 노트는 다시 코드 쪽으로 돌아간다. 글이 늘어나며 관리 화면 구석에 쌓인 태그와 연재 이야기다.