Nuxt3+microCMSでSSGしてJamstackサイトを作ろう(10/24更新)
表題の通り。Nuxt3のSSGがちゃんと動くようになったので国産のヘッドレスCMS・microCMSを使ってJamstackなブログやオウンドメディアを作ってみようや。
投稿した3ヶ月前ではFull Static Generateは出来なかったのですが、10月現在はできるようになりました! 当該部分を修正しています。
基本的にMicroCMSさん公式のブログに書いてあるチュートリアルに則り、追加としてSSGしてみただけの内容です。
事前準備・おことわり
- MicroCMS(ヘッドレスCMS)についてすでに知ってる前提で、BASE URL・API KEYを発行してることも前提とします
- Nuxt3は現在RC版です
Muxt3+MicroCMS+SSG
Nuxt3セットアップ
create-nuxt-app
の質問攻めも無く5秒で終わります。デフォルトでTypeScriptも使う設定です。
$ npx nuxi init <project-name>
$ cd ./<project-name>
$ npm install
また、SASS(SCSS)を使ってる人は以下を。Nuxt2の地獄の依存性エラーは起きません
$ npm install sass sass-loader fibers
記事一覧ページを作る
まず記事一覧のページを作ります。Nuxt3ではv2のようにデフォルトで/pages/
等のディレクトリは作られてないので自作しましょう。
<template>
<div>
<h3>MicroCMS+Nuxt3 SSG</h3>
<ol>
<li v-for="article in data.contents" :key="article.id">
<nuxt-link :to="`/${article.id}`">{{ article.title }}</nuxt-link>
</li>
</ol>
</div>
</template>
<script setup>
const { data } = await useFetch("/<API名>", {
baseURL: "https://<プロジェクト名>.microcms.io/api/v1",
headers: {
"X-MICROCMS-API-KEY": "<APIキー>",
},
});
</script>
Nuxt3はVue3なのでComposition APIでの記述が推奨されています。
Nuxt v2ではasyncData()
を使ってmicroCMSのAPIにアクセスしていましたが、Nuxt v3から追加されたuseFetch()
を使ってアクセスします。詳しくはこちらの記事を参照(ありがとうございます)
記事詳細ページを作る
記事表示部を作ります。
<template>
<main>
<h1 style="margin-bottom: 20px">{{ article.title }}</h1>
<p style="margin-bottom: 40px">
<time :datetime="article.publishedAt" v-text="article.publishedAt" />
</p>
<div class="post" v-html="article.content" />
</main>
</template>
<script setup>
const route = useRoute();
const slug = route.params.slug;
const { data: article } = await useFetch(`/<API名>/${slug}`, {
baseURL: "https://<プロジェクト名>.microcms.io/api/v1",
headers: {
"X-MICROCMS-API-KEY": "<APIキー>",
},
});
</script>
<style lang="scss" scoped>
* {
margin: 0 auto;
max-width: 800px;
}
:deep(.post) {
h2{font-size:24px;font-weight:700;margin:40px 0 1pc;border-bottom:1px solid #ddd}p,pre{line-height:1.8;letter-spacing:.2px}pre{background:#fafafa;border:1px solid #ddd;padding:1pc}
}
</style>
Nuxt 3では、動的ルートは_newsではなく[news]のように記述します。また、APIから帰ってくる定数の型定義も行っておくとより良いでしょう。
以下のように表示されればOK
API Keyを隠蔽する
CMSのコンテンツ表示の実装はできましたが、このままデプロイして公開するとAPIキーがもろバレして危ない。
本番環境で利用するには以下のように環境変数ファイルにAPI情報を格納して、PrivateとPublicで分けて情報を呼び出します。
-
.env
ファイルにAPI情報を格納
SERVICE_DOMAIN=***********
API_KEY=************************************
- 設定に追記
import { defineNuxtConfig } from 'nuxt'
const { API_KEY, SERVICE_DOMAIN } = process.env;
export default defineNuxtConfig({
privateRuntimeConfig: {
apiKey: API_KEY,
serviceDomain: SERVICE_DOMAIN
},
publicRuntimeConfig: {
apiKey: process.env.NODE_ENV !== 'production' ? API_KEY : undefined,
serviceDomain: process.env.NODE_ENV !== 'production' ? SERVICE_DOMAIN : undefined,
},
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
],
})
- 各ファイルにて
useRuntimeConfig()
で環境変数を呼び出す
<script setup>
const config = useRuntimeConfig();
const client = {
serviceDomain: config.serviceDomain,
apiKey: config.apiKey,
};
const { data } = await useFetch("/news", {
baseURL: `https://${client.serviceDomain}.microcms.io/api/v1`,
headers: {
"X-MICROCMS-API-KEY": client.apiKey,
},
});
</script>
※以前この記事を投稿した際は以下のようにSSGが出来なかったのですが、今は出来ました。
Nuxt3でfull static generateが出来ない!
Nuxt v2では途中からasyncData
を使い特定の設定をするとAPIのコールバックごと静的ファイルとして保存するfull static generateが使えたのですがNuxt3だと出来ない…
公式Docsによると完全な静的生成は未実装らしい…もうすぐRCから正式に移行しそうですが大丈夫でしょうか。。。
SSG&PaaSデプロイ
あとはCSSで整えるなりして各自のPaaSにデプロイするだけです。設定は以下の通り。
コマンド | nuxt generate |
---|---|
出力ディレクトリ | .output/public |
環境変数 | .envファイルの情報 |
DevToolのネットワークタブを見れば分かる通り、事前に_payload.js
にAPIから取得した情報が入っており、実APIへのリクエストを行っていないことがわかります。
デプロイエラーが出るとき
おそらくNuxt3プロジェクトをCloudflare Pagesにてデプロイする場合以下のエラーが出る事があります。
Nuxt CLI v3.0.0-rc.5
ERROR Only file and data URLs are supported by the default ESM loader
Node.jsのバージョンが低すぎる事が原因ですので環境変数に以下のようにNODE_VERSION
として指定しておきます。
最初、DevelopersIOさんの記事をもとに16.xx.xx
で指定したのですがえらったので15.10.0
で設定し直したら成功しました。
(備考)HTMLサニタイズ
v-html
で取得されたHTMLをそのまま表示させると何かと危ないのでサニタイズしておくとよいでしょう。(SSGで静的ファイルにビルド時に生成するので特にやる必要も無いかもしれません)
$ npm install sanitize-html
$ npm install -D @types/sanitize-html
以下のように変更。
+ import sanitizeHtml from "sanitize-html";
// <iframe>が含まれるなら許容設定追加
+ sanitizeHtml.defaults.allowedTags = sanitizeHtml.defaults.allowedTags.concat("iframe");
+ sanitizeHtml.defaults.allowedAttributes["iframe"] = ["*"];
<div class="post" v-html="article.content" />
+ <div class="post" v-html="sanitizeHtml(article.content)" />
その他わからないことがあれば公式ドキュメントを御覧下さい。
Discussion