← back to Homesonspec
apps/web/src/components/AdSlot.tsx
80 lines
"use client";
import Script from "next/script";
import { useEffect } from "react";
/**
* Google AdSense banner slot — GATED.
*
* Renders NOTHING until NEXT_PUBLIC_ADSENSE_CLIENT is set to a real
* `ca-pub-…` publisher id. No id → no script, no <ins>, no network call,
* so the site ships ad-free by default and Steve flips it live with one
* env var once the AdSense account is approved.
*
* Placement rule (honors the footer promise "Organic results are never
* mixed with paid placement"): banners live in their OWN labeled zones,
* never interleaved into the listings grid, always tagged "Advertisement".
*/
const CLIENT = process.env.NEXT_PUBLIC_ADSENSE_CLIENT || "ca-pub-5278231299883833"; // TK-11341 default pub id
declare global {
interface Window {
adsbygoogle?: unknown[];
}
}
/** Loads the AdSense library once, site-wide. No-op until the id is set. Mount in layout. */
export function AdSenseLoader() {
if (!CLIENT) return null;
return (
<Script
id="adsbygoogle-init"
async
strategy="afterInteractive"
crossOrigin="anonymous"
src={`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${CLIENT}`}
/>
);
}
export default function AdSlot({
slot,
format = "auto",
className,
}: {
slot?: string;
format?: string;
className?: string;
}) {
useEffect(() => {
if (!CLIENT || !slot) return;
try {
(window.adsbygoogle = window.adsbygoogle || []).push({});
} catch {
/* AdSense not yet loaded — the loader retries the queue */
}
}, []);
if (!CLIENT || !slot) return null; // never emit an unidentifiable manual unit
return (
<aside
className={`mx-auto w-full max-w-7xl px-4 ${className ?? "my-6"}`}
aria-label="Advertisement"
>
<p className="mb-1 text-center text-[10px] uppercase tracking-wide text-neutral-400">
Advertisement
</p>
<ins
className="adsbygoogle block"
style={{ display: "block" }}
data-ad-client={CLIENT}
data-ad-slot={slot}
data-ad-format={format}
data-full-width-responsive="true"
/>
</aside>
);
}