週末にちょっとしたウェブサイトというかリンク集(?)を作った。 今回は生成AIツールをフル活用していつもより効率よく作業ができた。
生成AIツールについては日々、新しいものがヤバイすごいと宣伝されているけど、実際にどう使っているのかという情報が少ないと感じている。
なので具体的な使い方を書いてみることにした。
作ったもの
『最も重要な「最も重要なマンガ10選」10選』は「最も重要なマンガ10選」であげられている各作品を読み始めるためのリンク集です(なんのこっちゃ)。
「最も重要なマンガ10選*1」は、はてな匿名ダイアリーのミームで、各投稿者が雑誌ごとに全期間を対象としておすすめ作品をあげて寸評していくというもの。
それをみて読者も感想や自分のおすすめをコメントするわいわいしたやりとりがある。
話題的に過去語りが多く、中年うけがいい。
私もいくつか知らない作品があって気になっていたのでリストを作って順番に読んでいた。
そのリストをサイトとして公開した。
このサイトはシンプルな一画面で、リストのタイトルとリンク、コメントを表示するだけのもの。
リストははてな匿名ダイアリーの記事をスクレイピングして取得し、AmazonのAPIを使ってリンク先を取得している。
リンクにアフィリエイトタグはつけていない。
というか便乗アフィサイトが出現したらいやだなぁと思ったので先に自分で作成した。
私は購入したものだけアフィリエイトタグをつけるポリシーをもっている。
開発の概要
生成AI/LLMツールを効果的に活用することで、ウェブサイト開発の効率を向上させることができた。
主なポイントは以下の通り:
- デザイン段階でのClaudeの活用:初期デザインの生成に Claude Artifacts を使用した
- コーディング作業:Cursor を使用して、AIアシストによるコード修正や提案を活用した
- データ処理におけるLLMの活用:スクレイピングしたデータの構造化やAmazon商品データの選別にGPT-4 Turboを使用し、複雑なデータ処理を自動化した
- 動的・静的コードの使い分け:プロトタイピングの段階では動的なアプローチを、本番環境では静的生成(SSG)を採用し、開発の柔軟性とパフォーマンスの両立を図った
- 拡張性を考慮したアーキテクチャ設計:静的サイトをベースにしつつ、将来的な機能拡張に備えた構成を採用した
- 柔軟なデータベース設計:Firestoreを採用することで、LLMの出力結果を直接保存でき、将来的なデータ構造の変更にも対応しやすい設計とした
最終的なアーキテクチャ
カテゴリ | 使用技術 |
---|---|
デザインツール | Claude Artifacts Cursor |
フロントエンド | Next.js(Static Exports) shadcn/ui Tailwind |
バックエンド (サーバーレス) |
Firebase Firestore Vercel AI SDK |
インフラ | Cloudflare Workers(Static Assets) |
UI開発に生成AIツールを使う
初期デザインの参考元
最初の発想時点でこれはカタログサイトになると思っていたので、似たようなマンガ系のサイトをいくつかチェックして、他にどのような構成やデザインが使われているかを確認した。
さらに、ECなどマンガ以外の類似サイトも調べて、インスピレーションを得るように努めた。
その結果、クックパッドの新デザインがTailwindを活用しており、自分の好みにぴったりで参考にしたいと感じた。
モバイルでクックパッドの画面をChromeのDevToolsで改変して機能を削減、スクリーンショットして、これを生成AIツールに渡すために保存しておいた。
デザインツールの選定と比較
ウェブサイトのコーディングを自動化する生成AIツールはいくつか選択肢がある。 今回はClaudeのArtifactsが作ったデザインをベースにした。
比較検討した主なツールと選定理由:
Claude Artifacts(採用)
- 特徴:生成物をプレビューする汎用的機能。プロンプトでshadcn/uiベースのコーディングを頼んだ
- 選定理由:最初の生成物の品質が高かった
ChatGPT Canvas(不採用)
- 特徴:OpenAI「Artifactsっぽいやつを俺たちも提供するぞ」
- 不採用理由:シンプルに成果物がしょぼく、自分自身でコーディングしたぐらいのレベルのものしか生成できなかった
v0(不採用)
- 特徴:Artifactsを今回のようなプロトタイプ生成に特化。コードをユーザーが変更して調整できる
- 不採用理由:Claude Artifactsと大きな差異がなかった。モデルが同じ(claude系)であることが原因と考えられる
Bolt(不採用)
- 特徴:プロジェクト全体を生成
- 不採用理由:部品的な取り込みが難しかった。モデルはclaude系
総じてUIのコーディングはClaudeのが得意、というのが分かった。
ツール上で細かい部分まで詰めたい場合はv0の方が良さそうだが、私は「あとはCursorさんとローカルでやりますんで」と切り上げたのでArtifactsで間に合った。
またプロジェクトの全体コードごと丸投げしたい場合はBoltもいい選択だと思う。コンポーネントのファイル分割を適切に行ってくれた。
Next.jsの利用
そして、Artifactsが生成するshadcn/uiのデザインを活用するために、Next.jsを選んだ。
shadcn/uiに加えてTailwindやradix-ui/も含まれており、これらをパツイチでビルドするためにNext.jsが最適だ(帰納的)。
ただし、この条件ならRemixを使用するのも選択肢の一つである。
Next.jsのが普及しているので構成トラブルが少ないと判断した。
Cursorを活用した開発
画面の更新作業にはCursorを使った。
Cursorの機能を活用することでNext.jsアプリのコード修正作業の効率が上がった。
デフォルトのモデル設定はClaude 3.5 Sonnetにしている。
Cursorでは、チャット上でソースコードの修正指示を出すと、diffを提案し、それをワンボタンでマージできる。
エディタとチャットサービス間をクリップボードで運搬するという人間が行うコピペプログラミングの本質的な作業をぐっと煮染めたようなUXである。
Next.jsのFast Refreshで、変更がすぐに反映されるのでフィードバックループも短縮される。
エディタがGitと連携しているので、万が一の際に気軽にロールバックできる点も安心だ。
また、Composerという実験的機能もあり、複数ファイルを横断して編集することもできるが、今回は1ファイルを中心に作業したため、活用しなかった。
データ整形にLLMを使う
スクレイピング
最初にはてな匿名ダイアリーの記事をLLMを活用してJSON形式に構造化し、データベースに保存するプロセスを構築した。
記事URLはせいぜい10個なので自分で探した。 URLを渡すと、記事のタイトル、本文、コメントを取得するスクリプトを作成した。
まず、各記事のURLから記事本文を取得し、そこからタイトルとコメントを抽出する。
次に、これらの要素をgpt-4-turbo(Vercel AI経由)を使ってJSONフォーマットに整形した。
最終的に、生成したJSONデータをFirestoreに保存する。 これを後工程で利用する。
作業の前にまずWebコンソールでこのプロンプトが機能するかを検証した。
データ抽出にはcheerioを使った。
はてな匿名ダイアリーは環境に優しいサービスなのでブラウザ上で電力を消費してビューの差分を書き換えたりしない。
そのようなサイトにはヘッドレスでブラウザと同じAPIが使えるcheerioがフィットする。
Amazonの商品データ取得
Amazonの商品データ取得には、PA API:Product Advertising API 5.0を使用した。
ASINが判明すると、その商品に対応するサムネイル画像とリンク先も確定できる。
まずデータベースに保存したタイトルを使ってPA APIで検索を行う。
その検索結果からシリーズの初巻を選ぶためにLLMに判断を任せた。
検索結果が必ずしも巻数順に並んでいるわけではないためルールベースで選ぶのは難しい。
これも実装前にまずWebコンソールで機能するかどうかを検証した。
LLMの選定
LLMにはgpt-4-turboを採用した。
Vercel AIのドキュメントに載っていたデフォルト設定*2をそのまま使用し、実際に試したところ精度に問題がなかったため、特に調整せずにそのまま取り入れた。
Vercel AIを利用しているため、精度やコストに不満があれば他社のモデルやローカルLLMに切り替えられる柔軟性もある。
APIが抽象化されているのでコードの変更もいらない。
以下にzodを使ってレスポンスをJSONに整形する方法が書かれている。
コストについてはあまり気にしていなかったが、今回の作業全体で約10ドル程度に収まった。
単発の課金なので、許容範囲内だと感じた。
継続利用して料金高すぎと感じたらgpt-4-oやローカルLLMにスイッチする算段もあるので、gpt-4-turboな理由は最初の選択肢でしかない。
デフォルトがgpt-4-turboであるのはAI SDKの開発者の人によると意図はあるらしく、Toolsを使った多段の論理処理では安定する感覚があるらしい*3。
動的なコードと静的なコードの使い分け
開発プロセスを効率化し、柔軟性を保ちながらも手早くサイトを立ち上げるために、動的なコードと静的なコードを戦略的に使い分けた。
TypeScriptを使わない範囲を定めた
あえてTypeScriptのコードを避ける範囲を設けることで、プロトタイピングの効率化を図った。
具体的には、データ処理のスクリプト部分などを対象とし、特にスクレイピングにおいては、最初はChromeのDevTools上で現在閲覧しているページに対して手動でスクレイピングを試した。
その結果、期待するデータが得られた段階で、最終的にNode.jsのコードに移行して自動化した。
スクリプトはJSにする、という方法はOSSプロジェクトでも自然に行われている。たとえばRemixもそうだ(remix/scripts at main · remix-run/remix)。
他にはNext.jsアプリ内のデータフェッチ関数の初期実装を進めるにあたり、まずNode.jsコンソール(REPL)でfirebase-adminを呼び出して試行錯誤し、どのコードを実行すればどのような結果が得られるかを確認した
コードが複数行にわたるようになったら、.mjsファイルとしてbin/ディレクトリに配置し、組んだロジックが正常に機能するかを確認する。
この際、VSCodeではCtrl+Alt+Nで手軽に実行とデバッグができ、効率的に検証が行える。
その後、UIに渡すデータ構造が固まったら、型定義を作成し、lib/ディレクトリに移動して清書し、最終的にTypeScript化してアプリに組み込む準備を整える。
このように段階的に進めることで、いきなりpageコンポーネントと接続したコードに手をつけるよりも、データ処理の部分を確実に作り込むことができた。
静的サイト生成(SSG)の採用
この効率化の考え方をサイト設計にも拡大し、Static Exports(SSG:すべて静的なサイト生成)を採用する方針とした。
閲覧時にはすべてのコンテンツが事前に確定している状態となり、Cloudflare CDNにすべてのファイルを配置することで、アクセスの高速化を図る。
アーキテクチャが確定した段階でまずデプロイしてPageSpeed Insightsでパフォーマンスを測定し、深刻な結果が出ないことを確認した(Largest Contentful Paintは予測していた範囲だったので許容した)。
さらに、この静的サイトにはリンク集以外の機能を持たせる余地も残しており、必要に応じてNext.jsやサーバーサイドの機能を用いて動的な要素を追加できるように、デプロイ先をCloudflare Workersにした。
これは過去の開発の経験から「ペライチの静的サイトだと思っていたら、複数画面のSSR対応が必要になってアーキテクチャを作り直した」という事態を避けるためである。
このアプローチにより、シンプルながらも拡張性のあるサイト設計が実現できた。 コンテンツは静的に、アーキテクチャは動的にという考え方になる。
柔軟なデータベース設計
これは、データベースにFirestore(動的スキーマでNo SQL)を採用していることとも関係している。
一般的には、RDBを選び、JOINに耐えられるように前もって柔軟なテーブル設計を行い、free tierがあるPostgresSQLサービスなど*4を利用するのが個人開発者にとってのファーストチョイスとなりがちだ。
しかし、Firestoreを使うことで、スキーマを決める前に、先のLLMの生成結果をそのままキャッシュとして保存できる柔軟性が得られ、開発中のデータ構造の変更に対しても対応できる。
静的サイトにしたことで、ビルド時のみクエリを実行する形にして開発中は本番DBを管理画面からごちゃごちゃ操作してもデプロイしない限りはサイトに影響がない。
最初はいいけど運用に入ったら作業量のしわ寄せが来るんじゃないの? と思うだろうが、そこを静的サイトにして決定を遅延させている。
サーバーサイド処理をしたくなったタイミングでよいしょとデータをRDBに盛ってくる算段だ。
余談だが、Firestoreのドキュメント設計については1つのビューに必要なデータを1クエリで取得できるようにデータ構造を工夫するのがポイント。
まとめ
生成AI/LLMツールは開発プロセス全体を最適化し、短期間でサイトを立ち上げることができる強力な味方となる。
今回の例では通常の開発の1/2の時間でサイトを完成させることができた(当社比12時間→6時間レベル)。
私はフロントエンドは専門でないため、ClaudeやCursorが生成したコードを元に学習した。もしかしたらこれがAI世代のコード写経に変わるのかもしれない。
あと最後にマンガの話ですが、個人的最高おすすめ作品の『ザ・ワールド・イズ・マイン』は読んでください。