🫠

Next.jsとRailsで日記アプリを作って学んだこと

に公開

はじめに

大相撲5月場所、楽しみですね
はじめまして、株式会社ウェイブでエンジニアをしているほさざえもんです
普段業務で使っているNext.jsとRailsで0からプロダクトを作った経験がなかったので日記アプリを個人開発しました

画面収録

実装したコードはこちら 👉 GitHub

開発期間は4週間くらいでした
個人開発を通して学んだことやハマったところについて振り返りたいと思います!

きっかけ

自分は今まで競プロやCTFの経験はあるものの、Web開発に関しては道筋のあるハンズオンに取り組む程度で0から考えて何かを作った経験がありませんでした
エンジニアたるもの、何かしらの0から開発する経験はしたいな〜と思って取り組み始めました
ただ作るだけだと、次に繋がるものが少ないので「何かしらのアウトプットをすること」「何かしらの設計技法を取り入れる」の目標を掲げました

技術スタック

フロントエンド:Next.js (App Router) + TypeScript + Tailwind CSS
バックエンド:Rails APIモード + PostgreSQL + devise_token_auth
インフラ:Docker(frontend / backend / db のマルチコンテナ構成)

技術スタックに関しては、勉強にもなると思ったので業務で使っている言語を中心に選択しました。

実装したこと

以下の機能を実装しました

  • ユーザー認証
    • サインアップ・ログイン・ログアウト
  • 日記機能
    • 作成(title, content, date)
    • 一覧表示(カレンダー形式+リスト形式)
    • 詳細閲覧、編集、削除
  • 計画機能
    • 開始日・終了日・内容を登録・管理

工夫したこと

API連携のために lib/api/APIクライアントを共通化
さらに、トークン付きのリクエストを管理する ヘッダーフック(getAuthHeaders) も導入し、責務を分離した構成を目指しました。これはコードの中で「目的の異なる処理を分けて管理しよう」という関心の分離という設計技法に該当します

躓いたこと/学んだこと

fetch に認証ヘッダーがつかず401

原因は「localStorageから取得したトークンがfetchのヘッダーに入っていなかった」ことでした

以下を行い解決しました

  • getAuthHeaders()lib/api/diaries.ts に定義
  • ログイン後に localStorage に access-token, client, uid を保存
  • それらをすべてfetchのヘッダーに設定
headers: {
  "Content-Type": "application/json",
  "access-token": accessToken,
  "client": client,
  "uid": uid,
}
  • このあたりは devtoolsのNetworkタブで確認しました。今回でdevtoolsとは仲良くなれた気がします

画面遷移後にトークンが保持されない

ログイン後、localStorageにトークンを保存しても、画面遷移後に消えてしまうバグが一時発生。

原因は useEffect のタイミングや fetch 側の取り出しミス。
トークンの読み取り処理を1つの関数にまとめて解消しました。

  • 基本的ですが同じような処理は一つにまとめた方が良いですね

Rails起動時に Rack::Cors が初期化されない

Rails起動時に Rack::Cors が初期化されていないというエラーが発生。

原因はgem 'rack-cors'development グループ内にしか定義されていなかった。
gem 'rack-cors'Gemfile のグローバルスコープに移動し、docker compose build を実行して解決。

  • CORSはすべての環境で必要になるので、グループに分けずトップレベルで定義した方が良いですね

JSX.Element[][] に対して Cannot find namespace 'JSX'

useState<JSX.Element[][]>() で TypeScript コンパイルエラー。JSX が見つからないと言われる。

原因はtsconfig.jsonjsx の設定がなく、Next.js によって "preserve" にされていたため、JSX 名前空間が認識されませんでした
型を明示せず、以下のように書き直すことで解決:

const [rows, setRows] = useState<React.ReactNode[][]>([]);
  • Next.js の tsconfig は自動的に最適化されるが、型の扱いで詰まることがあるらしいです。JSX.Element ではなく React.ReactNode を使うのが無難です

Tailwind クラスが build 時に除外される

Tailwind のクラス(例:bg-red-500)がコンポーネントで指定しても反映されませんでした。
これが一番沼っていて、chatGPTに呆れられるんじゃないかってくらい質問してました

原因はTailwind は静的解析でクラスを抽出するため、動的なクラス名は除外される。
コンポーネント毎にCSSを定義して解決しました。global.cssclsxsafelistを試したのですがうまくいかずでした。

  • ロジックとスタイルを分離した方が安定しますね。あとデザインって難しい

良かったこと

  • RailsとNext.jsの連携構成を一通り自分で作れたこと
  • API通信周りの責務分離(lib/api)やトークン管理の設計に多少慣れたこと
  • 技術スタックの整理に加え、「どこまで共通化すべきか」の設計観点が多少身についたこと
  • Tailwind CSSのカスタムUIにしっかり向き合えたこと
  • ChatGPTと仲良くなれたこと

所感

世は正に大AI時代で、技術的な問題はだいたいAIが解消してくれるので、一番のボトルネックは自分のモチベーションだったと思います。
なので、いつも自分の作業机で応援してくれている、いつメンに感謝したいです。
応援団

それでは次回「大相撲五月場所振り返り ~大の里横綱昇進おめでとう~」の記事で会いましょう。👋
読んでいただきありがとうございました!

wwwave's Techblog

Discussion