🎉

2日でファン限定支援サイトを作った話

2024/05/30に公開

はじめまして。新時代IP創出事業を手掛けるsaipと申します。
普段は社員3人のスタートアップ株式会社TrippyでCCO兼CTOを務め、生成I受託事業の傍ら、AIキャラクターとのゲーミフィケーションされたコミュニケーションが楽しめるアプリ「Oz-オズ-」を開発・運営しています。

最近、「Oz-オズ-」のキャラクターのプロモーションのためにXで発信し始めた漫画の後日譚的コンテンツがメンバーシップ制で楽しめる「Oz Fanz」というWebサイトを思い立って2日で公開しました。

https://fanz.0z.chat

この記事では、どのような技術スタックを用いてそのような高速開発が可能になったかを公開し、皆様からのご鞭撻をもとに、粗いシステムを改善していこうという魂胆です。私のWeb開発歴は1~2年くらいなので、かなり考慮漏れが存在しています。テストを一切書いていないなど…。

選定の方針

あまり資金に余裕がないので、コストを極力抑えることを念頭においています。

また、Github CopilotやClaude Opusをフル活用しているため、彼らが書きやすいコードやモジュール化が実現できるスタックに自然に寄っています。

アプリケーション

フルスタックNext.jsアプリケーションをVercelにデプロイしています。各APIエンドポイントはNext.jsのAPI Routesで生やしています。

API RoutesでのルーティングはHonoを使用しています。エンドポイントの可読性が好きです。

VercelはデフォルトでAPI RoutesでEdge Functionsが利用できますが、今回はすべてNode.jsランタイムを使用しています。理由は、得られるメリットに対して制限がつらいからです。

また、App RouterではなくPages Routerを使用しています。最初はApp Routerで作り始めたのですが、認証かAPI周りでバグが消えず、泣く泣く作り直しました。Pages Routerは今となっては不便な部分もありますが、サーバーとクライアントの境目が明確にしやすい気がして好きです。

Next.jsでHonoを使う最大のメリットとしてRPC(APIに渡す値の型をクライアントに共有できる)があると思っていますが、面倒になってしまいそこまではやっていません。規模が大きく複雑になってきたら使います。

データベース

Supabaseを使用しています。PlaetScaleの無料枠がなくなってしまったので…。Supabaseは東京リージョンがあり、ダッシュボードも使いやすいので嬉しいです。スケールしたときのコストはVercel同様若干心配ですが。

ORM

Supabaseはsupabase-jsとしてデータベースクライアントが提供されているのですが、スキーマのマイグレーションのしやすさも含めて今回はPrismaを使用しています。PrismaはスキーマもクエリもCopilotやClaudeが上手に書いてくれます。

CDN

みんなだいすきCloudflare。Vercelへプロキシしています。

ZarazでのGAトラッキングタグ自動追加など、もはやなくてはならない存在です。

ヘッドレスCMS

Newtを使用しています。

Newtのお陰で管理画面を自前で実装する必要がなくなり、本当に感謝しています。
NewtのCDNから取得したデータを、サーバーサイドでの認証をもとに、ユーザーのサブスクリプションに応じてデータを加工してからクライアントサイドに返却しています。

ロギング・エラー通知

Baselimeを使用しています。Cloudflareに買収されて以来無料で使えるようになりました。Vercelとの連携が本当に簡単で、新しいプロジェクトを作ると勝手に取得してログ取ってくれるので本当に助かっています。

2024年末にBaselimeブランドは終了されると告知されていますが、今後はCloudflareのダッシュボードに統合され、Cloudflare WorkersやAI Gatewayなどのログ永続化がシームレスに使えるようになりそうで期待しています。

メール配信

まだ実装していませんが、Newtからの更新通知WebhookをCloudflare Workersで受け取って、MailChannels……が一番低コスト(というか無料)なのでは、と踏んでいます。

認証

NextAuth (v5) で、Google OAuthを呼び出しています。サーバーサイドでの認証はHonoのAuth.js middleware for Honoを使ってみていますが、かなりシンプルに書けて良いです。ただ、未認証だと問答無用でUnauthorizedを返してしまうので、認証状態に応じて異なるAPIエンドポイントをクライアントに叩かせているのがあんまりきれいじゃないなーと思っています。

スタイリング

shadcn/uiを使っています。shadcn/ui最高。なので、必然的にTailwind CSSを使用しています。shadcn/uiはシンプルで洗練されていて、気に食わない部分は直接@/components/uiに殴り込みにいけるので素晴らしいです。もはやこれがないとフロントエンド書けません。

画像のぼかし処理

Newtではデフォルトでimgix的なURLクエリパラメータでのリサイズができるので、32x32に潰した上でbase64でクライアントに送り、CSSのblurでぼかしています。next/imageも使用しています。

動的OG画像

これから実装予定です→実装しました。base64で圧縮した画像を表示させたかったので、OG作成用APIエンドポイントのクエリパラメータにそのままbase64文字列を乗っけて、イメージレスポンスを@vercel/ogを使って返しています。@vercel/ogは内部的にSatoriが使われているようで、CSSのFilter Blurが使えたので楽でした!

テスト

書いていません…。

決済

Stripeを使用しています。今回はStripe ElementsではなくCheckout Session URLをサーバーから発行するようにしています。

まだ信用のないサイトでは、サイト内にフォームを埋め込むよりStripeに飛ぶほうが安心感があるのでは?というのと、XやChatGPTなど、結構Stripeに飛ばしちゃうパターンも多いので、それにならった感じです。サブスクリプションの管理や支払い方法の変更もStripeのカスタマーポータルに飛ばせてしまうので、考えることが減って助かります。


まとめますと、サービス開発で一番しんどい部分である管理画面をNewtに外注し、フロントエンドはshadcn/uiに乗っかり、認証はNextAuth+Honoに任せ、Next.jsエコノミーに大部分を任せたのが開発効率化につながったのではないかな、と思っています。LLMにも最大限の謝辞を送ります。

かなり突貫工事・mainブランチ直コミットで雑に作ってしまっていますが、こういう雑でわがままで勢いに任せた開発が一番楽しいです。

まだ未完成の部分も多いので、少しずつ改良していきます。

Discussion