fragmentary notesITエンジニアの気まぐれメモ

Nuxt3 Jamstackブログ再構築時の躓きポイント忘備

はじめに

なんやかんやあり、このブログを開設してから2年くらいが経ちました。

システム構築自体は公開するよりかなり以前に済ませていたこともあり、仕組み自体は3年くらいかな。そんな昨今、このシステムで採用しているフロントエンドフレームワーク Nuxt2のサポート終了が近づいていることもあり、この度 年末年始休暇+αの期間でNuxt3によるシステム再構築を実施しました。ちょうど自分専用の家計簿のリプレースと追加開発という大物が落ち着き、趣味コーディングがネタ切れ気味だったこともあり。

Nuxt3は、Vue3ベースのフレームワーク。ご存じの方も多いかと思いますが、Vue2→3のメジャーアップデートは多数のBreaking Changeを含み、その上で動くNuxt3も御多分に漏れず。。。Nuxt2→3の移行する手順自体は用意されてはいますが、軽く見た感触では一筋縄ではいかなそう。泥臭い移行でストレス貯めるより、心機一転1から作り直したほうがスッキリできそう。ということで、今回は公式の移行手段は使用せず、(もとのコードから使えところは再利用しつつも)素のNuxt3プロジェクトを立ち上げ、1から再構築し、先日無事リリースしました。(本当は年末年始休暇中に済ませるつもりでしたが、年始早々 体調を崩し寝込んでしまい、1週間ずれ込みました。)

何とかリリースまでこぎ着けたものの、Nuxt3、およびVue3のお作法に慣れていない中での開発は、大きいものから(ここで詰まるつもりではなかった)些細なものまで、さまざまな躓きポイントにぶつかりました。その都度、Google先生やChatGPTで調べながら解決したり、別の方法に迂回したりして切り抜けていきましたが、折角なので自分が詰まったポイントを書き留めておこうと思います。忘れたころに何か新しくNuxt3でものづくりしようとした際、同じところで詰まりそうな気がするので。。。

システム構成

だいぶ前置きが長いですが、今回のシステム構成のお話から。ベースは従来同様。

  • バックエンド(コンテンツ管理):microCMS ← 変更なし
  • フロントエンド:Nuxt3で静的生成 ← 今回 再構築した部分
  • 配信プラットフォーム:Netlify

このうちフロントエンド部分の裏側の仕組みを、Nuxt3によるSSG(Static Site Generation)で再構築しました。フロントエンドの表側、見てくれ部分のデザイン・UIは、微調整のみとどめ従来からほとんど変えていません、時間がなくて。。。この辺りはおいおい。また今回はmicroCMSが提供している通信用クライアント nuxt-microcms-module も使える部分は使用してみました。

さて、そろそろ本題の躓きポイント忘備に移りましょう、さすがに前置きが長すぎる。。。

躓きポイント1:APIデータ取得時のfetchオプション

Nuxt2時代は、microCMSからのデータ取得はaxiosをAPIクライアントとしてmicroCMSのWebAPIをkickする方式をとっていました。Nuxt3ではWebAPIをkickする仕組みとしてfetchベースのAPIが用意されています。また今回使用したmicoroCMSの通信用クライアントもシンプルに言ってしまえばNuxt3標準機能のfetchをラップしているものと思われます。なのでこれらを使用し意気揚々とデータ取得→画面表示のロジックを書いていたのですが、早速 うまくいかない。正確にはうまくいくケースといかないケースがあり、挙動が安定しませんでした。事象としては初回1回目は機能するがリロードすると壊れる。検索条件を変えたはずなのに前回と同じ結果が返ってくる等。

ブラウザのコンソールで原因を追いかけると、想定通りにAPIがkickされていなさそう。さらに調べると、fetch APIは前回実行結果をキャッシュようで、リクエストエンドポイントが同じだとリクエストパラメータやクエリが異なったとしてもAPIをcallしてくれないことがわかりました。

この挙動は、fetch時のオプションとして実行キーを指定してあげる解消するとのこと。具体的には実行キーが前回と同じであればキャッシュされたデータを使用し、異なればAPIをkickする、というものらしい(完全には仕組みを理解できていない、動かしてみて腑に落ちない動きをすることがあるので、自信なし)。なのでキーとしてリクエストパラメータやクエリを指定することで一応解消。

export async function useGetPosts(queryStr:string)
{
  const {data} = await useMicroCMSGetList<Post>(
    {
      endpoint: "posts",
      queries: {filters:queryStr}
    },
    {
      key: "posts_" + queryStr
    }
  );

  return data.value?.contents;
}

躓きポイント2:Page配下でネストするとAuto Importが機能しない?

NuxtにはAuto Importという機能があり、基本的にはお作法に則ったフォルダ構成で作成していれば、作成したcomponentsやcomposables、pluginsは自動でインポートされ、各ファイルのscriptの先頭でimport文を記述する必要はありません。が、なぜかpages配下にフォルダを作成し、その下に置いたファイル上では、<script setup lang="ts">ブロックでAuto Importが機能せず(?)、自分でimportを書いてあげないとエラーになってしまいました。

  • Nuxt3プロジェクトルート
    • pages
      • index.vue → Auto Importが機能する
      • posts
        • index.vue → Auto Importが機能する
      • categories
        • index.vue → Auto Importが機能しない

上記のように、pagesの配下にcategoriesフォルダを作って、その下に置いたvueファイルではAuto Importされませんでした。不思議なことにposts配下はAuto Importされるので、サブフォルダすべてで発生するわけではないみたい。postsのindex.vueをcategoriesにコピーして修正するつもりだったのですが、コピーした瞬間に、エラーで真っ赤になったときはビックリしました。。。

結局 根本原因はわかっていないのですが、lang="ts" を外し該当箇所をTypeScriptではなくJavaScriptして記述するとAuto Importされることがわかり、気持ち悪いですが対症療法的にこのやり方で逃げました。これを見つけたのも偶然で、lang="ts" の記述を忘れていたファイルだとエラーがでていないことがわかったため(ちなみに上記Auto Importが機能しているposts/index.vueは、ちゃんとlang="ts"付でもAuto Importされていました)。いずれちゃんと調査したいね。

Auto Importされなかったとき記述

<script setup lang="ts">
const { params, query } = useRoute(); // 「useRouteが見つからない」とエラー
・・・
</script>

Auto Importされたときの記述(暫定対応)

<script setup>
const { params, query } = useRoute();
・・・
</script>

躓きポイント3:動的ルーティングの記述方法

SSG構成にするため、ビルド時にすべてのページを事前生成する必要があります。何もしなくてもある程度は自動でクロールして生成してくれることもあるのですが、TOPページから直接リンクがつながっていない次ページの内容は検知されないことがあります。特に /posts/<postのid> のように動的ルーティングになっているページはほぼアウト。それ故、ビルド時に全てのページを生成してくれるよう指定してあげます。

具体的には、

  1. 全てのpostエントリの件数をAPI経由で取得する
  2. 必要に応じてページ分割した上で複数回APIをkickし、postエントリのidを取得する
  3. /posts/<取得したid> のリストをビルド対象ルートに追加する

というロジックをnuxt.configのhookに追加します。件数取得→ページ分割取得している理由は、microCMSの制約で複数レコードを取得する際に1度に取得できるレコード数の上限が100件までのため。例えば234件のレコードを取りたい場合は、1~100件目・101~200件目・201~234件目と3回に分けてデータ取得する必要があります。今のところまだ100件もデータがないのですが、忘れたころに動かなくなるのが嫌なので一応ちゃんと作っておきました。

以下nuxt.configのサンプルコード。ここで躓いたポイントとしてはnuxt.config内ではcomposablesで定義した関数をうまく呼べなかったこと。少し調べてみたものの、良い対処法が分からず、結局nuxt.config内にべた書きしました。composables内に書いた関数と内容が重複して二重メンテになるのが嫌なのですが。。。ここも今後の改善ポイント。

// ----------------------------------
// Postのidを取得・パスを生成する関数
// ----------------------------------
const getPostRoutes = async () => {
  const pageLimit = 100;
  const apiKey = process.env.MICROCMS_API_KEY

  // Postsの件数取得
  const cntUrl = (process.env.MICROCMS_API_ENDPOINT ? process.env.MICROCMS_API_ENDPOINT : "") + "/posts?field=id&limit=" + 0;
  const cntRes = await fetch(
    cntUrl,
    {
      method: "GET",
      headers: {
        'X-MICROCMS-API-KEY': apiKey ? apiKey : ""
      }
    }
  );
  // Posts総件数
  const totalCount = (await cntRes.json()).totalCount;
  // 分割ページ数
  const maxPage = Math.ceil(totalCount / pageLimit);

  // Posts id格納用配列
  let ids: any = [];

  // 分割ページ数分繰り返す
  for(let i = 0; i < maxPage; i++){
    const offset = pageLimit * i;
    const url = (process.env.MICROCMS_API_ENDPOINT ? process.env.MICROCMS_API_ENDPOINT : "") + "/posts?field=id&limit=" + pageLimit + "&offset=" + offset;

    const res = await fetch(
      url,
      {
        method: "GET",
        headers: {
          'X-MICROCMS-API-KEY': apiKey ? apiKey : ""
        }
      }
    );
    // 取得したid配列
    const id = (await res.json()).contents;
    // 初回実行時
    if(ids.length == 0)
    {
      // id格納用配列に取得内容を格納
      ids = id
    }
    else
    {
      // id格納用配列に追加
      ids = ids.concat(id)
    }
  }
  // 取得したidからパスを生成する
  return ids.map((obj: { id: string }) => `/posts/${obj.id}`);
}

export default defineNuxtConfig({
  hooks: {
    async 'nitro:config'(nitroConfig) {
      // Postのパス一覧を取得
      const slugs = await getPostRoutes();
      // ビルド対象ルートに追加
      nitroConfig.prerender?.routes?.push(...slugs);
    }
  },
})

躓きポイント4:埋め込みリンクが初期表示されない

microCMSで埋め込みリンク付のエントリを作成し、今回作成したフロントから確認すると表示されず真っ白になる事象が発生しました。一度ページをリロードすると表示されるため、初回時にうまくロードできていないみたい。

この事象自体はNuxt2時代の旧フロントでも発生していて、このブロックを初期ロードする方法があるとのことで、その内容をsetup内に記述することで解消しました。

<script setup lang="ts">
・・・
loadIframelyScript().then(initializeIframely)
function loadIframelyScript(){
  return new Promise((resolve) => {
    const script = document.createElement('script');
    script.src = 'https://cdn.iframe.ly/embed.js';
    script.async = true;
    script.onload = resolve;
    document.head.appendChild(script);
  });
}
function initializeIframely() {
  if (window.iframel) {
    window.iframely.load();
  }
}
・・・
</script>

終わりに

とりあえず覚えている範囲で躓いたポイント4点を適当に書き留めてみました。いくつか「何でこれで動かないのかわからない」「何でこれで動いているのかわからない」という残念ポイントが含まれているので、そのあたりはいずれ改善していきたいと思います。

一応、ソースコードはGitHubに置いておきます。

nuxt3weblog

直接本題とはそれますが…、今回 躓いた際の相談相手としてChatGPTを活用してみました。従来のGoogle先生にきくやり方だと、発生状況を検索条件に羅列しても、一番大事なところを端折った検索結果しかでてこないシチュエーションや、起きている事象をキーワードレベルで表現することが難しく検索できないケースがよくあり、頭を悩ませていました。その点ChatGPTはキーワードではなく、言葉・自然言語として質問できるので、起きている事象をあまり考え込むことなく会話ベースできいて、アドバイスをもらえる(?)点がすごく助かりました。

一方で…、これに慣れてしまうと、事象をつぶさに分析し、怪しそうなキーワードを探し、検索結果から得られる断片的な情報を頭の中で有機的につなげて、自分の欲しい情報に得るという工程を踏む必要がなくなり、これまで培ってきたこのような情報収集スキルが衰えてしまうのではという懸念・寂しさがあります。まあ現状、ChatGPTの返答が必ずしも正しいわけではなく(Nuxt3についてきいているのに、Nuxt2の情報を出してくるとか)、その内容の裏付けのために、別の方法で調べることもあるので、すぐにこれまでの情報収集スキルが無用の長物になるわけではないと思いますが。

よく言われることではありますが、いかにツールに使われる・ツールに操られる のではなく、ツールを使いこなすか大事。1つのツールを信用しきって何の疑いもなくすべてを鵜呑みにするのではなく、状況に応じツールを使い分け、1つのソースだけを信用するのではなく、複数の視点から確認・検証する。そんな心持ちで生きていきたいと思う今日この頃でした。

参考文献

Nuxt3 + microCMS のブログ作成チュートリアル

Generating Dynamic Routes for Static Site Generation with Nuxt 3

  • Home
  • /
  • Posts
  • /
  • Nuxt3 Jamstackブログ再構築時の躓きポイント忘備
Tech-TIPS

Comments

© 2024 shunya_wisteria