0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Githubレポジトリを1つの関数でCMSにするライブラリを作ってみた。

0
Posted at

はじめに

個人ブログやポートフォリオサイトを作るとき、「コンテンツの管理どうしよう」という問題に必ずぶつかります。

ContentfulやNotionをCMSとして使う方法も定番ですが、

  • 無料プランの制限が気になる
  • 専用の管理画面にログインするのが面倒
  • どうせMarkdownで書くなら、Gitで管理したい

という思いがありました。

一方、GitHubはすでにMarkdownファイルのホスティング場所として使っているわけで、「GitHub自体をCMSとして使えればそれで十分では?」と考えたのが、このライブラリを作ったきっかけです。


作ったもの

structcms — GitHub リポジトリをヘッドレスCMSとして使うための TypeScript ライブラリです。

npm install @asahi-fj/structcms

コンセプトはシンプルで、「1つの関数を呼ぶだけでコンテンツが取れる」です。


使い方

1. コンテンツリポジトリの構造

GitHub上に(プライベートでもOK)リポジトリを作り、以下のような構造でコンテンツを置きます。

your-content-repo/
└── contents/
    ├── hello-world/
    │   ├── content.md
    │   └── meta.json
    └── getting-started/
        ├── content.md
        └── meta.json

meta.json にタイトルや日付などのメタデータを書き、content.md に本文を書くだけです。

// contents/hello-world/meta.json
{
  "title": "Hello World",
  "description": "My first post",
  "publishedAt": "2026-04-11",
  "draft": false,
  "tags": ["intro"]
}
<!-- contents/hello-world/content.md -->
# Hello World

Welcome to my first post. This content is stored on GitHub.

2. GitHub Token の発行

GitHub → Settings → Developer settings → Personal access tokens から、対象リポジトリへの Contents: Read 権限を持つトークンを発行します。

3. コードを書く

import StructCms from "@asahi-fj/structcms";

const cms = StructCms({
  token: process.env.GITHUB_TOKEN!,
  owner: "your-org",
  repo: "your-content-repo",
});

const posts = await cms.getAll();
// [{ slug: "hello-world", content: "# Hello World\n...", meta: { title: "Hello World", ... } }]

StructCms() に設定を渡すだけで、あとは cms.getAll() を呼ぶだけ。


返ってくるデータの型

type Meta = {
  title: string;
  description: string;
  publishedAt?: string;
  draft?: boolean;
  tags?: string[];
};

type ContentEntry = {
  slug: string;    // contents/ 以下のディレクトリ名
  content: string; // Markdownの生文字列
  meta: Meta;
};

よくある操作のパターン

公開済み記事だけ取得

const posts = await cms.getAll();
const published = posts.filter((p) => !p.meta.draft);

タグで絞り込み

const typescriptPosts = posts.filter((p) =>
  p.meta.tags?.includes("typescript")
);

新しい順にソート

const sorted = posts.sort((a, b) =>
  (b.meta.publishedAt ?? "").localeCompare(a.meta.publishedAt ?? "")
);

Next.js との組み合わせ

App Router のブログページへの組み込み例です。

// lib/cms.ts
import StructCms from "@asahi-fj/structcms";

export const cms = StructCms({
  token: process.env.GITHUB_TOKEN!,
  owner: process.env.GITHUB_OWNER!,
  repo: process.env.GITHUB_REPO!,
});
// app/blog/page.tsx
import { cms } from "@/lib/cms";

export default async function BlogPage() {
  const posts = await cms.getAll();
  const published = posts.filter((p) => !p.meta.draft);

  return (
    <ul>
      {published.map((post) => (
        <li key={post.slug}>
          <a href={`/blog/${post.slug}`}>{post.meta.title}</a>
        </li>
      ))}
    </ul>
  );
}

サーバーコンポーネントで await cms.getAll() を呼ぶだけで、コンテンツ一覧が取れます。


環境変数

.env に以下を設定します。

GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
GITHUB_OWNER=your-org
GITHUB_REPO=your-content-repo

なぜ既存のCMSを使わなかったのか

structcms Contentful / Notion
管理UI GitHub(使い慣れたもの) 専用の管理画面
コスト GitHub無料枠で十分 プランによって制限あり
バージョン管理 Gitで完結 独自の履歴管理
Markdown そのまま書ける エディタ依存
セットアップ 関数1つ SDK + 環境設定

「すでにGitHubを使っているなら、それ以上のツールを増やさなくていい」という発想です。


今後やりたいこと

  • cms.getById(slug) の実装(coming soon)
  • キャッシュ対応
  • 画像ファイルのサポート
  • Zodによるメタデータのバリデーション

おわりに

「GitHubをCMSにする」というアイデア自体は目新しくはないかもしれませんが、ライブラリとして1関数で使えるようにパッケージ化したところが今回のポイントです。

個人ブログや小規模なドキュメントサイトであれば、ContentfulやNotionを導入するより手軽に使えると思います。ぜひ試してみてください!

npm install @asahi-fj/structcms
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?