MarkdownをGitHubに上げるだけでブログを公開できる仕組みをサクッと作ってみた


世の中には便利なCMSプロダクトがたくさんありますが、ぶっちゃけエンジニアだったらWeb上のフォームでコンテンツを入力するより、ローカルでMarkdownを作ってサクッとGitHubにPushするだけでブログ公開できたほうが楽だなーと思ったので作ってみました。

できるようになること

下記のようなインデックスページとブログページをMarkdownを作るだけで生成できるようになります。

ブログ公開の手順は下記の通りでとても簡単です。

  1. Markdownでブログを書く
  2. インデックスページを自動生成する
  3. GitHubにPushして自動デプロイ

実装

それでは実装していきます。

主要パッケージのバージョン

この記事を書いている時点の主要パッケージのバージョンは下記の通り。
以降は下記のパッケージが入っている前提で説明します。

package.json
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-markdown": "^4.3.1",
    "react-router-dom": "^5.1.2",
    "react-scripts": "3.4.3",
    "styled-components": "^5.2.0",
    "typescript": "^4.0.3",
    "@material-ui/core": "^4.9.11",
    "@material-ui/icons": "^4.9.1",

今回の肝となっているMarkdownからwebページを生成する処理はreact-markdownを使っています。
https://github.com/rexxars/react-markdown

Webサイトの準備

今回はReactを使ってWebサイトを作成します。
サイトの公開にはVercelを使っています。

Vercelを使ってReactで作ったアプリケーションを公開する方法は公式ドキュメントに載っているのでそちらを参照してください。
Githubと連携してしまえばPushするだけでデプロイされるのでものすごく簡単ですよ。
https://vercel.com/guides/deploying-react-with-vercel-cra

create-react-appで作ったwebサイトからサンプルコードを漏れなく消す』という記事でWebサイトをReactで作って最低限の状態まで作る手順を書いているのでそちらも見ていただけると嬉しいです。

ブログページの構成

/blogsでブログページへのインデックスを表示するページを作ります。
ブログページのURLは/blogs/:titleとします。:titleのところがブログごとに変動します。

これに合わせてrouterを作ります。

src/pages/index.tsx
// ※routerの箇所のみ抜粋しています
const Blogs = lazy(() => import('./Blogs'));

<Switch>
  <Route path="/blogs" component={Blogs} />
</Switch>
src/pages/Blogs/index.tsx
import React from 'react';
import { Route, Switch, Link } from 'react-router-dom';
import { List, ListItem, ListItemText } from '@material-ui/core';
import Blog from './Blog';
// data.jsonは後ほど紹介するバッチで自動生成しているjsonファイル
// ブログのインデックス情報を定義しています
import mds from './data.json';

export default () => {
  // react-markdownの機能を使ってmarkdownからページを生成
  const blogs = mds.map((md) => (
    <Route key={md} path={`/blogs/${md}`}>
      <Blog mdPath={`/mds/${md}.md`} />
    </Route>
  ));
  // mdsを基にブログページへのリンクを生成
  const index = mds.map((md) => (
    <ListItem button component={Link} to={`/blogs/${md}`}>
      <ListItemText primary={md} />
    </ListItem>
  ));
  return (
    <Switch>
      {blogs}
      <Route>
        <List component="nav" aria-label="secondary mailbox folders">
          {index}
        </List>
      </Route>
    </Switch>
  );
};

ブログページを作成

Markdownはpublic/mds/配下に格納することにしました。
とりあえずサンプルとしてfirst.mdとsecond.mdをMarkdown形式で作りました。

public/mds/first.md
# first blog

初めてのブログ
public/mds/second.md
# second blog

2 つめのブログ

json自動更新

public/mds/配下のファイルを読み込み、インデックスページの情報源data.jsonを自動生成するバッチを作りました。

updateBlogIndex.js
const fs = require('fs');

fs.readdir('./public/mds/', (err, files) => {
  if (err) throw err;
  const convertedFiles = files.map((file) => (
    file.replace(/\.md$/, '')
  ));
  console.log(convertedFiles);

  const j = JSON.stringify(convertedFiles.reverse());
  console.log(j);
  fs.writeFileSync('./src/pages/Blogs/data.json', j);
});

上記を実行すると下記のようなjsonファイルがブログタイトル(ファイル名)の配列が書かれたjsonが生成されます。

src/pages/Blogs/data.json
["second","first"]

本番環境へデプロイ

これで実装は完了です。
かなり簡単ですよね。むしろ下手にCMS導入するより簡単です!!(もちろんCMSはもっと多機能ですがw

#ブログ公開

最初に書いた通り下記の3つの手順でできます。

Markdownでブログを書く

public/mds/にmarkdown形式でブログを書きます。
現在の仕様だとファイル名がタイトルになります。

インデックスページを自動生成する

updateBlogIndex.jsを実行してインデックスページを生成します。

GitHubにPushして自動デプロイ

Vercelを使っているのでGithubの特定のブランチにPushすると自動デプロイされます。

今後やりたいこと

とりあえずMarkdownを作るだけでブログを公開するという最低限の仕組みは作れましたが、まだCMSとしては弱すぎるので下記は実装したいと思っています。

インデックス生成バッチを手動実行するのが面倒

手動実行とはえてして忘れるものです。
CIなどで自動実行させるようにして忘れないようにするべきです。

インデックスのページネーション

今は1つのページにすべてのインデックスが表示されてしまうので、ブログ数が大量に増えたら破綻します。
ページネーションさせるなどを考える必要があります。

ブログの管理方法を考える

今は名前の降順にインデックスが作成されます。
ファイルの命名規則を決めて日付をいれるなどをすれば今の仕様でも管理できるかも知れませんが、別途投稿日などを持つようにして管理しやすいようにしたい。
また、public/mds/直下に格納しているが投稿日などでディレクトリ階層を作ったほうが管理しやすくなりそう。

ブログページに投稿日を入れたい

ブログはいつ投稿されたかという情報が大事なので、ブログページに投稿日を入れるようにしたい。
ブログのファイル名などに投稿日を持たせるようにして、jsonを生成するときに日付情報を持たせるようにするなど方法を検討したい。