microCMS

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

千葉大輔

はじめに

こんにちは、でぃーすけです。  
今回は、Nuxt3とmicroCMSを使ってJamstackブログを作成するチュートリアルになります。
また弊社で公開している nuxt-microcms-module もNuxt3に対応しましたのでそちらの使用方法も併せてご紹介いたします。

環境について


  • Node.js v16.19.1
  • Nuxt v3.2.3
  • nuxt-microcms-module v3.0.0


※ 2023/5/31 追記:Nuxt v3.5.0以上でクライアントサイドの環境変数が読み込めない現象が起きています。こちらのチュートリアルはv3.4.3以下でお試しいただけると幸いです。
※ 2023/6/14 追記:nuxt-microcms-moduleのv3.0.2で上記の問題に対応しております。Nuxt.jsの最新バージョンをお使いの方はmicroCMSのモジュールについてもアップデートをお願いします。

Nuxtとは


https://nuxt.com/

ReactでいうNext.jsのように、Vue.jsを使用したフロントエンドとバックエンドの両方の機能を提供するフレームワークになります。
とても優秀なフレームワークであることは言うまでもないのですが、v3にアップデートされ特に優れていると感じたポイントをいくつか紹介します。

1. 自動インポート

Vue.jsやNuxt.jsが提供している組み込みのAPIはもちろん、components composables utils ディレクトリに作成されたファイルも自動インポートの対象になります。
これにより、TypeScriptの型サポートを受けつつ効率的に開発することができます。

2. 型の自動生成

上記で自動インポート可能になった関数群の型や便利なエイリアスを含むtsconfig.json、server ディレクトリに作成したAPIルートの型までも自動生成します。

この他にも、ルートごとにレンダリングモードを変更したり、新しいサーバーエンジン「Nitro」、モジュールシステムによる機能追加の容易さなど、とても完成度の高いフレームワークになっています。

ゴール

この記事では以下のような記事一覧ページと記事詳細ページを作成するまでをゴールとします。

Nuxtのセットアップ

まずはNuxtのセットアップを行います。

npx nuxi init microcms-blog


コンソールの指示通り、該当ディレクトリに移動して依存ライブラリのインストールをしましょう。

cd microcms-blog
npm install

セットアップができたところで一度開発サーバーを起動してみます。

npm run dev


http://localhost:3000/ にウェルカムページが表示されます。

(めちゃくちゃかっこいいですね・・・!)

microCMSの準備

microCMSでAPIを用意します。今回はmicroCMSのブログテンプレートを利用して進めていきます。
APIの作成

アカウント登録がまだの方はこちらのドキュメントを参考に、登録を行ってください。
はじめに

APIキーとサービスドメイン

あわせてAPIのエンドポイントと、APIキーも用意しておきましょう。ブログ一覧画面のAPIプレビューから確認するのがレスポンスの中身も確認できるので手軽で便利です。


取得したAPIキーとAPIのサービスドメイン名は環境変数ファイル.envに記載しておきます。

MICROCMS_SERVICE_DOMAIN=サービスドメイン名
MICROCMS_API_KEY=APIキー

microCMSモジュールの導入

microCMSのコンテンツの取得を簡単に扱える nuxt-microcms-module を導入します。
https://github.com/microcmsio/nuxt-microcms-module

npm install nuxt-microcms-module

次にConfigurationにモジュールを追加します。

// nuxt.config.ts

export default defineNuxtConfig({
+   modules: ["nuxt-microcms-module"],
+   microCMS: {
+     serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN,
+     apiKey: process.env.MICROCMS_API_KEY,
+   },
});

microCMSモジュールを追加すると、設定ファイルのキーにmicroCMS というオプションを指定できるようになります。
このオプションには serviceDomainapiKeytarget を指定でき、このうちtarget のみオプショナルな値で、serverまたはallを指定できます。
この targetmicroCMSへのリクエストをどこからするのかを指定するものでallを指定した場合はAPIキーがクライアント側でも使用することができます。
(APIキーの権限を適切に設定した上でクライアント側で使用ください)
今回はJamstackブログを作成しますのでtargetはデフォルトのserverにしておきAPIキーは隠蔽します。

そして、nuxt-microcms-module からは3つのGET関数が提供されます。

useMicroCMSGetList
useMicroCMSGetListDetail
useMicroCMSGetObject

// インターフェース

type useMicroCMSGetList = <T>(
  args: {
    endpoint: string;
    queries?: MicroCMSQueries;
  },
  fetchOptions?: FetchOptions
) => Promise<AsyncData<MicroCMSListResponse<T>>>;
type useMicroCMSGetListDetail = <T>(
  args: {
    endpoint: string;
    contentId: string;
    queries?: MicroCMSQueries;
  },
  fetchOptions?: FetchOptions
) => Promise<AsyncData<T & MicroCMSListContent>>;
type useMicroCMSGetObject = <T>(
  args: {
    endpoint: string;
    queries?: MicroCMSQueries;
  },
  fetchOptions?: FetchOptions
) => Promise<AsyncData<T & MicroCMSObjectContent>>;

// FetchOptionsはNuxt3が提供するuseFetchの第2引数と同じです。
// AsyncDataはuseFetchの戻り値です。
// https://nuxt.com/docs/api/composables/use-fetch

microCMSで取得できるコンテンツの型情報を作成

次はmicroCMSで取得できるコンテンツの型情報をあらかじめ作成しておきましょう。

カテゴリーAPIは以下のように、

// types/category.ts

export type Category = {
  name?: string;
};

ブログAPIは以下のように定義します。

// types/blog.ts

import type { MicroCMSImage, MicroCMSListContent } from "microcms-js-sdk";
import { Category } from "./Category";

export type Blog = {
  title?: string;
  content?: string;
  eyecatch?: MicroCMSImage;
  category: (MicroCMSListContent & Category) | null;
};

記事一覧ページの作成

最初に記事一覧のページを作成しましょう。

Nuxtはv3でもv2と同様にpagesディレクトリ内のVueファイルがルーティングに対応します。
(このpagesディレクトリがオプショナルである点はv3からの特徴になります!)

ただ、初期状態のままだとpagesディレクトリに作成したVueファイルが表示されないため、エントリーポイントであるapp.vueを修正する必要があります。

// app.vue

<template>
 <NuxtLayout>
    <NuxtPage />
 </NuxtLayout>
</template>

今回はlayoutsディレクトリを使用しないので、NuxtLayout が機能していないですがたとえばエラーページではレイアウトを変更したりする際に便利です。
https://nuxt.com/docs/guide/directory-structure/layouts

そしてNuxtPageが、pagesディレクトリに配置したそれぞれのVueファイルを表示するコンポーネントです。
https://nuxt.com/docs/api/components/nuxt-page

これでpagesディレクトリを使う準備ができたので早速作っていきます。

今回は記事一覧ページをTOPに表示しようと思うのでpages/index.vueを作成します。
そして、先ほど導入したmicroCMSモジュールから提供されるuseMicroCMSGetListを使ってブログAPIのコンテンツを取得しましょう。

// pages/index.vue

<template>
</template>

<script setup lang="ts">
import { Blog } from "~~/types/Blog";

const { data } = await useMicroCMSGetList<Blog>({
  endpoint: "blogs",
});
console.log(data)
</script>

コンソールにmicroCMSのコンテンツデータが表示されていれば成功です!

次に表示部分を作成していきましょう。
まずは見た目は気にせずHTMLのみで組んでいきます。

<template>
  <h1>Nuxt3 Jamstack Blogs</h1>
  <ul>
    <li v-for="blog in data?.contents" :key="blog.id">
      <NuxtLink :to="`/${blog.id}`">
        <img
          :src="blog.eyecatch?.url"
          :width="blog.eyecatch?.width"
          :height="blog.eyecatch?.height"
          alt=""
        />
        <div>
          <div>
            {{ blog.category?.name }}
          </div>
          <div>
            {{ blog.title }}
          </div>
          <div>
            {{ blog.publishedAt ?? blog.createdAt }}
          </div>
        </div>
      </NuxtLink>
    </li>
  </ul>
</template>

以下のように表示されていれば成功です。(ブラウザによって多少見た目が変わる可能性はあります)

記事詳細ページの作成

次に記事詳細ページを用意していきます。
先ほどのpagesディレクトリにpages/[id].vueを追加で作成します。
Nuxtでは[]で囲んだ部分が動的ルーティングとなり、そのページファイルで利用できます。

このページではuseMicroCMSGetListDetailを使用して詳細データを取得しましょう。

// pages/[id].vue

<template>
</template>

<script setup lang="ts">
import { Blog } from "~~/types/blog";

const { params } = useRoute();

const { data } = await useMicroCMSGetListDetail<Blog>({
  endpoint: "blogs",
  contentId: Array.isArray(params.id) ? params.id[0] : params.id,
});
console.log(data)
</script>

Nuxtから提供されているuseRouteを使用して、現在表示されるページの[id]部分を取得します。
ここでもコンソールを確認してデータが表示されていれば成功です。

表示部分も作成していきましょう。

<template>
  <template v-if="data">
    <h1>
      {{ data.title }}
    </h1>
    <img
      :src="data.eyecatch?.url"
      :width="data.eyecatch?.width"
      :height="data.eyecatch?.height"
      alt=""
    />
    <div>
      <div>
        {{ data.category?.name }}
      </div>
      <div>
        {{ data.publishedAt ?? data.createdAt }}
      </div>
    </div>
    <div v-html="data.content"></div>
  </template>
</template>

以下のように表示されていれば成功です。

見た目を整える

microCMSで取得したデータの表示は完了しましたが、現状だとデフォルトの表示で少し味気ないですよね。
今回はTailwindCSSを使って少しだけ整えてみましょう。

TailwindCSSモジュールの導入

まずはNuxtでTailwindCSSを使えるようにするために便利なモジュールを導入しましょう。
以下のようにインストールをして、

npm install -D @nuxtjs/tailwindcss

Nuxtの設定ファイルに追加しましょう。

// nuxt.config.ts

export default defineNuxtConfig({
-   modules: ["nuxt-microcms-module"],
+   modules: ["nuxt-microcms-module", "@nuxtjs/tailwindcss"],
  microCMS: {
    serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN,
    apiKey: process.env.MICROCMS_API_KEY,
  },
});

そして、詳細ページのようなコンテンツの見た目の調整には@tailwindcss/typographyというTailwindCSSプラグインが便利ですのでこちらもインストールします。

npm install -D @tailwindcss/typography

先ほどの設定ファイルにこちらも追加します。

// nuxt.config.ts
+ import tailwindTypography from "@tailwindcss/typography";

export default defineNuxtConfig({
  modules: ["nuxt-microcms-module", "@nuxtjs/tailwindcss"],
  microCMS: {
    serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN,
    apiKey: process.env.MICROCMS_API_KEY,
  },
+   tailwindcss: {
+     config: {
+       content: [],
+       plugins: [tailwindTypography],
+     },
+   },
});

dayjsを用いた日付表示

microCMSではISO8601形式のUTC(協定世界時)で日時データを返却しているため、フロントエンド側で適切な変換が必要となります。
また、その際に人間が読み取りやすい形式のフォーマットにも変換しようと思います。

まずは日付の変換に便利な dayjs をインストールします。

npm install dayjs

先述の通り、utilsディレクトリに作成したファイルも自動インポートの対象になりますので、utils/dateFormat.tsを作成してそこで日時変換をする関数を定義します。

// utils/dateFormat.ts

import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";

dayjs.extend(utc);
dayjs.extend(timezone);

export const dateFormat = (date: string) =>
  dayjs.utc(date).tz("Asia/Tokyo").format("YYYY年MM月DD日");

これでグローバルにdateFormat関数を呼ぶことができるようになりました。

全体レイアウト

全体のレイアウトを調整するためルートにあるapp.vueを編集していきます。

// app.vue

<template>
  <NuxtLayout>
+     <div class="mx-auto max-w-7xl px-5 sm:px-8 lg:px-10">
+       <div class="mx-auto max-w-3xl py-10 md:py-16">
        <NuxtPage />
+       </div>
+     </div>
  </NuxtLayout>
</template>

記事一覧ページ

さきほどの一覧ページのVueファイルを編集していきます。

// pages/index.vue

<template>
  <h1 class="text-center font-sans text-4xl font-semibold">
    Nuxt3 Jamstack Blogs
  </h1>
  <ul class="mt-16 grid grid-cols-1 gap-8">
    <li v-for="blog in data?.contents" :key="blog.id">
      <NuxtLink
        :to="`/${blog.id}`"
        class="flex flex-col gap-4 sm:transition-shadow sm:hover:shadow md:flex-row md:items-center lg:gap-6"
      >
        <img
          :src="blog.eyecatch?.url"
          :width="blog.eyecatch?.width"
          :height="blog.eyecatch?.height"
          class="md:w-1/3 md:flex-none"
          alt=""
        />
        <div class="md:p-2.5 md:pr-0">
          <div
            class="inline-block rounded border-2 border-indigo-600 px-1.5 py-0.5 text-sm font-semibold text-indigo-600"
          >
            {{ blog.category?.name }}
          </div>
          <div class="mt-2 text-lg font-semibold md:text-xl">
            {{ blog.title }}
          </div>
          <div class="mt-1 text-sm text-gray-700">
            {{ dateFormat(blog.publishedAt ?? blog.createdAt) }}
          </div>
        </div>
      </NuxtLink>
    </li>
  </ul>
</template>

以下のような表示になっていれば成功です。

記事詳細ページ

次は詳細ページです。

// pages/[id].vue

<template>
  <template v-if="data">
    <h1 class="text-3xl font-semibold">
      {{ data.title }}
    </h1>
    <img
      :src="data.eyecatch?.url"
      :width="data.eyecatch?.width"
      :height="data.eyecatch?.height"
      alt=""
      class="mt-6 md:mt-10"
    />
    <div
      class="mt-4 flex flex-col items-start gap-2 md:flex-row md:items-center md:gap-4"
    >
      <div
        class="rounded border-2 border-indigo-600 px-1.5 py-0.5 text-sm font-semibold text-indigo-600"
      >
        {{ data.category?.name }}
      </div>
      <div class="text-sm text-gray-700">
        {{ dateFormat(data.publishedAt ?? data.createdAt) }}
      </div>
    </div>
    <div v-html="data.content" class="prose mt-6 md:mt-10"></div>
  </template>
</template>

以下のような表示になっていれば成功です。

Vercelへのデプロイ

ここまででブログの一覧画面と詳細画面を作ることができましたのでデプロイしてみましょう。
今回はホスティングサービスとしてVercelを利用します。
https://vercel.com/

まずはここまでの作業内容をGitHubに上げます。
GitHubのリポジトリを作成後、下記コマンドでプッシュしていきます。

git init
git add .
git commit -m 'first commit'
git remote add origin your-repository // 自分のリポジトリを入力
git push -u origin main

そしてVercelにログイン後「Add New」をクリック、「Project」を選択します。
その後、先ほど追加したGitHubのリポジトリを選択し、ビルド設定に進みます。

ビルド設定ではフレームワークとしてNuxt.jsを選択し、ビルドコマンドを yarn generate に設定し環境変数のセットを行います。(.envの内容)



Deployボタンを押して、ページを確認してみましょう!

Webhookの設定


現状ではGitHubにソースコードをPushするたびにビルド&デプロイがされますが、microCMSの記事を追加・更新してもビルドはされません。
そこで、microCMSのWebhook機能を使って、記事の追加・更新のたびにVercelでビルド&デプロイがされるように設定しましょう。

設定方法は下記の記事で紹介しているので参考にしてみてください。
https://blog.microcms.io/webhook-cloudflare-vercel/

設定後に新しく記事を公開してみて、Vercelのビルドが動き出したら成功です。

おわりに

Vue2からVue3のマイグレーションは大変ではありますが、Nuxt3へのアップグレードには多くの利点があり、特に開発体験が向上していると感じました。
みなさんにもNuxt3をぜひ試していただけたら幸いです。

まずは、無料で試してみましょう。

APIベースの日本製ヘッドレスCMS「microCMS」を使えば、 ものの数分でAPIの作成ができます。

microCMSを無料で始める

microCMSについてお問い合わせ

初期費用無料・14日間の無料トライアル付き。ご不明な点はお気軽にお問い合わせください。

お問い合わせ

microCMS公式アカウント

microCMSは各公式アカウントで最新情報をお届けしています。
フォローよろしくお願いします。

  • X
  • Discord
  • github

ABOUT ME

千葉大輔
microCMSでプロダクトエンジニアをしています。Twitterでは「でぃーすけ」という名前で活動しており、ReactやNext.js、TypeScript、TailwindCSSが特に好きです。趣味はゲームやったりアニメ見たり料理したりをループしています。