Blog

ブログをNext.js(SSG)に移行しました

このブログはReactで1から作成しており、CSR (Client Side Rendering) として実行してました。

この度、Next.jsのSSG (Static Site Generation) を導入してみたので、その報告になります。

移行の背景

以前はサイトが表示されてから記事情報を取得しており、ブログ記事の表示まで時間がかかっていました。

今回はSSGで記事毎に予め生成されたHTMLを表示しているので、初期表示が早くなりました。

また、他の記事に遷移するときもprefetchが効いているので、基本的に爆速だと思います。

もう一つは外部サイトにmetaタグを渡すのが難しかったことです。

以前はprerender.io を使ってbotに向けてpreレンダリングされたhtmlを渡していました。

しかし、はてブ等の一部のbotは正しく判定できず、meta情報を渡せずにいました。

この問題もSSGに移行することで解決することができます。

ちなみに、ISR(Incremental Static Regeneration)も検討しましたが、記事数がそこまでないことと、CDNと組み合わせたときにキャッシュの制御が難しくなるのでやめました。

移行方法

Next.jsの新しいプロジェクトを作成し、必要に応じて以前のプロジェクトからコピーしていく方法で移行を行いました。

サイト自体は3年前に作成したものなので、至るところが非推奨の書き方になっていましたが、きりが無くなってしまうので、今回は目をつぶってNext.jsへの移行をメインで進めています。

APIの待ち合わせの処理が減り、reduxredux-sagaをなくしたことにより、記述がシンプルになり、コードの量も減りました。

移行のためのすべての変更はここから確認することができます。

ページングのpathについて

今まで、ブログのリストを表示する際に2ページ目以降のURLはqueryを使って以下のようにしていました。

https://at-sushi.work/blog?page=2

しかし、Next.jsではqueryに対してSSGのpageを分けることはできないので、今回以下のようにpathを変更して対応しました。

https://at-sushi.work/blog/list/2/

ここで議論されてるようなので、今後追加されるかも知れません。

生成時のAPIのキャッシング

SSGに移行したことにより、HTML生成のタイミングで大量のAPIを叩いてるのが気になりました。

リストで取得したものを個別でも叩き直したり、同じAPIを複数回叩いたりしていて、サーバの負荷が高くなり失敗する可能性があったことと、Build時間も長くなっていました。

そのため、以下のようなCacheを用意してAPI call数を抑制しています。

export class BlogCache {
  private map = new Map<number, IBlog>()

  setList(list: IBlog[]) {
    list.forEach((item) => {
      this.set(item)
    })
  }

  set(item: IBlog) {
    this.map.set(item.id, item)
  }

  get(id: number): IBlog | undefined {
    return this.map.get(id)
  }
}

ページは並列に作成されているようで、同一APIが叩かれる可能性は残りますが、だいぶリクエスト数を減らすことができました。

対応したPRはこちらになります。

ホスティング

特にこだわりはなく、AWSのS3 + CloudFront + Route 53の構成になっています。

S3を静的ウェブサイト ホスティング設定にしており、CloudFront経由でアクセスするようになっています。

この方法で公開する場合、Next.jsの設定でtrailingSlashの設定をtrueにする必要があります。

// next.config.js
module.exports = {
  trailingSlash: true,
}

デプロイと更新

マージのタイミングでGitHubActionsでS3にsyncとCloudFrontのinvalidateをかけています。

人気の記事等は記事を追加しなくても変更される可能性があるので、毎時23時に定期実行されるようになっています。

記事を書いたタイミングでは、一旦workflowの手動実行で対応しようかなと思っています。

上記を合わせると、以下のような設定になっています。

on:
  push:
    branches:
      - 'master'
  schedule:
    - cron: '0 14 * * *'
  workflow_dispatch:

今後やること

久しぶりにブログ自体の更新を行いました。

Next.jsはすごく高機能で、やりたいことが一瞬で実現することができました。

また移行もスムーズで、ほとんどそのままのコードが動作することにも驚きました。

結構書き方が古くなってるところが多いので、少しずつ書き直したいなと思っています。

また、要望もあったRSSも追加する予定です。

WorksPhotosはまだ先になると思います…

人気の記事

kotlin coroutinesのFlow, SharedFlow, StateFlowを整理する

Jetpack ComposeとKotlin Coroutinesを連携させる

Layout Composableを使って複雑なレイアウトを組む【Jetpack Compose】

テスト用Dispatcherの使い分け【Kotlin Coroutines】

Flow.combineの内部実装がすごい話

Jetpack ComposeのRippleエフェクトを深堀り、カスタマイズも