ポートフォリオブログの管理・表示の仕組み

投稿日: 2026/04/09
更新日: 2026/04/09

ポートフォリオブログの管理・表示の仕組み

目次

  1. はじめに
  2. 全体構成
  3. 記事の管理方法
  4. 記事の表示方法
  5. まとめ

1. はじめに

このサイトのブログ記事は、WordPressのようなCMSを使わず、MarkdownファイルをCloudflare R2に置いてNext.jsで取得・表示する構成にしています。

シンプルな構成ですが、記事の作成から公開まで一連の流れがあるので、その仕組みをまとめておきます。

2. 全体構成

[ローカル: /work/posts]
  ├── markdown/         # 記事本体(Markdownファイル)
  ├── posts-list.json   # 記事メタデータ一覧
  ├── createMD.sh       # 新規記事作成スクリプト
  └── upload.sh         # R2へのアップロードスクリプト
        ↓
[Cloudflare R2]
  posts.shoat-portfolio.com/posts/
  ├── posts-list.json
  └── markdown/{id}.md
        ↓
[Vercel: Next.js]
  fetch で取得 → Markdownをレンダリング → ブログ表示
  • 記事の保管: Cloudflare R2
  • フロントエンド: Vercel(Next.js)
  • CDN: Cloudflare

3. 記事の管理方法

新規記事の作成

createMD.sh を実行すると、今日の日付で自動的にMarkdownファイルが生成されます。

bash createMD.sh

ファイル名は YYYYMMDD-NNN.md(例: 20260409-001.md)の形式で、同日に複数作成した場合は連番が振られます。

生成されるファイルには以下のフロントマターが含まれます。

---
title: 新しい記事タイトルをここに記述
date: 2026-04-09
update: 2026-04-09
description:
---

Markdownファイルの構成

記事はフロントマター(YAML形式のメタデータ)+本文の構成です。

フィールド内容
title記事タイトル
date投稿日
update最終更新日(upload.sh が自動更新)
description記事の説明文(OGPや一覧ページに使用)

posts-list.json

記事一覧ページ用のメタデータをまとめたJSONファイルです。upload.sh がMarkdownのフロントマターを読み取り自動生成します。

[
  {
    "id": "20260409-001",
    "title": "ポートフォリオブログの管理・表示の仕組み",
    "date": "2026-04-09",
    "update": "2026-04-09",
    "description": "...",
    "published": true
  }
]

published: false にすると一覧に表示されなくなるため、下書き管理に使えます。デフォルトは true です。

Cloudflare R2 へのアップロード

記事を書き終えたら upload.sh を実行します。

bash upload.sh

処理の流れは以下の通りです。

① Markdownの update 日付をファイルの最終更新日で自動更新
② フロントマターから posts-list.json を生成
③ dry run で差分を確認
④ 確認後、Cloudflare R2 へ同期(aws s3 sync)

③のdry runで変更内容を確認してから実行できるので、誤ってファイルを削除するリスクを減らせます。

4. 記事の表示方法

データの取得

Next.jsサイド(app/lib/blog/blog.ts)でR2からデータを取得しています。

記事一覧の取得:

// posts-list.json を fetch して一覧を返す
// published: false の記事は除外、日付降順でソート
const getSortedPostsData = async () => { ... }

記事本文の取得:

// markdown/{id}.md を fetch してフロントマター+本文を返す
const getPostData = async (id: string) => { ... }

どちらも ISR(Incremental Static Regeneration) を使用しており、60秒ごとにキャッシュを再検証します。

fetch(url, { next: { revalidate: 60 } })

Markdownのレンダリング

記事詳細ページ(app/blog/[id]/page.tsx)では react-markdown でMarkdownをHTMLに変換しています。

import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';

<ReactMarkdown remarkPlugins={[remarkGfm]}>
  {content}
</ReactMarkdown>

remark-gfm を使うことでテーブル・チェックボックス・取り消し線などGitHub Flavored Markdownの記法が使えます。

シンタックスハイライトは highlight.js で対応しています。

ページ構成

URL役割
/blog記事一覧(app/blog/page.tsx
/blog/{id}記事詳細(app/blog/[id]/page.tsx

記事詳細ページはタイトル・descriptionを使って動的にOGP/メタタグを生成するため、SNSシェア時にも各記事の情報が正しく表示されます。

5. まとめ

このブログの管理・表示の流れをまとめると以下の通りです。

  1. createMD.sh で新しいMarkdownファイルを生成
  2. Markdownを編集して記事を執筆
  3. upload.sh でCloudflare R2に同期
  4. Next.js(Vercel)がR2からデータをfetchして表示

CMSを使わないシンプルな構成ですが、スクリプトで自動化できる部分はまとめているので、記事の公開作業は比較的楽になっています。