【個人開発】技術書検索サービス TechSeek の技術選定まとめ
はじめに
TechSeek は、技術書をタグやキーワードで探せる Web サービスです。
技術書探しでつまずきがちな点(キーワードが思い浮かばない、自分に合う本が分からないなど)を、少しでもスムーズにできたらと思い、つくりはじめました。
この記事では、個人開発として無理なく続けていくために、
どんな技術を選定したのか、そして実際の運用で良かったこと・詰まったことをまとめました。
技術選定の方針
TechSeek では、個人開発として無理のない構成であることを重視しています。
基準は次の2点です。
1. 固定費で運用できること
従量課金を避け、固定費で運用できる構成にしています。インフラには ConoHa VPS を使い、毎月いくらかかるかが分かる状態にしています。
お金の心配を減らし、機能をつくることや改善することに集中したいというのが理由です。
2. AI が参照できるコンテキストを、運用コストを増やさずに残せること
別でドキュメントを書くのではなく、コードそのものを仕様として使える形を意識しています。
- API 仕様は rswag でテストから OpenAPI を生成
- フロントは aspida で型付き API クライアントを自動生成
- UI は Storybook で、コンポーネント単位の表示・状態をドキュメントとして確認
こうしておくことで、運用コストを抑えつつ、AI が実装に必要な情報をコードや生成物から参照できる状態にしています。
全体アーキテクチャ概要
TechSeek では、フロントエンドとバックエンドを分離した構成を採用しています。
フロントエンドは Next.js、バックエンドは Rails API とし、インフラは VPS 上で運用しています。
1. アプリケーション構成(フロントエンド / バックエンド / インフラ)
フロントエンドとバックエンドは、それぞれ別の VPS に配置し、データベースには SQLite を採用しています。
Polyrepo 構成による開発フローの特徴
フロントエンドとバックエンドをリポジトリで分離することで、それぞれの開発サイクルを独立させられます。一方で、API 仕様を aspida で型生成する際には、「Rails API 変更 → OpenAPI 再生成 → Next.js 側で型生成 → フロント修正」というステップが発生し、修正時にリポジトリを行き来する必要があり、開発フローがやや煩雑になる場面もありました。
2. 外部 API 連携
- 楽天 Books API:書籍データの取得
- OpenAI API:書籍データに対するタグ提案や、検索サジェストの補助
3. 管理・UI 開発まわり
- ActiveAdmin:書籍・タグ管理
- Storybook:UI コンポーネント確認用
フロントエンド構成
レンダリング / ルーティング
・Next.js(SSR/SSG 併用)
当初は、Next.js だけで書籍データを JSON として管理し、静的生成(SSG)する構成を想定していました。
ただ、書籍数が増えるにつれて、JSON の整合性を保つことや、書籍データの追加・修正が徐々に負担になってきました。
さらに API キーを安全に扱う必要も出てきたため、サーバーサイド層を明確に設け、SSR も使える構成に切り替えました。
API 連携 / 型共有
・aspida
OpenAPI から型安全な API クライアントを生成します。Rails 側で OpenAPI を出し、Next 側で型を取り込みます。
# Rails 側: RSpec から OpenAPI を生成
bundle exec rswag:specs:swaggerize # spec/requests/*.rb が起点
# Next 側: 型付きクライアント生成
yarn aspida
OpenAPI スキーマから型を自動生成されるため、API 仕様変更のたびに型定義ファイルを手動で修正する手間が要りません。修正漏れのリスクも減らせる点が利点です。
データ取得(キャッシュ / 再検証)
・SWR
データ取得時のローディングやエラーハンドリングを
毎回実装しなくて済むよう、データ取得用のライブラリを導入しています。
シンプルで慣れていることもあり、SWR を採用しています。
状態管理
・zustand
グローバルに扱う状態が限定的なので、学習コストが低くシンプルな zustand を選びました。
書籍のブックマーク状態を React 側で保持・同期する用途であれば、十分でした。
スタイリング / アニメーション
・vanilla-extract / framer-motion
TypeScript で型安全にスタイルを管理できる点から vanilla-extract を採用。
アニメーションは状態変化に応じた動きを宣言的に記述できる点から、framer-motion を採用しています。
UI コンポーネント検証
・Storybook
ページから切り離し、コンポーネント単位で状態を固定して確認します。ローディング・空状態・エラー状態を Storybook に残すことで、実装意図を AI にも伝えやすくなりました。
今回作成した TechSeek の Storybook
https://storybook.techseek.site/
テスト(ユニット / UI / コンポーネント / E2E)
・Vitest / React Testing Library / Playwright
期待する振る舞いをコードとして固定するためにテストを書いています。AI による実装や修正でも意図がぶれにくく、必要なコンテキストを先に揃えてプロンプトのやりとりを減らせます。
ユニットテスト・コンポーネントテストに加えて、E2E テストも Playwright で導入しています。特に検索周りの挙動では、入力フィールド・検索ボタン・結果表示など複数のコンポーネント間の連携が複雑なため、バグが発生しやすい箇所でした。そのため、実際のユーザー操作に近い形で検索機能の動作を検証できる E2E テストを追加しました。
Lint / フォーマット
・ESLint / Prettier
JavaScript/TypeScript のコード品質を保つため、ESLint と Prettier を導入しています。ESLint でコードの品質チェックを行い、Prettier でコードフォーマットを統一しています。
・markuplint
HTML/Markdown の構造やアクセシビリティを検証する Lint ツールです。アクセシビリティ属性の不足などを自動検出できます。ビルド時に実行し、マークアップの品質を保っています。
環境変数 / シークレット
・dotenvx
dotenvx は、暗号化した .env をリポジトリで管理できるライブラリです。
シークレットとして管理するのは復号キーだけで済むようになります。
Rails の credentials に近い運用ができる点から、採用しました。
バックエンド構成
データ / ドメイン
・Rails API
バックエンドの基盤として Rails API を採用しています。UI や表示ロジックは Next.js に寄せ、Rails 側はデータとドメインロジックに集中させています。
Rails API の主な責務は以下です。
- ドメインロジック(本の扱い・公開条件・検索対象の判断など)
- 外部 API 連携(書誌情報取得、タグ付与)
- 管理画面向けデータ提供
- API 仕様の定義・公開(OpenAPI)
Next.js の Route Handler + Prisma で完結させる案もありました。しかし、Rails/ActiveAdmin の経験があり管理画面を早期に用意できる見通しが立ったこと、また業務で使っている Rails の API 設計を個人開発でも深掘りしたい点が決め手でした。
ちなみに、今回は Next.js 起点で作り始めたため、フロントエンドとは別に Rails API を用意しました。今あらためてつくるとしたら、わざわざリポジトリを分けず、AI を使った実装の速度を高めやすい Rails + inertia-rails 等のモノリス構成を選ぶと思います。
テスト(ユニット / インテグレーション / リクエスト)
・RSpec
API の振る舞い(レスポンス構造・ステータスコード・エラー時の挙動)をコードとして明示しています。
その結果、AI へのプロンプトのやりとりを減らせています。
API 仕様管理
・rswag
RSpec から OpenAPI 定義を自動生成します。API 仕様をテストコードに集約しています。
bundle exec rspec spec/requests/books_spec.rb
bundle exec rswag:specs:swaggerize # swagger/v1/swagger.yaml を生成
- API ドキュメント(Swagger UI):
管理画面
・ActiveAdmin
書籍データやタグは運用しながら増やす前提なので、DB + 管理画面で管理しています。書誌情報の確認・調整、公開/非公開の切り替え、タグ管理を、管理 UI を自前実装せずに済ませられています。
管理画面や API ドキュメントのイメージは以下のスクリーンショットで確認できます。
- 管理画面(一覧):
- 管理画面(新規登録):
- 管理画面(登録後詳細):
インフラ構成
サーバー / 実行環境
・ConoHa VPS / Kamal
固定費で読める VPS を採用し、Kamal で Docker ベースのアプリをシンプルにデプロイしています。「自分で把握できる範囲で固定費に収めたい」という動機に合致していました。
Kamal まわりの構成や手順は別記事にまとめています。
データベース
・SQLite
同時書き込みがほぼ発生しないため SQLite を採用。アプリと同じ VPS で完結し、DB サーバーを別途立てずに済みます。
想定している前提
- 書き込み頻度: 管理画面とバッチが中心で、同時書き込みはほぼない
- 許容する復旧時間: 数分〜十数分以内に戻せれば十分
- スキーマ変更: マイグレーション時はアプリを短時間止める運用で許容
SQLite はサーバー障害時の復旧を自前で設計する必要があるため、後述のバックアップ構成を用意しています。
代替案
マネージド DB(AWS / GCP など)
- 可用性や運用負荷の低さは魅力的
- ただし利用量に応じた従量課金となり、個人開発ではコストの見通しを立てづらいと判断
Turso(libSQL)
- SQLite 互換でレプリケーションや分散構成が可能
- ActiveRecord 向けは technical preview とされ、基本的な DB 操作の安定性に懸念があり、現時点では本番利用は見送り
バックアップ / オブジェクトストレージ
・Litestream / ConoHa Object Storage
SQLite は 1 ファイルに集約されるため、サーバー障害時に失うと復旧できません。Litestream で書き込みのたびにオブジェクトストレージへ継続バックアップしています。
- バックアップを手動で取得・管理しない
- 障害時に「戻せる状態」を常に維持
- 追加の DB やサーバーを用意しない
バックアップ先を同一プロバイダ(ConoHa)に寄せ、設定と運用をシンプルにしています。
Litestream の考え方や仕組みについては、以下の記事が参考になりました。
CDN / キャッシュ / セキュリティ
・Cloudflare
DNS を Cloudflare に向け、基本的な WAF / DDoS 対策を有効化。静的アセットは Cloudflare の標準キャッシュ設定で配信し、オリジンへのリクエストを抑えています。
CI / CD
・GitHub Actions
プルリク作成時に Lint(ESLint/Prettier)とテスト(RSpec/Vitest)を自動実行します。main マージで Docker イメージをビルドして Kamal 経由で VPS へ自動デプロイします。
Infrastructure as Code
・Terraform
Cloudflare の DNS やセキュリティ設定をコードで管理し、再現性を確保しています。
ログ管理
・Sentry
本番で発生した例外を収集し、Next.js のソースマップを送信してビルド前のコード上でエラー位置を確認できるようにしています。
開発ツール
Cursor
Cursor は、エディタ統合型の AI アシスタントです。
実装フェーズで使っています。spec-kit で作成した Tasks を基に、Cursor で実装を進めます。
Claude Code
Claude Code は、コード生成やレビューを支援する AI アシスタントです。
設計フェーズで使っています。spec-kit の Constitution / Plan / Tasks の作成を Claude Code で行います。
Devin
Devin は、AI エージェントによる開発タスクの自動化ツールです。
目視で確認しやすい軽めのタスク(CI 環境の構築、API レスポンスのカラム追加など)を任せています。軽めのタスクでも1ACU〜5ACU(日本円で約 350〜1,750 円)とコストはかかりますが、自動でPR を作成し、merge ボタンを押すだけでよくなるのはとても便利なため、使っています。
その他のツール
rulesync
- rulesync:AI ツールごとに散らばるルール(レビュー方針・規約・commands)を 1 つに集約し、各ツール向けに生成できる CLI。
spec-kit
- spec-kit:AI 実装に入る前に「前提・制約・受け入れ基準」を仕様としてまとめ、仕様→計画→タスク→実装の流れを型化するツールキット。
今後の変更に応じて、内容は適宜更新していく予定です。
Discussion