Methodology v1.0
July 18, 2026 • By AICompatible Team • 8 min read

如何在 Next.js、WordPress 和 Shopify 中动态生成 llms.txt:实用开发者指南

为什么动态生成 llms.txt 文件对不断增长的平台至关重要

核心要点(BLUF):在高速内容平台上,静态 llms.txt 文件会在几小时内过时,在 AI 代理发现的内容与实际存在的内容之间造成严重差距。动态生成确保实时准确性,消除手动维护开销,并保证大型语言模型始终接收当前的站点结构、内容层级和资源位置——这对于每天发布数十个页面的企业级 WordPress 安装、Next.js 应用程序和 Shopify 商店来说是不可或缺的。

理解 llms.txt 规范和动态需求

llms.txt 文件作为机器可读的清单文件,引导 AI 爬虫、语言模型和自主代理浏览您网站的信息架构。与专注于爬取权限的 robots.txt 不同,llms.txt 提供语义结构、内容分类和专门为 LLM 消费优化的优先级信号。

在以下情况下,动态生成变得至关重要:

  • 内容更新速度超过手动更新能力:电商目录每天添加 50+ 个产品、新闻网站每小时发布内容,或 SaaS 文档随每个发布周期更新
  • 分类体系以编程方式演变:自动生成的分类页面、动态过滤系统或用户生成的内容层级
  • 存在个性化层:地理变体、特定语言路由或需要条件性暴露的身份验证保护资源
  • 多源内容聚合:无头 CMS 架构、提供内容 API 的微服务或联合数据源

WordPress 动态 llms.txt 实现

WordPress 为 43% 的网站提供支持,使其 llms.txt 实现模式变得至关重要。以下方法使用 template_redirect 动作在主题渲染之前拦截请求,确保最大性能和与缓存层的兼容性。

完整的 WordPress PHP 实现

<?php
/**
 * WordPress 动态 llms.txt 生成器
 * 添加到主题的 functions.php 或创建为插件
 */

add_action('template_redirect', 'serve_dynamic_llms_txt');

function serve_dynamic_llms_txt() {
    // 仅响应 /llms.txt 请求
    if ($_SERVER['REQUEST_URI'] !== '/llms.txt') {
        return;
    }

    // 设置适当的响应头
    header('Content-Type: text/plain; charset=utf-8');
    header('X-Robots-Tag: noindex');
    
    // 防止缓存真正动态的内容
    header('Cache-Control: no-cache, must-revalidate');
    
    // 开始输出
    echo "# llms.txt - 动态生成\n";
    echo "# 生成时间: " . current_time('c') . "\n\n";
    
    // 站点元数据
    echo "# 站点: " . get_bloginfo('name') . "\n";
    echo "# 描述: " . get_bloginfo('description') . "\n";
    echo "# URL: " . home_url() . "\n\n";
    
    // 主要页面
    echo "## 主导航\n\n";
    $pages = get_pages(array('sort_column' => 'menu_order', 'hierarchical' => 0));
    foreach ($pages as $page) {
        echo "- [{$page->post_title}](" . get_permalink($page->ID) . ")\n";
    }
    echo "\n";
    
    // 按分类的博客文章
    echo "## 博客内容\n\n";
    $categories = get_categories(array('hide_empty' => true, 'orderby' => 'count', 'order' => 'DESC'));
    
    foreach ($categories as $category) {
        echo "### {$category->name} ({$category->count} 篇文章)\n\n";
        
        $posts = get_posts(array(
            'category' => $category->term_id,
            'numberposts' => 10,
            'orderby' => 'date',
            'order' => 'DESC'
        ));
        
        foreach ($posts as $post) {
            $date = get_the_date('Y-m-d', $post->ID);
            echo "- [{$post->post_title}](" . get_permalink($post->ID) . ") - {$date}\n";
        }
        echo "\n";
    }
    
    // WooCommerce 产品集成
    if (class_exists('WooCommerce')) {
        echo "## 产品目录\n\n";
        
        $product_categories = get_terms(array(
            'taxonomy' => 'product_cat',
            'hide_empty' => true,
            'orderby' => 'count',
            'order' => 'DESC',
            'number' => 10
        ));
        
        foreach ($product_categories as $cat) {
            echo "- [{$cat->name}](" . get_term_link($cat) . ") - {$cat->count} 个产品\n";
        }
        echo "\n";
    }
    
    // 文档或自定义文章类型
    $custom_post_types = get_post_types(array('public' => true, '_builtin' => false), 'objects');
    
    foreach ($custom_post_types as $cpt) {
        echo "## {$cpt->labels->name}\n\n";
        
        $cpt_posts = get_posts(array(
            'post_type' => $cpt->name,
            'numberposts' => 20,
            'orderby' => 'date',
            'order' => 'DESC'
        ));
        
        foreach ($cpt_posts as $post) {
            echo "- [{$post->post_title}](" . get_permalink($post->ID) . ")\n";
        }
        echo "\n";
    }
    
    exit; // 防止 WordPress 继续执行
}

WordPress 性能优化策略

对于高流量的 WordPress 安装,实施以下缓存模式:

  • 瞬态 API 缓存:将生成的输出存储在 set_transient('llms_txt_cache', $output, 3600) 中,并在文章发布钩子上自动失效
  • 对象缓存集成:为分布式环境利用 Redis 或 Memcached
  • CDN 边缘缓存:为 Cloudflare 或 Fastly 边缘节点设置适当的 Cache-Control 响应头(例如 max-age=1800
  • 选择性重新生成:挂接到 save_postcreated_termdeleted_term 动作,仅在内容更改时使缓存失效

Next.js 路由处理器实现(App Router)

Next.js 13+ App Router 提供了适合大型 llms.txt 文件的原生流式传输能力。以下实现使用带有 TypeScript 的路由处理器,以实现类型安全和边缘运行时兼容性。

Next.js TypeScript 实现

// app/llms.txt/route.ts

import { NextRequest, NextResponse } from 'next/server';

// 可选:启用边缘运行时以实现全球分发
// export const runtime = 'edge';

export async function GET(request: NextRequest) {
  const encoder = new TextEncoder();
  
  const stream = new ReadableStream({
    async start(controller) {
      // 辅助函数用于写入块
      const write = (text: string) => {
        controller.enqueue(encoder.encode(text));
      };
      
      // 头部部分
      write('# llms.txt - 动态站点地图\n');
      write(`# 生成时间: ${new Date().toISOString()}\n`);
      write(`# 基础 URL: ${process.env.NEXT_PUBLIC_SITE_URL}\n\n`);
      
      // 从 CMS/数据库获取数据
      try {
        // 示例:从无头 CMS 获取
        const pages = await fetch(`${process.env.CMS_API_URL}/pages`, {
          headers: { 'Authorization': `Bearer ${process.env.CMS_API_KEY}` },
          next: { revalidate: 3600 } // ISR 风格缓存
        }).then(res => res.json());
        
        write('## 主要页面\n\n');
        pages.forEach((page: any) => {
          write(`- [${page.title}](${process.env.NEXT_PUBLIC_SITE_URL}${page.slug})\n`);
        });
        write('\n');
        
        // 带分页处理的博客文章
        write('## 博客文章\n\n');
        let page = 1;
        let hasMore = true;
        
        while (hasMore && page <= 10) { // 限制以防止无限循环
          const posts = await fetch(
            `${process.env.CMS_API_URL}/posts?page=${page}&per_page=50`,
            { next: { revalidate: 1800 } }
          ).then(res => res.json());
          
          if (posts.length === 0) {
            hasMore = false;
            break;
          }
          
          posts.forEach((post: any) => {
            const date = new Date(post.publishedAt).toISOString().split('T')[0];
            write(`- [${post.title}](${process.env.NEXT_PUBLIC_SITE_URL}/blog/${post.slug}) - ${date}\n`);
          });
          
          page++;
        }
        write('\n');
        
        // 产品目录(如果适用)
        const products = await fetch(`${process.env.CMS_API_URL}/products?limit=100`)
          .then(res => res.json())
          .catch(() => []);
        
        if (products.length > 0) {
          write('## 产品\n\n');
          
          // 按类别分组
          const categorized = products.reduce((acc: any, product: any) => {
            const cat = product.category || '未分类';
            if (!acc[cat]) acc[cat] = [];
            acc[cat].push(product);
            return acc;
          }, {});
          
          Object.entries(categorized).forEach(([category, items]: [string, any]) => {
            write(`### ${category}\n\n`);
            items.forEach((product: any) => {
              write(`- [${product.name}](${process.env.NEXT_PUBLIC_SITE_URL}/products/${product.slug})\n`);
            });
            write('\n');
          });
        }
        
        // API 文档路由
        write('## API 文档\n\n');
        write(`- [API 参考](${process.env.NEXT_PUBLIC_SITE_URL}/docs/api)\n`);
        write(`- [身份验证指南](${process.env.NEXT_PUBLIC_SITE_URL}/docs/auth)\n`);
        write(`- [速率限制](${process.env.NEXT_PUBLIC_SITE_URL}/docs/limits)\n\n`);
        
      } catch (error) {
        write(`# 生成动态内容时出错: ${error}\n`);
      }
      
      controller.close();
    }
  });
  
  return new NextResponse(stream, {
    headers: {
      'Content-Type': 'text/plain; charset=utf-8',
      'Cache-Control': 'public, s-maxage=1800, stale-while-revalidate=3600',
      'X-Robots-Tag': 'noindex',
    },
  });
}

Next.js 高级模式

增量静态再生(ISR)集成:通过在 fetch 调用中设置 revalidate 值,将路由处理器与 ISR 结合,允许边缘缓存同时保持新鲜度保证。

并行数据获取:使用 Promise.all() 同时获取多个内容源,减少总生成时间:

const [pages, posts, products] = await Promise.all([
  fetchPages(),
  fetchPosts(),
  fetchProducts()
]);

条件性部分:实施功能标志或基于环境的条件,为预发布环境与生产环境暴露不同的内容结构。

Shopify 动态 llms.txt 实现

Shopify 的架构根据您的商店设置需要不同的方法。该平台的 Liquid 模板引擎和主题结构呈现出独特的挑战和机遇。

方法 1:自定义 Liquid 模板

在主题中创建新的页面模板:

{% comment %}
  文件: templates/page.llms.liquid
  在 Shopify 管理后台创建带有模板后缀 "llms" 的页面
  通过以下方式访问: yourstore.com/pages/llms-txt
{% endcomment %}

{% layout none %}
{% content_for "content_type" %}text/plain{% endcontent_for %}

# llms.txt - {{ shop.name }}
# 生成时间: {{ "now" | date: "%Y-%m-%d %H:%M:%S %Z" }}
# 商店 URL: {{ shop.url }}

## 产品系列

{% for collection in collections %}
{% if collection.products_count > 0 %}
### {{ collection.title }} ({{ collection.products_count }} 个产品)

{% for product in collection.products limit: 20 %}
- [{{ product.title }}]({{ shop.url }}{{ product.url }}) - {{ product.price | money }}
{% endfor %}

{% endif %}
{% endfor %}

## 博客文章

{% for article in blogs.news.articles %}
- [{{ article.title }}]({{ shop.url }}{{ article.url }}) - {{ article.published_at | date: "%Y-%m-%d" }}
{% endfor %}

## 页面

{% for page in pages %}
- [{{ page.title }}]({{ shop.url }}{{ page.url }})
{% endfor %}

方法 2:Shopify 应用代理

对于企业级 Shopify Plus 商店,实施在服务器端生成 llms.txt 的应用代理:

  1. 配置应用代理:在 Shopify 应用设置中,将代理路径设置为 /apps/llms,指向您的服务器端点
  2. 服务器实现:构建使用 Shopify Admin API 获取当前库存、产品系列和内容的 Node.js/Python/Ruby 端点
  3. URL 重写:使用 Shopify Scripts 或主题修改将 /llms.txt 重定向到 /apps/llms/generate
// Express.js 应用代理处理器
app.get('/generate', async (req, res) => {
  const shopifyClient = new Shopify.Clients.Rest(
    req.query.shop,
    process.env.SHOPIFY_ACCESS_TOKEN
  );
  
  res.setHeader('Content-Type', 'text/plain');
  
  // 获取产品系列
  const collections = await shopifyClient.get({
    path: 'custom_collections',
  });
  
  let output = '# llms.txt\n\n## 产品系列\n\n';
  
  for (const collection of collections.body.custom_collections) {
    const products = await shopifyClient.get({
      path: `collections/${collection.id}/products`,
      query: { limit: 50 }
    });
    
    output += `### ${collection.title}\n\n`;
    products.body.products.forEach(product => {
      output += `- [${product.title}](https://${req.query.shop}/products/${product.handle})\n`;
    });
    output += '\n';
  }
  
  res.send(output);
});

方法 3:Shopify Hydrogen(无头)

对于 Hydrogen 店面,实施类似于 Next.js 的服务器路由:

// app/routes/llms[.]txt.tsx
import { LoaderFunction } from '@shopify/remix-oxygen';

export const loader: LoaderFunction = async ({ context }) => {
  const { storefront } = context;
  
  const { collections } = await storefront.query(`
    query LLMSData {
      collections(first: 50) {
        nodes {
          title
          handle
          products(first: 20) {
            nodes {
              title
              handle
            }
          }
        }
      }
    }
  `);
  
  let output = '# llms.txt\n\n';
  
  collections.nodes.forEach(collection => {
    output += `## ${collection.title}\n\n`;
    collection.products.nodes.forEach(product => {
      output += `- [${product.title}](/products/${product.handle})\n`;
    });
    output += '\n';
  });
  
  return new Response(output, {
    headers: {
      'Content-Type': 'text/plain',
      'Cache-Control': 'public, max-age=3600'
    }
  });
};

验证和测试方法

确保动态生成的 llms.txt 文件满足规范要求并实现最佳性能需要系统化验证。

格式验证清单

  • Markdown 合规性:验证正确的标题层级(H1 用于标题,H2 用于部分,H3 用于子部分)
  • 链接完整性:所有 URL 必须是绝对路径、正确编码并返回 200 状态码
  • 字符编码:UTF-8 编码,正确处理特殊字符、表情符号和国际文本
  • 文件大小考虑:保持在 10MB 以下以实现最佳 LLM 处理;对于较大的站点实施分页或摘要
  • 更新频率指示器:包含生成时间戳和更改频率提示

自动化测试脚本

import requests
import re
from urllib.parse import urlparse

def validate_llms_txt(url):
    """验证 llms.txt 格式和内容"""
    
    response = requests.get(url)
    
    # 检查响应头
    assert response.status_code == 200, "文件无法访问"
    assert 'text/plain' in response.headers.get('Content-Type', ''), "错误的内容类型"
    
    content = response.text
    lines = content.split('\n')
    
    # 验证结构
    assert lines[0].startswith('#'), "必须以标题开头"
    
    # 提取并验证所有 URL
    url_pattern = r'\[([^\]]+)\]\(([^\)]+)\)'
    urls = re.findall(url_pattern, content)
    
    print(f"找到 {len(urls)} 个链接")
    
    # 样本验证(检查前 10 个链接)
    for title, link in urls[:10]:
        parsed = urlparse(link)
        assert parsed.scheme in ['http', 'https'], f"无效的协议: {link}"
        assert parsed.netloc, f"缺少域名: {link}"
        
        # 可选:检查链接是否可访问
        try:
            link_response = requests.head(link, timeout=5, allow_redirects=True)
            assert link_response.status_code < 400, f"损坏的链接: {link}"
        except requests.RequestException as e:
            print(f"警告: 无法验证 {link}: {e}")
    
    # 检查必需的部分
    assert '##' in content, "缺少部分标题"
    
    # 验证文件大小
    size_mb = len(content.encode('utf-8')) / (1024 * 1024)
    assert size_mb < 10, f"文件过大: {size_mb:.2f}MB"
    
    print("✓ 验证通过")
    return True

# 使用方法
validate_llms_txt('https://yoursite.com/llms.txt')

性能监控

实施以下监控策略以确保动态生成不会影响站点性能:

  • 响应时间跟踪:为超过 2 秒的生成时间设置警报
  • 缓存命中率监控:跟踪缓存响应与重新生成响应的百分比
  • 错误率日志记录:监控生成期间失败的数据库查询或 API 调用
  • 资源利用率:测量高峰生成期间的 CPU 和内存使用情况

高级优化技术

条件性内容暴露

基于用户代理、地理位置或身份验证状态实施智能过滤:

// 带条件性部分的 Next.js 示例
export async function GET(request: NextRequest) {
  const userAgent = request.headers.get('user-agent') || '';
  const isGoogleBot = userAgent.includes('Googlebot');
  const isLLMCrawler = userAgent.includes('GPTBot') || userAgent.includes('Claude');
  
  // 根据爬虫类型暴露不同的内容深度
  const maxItems = isLLMCrawler ? 1000 : isGoogleBot ? 500 : 100;
  
  // 使用适当的限制生成内容
}

层级优先级

构建内容以首先呈现高价值页面,使用优先级指示器:

## 高优先级内容

- [2024 产品发布](/launch) - 优先级: 高,更新时间: 2024-01-15
- [文档首页](/docs) - 优先级: 高,更新时间: 2024-01-10

## 标准内容

- [博客归档](/blog) - 优先级: 中

多语言支持

对于国际站点,生成特定语言的 llms.txt 变体:

// WordPress 多语言示例
function serve_dynamic_llms_txt() {
    $lang = isset($_GET['lang']) ? sanitize_text_field($_GET['lang']) : 'en';
    
    if ($lang !== 'en') {
        // 生成本地化版本
        $posts = get_posts(array(
            'lang' => $lang,
            'numberposts' => 50
        ));
    }
    
    echo "# llms.txt ({$lang})\n\n";
    // ... 其余生成代码
}

安全考虑

动态生成引入了必须解决的潜在安全向量:

  • 速率限制:实施请求节流以防止资源耗尽攻击(例如,每个 IP 每小时 60 个请求)
  • 输入清理:验证并转义所有动态内容以防止注入攻击
  • 身份验证绕过防护:永远不要在 llms.txt 中暴露私有内容 URL,即使页面本身受到保护
  • 信息泄露:避免透露内部系统路径、API 端点或敏感元数据
  • DDoS 缓解:使用 CDN 级别保护并为上游服务故障实施断路器

维护和监控最佳实践

建立操作程序以确保长期可靠性:

  1. CI/CD 中的自动化测试:在部署管道中包含 llms.txt 验证
  2. 模板的版本控制:使用详细的提交消息跟踪生成逻辑的更改
  3. 警报阈值:为生成失败、性能下降或格式违规设置通知
  4. 定期审计:每月审查包含的内容、损坏的链接和结构准确性
  5. 文档:维护用于故障排除常见问题和更新生成逻辑的操作手册

结论

动态 llms.txt 生成将静态文件转变为准确代表您站点当前状态的活文档。通过为 WordPress、Next.js 和 Shopify 实施特定平台的解决方案,您可以确保 AI 代理始终接收关于您内容架构的权威、最新信息。对动态生成的投资通过改善 AI 可发现性、减少维护负担和增强对数字资产的语义理解而获得回报。随着 LLM 驱动的搜索和发现工具变得越来越普遍,动态 llms.txt 文件将从竞争优势转变为严肃网站的基线要求。