@smalk/nextjs-ads
v0.1.1
Published
Smalk AI Ads — server-side ad injection for Next.js (App Router, Pages Router, middleware)
Maintainers
Readme
@smalk/nextjs-ads
Server-side Smalk ad injection for Next.js publisher sites.
- App Router (
<SmalkAd>Server Component) - Pages Router (
getSmalkAd()+<AdHtml>) - Middleware variant (
withSmalkAds()for raw<div smalk-ads>placeholders)
Compat: Next 13.4+, Node 18+.
Install
npm install @smalk/nextjs-adsSet two env vars:
SMALK_PROJECT_KEY=your-workspace-uuid
SMALK_API_KEY=your-api-key(SMALK_API_BASE_URL is optional — defaults to https://api.smalk.ai.)
Usage — App Router
// app/blog/[slug]/page.tsx
import { SmalkAd } from '@smalk/nextjs-ads/app';
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return (
<article>
<h1>{slug}</h1>
<SmalkAd pathname={`/blog/${slug}`} />
</article>
);
}Usage — Pages Router
// pages/blog/[slug].tsx
import { getSmalkAd, AdHtml } from '@smalk/nextjs-ads/pages';
export const getServerSideProps = async (ctx) => {
const ad = await getSmalkAd(ctx.req);
return { props: { ad } };
};
export default function Page({ ad }) {
return (
<article>
<h1>Title</h1>
<AdHtml ad={ad} />
</article>
);
}Usage — Middleware (raw <div smalk-ads>)
// middleware.ts
import { withSmalkAds } from '@smalk/nextjs-ads/middleware';
export default withSmalkAds();
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};Then put <div smalk-ads></div> anywhere in your page HTML — middleware regex-replaces it with API HTML at request time.
Caching
fetchdata cache: 20 minrevalidate(override with<SmalkAd revalidate={N} />)- Hash compare: when API returns a hash that differs from the last cached hash for that URL, the package calls
revalidatePath()to bust the Full Route Cache - HTTP: callers should not add CDN caching to ad-bearing pages (
Cache-Control: private, no-cache, must-revalidaterecommended at the edge)
Dynamic rendering — important
For ads to refresh between deploys, the route hosting <SmalkAd> must render dynamically (server-side per request), not be baked into the static build output.
The package handles this automatically: <SmalkAd> calls headers() (built-in Next 13.4+ opt-out) and unstable_noStore() (Next 14+ explicit opt-out). This makes the surrounding route dynamic by default.
You only need to act manually in two edge cases:
output: 'export'innext.config.js— full static export, no Node server at runtime. Server Components cannot run on requests; the ad HTML is frozen at build time. Not supported by this plugin. Removeoutput: 'export'(or switch the affected routes off it).Custom
dynamicexport overriding the heuristic — if yourpage.tsxor a parentlayout.tsxexportsexport const dynamic = 'force-static'or'error', that overrides the plugin's opt-out. Either remove that line, or explicitly mark the route as dynamic:// app/blog/layout.tsx (or page.tsx) export const dynamic = 'force-dynamic';Putting it on
layout.tsxis the lowest-friction option — it applies to every page under that folder without per-page edits.
If you're not sure, leave the plugin to handle it and check view-source of your deployed page: if you see <!-- smalk: no ad --> updating between deploys (and our dashboard registers a new AdPlacement after first request), you're good.
Trust Boundary
Ad HTML is rendered via React's raw-HTML escape hatch inside package-owned components only. The publisher never calls the unsafe React API directly. We do not ship a sanitizer (DOMPurify): ads include <script type="application/ld+json"> JSON-LD that AI crawlers parse for citation freshness, and the default DOMPurify config strips it. Smalk vets ad content server-side; this is the same trust model used by the WordPress (smalk-ai-ads-pro) and Drupal (smalk_d8) plugins.
Troubleshooting
curl -sA "Mozilla/5.0 ChatGPT-User/1.0" https://yoursite.com/blog/article | grep -iE 'smalk-ads|booking'If the response only contains <!-- smalk: no ad -->, either no booking is active or the API timed out (100 ms). Check publisher dashboard inventory status.
Middleware variant — important caveat
The withSmalkAds() helper is exposed for completeness but does not work as a Next.js middleware for HTML body transformation. Next.js Edge middleware runs BEFORE the route renders; NextResponse.next() is a sentinel and its body is empty. The helper is reusable in non-Next.js custom-server / Express-style setups (e.g., a Node fronting reverse proxy). For in-Next.js usage, prefer <SmalkAd> (App Router) or getSmalkAd() (Pages Router).
License
MIT
