How to Block AI Bots on Analog
Analog is the full-stack Angular meta-framework — file-based routing for Angular, server-side rendering, API routes, and a Nitro-powered server. It brings the Angular ecosystem into the modern SSR era alongside Next.js, Nuxt, and SvelteKit. Like Nuxt and TanStack Start, Analog uses Nitro as its HTTP server layer, which means the same server/middleware/ and routeRules patterns apply. What's distinct is the Angular-specific meta tag API: routeMeta for per-route head tags, and Angular's Meta service for programmatic control. This guide covers all four AI bot protection layers with Analog-specific patterns.
1. robots.txt
Analog is built on Vite. Files in public/ are served at the root URL automatically — the standard Vite convention.
Static robots.txt
Create public/robots.txt:
# Block AI training crawlers
User-agent: GPTBot
Disallow: /
User-agent: ClaudeBot
Disallow: /
User-agent: Claude-Web
Disallow: /
User-agent: anthropic-ai
Disallow: /
User-agent: CCBot
Disallow: /
User-agent: Google-Extended
Disallow: /
User-agent: PerplexityBot
Disallow: /
User-agent: Applebot-Extended
Disallow: /
User-agent: Amazonbot
Disallow: /
User-agent: meta-externalagent
Disallow: /
User-agent: Bytespider
Disallow: /
User-agent: Diffbot
Disallow: /
# Allow standard search crawlers
User-agent: Googlebot
Allow: /
User-agent: Bingbot
Allow: /
User-agent: *
Allow: /Dynamic robots.txt via Nitro server route
Analog's API routes live in src/server/routes/. Files there are served by Nitro before Angular SSR handles the request:
// src/server/routes/robots.txt.ts
import { defineEventHandler } from 'h3'
export default defineEventHandler((event) => {
const isDev = process.env['NODE_ENV'] !== 'production'
event.node.res.setHeader('Content-Type', 'text/plain; charset=utf-8')
if (isDev) {
return 'User-agent: *
Disallow: /
'
}
return `User-agent: GPTBot
Disallow: /
User-agent: ClaudeBot
Disallow: /
User-agent: Claude-Web
Disallow: /
User-agent: anthropic-ai
Disallow: /
User-agent: CCBot
Disallow: /
User-agent: Google-Extended
Disallow: /
User-agent: PerplexityBot
Disallow: /
User-agent: Applebot-Extended
Disallow: /
User-agent: Amazonbot
Disallow: /
User-agent: meta-externalagent
Disallow: /
User-agent: Bytespider
Disallow: /
User-agent: Googlebot
Allow: /
User-agent: Bingbot
Allow: /
User-agent: *
Allow: /
`
})src/server/routes/) take priority over public/ static files when paths match. If you have both public/robots.txt and src/server/routes/robots.txt.ts, the server route wins. Use one approach consistently.2. noai meta via routeMeta and Meta service
Analog provides two ways to manage document head meta tags — routeMeta for file-based per-route definitions, and Angular's Meta service for programmatic runtime control.
Site-wide default — Angular Meta service in AppComponent
For a site-wide noai meta tag that applies to every page, inject the Meta service in your root AppComponent:
// src/app/app.component.ts
import { Component, inject } from '@angular/core'
import { Meta } from '@angular/platform-browser'
import { RouterOutlet } from '@angular/router'
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
template: '<router-outlet />',
})
export class AppComponent {
private meta = inject(Meta)
constructor() {
// AI bot protection — applies to every page
this.meta.addTag({ name: 'robots', content: 'noai, noimageai' })
}
}Per-route override — routeMeta
Analog's routeMeta export lets you define route-specific meta tags alongside the page component in the same file:
// src/app/pages/blog/[slug].page.ts
import { Component } from '@angular/core'
import { RouteMeta } from '@analogjs/router'
// Per-route meta — overrides AppComponent Meta for this route
export const routeMeta: RouteMeta = {
title: 'Blog Post',
meta: [
// Allow AI indexing for public blog posts
{ name: 'robots', content: 'index, follow' },
],
}
@Component({
standalone: true,
template: `
<article>
<h1>Blog Post</h1>
</article>
`,
})
export default class BlogPostPage {}routeMeta and the Meta service in AppComponent set a robots meta tag, the routeMeta value takes precedence for that route — Analog's router updates head tags on navigation. Test with View Source (not browser DevTools) to verify the SSR-rendered value, which is what crawlers see.Dynamic meta from route data
For pages that load robots values from a CMS or API, use the Meta service in the component after data is resolved:
// src/app/pages/pages/[slug].page.ts
import { Component, inject, OnInit } from '@angular/core'
import { Meta } from '@angular/platform-browser'
import { ActivatedRoute } from '@angular/router'
import { toSignal } from '@angular/core/rxjs-interop'
import { map } from 'rxjs'
@Component({
standalone: true,
template: `<div>{{ data()?.title }}</div>`,
})
export default class DynamicPage implements OnInit {
private meta = inject(Meta)
private route = inject(ActivatedRoute)
data = toSignal(this.route.data.pipe(map((d) => d['page'])))
ngOnInit() {
const page = this.data()
this.meta.updateTag({
name: 'robots',
content: page?.robotsTag ?? 'noai, noimageai',
})
}
}3. X-Robots-Tag via Nitro routeRules
Analog exposes Nitro's configuration through the analog() plugin in vite.config.ts. Use routeRules to set response headers at the server level:
// vite.config.ts
import { defineConfig } from 'vite'
import analog from '@analogjs/platform'
export default defineConfig({
plugins: [
analog({
nitro: {
routeRules: {
// Apply X-Robots-Tag to all routes
'/**': {
headers: {
'X-Robots-Tag': 'noai, noimageai',
},
},
},
},
}),
],
})Nitro applies these rules before Angular SSR runs — every HTML response, API route response, and server middleware response receives the header.
Exclude API routes
// vite.config.ts
analog({
nitro: {
routeRules: {
'/**': {
headers: { 'X-Robots-Tag': 'noai, noimageai' },
},
// Don't add X-Robots-Tag to API JSON responses
'/api/**': {
headers: { 'X-Robots-Tag': undefined },
},
},
},
})4. Hard 403 via Nitro server middleware
Nitro server middleware in src/server/middleware/ runs on every HTTP request before routing. Analog auto-discovers these files — no registration needed.
Create src/server/middleware/bot-block.ts:
// src/server/middleware/bot-block.ts
import { defineEventHandler, getHeader, createError } from 'h3'
const AI_BOT_PATTERNS = [
'GPTBot',
'ClaudeBot',
'Claude-Web',
'anthropic-ai',
'CCBot',
'Google-Extended',
'PerplexityBot',
'Applebot-Extended',
'Amazonbot',
'meta-externalagent',
'Bytespider',
'Diffbot',
'YouBot',
'cohere-ai',
]
const EXEMPT_PATHS = [
'/robots.txt',
'/sitemap.xml',
'/favicon.ico',
]
function isAIBot(ua: string): boolean {
const lower = ua.toLowerCase()
return AI_BOT_PATTERNS.some((p) => lower.includes(p.toLowerCase()))
}
export default defineEventHandler((event) => {
const path = event.path ?? ''
// Always allow crawlers to access robots.txt
if (EXEMPT_PATHS.some((p) => path === p)) {
return
}
const ua = getHeader(event, 'user-agent') ?? ''
if (isAIBot(ua)) {
throw createError({
statusCode: 403,
statusMessage: 'Forbidden',
})
}
})src/server/middleware/ in alphabetical filename order. If you have multiple middleware files, name them accordingly (e.g., 00-bot-block.ts, 01-auth.ts) to control execution order.Combined middleware — block + headers
You can combine the X-Robots-Tag header injection and hard 403 in one middleware file instead of splitting between routeRules and a separate file:
// src/server/middleware/ai-protection.ts
import { defineEventHandler, getHeader, setResponseHeader, createError } from 'h3'
const AI_BOT_PATTERNS = [
'GPTBot', 'ClaudeBot', 'Claude-Web', 'anthropic-ai',
'CCBot', 'Google-Extended', 'PerplexityBot', 'Applebot-Extended',
'Amazonbot', 'meta-externalagent', 'Bytespider', 'Diffbot',
]
export default defineEventHandler((event) => {
const path = event.path ?? ''
if (path === '/robots.txt' || path === '/sitemap.xml') {
return // let Nitro serve these normally
}
const ua = getHeader(event, 'user-agent') ?? ''
if (AI_BOT_PATTERNS.some((p) => ua.toLowerCase().includes(p.toLowerCase()))) {
throw createError({ statusCode: 403, statusMessage: 'Forbidden' })
}
// Legitimate request — add header before Angular SSR responds
event.node.res.setHeader('X-Robots-Tag', 'noai, noimageai')
})5. Nitro shared with Nuxt and TanStack Start
Analog uses the same Nitro server as Nuxt, TanStack Start, and SolidStart. This means:
- Same server/middleware/ convention. The
defineEventHandlerpattern fromh3is identical. - Same routeRules syntax. The
vite.config.tsapproach maps directly to how Nuxt configures Nitro innuxt.config.ts. - Same deployment adapters. Vercel, Netlify, Cloudflare Workers, Node — all Nitro adapters work with Analog.
The Angular-specific parts are the frontend meta tag APIs (routeMeta, Meta service) — the server layer is Nitro throughout.
6. Deployment
Configure the deployment adapter in vite.config.ts via the analog() plugin:
// vite.config.ts
analog({
nitro: {
preset: 'vercel', // or 'netlify', 'cloudflare-pages', 'node-server'
routeRules: {
'/**': {
headers: { 'X-Robots-Tag': 'noai, noimageai' },
},
},
},
})| Platform | robots.txt | Meta tags | X-Robots-Tag | Hard 403 |
|---|---|---|---|---|
| Node server | ✓ | ✓ | ✓ | ✓ |
| Vercel | ✓ | ✓ | ✓ | ✓ |
| Netlify | ✓ | ✓ | ✓ | ✓ |
| Cloudflare Pages | ✓ | ✓ | ✓ | ✓ |
| Firebase | ✓ | ✓ | ✓ | ✓ |
FAQ
How do I serve robots.txt in Analog?
Place robots.txt in public/ — Vite serves it at the root URL automatically. For dynamic content (environment-specific rules), create src/server/routes/robots.txt.ts and export a defineEventHandler() that returns text/plain. Set the Content-Type header explicitly: event.node.res.setHeader('Content-Type', 'text/plain').
How do I add noai meta tags in Analog?
For a site-wide default, inject Angular's Meta service in AppComponent and call this.meta.addTag({ name: "robots", content: "noai, noimageai" }) in the constructor. For per-route overrides, export routeMeta: RouteMeta = { meta: [{ name: "robots", content: "index, follow" }] } from the page file — Analog's router updates head tags on navigation.
How do I set X-Robots-Tag headers in Analog?
Add routeRules to the Nitro config in vite.config.ts: analog({ nitro: { routeRules: { "/**": { headers: { "X-Robots-Tag": "noai, noimageai" } } } } }). Nitro applies these at the server level before Angular SSR processes the request.
How do I block AI bots in Analog?
Create src/server/middleware/bot-block.ts and export a default defineEventHandler(). Check getHeader(event, 'user-agent') and throw createError({ statusCode: 403 }) for matched bots. Nitro auto-discovers files in src/server/middleware/ — no registration needed. Exempt /robots.txt by checking event.path first.
What is the difference between routeMeta and the Angular Meta service?
routeMeta is Analog's file-based routing mechanism — statically defined per route, colocated with the component. It works with SSR: the value is in the initial HTML response. The Angular Meta service is Angular's runtime API for programmatic meta tag management — useful for dynamic values from API responses. For static site-wide meta, use Meta in AppComponent. For per-route static values, use routeMeta.
Does Analog use the same Nitro server as Nuxt?
Yes. Analog, Nuxt, TanStack Start, and SolidStart all use Nitro. The src/server/middleware/ convention, routeRules syntax, deployment adapters (Vercel, Netlify, Cloudflare, Node), and H3 event handler API are identical. If you know Nitro from Nuxt, Analog's server layer requires no relearning.
Is your site protected from AI bots?
Run a free scan to check your robots.txt, meta tags, and overall AI readiness score.