Next.jsベースのブログを作ってみた（サンプルを利用して）

# はじめに

この記事はインフォコム株式会社の非公式Advent Calendarの22日目の記事です。

# やったこと

個人的に作ったアプリの紹介のために、GitHub Pages上でブログを立てました。静的サイトジェネレーターを使うことは決めていましたが、以前から気になっていたNext.jsを使ってみることにしました。

- React.js + Next.js を使った静的サイトジェネレーター(Static Site Generator)で構築
    - こちらのサンプル [gh640/nextjs-blog-example-ja](https://github.com/gh640/nextjs-blog-example-ja) をほぼそのまま利用させてもらいました（MITライセンス）
- カスタムドメインを利用

# サンプルの使い方

## 準備
[gh640/nextjs-blog-example-ja](https://github.com/gh640/nextjs-blog-example-ja) に記載されている通り、次のように準備します。

- node.js, npmをインストール ... 今回 node v16.0 を使いました
- コードをクローン
- npm install を実行

これで、必要なモジュールがインストールされます。主なモジュールは次の通りです。
- next
- react
- react-dom
- react-markdown
- remark
- remark-gfm
- remark-html
- remark-prism

（サンプルの最新のコードでは、利用するモジュールが変更されています）

さらに、見出しへのアンカー追加と目次生成のため、次のモジュールを追加しました。

- remark-slug
- remark-toc


## 記事の追加

### フォルダー構成

サンプルのコードでは、フォルダ構成が次のようになっています。

- components
- content
    - posts ... ここに記事を置く
- lib
- pages
- public
    - images ... ここに画像を置く

### 記事の保存場所

記事は contents/post/ の下に markdown形式で記述します。
記事で利用する画像は、public/images/ の下に追加し、記事には次の形式で埋め込みます。

```:markdownに記述
![説明](/images/ファイル名)
```

## 公開

```:ターミナルで実行
npm run build
npm run export
```

これで out/ 以下に静的なファイルが生成されます。この中身をWebサーバーに設置すれば、公開することができます。（今回は手元での確認用にVS CodeのLive Serverプラグインを使いました）

※サンプルのままでは、静的生成後のファイルのリンクが正しく貼れていなかったので、このあと記述するように変更を加えています。




# 今回手を入れた部分

ほとんど元のサンプルのままですが、一部変更を加えています。また、GitHub Pagesを使って公開するための手順を組み込みました。

- ページ生成処理の変更
    - indexページからの個別記事へのリンクの変更
    - 記事内での見出しへのアンカーの埋め込み
    - 目次の生成
- サイト公開の仕組みの追加
    - GitHub Actionsを使ったビルド、GitHub Pagesでの公開
    - カスタムドメインの設定

# ページ生成処理の変更

## indexページからの個別記事へのリンクの変更

indexページ（トップページ）から各記事へのリンクが、（プレビュー時やNext.jsサーバー起動時には大丈夫なのですが）静的ページ生成( npm run export )の時には正しく遷移しない状態になりました。今回は静的生成のみ利用するので、そちらに合わせて明示的に".html"の拡張子を指定することにしました。

```js:pages/index.jsの抜粋(before)
<h2><Link href="/posts/[id]" as={`/posts/${post.slug}`}><a>{post.title}</a></Link></h2>
```

```js:pages/index.jsの抜粋(after)
<h2><Link href="/posts/[id]" as={`/posts/${post.slug}.html`}><a>{post.title}</a></Link></h2>
```

この結果、逆にプレビュー時（npm run dev）や、サーバー起動時（npm run start）にはリンクが正しく遷移しなくなってしまいました。本当はどちらも適切に動作する修正方法があるはずですが、そこは分かっていません。

## 記事内での見出しへのアンカーの埋め込み&目次生成

見出しへのアンカーの埋め込みには remark-slug を、目次の生成には remark-toc を使いました。lib/content-loader.js を次のように変更しています。

```js:lib/content-loader.js抜粋
import path from "path"

// ... 省略 ...

import matter from "gray-matter"

// --- 追加---
import remarkslug from "remark-slug"
import remarktoc from 'remark-toc'

// ... 省略 ...


/**
 * Markdown のファイルの中身をパースして取得する
 */
const readContentFile = async ({ fs, slug, filename }) => {

  // ... 省略 ...


  const parsedContent = await remark()
    .use(remarkslug) // 追加
    .use(remarktoc, { heading: '目次', maxDepth: 2 })  // 追加
    .use(html)
    .use(gfm)
    .use(() => genAttrsAdder("image", {loading: "lazy"}))
    .use(prism)
    .process(matterResult.content)

  // ... 省略 ...

  return {
    title,
    published: formatDate(rawPublished),
    content,
    slug,
  }
}

```

.use(remarkslug) 、 .use(remarktoc, ... ) を追加することで、アンカー埋め込みと、目次生成を行っています。

### アンカーへのリンク

実際にアンカーに飛ぶリンクを付けるにはmarkdownファイル中で次の様に記述します。

```md:

# 章の見出し

[リンクをつけたいテキスト](#user-content-章の見出し)」

```

- "user-contents-"を頭に付与。見出し中にスペースが含まれる場合は、"-"（ハイフン）として指定

### 目次の生成

目次を生成するには、markdownで次の様にページの最初に「目次」の見出しを記述します。

```md:
# 目次

# 最初の章

# 第2の章

```

「目次」の見出しの箇所に目次が生成されるのは、.use(remarktoc ...)で引数に「heading: '目次'」を指定しているためです。


# サイト公開の仕組み


## GitHub Actionsの利用

ブログの公開には、GitHub ActionsとGitHub Pagesを使いました。今回指定したアクションは次の通りです。

```yaml
name: Release GitHub Page

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  workflow_dispatch:

env:
  NODE_VERSION: '16'

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - name: Install packages
        run: npm install
      - name: build blog
        run: npm run build
      - name: export blog
        run: npm run export

      # 生成した結果を保存しておく
      - name: upload artifact
        uses: actions/upload-artifact@v2
        with:
          name: export_out
          path: ./out

      # gh-pagesを使って公開(private key: DEPLOY_KEY_NAME)
      - name: deploy
        uses: peaceiris/actions-gh-pages@v3
        #env:
        with:
          deploy_key: ${{ secrets.DEPLOY_KEY_NAME }} # 鍵の名前を指定
          PUBLISH_BRANCH: gh-pages
          PUBLISH_DIR: ./out
          cname: sever.domain.com #カスタムドメインを指定

```

全体の流れは次の通りです。

- 最新のソース（とコンテンツ）を取得
- Node.jsやモジュールを準備
- ブログの記事を静的生成
    - npm run build
    - npm run export
- 生成した内容をアーチファクトとして保存（目視で確認するため）
- GitHub pages にデプロイ
    - デプロイに使う公開鍵、秘密鍵はあらかじめリポジトリの設定から指定しておく
        - 公開鍵 ... Settings - Deploy keys
        - 秘密鍵 ... Settings - Secrets - Actions secrets
    - カスタムドメインを指定
        - ※ _sever.domain.com_ は、実際には自分で取得したドメインを指定

当初うまくデプロイできなかったのですが、鍵の指定が間違えていたことと、カスタムドメインの指定が正しくなかったことが原因でした。


## カスタムドメイン

GitHub Pagesで公開したWebサイトのURLはユーザー名を「username」リポジトリ名を「reponame」とすると、次の形式になります

- username.github.io/reponame/

ところが、Next.jsを使ったサンプルで静的生成した場合、out以下のファイルはドメインの直下 (username.github.io/）に置かれる前提で生成されます。

これを特定のディレクトリの下に設置される前提でファイルを生成させる設定があるはずですが、調べていません。今回はブログを独自ドメインで公開したかったので、カスタムドメインを利用することにしました。

自分が所有しているドメイン (mydomain.com とします）を使って、例えば blog.mydomain.com を作り、DNSで次のように設定します。

- blog.mydomain.com のCNAME → username.github.io

同時に、GitHubのレポジトリで次の様に設定します。

 - Settings - Pages - Custom domain に「blog.mydomain.com」を設定

すると、username.github.io/reponame/ の代わりにカスタムドメイン blog.mydomain.com が利用できます。

この設定は、GitHub ActionsでGitHub Pagesへのデプロイを行うとクリアされてしまうので、Actionsの処理の中で、毎回設定し直すように指定しています。


# 宣伝

今回作ったブログがこちらです。自作のアプリを紹介しているので、よかったら使ってみてください。

- oralabo blog ... [https://oralabo.wlab.dev](https://oralabo.wlab.dev)