Netlify + Decap CMS + Nuxtでブログサイトを構築する

## 概要
NetlifyでWebサイトを運用していたが、新しくブログ機能を追加実装することにした。
内容としては、
・同一ドメインのサブフォルダとしてアクセスできるようにする。
　（`https://hostname/blog/`にアクセスすると、ブログ記事の一覧が見れるイメージ）
・既存のGithubリポジトリをそのまま活用する。
・Decap CMS + Nuxtを使うことは決まっている。
　Decap CMS → Markdown形式のコンテンツをWeb上で編集・管理できるGUIツール
　Nuxt → Markdownなどの静的ファイルを読み込んで、静的Webサイトを生成するフレームワーク
　　nuxtはVer3.17.5
　　@nuxt/contentはVer2.13.4

## 構成
自動生成されるものは記載対象外。
後述の手順のなかで作成・更新する必要があるものを明記している。
```GitHubリポジトリのディレクトリ構成.
project-root
├── /public/
│   ├── index.html            ← 既存のトップページ
│   ├── /admin/               ← Decap CMS
│   │   ├── config.yml        ← Decap CMSの設定情報を管理（GiHubとの連携情報など）
│   │   └── index.html        ← Decap CMSのUI
│   └── /blog/                ← からっぽ（Netlify公開時にここにブログコンテンツを入れて公開）
├── /blog/                    ← Nuxtで構築（Nuxtアプリのルートで今回追加したもの）
│   ├── /content/*.md         ← Decap CMSで保存された記事
│   ├── /pages/               ← 
│   │   │── [slug].vue        ← 記事ページ
│   │   └── index.vue         ← ブログ一覧ページ
│   │── nuxt.config.ts        ← Nuxtアプリ全体の設定ファイル
│   └── package.json          ← Node.jsプロジェクトの設定ファイル
└── /netlify.toml             ← Netlifyのビルドやデプロイの設定を記述するファイル
```

Netlifyで公開するときは、Githubリポジトリの構成のままではなく階層が深くならない構成にしている。（これも後述するが、ビルド・デプロイの設定により自動で構成を変えることができる）
```Netlifyで公開するときのディレクトリ構成.
project-root
├── index.html            ← 既存のトップページ
├── /admin/               ← Decap CMS
│   └── config.yml        ← Decap CMSの設定情報を管理（GiHubとの連携情報など）
│   └── index.html        ← Decap CMSのUI
└── /blog/                ← からっぽ（Netlify公開時にここにブログコンテンツを入れて公開）
    │── /folder/          ← ブログ記事ごとのフォルダ
    └── index.html        ← ブログ一覧ページ
```
## ① Decap CMSの導入・構築
**ブログの記事をmdファイルとして作成するのに必要。**

### 1.public/admin/config.ymlファイルを作成する。
ここでは、GitHub OAuth認証ではなくNetlify Identity + Git Gateway認証としている。
```config.yml
backend:
  name: git-gateway
  repo: GitHubユーザー名/GitHubリポジトリ名
  branch: main

media_folder: "blog/images/uploads" # アップロード画像の保存先　
public_folder: "blog/images/uploads"

collections:
  - name: "blog"
    label: "Blog"
    folder: "blog/content"  # Markdown記事を置く場所
    create: true
    slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
    fields:
      - { label: "Title", name: "title", widget: "string" }
      - { label: "Date", name: "date", widget: "datetime" }
      - { label: "Body", name: "body", widget: "markdown" }
```

### 2.public/admin/index.htmlファイルを作成する。
```index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Content Manager</title>
    <!-- Include the script that enables Netlify Identity on this page. -->
    <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
  </head>
  <body>
    <!-- Include the script that builds the page and powers Decap CMS -->
    <script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
  </body>
</html>
```

### 3.Netlifyの管理画面で設定を行う。
サイトを選択して「Project Configuration」をクリックする。
・「Identity」を有効にする。
・「Git Gateway」を有効にする。
・「External providers」に「GitHub」を追加する。
・「Registration preferences」は最初「Open」にしてユーザーが追加できたら、
　（Decap CMSの画面でGitHubアカウントのログインを行うと自動的にユーザーが追加される）、
 　以降は「Invite only」に戻しておく。


## ② Nuxtの導入・構築
**Decap CMSで作成したmdファイルを読み取って静的Webサイトを作り出すのに必要。**

### 4.project-root直下にblogフォルダを作成する。
### 5.Homebrewを使用してNode.jsをインストールする。
```shell
brew install node
```
＜補足＞
Node.jsをインストールすると、npmとnpxコマンドが使えるようになる。
npm はnode package managerの略で、Node.jsパッケージの管理ツールのこと。
npx はnode package executerの略で、Node.jsパッケージの実行を行うツールのこと。

### 6.以下コマンドを実行して、blogフォルダをNuxtアプリ化する。
```shell
cd project-root
npx nuxi init blog
cd blog
npm install
npm install @nuxt/content@2
```
＜補足＞
npm list @nuxt/contentというコマンドでNuxtのバージョンが確認可能。
バージョン2と3では構造が異なるなどあるらしい。
ここでは安定版のバージョン2を指定している。

### 7.blog/nuxt.config.tsファイルを作成する。
```nuxt.config.ts
export default defineNuxtConfig({
  ssr: true,
  modules: ['@nuxt/content'],
  content: {
    dir: 'content',
  },
  app: {
    baseURL: '/blog/',
  },
  nitro: {
    preset: 'static'
  }
})
```

### 8.ルート直下にnetlify.tomlファイルを作成する。
```netlify.toml
[build]
  base = "blog"                       # blogフォルダに移動して実行
  command = "npm run generate"
  publish = "../public"               # blogフォルダから見たpublicフォルダのパスを指定

[[redirects]]
  from = "/admin/*"
  to = "/admin/index.html"
  status = 200
```
publicフォルダのなかをルート直下に直接展開して公開したいので、publishがこのような設定になっている。ここの指定を間違えるとおかしなことになるので注意が必要。
Netlify上でどのような構成で公開されているかは管理画面で確認が可能。
「Deploys」→「デプロイ履歴でPublishedマークの行を選択」→一番下の「Deploy file browser」に表示される。初めから想定通りにはいかないので、ここを見ながら調整する。

### 9.blog/package.jsonファイルを修正する。
scriptsのなかにある
"generate": "nuxt generate",
という１行を、以下のように修正する。
"generate": "nuxt generate && mv .output/public/* ../public/blog",

ビルドするとblog/.output/publicが生成されるのだが、その中身をpublic/blogに移動する、
ということをやっている。


### ここで一旦説明。
nuxt.config.ts、netlify.toml、package.jsonの３つは混同しやすい。

■nuxt.config.tsはNuxtアプリの設定ファイル
　なので当然Netlifyは関係無いし、「静的Webサイトをどういった設定のもと生成するか」
　を記述している。
■netlify.tomlはNetlifyのビルドやデプロイの設定を記述するファイル
　リポジトリ直下に置いて、Netlifyがビルド・デプロイ時に自動的に読み取る。
　なんのコマンドでビルドして、どのフォルダの中身を公開するかを記述している。
　Netlifyの管理画面でも同様の設定ができる。
■package.jsonはNode.jsプロジェクトの設定ファイル
　プロジェクト、スクリプトや依存パッケージの管理情報を記述している。
　今回だと、「npm run generate」を実行したら、内部的には
 「nuxt generate && mv .output/public/* ../public/blog」を実行してね。という意味になる。

 ### 10.blog/pages/に、vueファイルを２つ作成する。
```index.vue
 <template>
  <div>
    <h1>ブログ一覧</h1>
    <ul v-if="posts">
      <li v-for="post in posts" :key="post._path">
        <NuxtLink :to="post._path">{{ post.title }} - {{ post.date }}</NuxtLink>
      </li>
    </ul>
    <p v-else>記事が見つかりませんでした。</p>
  </div>
</template>

<script setup>
const { data: posts } = await useAsyncData('posts', () =>
  queryContent().sort({ date: -1 }).find()
)
</script>
```
```[slug].vue
<template>
  <article v-if="post">
    <h1>{{ post.title }}</h1>
    <p>{{ post.date }}</p>
    <ContentRenderer :value="post" />
  </article>
  <p v-else>記事が見つかりませんでした。</p>
</template>

<script setup>
const route = useRoute()

const { data: post } = await useAsyncData('post', () =>
  queryContent().where({ _path: `/${route.params.slug}` }).findOne()
)
</script>
```

## ③ 動作確認
以下のURLにアクセスしたときは、既存のトップページが表示される。
`https://hostname`

以下のURLにアクセスしたときは、Decap CMSのトップページが表示される。
`https://hostname/admin/`
「Login with Netlify Identity」をクリック
![スクリーンショット 2025-06-10 16.49.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15241/3fb55017-3bfe-4f6b-a55a-e51097e92138.png)
「Continue with GitHub」をクリックして、GitHubアカウントでログインする
![スクリーンショット 2025-06-10 16.50.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15241/e76a8666-f5e0-436d-899e-dbf155a059f8.png)
あとは管理画面上でマークダウン形式の記事を書くだけ。
mdファイルはGitHubリポジトリに直接保存される。
これをトリガーにしてNetlify上でビルド・デプロイが自動で走りページが公開される。

以下のURLにアクセスしたときは、
`https://hostname/blog/`
次のようなブログ一覧ページが表示される。
![スクリーンショット 2025-06-10 16.53.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15241/863d9253-2e85-4b28-8e2c-53c85ba399fe.png)