LoginSignup
67
62

More than 3 years have passed since last update.

Next.js + RailsでポートフォリオサイトをISR対応&メンテナンスフリー化した

Last updated at Posted at 2021-02-25

2年ほど前にNuxt.jsを使ってポートフォリオサイトを作成しました。
今回、このサイトをNext.js + Railsでリニューアルしたので、経緯を記事にまとめます。

リニューアル後のページ

https://portfolio.y-uuu.net/
image.png

デザインは前回のものを踏襲していて、ほとんど変わっていません。

リポジトリ

リニューアルの目的

Next.jsを使って何か作りたい

nextjs.png

昨年からReactやNext.jsを触ってノウハウを蓄積するようにしています。私自身普段はRailsを使った開発をしているので、Next.jsを採用するとしたらRailsと組み合わせて使う可能性が高いです。

昨今のフロントエンド界隈の盛り上がりを横目に、フロントエンドにNext.js/バックエンドにRailsを用いて、何か作りたいと思っていました。

デプロイなしで内容を更新したい

image.png

前回、勢いでポートフォリオサイトを作成したものの、単なる静的ページとして公開していたので記載内容を変更するためにはVueコンポーネントを直接編集する必要がありました。

今回は管理ページを別途作成し、ログインすることで記載内容を容易に追加・変更できるようにしています。

メンテナンスフリーにしたい

image.png

image.png

リニューアル後のサイトではQiitaやZennに公開した記事、SpeakerDeckに公開したスライドをを自動的に収集し、メンテナンスしなくても内容が自動更新されるようになっています。

システム構成

Next.jsのデプロイ先としてVercelを、Railsのデプロイ先としてherokuを使っています。
また、画像の格納先としてAWSのS3を利用しました。

Untitled_Portfolio_Site_-_Cacoo.png

ライブラリ・フレームワーク

フロントエンド

  • React
  • Next.js
  • React Hook Form
  • react-dropzone
  • react-spinners
  • react-tippy
  • axios
  • SWR
  • Tailwind CSS

バックエンド

  • Ruby on Rails
  • devise
  • faraday
  • rails_same_site_cookie
  • AWS SDK for Ruby V3

実装上のポイント

ISR(Incremental Static Regeneration)

Next.jsを使ってISRを実現しています。herokuがレスポンスを返す時間に関わらず、来訪者がすぐにページを閲覧できるようにする狙いです。

以下はISRの挙動の解説です。

Next.js(ISR有効)は、アクセスがあった際に生成済みの静的ページをレスポンスします。このとき、herokuへのアクセスは発生しません。

Notification_Center.png

前回のページ生成から指定した時間を経過した後にアクセスが発生すると、静的ページを再生成します。このときNext.jsはサーバーサイドでページを再生成するのを待たず、いったん前回の静的ページをレスポンスします。

Untitled_Portfolio_Site_-_Cacoo.png

再生成が完了すると、以降その静的ページをレスポンスします。

前提として、herokuのFreeプランだと30分間アクセスがない場合にdynoがSleepするので、次にアクセスがあった場合にdynoが起動するまで数十秒ほど待たされてしまうという問題があります。本来の使い方ではないかもしれませんが、バックエンドの処理に時間がかかる場合でも生成済みのページを即時にレスポンスできるという点で、ISRは有用だと感じました。

記事・スライドの自動収集

yuuu-portfolio-v2-api_·_Scheduler___Heroku.png

Heroku Schedulerを使うことで、日次でrakeタスクを実行して、記事・スライドを自動収集しています。

QiitaはAPIを、ZennはFeedを使って、自分自身の記事を収集しDBに保存しています。

namespace :qiita do
  desc "Fetch articles from qiita"
  task fetch: :environment do
    res = Faraday.get('https://qiita.com/api/v2/users/Y_uuu/items?per_page=100')
    return if res.status != 200

    items = JSON.parse(res.body)
    items.each do |item|
      next if Article.find_by(link: item['url']).present?

      item_res = Faraday.get(item['url'])
      next if res.status != 200

      Article.create(
        title: item['title'],
        body: item['body'].truncate(100) + '...',
        published_at: Time.zone.parse(item['created_at']),
        link: item['url'],
      )
    end
  end
end

SpeakerDeckは収集方法を悩んだのですが、よくよく調べると https://speakerdeck.com/yuuu.atom のように、自身のアカウント名の末尾に .atom を付与することでFeedを取得できることがわかったので、これを使って収集するようにしました。

認証

image.png

最初は「SPAの認証はJWT」という思い込みがあったのですが、いろいろ調べていくうちに「cookieを使った認証でも問題ない」との結論に至りました。認証のバックエンドもRailsで、deviseというGemを使ったよくある実装です。

ただし、今回はCrossOriginな構成のためつまづきポイントが多くありました。具体的な実装方法は別記事にまとめたので、興味のある方は参照ください。

Rails 6.1対応版: APIモードのRailsに対してCrossOriginなSPAからSession認証する方法

ファイルアップロード

image.png

当初は、バックエンドがRailsということで、Active Storageを使ってファイルアップロードを実現する予定でした。実装をしていく上で「わざわざActive Storageを使う必要があるのか?」という疑問が生じ、最終的にはS3の署名付きURLを使ってアップロード・閲覧する方式に変更したという経緯があります。

こちらも別記事にまとめたので、興味のある方は参照ください。

RailsをバックエンドとしたSPAでのファイルアップロード機能の作り方に悩んだ話

感想

ISRが良い

image.png

SSRとSSGのいいとこ取りができていて良いです。SSGのようにデプロイのビルドが長くなることもなく、かつSSRを使った場合に比べてページの表示が高速なので満足です。

Vercelが良い

image.png

今回初めてVercelを使ってみたのですが、GitHubのリポジトリを指定するだけで簡単にCI/CDを構築できました。前回Netlifyを使った時も同様の感動があったのですが、とかくNext.jsを使う場合はほとんど設定が不要で、噂通りVercelとの組み合わせがベストだと実感しました。

バックエンドのRails・herokuも良い

image.png

死んだと言われて久しいRailsですが、自分にとってはやはり最速で実装ができるフレームワークです。バックエンドは必要最低限実装しつつ、フロントエンドの実装に注力するスタイルで開発が進められました。

herokuを使うことでデプロイも非常に簡単でした。

まとめ

個人的には十分満足できるポートフォリオサイトが完成しました。

Next.js + Railsで何か作ろうとしている人の参考になれば幸いです。質問・感想などありましたら、ぜひコメントをお願いします。

67
62
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
67
62