diff --git a/app/(marketing)/blog/layout.tsx b/app/(marketing)/blog/layout.tsx new file mode 100644 index 00000000..380c22a6 --- /dev/null +++ b/app/(marketing)/blog/layout.tsx @@ -0,0 +1,22 @@ +import { AdUnit } from "@/components/ads/ad-unit"; + +/** + * Ads run on /blog/** only — the editorial pages — and nowhere else on the + * site. Scoping them to this layout keeps them off the marketing pages, the + * app, and transactional email; the RSS route under /blog is a route handler + * and never sees a layout, so the feed stays clean too. + * + * Both units leave `format` unset so /ad.js sizes them to the column: a + * leaderboard on desktop, a mobile banner on narrow screens. + */ +export default function BlogLayout({ children }: { children: React.ReactNode }) { + return ( + <> + {children} +
+ + +
+ + ); +} diff --git a/components/ads/ad-unit.tsx b/components/ads/ad-unit.tsx new file mode 100644 index 00000000..a9b1cee1 --- /dev/null +++ b/components/ads/ad-unit.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { useEffect } from "react"; +import type { AdFormatId } from "@/lib/ads/formats"; + +// Our own slot on the network — the one this site earns from. Overridable so a +// preview/staging deploy can point at a test slot instead of billing the real +// one for impressions nobody sees. +const SLOT_ID = + process.env.NEXT_PUBLIC_AD_SLOT_ID ?? "94cd644a-bcc2-4bba-ab66-b4a89f3727cc"; + +const SCRIPT_SRC = "/ad.js"; + +declare global { + interface Window { + crawlproofAds?: { scan: () => void }; + } +} + +/** + * A single ad container filled by /ad.js. + * + * Omitting `format` lets the script pick by container width (leaderboard on + * desktop, mobile banner on narrow screens), which is what we want in the blog + * column. The script only auto-scans once at load, so mounting a unit via + * client-side navigation has to re-trigger the scan by hand. + */ +export function AdUnit({ + format, + className, +}: { + format?: AdFormatId; + className?: string; +}) { + useEffect(() => { + if (window.crawlproofAds) { + window.crawlproofAds.scan(); + return; + } + // First unit on the page loads the script; later ones ride the same tag. + if (document.querySelector(`script[src="${SCRIPT_SRC}"]`)) return; + const s = document.createElement("script"); + s.src = SCRIPT_SRC; + s.async = true; + document.body.appendChild(s); + }, []); + + return ( +
+ ); +}