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
22 changes: 22 additions & 0 deletions app/(marketing)/blog/layout.tsx
Original file line number Diff line number Diff line change
@@ -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}
<div className="mx-auto max-w-3xl px-4 sm:px-6 pb-16">
<AdUnit />
<AdUnit format="text_link" className="mt-6" />
</div>
</>
);
}
51 changes: 51 additions & 0 deletions components/ads/ad-unit.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div data-cp-ad data-slot={SLOT_ID} data-format={format} className={className} />
);
}
Loading