A zero-dependency ad engine for Next.js App Router.
Insert ads naturally into articles and feeds with zero layout shift, consent-aware delivery, and automatic fallback to affiliate content.
npm install adinject-react
# or
bun add adinject-reactimport { AdInjectProvider, ConsentProvider, AdSenseScript } from "adinject-react";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<AdSenseScript client="ca-pub-1234567890123456" />
</head>
<body>
<ConsentProvider mode="google-consent-v2">
<AdInjectProvider client="ca-pub-1234567890123456">
{children}
</AdInjectProvider>
</ConsentProvider>
</body>
</html>
);
}import { AdSenseSlot } from "adinject-react";
export default function Sidebar() {
// Inherits client ID from AdInjectProvider automatically
return <AdSenseSlot slot="9876543210" format="rectangle" />;
}import { InArticleAds, AdInjectFeed } from "adinject-react";
// In-Article: automatically places ads every 3 paragraphs
<InArticleAds html={recipeHtml} interval={3} slot="9876543210" />
// In-Feed: interleaves ad cards into recipe or product grids
<AdInjectFeed
items={recipes}
interval={3}
startOffset={1}
renderItem={(recipe) => <RecipeCard recipe={recipe} />}
/>For local development, inspect your live ad slots, audit policy compliance, simulate realistic creatives, and visually point-and-click to place ads on your page using adinject-devtools:
npm install --save-dev adinject-devtools
# or
bun add -d adinject-devtoolsimport { AdInjectProvider } from "adinject-react";
import { AdInjectDevTools } from "adinject-devtools";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AdInjectProvider client="ca-pub-1234567890123456">
{children}
{/* Automatically tree-shaken in production builds */}
<AdInjectDevTools />
</AdInjectProvider>
</body>
</html>
);
}- 🎯 Point & Click Visual Inserter: Click anywhere on your live webpage to place mock ads and generate copy-paste ready JSX.
- ✨ Auto-Pick Best Places: 1-click AI engine that selects optimal high-CTR, zero-CLS locations.
- 🤖 AI Prompt Generator: Generates clean Markdown prompts for Cursor, Claude Code, Antigravity & Copilot.
- 🛡️ Zero-CLS Policy Auditor: Audits Better Ads & Google Publisher compliance in real time.
- 🎨 Creative Mockup Studio: 6 industry themes (Tech, SaaS, Food, E-Commerce, Travel, Finance) with 1-click drop onto page.
- 🌐 Live Production Website (lady.recipes) — Experience real-time in-feed ad insertion with zero layout shift.
- 📺 Watch Walkthrough Video — Step-by-step video demo of feed placement and responsive behavior.
| Problem with Raw Scripts | How adinject-react Solves It |
|---|---|
| Cumulative Layout Shift (CLS): Ad scripts push reading content down when creatives load. | CLS-Safe by Design: Pre-allocates exact aspect-ratio bounding boxes before network scripts load. |
| Blank Boxes on AdBlock / Unfill: Empty whitespace breaks user reading experience. | Affiliate Fallback Engine: Replaces unfilled or blocked slots with high-converting cookbook/affiliate cards. |
| GDPR / Privacy Liability: Third-party ad scripts fire before user grants consent. | Consent Gated: Integrated with Google Consent Mode v2 & IAB GPP (granted vs denied state machine). |
| Hydration & SPA Rerenders: Ad tags fire twice on route transitions or React StrictMode. | Idempotent Caching: Uses idempotencyKey memoization so renders never duplicate ad slots or links. |
Google AdSense / GAM answers: "How do I serve an ad creative?"
adinject-react answers: "Where, when, and how should ads be placed in React content safely?"
Content Body / Feed Items
│
┌───────────────┴───────────────┐
▼ ▼
In-Article Spacing In-Feed Grids
(Paragraph / Word Rules) (Interval / Offset Rules)
│ │
└───────────────┬───────────────┘
▼
Consent State Machine
(Google Consent v2 / GPP)
│
┌───────────────┴───────────────┐
▼ ▼
[Granted] [Denied]
│ │
Ad Network Adapter ▼
┌──────────┼──────────┐ Affiliate Fallback
AdSense GAM Ezoic Banner
│ │ │ │
└──────────┼──────────┘ │
▼ │
Blocked / Unfilled? ─────────────────────┘
▼
Render Zero-CLS Slot
Automatically injects ad slots between content paragraphs without modifying your CMS source text:
import { InArticleAds } from "adinject-react";
export default function ArticlePage({ post }: { post: Post }) {
return (
<article className="prose max-w-2xl mx-auto">
<h1>{post.title}</h1>
<InArticleAds
html={post.contentHtml}
interval={3} // Ad every 3 paragraphs
startOffset={1} // First ad appears after paragraph 1
maxAds={4} // Cap at 4 ads per article
slot="9876543210"
fallback={{
id: "book-fallback",
name: "Recipe E-Book",
type: "custom_cta",
targetUrl: "https://shop.example.com/cookbook",
title: "Download our 50 Best Mediterranean Recipes",
badgeText: "Recommended",
ctaText: "Get E-Book",
}}
/>
</article>
);
}Interleaves ad cards cleanly into product catalogs, recipe grids, or infinite scroll feeds:
import { AdInjectFeed } from "adinject-react";
export default function RecipeGrid({ recipes }: { recipes: Recipe[] }) {
return (
<AdInjectFeed
items={recipes}
interval={3} // In-feed ad card every 3 recipes
startOffset={1} // Place first ad after 1st recipe card
className="grid grid-cols-1 md:grid-cols-3 gap-6"
renderItem={(recipe) => <RecipeCard key={recipe.id} recipe={recipe} />}
/>
);
}adinject-react is network-agnostic. Use Google AdSense, Google Ad Manager (GAM), Ezoic, or write your own adapter:
import { AdInjectFeed, gamAdapter, ezoicAdapter } from "adinject-react";
// Google Ad Manager (GAM / GPT)
const gam = gamAdapter({
networkCode: "12345678",
testMode: process.env.NODE_ENV !== "production",
});
// Ezoic
const ezoic = ezoicAdapter({ siteId: "12345" });
// Use with Feed or Slot
<AdInjectFeed items={recipes} adapter={gam} renderItem={...} />Automatically transforms article keywords into compliant, high-converting affiliate links with FTC disclosures:
import { injectHtmlAffiliateKeywords } from "adinject-react";
const monetizedHtml = injectHtmlAffiliateKeywords(articleHtml, {
rules: [
{
keyword: "air fryer",
targetUrl: "https://amazon.com/dp/B08...?tag=mytag-20",
maxReplacements: 1, // Limit replacements per article
},
{
keyword: ["dutch oven", "cast iron pot"],
targetUrl: "https://amazon.com/dp/B07...?tag=mytag-20",
},
],
maxLinksTotal: 4,
maxLinksPerThousandWords: 2, // Density guard against keyword stuffing
});Tracks real viewability metrics (at least 50% in viewport for
import { useAdMetrics } from "adinject-react";
function CustomAdBanner() {
const { ref, isViewable, viewableDurationMs } = useAdMetrics({
threshold: 0.5,
viewableTimeMs: 1000,
filterInvalidTraffic: true, // Filters headless bots and crawlers
onViewable: () => console.log("Ad viewability milestone achieved"),
});
return <div ref={ref}>{/* Ad Creative */}</div>;
}Operates directly on Sanity's Abstract Syntax Tree (AST) before React renders, eliminating regex parsing, DOM scraping, and hydration layout shifts:
import { injectPortableTextAds, injectPortableTextAffiliate } from "adinject-react";
// Injects ad slots and affiliate recommendation cards directly into the Portable Text AST
const { content: transformedBlocks } = injectPortableTextAds({
blocks: post.body,
rule: {
id: "article_rule",
name: "Article Body Ads",
enabled: true,
paragraphInterval: 3, // Injects after every 3 paragraphs
minWordsBeforeFirstAd: 80, // Minimum 80 words before first ad
maxAdsPerArticle: 3, // Maximum 3 ads
adUnitId: "article_slot",
},
adUnit: {
id: "article_slot",
client: "ca-pub-1234567890",
slot: "9876543210",
format: "fluid",
testMode: true,
},
});Official Sanity Reference Template & Starter:
Check out the complete, production-ready example showcase at https://github.com/LinuxCTRL/sanity-adinject.
| Environment | Support | Notes |
|---|---|---|
| Next.js 16 | ✅ | Fully verified with React 19 compiler |
| Next.js 15 | ✅ | App Router & Turbopack compatible |
| Next.js 14 | ✅ | App Router & Pages Router |
| React 19 | ✅ | Full support for latest React 19 hooks and JSX |
| React 18 | ✅ | Backward compatible |
| Zero Runtime Dependencies | ✅ | 0 external node_modules dependencies |
| TypeScript | ✅ | Strict type definitions included natively |
- 0 External Telemetry:
adinject-reactsends zero analytics, metrics, or telemetry back to any third-party server. - 100% First-Party Execution: All layout calculations, keyword matching, and paragraph parsing run locally in your app.
- Cookie-Free Fallbacks: When user consent is denied, ad slots swap to pure static affiliate cards with zero tracking cookies.
- IAB GPP & TCF v2: Fully listens for global consent signals (
window.__gppandwindow.__tcfapi).
| Component | Purpose |
|---|---|
<AdInjectProvider /> |
Root provider distributing client, testMode, defaultFallback, and config defaults. |
<ConsentProvider /> |
State machine for Google Consent Mode v2 & IAB GPP compliance. |
<AdSenseSlot /> |
Zero-CLS responsive Google AdSense slot with automatic unfill detection. |
<InArticleAds /> |
Declarative in-article ad spacing container for HTML and Markdown. |
<AdInjectFeed /> |
In-feed ad card inserter for grid catalogs, list feeds, and infinite scroll. |
<InFeedAdCard /> |
Standalone feed ad card with sponsored label and responsive aspect ratios. |
<AdFallback /> |
Fallback banner component when ads are unfilled or blocked. |
<AffiliateCard /> |
High-converting affiliate product card (horizontal, card, compact, minimal). |
<AffiliateEquipmentBox /> |
Structured equipment list for recipe & review articles. |
<AffiliateDisclosure /> |
FTC & Amazon Associates compliant disclosure banner. |
<AffiliateABSlot /> |
Deterministic A/B testing container for affiliate products. |
| Export | Purpose |
|---|---|
injectHtmlAds() |
Injects ad markers into HTML strings based on paragraph rules. |
injectMarkdownAds() |
Injects ad markers into Markdown strings without breaking code blocks. |
injectFeedAds() |
Injects ad items into JavaScript arrays with idempotencyKey caching. |
injectPortableTextAds() |
Injects ad blocks into Sanity Portable Text AST structures. |
injectHtmlAffiliateKeywords() |
Autolinks keywords in HTML with density caps. |
injectMarkdownAffiliateKeywords() |
Autolinks keywords in Markdown/MDX with link protection. |
useConsent() |
Accesses active consent status (granted, denied, pending). |
useAdMetrics() |
Measures IAB ad viewability and viewable duration with IVT bot filtering. |
gamAdapter() |
Built-in adapter for Google Ad Manager (GPT). |
ezoicAdapter() |
Built-in adapter for Ezoic ad placeholders. |
MIT ©