Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/superpowers/plans/2026-10-28-adsense-delivery-readiness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# AdSense Delivery Readiness Implementation Plan

> **For agentic workers:** Execute inline in this session; validate with a production build and Playwright browser check.

**Goal:** Publish the site's ads.txt seller declaration and remove the visible ad frame when AdSense reports no fill.

**Architecture:** Add the exact seller line to Next.js `public/ads.txt`, which exports to the domain root. Observe the rendered AdSense slot's `data-ad-status` attribute and unmount the banner when it becomes `unfilled`.

**Tech Stack:** Next.js static export, React, TypeScript, Playwright CLI.

**Spec:** `docs/superpowers/specs/2026-10-28-adsense-footer-slot-design.md`

## Global Constraints

- Use publisher ID `ca-pub-4334384724173584` and responsive slot ID `8535614411`.
- Keep the banner responsive and after page content, before the shared footer.
- Do not treat client code as a substitute for AdSense account activation or site approval.

## Review Focus

- AdSense sets `data-ad-status="unfilled"` asynchronously: observe the attribute and hide the whole banner container.
- A filled ad remains visible: only hide on the exact `unfilled` status.
- Next static export serves `/ads.txt` at the root with plain text, not the app fallback HTML.

### Task 1: Publish ads.txt and hide unfilled banner

**Files:**
- Create: `public/ads.txt`
- Modify: `src/app/components/AdSenseBanner/index.tsx`

**Interfaces:**
- The ads.txt line is `google.com, pub-4334384724173584, DIRECT, f08c47fec0942fa0`.
- The component continues to render only when both existing environment values are configured.

- [x] Check the live failure in Playwright: with `data-ad-status="unfilled"`, the `Publicidade` aside is currently visible.
- [x] Add the seller declaration and observe slot status changes with a `MutationObserver`; return `null` only after status is exactly `unfilled`.
- [x] Run `NEXT_PUBLIC_ADSENSE_CLIENT_ID=ca-pub-4334384724173584 NEXT_PUBLIC_ADSENSE_SLOT_ID=8535614411 npm run build`.
- [x] Serve the static export and verify `/ads.txt` contains the seller line and an unfilled slot hides the whole aside.
- [x] Commit as `fix(adsense): publish ads.txt and hide unfilled slot`.
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,9 @@ Create a reusable client component that renders only when both `NEXT_PUBLIC_ADSE
- When configured, the slot appears after route content and before the footer.
- It is responsive and does not stick to or cover the viewport.
- The production build succeeds.

## Delivery readiness follow-up

- Publish the AdSense seller declaration at `/ads.txt` using the existing HelpDev publisher ID.
- Hide the complete banner container after AdSense marks its slot `data-ad-status="unfilled"`; a blank placeholder should not remain visible when Google has no ad to serve.
- These changes do not bypass AdSense account setup or site review.
1 change: 1 addition & 0 deletions public/ads.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
google.com, pub-4334384724173584, DIRECT, f08c47fec0942fa0
30 changes: 25 additions & 5 deletions src/app/components/AdSenseBanner/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useRef } from 'react';
import { useEffect, useRef, useState } from 'react';

declare global {
interface Window {
Expand All @@ -13,16 +13,35 @@ const slotId = process.env.NEXT_PUBLIC_ADSENSE_SLOT_ID;

export default function AdSenseBanner() {
const initialized = useRef(false);
const adElement = useRef<HTMLModElement | null>(null);
const [unfilled, setUnfilled] = useState(false);
const enabled = Boolean(publisherId && slotId);

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

initialized.current = true;
(window.adsbygoogle ||= []).push({});
const element = adElement.current;
if (!element) return;

const observer = new MutationObserver(() => {
setUnfilled(element.getAttribute('data-ad-status') === 'unfilled');
});
observer.observe(element, {
attributes: true,
attributeFilter: ['data-ad-status'],
});

setUnfilled(element.getAttribute('data-ad-status') === 'unfilled');

if (!initialized.current) {
initialized.current = true;
(window.adsbygoogle ||= []).push({});
}

return () => observer.disconnect();
}, [enabled]);

if (!enabled) {
if (!enabled || unfilled) {
return null;
}

Expand All @@ -36,6 +55,7 @@ export default function AdSenseBanner() {
Anúncio
</p>
<ins
ref={adElement}
className="adsbygoogle block min-h-[90px] w-full"
style={{ display: 'block' }}
data-ad-client={publisherId}
Expand Down
Loading