Shield specific UI components or entire routes from LLM scrapers with zero hydration mismatches and built-in fallback support.
npm i next-ai-shieldisAIBot(userAgent): small utility for Next.js Middleware (or anywhere).AI_BOTS: the default bot list (exported for reuse/extension).<ShieldRegistry>: a Server Component that readsuser-agentvianext/headersand providesisBlockedto Client Components via context.<Shield fallback>/<ServerShield fallback>/<ClientShield fallback>:- In Server Components,
Shieldresolves to the server implementation. - In Client Components,
Shieldresolves to the client implementation (reading from context).
- In Server Components,
- Pages Router helpers (import from
next-ai-shield/pages):withShieldGetServerSideProps()to injectpageProps.aiShield.isBlocked.getUserAgentFromReq()/getIsBlockedFromReq()utilities.
app/layout.tsx (Server Component):
import { ShieldRegistry } from 'next-ai-shield';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<ShieldRegistry>{children}</ShieldRegistry>
</body>
</html>
);
}app/page.tsx (Server Component):
import { Shield } from 'next-ai-shield';
export default function Page() {
return (
<Shield fallback={<p>Protected</p>}>
<div>Secret server content</div>
</Shield>
);
}app/secret/ClientThing.tsx:
'use client';
import { Shield } from 'next-ai-shield';
export function ClientThing() {
return (
<Shield fallback={<p>Protected</p>}>
<button>Secret client content</button>
</Shield>
);
}All detection uses case-insensitive substring matching against a bot list.
import { isAIBot } from 'next-ai-shield';
isAIBot(userAgent, { extendBots: ['MyCustomBot'] });import { AI_BOTS, isAIBot } from 'next-ai-shield';
isAIBot(userAgent, { bots: [...AI_BOTS, 'MyCustomBot'] });If you want a known bot to be allowed, put it in allowBots.
import { isAIBot } from 'next-ai-shield';
// Claude-Web will NOT be blocked, but other bots still will be.
isAIBot(userAgent, { allowBots: ['Claude-Web'] });Pages Router doesn’t have next/headers, so use next-ai-shield/pages to detect on the server and pass a flag into pageProps.
pages/_app.tsx:
import type { AppProps } from 'next/app';
import { ShieldProvider } from 'next-ai-shield';
import type { AiShieldPageProps } from 'next-ai-shield/pages';
export default function App({ Component, pageProps }: AppProps<AiShieldPageProps>) {
const isBlocked = pageProps.aiShield?.isBlocked ?? false;
return (
<ShieldProvider isBlocked={isBlocked}>
<Component {...pageProps} />
</ShieldProvider>
);
}pages/secret.tsx:
import { Shield } from 'next-ai-shield';
import { withShieldGetServerSideProps } from 'next-ai-shield/pages';
export const getServerSideProps = withShieldGetServerSideProps(async () => {
return { props: {} };
});
export default function SecretPage() {
return (
<Shield fallback={<p>Protected</p>}>
<div>Secret content</div>
</Shield>
);
}getStaticPropscannot reliably detect bots (there’s no per-requestuser-agent), so it will not block.- Pages Router users should not use
ShieldRegistry(it is App Router / Server Components only).
import { withShieldGetServerSideProps } from 'next-ai-shield/pages';
export const getServerSideProps = withShieldGetServerSideProps(
async () => ({ props: {} }),
{ allowBots: ['Claude-Web'], extendBots: ['MyCustomBot'] }
);middleware.ts:
import { NextResponse, type NextRequest } from 'next/server';
import { isAIBot } from 'next-ai-shield';
export function middleware(req: NextRequest) {
const ua = req.headers.get('user-agent');
if (isAIBot(ua, { allowBots: ['Claude-Web'] })) {
const url = req.nextUrl.clone();
url.pathname = '/protected';
return NextResponse.rewrite(url);
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)']
};