Aokashi Room

なんでも書き続けるAokashiの部屋

Aokashi Home を GatsbyJS で再構築しました

これまで Aokashi Home は2017年3月から HUGO で静的サイトを生成していましたが、 GatsbyJS で静的サイトを生成する形に切り替えました。

  • スクリーンショットはすべて開発中のものです。
  • GatsbyJS で発生したエラーとその解決策について見たい場合は、「実装で躓いたところと解決策」からご覧ください。

経緯

そもそもなぜ再構築したのかは、下記の理由があります。

  • 当時手間で不安定だったデプロイの手法を、簡単で確実なものにするため
  • ポートフォリオの作成が必要になったため
  • 今のスキルを活かして、扱いやすいユーザー体験を実現したいため
  • React の勉強をしたかったため

といったことが重なり、せっかくの機会なので、Webサイトを作り直しました。

デプロイ手法について

以前は下記の方法でデプロイをしていました。

  • Webサイトを編集し、GitHubにプッシュ
  • GitHubと連携しているwerckerがプッシュを検知して、HUGOでWebサイトをビルド
  • そのWebサイトのデータを rsync コマンドを使用してVPSにデプロイ

ただし、この方法には、下記の懸念点がありました。

  • rsync でのデプロイ先にはサイトのリポジトリに存在しないWWAのマップデータやHTMLファイルが含まれるため、 rsync コマンドの delete モードが使用するとWWAが消えてしまう (→ 【rsync】コマンド(その1)――ファイルやディレクトリを同期する)
    • リポジトリWWAのマップデータやHTMLファイルを含ませる方法が手っ取り早いが、WWAのイメージ画像ファイルには他者が作成したイメージ画像が含まれていて、その画像ファイルを自由にダウンロードできるリポジトリに含ませたくなかった
  • rsyncdelete モードを切ってデプロイした場合だと、Webサイトで削除が発生した分が反映されない

スキルを生かしたユーザー体験

f:id:aokashi:20191130221157p:plain
以前のWebサイトでは、トップへ戻るボタンや目次の表示切り替えがありました。

以前のWebサイトでは、 jQuery を使用してトップへ戻るボタンを実装していましたが、後に jQuery の使用を取りやめ、 JavaScript を含ませない状態で Web サイトを運用していました。

その関係で、トップへ戻りづらかったり、目的のコンテンツが簡単に探せなかったりしたかもしれません。

React の勉強

今回使用した GatsbyJS は React でレンダリングするWebページを生成できる「静的サイトジェネレーター」の一種です。React で使用できるコンポーネントを、この GatsbyJS でも使用することができます。

今後ツール開発で React を使用することがあって、その勉強として GatsbyJS を選びました。

ちなみに、以前は同じJavaScriptのビューフレームワークである Vue.js を利用した Gridsome で再構築していました。

トップページ

f:id:aokashi:20191130011119p:plainf:id:aokashi:20191130011137p:plain
それぞれPCとスマートフォンの表示。

前回の投稿 から大きく変更しました。PCでは広々とした空間を、スマートフォンでは扱いやすさをコンセプトに配置し直しました。

PCのレイアウトを大きく変えた理由は2つ存在しています。

  • 10年くらい前に閲覧した、コンテンツのメニューを端に寄せて中央のコンテンツを大きく見せるWebサイトの配置に感動した覚えがあって、それに影響を受けた
  • 最近のWebサイトだとデバイスのサイズ分のトップ画面があって、それに追従するため

下部には最新のブログ記事と自分の各SNSへのリンクが設置されています。FontAwesome でアイコンがある場合はそのアイコンが使用されます。ちなみに、 FontAwesome は react-fontawesome を使用しました。

github.com

トップページは下へスクロールすると、自分に関する説明が表示されます。ただ、スマートフォンだとスクロールを促すような演出が無いので、自分に関する説明を見ないまま別のコンテンツに進んでしまうかもしれません。現状の課題点ですね。

レイアウト

トップページ以外のレイアウトについては、下記のように変更しました。

f:id:aokashi:20191117010735p:plainf:id:aokashi:20191117010848p:plain
PCではCoden Cityで採用された左メニューレイアウトを採用。懐かしい!

なお、最下部には「トップへ戻る」ボタンが配置されています。

ポートフォリオについては、画像があるとカルーセルが設置されます。カルーセルは pure-react-carousel を使用しました。スタイルシートが自由に調整できるのでおすすめです。

github.com

GatsbyJS は、公開されている React のコンポーネントパッケージが自由に利用できるところが良いですね。

コンテンツ

コンテンツは下記のように変更しました。

また、トップページでは、これに加えて、外部サイトであるブログと WWA FanSquare も含めています。

ポートフォリオは、インターネットを始めた頃から現在までの間に制作したものを集めました。昔のコンテンツをまとめていたことは昔もありましたが、場所が分散していました。今回のポートフォリオで分かりやすくまとめられて満足しています。

廃止したコンテンツについて

  • お知らせ ... ブログや Twitter で随時告知しますのでそちらをご確認ください。
  • おすすめサイト ... 今の個人サイトにリンク集を掲載する習慣が無いことと、以前掲載していたWebページがアクセスできなくなったため、しばらく公開を取りやめます
  • Portable Launcher 紹介サイト ... GitHubリポジトリを紹介ページにしても良さそうなので、今回の再構築では含めませんでした
  • Playing と Making ページそのもの ... サイト管理が難しくなった関係で廃止になりました

ちなみに、過去のWebサイトについては、ポートフォリオの各ページで見ることができます。

URL の変更について

以前は一部ページのURLで .html が含まれていました。

  • 当サイトについて: about.html
  • 各素材ページ: materials/*.html

ただ、 .html を含めないほうが URL を間違えないので、全ページ .html を省略しました。

また、WWAJavaアプレットの起動方法については、資料集に移転しています。

スタイルシート

スタイルシートも変更を加えました。フレームワークBulma を採用しました。WWAや素材、ポートフォリオの各項目にBulmaのBoxを使用しています。

Bulmaに決めた理由としては、下記の理由が挙げられます。

  • Bootstrap ... 構築してしまうとBootstrap臭がしてしまい、Webサイトのオリジナリティを失う
  • Tailwind CSS ... sassとの相性が悪い
    • text-smp-4 といった、CSSの記述で済ませられるクラスが多数用意されていて、これを使用すると sass を使用する意味が無くなる
    • 実はWebサイトのリニューアルの目的の1つとして、 sass を使用したWebサイト構築が含まれている
  • Bulma ... sass で構築している関係で sass を使用することが可能で、変数の調整でカスタマイズが可能

いかにフレームワークっぽさを感じないで、sassの特徴を発揮できるか模索した結果です。

ただし、Bulmaを使用した場合でもカスタマイズの幅には限界はあります。ポートフォリオの各ページにある「〇〇とは」の項目は、本来なら枠線を付けたかったのですが、タイトルを含めると枠線が無効になるため、仕方なく枠線無しで表示しています。今後のアップデートに期待ですね。

f:id:aokashi:20191116144841p:plain
〇〇とは(AboutNote) の表示

ちなみに開発に掛かる時間を短縮するために、オレオレフレームワークの廃止を目標としましたが、前述のフレームワークの選定のために逆に時間を費やしてしまいました。

なお、ページ全体のレイアウトについては、グリッドレイアウトを使用したいことと、フレームワークだけでは限界があることから、フレームワークのクラスはあまり使用していません。

ホスティング

Aokashi Home 自体は Netlify でホスティングしておきたかったのですが、ここはWWAのランキングCGIの関係上、ホスティングは引き続きVPSにしました。ただし、VPSの中身も作り直しました。

  • 以前
    • CentOS 7 を使用
    • Apache を導入していて、ドキュメントルートは www.aokashi.netcontents.aokashi.net で違う
    • デプロイはクラウド上の wercker が行う
  • 今回
    • Ubuntu Server 18.04 を使用
    • Docker を導入
    • docker-compose-letsencrypt-nginx-proxy-companion リポジトリを使用していて、www.aokashi.netcontents.aokashi.net をコンテナで分けている
    • なお、 www.aokashi.net は NGINX + fcgiwrap 、 contents.aokashi.net は従来と同じく CentOS 7 + Apache の構成になっている
    • その上、ドキュメントルートはリクエストの location によって異なる
    • デプロイは現時点では手動だが、後に Jenkins や Concourse をVPS内で導入し、デプロイを自動化するつもり

github.com

f:id:aokashi:20191130160721p:plain
ドキュメントルートの事情をまとめるとこんな感じ。

docker-compose-letsencrypt-nginx-proxy-companion は .env ファイルを作成して立ち上げておくと、あとはコンテナに環境変数を与えるだけで HTTPS 対応のサイトが出来上がります。 Let's Encrypt 証明書を certbot で取得したときは入力に手間がかかりましたが、ここまで簡単過ぎてびっくりしました。

実装で躓いたところと解決策

再構築するにあたって躓いたところと解決策についてです。

絞り込み機能が実装できない

GatsbyJS は markdown ファイルや YAML データの取り込みに GraphQL というクエリ言語を使用しています。 GraphQL を利用することで、出力されるデータのフォーマットが分かりやすくなったり、 filtergroup といった SQL で使用していた絞り込みやグルーピングを使用することができたりします。

WWA素材やポートフォリオで、タグによる絞り込み機能を実装しようとした場合、下記のようなことをついしてしまいがちです。

const data = graphql`
query WWAMaterialDataQuery {
  allWwaMaterialYaml(filter: {tags: {in: "${this.state.tagBy}"}}) {
    nodes {
      name
      file
      description
      publishedAt
      tags
    }
    group(field: tags) {
      fieldValue
    }
  }
}
`

これだと String interpolation is not allowed in graphql tag とエラーが発生してしまい、動作しません。

GraphQL のクエリは GatsbyJS のページ生成時 (gatsby build とか) にしか処理されないため、ユーザーの操作で変わってしまうプロパティやステートの変数はもちろん、予め定義した定数でさえも割り当てることができません。

今回の絞り込み機能は、データを出力するメソッドを実装して絞り込みを行ったり、タグ毎にタグ一覧ページを作成したりして解決しました。

const data = graphql`
  ...
`

const WWAMaterialData = data.AllWwaMaterialYaml.nodes.filter(
  node => node.tags.includes(this.state.tagBy)
)

パスに # を含むページがビルドできない

これは、ポートフォリオページにタグ機能を実装する際に発生しました。

ポートフォリオの各項目に付いているタグを取得し、そのタグごとに項目一覧のページを生成する流れをしていました。しかし、動作確認用で使用している Netlify でビルドが失敗してしまい、ログを確認したところURLの禁則文字がパスに含まれていました。

実はポートフォリオの各項目のタグに C# を与えていて、 # が原因で正常にページが生成できていなかったようです。

f:id:aokashi:20191130162438p:plain
画面のように、ページ一覧には C%23 となっていても、 404 が返ってきます

上記の事情がありましたので、仕方なく C#C_Sharp という名前に変更する形で解決しました。どうしても # を含めたい場合は ♯ のように全角にする手もあります。

ちなみに、Wikipedia でもURLの禁則文字をページタイトルに含めることができないため、C# では C_Sharp という名前になっています。

しかし、どうしてプログラミング言語の名前に禁則文字を含めるんですかね。

ポートフォリオの frontmatter の日付が日付として認識してくれない

frontmatter とは、Markdownページの先頭にあるプロパティを記述した箇所です。

---
path: /portfolio/wwa_game
template: portfolio-item
date: "2010-02-21"
title: WWAゲームの制作
season: junior-high-school
tags:
  - WWA
images:
  - path: /portfolio/wwa_game/making_wwa_2010.png
    alt: 2010年頃のWWAゲーム制作の様子
    description: 初期のWWAゲームの制作の様子です。
---
--- の上にあるのが frontmatter です。

このうち、日付を記述するためにdateを設定しました。実はGatsbyJS の GraphQL では日付のフォーマット (YYYY-MM-DDThh:mm:ss とか) になっていれば日付として認識され、 GraphQL で日付を取得する際に下記のようにを記述すればフォーマットを変更することができます。

frontmatter {
  path
  title
  tags
  date: (formatString: "YYYY月MM日DD日", lang: "ja-JP")
}

しかしながら、ポートフォリオの項目をひたすら増やしていると、Unknown argument "formatString" on field date とエラーが表示されてポートフォリオが機能しません。これは、日付として GraphQL が認識していないことでしょう。

自分のサイトのリポジトリのコミットを見ていると、一部の項目で日付のフォーマットを満たしていない記述が見つかりました。ただし、修正しても認識してくれないため、下記のように実装を変更しました。

  1. formatStringlang の使用を諦める
  2. 代わりに、日付を引数にフォーマットを変更するメソッドを定義し、フォーマットを変更したい場合はそのメソッドを利用する

Element type is invalid のエラーが発生している

この不具合の原因は、 importexport で間違いがあります。

import graphql from "gatsby" // { } で囲っていない

const header = ({ children }) => {
  return (
    <header>
      ...
    </header>
  )
}

// export default ... が無い

Invalid Hook Call のエラーが発生している

Invalid Hook Call というのは、 React の Hooks 機能に関わる不具合ですが、 React の Hooks 機能を使用しなくてもこのエラーが発生することがあります。

この不具合の原因の一つとしては、下記のようにクラスコンポーネントuseStaticQuery を使用していることです。

class WWAMaterialPage extends React.Component {
  render() {
    const data = useStaticQuery(`
      ...
    `) // Invalid Hook Call
  }
}

useStaticQuery メソッドはクラスコンポーネントでは使用できません。StaticQuery コンポーネントを使用する必要があります。

class WWAMaterialPage extends React.Component {
  render() {
    return (
      <div>
        <StaticQuery
          query={graphql`
            ...
          `}
          render={ data =>
            <>
              ...
            </>
          }
        />
      </div>
    )
  }
}

なお・・・クラスコンポーネントでなければいけない理由の一つとして、ステートを使用したいことが挙げられますが、1つか2つぐらいしかステートを使用しなければ、 React の Hooks 機能を使用する手もあります。

const WWAPage = () => {
  // onlyWWAWing がステートそのもの、変更したい場合には checkOnlyWWAWing(値) を使用する
  const [onlyWWAWing, checkOnlyWWAWing] = useState(false);
  const data = useStaticQuery(graphql`
    query {
      ...
    }
  `)
  return (
    ...
  )
}

再構築後の予定について

  • 後にCIを導入し、デプロイを自動化する予定です
  • 資料集については引き続きPukiwikiで運用を続けますが、遠くないうちに当サイトの一部としてGatsbyJSに取り込む予定です
  • ポートフォリオについては、就職して落ち着いてきたら資料集に統合して、次追加されるコンテンツのスペースを確保するつもりです
  • 廃止したお知らせは、どこかの場所で保管するつもりです
  • 実は、 WWAのランキングCGIの関係上 と言っておきながら、ランキングCGIがまだ稼働できていません
    • 遠くないうちに修正する予定です
    • ただ、WWAのランキングは Java アプレットWWAからじゃないと登録できないし、もう廃止しても良いのではと思ってしまう

今回のWebサイト構築で、Apache で普通のHTMLを動かすサイトから、NGINX 上で React で動作するサイトへと変化しました。今思うと 変化しすぎたなあー と感じています。研究活動や就職活動で支障が出ないように調整したため、消えてしまったコンテンツ (特にお知らせのアーカイブ) もありますが、近いうちに何かしらの形で残そうと思います。

ちなみに4年前は MODX 、2年前はHUGOでWebサイトを作り直したので、2年毎に作り直ししているんですよね。今回の GatsbyJS での再構築でだいぶ疲れてしまったので、ここ数年は GatsbyJS とお世話になろうかなと思います。

12月になったところですが、これからなにか発表できるものがあるかもしれません。今後もよろしくお願いします。

参考記事

akabeko.me

lab.unicast.ne.jp