発端はMarkdown-itへの拡張

記事本文をMarkdown-itへ拡張させるために、Markdownのフィールドをプレーンテキストで取得することにしました。Newtはクエリで {field}[fmt]=text を設定することでプレーンテキストを取得することができるのですが、以前書いた方法ではどうにもうまくいかず。そこでいつのまにかできていたチュートリアルを参考に、記事取得部分を公式のものに書き直ししました。

しかしながら、ただ書き直すだけでは面白くないので、これを機に以前より気になっていたPayloadの取り扱いについても再考。

記事一覧の取得

プラグインは公式の通りにしましたんで記載は省略。公式ではpagesでそのままプラグインを使ってましたが、うちはコンテンツのページが複数あるためcomposableで共通化させました。

まずは各コンテンツの一覧を取得します。ページによって型は異なります。

export const useContents = async <T>(appUid: string, modelUid: string) => {
  const { data } = await useAsyncData(`${appUid}-${modelUid}`, async () => {
    const { $newtClient } = useNuxtApp()
    return await $newtClient.getContents<T>({
      appUid,
      modelUid,
      query: {
        body: { fmt: 'text' },  // bodyはプレーンテキストで取得
      },
    })
  })
  return data.value?.items
}

appUidとmodelUidを引数にプラグインを使用。2つを組み合わせたものをkeyとします。

個別記事の取得

うちのサイトでは個別記事はJournalのページでしか使用してませんのでappUidとmodelUidは固定。なおslugで記事を指定します。

export const useArticle = async (slug: string) => {
  const { data: foobar } = useNuxtData('foo-bar')
  const { data } = await useAsyncData(
    slug,
    async () => {
      const { $newtClient } = useNuxtApp()
      return await $newtClient.getFirstContent<Article>({
        appUid: 'foo',
        modelUid: 'bar',
        query: {
          slug,
          body: { fmt: 'text' },  // bodyはプレーンテキストで取得
        },
      })
    },
    {
      default: () => {
        return foobar.value?.items.find((article: Article) => article.slug === slug)
      },
    },
  )
  return data.value as Article | undefined
}

前も書きましたが、Nuxt2ではPayloadを使うことによりAPIリクエストを減らし、そのPayloadも適正なサイズにするためにnuxt.config.jsのgenerateの箇所にいろいろ書いてましたが、今回はその方法が使えないっぽいんで上記のように書いてます。

さて、前回の書き方では個別記事毎にリクエストが発生するので効率的ではありませんでした。しかし、記事一覧のPayloadを使うために個別記事のページで毎回記事一覧をuseFetchし個別記事を抽出する方法だと、個別記事のPayloadがすべて一覧のものになってしまいサイズが大きくなってしまいます。というわけで、

  • 記事一覧はfoo-bar(appUid: foo, modelUid: bar)
  • 一覧ページでは useContents('foo', 'bar') でfoo-barの一覧を取得し、その際、key 'foo-bar' として一覧のPayloadが生成される
  • 個別記事では、まず useNuxtData('foo-bar') により一覧のPayloadを取得
  • その中からslugにマッチする個別記事を抽出したものをuseAsyncDataのdefaultに設定
  • useAsyncDataで生成されるPayloadのkeyはslug名で指定

としました。

これで、個別記事で毎回APIリクエストが発生することはなく、かつ各ページのPayloadもその記事のもののみとなり、リクエストとPayloadサイズの適正化が図れることとなりました。

※このコードではやっぱりAPIリクエストが発生してしまいます。というわけでさらに調べてみて改善できました。

また取得する際も、

<script setup>
const contents = await useContents('foo', 'bar')
...
</script>
<script setup>
const route = useRoute()
const content = await useArticle(route.params.slug)
...
</script>

となり、非常にスッキリとしたものになりました。