llms.txt لـ CMS بلا واجهة

لا تقدم منصات CMS بلا واجهة مثل Contentful وSanity وStrapi وDirectus الملفات مباشرةً؛ أنشئ llms.txt وقت البناء من بيانات CMS أو اجلبه وقت الطلب عبر مسار خادم.

آخر تحديث:

هل تستخدم منشئ مواقع مستضافاً بدلاً من CMS بلا واجهة؟ راجع الأدلة المخصصة لـ Shopify, Webflow, Wix و Squarespace.

النمط الأساسي: واجهة CMS البرمجية ← خطوة البناء ← ملف ثابت

تدير منصات CMS بلا واجهة المحتوى، لكنها لا تقدّم ملفات عشوائية في مسارات عشوائية. وللنشر llms.txt, تحتاج إلى جلب المحتوى من CMS لديك وكتابة الملف بنفسك، إما في وقت البناء (المخرجات الثابتة) أو في وقت الطلب (مسار خادم).

  1. استعلم عن CMS لديك, واجلب الصفحات المنشورة مع حقول العنوان والاسم المختصر والملخص.
  2. حوّلها إلى روابط Markdown, نسّق كل إدخال على الصورة - [Title](https://url/): description.
  3. اكتب الملف أو أعده, اكتب إلى public/llms.txt وقت البناء، أو أعده من مسار خادم وقت الطلب.

مثال Contentful

استخدم Contentful JavaScript SDK لجلب الإدخالات عبر Content Delivery API. شغّل هذا السكربت أثناء خطوة البناء (مثلاً في package.json النصوص البرمجية أو مسار CI لديك) قبل بناء الإطار:

scripts/generate-llms-txt.mjs (Contentful)
// scripts/generate-llms-txt.mjs
// Contentful: fetch published doc entries and generate llms.txt at build time

import { createClient } from 'contentful';
import { writeFileSync } from 'fs';

const client = createClient({
  space: process.env.CONTENTFUL_SPACE_ID,
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
});

async function generateLlmsTxt() {
  // Fetch entries of content type 'docPage' sorted by display order
  const entries = await client.getEntries({
    content_type: 'docPage',
    order: 'fields.order',
    select: 'fields.title,fields.slug,fields.summary',
    limit: 100,
  });

  const SITE_URL = process.env.SITE_URL || 'https://yoursite.com';

  const links = entries.items
    .map((entry) => {
      const { title, slug, summary } = entry.fields;
      return `- [${title}](${SITE_URL}/docs/${slug}/): ${summary ?? ''}`;
    })
    .join('\n');

  const content = [
    '# Your Site',
    '',
    '> One-sentence description of your product.',
    '',
    '## Documentation',
    '',
    links,
    '',
    '## Optional',
    '',
    `- [Changelog](${SITE_URL}/changelog/): Release history.`,
  ].join('\n');

  writeFileSync('public/llms.txt', content, 'utf-8');
  console.log(`Generated llms.txt with ${entries.items.length} entries.`);
}

generateLlmsTxt().catch(console.error);

أضف النص البرمجي إلى مسار البناء لديك:

package.json
{
  "scripts": {
    "prebuild": "node scripts/generate-llms-txt.mjs",
    "build": "next build"
  }
}

مثال Sanity

استخدم GROQ، لغة استعلام Sanity، لجلب الحقول التي تحتاج إليها بالضبط. ويقوم !(_id in path("drafts.**")) يضمن المرشح إدراج الوثائق المنشورة فقط:

scripts/generate-llms-txt.mjs (Sanity)
// scripts/generate-llms-txt.mjs
// Sanity: use GROQ to query published docs and generate llms.txt

import { createClient } from '@sanity/client';
import { writeFileSync } from 'fs';

const client = createClient({
  projectId: process.env.SANITY_PROJECT_ID,
  dataset: process.env.SANITY_DATASET || 'production',
  useCdn: false, // always fetch fresh data at build time
  apiVersion: '2024-01-01',
});

async function generateLlmsTxt() {
  // GROQ query: fetch all published doc entries with title, slug, and summary
  const docs = await client.fetch(
    `*[_type == "doc" && !(_id in path("drafts.**"))] | order(order asc) {
      title,
      "slug": slug.current,
      summary
    }`
  );

  const SITE_URL = process.env.SITE_URL || 'https://yoursite.com';

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

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

  writeFileSync('public/llms.txt', content, 'utf-8');
  console.log(`Generated llms.txt with ${docs.length} docs.`);
}

generateLlmsTxt().catch(console.error);

مثال Strapi

تقدّم Strapi واجهة REST API على /api/:collection. استخدم fields و filters معاملات الاستعلام لجلب الوثائق المنشورة فقط مع الحقول التي تحتاج إليها:

scripts/generate-llms-txt.mjs (Strapi)
// scripts/generate-llms-txt.mjs
// Strapi v4/v5: fetch published articles via REST API and generate llms.txt

import { writeFileSync } from 'fs';

const STRAPI_URL = process.env.STRAPI_URL || 'http://localhost:1337';
const STRAPI_TOKEN = process.env.STRAPI_API_TOKEN;
const SITE_URL = process.env.SITE_URL || 'https://yoursite.com';

async function generateLlmsTxt() {
  // Fetch published docs, adjust the collection slug and fields as needed
  const res = await fetch(
    `${STRAPI_URL}/api/docs?fields[0]=title&fields[1]=slug&fields[2]=summary&filters[publishedAt][$notNull]=true&pagination[limit]=100`,
    {
      headers: STRAPI_TOKEN ? { Authorization: `Bearer ${STRAPI_TOKEN}` } : {},
    }
  );

  if (!res.ok) throw new Error(`Strapi API error: ${res.status}`);
  const { data } = await res.json();

  const links = data
    .map((item) => {
      const { title, slug, summary } = item.attributes ?? item; // v4 vs v5
      return `- [${title}](${SITE_URL}/docs/${slug}/): ${summary ?? ''}`;
    })
    .join('\n');

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

  writeFileSync('public/llms.txt', content, 'utf-8');
  console.log(`Generated llms.txt with ${data.length} entries.`);
}

generateLlmsTxt().catch(console.error);

تغلف Strapi v4 بيانات الاستجابة في كائن attributes كائن؛ تعيد Strapi v5 الحقول في المستوى الأعلى. ويعالج المثال الحالتين مع بديل.

التكامل مع معالج مسار Next.js

إذا أردت بقاء الملف متزامناً دائماً من دون إعادة بناء كاملة، فاستخدم Route Handler في Next.js App Router مع revalidate مضبوطاً على مدة TTL التي تريدها. سيخزّن Next.js الاستجابة مؤقتاً ويعيد إنشاءها في الخلفية:

app/llms.txt/route.ts (Next.js + Contentful)
// app/llms.txt/route.ts
// Next.js App Router, fetch from CMS at request time (or cache with revalidate)

import { NextResponse } from 'next/server';
import { createClient } from 'contentful';

// Cache for 1 hour (Next.js incremental static regeneration)
export const revalidate = 3600;

const client = createClient({
  space: process.env.CONTENTFUL_SPACE_ID!,
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
});

export async function GET() {
  const entries = await client.getEntries({
    content_type: 'docPage',
    order: 'fields.order',
    select: 'fields.title,fields.slug,fields.summary',
    limit: 100,
  });

  const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://yoursite.com';

  const links = entries.items
    .map((e: any) => `- [${e.fields.title}](${SITE_URL}/docs/${e.fields.slug}/): ${e.fields.summary ?? ''}`)
    .join('\n');

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

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

تكامل مع نقطة نهاية Astro

في Astro، أنشئ src/pages/llms.txt.ts نقطة نهاية مع export const prerender = true لإنشاء الملف وقت البناء. سيستدعي Astro واجهة CMS البرمجية أثناء astro build واكتب الملف الثابت إلى dist/llms.txt:

src/pages/llms.txt.ts (Astro + Sanity)
// src/pages/llms.txt.ts
// Astro endpoint, fetch from CMS at build time (static generation)

import type { APIRoute } from 'astro';
import { createClient } from '@sanity/client';

// This endpoint is pre-rendered at build time
export const prerender = true;

const sanity = createClient({
  projectId: import.meta.env.SANITY_PROJECT_ID,
  dataset: import.meta.env.SANITY_DATASET || 'production',
  useCdn: false,
  apiVersion: '2024-01-01',
});

export const GET: APIRoute = async () => {
  const docs = await sanity.fetch(
    `*[_type == "doc" && !(_id in path("drafts.**"))] | order(order asc) {
      title, "slug": slug.current, summary
    }`
  );

  const SITE_URL = import.meta.env.SITE_URL || 'https://yoursite.com';

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

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

  return new Response(body, {
    headers: { 'Content-Type': 'text/plain; charset=utf-8' },
  });
};

إنشاء ثابت مقابل ديناميكي

  • ثابت (وقت البناء)، وأسرع (مخزّن مؤقتاً على CDN)، وأبسط، ومن دون اعتماد على CMS وقت التشغيل. وهو أفضل عندما يتغير المحتوى نادراً أو تنشر عند كل تغيير.
  • ديناميكي (مسار خادم), يعكس دائماً أحدث محتوى في CMS. وهو الأنسب عندما يتغير المحتوى كثيراً بين عمليات النشر، أو عندما لا تستطيع تشغيل إعادة بناء عند تغير المحتوى. أضف Cache-Control رأس لتجنب إرهاق API الخاص بـ CMS في كل طلب.

قائمة فحص

  • يجلب السكربت أو نقطة النهاية فقط منشور المحتوى (وليس المسودات).
  • جميع عناوين URL المنشأة مطلق (https://).
  • يبدأ الملف بعنوان H1 واحد بالضبط.
  • يأتي ملخص الاقتباس مباشرة بعد العنوان H1.
  • يضم كل قسم رابطاً واحداً على الأقل.
  • الملف أصغر من 20 كيلوبايت (انتقِ المحتوى ولا تسرد كل إدخال).
  • تُشغّل خطوة البناء قبل بناء الإطار (prebuild نص برمجي أو خطوة CI).
  • تم التحقق باستخدام llmtxt.info/validator/ بعد كل عملية نشر.

أدلة ذات صلة

المصادر