TypeScript
vue.js
nuxt.js
0

Nuxt.jsでポートフォリオサイトを作製する

フロントエンドについての学習のため、Nuxt.jsでポートフォリオサイトを作製してみました。

fmatzy.github.io

GitHub Pagesで (お金をかけずに) ホスティングしつつ、多少動的なこともしてみました。Vue.js、Nuxt.jsについてほぼ知識のないところから作製したのでかなり荒削りです。

fmatzy/fmatzy.github.io-dev

工夫した点は以下の通りです。

  • GitHubのリポジトリとQiitaの投稿をそれぞれAPIで取得して表示した。
  • トップページはMarkdown形式で書くようにした。
  • Nuxt.jsで静的なページを生成してGitHub Pagesでホスティングした。

GitHubのリポジトリとQiitaの投稿をそれぞれAPIで取得

せっかくSPAのフレームワークを使用するので、なにかのAPIを叩いて動的にコンテンツを生成することにしました。

/reposページ/postsページページはそれぞれGitHubとQiitaのAPIからデータを取得して表示するようにしています。

repos.vue
@Component({
  components: {
    Repository
  }
})
export default class extends Vue {
  @Prop() repos

  async mounted() {
    const { data: repos } = await axios.get("https://api.github.com/users/fmatzy/repos")
    this.repos = repos
  }
}
posts.vue
@Component({
  components: {
    Post
  }
})
export default class extends Vue {
  @Prop() posts

  async mounted() {
    const { data: posts } = await axios.get("https://qiita.com/api/v2/users/fmatzy/items?page=1&per_page=10")
    this.posts = posts
  }
}

単純にページにアクセスしたらaxiosでAPIにリクエストを送り、受け取ったデータをv-forとComponentを利用してビューに渡しています。Componentの分割についてはRails Tutorialのパーシャルの考え方を参考にしました。

Repository.vue
<template>
  <li class="repository">
    <span class="name"><a :href="repo.html_url">{{repo.name}}</a></span>
    <span class="description">{{repo.description}}</span>
  </li>
</template>

<script lang="ts">
import {
  Component,
  Prop,
  Vue
} from "nuxt-property-decorator"

@Component({})
export default class Repository extends Vue {
  @Prop() repo
}
</script>
Post.vue
<template>
  <li class="post">
    <span class="title"><a :href="post.url">{{post.title}}</a></span>
    <span class="created">{{ post.created_at | formatTime }}</span>
  </li>
</template>

<script lang="ts">
import {
  Component,
  Prop,
  Vue
} from "nuxt-property-decorator"
import moment from "moment"

@Component({
  filters: {
    formatTime: function(time: string): string {
      return moment(time).format('MMMM Do YYYY, h:mm:ss')
    }
  }
})
export default class Post extends Vue {
  @Prop() post
}
</script>

Markdownを用いたページ生成

ソース内にMarkdownファイルを配置し、それを取得して描画しようとしましたが、これが一番詰まりました。最終的には@nuxtjs/markdownitを使用し、<template lang="md">を指定してtemplate部分に直接Markdownを書いています。

一応、Markdownファイルを取得してHTMLレンダリングする方法として、

  • AjaxでMarkdownファイルを取得してクライアントサイドで描画する。
  • TypeScriptのソース内でファイルをimportする。

というのも試してみましたが、それぞれ微妙なエラーで詰まることになったので、解決策と合わせて書いておきます。

AjaxでMarkdownファイルの取得

外部のAPIにリクエストを送るのと同様に、単純にaxiosでファイルを取得し、受け取ったデータをMarkdownの描画ライブラリに渡すというアイデアです。

@Component({
  components: {
    MdArticle // stringを渡すとHTMLにレンダリングするコンポーネント
  }
})
export default class extends Vue {
  @Prop() source

  async mounted() {
    const { data: source } = await axios.get("/contents/about.md")
    this.source = source
  }
}

しかし、単純に上記のように書くと、axiosがレスポンスをJSONでパースするときに例外が飛びます。JSON以外のデータを取得するときは、パースを無効化するために以下のようにする必要があるようです。

async mounted() {
  const { data: source } = await axios.get("/contents/about.md", {transformResponse: d => d})
  this.source = source
}

参考: Disable JSON parsing in Axios - Stack Overflow

TypeScript内でMarkdownファイルのimport

@nuxtjs/markdownitを使えばAjaxでファイルを取得しなくても、JavaScript側でimportできるようでしたので、そちらを試しました。

import mdFile from "../about.md"

しかし、@nuxtjs/markdownitのREADMEに書かれている上記のような書き方はTypeScriptではエラーになります。JavaScriptを使用した解説だと当然できるように書かれているので焦りました。

TypeScriptでこうしたテキストファイルをモジュールとして読み込む場合、以下のような内容の型定義ファイルを作製し、TypeScriptのコンパイラが読み込めるようにする必要があります。

md.d.ts
declare module "*.md" {
    const content: string;
    export default content;
}

TypeScriptのコンパイラはデフォルトで@typesフォルダ内を検索するため、適当に@typesフォルダを作成してぶち込めばimportできるようになります。

ただ、このやり方だとVS Codeのエラー扱いが消えないのが気持ち悪かったのでやめました…。

参考: How to import markdown(.md) file in typescript - Stack Overflow

Nuxt.jsで構築したサイトのGitHub Pagesでのホスティング

Nuxt.jsで静的ページを生成しているため、ルーティングに対して404.htmlをハックするような面倒臭いことはなく、そのまま/reposページ/postsページページにアクセスできます。

GitHub Pagesへのデプロイ方法もNuxt.jsの公式サイトで解説されている通りなので、非常に簡単でした。

GitHub Pages へデプロイ - Nuxt.js

いずれはCIで自動デプロイするようにしたいですね。

感想

素人の状態からでもNuxt.js/GitHub Pagesを使用すれば簡単にポートフォリをサイトを構築できることがわかりました。

そして、フロントエンドのフレームワークを触ってみて一番難しいのはCSSを弄る美的センスだということもわかりました。

次はNuxt.jsとバックエンドを組み合わせて、もう少し動的なサイトの構築も試してみたいと思います。