世界を旅して暮らしたい放浪エンジニアブログ

Nuxt.jsで構築したブログをカイゼンした話

本ブログはNuxt.js + Contentful + Netlifyで構成されています。今回は本ブログを新たに改善した話をまとめたいと思います。

[ 目次 ]

はじめに

こんにちは、香港に住んでいるWEBデベロッパーのなかむ(@nakanakamu0828)です。

今回は本ブログを改善する為、リプレイスした話をまとめたいと思います。本ブログはNuxt.js + Contentful + Netlifyで構成されています。基本的なシステム構成はそのままにし、UIや内部のソースコード、パフォーマンスなど改善しています。

本ブログを構築した時の記事もご参照ください。

技術ブログを新しく作り直しました

それでは改善の内容をまとめていきます。

CSSフレームワークの変更(Bulma → TailwindCSS)

本ブログは元々Bulma(Buefy)を利用して、UIを構築していましたが、最近筆者が注目しているTailwindCSSに変更しました。

変更した理由は以下です。

  • BulmaのようなUIコンポーネントを提供するCSSフレームワークの場合、デザインをカスタマイズする際にclassを上書くことが多くCSSが汚くなりがち(私がCSSの能力不足なこともあります・・・)。
  • TailwindCSSの場合はユーティリティークラスの組み合わせから色々なパターンのUIを表現できるので、カスタマイズしやすい。
  • TailwindCSSはPurgeCSSと組み合わせることで、未使用のclass定義を削除しcssのサイズを圧縮できる
  • TailwindCSSのlg:〜のようなResponsiveなclassの定義がわかりやすい。
  • TailwindCSSのユーティリティクラスを利用する際に、どのようなstyleが適応されるか確認しやすくstyleの学習になる。

私個人としてはTailwindCSSを利用するほうがメリットが大きいと感じています。Vue.jsなどコンポーネント毎にスタイルを定義する際は、豊富なユーティリティクラスを用意しているTailwindCSSは強みです。

もちろんデメリットもあると思っています。UIコンポーネントが用意されていない為、Bulmaで実装するよりTailwindCSSで実装する方がより時間がかかりる印象です。

まずは使い込んで慣れていくことが大事ですね。

Atomic Designを採用し、コンポーネントを用意する

最近、「Atomic Designを採用しています」とかブログやLTのスライドでよく耳にするAtomic Designを今回は取り入れてみました。コンポーネントの役割・責務を明確にすることを目的として採用しました。
Atomic Designについては、以下の記事をご参照ください。有識者の方々のノウハウがとても参考になります。

Vuexの導入

ブログ開発当初は Nuxt.js + Contentfulのテンプレを元に作ってしまったので、ソースコードを整理されていませんでした。今回はVuexを導入して状態管理を行うように修正しています。

Vuex

クラシックモードがNuxt.jsのv3で非対応になるようなので、モジュールモードを利用して実装しています。

クラシックモードでは以下のようなwarningとなります。

Classic mode for store/ is deprecated and will be removed in Nuxt 3

今回は、投稿データやカテゴリー、タグなどContentfulとの連携が必要なデータを状態管理するようにしています。

UI・SEO改善

前提として私はエンジニアなのでデザインの知識をベースとして改善したというより、ユーザー目線やSEOの観点からUIを修正しました。
上述した通りCSSフレームワークも変更しているので、基本的には全て一からコーディングし直しています。

URL正規化を目指す改善

URLにマルチバイトを使わない

カテゴリーやタグページはContentfulで管理している名称をURLに利用していました。
URLのマルチバイトはエンコードされますが、URLが長くなりますし、なんだか気持ち悪い・・・
英数字(一部記号あり)のみのURLですっきりさせたいですよね。
今回はカテゴリーやタグをjsonファイルで管理し、名称に加えてslugを追加しています。

-- assets/json/categories.json
[
    {
        "name": "フロントエンド",
        "slug": "frontend"
    },
    {
        "name": "バックエンド",
        "slug": "backend"
    },
    {
        "name": "デザイン",
        "slug": "design"
    },
    {
        "name": "海外生活",
        "slug": "international-life"
    },
    {
        "name": "未分類",
        "slug": "other"
    }
]

Contentfulとjsonファイルで二重管理になってしまいますが、jsonファイルのデータを正として利用するようにしています。Contentfulでデータ管理しない理由は、

  • Freeプランなのでデータを節約したい
  • データ量も多くないのでjsonでローカルPCから修正して管理したい

と思ったからです。
結果以下のようにURLを変更しました。

■ Before
https://blog.nakamu.life/categories/フロントエンド/
ブログ改善 カテゴリーurl変更 before

■ After
https://blog.nakamu.life/categories/frontend
ブログ改善 カテゴリーurl変更 after

URLの最後の/(スラッシュ)をなくす

CSR(クライアントサイドレンダリング)時は、/(スラッシュ)が付かないのですが、generateした静的HTMLへのアクセスは/(スラッシュ)がついており、Google Search Consoleで重複コンテンツがあると怒られていました。

原因はgenerate時のオプションのsubFoldersです。このオプションはデフォルトがtrueになっており、trueの場合は全てのルーティングに合わせたディレクトリと、 index.html が提供されます。

-| dist/
---| index.html
---| posts/
-----| index.html

subFoldersオプションをfalseにすると、ーティングパスに従う形で HTML ファイルを生成します

-| dist/
---| index.html
---| posts.html

これで、CSR時も静的HTMLへのアクセスも/(スラッシュ)なしで統一されます。

問い合わせ画面の作成

モーダルで問い合わせできるようにしていましたが、今回は問い合わせページを新たに作成しました。
今後は過去の問い合わせ内容・返信内容を乗せてコンテンツを増やし、より多くのユーザーさんに見てもらえるようにしていきたいと思っています。

また、Lambdaを利用してSlackに通知する方法を辞め、以前ブログにも投稿したgetform.ioを利用して問い合わせデータを管理するように変更しています。

■ Before
ブログ改善 問い合わせ画面 before

■ After
ブログ改善 問い合わせ画面 after

パフォーマンス改善

本ブログはSSGを利用していますが、generateのパフォーマンス改善やgenerateされた静的コンテンツのパフォーマンス改善を行っています。

payloadを利用してgenerateを高速化

payloadの機能を利用して、generate時にpageコンポーネントへpayloadデータを渡し、ContentfulへのAPIリクエスト回数を減少させています。

公式にも記載がありますが、以下のような利用方法となります。

-- nuxt.config.js
import fs from 'fs'

・・・
const { createClient } = require('./plugins/contentful')
const client = createClient()

export default {
・・・
 
  generate: {
    routes: function () {
    return client.getEntries({
        content_type: process.env.CTF_BLOG_POST_TYPE_ID,
        order: '-fields.publishDate'
      })
      .then((posts) => {
        return posts.map((post) => {
          return {
            route: '/posts/' + post.slug,
            payload: {
              post: post
            }
          }
        })
      })
    }
  }
 
・・・  
}

※ 実際のソースコードとは異なります。サンプルです。

payloadは、pageコンポーネントのasyncDataの引数から取得可能です。

async asyncData ({ params, error, payload }) {
  if (payload) {
    return { post: payload.post }
  } else {
    return { post: **postデータ取得** }
  }
}

generate時にContentfulから取得したデータをjson化

Nuxt.jsでは、CSR(クライアントサイドレンダリング)時にContentful APIを利用してデータを取得します。この場合Contentfulへのアクセスにパフォーマンスが依存してしまいます。
正直Contentfulへのアクセスが遅いように私は感じました。なので、generate時にjson形式でContentfulのデータを出力し、CSR時はjsonデータを読み込む ように変更しパフォーマンスを改善しました。

以下がjson出力処理のサンプルです。

-- nuxt.config.js
import fs from 'fs'

・・・
const { createClient } = require('./plugins/contentful')
const client = createClient()

export default {
・・・
 
  generate: {
    routes: function () {
    return client.getEntries({
        content_type: process.env.CTF_BLOG_POST_TYPE_ID,
        order: '-fields.publishDate'
      })
      .then((posts) => {
        
        // jsonファイル出力
        fs.writeFile(
          './assets/json/posts.json',
          JSON.stringify(posts.items),
          err => {
            if (err) console.log(err)
          }
        )
        
        return posts.map((post) => {
          return {
            route: '/posts/' + post.slug,
            payload: {
              post: post
            }
          }
        })
      })
    }
  }
 
・・・  
}

※ 実際のソースコードとは異なります。サンプルです。

users.jsonにはユーザー情報が格納されます。
このデータをVuexで管理し、UI構築に利用することでレンダリングの高速化が見込めます。

対応できなかったこと

以下が今回対応できなかった内容です。

  • 画像の最適化(webp形式を利用するなど)と遅延読み込み
  • Typescriptの導入
  • jestを利用したテスト
  • storybookを利用したUIコンポーネントの管理

最後に

今回の改善には個人的に満足しています。Nuxt.js・TailwindCSSの理解力が高まったことは今後のエンジニア人生に活ききてくると思います。
まだまだ"対応できなかったこと"にも記載した通り改善できるところは多いです。
まずは、画像の最適化から改善を進めたいと思います。

前のページ

次のページ

Profile

なかむ🇭🇰Webデベロッパー

なかむ🇭🇰Webデベロッパー

香港在住4年目になるWEBエンジニアのなかむです。 現在は、LaravelやRailsを利用したWEB開発を中心にエンジニアをしています。 顧客は全て日本の企業になります。リモート開発にて各企業様の支援を行なっております

プロフィール詳細はこちら

Latest Posts