はじめに
Vite + React + TypeScriptでChrome拡張を作り、Chrome Web Storeに公開しました。
この記事は、実装から公開までにやったことを自分用の備忘録としてまとめたものです。
Chrome拡張は普通のWebアプリと似ている部分もありますが、実際に作ってみると manifest、background service worker、content script、permissions、ストア審査など、Webアプリとは違う詰まりどころが多かったです。
今回作ったのは、YouTubeを目的を持って使うためのChrome拡張です。
YouTubeのShorts、ホームフィード、関連動画、コメント、Autoplayなどを隠し、YouTubeを開く前に「何を見るのか」を入力するPurpose Checkを出します。
この記事では、Chrome拡張を作って公開するまでの技術メモとして書きます。
技術スタック
使ったもの:
Vite
React
TypeScript
CRXJS
Tailwind CSS
daisyUI
Chrome Extension Manifest V3
構成はmonorepoにしました。
youtube-dopamine-detox/
apps/
extension/
web/
packages/
shared/
apps/extension がChrome拡張本体です。
apps/web はLPとPrivacy Policyです。
Chrome Web StoreではPrivacy Policy URLが必要になるので、LP側はCloudflare Pagesにデプロイしました。
拡張機能の構成
今回の拡張は、ざっくり以下の構成です。
popup
options
background service worker
content script
intercept page
役割はこんな感じです。
| 画面 / 処理 | 役割 |
|---|---|
| popup | 現在の状態確認とYouTubeを開く導線 |
| options | 各種設定、目的ログ、言語切り替え |
| background | YouTube URL検知、purpose checkへのリダイレクト |
| content script | YouTube上のShorts、関連動画などの非表示 |
| intercept page | YouTubeを開く前に目的を入力する画面 |
manifest設定
Manifest V3では、必要な権限を明示します。
今回使った主な権限は以下です。
storage
tabs
alarms
host_permissions for youtube.com
用途はこうです。
storage:
設定と目的ログをChrome storageに保存するため。
tabs:
YouTubeタブをpurpose check画面へリダイレクトし、確認後に元のURLへ戻すため。
alarms:
セッション時間の管理に使うため。
host_permissions:
YouTubeページ上のUI要素を制御するため。
Chrome Web Storeの審査では、権限ごとに理由を書く欄があります。
不要な権限は削る必要がありますが、今回のようにYouTubeページ上でDOMを操作するなら、YouTubeへのhost permissionは避けられません。
ただし、<all_urls> ではなく、YouTube関連ドメインに絞りました。
content scriptでYouTubeのUIを隠す
YouTubeのUI制御はcontent scriptで行いました。
やっていることは、かなり地味です。
- Shortsへの導線を隠す
- ホームフィードを隠す
- 関連動画を隠す
- コメントを隠す
- 終了画面のおすすめを隠す
- Autoplayを止める
YouTubeはSPAなので、初回ロード時だけ処理しても不十分でした。
ページ遷移したように見えても、実際にはDOMが後から差し替わります。
そのため、以下のような対策が必要でした。
- 初回実行
- URL変化の検知
- MutationObserver
- 必要に応じた再適用
ここはChrome拡張というより、YouTubeのDOMと戦う感じでした。
Purpose Checkの流れ
今回一番試したかったのが、YouTubeを開く前に目的を書くPurpose Checkです。
流れはこうです。
1. ユーザーがYouTubeを開く
2. background service workerがURLを検知
3. intercept pageへリダイレクト
4. ユーザーが目的を入力
5. 一時許可をChrome storageへ保存
6. 元のYouTube URLへ戻す
これにより、「なんとなくYouTubeを開く」前に一度だけ摩擦を入れています。
完全ブロックではなく、目的のある利用だけ通すイメージです。
ローカル保存にした理由
MVPでは、アカウントもバックエンドも作りませんでした。
理由はシンプルです。
- 実装を軽くしたい
- Privacy Policyを説明しやすくしたい
- 最初はユーザーの反応を見るだけで十分
- 外部送信がない方がChrome Web Store審査でも説明しやすい
設定と目的ログは chrome.storage.local に保存しています。
外部サーバーには送っていません。
初期MVPでは、このくらいで十分だと判断しました。
LPとPrivacy Policy
Chrome Web Storeに出すには、Privacy Policy URLが必要でした。
そのため、拡張機能とは別にLPを作りました。
構成:
apps/web
デプロイ先:
Cloudflare Pages
Cloudflare Pagesでは、monorepoの中の apps/web だけをbuildしました。
設定は以下です。
Root directory: /
Build command: npm run build -w apps/web
Build output directory: apps/web/dist
普通のViteアプリだけなら npm run build と dist で済みます。
今回はmonorepoだったので、workspace指定でbuildしました。
Chrome Web Store公開で必要だったもの
公開時に用意したものです。
拡張機能のzip
128x128のアイコン
スクリーンショット
Small promo tile
説明文
Privacy Policy URL
権限の説明
テスト手順
2段階認証
zip作成で重要なのは、manifest.json がzipの直下に来ることです。
dist フォルダごとzipにするとズレます。
自分は以下のようにしました。
npm run build -w apps/extension
cd apps/extension/dist
zip -r ../../../youtube-dopamine-detox-v0.0.1.zip .
バージョンを上げて再アップロードする場合は、manifest側のversionも上げます。
version: '0.0.2'
環境変数の扱い
Viteでフロントエンドから使う環境変数は VITE_ から始めます。
LP側ではCloudflare Pagesに環境変数を設定できます。
一方、Chrome拡張側はChrome Web Storeで環境変数を設定するわけではありません。
ローカルでbuildするときに .env を読み込み、その値がbuild後のJSに埋め込まれます。
例:
VITE_FEEDBACK_FORM_URL=https://forms.gle/...
その後にbuildします。
npm run build -w apps/extension
フォームURLを変えたら、拡張機能は再buildして再アップロードが必要です。
公開してみて感じたこと
Chrome拡張は、実装だけならWebアプリの延長で作れます。
ただ、公開まで含めると考えることが一気に増えます。
- 権限は本当に必要か
- Privacy Policyに何を書くか
- host permissionsをどう説明するか
- スクショやストア素材をどう作るか
- Chrome Web Storeの審査にどう出すか
特に、権限とプライバシー周りは雑にできません。
今回は、アカウントなし、外部送信なし、ローカル保存中心にしたことで、かなり説明しやすくなりました。
まとめ
Vite + React + TypeScriptでもChrome拡張は作れます。
ただし、普通のWebアプリとは違って、以下を早めに考えておくと楽です。
- manifest設計
- permissions
- content scriptの責務
- background service workerの役割
- Privacy Policy
- Chrome Web Store用素材
- 公開後のフィードバック導線
技術的にはまだ荒い部分もありますが、まずはChrome Web Storeに公開するところまで到達できました。
ここからは、実際に使ってくれる人を探しながら改善していきます。
作ったもの
今回の知見を使って、YouTubeを目的を持って使うためのChrome拡張を作りました。
YouTube Dopamine Detox
LP
YouTubeを完全にブロックするのではなく、Shorts、ホームフィード、関連動画、Autoplayなどを減らして、開く前に目的を書くための拡張です。
フィードバックも歓迎です。(googleform)
最後に
こんなチャレンジやってますんで気が向いたら、覗いてみてください。
参考
- Chrome Extensions Docs: https://developer.chrome.com/docs/extensions/
- Chrome Web Store Publish: https://developer.chrome.com/docs/webstore/publish
- Chrome Web Store Privacy fields: https://developer.chrome.com/docs/webstore/cws-dashboard-privacy
- CRXJS: https://crxjs.dev/vite-plugin
