📑

【Next.js】microCMSを利用したllms.txtの作成方法

に公開

こんにちは!@Ryo54388667です!☺️
現在、都内でエンジニアとして働いています。普段はTypeScriptやNext.jsを使った開発をしています!

今回は、個人メディアにllms.txtを実装した話をしたいと思います😊

↓ 下記は実際に作成したもの
https://ryotablog.jp/ja/docs/llms.txt

はじめに

llms.txtは、AIがウェブサイトの情報を効率的に理解できるようにするための設定ファイルです。簡単に言うと、robots.txtのAI版みたいなものです!自分のモチベーションとしては、SEOも重要ですが今後はAIにいかにピックしてもらえるかどうかも同じくらい重要だなと思ったので、今回の実装にいたりました!

今回は、microCMSを使ったNext.jsブログにllms.txtを実装した方法について詳しく解説していきます。

llms.txtとは?

llms.txtは、大規模言語モデル(LLM)がウェブサイトの情報を効率的に理解し、活用するための設定ファイルです。

https://llmstxt.org/

特徴

  • Markdown形式での記述:拡張子は.txtですが、中身はMarkdown形式です
  • シンプルな構造:HTMLやCSS、JavaScriptなどの複雑なコードを除外し、必要な情報だけを提供
  • AIフレンドリー:LLMが限られたコンテキストウィンドウ内で効率的に情報を処理できる
    まだまだロングコンテキスト対応のモデルは少ないですし、重要な観点かなと思います!🤖

実装方法

それでは、実際にNext.jsとmicroCMSを使ってllms.txtを実装する方法を見ていきましょう!

【結論はこちら】コード全体 (GitHub)
  1. Route Handlerの作成

方針としては、Next.jsのApp Routerを使って、Route Handlerでllms.txtのプレーンテキストをレスポンスする感じで実装します!

// src/app/docs/llms.txt/route.ts
  1. データ取得関数の実装

microCMSからブログ記事とカテゴリ情報を取得する関数を実装します。

async function fetchLlmsTxtData(): Promise<LlmsTxtData> {
  const [posts, categories] = await Promise.all([
    getAllBlogList({
      fields: "id,title,publishedAt,noIndex,description,category",
      orders: "-publishedAt",
    }),
    getAllCategoryList({
      fields: "id,name",
      orders: "createdAt",
    }),
  ]);

  return { posts, categories };
}

ここでのポイントは、必要最小限のフィールドだけを取得することです。これにより、APIレスポンスのサイズを削減し、処理速度を向上させることができます👌このあたりの柔軟さがmicroCMSの推しポイントです!

  1. コンテンツの生成

取得したデータを元に、llms.txt形式のコンテンツを生成します。

function buildLlmsTxt({ posts, categories }: LlmsTxtData, locale: string): string {
  const filteredPosts = filterAndSortPosts(posts);

  const sections = [
    generateHeader(),
    generateCategoriesSection(categories),
    generateContentSection(filteredPosts, locale),
  ];

  return sections.join("\n\n") + "\n";
}
  1. ヘッダー部分の生成

サイトの基本情報とライセンス情報を含むヘッダーを生成します。ライセンス情報はあってマイナスになることはないと思ったのでこのセクションを設けました。こちらはお好みで判断をお願いします〜

この形式はこちらのページに準拠しています。
下記の形式

# タイトル

> 概要など

## セクション名

- [ページのタイトル](https://link_url): その他の説明
function generateHeader(): string {
  return `# ${SITE_TITLE} (${SITE_DOMAIN})
> ${SITE_DESCRIPTION_EN}

## License
This content is made available under the ${LLMS_TXT_CONFIG.license}.
You are free to share and adapt this material for any purpose, including commercial use, as long as you provide appropriate attribution.

## Contact
For inquiries regarding content usage, corrections, or collaborations:
- Email: ${AUTHOR_E_MAIL}
- Website: ${baseURL}`;
}
  1. カテゴリ一覧の生成

カテゴリ情報をシンプルなリスト形式にします。

function generateCategoriesSection(categories: CategoriesContentType[]): string {
  if (categories.length === 0) {
    return "## Categories\n\nNo categories available.";
  }

  const categoryList = categories.map(({ name }) => `- ${name}`).join("\n");

  return `## Categories\n\n${categoryList}`;
}
  1. コンテンツ一覧の生成

ブログ記事の一覧を、タイトルとURLのペアにします。

function generateContentSection(posts: BlogsContentType[], locale: string): string {
  if (posts.length === 0) {
    return "## Content\n\nNo content available.";
  }

  const contentList = posts
    .map((post) => {
      if (!post.publishedAt) return null;
      
      const categoryId = getPrimaryCategoryId(post);
      const url = `${baseURL}/${locale}/blogs/${categoryId}/${post.id}`;
      const linkDetails = post.description ? `: ${post.description}` : "";
      return `- [${post.title}](${url})${linkDetails}`;
    })
    .filter(Boolean)
    .join("\n");

  return `## Content\n\n${contentList}`;
}
  1. キャッシュ設定

パフォーマンス向上のため、適切なキャッシュヘッダーを設定します。キャッシュについてはプロジェクトごとに相談ですね!あくまで一例です。

const LLMS_TXT_CONFIG = {
  cache: {
    maxAge: 3600, // 1時間キャッシュ
    staleWhileRevalidate: 86400, // 24時間は古いキャッシュを提供可能
  },
  retryAfter: 300, // エラー時の再試行推奨時間(5分)
  license: "Creative Commons Attribution 4.0 International License (CC BY 4.0)",
} as const;
  1. エラーハンドリング

ネットワークエラーや予期しないエラーに対して、適切なエラーレスポンスを返します。

function createErrorResponse(error: unknown): NextResponse {
  console.error("Error generating llms.txt:", error);

  const isNetworkError =
    error instanceof Error &&
    (error.message.includes("fetch") || error.message.includes("network"));

  const status = isNetworkError ? 502 : 500;
  const message = isNetworkError ? "Bad Gateway" : "Internal Server Error";

  return new NextResponse(message, {
    status,
    headers: {
      "Content-Type": "text/plain; charset=utf-8",
      "Retry-After": LLMS_TXT_CONFIG.retryAfter.toString(),
      "X-Content-Type-Options": "nosniff",
    },
  });
}

これで、ユーザーがllms.txtに簡単にアクセスできるようになりました!

実装結果

実装後、/docs/llms.txtにアクセスすると、以下のような形式でコンテンツが表示されます:

# わたしのブログ (xxxx.com)
> A technical blog about frontend development and engineering

## License
This content is made available under the Creative Commons Attribution 4.0 International License (CC BY 4.0).

## Contact
- Email: example@email.com
- Website: https://xxxxx.com

## Categories
- フロントエンド
- バックエンド
- インフラ

## Content
- [【Next.js】microCMSを利用したllms.txtについて](https://xxxxx.com): llms.txtの実装方法を解説
【結論はこちら】コード全体 (GitHub)

まとめ

今回は、Next.jsとmicroCMSを使ったllms.txtの実装方法について解説しました!

実装のポイントをまとめると:

  1. シンプルな構造:必要な情報だけを提供することで、AIが効率的に情報を理解できる
  2. 適切なキャッシュ:パフォーマンスを考慮したキャッシュ設定
  3. エラーハンドリング:ネットワークエラーなどに対する適切な対応

llms.txtは比較的新しい概念ですが、AI時代のウェブサイト最適化として今後重要性が増していくと思います!

最後まで読んでいただきありがとうございました🙏
ご意見・ご質問などありましたらお気軽にコメントください😊

個人のメディアもあります!

https://ryotablog.jp/ja/blogs

この個人メディアの構成について

https://zenn.dev/ryota_09/articles/71dab6d4dae52f

参考リンク

Discussion