Generate a
robots.txtfor 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()],
});
policies or a dynamic policyBuilder(ctx).{ mode, command, root }.Cache-Control: no-store so edits show instantly, and honors a custom Vite base.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).
// 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: /
vite) β served at the robots.txt route (under your base) with no-store headers.vite build) β emitted as a build asset to the root of your build outDir (e.g. dist/robots.txt).Place the plugin after your framework plugin in the
pluginsarray.
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. |
RobotsPolicyinterface 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
}
generateRobotsTxt({
policyBuilder: ({ mode }) =>
mode === 'production'
? [{ userAgent: '*', allow: ['/'] }]
: [{ userAgent: '*', disallow: ['/'] }],
});
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}`,
});
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.
| 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.
plugins (after your framework plugin) and youβre hitting the route under your configured base.no-store; if a proxy/CDN still caches, set headers at the edge.outputDir to place it in your static folder.MIT Β© dev.zarghami