RYO620
DESIGN & DEVELOPMENT
Nuxt.js + microCMS + Firebase + GitHub Actions で JAMstack なブログに移行した
Ryosuke

Nuxt.js + microCMS + Firebase + GitHub Actions で JAMstack なブログに移行した

長く取り組んでいたブログ移行がようやく終わったのでその一発目の記事です。 Nuxt.js + microCMS の JAMstack なブログを TypeScript で書いて GitHub Actions で Firebase に自動デプロイできるようにしました(なんかラノベのタイトルみたい)。 ここ1年くらいでよく見かけるようになった構成でしょうか。これまで Wordpress + レンタルサーバ構成でしたがほぼほぼ JavaScript で完結するのでだいぶスッキリしました。

Nuxt.js は v2.14.0、TypeScript で書きました。


ブログを Markdown で書く

WordPressのpタグ自動挿入も便利なのですが、今後記事リソースを有効活用するためにもう少し汎用的な形式のMarkdownで記述することに。 汎用的とは言いつつも、どうしても classwidth を書きたい箇所もあったので そのあたりをカスタムできるプラグインを探しました。 Marked がちょうど良さげ。

以下はaタグをカスタムしたもの。 外部リンク時に target="_blank" rel="noopener noreferrer" を追加したり、mustache記法でクラスなどの属性値を付与できます。

import marked from 'marked' const Renderer = new marked.Renderer() // aタグをカスタム Renderer.link = (href, title, text) => { // title追加 const titleAttr = title !== null ? `title='${title}'` : '' // 外部リンクのtarget blank const targetAttr = href!.includes('http') ? 'target="_blank" rel="noopener noreferrer"' : '' // mustache記法による属性追加 // クォートを変換 text = text.replace(/&quot;/g, '"') const regex = /\s*\{\{(.*)\}\}/ const addedAttr = text.match(regex) !== null ? text.match(regex)![1] : '' return `<a href='${href}' ${addedAttr} ${targetAttr} ${titleAttr}>${text.replace( regex, '' )}</a>\n` } // カスタムしたレンダラを指定 marked.setOptions({ renderer: Renderer, }) // HTML変換された文字列を返す marked(this.markdownText)

参考: marked: Overriding renderer methods


microCMSからデータ取得

microCMSの管理画面microCMSの管理画面

microCMS は国産のヘッドレスCMS。 機能追加/改善のサイクルが早く、個人では十分すぎる無料枠もあるのでありがたく使わせてもらっています。

asyncData() 内で axios を使って microCMSの API を叩きます。

~/plugin/fetchBlogData.ts
import Vue from 'vue' import { Context } from '@nuxt/types' import { Route } from 'vue-router' export default Vue.mixin({ async asyncData({ $axios, $config, route }: Context) { const client = $axios.create({ baseURL: $config.MICRO_CMS_API_URL, headers: { 'X-API-KEY': $config.MICRO_CMS_API_KEY, }, }) try { const [postList, categoryList, tagList] = await Promise.all([ client.get( `post?fields=id,title,publishDate,category.id,category.name,keyvisual.url` ), client.get('category?fields=id,name&limit=100'), client.get('tag?fields=id,name&limit=100'), ]) return { postCount: postList.data.totalCount, postList: postList.data.contents, categoryList: categoryList.data.contents, tagList: tagList.data.contents } } catch (err) { console.error(err) } }, })

上記は記事一覧系ページの asyncData。全記事一覧の他、任意のカテゴリでの一覧、任意のタグでの一覧、月別など色んなページで使うので minxin化しました。 記事一覧と同時に全カテゴリ一覧と全タグ一覧を取得しています。これはブログメニュー内にリンクを表示するため。

ブログメニュー内のリンクブログメニュー内のリンク

microCMSのAPIキーは nuxt.config.ts 内の privateRuntimeConfig に書いています。

nuxt.config.ts
const config: Configuration = { // // ... // [process.env.NODE_ENV === 'development' ? 'publicRuntimeConfig' : 'privateRuntimeConfig']: { MICRO_CMS_API_KEY: process.env.MICRO_CMS_API_KEY, MICRO_CMS_API_URL: process.env.MICRO_CMS_API_URL, }, // // ... // } export default config

公式ブログで言及されていますが、 process.env経由 でも隠蔽できないのには驚き! これ知らなかった、危ないなー……。

参考: NuxtのJamstack構成におけるAPIキーの隠蔽方法について

request
client.get( `post?fields=id,title,publishDate,keyvisual.url` )

API は様々なクエリが使えます。 fields=title で取得する記事の要素の選別。 limit=10offset=10 で取得件数と開始インデックスの指定。 記事一覧を取得すると postList.data.totalCount に記事総数も入っているため、これと合わせて使うことでページネーションが実装できます。

filters= は絞り込みのクエリで、 filters=category[equals]web でwebカテゴリに絞った記事一覧に。 tag のように記事が複数の要素を持つ場合は filters=tags[contains]unity 、 月別に出したい場合は filters=publishDate[begins_with]2020-08 という感じです。


ホスティング

使い慣れてる Firebase Hosting にしました。 nuxt-ts generate で静的ファイルを出力して、そのファイルをまるっとデプロイするだけです。

WordPress製の旧ブログ時と変更したいURLパスがあったので、 firebase.json でリダイレクトを指定しました。 source に glob パターンで記述することで destination に指定したパスにリダイレクトしてくれます。

firebase.json
{ "hosting": [ { "target": "production-site", "public": "dist", "ignore": ["firebase.json", "**/.*"], "redirects": [ { // /2020/08/POST_NAME/ というURLを /post/POST_NAME/ に変更 "source": "/[0-9][0-9][0-9][0-9]/[0-9][0-9]/:post*", "destination": "/post/:post", // 恒久的なリダイレクト "type": 301 }, { // /categories/gamedev/ というURLを /category/gamedev/ に変更 "source": "/categories/:id*", "destination": "/category/:id", "type": 301 }, { "source": "/page/:num*", "destination": "/:num", "type": 301 } ] } ] }

あと、このブログ引っ越しに伴って、Xserverで管理してた独自ドメインを Google Domains に移管しました。 この作業が一番面倒でしたね、AレコードとかTXTレコードとか未だによくわからない。 どっちも Google サービスなんだからボタン一発で接続してほしいな……。


GitHub Actions で自動化

初めて使いました! 今回は 「masterブランチへのpush」 or 「microCMSの記事の更新」 をトリガーにして、ビルドとデプロイが走るようになっています。

リポジトリに .github/workflows/generate-and-deploy.yml の YAMLファイルを作り、以下を記述しました。

generate-and-deploy.yml
name: Generate static page & Deploy to Firebase Hosting on: repository_dispatch: types: [update_post] push: branches: - master jobs: generate-deploy: name: GENERATE and DEPLOY runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v1 with: node-version: '12.x' - name: Install dependencies run: yarn - name: Generate static page env: MICRO_CMS_API_KEY: ${{ secrets.MICRO_CMS_API_KEY }} MICRO_CMS_API_URL: ${{ secrets.MICRO_CMS_API_URL }} run: yarn generate - name: Deploy to Firebase Hosting run: yarn deploy --token=${{ secrets.FIREBASE_TOKEN }}

ビルドに使用する API_KEY 等の環境変数、 Firebase のトークンは GitHub のシークレットキーに登録しています。

こんな感じに書いておくと runprocess.env.MICRO_CMS_API_KEY が使えます。

- name: Generate static page env: MICRO_CMS_API_KEY: ${{ secrets.MICRO_CMS_API_KEY }} MICRO_CMS_API_URL: ${{ secrets.MICRO_CMS_API_URL }} run: yarn generate

参考: 暗号化されたシークレットのワークフロー内での利用


まとめ

そんなにアクセスがあるサイトでもないし環境構築の学習が目的だったのですが、非常に学びの多い取り組みでやって良かったです。 もうちょっとカスタムしたい箇所もあるので少しずついじっていこうかなと思います。

microCMS には下書きを確認するAPIもあるので、開発環境からそれが見えるようにしたいところ。 その場合は SSR かな、Sparkプランだと Cloud Functions(node.js 10)が使えないっぽいけど……。

デザインも数年前から代り映えしないのでどうにかしたい。