SoundCloudみたいなサービスを個人で開発した
リンク
スクリーンショット
プロフィール | 検索 | プレイヤー |
---|---|---|
サービス概要
タイトル通り、現状はSoundCloudみたいに自分の作った曲を投稿できるサービスです。
現在実装されている機能は以下の通りです。
- 楽曲投稿
- アルバム
- ランキング
- メッセージ(DM)
- フォローとフィード(タイムライン)
- いいねとブックマーク
- タグ・キーワードによる検索
このようにとりあえずSoundCloudのような楽曲投稿サービスとしての機能は最低限備えていますが、ゆくゆくはミュージシャンの活動拠点になることを目指しています。というのは、例えばYouTubeへの投稿や各種サブスク配信へのリンク、またライブなどイベントへの参加日程など、活動の実績/予定などがすべて1ページでわかるようにしたいと思っています。
また、ミュージシャン同士の交流/発見の場にしていきたいです。バンドメンバーや演奏を依頼する相手を見つけるのに適した場所はあまりありません(いわゆるメン募サイトは機能不全だし、Twitterが現実解かもしれないが検索性は高くない)。
というわけで、実は目指しているものはSoundCloudというよりも、音楽版の Dribbble や Behance です。 さまざまなミュージシャンの才能を集約して検索可能にすることが最大の目標です。
(もちろんX.comのように各ユーザーのページの閲覧のためにログインを強制するつもりは一切ありません。in/outは完全にオープンにします)
技術構成
まず技術選定の方針としては可能な限り無料を原則としました。現状、費用がかかっているのはVercelのProプランのみで、スケールしない限りは$20/月で運用できます(Vercelは他のプロジェクトとも共用しているので立ち上げに際してコストがかかってるという意識はないです)。
Next.js (App Router) + Vercel
フレームワークはNext.jsで、Vercelにデプロイしています。
VercelなのでServer ComponentやISR、Server Actionなど最新機能を盛りだくさんで実装しました。それらの使い心地などに関して細かく述べることはしませんが、これによってVercelにロックインされてしまった感はあります。
以前試したときは Next on Cloudflare Pages は(Pages Routerであっても)あまり実用的に動かなかったし、Cloud Runでも全機能が使えるわけではなさそうです。ただ、Vercel Proに含まれている1TBの帯域を超えるほどスケールするには時間がかかるという前提で、とりあえずは最新機能を使ってみました。
CSS Modules + Camome UI
スタイリングにはCSS Modulesを使用しています。また、基本的なコンポーネントの基盤として Camome UI を採用しました(といってもこれは僕が開発したライブラリなので採用もクソもないですが)。
CSS Modulesを選んだ理由は、何よりも最も生のCSSに近いからです。Server Componentでも使えるスタイリング方法といえばTailwindもありますが、寿命という意味では個人的にちょっと信頼できないと感じています。
Camome UIのコンポーネントをベースにしつつ、その他のUIも Camome UIのデザイントークン を使ってスタイリングしています。
Radix UI
Camome UIはHTML/CSS以外は提供しないので、モーダルなどの複雑なUIは Radix UI を用いて実装しています。
他にもHeadless UIも使ったことがありますが、Radixの方が痒いところに手が届くしスタイリングもしやすいと思います。特に data-
属性を使ったアニメーション制御はかなり開発体験がいいです。
Prisma
ORMには Prisma を使っています。
有名なライブラリなのでいまさらあまり言及することはないと思います。ひとつだけ不満を述べるとすれば、複雑なクエリを書く際に $queryRaw
に頼らざるを得ないことが多いということでしょうか。Drizzle はPrisma式のAPIとクエリビルダーの両方をサポートしているようなので、もしかしたらそのあたりはベターだったりするかもしれません(使ったことはないです)。
Supabase
データベースは Supabase を生のPostgresとして利用しています。
無料枠でも500MBまで使わせてもらえるので立ち上げには最適だと思います。それに単なるPostgresとして使うのであればロックインされることもないので安心です(無料枠だけ使って他に移行するのもアレですが)。
(マネージドPostgresとして見た際に、NeonやPlanetScaleなどの有料プランと比較して優位性があるかどうかはまだ検討していません。)
R2
画像や音声ファイルのホスティングには Cloudflare R2 を採用しました。
楽曲投稿サービスという特性上、静的ファイルの置き場所の選定はコストに大きな影響を与えます。R2の最大の長所はエグレス料金ゼロ、すなわち下りの通信帯域に料金が加算されない点です。また、10GBまでは無料枠に含まれるというのも嬉しいです。
NextAuth.js
認証基盤は NextAuth.js です。
外部の認証系SaaSに依存しなくても必要十分な機能とDXを提供してくれる優秀なライブラリです。PrismaAdapterを使えばDBへのアカウントやセッションの保存をいい感じにやってくれます。また、Googleなどの外部IDプロバイダとの連携も簡単です。
ただし、新規ユーザー登録を一時的にブロックする (issue) ことが現状できないといった難点もあったりします。
TanStack Query
クライアントサイドでの非同期のデータ取得には TanStack Query を使っています。
はじめのうちはNext.jsのRoute HandlerでAPIを作成して通信していましたが、途中からServer Actionによる取得に切り替えました。これが用途として正しいのかまだよくわかってないですが、とりあえずフレームワーク組み込みのRPCとして機能しています。
こだわりポイント
プレイヤー
デスクトップ | モバイル |
---|---|
楽曲のプレイヤーは <audio>
タグをベースに実装しています。再生リストにも対応しているのでアルバムごとの再生などもできます。デスクトップではサイドバーに常時表示し、モバイルではApple MusicやSpotifyのようなモーダルなUIです。
また MediaSession API に対応しているので、ホーム画面へのタイトルやアートワークの表示、再生や早送りなどの操作が可能です。
ちなみに、iOSでは audioElm.volume
を変更しても音量を絞れないのでUI上のスライダーは効果がありません。WebAudio APIを経由すれば実現できそうですが、とりあえず先送りです。
公開ページの静的生成 + キャッシュのパージ
プロフィールや楽曲などの公開ページはNext.jsの機能を使って静的生成されています。cookies()
などの Dynamic Functions を使用しないことでリクエストごとにDBへアクセスすることを防ぎ、高速に配信しています。
しかしキャッシュされるということは、ユーザーがコンテンツを更新しても即座に反映されないということです。そこでフォームのPOSTごとに revalidatePath
を実行し、オンデマンドでキャッシュのパージをしています。
これで確かにパフォーマンスと即時反映を両立できているのですが、正直どのテーブルの更新がどのページに影響を与えるのかを完全に把握するのは難しいし、面倒です。複数人で開発するならなおさらだと思います。とりあえず今回はNext.js開発チームの思想に沿う形で実装してみましたが、引き続きこれをやりたいかと言えば微妙です。
また、サーバーサイドでログイン状態に応じた動的な応答をしないということは、その部分をクライアントサイドで補う必要があるということです。例えばあるユーザーをフォローしているかどうかをチェックするための非同期通信と状態管理が必要です。このあたりも静的生成を選択する上で混入する複雑性の一部といえるかもしれません。
TODO
- メンバーや依頼の募集
- コラボレーション
- 高度な検索
まとめ
興味のある方はぜひ登録よろしくお願いします。
Discussion