🍔

Vue.js と microCMSでブログを作ってNetlifyにデプロイする

12 min read

はじめに

動機

友達がポートフォリオサイトを作りたいと言っていたので、以下の要件でどうサイトを作るか考えて、Vue.js と microCMS、Netlify を使おうと思い、試しにブログ風のサイトをつくりました。本記事はそれのアウトプットです。

要件

  • プログラミングの知識がなくてもサイトを更新できる。
  • 個人的にWordPress は興味が湧かないので使いたくない。ヘッドレス CMS 使ってみたい。
  • 業務でも触れている Vue.js を使いたい。
  • できるだけお金をかけない方法が良い。

今回作るもの

ブログっぽいサイト。とりあえず記事一覧と記事詳細画面を作ります。

サイトを作る

Netlify へのデプロイ時に Github を連携させるので、repositoryの 作成、commit, push はよしなにやってください。

vue をインストールしていない場合は、こちらを参考にインストールお願いします。https://jp.vuejs.org/v2/guide/installation.html

この記事を書いたときのvue cliのversionは3.11.0でした。

プロジェクト作成

それでは作っていきます。

$ vue create my-blog

Please pick a preset と聞かれますが、こだわりがなければ default (babel, eslint) で良いと思います。

成功したよと表示されたら、動くか確認しましょう。

$ cd my-blog
$ npm run serve

http://localhost:8080/ に Vue のロゴ等が表示されたら OK です。

Vuetify を入れる

次にデザインを楽して綺麗に整えたいので、Vuetify を入れます。
必須ではないですが、これからは Vuetify を使っている前提で書いてきます。

$ vue add vuetify

Choose a preset: (Use arrow keys)
❯ Default (recommended)

インストールできたら npm run serve をして、またhttp://localhost:8080/ を見てみます。Vuetify のロゴ等が表示されていたら OK です。

Vue Router を入れる

ページ遷移するためにVue Router を入れます。

$ vue add router

? Use history mode for router? (Requires proper server setup for index fallback in production) Yes

これで下準備が整いました。

ナビゲーションを整える

Vuetify App barsを使ってナビゲーションを整えましょう

App.vue
<template>
  <v-app>
    <v-container>
    <!-- スタイルは本記事のメインではないので雑に設定しています。この辺りもお好みで! -->
      <v-app-bar color="#FFFFFF" flat max-height="100" class="mb-5">
        <v-toolbar-title>
          <h1><router-link to="/" class="logo">ブログ!</router-link></h1>
        </v-toolbar-title>

        <v-spacer></v-spacer>

        <div id="nav">
          <router-link to="/">Home</router-link> |
          <router-link to="/about">About</router-link>
        </div>
      </v-app-bar>

      <router-view />
    </v-container>
  </v-app>
</template>

<style lang="scss">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;

  a {
    color: #2c3e50;
    text-decoration: none;
  }
}

#nav {
  padding: 30px;

  .router-link-exact-active {
    color: #42b983;
  }
}
</style>

ヘッダーがこんな見た目になったと思います。

ヘッダーの見た目

記事一覧画面を作る(トップページ)

vue-router を入れたときに作られた Home.vue を記事一覧画面にしていきます。

レイアウトには Vuetify のCardsを使っています。
また複数のカードを並べて表示するのでグリッドシステムのv-row, v-col を使っています。

雑に複数のカードを並べてみましょう。

Home.vue
<template>
  <div>
    <v-row>
      <v-col>
        <v-card class="mx-auto" width="300" height="330">
          <v-img
            class="white--text align-end"
            height="200px"
            src="https://cdn.vuetifyjs.com/images/cards/docks.jpg"
          >
            <v-card-title>Top 10 Australian beaches</v-card-title>
          </v-img>

          <v-card-text class="text--primary">
            <div>Whitehaven Beach</div>

            <div>Whitsunday Island, Whitsunday Islands</div>
          </v-card-text>

          <v-card-actions>
            <v-btn color="orange" text>More</v-btn>
          </v-card-actions>
        </v-card>
      </v-col>

      <v-col>
        <v-card class="mx-auto" width="300" height="330">
          <v-img
            class="white--text align-end"
            height="200px"
            src="https://cdn.vuetifyjs.com/images/cards/docks.jpg"
          >
            <v-card-title>Top 10 Australian beaches</v-card-title>
          </v-img>

          <v-card-text class="text--primary">
            <div>Whitehaven Beach</div>

            <div>Whitsunday Island, Whitsunday Islands</div>
          </v-card-text>

          <v-card-actions>
            <v-btn color="orange" text>More</v-btn>
          </v-card-actions>
        </v-card>
      </v-col>

      <v-col>
        <v-card class="mx-auto" width="300" height="330">
          <v-img
            class="white--text align-end"
            height="200px"
            src="https://cdn.vuetifyjs.com/images/cards/docks.jpg"
          >
            <v-card-title>Top 10 Australian beaches</v-card-title>
          </v-img>

          <v-card-text class="text--primary">
            <div>Whitehaven Beach</div>

            <div>Whitsunday Island, Whitsunday Islands</div>
          </v-card-text>

          <v-card-actions>
            <v-btn color="orange" text>More</v-btn>
          </v-card-actions>
        </v-card>
      </v-col>
    </v-row>
  </div>
</template>

<script>
export default {
  name: "Home"
};
</script>

こんな見た目になったと思います。

記事一覧の見た目

記事詳細画面を作る

次に記事の詳細画面を作ります。

ファイルを用意します。

$ touch src/views/ArticleDetail.vue
ArticleDetail.vue
<template>
  <v-card flat>
    <v-img
      height="400"
      src="https://cdn.vuetifyjs.com/images/cards/docks.jpg"
    ></v-img>

    <v-card-title>Top 10 Australian beaches</v-card-title>

    <v-card-text>
      <div>Whitehaven Beach</div>

      <div>Whitsunday Island, Whitsunday Islands</div>
    </v-card-text>
  </v-card>
</template>

<script>
export default {
  name: "ArticleDetail"
};
</script>

記事詳細画面の見た目

コンポーネントの準備ができたらルーティングを設定します

router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
const Home = () => import("../views/Home.vue");
const About = () => import("../views/About.vue");
const ArticleDetail = () => import("../views/ArticleDetail.vue");

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home
  },
  // Aboutページは作りませんが、vue-routerを入れたときに作られていたのでそのままにしています。
  // 必要に応じて設定してください。
  {
    path: "/about",
    name: "About",
    component: About
  },
  {
    path: "/articles/:id",
    name: "article-detail",
    component: ArticleDetail
  }
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});

export default router;

コンテンツを入れる

microCMS に登録 & サービスを作成する

microCMS のアカウントを作ります。そして、サービスを作成します。
公式ブログの説明が分かりやすかったので、こちらを見ていただければと思います。

https://microcms.io/blog/getting-started/

ブログの記事を登録して、API を作成する

それでは記事を取得するためのAPIを作成します。

API名とエンドポイントを決めます。
API名・URIを決める

複数の記事を取得したいので、リスト形式にします。
リスト形式を選択

APIスキーマを以下のように定義します。
APIスキーマの定義

記事を登録しましょう。
記事を登録

記事を取得して表示する

microCMS に登録した記事を API 経由で取得して、表示できるようにします。

microCMS の API キーを取得してください。

APIキーの取得

左上の歯車アイコンをクリックすると設定画面に飛ぶので、API-KEY を表示してコピーしてください。

API-KEY は env ファイルに書いておきます。

$ touch .env.local
.env.local
VUE_APP_X-API-KEY=コピーしたAPIキーをペースト

そして、API からデータを取得できるようにするために、今回は HTTP クライアントのaxiosを使います。

$ npm install axios
Home.vue
<template>
  <div>
    <v-row>
      <v-col v-for="article in articles" :key="article.id">
        <v-card class="mx-auto" width="300" height="330">
          <v-img
            class="white--text align-end"
            height="200px"
            :src="article.image.url"
          >
            <v-card-title>{{ article.title }}</v-card-title>
          </v-img>

          <v-card-text class="text--primary">
            <div class="summary">{{ article.summary }}</div>
          </v-card-text>

          <v-card-actions>
            <v-btn color="orange" text>More</v-btn>
          </v-card-actions>
        </v-card>
      </v-col>
    </v-row>
  </div>
</template>

<script>
import axios from "axios";

export default {
  name: "Home",

  data: () => ({
    articles: []
  }),

  async mounted() {
    // 記事を取得する
    const response = await axios.get(
      "https://my-blog-harasho.microcms.io/api/v1/articles",
      {
        headers: { "X-API-KEY": process.env.VUE_APP_X_API_KEY }
      }
    );
    this.articles = response.data.contents;
  }
};
</script>

<style scoped>
.summary {
  white-space: pre-wrap;
}
</style>

画像

microCMS に登録した記事を取得できていることが確認できました。

記事一覧画面から詳細画面へのリンクを追加します。

Home.vue
<v-card-actions>
  <!-- 詳細画面で記事を取得できるように、記事のidをパラメーターとして渡す -->
  <router-link :to="{ name: 'article-detail', params: { id: article.id } }">
    <v-btn color="orange" text>More</v-btn>
  </router-link>
</v-card-actions>

記事詳細画面で、特定の記事を取得して表示できるようにします。

microCMS で記事に埋め込んだ画像を表示できるようにv-htmlを使います。
記事は自身で作ったものですが、念のためサニタイズも行います。

$ npm install sanitize-html
ArticleDetail.vue
<template>
  <v-card flat>
    <v-img height="400" :src="article.image.url"></v-img>

    <v-card-title>{{ article.title }}</v-card-title>

    <v-card-text>
      <div v-html="sanitizedBody"></div>
    </v-card-text>
  </v-card>
</template>

<script>
import axios from "axios";
import sanitizeHtml from "sanitize-html";

export default {
  name: "ArticleDetail",

  data: () => ({
    article: {}
  }),

  computed: {
    sanitizedBody() {
      // imgタグのみ使えるようにする
      return sanitizeHtml(this.article.body, {
        allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"])
      });
    }
  },

  async mounted() {
    // idを指定して特定の記事を取得する
    const response = await axios.get(
      "https://my-blog-harasho.microcms.io/api/v1/articles/" +
        this.$route.params.id,
      {
        headers: { "X-API-KEY": process.env.VUE_APP_X_API_KEY }
      }
    );
    this.article = response.data;
  }
};
</script>

これで画面は完成とします。

デプロイする

Netlifyを使います。アカウントがある人はログイン。ない人は登録してください。

手順は次のようになります。

  1. 管理画面のトップページの New site from Git をクリック
  2. Github と連携
  3. デプロイするリポジトリを選択
  4. Basic build settings の Build command は npm run build, publish directory は dist にする
  5. show advanced -> New variable をクリックして、環境変数 VUE_APP_X_API_KEY を設定する
  6. Deploy Site をクリック
  7. サイトが公開されます!

※今後は 選択した branch に push するたびにサイトが更新されます。

※設定方法はこちらのサイトがわかりやすいです。

https://cr-vue.mio3io.com/tutorials/netlify.html

追加の設定

サイト上でリロードをしたり、 URL を直接指定すると、404 エラーとなります。
これは困るので、index.html を返すように、リダイレクトの設定を追加します。

https://docs.netlify.com/routing/redirects/rewrites-proxies/#history-pushstate-and-single-page-apps
$ touch public/_redirects
_redirects
/* /index.html 200

あとがき

ざっくり作り終えて以下のやり残しがあるなと思いました。気が向いたら追加していきます…!

  • API を何度も叩くのは無駄なので、Vuex などで管理して、できるだけ API を叩く回数を減らしたいです。
  • APIから記事を取得できるまでの間は、ローディング画面など入れたいです。
  • 404ページを作っていません。
  • エラーハンドリングしていません。
  • 記事が増えてきたら、ページネーションが欲しくなりますね。
  • 実際にブログとして運用するとなったらSSR していないので SEO が少し不安です。