見出し画像

Nuxt3 + microCMSで簡単なブログサイトを作ってみる

この記事は「株式会社メンバーズ Jamstack研究会主催 Advent Calendar2023」の15日目の記事です。


はじめに

こんにちは、メンバーズの菅原です。
日頃Nuxt3を使って開発をしていますが、Nuxt3を静的サイトジェネレーターとして使ったことが無いので今回はmicroCMS の 公式のチュートリアルを参考にして microCMS と Nuxt3 を利用したJamstackなブログサイトを作成してみようと思います。

本記事では、microCMS と Nuxt3 で 記事一覧ページと記事詳細ページで構成されたシンプルなブログサイトを作り、SSG でVercel にデプロイしてページを表示するまでをご紹介いたします。

技術スタック

・microCMS
・Node.js v18.6.0
・Nuxt v3.8.2
・nuxt-microcms-module v3.0.2

Nuxtのプロジェクト作成

まずはNuxt側のセットアップを進めます。

$ npx nuxi@latest init microcms-nuxt

今回はパッケージマネージャーはyarnを選択しました。
セットアップが完了したらコンソールの指示に従ってディレクトリの移動をして開発サーバーを立ち上げてみます。

$ cd microcms-nuxt
$ yarn run dev

http://localhost:3000/ にアクセスしてNuxt3のWelcomeページが表示されたら完了です。

microCMSの準備

次に公式を参考にmicroCMS側のAPIを用意していきます。
テンプレートが用意されているので今回は「ブログ」を選択しました。

また、今回はテンプレートから生成されたAPIの他に「タグ」のAPIを追加で用意します。

エンドポイントはtags、形式は リスト形式を選択しました。
tagsのスキーマは以下の通りでカテゴリと同じ構成になります。

記事側にもtagsのフィールドを追加します。
カテゴリは記事に対して1つまでなのに対して、タグは記事に複数付けられるように種類は「複数コンテンツ参照」を選択します。

作成できたら、一覧画面のAPIプレビューボタンから、APIキーとAPIのサービスドメイン名を確認して、環境変数ファイル.envに記載しておきます。

MICROCMS_SERVICE_DOMAIN=サービスドメイン名 // .microcms.io は含まない
MICROCMS_API_KEY=APIキー

microCMSモジュールを導入

次に nuxt-microcms-moduleを導入します。

nuxt-microcms-module からはuseFetchがラップされた3つのcomposableが提供されます。
今回はこちらを使用してmicroCMSのコンテンツの取得をしていきます。

$ yarn add nuxt-microcms-module

次にnuxt.config.tsにモジュールを追加します。

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

microCMSのコンテンツの型情報を作成

実装の前にmicroCMSのコンテンツの型を作っていきます。

// types/tag.ts
export type Tag = {
  name?: string;
};
// types/category.ts
export type Category = {
  name?: string;
};
// types/blog.ts
import type { MicroCMSImage, MicroCMSListContent } from "microcms-js-sdk";
import type { Category } from "./category";
import type { Tag } from "./tag";

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

記事一覧の作成

最初にapp.vue を変更します。<NuxtPage/> コンポーネントを使用して pagesディレクトリへのルーティングを有効にします。

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

次にpages/index.vueを作成します。ここに記事の一覧を表示します。
先ほど導入したmicroCMSモジュールのuseMicroCMSGetListを使ってblogコンテンツの情報を取得し、その内容をページに表示します。

<template>
  <ul>
    <li v-for="blog in blogs?.contents" :key="blog.id">
      <NuxtLink :to="`/${blog.id}`">
        <img
          :src="blog.eyecatch?.url"
          :width="blog.eyecatch?.width"
          :height="blog.eyecatch?.height"
        />
        <div>
          <div>
            {{ blog.category?.name }}
          </div>
          <div>
            {{ blog.tags?.map(({ name }) => name).join(" / ") }}
          </div>
          <div>
            {{ blog.title }}
          </div>
          <div>
            {{ blog.publishedAt ?? blog.createdAt }}
          </div>
        </div>
      </NuxtLink>
    </li>
  </ul>
</template>

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

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

以下のように表示されました。

記事詳細ページ作成

次に記事詳細ページを用意します。
pagesディレクトリにpages/[id].vueを作成します。(Nuxt3では[]で囲んだページファイルが動的ルーティングになります。)
useMicroCMSGetListDetailを使用してidに紐付く記事データを取得し表示します。idはuseRouteを使用すると現在のページの[id]部分を取得できるのでそちらを利用します。

<template>
  <template v-if="blog">
    <h1>
      {{ blog.title }}
    </h1>
    <img
      :src="blog.eyecatch?.url"
      :width="blog.eyecatch?.width"
      :height="blog.eyecatch?.height"
    />
    <div>
      <div>
        {{ blog.category?.name }}
      </div>
      <div>
        {{ blog.tags?.map(({ name }) => name).join(" / ") }}
      </div>
      <div>
        {{ blog.publishedAt ?? blog.createdAt }}
      </div>
    </div>
    <div v-html="blog.content"></div>
  </template>
</template>
<script setup lang="ts">
import type { Blog } from "~~/types/blog";

const {
  params: { id },
} = useRoute();

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

以下のように記事が表示されました。

見た目を整える

最後に見た目を整えていきます。

Vuetifyの導入

今回はvueのUIライブラリのvuetify3を利用します。
vuetifyのインストールと設定を行っていきます。
アイコンとsassも使用したいので一緒にインストールします。

$ yarn add vuetify @mdi/font sass

plugins/vuetify.js を追加します。

// plugins/vuetify.js
import { createVuetify } from "vuetify";
import * as components from "vuetify/components";

export default defineNuxtPlugin((nuxtApp) => {
  const vuetify = createVuetify({
    ssr: true,
    components,
  });

  nuxtApp.vueApp.use(vuetify);
});

nuxt.config.tsにビルドの設定をなどを追加します。

export default defineNuxtConfig({
  devtools: { enabled: true },
  build: {
    transpile: ["vuetify"],
  },
  vite: {
    ssr: {
      noExternal: ["vuetify"],
    },
  },
  css: ["vuetify/styles", "@mdi/font/css/materialdesignicons.css"],
  modules: ["nuxt-microcms-module"],
  microCMS: {
    serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN,
    apiKey: process.env.MICROCMS_API_KEY,
  },
});

これでVuetifyの導入は完了です。

dayjsの導入

日付のフォーマットの変換を行いたいのでdayjsもインストールします。

$ yarn add dayjs

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, template: string = "YYYY/MM/DD") =>
  dayjs.utc(date).tz("Asia/Tokyo").format(template);

これで事前の準備は完了です。

記事一覧ページ

それでは実際にページの見た目を整えていきます。
pages/index.vueのファイルを編集します。

<template>
  <v-container>
    <v-row>
      <v-col v-for="blog in blogs?.contents" cols="4">
        <v-card class="mx-auto" link :href="blog.id">
          <v-img cover :src="blog.eyecatch?.url" alt="" />
          <v-card-item>
            <p class="text-h6 mb-2 font-weight-bold">{{ blog.title }}</p>
            <v-chip
              variant="outlined"
              class="ma-1"
              label
              text-color="white"
              color="primary"
              size="small"
            >
              <v-icon start icon="mdi-format-align-left"></v-icon>
              {{ blog.category?.name }}
            </v-chip>
            <v-chip
              size="small"
              v-for="tag in blog.tags"
              prepend-icon="mdi-tag"
              class="ma-1"
              color="primary"
            >
              {{ tag.name }}
            </v-chip>
            <div class="d-flex flex-row-reverse">
              <v-chip
                prepend-icon="mdi-clock-time-four-outline"
                class="mt-1"
                variant="text"
              >
                {{ dateFormat(blog.publishedAt ?? blog.createdAt) }}
              </v-chip>
            </div>
          </v-card-item>
        </v-card>
      </v-col>
    </v-row>
  </v-container>
</template>

今回、記事一覧はカードデザインにしました。

記事詳細ページ

次は詳細ページです。
pages/[id].vueのファイルを編集します。

<template>
  <template v-if="blogs">
    <v-container>
      <v-row justify="center">
        <v-col cols="8">
          <v-img :src="blogs.eyecatch?.url"></v-img>
          <h1 class="text-h4 mt-6 mb-3 font-weight-bold">{{ blogs.title }}</h1>
          <v-chip
            variant="outlined"
            class="ma-2"
            label
            text-color="white"
            color="primary"
          >
            <v-icon start icon="mdi-format-align-left"></v-icon>
            {{ blogs.category?.name }}
          </v-chip>
          <v-chip
            v-for="tag in blogs.tags"
            prepend-icon="mdi-tag"
            class="ma-2"
            color="primary"
          >
            {{ tag.name }}
          </v-chip>
          <p class="text-body text-end">
            <v-icon start icon="mdi-clock-time-four-outline"></v-icon>
            <span>{{ dateFormat(blogs.publishedAt ?? blogs.createdAt) }}</span>
          </p>
          <div class="blogContent mt-8" v-html="blogs.content"></div>
        </v-col>
      </v-row>
    </v-container>
  </template>
</template>
<style lang="scss">
.blogContent {
  img {
    width: 100%;
    height: auto;
  }

  h2 {
    font-size: 1.5rem;
    margin: 4rem 0 1.5rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid #e0e0e0;

    &:first-child {
      margin-top: 0;
    }
  }

  p {
    margin: 1.5rem 0 0.5rem;
  }

  & > ol,
  & > ul {
    list-style-position: inside;
  }
}
</style>

記事詳細は以下のようになりました。

デプロイ

これで一覧画面と詳細画面が完成したのでVercelにデプロイします。
先にここまでの作業内容をGitHubに上げます。

$ git commit -m "first commit"
$ git branch -M main
$ git remote add origin [リポジトリ]
$ git push -u origin main

次に、VercelにログインしてAdd New > Projectを選択して、GitHubのリポジトリを選択してビルドの設定を行います。
ビルドコマンドを yarn generate に設定しEnvironment Variablesに.envの内容をセットします。

Deployボタンを押してデプロイし、ページの表示が確認できたら完了です。

おわりに

今回は、MicroCMS + Nuxt3 でブログサイトを作りました。
簡単に短時間でサーバーレスで軽量なブログサイトを作ることができました。
microCMSは今回初めて触りましたが管理画面のUIもわかりやすくストレスなく開発を行うことができました。API経由でPOSTやDELETEなども可能なようなので次はCRUD操作を行うようなサイトを作ってみたいと思います。


この記事が気に入ったらサポートをしてみませんか?