RSS

Next.js에 Google AdSense 붙이는 방법: 승인·ads.txt·CSP·동의 관리까지

마케팅글: , Duolabs30분 읽기CSPGoogle AdSenseNext.jsads.txt마케팅웹 수익화

Google AdSense는 스크립트 한 줄만 붙이면 끝나는 도구처럼 보이지만, 실제 운영에서는 계정 심사, 사이트 소유권 확인, 광고 코드, ads.txt, 개인정보처리방침, 동의 관리, 보안 헤더가 함께 맞아야 한다. 하나라도 빠지면 사이트 연결에 실패하거나 광고 영역만 비어 있거나, 기존 페이지의 JavaScript가 차단될 수 있다.

이 글은 Duolabs Blog의 Next.js 16 App Router 구조를 기준으로 한다. 다른 Next.js 사이트에도 같은 원칙을 적용할 수 있지만 파일 경로와 CSP 처리 방식은 배포 환경에 맞게 조정해야 한다.

결론부터 정리하면

Duolabs Blog에는 다음 순서로 적용하는 것이 안전하다.

  1. AdSense 계정을 만들고 blog.duolabs.co.kr을 사이트로 등록한다.
  2. 개인정보처리방침과 유럽 지역 동의 메시지를 준비한다.
  3. 게시자 ID를 배포 환경 변수에 넣고 루트 레이아웃에 AdSense 코드를 한 번만 추가한다.
  4. /ads.txt를 공개하고 실제 URL에서 200 응답을 확인한다.
  5. 현재 Nginx CSP를 요청별 nonce 기반 strict CSP로 바꾼다.
  6. 우선 공개 게시글 본문 끝에 수동 반응형 광고 1개만 배치한다.
  7. 프로덕션에서 CSP, 크롤러 접근, 모바일 레이아웃, Core Web Vitals를 확인한 뒤 심사를 요청한다.

2026년 7월 현재 Duolabs Blog를 점검한 결과는 다음과 같다.

항목 현재 상태 해야 할 일
공개 콘텐츠와 사이트맵 있음 유지
robots.txt 전체 허용 Cloudflare가 광고 크롤러에 도전을 걸지 않는지 추가 확인
ads.txt 404 루트 경로에 추가
개인정보 및 쿠키 안내 별도 페이지 없음 광고 적용 전에 추가
AdSense 스크립트 없음 루트 레이아웃에 한 번만 추가
CSP 외부 광고 스크립트와 프레임 차단 nonce 기반 strict CSP로 전환
광고 위치 없음 게시글 본문 끝 1개부터 시작

가장 중요한 문제는 CSP다. 현재 응답 헤더의 script-src 'self' 'unsafe-inline'pagead2.googlesyndication.com의 스크립트를 차단하고, frame-src는 YouTube만 허용한다. 따라서 AdSense 코드만 넣으면 정상 작동하지 않는다.

AdSense 적용은 네 가지 작업이다

AdSense를 처음 적용할 때 아래 작업을 하나로 생각하면 문제 원인을 찾기 어렵다.

1. 계정과 사이트 심사

Google이 결제 정보와 사이트 전체를 확인하는 단계다. 공개적으로 접근 가능한 원본 콘텐츠, 명확한 탐색 구조, 정책 준수 여부가 중요하다. Google은 일반적으로 며칠이 걸리지만 경우에 따라 2주에서 4주가 걸릴 수 있다고 안내한다.

특정 글 수를 채우면 무조건 승인된다는 공식 기준은 없다. 얇은 글을 급하게 늘리는 것보다 누가 작성했고 어떤 정보를 제공하는지 분명한 글, 정상적으로 이동할 수 있는 메뉴, 운영자 정보와 개인정보처리방침을 갖추는 편이 낫다.

2. 사이트 연결

AdSense의 사이트 추가에서 blog.duolabs.co.kr을 등록한다. 연결 확인 방법은 AdSense 코드, ads.txt, 메타 태그 중 선택할 수 있다. 이 글에서는 이후 광고 게재까지 이어지도록 AdSense 코드를 루트 레이아웃에 설치한다.

3. 광고 게재 방식

사이트 전체에 위치를 자동 결정하는 자동 광고와 직접 위치를 정하는 광고 단위가 있다.

방식 장점 주의점 Duolabs 권장
자동 광고 적용이 빠르고 형식을 자동 최적화 예상하지 않은 위치에 노출될 수 있음 승인 및 배치 확인 후 제한적으로 사용
수동 광고 단위 위치와 개수를 직접 통제 컴포넌트와 반응형 공간을 관리해야 함 게시글 끝 1개로 시작

Duolabs Blog는 광고 자체보다 개발 문의와 브랜드 신뢰가 더 중요한 사이트다. 홈, 상단 고정 헤더, 카테고리 탐색, 문의 버튼 주변에 광고를 넣으면 작은 수익 때문에 주요 전환을 떨어뜨릴 수 있다. 공개 게시글을 끝까지 읽은 지점에 반응형 광고 하나를 놓고 데이터를 보는 편이 적절하다.

4. 정책과 개인정보 처리

광고가 표시되면 쿠키, IP 주소, 광고 식별자와 제3자 광고 사업자의 데이터 처리를 사용자에게 알려야 한다. 유럽경제지역, 영국, 스위스 사용자에게 개인 맞춤 광고를 제공하려면 Google 인증 CMP와 IAB TCF 연동 요구도 적용된다.

1단계: AdSense 계정과 사이트 등록

AdSense 콘솔에서 다음 순서로 진행한다.

  1. Google AdSense 계정을 만든다.
  2. 결제 프로필의 이름, 주소, 국가 정보를 실제 정보와 일치시킨다.
  3. 사이트 > 사이트 추가에서 blog.duolabs.co.kr을 입력한다.
  4. 사이트 연결 화면에서 AdSense 코드 방식을 선택한다.
  5. 아래 구현을 배포한 뒤 확인검토 요청을 누른다.

등록 주소는 실제 광고를 게재할 호스트와 일치해야 한다. duolabs.co.krblog.duolabs.co.kr은 크롤러 관점에서 별도 URL이다. 이 문서의 대상은 블로그이므로 블로그 서브도메인을 등록한다.

게시자 ID는 다음 두 형식으로 보인다.

  • 광고 코드: ca-pub-1234567890123456
  • ads.txt: pub-1234567890123456

게시자 ID와 광고 슬롯 ID는 페이지 소스에 공개되는 값이라 비밀번호나 API 비밀 키가 아니다. 그래도 코드 여러 곳에 직접 반복하지 말고 환경 변수로 한 번만 관리하는 편이 실수를 줄인다.

2단계: Next.js 루트에 AdSense 코드 추가

프로덕션 환경 변수에 게시자 ID를 넣는다.

ADSENSE_CLIENT_ID=ca-pub-1234567890123456
ADSENSE_ARTICLE_SLOT_ID=1234567890

두 값 모두 페이지 소스에 공개되지만 서버 환경 변수로 읽고 필요한 컴포넌트에 전달하면 Docker 런타임 설정을 그대로 사용할 수 있다. NEXT_PUBLIC_ 변수로 바꾸는 경우에는 Next.js 빌드 시점에 값이 브라우저 번들에 고정된다는 점을 고려해야 한다.

개발 환경에서는 실제 광고 요청을 만들지 않는 것이 좋다. Duolabs Blog의 src/app/layout.tsx에서 프로덕션일 때만 전역 스크립트를 한 번 렌더링한다.

import Script from "next/script";
import { headers } from "next/headers";

const adsenseClient = process.env.ADSENSE_CLIENT_ID;

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const nonce = (await headers()).get("x-nonce") ?? undefined;
  const enableAds = process.env.NODE_ENV === "production" && Boolean(adsenseClient);

  return (
    <html lang="ko">
      <body>
        {children}
        {enableAds && (
          <Script
            id="google-adsense"
            nonce={nonce}
            async
            strategy="beforeInteractive"
            crossOrigin="anonymous"
            src={
              "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=" +
              adsenseClient
            }
          />
        )}
      </body>
    </html>
  );
}

beforeInteractive 스크립트는 루트 레이아웃에 두면 Next.js가 문서의 head에 주입한다. Google이 안내하는 head 설치 형태와 맞고 사이트 연결 크롤러도 쉽게 확인할 수 있다. 같은 스크립트를 광고 컴포넌트마다 다시 넣으면 안 된다.

자동 광고를 켜면 이 전역 코드만으로 Google이 광고 위치를 선택할 수 있다. 수동 광고만 운영하려면 AdSense 콘솔에서 자동 광고를 끄고 다음 광고 단위를 명시적으로 렌더링한다.

3단계: 수동 반응형 광고 컴포넌트 만들기

src/app/_components/AdSenseUnit.tsx를 만든다.

"use client";

import { useEffect, useRef } from "react";

declare global {
  interface Window {
    adsbygoogle?: unknown[];
  }
}

type Props = {
  client: string;
  slot: string;
};

export default function AdSenseUnit({ client, slot }: Props) {
  const requested = useRef(false);
  const enabled = process.env.NODE_ENV === "production" && client && slot;

  useEffect(() => {
    if (!enabled || requested.current) return;

    requested.current = true;
    try {
      window.adsbygoogle = window.adsbygoogle || [];
      window.adsbygoogle.push({});
    } catch (error) {
      console.error("AdSense request failed", error);
    }
  }, [enabled]);

  if (!enabled) return null;

  return (
    <section className="my-10 min-h-[100px]" aria-label="광고">
      <ins
        className="adsbygoogle"
        style={{ display: "block" }}
        data-ad-client={client}
        data-ad-slot={slot}
        data-ad-format="auto"
        data-full-width-responsive="true"
      />
    </section>
  );
}

이 컴포넌트에서 중요한 점은 세 가지다.

  • useRef로 같은 마운트에서 광고 요청을 중복 실행하지 않는다.
  • Google이 <ins> 내부 DOM을 관리하므로 React가 그 안에 자식 노드를 렌더링하지 않는다.
  • 광고 영역의 최소 높이를 확보해 광고가 늦게 나타날 때 레이아웃 이동을 줄인다. 실제 높이는 운영할 광고 형식과 모바일 측정 결과에 맞춘다.

현재 게시글 페이지는 본문과 데스크톱 목차가 같은 flex 컨테이너에 있다. 광고를 그대로 형제 요소로 추가하면 세 번째 열이 될 수 있으므로 본문과 광고를 하나로 묶는다.

import AdSenseUnit from "@/app/_components/AdSenseUnit";

const adsenseClient = process.env.ADSENSE_CLIENT_ID;
const articleSlot = process.env.ADSENSE_ARTICLE_SLOT_ID;

<div className="lg:flex lg:gap-10">
  <div className="min-w-0 flex-1">
    <div
      className="doc-prose"
      dangerouslySetInnerHTML={{ __html: html }}
    />

    {adsenseClient && articleSlot && (
      <AdSenseUnit client={adsenseClient} slot={articleSlot} />
    )}
  </div>

  {headings.length >= 3 && <aside>기존 목차 컴포넌트</aside>}
</div>

처음에는 글 제목 바로 아래보다 본문 끝이 안전하다. 제목과 광고가 너무 가까우면 첫 화면의 콘텐츠 밀도가 떨어지고, 모바일에서 광고가 핵심 탐색 요소처럼 보일 수 있다.

4단계: 루트에 ads.txt 공개하기

ads.txt는 이 사이트의 광고 지면을 판매할 수 있는 사업자를 선언하는 파일이다. 필수는 아니지만 Google이 강하게 권장하며, 누락되면 AdSense 콘솔에 경고가 나타날 수 있다.

Next.js에서는 정적 파일이나 Route Handler로 제공할 수 있다. 게시자 ID를 배포 환경에서 관리하려면 src/app/ads.txt/route.ts가 편하다.

const publisherId = process.env.ADSENSE_CLIENT_ID?.replace("ca-", "");

export function GET() {
  if (!publisherId) {
    return new Response("", {
      status: 404,
      headers: { "content-type": "text/plain; charset=utf-8" },
    });
  }

  const body = `google.com, ${publisherId}, DIRECT, f08c47fec0942fa0\n`;

  return new Response(body, {
    headers: {
      "content-type": "text/plain; charset=utf-8",
      "cache-control": "public, max-age=3600",
    },
  });
}

배포 후 반드시 브라우저나 curl로 확인한다.

curl -i https://blog.duolabs.co.kr/ads.txt

정상 결과는 200 응답과 아래 한 줄이다.

google.com, pub-1234567890123456, DIRECT, f08c47fec0942fa0

ca-pub-...가 아니라 pub-... 형식인지, 다른 HTML이 섞이지 않았는지 확인한다. 변경 내용이 AdSense 상태에 반영되는 데 며칠이 걸릴 수 있고 광고 요청이 적은 사이트는 최대 한 달 정도 걸릴 수 있다.

5단계: Duolabs의 CSP를 AdSense와 호환시키기

도메인 허용 목록만 늘리면 왜 부족한가

AdSense는 광고와 수요 사업자에 따라 여러 하위 리소스를 동적으로 불러온다. Google은 사용 도메인이 바뀔 수 있으므로 AdSense에서 도메인 허용 목록 방식의 CSP를 지원하지 않고, 요청마다 무작위 nonce를 만드는 strict CSP를 사용하라고 안내한다.

현재 Duolabs Nginx 설정에 pagead2.googlesyndication.com 하나만 추가하는 방법은 처음에는 작동하는 것처럼 보여도 이후 스크립트, 연결, 프레임이 계속 차단될 수 있다.

Next.js 16의 proxy.ts에서 nonce 만들기

Duolabs Blog는 이미 런타임 DB 조회 때문에 동적 렌더링을 사용한다. 요청별 nonce를 적용하면 정적 페이지 캐시를 포기해야 하지만, 이 프로젝트는 원래 동적 렌더링이므로 구조적으로 잘 맞는다.

src/proxy.ts에서 요청마다 nonce를 만들고 요청과 응답 헤더에 같은 CSP를 넣는다.

import { NextRequest, NextResponse } from "next/server";

export function proxy(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
  const nonceSource = "'nonce-" + nonce + "'";

  const csp = [
    "default-src 'self'",
    [
      "script-src 'self'",
      nonceSource,
      "'unsafe-inline'",
      "'unsafe-eval'",
      "'strict-dynamic'",
      "https:",
      "http:",
    ].join(" "),
    "style-src 'self' 'unsafe-inline'",
    "img-src 'self' data: https:",
    "font-src 'self' data:",
    "connect-src 'self' https:",
    "frame-src https:",
    "frame-ancestors 'self' https://duolabs.co.kr https://*.duolabs.co.kr",
    "base-uri 'none'",
    "form-action 'self'",
    "object-src 'none'",
    "manifest-src 'self'",
    "upgrade-insecure-requests",
  ].join("; ");

  const requestHeaders = new Headers(request.headers);
  requestHeaders.set("x-nonce", nonce);
  requestHeaders.set("Content-Security-Policy", csp);

  const response = NextResponse.next({
    request: { headers: requestHeaders },
  });
  response.headers.set("Content-Security-Policy", csp);

  return response;
}

export const config = {
  matcher: [
    {
      source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
      missing: [
        { type: "header", key: "next-router-prefetch" },
        { type: "header", key: "purpose", value: "prefetch" },
      ],
    },
  ],
};

script-src는 Google의 AdSense strict CSP 지침을 반영한 것이다. unsafe-eval처럼 보안상 넓어지는 항목이 있으므로 광고 도입 결정 자체에 이 비용을 포함해야 한다. 나머지 connect-src, frame-src도 광고 네트워크의 동적 HTTPS 요청을 위해 기존보다 넓어진다.

Next.js는 요청의 CSP에서 nonce를 읽어 프레임워크 스크립트에 자동으로 붙이고, 루트 레이아웃에서는 headers()로 같은 값을 읽어 AdSense <Script>에 전달할 수 있다.

Nginx의 기존 CSP를 동시에 남기지 않는다

여기가 Duolabs 배포에서 가장 중요한 부분이다. 브라우저는 CSP 헤더를 여러 개 받으면 둘 중 하나를 선택하는 것이 아니라 모두 만족하도록 더 엄격하게 적용한다. proxy.ts에서 새 CSP를 내려도 Nginx가 기존 add_header Content-Security-Policy ...를 하나 더 추가하면 기존 정책이 AdSense를 계속 차단한다.

따라서 infra/nginx/nginx.conf의 기존 정적 CSP add_header는 제거하고, Next.js가 만든 응답 CSP가 프록시를 통과하도록 해야 한다. 다른 보안 헤더는 그대로 유지한다.

바로 강제 정책으로 바꾸기 전에 같은 값을 Content-Security-Policy-Report-Only로 먼저 배포해 위반 보고와 브라우저 콘솔을 살펴보는 것이 안전하다. 광고, YouTube 임베드, 테마 스크립트, Next.js 라우팅을 함께 테스트한 다음 강제 헤더로 전환한다.

6단계: 개인정보처리방침과 동의 관리

Google 게시자 정책은 Google 제품 사용으로 발생하는 데이터 수집, 공유, 사용과 쿠키, 웹 비콘, IP 주소 같은 기술을 개인정보처리방침에 명확히 알리도록 요구한다.

최소한 다음 내용을 실제 운영 방식에 맞게 작성한다.

  • Google을 포함한 제3자 사업자가 광고 제공을 위해 쿠키를 사용한다는 사실
  • 이전 사이트 방문 정보 등을 기반으로 개인 맞춤 광고가 제공될 수 있다는 사실
  • 사용자가 Google 광고 설정에서 개인 맞춤 광고를 관리하거나 해제하는 방법
  • 사용하는 다른 광고 네트워크와 해당 사업자의 처리방침 링크
  • 개인정보 문의 및 삭제 요청 창구

문구를 복사해 붙이는 것으로 끝내지 말고 실제 활성화한 광고 기능, 분석 도구, 보유 기간과 일치시켜야 한다. 국내 개인정보보호법 적용 여부와 필요한 동의 방식은 운영 주체가 별도로 검토해야 한다.

해외 방문자가 적어도 CMP를 준비하는 이유

공개 블로그는 검색을 통해 어느 국가에서든 방문할 수 있다. EEA, 영국, 스위스 사용자에게 개인 맞춤 광고를 제공하려면 Google 인증 CMP가 필요하다. 가장 간단한 시작 방법은 AdSense의 개인정보 보호 및 메시지에서 유럽 규정 메시지를 만들고 Google CMP를 사용하는 것이다.

다음 항목을 확인한다.

  1. AdSense에서 blog.duolabs.co.kr용 유럽 규정 메시지를 만든다.
  2. 동의, 옵션 관리, 동의하지 않음 버튼 구성을 선택한다.
  3. 개인정보처리방침 URL을 연결한다.
  4. 시크릿 창에서 ?fc=alwaysshow 테스트 파라미터로 메시지를 확인한다.
  5. 거부 후 개인 맞춤 광고 요청이 발생하지 않는지 검증한다.

CMP는 Google 정책 준수를 돕는 도구이지 각 국가 법률의 자동 준수를 보장하는 인증서는 아니다.

7단계: 심사 전 체크리스트

콘텐츠와 탐색

  • 공개 게시글이 로그인 없이 열린다.
  • 글마다 실질적인 원본 정보와 명확한 제목이 있다.
  • 홈, 카테고리, 게시글 사이를 정상적으로 이동할 수 있다.
  • 운영 주체, 문의 방법, 개인정보처리방침을 찾을 수 있다.
  • 빈 페이지, 공사 중 페이지, 복제 콘텐츠에는 광고 코드를 넣지 않는다.

크롤러와 네트워크

  • robots.txtMediapartners-GoogleGoogle-Display-Ads-Bot을 막지 않는다.
  • Cloudflare WAF나 Bot 설정이 이 크롤러에 CAPTCHA나 Access 로그인을 요구하지 않는다.
  • 사이트 연결용 페이지가 200을 반환하고 리디렉션 반복이 없다.
  • /ads.txt가 200과 text/plain으로 열린다.
  • 페이지 소스에서 게시자 ID와 AdSense 스크립트를 확인할 수 있다.

Duolabs의 현재 robots.txtUser-Agent: *에 대해 /를 허용하므로 명시적 차단은 없다. 다만 robots 허용과 Cloudflare WAF 허용은 별개이므로 배포 로그에서 광고 크롤러의 상태 코드도 확인해야 한다.

광고 정책

  • 자신의 실제 광고를 클릭하지 않는다.
  • 사용자에게 광고 클릭이나 사이트 후원을 요청하지 않는다.
  • 다운로드, 메뉴, 이전·다음, 복사 버튼 가까이에 광고를 두지 않는다.
  • 광고를 일반 콘텐츠나 시스템 알림처럼 위장하지 않는다.
  • 콘텐츠보다 광고가 많아지지 않게 한다.
  • 자동 새로고침이나 인위적인 노출 증가 코드를 넣지 않는다.

의도하지 않은 클릭도 무효 트래픽에 포함될 수 있다. 특히 모바일에서는 손가락으로 자주 누르는 요소와 광고 사이에 충분한 여백을 둔다.

8단계: 배포 후 검증 방법

1. 응답과 공개 파일 확인

curl -I https://blog.duolabs.co.kr/
curl -i https://blog.duolabs.co.kr/ads.txt
curl -s https://blog.duolabs.co.kr/게시글-슬러그 | grep ca-pub

첫 번째 응답에서 CSP가 하나만 내려오는지 확인한다. 두 번째는 200과 올바른 게시자 ID를 확인한다. 세 번째는 실제 프로덕션 HTML에 광고 코드가 포함됐는지 보는 검사다.

2. 브라우저 개발자 도구 확인

  • Console에 CSP 차단 메시지가 없는가
  • Network에서 adsbygoogle.js가 200으로 로드되는가
  • No slot size for availableWidth=0 오류가 없는가
  • 같은 <ins>에 광고를 두 번 요청했다는 오류가 없는가
  • 광고 차단 확장 기능을 끈 별도 프로필에서도 같은가

반응형 광고의 부모 너비가 0이면 광고 크기를 계산할 수 없다. 숨겨진 탭이나 display: none 컨테이너에서 먼저 요청하지 말고, 본문처럼 실제 너비가 잡힌 영역에 렌더링한다.

3. 광고가 비어 있어도 바로 코드 오류로 단정하지 않는다

다음 상황에서는 구현이 정상이어도 광고가 즉시 나타나지 않을 수 있다.

  • 사이트 심사가 아직 끝나지 않음
  • 사이트 상태가 Ready가 아님
  • 새 광고 단위가 활성화되는 중임
  • 해당 요청에 채울 광고가 없음
  • 브라우저나 네트워크가 광고를 차단함
  • 동의 결과에 따라 광고 요청이 제한됨

먼저 AdSense의 사이트 상태, 브라우저 네트워크, CSP 콘솔 순서로 원인을 분리한다. Google은 준비된 사이트의 새 광고가 보통 몇 분 안에 표시되지만 때로 한 시간 정도 걸릴 수 있다고 안내한다.

9단계: Duolabs에 맞는 광고 운영 기준

AdSense 수익만 보면 광고 개수를 늘리고 싶어지지만 Duolabs Blog의 더 큰 목적은 전문성 증명과 개발 문의 획득이다. 아래 지표를 함께 봐야 한다.

지표 보는 이유
페이지 RPM 광고 수익성 확인
광고 노출 가능 비율 위치가 실제로 보이는지 확인
문의 및 회사 사이트 클릭률 광고 때문에 본래 전환이 줄지 확인
이탈률과 읽기 완료 독서 경험이 나빠지지 않았는지 확인
LCP, CLS, INP 광고가 성능과 레이아웃 안정성을 해치지 않는지 확인

초기 운영안은 다음처럼 단순하게 잡을 수 있다.

  1. 게시글 하단 반응형 광고 1개로 2주에서 4주 측정한다.
  2. 문의 전환과 Core Web Vitals에 문제가 없으면 긴 글의 중간 광고를 별도 실험한다.
  3. 자동 광고를 시험할 때는 홈과 주요 전환 페이지를 페이지 제외에 등록한다.
  4. 형식이나 위치를 바꿀 때 한 번에 하나만 바꿔 결과 원인을 구분한다.

광고 수익보다 문의 전환 손실이 크면 광고를 줄이거나 콘텐츠 유형별로 제외하는 것이 맞다. 모든 공개 글에 같은 광고 밀도를 적용할 이유는 없다.

실제 적용 순서 요약

AdSense 계정 및 사이트 등록
  → 개인정보처리방침과 CMP 준비
  → 게시자 ID와 광고 슬롯 발급
  → proxy.ts strict CSP와 nonce 적용
  → Nginx의 중복 CSP 제거
  → 루트 레이아웃에 전역 스크립트 1회 추가
  → /ads.txt 공개
  → 게시글 하단 수동 광고 1개 추가
  → Report-Only 및 모바일 검증
  → 심사 요청
  → 승인 후 수익과 문의 전환 함께 측정

AdSense 적용에서 코드 자체는 작은 부분이다. Duolabs Blog에서는 현재의 정적 Nginx CSP를 strict CSP로 전환하는 작업, 없는 ads.txt와 개인정보 안내를 추가하는 작업, 광고를 문의 전환과 충돌하지 않는 곳에 제한하는 작업이 핵심이다. 이 세 가지를 먼저 설계하면 승인 이후에 광고 때문에 보안과 사용자 경험을 다시 뜯어고치는 일을 줄일 수 있다.

공식 문서