Reactで作成したポートフォリオサイトをNextに移行した


この記事は個人開発 Advent Calendar 2020 5日目の記事です。

昨日は@alclimbさんによる「【個人開発向け】失敗の経験と成功した人から学んだこと10選」でした。
私はあまり一般公開のアプリは作らないので、なかなか大変そうだなーと参考になりました。ありがとうございました。


半年ほど前に作成したFirebase×ReactでつくったポートフォリオサイトをNextに移行した話です。

ポートフォリオサイト概要

「Qiitaなどの外部サイトに投稿した記事」と「個人開発の製作物」をまとめたサイトがほしいなーと思い、半年ほど前にReactでポートフォリオサイト「焼きらぼ。」を作りました。

外部サイトの記事リンクを貼るだけでなく、サイト内で直接記事を投稿できるようにしています。
(ポートフォリオサイトというよりはブログに近い感じ)

FirebaseとReactで作成しました。

ポートフォリオサイトの概要や設計について詳しくは焼きらぼ -概要を参照してください

サイト:https://yaki-lab.web.app

課題

公開して半年ほどたちましたが、いくつか課題がでてきました

表示速度が遅い

まず表示速度が遅く、トップページの表示に1秒ほどかかっていました。
どうやら表示時にReact(クライアントサイド)でDB(Firestore)からデータ取得をしてからレンダリングしてたため、遅くなっているようでした。

DBのセキュリティが弱い

Firestoreのセキュリティ設定が「認証なしで全ユーザにREADを許可」になっていたため、セキュリティが弱い状態でした。
またアクセス毎にDB接続を行っていたため、アクセス数が増えたり悪戯をされたりするとFirestoreの1日の使用量を超えてしまう可能性もありました。

対応

2つの課題に対応するため、アクセス時にクライアントサイドでDB接続を行わないことにしました。

そもそも記事の投稿は私一人しか行わない上、更新頻度もあまり高くありませんでした。
そのため、記事一覧ページや記事ページは編集後(DB登録後)に静的サイトとして出力して公開しても問題ありません。
そうすると、DB接続を行わないため表示速度も早くなり、セキュリティも向上します。

というわけで記事一覧ページや記事ページなどの一般公開用サイトをNextのSSGを利用して静的サイトとして公開することにしました。

NextのSSGではビルド時にDBからデータを取得し、静的サイト用のファイルを出力することができます

※記事の投稿・編集を行える管理者用サイトはDBの最新データが必要なため、認証必須にしてクライアントサイドでDB接続を行うままにしています(URL非公開)

Next(SSG)への移行

Nextへ移行する際は新規でNextのプロジェクトを作成し、一般公開用サイトをページ単位で移行しました。
移行ポイントは以下のとおりです。

ルーティングの変更

Nextはpage下のフォルダ構成でルーティングを行ってくれるため、ただファイルを配置するだけでルーティング設定が行えます。
Reactで作成していた時はReactRouterDomを使ってルーティングの設定を行っていましたが、この部分は不要になりました。

変更前
<Route exact path='/Article/:id' component={Article} />

またhttps://***/Article/記事IDといったようにURLに変数が含まれるものは、Articleフォルダ下に[id].jsのように[]で囲ったファイル名に変更することで対応できます。

初期処理の変更

ページ表示時にDBからデータを取得する処理を、useEffectで行っていたのを、getStaticPropsで行うように変更しました。またデータをstateで保持する必要もなくなったので削除しました。

変更前
  useEffect(() => {
    // firestoreから取得
    getData.then((data) => {
      // stateにもたせて保存していた
      setValues(data)
    })
  }, []);
変更後
// ビルド時にサーバーサイドでDB取得する
export const getStaticProps = async () => {
  const data= await getData()
  return { props: { data: data }  }
}

getStaticPathsの実装

NextのSSGではビルド時に全ページのデータ取得を行うため、[id].jsのようなURLに変数を含むページはgetStaticPathsで変数が取りうる値をすべて取得する必要があります。

変更後
export const getStaticPaths = async() => {
  const ids = await getIds()
  return {
    paths: ids,
    fallback: false,
  }
}

リンクのhref変更

SSG機能では、a.jsで作成されたページはa.htmlとしてファイル出力されます。
そのため、a.htmlにアクセスしたいとき、URLはa.htmlにする必要があります。

※デバッグ時(Nextが動いているとき)のURLはaでアクセスできますが、静的サイト出力後のa.htmlファイルに対するアクセスはa.htmlでないとアクセスできません。

なのでリンクを貼るときはhrefに.htmlをつける必要があります。

変更前
<a href="a">リンク</a>
変更後
<a href="a.html">リンク</a>

静的サイトとしてデプロイ

NextのSSGで静的サイトとしてファイルを出力するにはnpm run build && next exportを行います。実行するとoutフォルダに静的サイト用のファイルが出力されます。

package.jsonでコマンドを定義することもできます。、

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "export": "npm run build && next export"
  },
  ...

outフォルダの内容をFirebaseのホスティングにそのままアップロードすることで、静的サイトを公開することができます。

ちなみにFirebaseのホスティングは1プロジェクトで複数持てるので、一般公開用サイトと管理用サイトのドメインを分けています。
複数のドメインはFirebaseのコンソールで追加することができます。
そしてデプロイ時にドメイン名を指定することで特定のドメインにデプロイできます。

firebase deploy --only hosting:ドメイン名

結果

実際のページはこちらです
静的サイトなので当然ですが、ページの表示速度が上がってすぐに表示されるようになりました。
DBの無料枠も気にする必要がなくなったので安心しました。

ついでに行った対応

デザインの修正

当初は少しやっつけで作成したため、半年たって見るとデザインがちょっと微妙だと思いました。
せっかく機能追加を行うということで、デザイン側も手を入れることにしました。

昔のデザインはこちら

なんかガタガタしてるというかうるさいというか。。。

↓改良後

個人的にはけっこうスッキリして良くなった…と思っています!

リファクタリング

コピペで作成したほぼ同じ内容のコンポーネントを共通コンポーネント化したりしました。

やってみた感想

ReactからNextへの移行は思ったより簡単にできてよかったです。
また機能改善のついでにデザイン周りを見直したり、気になっていたところのリファクタリングをかけることができてよかったです。

一回完成させた個人開発の製作物は、長く運用していないとなかなか見直さないものですが、機能改善やリファクタリングを行うと新しい発見があって良いと思いました。また仕様や挙動がある程度決まっているため、新しい技術を試しに導入してみやすい環境だと思います。

みなさんもすでに完成済みの個人開発の製作物があれば、一度見直してみてはいかがでしょうか。

明日は@UedaTakeyukiさんによる「個人開発でも気軽に使える Copy Protection サービスをつくりました」です!お楽しみに!