Recipes
The first three recipes are lifted directly from this site's own source — not illustrative pseudocode, the actual files running in production. The fourth (Express) is adapted from the package's own README and verified against its published types, since this project doesn't run an Express server to compile it against directly.
App Router setup
Detect crawlers in the edge proxy and log visits without blocking the response, using event.waitUntil() rather than createNextMiddleware's onDetect (which isn't awaited and doesn't expose waitUntil):
import { NextRequest, NextResponse, type NextFetchEvent } from "next/server";
import { detectBot } from "ai-visibility/detector";
import { logCrawlerVisit } from "@/lib/crawler-log";
export function proxy(req: NextRequest, event: NextFetchEvent) {
const res = NextResponse.next();
const bot = detectBot(req.headers.get("user-agent") ?? "");
if (bot) {
event.waitUntil(logCrawlerVisit(bot, req.nextUrl.pathname));
}
return res;
}
export const config = {
matcher: "/((?!_next/static|_next/image|favicon.ico).*)",
};Then generate robots.txt as a Route Handler:
import { RobotsGenerator } from "ai-visibility/generators";
export async function GET() {
const body = new RobotsGenerator({
allowAI: ["GPTBot", "ClaudeBot", "PerplexityBot", "Google-Extended", "Bingbot", "CCBot", "Applebot-Extended"],
blockAI: [],
disallow: ["/api"],
sitemapUrl: "https://example.com/sitemap.xml",
}).generate();
return new Response(body, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
}Adding JSON-LD to a page
Use the plain object SchemaBuilder returns — not toScriptTag(), which returns a full HTML string meant for raw template injection, not JSX:
import { SchemaBuilder } from "ai-visibility/schema";
function ArticleJsonLd({ headline, author, publishedDate, url }: {
headline: string; author: string; publishedDate: string; url: string;
}) {
const schema = SchemaBuilder.article({ headline, author, publishedDate, url });
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}Generating llms.txt from a content directory
Build the page list from what actually exists in a content directory at request time, rather than hand-maintaining it — this is exactly how this site's own llms.txt and llms-full.txt are generated:
import { LLMSTextGenerator } from "ai-visibility/generators";
import { getAllPages } from "@/lib/pages-index"; // reads MDX frontmatter from content/
export const dynamic = "force-static";
export async function GET() {
const body = LLMSTextGenerator.minimal({
siteName: "Example",
description: "Example site description.",
baseUrl: "https://example.com",
pages: getAllPages(), // built from content/*.mdx frontmatter, not hardcoded
});
return new Response(body, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
}Express bot optimization
Adapted from the package README, checked against ai-visibility/express's published types (this project has no Express server to compile it against directly):
import express from "express";
import { createAIMiddleware, optimizeResponseForAI } from "ai-visibility/express";
const app = express();
app.use(createAIMiddleware({ verbose: true }));
app.use(optimizeResponseForAI({
stripJs: true,
removeAds: true,
removeTracking: true,
}));
app.get("/", (req, res) => {
// req.isAIBot / req.aiBotInfo are set by createAIMiddleware for any
// downstream handler that wants to branch on it
res.send("<html>...</html>");
});