llms.txt with Cloudflare Pages
Two approaches: place a static file in your build output directory for instant zero-config deployment, or use a Cloudflare Pages Function for dynamic content.
Last updated:
Approach 1 — Static file in the build output
The simplest option. Cloudflare Pages serves every file in your build output directory directly from their global CDN. No Workers, no Functions, no configuration changes needed.
# For any framework deployed on Cloudflare Pages,
# place llms.txt in the directory that gets published.
#
# Framework → file location
# Astro → public/llms.txt
# Next.js → public/llms.txt
# SvelteKit → static/llms.txt
# Hugo → static/llms.txt
# Eleventy → _site root (copy via passthrough)
# Plain HTML → project root or output folder
# After deploy, Cloudflare serves it at /llms.txt from their global CDN.
# Verify:
curl -I https://your-domain.com/llms.txt
# Expected: HTTP/2 200 | Content-Type: text/plain | CF-Cache-Status: HIT The correct location depends on your framework:
- Astro →
public/llms.txt(copied todist/automatically) - Next.js →
public/llms.txt(Cloudflare Pages Next.js adapter supports this) - SvelteKit →
static/llms.txt - Hugo →
static/llms.txt - Eleventy → add a passthrough copy:
eleventyConfig.addPassthroughCopy("llms.txt") - Plain HTML → place in the folder you set as your build output directory
Once deployed, Cloudflare serves the file from their edge network worldwide. The
Content-Type: text/plain header is set automatically based on the
.txt extension.
Approach 2 — Cloudflare Pages Function
Cloudflare Pages Functions let you handle specific routes with TypeScript code running on the
Cloudflare Workers runtime. Create a file at functions/llms.txt.ts and it automatically
handles requests to /llms.txt.
// functions/llms.txt.ts
// Cloudflare Pages Functions use the file path as the route.
// This file handles GET requests to /llms.txt
interface Env {
// Add KV namespace or D1 bindings here if needed
}
export const onRequestGet: PagesFunction<Env> = async (context) => {
// Build content — hardcode here or pull from KV / D1 / API
const content = [
'# My Site',
'',
'> One-sentence description of what this site is about.',
'',
'## Documentation',
'',
'- [Getting started](https://yoursite.com/docs/getting-started/): first steps.',
'- [API reference](https://yoursite.com/docs/api/): full endpoint catalog.',
'',
'## Optional',
'',
'- [Changelog](https://yoursite.com/changelog/): version history.',
].join('\n');
return new Response(content, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
// Cache at the edge for 1 hour, allow stale for 24h
'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
},
});
};
When to use this: when you want to generate the file from a data source (database, CMS API, KV store) without rebuilding the entire site. Example with KV Namespace:
// functions/llms.txt.ts — pulling content from KV
// Useful if you update the file from a CMS webhook without redeploying.
interface Env {
LLMS_TXT: KVNamespace;
}
export const onRequestGet: PagesFunction<Env> = async ({ env }) => {
const content = await env.LLMS_TXT.get('content');
if (!content) {
return new Response('# My Site\n\n> Content not configured yet.', {
status: 200,
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
}
return new Response(content, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'public, max-age=300, stale-while-revalidate=3600',
},
});
};
With the KV approach, you can update the content of /llms.txt by writing to the KV namespace
(via API, dashboard, or a webhook from your CMS) without triggering a full Pages deployment.
Alternative: Cloudflare Worker
If your site is not on Cloudflare Pages but uses Cloudflare as a CDN/DNS proxy, you can
intercept the /llms.txt path with a standalone Cloudflare Worker using a
route pattern:
// Cloudflare Worker — wrangler.toml config
// Use this if you want a standalone Worker (not tied to Pages).
// wrangler.toml
// name = "llms-txt-worker"
// main = "src/index.ts"
// compatibility_date = "2024-09-01"
//
// [[routes]]
// pattern = "yoursite.com/llms.txt"
// zone_name = "yoursite.com"
// src/index.ts
export default {
async fetch(request: Request): Promise<Response> {
const content = `# My Site
> One-sentence description.
## Core pages
- [Getting started](https://yoursite.com/docs/getting-started/): first steps.
- [API reference](https://yoursite.com/docs/api/): full endpoint catalog.
`;
return new Response(content, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'public, max-age=3600',
},
});
},
} satisfies ExportedHandler;
Cache headers and CDN behaviour
Cloudflare caches static files from your build output automatically. For static files, you don’t need to set cache headers — Cloudflare respects the default TTL from your Pages project settings.
For Pages Functions, set Cache-Control explicitly in the response. Recommended:
-
public, max-age=3600— cache at edge for 1 hour (good for manually-maintained files). -
public, max-age=3600, stale-while-revalidate=86400— serve stale for up to 24h while revalidating in background. -
public, max-age=300— 5-minute cache for KV-backed content that may update frequently.
When you deploy a new version, Cloudflare automatically invalidates the cache for changed static files. For Pages Functions, use the Cloudflare dashboard or API to purge the specific URL if you need immediate invalidation.
Verify after deploy
# Check headers — look for Content-Type and CF-Cache-Status
curl -I https://yoursite.com/llms.txt
# Check content
curl https://yoursite.com/llms.txt
# Purge Cloudflare cache if you deployed a new version:
# Dashboard → Caching → Configuration → Purge Everything
# or via API:
curl -X POST "https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/purge_cache" \
-H "Authorization: Bearer {CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"files":["https://yoursite.com/llms.txt"]}' After verifying headers, paste your live URL into the validator to confirm spec compliance: exactly one H1, valid link syntax, all URLs absolute, no empty sections.