ラジオ好きの方に向けたサービスを作りましたので、ぜひ使ってほしいという一心で記事を執筆しました。また、未経験初学者のなかではすこし珍しめな技術スタックを採用してみて感じた「よかった点、むずかしかった点」についても記載しました。(技術面での採用というよりかは初心者の個人開発における苦労ポイントとなっています、ご了承ください)
- Reactを中心に学んでいる初学者の人
- 個人開発におけるReactフレームワーク悩み中の人
- ラジオ、お笑い好きの人
にぜひ読んでいただきたいです。
開発したのは、ラジオの切り抜きシェアプラットフォームサービスです。
コードはこちらです
サービスの説明
利用イメージ
Spotify、Youtubeのリンクと、再生開始/終了時間を投稿するだけで簡単にシェアできます
どうして作ったか
私自身、ラジオというコンテンツが好きなのですが、以下の2つが個人的に課題でした。
- 面白かったところにしぼって聞いたり、遡ることが難しい
- 動画コンテンツは切り抜きや「最も再生された部分」などの機能で、見どころに絞って聞くことも可能
- 一方でラジオコンテンツはYoutubeのClip機能(1分)で収まりきらずシェアされていなかったり、そもそも動画プラットフォームに掲載されているものは著作権的に✗
- 新しい番組に入門しにくい
- コンテンツの特性上、全編聞き流す中で面白いポイントや興味を引くポイントが出てくるが、1エピソードがそこそこ長い
- 聴数ランキングがない(Spotify)、radikoもアーカイブが残らないのでどのエピソードから聞くべきか迷う
これらの課題を解決するために、(自分が欲しかったので)
- 簡単に面白かったところを共有できて、気軽に聞き直せる
- ラジオ番組ごとに面白かったポイントをまとめているので、入門しやすい
以上2点の特徴を持つプラットフォームを作りました。
技術構成
サービス構成
Remix
Web標準に準拠することを重視した設計がなされている、Reactベースのフルスタックフレームワークです。
(今後 React Router v7に統合されると発表されています。)
Cloudflare D1 + R2
- CDN のエッジロケーション上で動く Cloudflare の分散データベース
(Cloudflare R2の上に構築されているとのことなので図示している構成は厳密には誤っているのかもしれませんが、簡略化して記載しています) - 5GBまで無料のDB、1日当たり500万回のリード、10万回までのライトが可能
- Cloudflare R2はエグレス料金が無料で、S3互換のあるオブジェクトストレージ
Umami(webアナリティクス)
Cloudflareを使っていたため、Cloudflare Zaraz + Google Analytics4 も候補にありましたが、ユーザーへのクッキーの利用許可表示が不要という点で、Umamiを採用しています。
Ignore IPの設定がCloud版だとできなかったため、supabase、Vercelでセルフホストしています。
参考にさせていただいた記事
他に利用しているライブラリ
Vite + Vitest
開発途中でRemix × Viteがデフォルトとなったため移行しました。
またユニットテスト、コンポーネントテストにおいては、大きくJestと変わらないと判断して速度重視でVitestを採用しています。
Mantine(UI Component ライブラリ)
基本機能を作るのに必要なコンポーネントがすべて揃っており、他ライブラリの選定に悩むことなくきれいなUIを作れるのでとても重宝しています。(Tailwind併用はやや面倒な印象があります)
Conform + Zod (サーバーサイドとフロントエンドのバリデーション)
FormDataの検証をサーバーサイドでも簡単に実装できるライブラリです。
Jotai (グローバルな状態管理ツール)
Recoilを過去Next.13利用時は使っていましたが、バンドルサイズが小さい、keyが不必要でシンプル、継続的に開発されているという観点で採用しています。
Biome + Lefthook (Linter, Formatter, Githook)
高速かつ管理対象のファイルがシンプルになるという観点からBiomeを採用しています。
音声コンテンツということもありアクセシビリティを考慮した設計を心がけていますが、
Eslint利用時のeslint-plugin-jsx-a11yへの互換性が不足しているという話もあるため、そのあたりは今後の拡充を待ちながら適用していくのが良いと考えています。
参考にさせていただいた記事
工夫点
ポートフォリオで終わらせないための、高速で、スケールして、安価(ほぼ無料)な構成
個人的に、就職活動が終わったと同時にサービスクローズしてしまうのはもったいないと感じていますし、特にこのサービスはコンテンツが長期的に貯まることで魅力的になるものです。スケールして、サーバー費用も抑えられる、かつパフォーマンス的にも問題がないという点で、Remix + Cloudflare D1という選択肢にしています。
(構成上ビルドサイズの制限がありますが、軽量なアプリケーションなので問題ないと判断しました)
もともとは業務委託に際して勉強する必要があったためRemix(ORMはPrisma)を使いはじめたという経緯でした。DBに使っていたPlanetScaleの無償プランが廃止となり、ちょうどRemix + Viteの安定版が発表されたタイミングでもあったため、個人的にエッジDBを利用した開発にも興味があり、開発開始後3週間ほどでVite+Cloudflare D1 + Drizzleに変更しました。
シームレスなラジオ再生
再生リンクと再生時間をまとめたデータベースにとどまらず、サービス内で気軽にラジオ切り抜きが再生できるように意識しました。
Spotify SDKを使うという手段もありましたが、利用時Spotifyのログインが必須という点がユーザーの高い障壁となるため、少々取り扱いに難儀しましたがiFrame APIを採用しています。
(2022年時点でSpotify利用経験率は13% 参考 https://webtan.impress.co.jp/n/2022/10/04/43402)
また、サービス内で他のページに遷移したときも再生が維持されるので、再生しながらアプリ内を横断して検索するという体験を阻害しないようにしています。
投稿しやすいUI
投稿時の煩雑さを極力減らすために、以下の2点を新規切り抜き投稿画面で実装しています。
- 現在選択している番組を番組名のデフォルト値とする
- 再生開始時間を設定すると、終了時間の「時間」「分数」が連動
- モバイル端末でも秒数まで選択できるように、TimeInputの代わりにSelectを利用
Conformのform.updateを利用して値の変更をしています。
<Select
{...getInputProps(startHours, { type: "text" })}
name="startHours"
data={hours}
defaultValue={"00"}
withCheckIcon={false}
clearable={false}
allowDeselect={false}
onChange={(value) => {
if (isEndTimeUnset) {
form.update({
name: endHours.name,
value: value || "00",
});
}
}}
size="md"/>
参考にさせていただいた記事
今回の構成でむずかしかった点、よかった点
✗ むずかしかった点
フレームワークとしてのベストプラクティスが見つけづらい
Remixの採用事例も増えてきているものの、日本語での記事は少ないのが現状です。
(2024/07/06時点で、Qiitaでの記事数は Next.jsが4088に対してRemixは177)
初学者のうちは、挙動がRemix等のフレームワークによるものか、Reactによるものか、Javascriptによるものかの判別がつきづらいと考えているので、ある程度実務での採用事例が多い言語を選ぶほうがよりベストプラクティスに近づけるのかと思います。
相談する相手を見つけにくい
上記と関連しますが、他のメンタリングサービスにおいてもRemixが対象フレームワークとして記載されている事は多くありません。
メンターマッチングサービスであるMentaにおいてもRemixと明記されているメンターさんは2件でした。(開発を中心に行っていた4月頃は0件でした)
他の方からのフィードバックを受けづらいという点は初学者の個人開発だからこそ機会損失だったなと感じています。
採用技術の変更とそれに伴った移行に苦労した
Remix Viteの安定版に伴った移行や、Prisma + PlanetScale → Cloudflare D1 + Drizzleへの移行など、大きな変更を幾度かしたため、かなり苦労したポイントではありました。
◯ よかった点(結果的に)
シンプルに機能実装できて過去の実装を思い出しやすい
Remixは、一つのファイルにloader関数とaction関数を書くことが可能です。
一つ前の機能実装から時間が経つと、どこでなんの処理をしていたかを追うだけで一苦労となっていましたが、機能ごとの挙動を追いやすくなりました。(もちろんディレクトリ設計やコミット時のメッセージや切り分けが悪かったのかもしれませんが)
また、データ更新時のRevalidate処理を度々書く必要もなく、useRouteLoaderDataを使うことで状態管理は最小限に留められるため、Next.13やTanstack Queryを利用してたころと比べるととても快適です。
公式ドキュメントや海外記事、内部コードを見る機会が増えた
Remixの採用事例は先程も申し上げた通り少ないため、必然的に海外のDeveloperの記事や公式ドキュメントを参照する機会が増えました。また、最近Yamada UI(OSS)にもコントリビュートさせてもらう機会があり、UIライブラリに関しては挙動で詰まった際、内部コードまで見に行くように心がけています。
どうしても私のような初心者かつエンジニア職以外の本職との両立となると、とりあえず動くコードのコピペやChatGPTから出力されたコードに走りがちですが、一次情報(公式ドキュメントやソースコード)を見に行けるとより実力がついていくのかなと思うので今後も心がけたいです。
これからの機能的改善点
連続再生機能+切り抜き集作成機能
動画配信コンテンツにアップロードされているラジオコンテンツの一つとして、「まとめ」があります。
あるテーマやコーナーごとにトーク内容が集められており、再生回数も多いです。
また、まとめ動画のコメント欄では、「このテーマだと、~ のトークも好き」といった、他の場面もまとめに追加してほしいといった旨のコメントも多く寄せられているので、今後は
- まとめ作成機能
- まとめへのエピソード追加を提案できる機能
を作成していきたいと考えています。
切り抜きごとのページを作成
現在は各切り抜きのページを設けていませんが、拡散しやすいように各切り抜きにURLをもたせ、OGP画像の自動生成機能をつけることで、よりシェアされるプラットフォームとしたいです。
@vercel/og Pages Pluginを利用して動的にOGP画像を生成しようと試みましたが、サイズ制限により一筋縄ではいなかったので、今後の課題です。
Cloudinary等のSaaSを利用することも思慮に入れています。
参考にさせていただいた記事
それ以外にも
- 再生機能を中心としたE2Eテストの導入
- CI/CDの構築(現在は最低限プッシュ時の自動デプロイのみ)
- Conform機能を利活用したフォームの整備
などなど取り組みたいところは多分にありますが、順番に解決していこうと思います。
正直良し悪しポイントについては、「そりゃそうだ」という内容になってしまった気もしますが、未経験の状態から変な尖りを見せるとこのような影響もありますという一例として参考にしていただけると嬉しいです。
サービスもぜひ一度触ってみてください!
最後まで読んでいただきありがとうございました。