The official reference implementation and example template demonstrating how to integrate adinject-react and adinject-devtools with Next.js 16 App Router and Sanity CMS.
flowchart TD
A[Sanity Content Lake / Studio] -->|Portable Text AST| B[Next.js Server Component]
B -->|injectPortableTextAds / injectPortableTextAffiliate| C[Enhanced AST with Ad & Affiliate Blocks]
C -->|@portabletext/react| D[Rendered Article Page]
D -->|components.types['adinject.adSlot']| E[Zero-CLS AdSenseSlot Frame]
D -->|components.types['adinject.affiliateCard']| F[Responsive AffiliateCard]
G[AdInjectProvider] -->|Context Defaults| D
H[AdInjectDevTools] -->|Visual Inspector & Policy Audit| D
-
AST-Level Portable Text Ad Injection
UsesinjectPortableTextAdsto insert ad blocks (adinject.adSlot) directly into the Sanity Portable Text AST before React renders. Avoids brittle HTML regex parsing, DOM scraping, or client-side hydration flickers. -
Affiliate Product Recommendation Embeds
UsesinjectPortableTextAffiliateto dynamically inject high-converting product review cards with FTC disclosures, ratings, and merchant comparisons. -
In-Feed Grid Monetization
Uses<AdInjectFeed />to interleave responsive ad cards between articles in a blog feed at configurable intervals. -
Zero Cumulative Layout Shift (CLS)
Guarantees 0.00 CLS scores by pre-reserving container heights and aspect ratios with<AdSlotFrame />. -
Integrated Visual DevTools & Policy Auditor
Includes<AdInjectDevTools />floating dock providing real-time ad slot inspection, placement recommendations, and Google AdSense density policy auditing.
bun add adinject-react @portabletext/react next-sanity
bun add -d adinject-devtoolsAdd adSlotType and affiliateCardType to your Sanity Studio schema so that your Portable Text editor can preview and optionally accept manually placed ads:
// src/sanity/schemaTypes/adSlotType.ts
import { defineField, defineType } from "sanity";
export const adSlotType = defineType({
name: "adinject.adSlot",
title: "Ad Slot (AdInject)",
type: "object",
fields: [
defineField({ name: "slotId", title: "Slot ID", type: "string" }),
defineField({ name: "format", title: "Format", type: "string", initialValue: "fluid" }),
],
});Include it in your Portable Text block configuration (blockContentType.ts):
defineArrayMember({ type: "adinject.adSlot" }),
defineArrayMember({ type: "adinject.affiliateCard" }),Transform the AST before passing it to Sanity's @portabletext/react component:
// src/components/blog/PortableTextRenderer.tsx
"use client";
import { PortableText, type PortableTextComponents } from "@portabletext/react";
import {
injectPortableTextAds,
injectPortableTextAffiliate,
AdSenseSlot,
AffiliateCard,
type InjectedAdBlock,
type InjectedAffiliateBlock,
} from "adinject-react";
export function PortableTextRenderer({ blocks }) {
// 1. Inject ad slots into Portable Text AST
const { content: transformedBlocks } = injectPortableTextAds({
blocks,
rule: {
id: "in_article_rule",
name: "Article Body Ads",
enabled: true,
paragraphInterval: 3, // Insert an ad every 3 paragraphs
minWordsBeforeFirstAd: 80, // Wait at least 80 words
maxAdsPerArticle: 3, // Maximum 3 ads
},
adUnit: {
id: "slot_article",
client: "ca-pub-1234567890",
slot: "9876543210",
format: "fluid",
testMode: true,
},
});
// 2. Map custom Sanity types to AdInject components
const components: PortableTextComponents = {
types: {
"adinject.adSlot": ({ value }) => (
<AdSenseSlot
client={value.adUnit.client}
slot={value.adUnit.slot}
format={value.adUnit.format}
testMode={value.adUnit.testMode}
/>
),
"adinject.affiliateCard": ({ value }) => (
<AffiliateCard product={value.data || value} variant="horizontal" />
),
},
};
return <PortableText value={transformedBlocks} components={components} />;
}// src/components/monetization/MonetizationProvider.tsx
"use client";
import { AdInjectProvider } from "adinject-react";
import { AdInjectDevTools } from "adinject-devtools";
export function MonetizationProvider({ children }: { children: React.ReactNode }) {
return (
<AdInjectProvider
client="ca-pub-1234567890"
testMode={true}
>
{children}
<AdInjectDevTools position="bottom-right" />
</AdInjectProvider>
);
}bun run dev- Open http://localhost:3000 to view the example blog feed and article detail pages.
- Open http://localhost:3000/studio to access Sanity Studio.
bun testExecutes the test suite verifying AST idempotency, threshold limits, affiliate card injection, and feed interleaving.
bun run buildGenerates static pages (SSG) with verified Server-Side AST transformations and zero hydration mismatches.
MIT © LinuxCTRL