メインコンテンツまでスキップ

機能仕様書

ページ一覧

パスページ名認証
/トップページ不要
/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/contactauthMiddleware(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クエリパラメータ

パラメータ必須デフォルト説明
semanticNofalseセマンティック検索ON/OFFsemantic=true
srNo0.5semanticRatio(semantic=true時のみ有効)sr=0.3

URLクエリパラメータ連動

  • 検索実行時にクエリパラメータをURLに反映する(pushState によるリロードなしのURL更新)
  • URLを直接入力/共有リンクでアクセスした場合、パラメータからフォームの状態を復元する

URLクエリパラメータ設計

検索結果はURLのクエリパラメータで状態を保持する。これにより検索結果のブックマーク・共有・ブラウザバックが可能になる。

ベースURL:

/search?q=キーワード&channel=チャンネルID&target=title,description,subtitle&sort=relevance&page=1

パラメータ一覧:

パラメータ必須デフォルト説明
qYes検索キーワードq=ゲーム実況
channelYesチャンネルIDchannel=UC...
targetNotitle,description,subtitle検索対象(カンマ区切り)target=title,subtitle
sortNorelevance並び順(relevance / newestsort=newest
pageNo1ページ番号page=3
semanticNofalseセマンティック検索ON/OFFsemantic=true
srNo0.5semanticRatio(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. ユーザーが検索を実行(無料)
  2. 検索結果ページを表示
    • 動画タイトル・サムネイル・タイムスタンプ → 常に表示
    • 字幕テキスト → ぼかし表示(デフォルト)
    • 消費済み動画(1ヶ月以内)→ 通常表示(マーク付き、クレジット消費なし)
  3. ページ上部に消費確認メッセージを表示:
    • 「○クレジットを消費して閲覧しますか?(残り: ○クレジット)」
    • [OK] ボタン
    • 横にトグルボタン:「今後確認しない」(デフォルト: 確認あり
  4. [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 を表示する

エラーハンドリング

  • API失敗時・ネットワークエラー時にエラーメッセージを表示する
  • リトライボタンを提供する

通知・トースト

  • 操作成功/失敗時に トースト通知 でフィードバックを表示する(お気に入り追加、プラン変更等)

404ページ

  • 存在しないページへのアクセス時に404ページを表示する
  • トップページへの導線を提供する

SEO / OGP

  • 各ページに適切な メタタグ(title, description)を設定する
  • OGP画像 を設定し、SNSシェア時にプレビューを表示する

テーマ

  • Dark / Light モード 対応

国際化(i18n)

  • 対応言語(計7言語):
    • 日本語
    • 英語
    • 中国語
    • 韓国語
    • スペイン語
    • フランス語
    • ドイツ語

将来機能

ライブ配信コメント検索

  • YouTubeライブ配信の チャットコメントを検索可能 にする
  • 参考: https://comment2434.com/comment/
  • キーワード検索、チャンネル絞り込み、期間指定
  • クレジット消費あり(詳細は実装時に決定)

字幕CSV一括ダウンロード

  • 取得済み字幕を CSV形式で一括ダウンロード
  • 参考: https://www.youtube-transcript.io/
  • Plus以上 の機能(Freeでは不可)
  • クレジット消費あり(詳細は実装時に決定)

直接広告販売(ダイレクト広告)

AdSense等の広告ネットワークを介さず、広告主から直接広告を募集して掲載する収益モデル。中間マージンなしで高い収益率を実現する。PVが十分に増えた段階で導入する。

フェーズ1: 申込フォーム(日本国内限定)

  • サイト内に 専用の広告掲載申込フォーム を設置
  • 対象は 日本国内の広告主 に限定
  • 運営側で広告の審査・掲載を行う

フェーズ2: セルフサービス管理画面(グローバル対応)

  • リクエストが世界的に増えてきた段階で、広告主用の管理画面 を構築
  • 広告主自身が 広告画像・リンク・ターゲット国 を設定できる

ターゲティング

  • 国単位(IPベース) でユーザーの所在国を判定し、その国向けの広告を表示
  • 広告主は表示対象の国を 1つ以上指定

課金モデル

  • 月額固定 — 特定の枠に広告を掲載(PV規模に応じて価格設定)