Skip to content
Guides/VitePress
VitePress · Vue · Static Site · Documentation9 min read

How to Block AI Bots on VitePress: Complete 2026 Guide

VitePress is the documentation engine behind Vue, Vite, Rollup, Vitest, Pinia, and dozens of other major open-source projects. Built on Vite and Vue 3, it generates a static site with SSG. Bot blocking splits across the content layer (VitePress config and public/) and the hosting platform layer.

robots.txt in public/

VitePress copies everything in the public/ directory (at your project root, not inside docs/) to the root of the build output at .vitepress/dist/. Place robots.txt in public/and it will be served at https://yoursite.com/robots.txt — no config required.

public/ location: The public/ directory must be at the same level as your .vitepress/ config directory — typically the project root or your docs/ directory, depending on how your project is structured. If your config is at docs/.vitepress/config.ts, put public/robots.txt at docs/public/robots.txt.

public/robots.txt

User-agent: *
Allow: /

# Block AI training crawlers
User-agent: GPTBot
Disallow: /

User-agent: ClaudeBot
Disallow: /

User-agent: anthropic-ai
Disallow: /

User-agent: CCBot
Disallow: /

User-agent: Google-Extended
Disallow: /

User-agent: AhrefsBot
Disallow: /

User-agent: Bytespider
Disallow: /

User-agent: Amazonbot
Disallow: /

User-agent: Diffbot
Disallow: /

User-agent: FacebookBot
Disallow: /

User-agent: cohere-ai
Disallow: /

User-agent: PerplexityBot
Disallow: /

User-agent: YouBot
Disallow: /

Sitemap: https://docs.example.com/sitemap.xml

After npx vitepress build, verify: ls .vitepress/dist/robots.txt.

noai meta via transformHead hook

VitePress provides a transformHead build hook in .vitepress/config.ts. It runs for every page during static generation and lets you inject <head> entries programmatically — including the noai meta tag.

.vitepress/config.ts

import { defineConfig } from 'vitepress'
import type { TransformContext } from 'vitepress'

export default defineConfig({
  title: 'My Docs',
  description: 'My documentation site',

  // Global head entries — applied to every page
  head: [
    // Fallback: set here if not using transformHead
    // ['meta', { name: 'robots', content: 'noai, noimageai' }],
  ],

  // Per-page head injection — runs at build time for every page
  async transformHead({ pageData }: TransformContext) {
    const robots: string =
      (pageData.frontmatter.robots as string | undefined) ?? 'noai, noimageai'

    return [
      ['meta', { name: 'robots', content: robots }],
    ]
  },
})
transformHead vs head: Entries in the top-level headarray are injected globally into every page — no per-page logic possible. The transformHead hook runs per page, giving you access to pageData.frontmatter for conditional logic. Use transformHeadwhen you need per-page overrides; use head for truly global static tags.

Simple approach — global head array (no per-page override)

If you don't need per-page overrides, the simpler approach is the global head array:

import { defineConfig } from 'vitepress'

export default defineConfig({
  title: 'My Docs',

  head: [
    ['meta', { name: 'robots', content: 'noai, noimageai' }],
  ],
})

Per-page override via frontmatter

With the transformHead hook reading pageData.frontmatter.robots, override the meta tag on any individual page via its YAML frontmatter:

Default (no frontmatter — uses hook default)

---
title: My Page
---

# My Page

Page content here.

Allow indexing but no AI training

---
title: My Public Page
robots: "index, follow, noai, noimageai"
---

Allow everything (e.g. landing / home page)

---
title: Home
robots: "index, follow"
---

Block everything

---
title: Internal Page
robots: "noindex, noai, noimageai"
---

Using the head frontmatter key directly

VitePress also supports a head array in frontmatter that merges with the global head config — useful for one-off overrides without touching transformHead:

---
title: My Page
head:
  - - meta
    - name: robots
      content: "index, follow"
---
Merge behaviour: Frontmatter head entries are merged with global head entries — they do not replace them. If you set a global robots meta in head and also set one via frontmatter, both will appear in the output. Use transformHead for clean per-page override (it controls the logic yourself).

X-Robots-Tag via hosting platform

X-Robots-Tag is an HTTP response header — VitePress outputs static files. Add it at the hosting layer.

Netlify — netlify.toml

[build]
  command = "npx vitepress build docs"
  publish = "docs/.vitepress/dist"

[[headers]]
  for = "/*"
  [headers.values]
    X-Robots-Tag = "noai, noimageai"

Vercel — vercel.json

{
  "buildCommand": "npx vitepress build docs",
  "outputDirectory": "docs/.vitepress/dist",
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "X-Robots-Tag",
          "value": "noai, noimageai"
        }
      ]
    }
  ]
}

Cloudflare Pages — docs/public/_headers

VitePress copies public/ to .vitepress/dist/. Place _headers in docs/public/_headers (or public/_headers at the project root if that's your structure):

/*
  X-Robots-Tag: noai, noimageai

GitHub Pages

GitHub Pages does not support custom HTTP headers. The noai meta tag via transformHead or global head is your only option. For X-Robots-Tag or hard 403 blocking, migrate to Netlify, Vercel, or Cloudflare Pages.

Hard 403 via edge functions

Netlify Edge Function

Create netlify/edge-functions/block-ai-bots.ts:

import type { Context } from '@netlify/edge-functions';

const AI_BOTS = [
  'GPTBot', 'ClaudeBot', 'anthropic-ai', 'CCBot',
  'Google-Extended', 'AhrefsBot', 'Bytespider',
  'Amazonbot', 'Diffbot', 'FacebookBot', 'cohere-ai',
  'PerplexityBot', 'YouBot',
];

export default async function handler(
  request: Request,
  context: Context
): Promise<Response> {
  const ua = request.headers.get('user-agent') || '';
  const isBot = AI_BOTS.some((bot) =>
    ua.toLowerCase().includes(bot.toLowerCase())
  );
  if (isBot) {
    return new Response('Forbidden', { status: 403 });
  }
  return context.next();
}

export const config = { path: '/*' };

Register in netlify.toml:

[[edge_functions]]
  path = "/*"
  function = "block-ai-bots"

Cloudflare Pages Functions

Create functions/_middleware.ts at the project root:

import type { PagesFunction } from '@cloudflare/workers-types';

const AI_BOTS = [
  'GPTBot', 'ClaudeBot', 'anthropic-ai', 'CCBot',
  'Google-Extended', 'AhrefsBot', 'Bytespider',
  'Amazonbot', 'Diffbot', 'FacebookBot', 'cohere-ai',
  'PerplexityBot', 'YouBot',
];

export const onRequest: PagesFunction = async (context) => {
  const ua = context.request.headers.get('user-agent') || '';
  const isBot = AI_BOTS.some((bot) =>
    ua.toLowerCase().includes(bot.toLowerCase())
  );
  if (isBot) {
    return new Response('Forbidden', { status: 403 });
  }
  return context.next();
};

Deployment quick-reference

PlatformBuild commandPublish dirCustom headersEdge functions
Netlifynpx vitepress build docsdocs/.vitepress/dist✅ netlify.toml✅ netlify/edge-functions/
Vercelnpx vitepress build docsdocs/.vitepress/dist✅ vercel.json⚠️ Next.js required
Cloudflare Pagesnpx vitepress build docsdocs/.vitepress/dist✅ public/_headers✅ functions/_middleware.ts
GitHub PagesCI: npx vitepress build.vitepress/dist🚫 No🚫 No
AWS Amplifynpx vitepress build docsdocs/.vitepress/dist✅ amplify.yml customHeaders✅ Lambda@Edge

Full .vitepress/config.ts

import { defineConfig } from 'vitepress'
import type { TransformContext } from 'vitepress'

export default defineConfig({
  title: 'My Docs',
  description: 'Documentation site',

  // noai for all pages via transformHead (enables per-page frontmatter override)
  async transformHead({ pageData }: TransformContext) {
    const robots: string =
      (pageData.frontmatter.robots as string | undefined) ?? 'noai, noimageai'
    return [['meta', { name: 'robots', content: robots }]]
  },

  themeConfig: {
    nav: [
      { text: 'Home', link: '/' },
      { text: 'Guide', link: '/guide/' },
    ],
    sidebar: {
      '/guide/': [
        { text: 'Introduction', link: '/guide/' },
      ],
    },
    socialLinks: [
      { icon: 'github', link: 'https://github.com/your-org/your-repo' },
    ],
  },
})

FAQ

How do I add robots.txt to a VitePress site?

Place robots.txt in the public/ directory. VitePress copies everything in public/ to the root of .vitepress/dist/ on build — no configuration required. If your docs are in a docs/subdirectory, put it at docs/public/robots.txt.

How do I add the noai meta tag to every VitePress page?

Use the transformHead hook in .vitepress/config.ts. Return [["meta", { name: "robots", content: "noai, noimageai" }]]. For per-page override, read pageData.frontmatter.robots with a default fallback.

How do I override the robots meta tag on a specific VitePress page?

Add robots: "index, follow" to the page's YAML frontmatter. The transformHead hook reads pageData.frontmatter.robots ?? 'noai, noimageai' — the frontmatter value overrides the default.

What is the transformHead hook in VitePress?

A build-time hook in .vitepress/config.ts that runs for every page during static generation. It receives a TransformContext with pageData (frontmatter, filePath) and returns an array of HeadConfig tuples ([tag, attrs] or [tag, attrs, content]) to inject into that page's <head>.

How do I add X-Robots-Tag on a VitePress site?

At the hosting layer. Netlify: [[headers]] in netlify.toml. Vercel: headers() in vercel.json. Cloudflare Pages: _headers in public/ (copied to dist root by VitePress).

Does VitePress have a built-in way to block AI bots?

No built-in feature — use public/robots.txt and the transformHead hook for the content layer. For hard blocking, use a Netlify Edge Function or Cloudflare Pages middleware.

Is your site protected from AI bots?

Run a free scan to check your robots.txt, meta tags, and overall AI readiness score.