<!-- Generated from llms-txt-remix/index.html. The canonical document is the HTML page. -->

- [ Home ](/) 
/
- [ How to create ](/how-to-create/) 
/
- Remix guide            
# llms.txt for Remix

Two approaches for Remix: a zero-config static file in public/, or a resource route at app/routes/llms[.]txt.ts that returns a plain-text Response, optionally generated from your content.

Last updated: April 22, 2026

ℹ Which approach to use?

Use the  static file  if your  llms.txt  rarely changes and you don't need
to generate it from content. Use a  resource route  
for dynamic generation, especially if your docs are stored in a database or generated from MDX files.

## Option 1: static file in public/

Remix serves everything in the `public/` directory statically at the root of your site.
Place your file at `public/llms.txt`, no routing configuration needed.
public/llms.txt, directory structure   
Copy

```
# Remix static file approach
#
# Place your file at: public/llms.txt
# Remix (and the underlying Node/Cloudflare/Vercel adapter)
# serves everything in public/ at the root of your site.
#
# Project structure:
# your-remix-app/
# ├── public/
# │   └── llms.txt   ← add this
# ├── app/
# └── remix.config.js (or vite.config.ts)

```

This approach works with all Remix adapters: **@remix-run/node**, **@remix-run/cloudflare**, **@remix-run/vercel**, and **@remix-run/netlify**. The static file takes precedence over any matching route.

## Option 2: resource route

Create a resource route at `app/routes/llms[.]txt.ts`. The `[.]` syntax escapes the dot in the filename so Remix maps it to the URL path `/llms.txt`. Export a `loader` function that returns a `Response` with the correct `Content-Type`.
app/routes/llms[.]txt.ts   
Copy

```
// app/routes/llms[.]txt.ts
// Remix resource route, the [.] escapes the dot in the filename
// This file is served at exactly /llms.txt
import type { LoaderFunctionArgs } from '@remix-run/node';

export async function loader({ request }: LoaderFunctionArgs) {
const content = `# Your Site

> One-sentence description of what your site or product does.

## Documentation

- [Getting Started](https://yoursite.com/docs/start): Install and configure.
- [API Reference](https://yoursite.com/docs/api): Full endpoint catalog.

## Product

- [Overview](https://yoursite.com/product): Features and capabilities.
- [Pricing](https://yoursite.com/pricing): Plans and billing.

## Optional

- [Changelog](https://yoursite.com/changelog): Release history.
- [GitHub](https://github.com/your-org/your-repo): Source code.
`;

return new Response(content, {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
},
});
}

```

✓ Use Web Response, not json()

Remix's  json()  helper sets  Content-Type: application/json . For  llms.txt  you must use  new Response(body, { headers: { 'Content-Type': 'text/plain; charset=utf-8' }
})  
directly.

For dynamic generation from your content layer, fetch docs in the loader and build the Markdown
link list programmatically:
app/routes/llms[.]txt.ts, dynamic   
Copy

```
// app/routes/llms[.]txt.ts
// Generate from your content / MDX files
import type { LoaderFunctionArgs } from '@remix-run/node';
import { getAllDocs } from '~/models/docs.server';

export async function loader({ request }: LoaderFunctionArgs) {
// Fetch your documentation index from a DB or file system
const docs = await getAllDocs();

const links = docs
.map((doc) => `- [${doc.title}](https://yoursite.com/docs/${doc.slug}/): ${doc.summary}`)
.join('\n');

const body = [
'# Your Site',
'',
'> One-sentence summary of your project.',
'',
'## Documentation',
'',
links,
].join('\n');

return new Response(body, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400',
},
});
}

```

## Vite plugin approach

If you use Remix with Vite (the default for Remix v2.7+), you can generate `llms.txt` as part of the build using a Vite plugin. The generated file is placed in the
build output directory and served as a static asset, this combines the simplicity of a static file
with build-time content access.
vite.config.ts, generate llms.txt at build   
Copy

```
import { vitePlugin as remix } from '@remix-run/dev';
import { defineConfig } from 'vite';
import { writeFileSync } from 'node:fs';
import { resolve } from 'node:path';

// Simple Vite plugin to generate llms.txt at build time
function llmsTxtPlugin() {
return {
name: 'generate-llms-txt',
buildStart() {
const content = `# Your Site\n\n> Summary.\n\n## Documentation\n\n- [Docs](https://yoursite.com/docs): Guide.\n`;
writeFileSync(resolve(__dirname, 'public/llms.txt'), content, 'utf-8');
},
};
}

export default defineConfig({
plugins: [remix(), llmsTxtPlugin()],
});
```

## Verify

After deploying, confirm the file is served correctly:
Verification   
Copy

```
curl -I https://yoursite.com/llms.txt
# Expected:
# HTTP/2 200
# content-type: text/plain; charset=utf-8

curl https://yoursite.com/llms.txt | head -5
```

## Related guides

- [How to create llms.txt](/how-to-create/), templates and checklist. 
- [llms.txt format reference](/llms-txt-format/), spec details. 
- [Vercel guide](/llms-txt-vercel/), deploy Remix to Vercel. 
- [Cloudflare Pages guide](/llms-txt-cloudflare/), Remix on Cloudflare. 
- [Validator](/validator/) · [Generator](/generator/).        
## Sources

- [ llmstxt.org, official spec ](https://llmstxt.org/)
- [ Remix docs, resource routes ](https://remix.run/docs/en/main/guides/resource-routes)
- [ Remix docs, file conventions ](https://remix.run/docs/en/main/file-conventions/routes)           
On this page

- [ Option 1: static file in public/ ](#static)
- [ Option 2: resource route ](#resource-route)
- [ Vite plugin approach ](#vite-plugin)
- [ Verify ](#verify)
