vite-plugin-robots-txt

Generate a robots.txt for any Vite app β€” framework-agnostic, env-aware, with a no-store dev preview. Zero dependencies, fully typed, ESM + CJS.

import generateRobotsTxt from 'vite-plugin-robots-txt';

export default defineConfig({
  plugins: [generateRobotsTxt()],
});

Features


Installation

npm i -D vite-plugin-robots-txt
# or: pnpm add -D vite-plugin-robots-txt
# or: yarn add -D vite-plugin-robots-txt

Requires Node β‰₯ 18 and Vite β‰₯ 4 (tested on Vite 5).


Quick start

// vite.config.ts
import { defineConfig } from 'vite';
import generateRobotsTxt from 'vite-plugin-robots-txt';

export default defineConfig({
  plugins: [generateRobotsTxt()],
});

With no options you get a neutral, allow-all file:

User-agent: *
Allow: /

Place the plugin after your framework plugin in the plugins array.


API

generateRobotsTxt(options?: RobotsOptions): Plugin

RobotsOptions

Option Type Default Description
filename string "robots.txt" Output filename.
policies RobotsPolicy[] allow-all Explicit user-agent blocks. When non-empty, takes precedence over policyBuilder.
policyBuilder (ctx) => RobotsPolicy[] β€” Build policies dynamically from the build context.
sitemaps string[] \| (ctx) => string[] \| undefined β€” Sitemap: URLs, static or computed.
footerComment string \| (ctx) => string \| undefined β€” Trailing # comment line, static or computed.
noStoreInDev boolean true Serve the dev file with no-store headers.
outputDir string β€” Also mirror the file to this directory on disk (relative to project root), in both dev and build.

RobotsPolicy

interface RobotsPolicy {
  userAgent?: string;   // default "*"
  allow?: string[];     // -> Allow: <path>
  disallow?: string[];  // -> Disallow: <path>
  crawlDelay?: number;  // -> Crawl-delay: <seconds>
}

Paths within each policy are de-duplicated automatically.

RobotsContext (passed to every function-form option)

interface RobotsContext {
  mode: string;               // "development" | "production" | custom
  command: 'serve' | 'build';
  root: string;               // absolute project root
}

Recipes

Block everything outside production

generateRobotsTxt({
  policyBuilder: ({ mode }) =>
    mode === 'production'
      ? [{ userAgent: '*', allow: ['/'] }]
      : [{ userAgent: '*', disallow: ['/'] }],
});

Multiple user agents + crawl delay

generateRobotsTxt({
  policies: [
    { userAgent: 'Googlebot', allow: ['/'], disallow: ['/no-google'] },
    { userAgent: 'Bingbot', allow: ['/'], crawlDelay: 10 },
    { userAgent: '*', allow: ['/'], disallow: ['/admin', '/preview'] },
  ],
});

Produces:

User-agent: Googlebot
Allow: /
Disallow: /no-google

User-agent: Bingbot
Allow: /
Crawl-delay: 10

User-agent: *
Allow: /
Disallow: /admin
Disallow: /preview
generateRobotsTxt({
  sitemaps: ({ mode }) =>
    mode === 'production' ? ['https://example.com/sitemap.xml'] : [],
  footerComment: ({ mode }) => `generated for ${mode}`,
});

Mirror to a static folder

If your framework serves a static directory (e.g. SvelteKit’s static/), mirror the file there too:

generateRobotsTxt({ outputDir: 'static' });

The mirror is written only when its contents change.


How it works

Phase Behavior
vite (dev) A middleware answers the robots.txt route (resolved against your Vite base). With noStoreInDev (default) the response carries Cache-Control: no-store so changes appear on every refresh.
vite build The file is emitted via Rollup’s asset pipeline to the root of your build outDir.
outputDir When set, the file is also written to disk in both dev and build β€” skipped if unchanged.

The dev middleware takes precedence over a physical file at the same route, so the preview always reflects your current config.


Troubleshooting


License

MIT Β© dev.zarghami