Next.js robots.txt
Next.js gives you two clean ways to ship robots.txt: a static file in public/, or a typed app/robots.ts metadata route that generates it programmatically. Here's when to use each, with working code.
Two ways to serve robots.txt
A static public/robots.txt is simplest and best when your rules never change. The App Router's app/robots.ts (a MetadataRoute.Robots export) is better when rules depend on the environment, a CMS, or your base URL — it's typed, testable, and keeps the sitemap URL in sync with the rest of your metadata.
| Approach | Use when |
|---|---|
public/robots.txt | Rules are static and simple |
app/robots.ts | Rules are dynamic, env-aware, or generated |
The app/robots.ts metadata route
Export a default function returning a MetadataRoute.Robots object. Next.js serves it at /robots.txt automatically. This is the approach this very site uses.
import type { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{ userAgent: '*', allow: '/', disallow: ['/dev', '/api/'] },
{ userAgent: ['GPTBot', 'CCBot', 'Google-Extended'], disallow: '/' },
],
sitemap: 'https://example.com/sitemap.xml',
host: 'https://example.com',
}
}The static public/robots.txt
Drop a plain file at public/robots.txt and Next.js serves it verbatim at /robots.txt:
User-agent: *
Allow: /
Disallow: /api/
Sitemap: https://example.com/sitemap.xmlDon't do both
Block crawlers on preview/staging deploys
A major advantage of app/robots.ts: you can return Disallow: / on preview deployments and the real rules in production, so staging never gets indexed.
export default function robots(): MetadataRoute.Robots {
const isProd = process.env.VERCEL_ENV === 'production'
return isProd
? { rules: [{ userAgent: '*', allow: '/' }], sitemap: 'https://example.com/sitemap.xml' }
: { rules: [{ userAgent: '*', disallow: '/' }] }
}Common Next.js mistakes
Shipping a dev Disallow: / to production
If you env-gate robots.txt, double-check the production branch returns the allow rules.
Having both a static file and robots.ts
The static public file silently overrides the route. Keep only one.
Hardcoding the wrong host
Use an absolute https sitemap URL that matches your canonical domain.
Blocking /_next/static
That holds your JS and CSS chunks. Blocking it breaks rendering for crawlers.
How do I add robots.txt to a Next.js app?
Either place a static file at public/robots.txt, or add app/robots.ts exporting a default function that returns a MetadataRoute.Robots object. Next.js serves either at /robots.txt. Use robots.ts when the rules need to be dynamic.
Should I use app/robots.ts or a static file?
Use a static public/robots.txt for simple, unchanging rules. Use app/robots.ts when rules depend on the environment (e.g. blocking preview deploys), a CMS, or your base URL — it's typed and generated at build time.
Why is my Next.js robots.txt not updating?
A static public/robots.txt overrides app/robots.ts. If you have both, the route is ignored. Remove the static file, or edit the file that's actually being served — confirm with the Analyzer.
How do I block AI crawlers in Next.js?
In app/robots.ts, add a rule with userAgent set to an array like ['GPTBot', 'CCBot', 'Google-Extended'] and disallow '/'. In a static file, add the equivalent User-agent groups.
Robots.txt Analyzer
Fetch and audit any site's live robots.txt in one report.
Robots.txt Studio Editorial · Technical SEO & crawling
We build robots.txt tooling and parse thousands of real-world files. Guides are written by practitioners and reviewed against the Google and RFC 9309 specifications.