フロントエンドについての学習のため、Nuxt.jsでポートフォリオサイトを作製してみました。
GitHub Pagesで (お金をかけずに) ホスティングしつつ、多少動的なこともしてみました。Vue.js、Nuxt.jsについてほぼ知識のないところから作製したのでかなり荒削りです。
工夫した点は以下の通りです。
- GitHubのリポジトリとQiitaの投稿をそれぞれAPIで取得して表示した。
- トップページはMarkdown形式で書くようにした。
- Nuxt.jsで静的なページを生成してGitHub Pagesでホスティングした。
GitHubのリポジトリとQiitaの投稿をそれぞれAPIで取得
せっかくSPAのフレームワークを使用するので、なにかのAPIを叩いて動的にコンテンツを生成することにしました。
/reposページと/postsページページはそれぞれGitHubとQiitaのAPIからデータを取得して表示するようにしています。
@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
}
}
@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のパーシャルの考え方を参考にしました。
<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>
<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のコンパイラが読み込めるようにする必要があります。
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の公式サイトで解説されている通りなので、非常に簡単でした。
いずれはCIで自動デプロイするようにしたいですね。
感想
素人の状態からでもNuxt.js/GitHub Pagesを使用すれば簡単にポートフォリオサイトを構築できることがわかりました。
そして、フロントエンドのフレームワークを触ってみて一番難しいのはCSSを弄る美的センスだということもわかりました。
次はNuxt.jsとバックエンドを組み合わせて、もう少し動的なサイトの構築も試してみたいと思います。