Skip to content

Commit 2134159

Browse files
authored
fix(adsense): publish ads.txt and hide unfilled slot (#39)
1 parent d1b0cfd commit 2134159

4 files changed

Lines changed: 71 additions & 5 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# AdSense Delivery Readiness Implementation Plan
2+
3+
> **For agentic workers:** Execute inline in this session; validate with a production build and Playwright browser check.
4+
5+
**Goal:** Publish the site's ads.txt seller declaration and remove the visible ad frame when AdSense reports no fill.
6+
7+
**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`.
8+
9+
**Tech Stack:** Next.js static export, React, TypeScript, Playwright CLI.
10+
11+
**Spec:** `docs/superpowers/specs/2026-10-28-adsense-footer-slot-design.md`
12+
13+
## Global Constraints
14+
15+
- Use publisher ID `ca-pub-4334384724173584` and responsive slot ID `8535614411`.
16+
- Keep the banner responsive and after page content, before the shared footer.
17+
- Do not treat client code as a substitute for AdSense account activation or site approval.
18+
19+
## Review Focus
20+
21+
- AdSense sets `data-ad-status="unfilled"` asynchronously: observe the attribute and hide the whole banner container.
22+
- A filled ad remains visible: only hide on the exact `unfilled` status.
23+
- Next static export serves `/ads.txt` at the root with plain text, not the app fallback HTML.
24+
25+
### Task 1: Publish ads.txt and hide unfilled banner
26+
27+
**Files:**
28+
- Create: `public/ads.txt`
29+
- Modify: `src/app/components/AdSenseBanner/index.tsx`
30+
31+
**Interfaces:**
32+
- The ads.txt line is `google.com, pub-4334384724173584, DIRECT, f08c47fec0942fa0`.
33+
- The component continues to render only when both existing environment values are configured.
34+
35+
- [x] Check the live failure in Playwright: with `data-ad-status="unfilled"`, the `Publicidade` aside is currently visible.
36+
- [x] Add the seller declaration and observe slot status changes with a `MutationObserver`; return `null` only after status is exactly `unfilled`.
37+
- [x] Run `NEXT_PUBLIC_ADSENSE_CLIENT_ID=ca-pub-4334384724173584 NEXT_PUBLIC_ADSENSE_SLOT_ID=8535614411 npm run build`.
38+
- [x] Serve the static export and verify `/ads.txt` contains the seller line and an unfilled slot hides the whole aside.
39+
- [x] Commit as `fix(adsense): publish ads.txt and hide unfilled slot`.

‎docs/superpowers/specs/2026-10-28-adsense-footer-slot-design.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,9 @@ Create a reusable client component that renders only when both `NEXT_PUBLIC_ADSE
3939
- When configured, the slot appears after route content and before the footer.
4040
- It is responsive and does not stick to or cover the viewport.
4141
- The production build succeeds.
42+
43+
## Delivery readiness follow-up
44+
45+
- Publish the AdSense seller declaration at `/ads.txt` using the existing HelpDev publisher ID.
46+
- 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.
47+
- These changes do not bypass AdSense account setup or site review.

‎public/ads.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
google.com, pub-4334384724173584, DIRECT, f08c47fec0942fa0

‎src/app/components/AdSenseBanner/index.tsx‎

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { useEffect, useRef } from 'react';
3+
import { useEffect, useRef, useState } from 'react';
44

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

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

1820
useEffect(() => {
19-
if (!enabled || initialized.current) return;
21+
if (!enabled) return;
2022

21-
initialized.current = true;
22-
(window.adsbygoogle ||= []).push({});
23+
const element = adElement.current;
24+
if (!element) return;
25+
26+
const observer = new MutationObserver(() => {
27+
setUnfilled(element.getAttribute('data-ad-status') === 'unfilled');
28+
});
29+
observer.observe(element, {
30+
attributes: true,
31+
attributeFilter: ['data-ad-status'],
32+
});
33+
34+
setUnfilled(element.getAttribute('data-ad-status') === 'unfilled');
35+
36+
if (!initialized.current) {
37+
initialized.current = true;
38+
(window.adsbygoogle ||= []).push({});
39+
}
40+
41+
return () => observer.disconnect();
2342
}, [enabled]);
2443

25-
if (!enabled) {
44+
if (!enabled || unfilled) {
2645
return null;
2746
}
2847

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

0 commit comments

Comments
 (0)