機能仕様書
ページ一覧
| パス | ページ名 | 認証 |
|---|---|---|
/ | トップページ | 不要 |
/search | 検索結果ページ | 不要 |
/videos/:id | 動画詳細ページ | 不要 |
/pricing | 料金プランページ | 不要 |
/profile | プロフィールページ | 必要 |
/contact | お問い合わせページ | 必要 |
/privacy | プライバシーポリシー | 不要 |
/terms | 利用規約 | 不要 |
トップページ (/)
- サービスの概要説明(キャッチコピー、主要機能のハイライト)
- CTA: 検索を試すボタン、ログインボタン
- 使い方の簡単な説明
- クレジットの仕組み説明: 「一度クレジットを消費した動画は、1ヶ月間クレジットを消費せずに何度でも閲覧できます」という旨を明示的に記載する(アイコン付きで視覚的にわかりやすく)
検索UI
- キーワード入力は 必須
- チャンネル選択は 必須。Combobox 形式(検索・選択可能なドロップダウン)
- プレースホルダー: 「チャンネルを選択またはURLを入力」
- チャンネルURLの直接入力にも対応
ボット対策(Cloudflare Turnstile)
/api/search エンドポイントは Cloudflare Turnstile によるボット対策で保護されている。主目的は YouTube Data API の無料クォータ(1 日 10,000 ユニット)を自動スクレイパから守ること。
- 配置: 検索系ページ(
/[locale]/search/**)の layout に<TurnstileProvider>を 1 つ常駐させる - 表示モード: invisible(
appearance: "interaction-only"+execution: "execute")。ユーザーにはウィジェットが見えず、裏で token を事前取得する - トークンの流れ: Provider が取得した token を
useTurnstileToken()フック経由でfetchSearchに渡し、x-turnstile-tokenヘッダに乗せて送信。送信完了後はrefreshToken()で次の検索用 token を先行取得するためユーザーに待ち時間は発生しない - API 側: Hono middleware (
apps/api/src/middleware/turnstile-middleware/) がchallenges.cloudflare.com/turnstile/v0/siteverifyで検証し、失敗時はTURNSTILE_REQUIRED/TURNSTILE_INVALID/TURNSTILE_UPSTREAM_ERRORを返す - 適用範囲: 現在は
/api/searchのみに適用。/api/contactはauthMiddleware(Google OAuth 必須)で保護されているため Turnstile は不要
環境変数:
CLOUDFLARE_TURNSTILE_SECRET_KEY(API)とNEXT_PUBLIC_TURNSTILE_SITE_KEY(Web)を参照。本番は Cloudflare ダッシュボードで発行、Preview / ローカルは Cloudflare 公式テストキーを使用する。詳細は 環境変数 を参照。
検索対象フィルター
- 検索対象を タイトル・説明文・ 動画内(字幕) から選択できる(チェックボックス)
- デフォルトは 3つすべて選択 された状態
- チェックを外した項目は検索対象から除外される(例: タイトルのチェックを外すと説明文・動画内のみで検索)
セマンティック検索(意味で検索)
キーワードの完全一致だけでなく、意味的に近い結果も検索できる機能。MeilisearchのHybrid Search(semanticRatio)を利用する。
UI構成
- スイッチ: 検索フォーム内に「意味で検索」トグルスイッチを配置
- OFF(デフォルト): キーワード検索のみ(従来通り)
- ON: キーワード検索 + セマンティック検索のハイブリッド
- Infoアイコン: スイッチの横に
(i)アイコンを配置- クリックするとダイアログが表示される
説明ダイアログの内容
- タイトル: 「意味で検索とは?」
- 本文:
- 通常の検索は入力したキーワードと一致する字幕を検索します
- 「意味で検索」をONにすると、キーワードと意味が近い表現も検索結果に含まれるようになります
- 例: 「最高」で検索 →「素晴らしい」「すごい」「感動的」なども検索結果に表示
- ボタン: 「閉じる」
プリセット選択
スイッチをONにした場合、検索の意味重視度を3段階から選択できる(全プラン共通)。
| プリセット | semanticRatio | 説明 |
|---|---|---|
| 弱 | 0.3 | キーワード重視。意味が近い結果も少し含む |
| 中(デフォルト) | 0.5 | キーワードと意味のバランス |
| 強 | 0.8 | 意味重視。幅広い表現を検索結果に含む |
URLクエリパラメータ
| パラメータ | 必須 | デフォルト | 説明 | 例 |
|---|---|---|---|---|
semantic | No | false | セマンティック検索ON/OFF | semantic=true |
sr | No | 0.5 | semanticRatio(semantic=true時のみ有効) | sr=0.3 |
URLクエリパラメータ連動
- 検索実行時にクエリパラメータをURLに反映する(
pushStateによるリロードなしのURL更新) - URLを直接入力/共有リンクでアクセスした場合、パラメータからフォームの状態を復元する
検索結果ページ (/search)
URLクエリパラメータ設計
検索結果はURLのクエリパラメータで状態を保持する。これにより検索結果のブックマーク・共有・ブラウザバックが可能になる。
ベースURL:
/search?q=キーワード&channel=チャンネルID&target=title,description,subtitle&sort=relevance&page=1
パラメータ一覧:
| パラメータ | 必須 | デフォルト | 説明 | 例 |
|---|---|---|---|---|
q | Yes | — | 検索キーワード | q=ゲーム実況 |
channel | Yes | — | チャンネルID | channel=UC... |
target | No | title,description,subtitle | 検索対象(カンマ区切り) | target=title,subtitle |
sort | No | relevance | 並び順(relevance / newest) | sort=newest |
page | No | 1 | ページ番号 | page=3 |
semantic | No | false | セマンティック検索ON/OFF | semantic=true |
sr | No | 0.5 | semanticRatio(semantic=true時のみ有効) | sr=0.3 |
ルール:
qまたはchannelが空の場合は検索を実行しない(バリデーションエラー)targetはデフォルト値(全選択)の場合はURLから省略可能sort,pageもデフォルト値の場合は省略可能- フォーム操作時にURLを
pushStateで同期更新する(リロードなしでURL変更) - URLを直接入力/共有リンクでアクセスした場合、パラメータからフォーム の状態を復元する
URL例:
# 最小(キーワード + チャンネル)
/search?q=ゲーム実況&channel=UCxxxxx
# 字幕のみ検索、新着順、2ページ目
/search?q=ゲーム実況&channel=UCxxxxx&target=subtitle&sort=newest&page=2
# タイトルと説明文のみ
/search?q=ゲーム実況&channel=UCxxxxx&target=title,description
# セマンティック検索ON(デフォルト精度)
/search?q=ゲーム実況&channel=UCxxxxx&semantic=true
# セマンティック検索ON + 精度調整(Proプラン)
/search?q=ゲーム実況&channel=UCxxxxx&semantic=true&sr=0.8
チャンネル初回取得時のUX
指定されたチャンネルがMeilisearchに未登録の場合、字幕の初回取得を行う。取得フローの詳細は データ保存戦略 の「チャンネル字幕の初回取得フロー」を参照。
- 「このチャンネルの字幕を初めて取得しています。しばらくお待ちください。」メッセージを表示
- プログレスバー: 取得済み動画数 / 全動画数、推定残り時間を表示
- 「次回以降の検索は高速に行えます」の案内メッセージ
- 取得が進むにつれ、インデックス済み動画の検索結果が順次表示される
- 全動画取得完了時に「取得完了」のトースト通知を表示
差分取得時のUX
検索時に前回取得から24時間以上経過しており、新規動画が見つかった場合の表示。差分取得フローの詳細は データ保存戦略 の「差分取得(検索トリガー)での利用」を参照。
- 差分取得が発生した場合:
- 既存のインデックス済み動画で即座に検索結果を表示
- 画面上部に「新しい動画の字幕を取得中です(○件)」のバナーを表示
- 差分取得完了後、検索結果を自動更新し「新しい動画が追加されました」トースト通知
- 差分チェックで新規動画がない場合: 通常の検索結果を表示(バナーなし)
検索結果のグリッド表示
- デフォルト: 4列 × 5行 = 20件/ページ
- 列数変更可能: 3, 4, 5
- 行数変更可能: 5, 10, 20
検索結果の表示
各動画カードには以下を表示する:
- 常に表示(無料): 動画タイトル、サムネイル、ヒット箇所のタイムスタンプ
- クレジット消費後に表示: ヒット箇所の字幕テキスト(前後5秒)
- 検索ワードを Bold で強調表示する
クレジット消費フロー (ページ単位)
検索結果の字幕テキストは ページ単位 でクレジットを消費して閲覧する。
基本フロー
- ユーザーが検索を実行(無料)
- 検索結果ページを表示
- 動画タイトル・サムネイル・タイムスタンプ → 常に表示
- 字幕テキスト → ぼかし表示(デフォルト)
- 消費済み動画(1ヶ月以内)→ 通常表示(マーク付き、クレジット消費なし)
- ページ上部に消費確認メッセージを表示:
- 「○クレジットを消費して閲覧しますか?(残り: ○クレジット)」
- [OK] ボタン
- 横にトグルボタン:「今後確認しない」(デフォルト: 確認あり)
- [OK] を押す → ページ内の未消費動画のクレジットを一括消費 → 字幕テキストが表示される
確認スキップモード
- トグルを「今後確認しない」に切り 替えた場合:
- ページアクセス時に自動消費
- ぼかしなしで即表示
- トグルの設定はユーザー設定として保持される
消費済み動画の扱い
- クレジット消費から 1ヶ月以内 の動画は再消費不要
- 消費済み動画には マーク(アイコン) を動画カードに表示し、再消費不要であることを視覚的に示す
- マークにホバー/タップすると「この動画は○月○日まで追加クレジットなしで閲覧できます」というツールチップを表示
- ページのクレジット消費計算時、消費済み動画は除外される
- 例: 20件中5件が消費済み → 「15クレジットを消費して閲覧しますか?」
- 検索結果ページ上部にも「消費済みの動画(🔓マーク付き)は1ヶ月間追加クレジットなしで閲覧できます」の説明テキストを表示(初回のみ、閉じたら再表示しない)
user_video_accessテーブルでexpires_atが有効なレコードの存在を確認(動画単位で判定)
クレジット不足時のUX
- 残クレジットがページの必要数に足りない場合:
- 「クレジットが不足しています(必要: ○ / 残り: ○)」
- 残りのクレジットで見られる分だけ消費するオプション(関連度順で優先消費)
- アップグレード導線を表示
- クレジットが0の場合:
- 字幕テキストはすべてぼかし表示
- 「クレジットがありません」+ アップグレード導線
ページネーション
- ページネーション 方式で結果を表示する
- 結果の並び順: 関連度順(デフォルト)、新着順
検索結果0件
- 「該当する結果が見つかりませんでした」メッセージを表示
- 検索キーワードの修正を提案する
広告表示(Freeプランのみ)
- 検索結果リストの 一番上 と 一番下 に広告枠を配置する
- Plus以上のプランでは広告非表示
動画詳細ページ (/videos/:id)
- 検索結果の 動画タイトルをクリック すると遷移する
- レイアウト:
- 左側: YouTube動画の埋め込みプレーヤー
- 右側: 字幕テキストの一覧表示(タイムスタンプ付き、スクロール可能)
- 動画上部に タイトル・投稿日・再生回数・高評価数・チャンネル情報 を表示する
- 新しいタブで開くボタン を併設する
料金プランページ (/pricing)
- Free / Plus / Pro の3プラン比較表
- Pro は 3K / 6K / 10K の クレジット量を選択可能
- Stripe Checkout へのアップグレード導線
- 各プランの機能・制限の説明(クレジット数、広告有無、CSV一括ダウンロード対応等)
- クレジット制の仕組み説明(1分=1クレジット、検索は無料、閲覧はクレジット消費済み=表示/未消費=ぼかし)
プロフィールページ (/profile)
- お気に入りチャンネル管理: 一覧表示・追加・削除(無制限、アカウント登録が必要)
- プラン確認・変更: 現在のプラン表示、Stripeを使ったアップグレード/ダウングレード導線
- クレジット残高 の表示(今月の残りクレジット数 / 上限)
- アカウント設定: メールアドレス確認、アカウント削除
- プロフィールページの最下部に「アカウントを削除」ボタン(赤色、Destructive variant)
- クリック時に確認ダイアログを表示:
- 「アカウントを削除すると、以下のデータがすべて即座に削除されます。この操作は元に戻せません。」
- 削除されるデータの一覧: お気に入りチャンネル、クレジット残高、アクセス履歴
- 課金ユーザーの場合:「現在のサブスクリプション({プラン名})も即時解約されます」
- 「削除する」ボタン(赤色)と「キャンセル」ボタン
- 削除処理中はローディング状態を表示
- 削除完了後、トップページにリダイレクト + 「アカウントが削除されました」トースト通知
- 処理フローの詳細は データ保存戦略 の「アカウント削除フロー」を参照
お問い合わせページ (/contact)
- ログイン必須。未ログイン時は
/login?redirect=/contactにリダイレクトする - 名前・メールアドレスはログイン中のセッションから自動で埋め込み(表示のみで編集不可)
- ユーザーが入力するのは 件名 / カテゴリ(バグ報告・機能要望・質問・その他) / 本文 の 3 項目
- ボット対策は
authMiddleware(Google OAuth 必須)による認証で担保する。認証済みユーザーのみアクセス可能なため Turnstile は不要 - 送信先は
POST /api/contactで、バックエンドが Google Form のformResponseエンドポイントにURLSearchParamsで POST する- DB への保存や Slack 通知は行わず、受信ストレージは Google Form 側に集約する
- entry ID はコードに定数として保持し、
GOOGLE_FORM_URLのみ環境変数で管理する
- 送信成功時はフォームをリセットし、数秒間送信ボタンを disabled にして連打を防ぐ
認証・課金
- 検索はログイン不要で利用可能。動画字幕の閲覧(クレジット消費)には Google Auth ログイン が必要
- 登録すればお気に入り機能を利用可能
- 課金(Plus/Proプラン)には Google Auth ログイン が必要
- 課金管理は Stripe を使用する
クレジット制
- 検索の実行は 無料(クエリの送信にクレジットは不要)
- 字幕へのアクセスに 動画の長さ(分、切り上げ)= クレジット を ユーザー単位 で消費する
- クレジット消費済みの動画は検索結果に 通常表示、未消費の動画は ぼかし表示
- アクセス権の有効期間は 4週間(期限切れ後は再度クレジット消費が必要)
- クレジット不足時はアクセス可能な分のみ通常表示し、残りはぼかし表示(アップグレード導線を表示)
- ヘッダーに クレジット残高 を表示する
- クレジット残高が少なくなった場合: アップグレード誘導を表示
共通UI
ヘッダー
- 未ログイン時: 右上に ログインボタン を表示する
- ログイン時: 右上に ユーザーアイコン を表示する
- クリックするとドロップダウンメニューが表示され、プロフィール と ログアウト の導線を提供する
フッター
- プライバシーポリシー (
/privacy) へのリンク - 利用規約 (
/terms) へのリンク - 開発者のX(Twitter) へのリンク
- お問い合わせ (
/contact) へのリンク
レスポンシブ対応
- モバイル / タブレット / デスクトップ に対応する
- 動画詳細ページは、モバイルでは縦並び(動画→字幕テキスト)に切り替える
ローディング状態
- 検索中は スケルトンUI を表示する