---
title: LOVE_推し診断　技術解説
tags: 
author: [とくゆー](https://docswell.com/user/1305853)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/V7NYXXL6E8.jpg?width=480
description: LOVE_推し診断　技術解説 by とくゆー
published: July 21, 26
canonical: https://docswell.com/s/1305853/5E1Q48-equal_love_sindan
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/V7NYXXL6E8.jpg)

TECHNICAL BRIEF / READ-ALONG
⚠ 非公式・ファンメイド / Unofficial fan project — 公式とは一切関係ありません
=LOVE 推し診断
30問に答えると相性の良い =LOVE メンバーを提示するWebアプリの
仕様書 / アーキテクチャドキュメント
TL;DR
静的TSデータ + クライアント計算のみで完結する Next.js 16 (App Router) 製のミニアプリ。 サーバーDBなし、回答は完走時のみ localStorage 保
存、診断は純関数で表現。一致率は 70〜99% にクランプ。
FOR
READING TIME
STACK
SCOPE
フロントエンド /
Webアプリ開発者
約 10〜15 分
Next.js 16 / React 19
TypeScript / Tailwind 4
仕様 / データ設計 / 診断ロジック /
永続化 / 運用 / 設計意図
(13ページ / 図解込み)
前提
=LOVE (イコラブ) は10人組アイドルグループ。本資料内の members / questions はプロダクト内の静的TSデータを指し、データ内容の解説ではなく実装仕様の解説を目的とする。
DOC · =love-shindan · v1
01 / 13


# Page. 2

![Page Image](https://bcdn.docswell.com/page/YJ9P884R73.jpg)

=LOVE 推し診断 / Technical Brief
非公式・ファンメイド
02 / HOW TO READ &amp; TABLE OF CONTENTS
この資料の読み方 と 目次
READING GUIDE
TABLE OF CONTENTS
読み方
目次
🎯 目的
03
プロダクト概要
09
状態管理と永続化
04
提供価値とスコープ
10
アーキテクチャ全体図
05
画面構成とフロー
11
技術スタックと運用
📖 表記ルール
06
画面ごとの責務
12
設計意図とトレードオフ
OFF = トレードオフの明示。
07
ドメインモデルとデータ
13
FAQ と 参考情報
個々の質問文・メンバー名などのデータ中身は非掲載。あくまで実装の仕様・構
08
診断アルゴリズム ★
プロダクトの仕様と技術構成を、後から特定箇所を引ける形で残す。 説明の網羅
性より、1ページ1トピックの参照性を優先。
👤 読み手
Next.js / React / TypeScript の基礎を前提とする。 Tailwind、shadcn、Vercel、
localStorage API の存在は既知として扱う。
monospace = ソース中の識別子・キー名 / bold = 定義される用語 / TRADE-
🚫 扱わない範囲
造を対象とする。
=LOVE
推し診断 / 02 How to read
What / For whom / How long
In-scope / Out-of-scope
/
→ /diagnosis → /result
TOP /
診断 / 結果
4 entities + dataset stats
useState + localStorage
Data flow (client only)
Stack / Deploy / OGP / ENV
なぜDBなし / なぜクランプ / max-w-md
よくある疑問 / 拡張余地
集計 → ランキング → 一致率
★ = コア。時間が無ければ 08 を最優先で読む。
02 / 13


# Page. 3

![Page Image](https://bcdn.docswell.com/page/GJ8D88QGJD.jpg)

=LOVE 推し診断 / Technical Brief
非公式・ファンメイド
03 / PRODUCT OVERVIEW
プロダクト概要
DEFINITION
=LOVE 推し診断 とは、ユーザーが 30問4択のアンケート に答えると、事前に定義されたスコアリング関数を通じて 「相性の良いメンバ
ー」を 10人の中から1人 選出し、一致率 (70〜99%) と 上位3人のランキング を提示する クライアント計算完結型のWebアプリを指す (以降
本資料では単に「本アプリ」と表記)。
WHAT ·
何を提供するか
FOR ·
想定利用者と文脈
・30問4択のアンケート形式の診断UI
・一次想定: =LOVE のファン (グループ既知)
・1位メンバーの詳細 (プロフィール・SNS・センター曲・ソロ曲)
・流入経路: SNS (X) からのモバイル直リンク中心
・上位3人までの ranking と相対比 (ratio)
・利用状況: 電車内など片手・数分の隙間時間
・一致率帯 (3段階) に応じた診断コメント
・再訪動機: 診断のやり直し、結果のスクショ共有
TIME ·
想定所要時間
・診断本体: 約 1 分 (30問 × 数秒/問)
・初回ロード: 静的アセット + フォント読込 (Zen Maru Gothic 等)
・離脱リスクを下げるため、質問文と選択肢は短文で統一
NATURE ·
位置づけ
・個人開発のファンメイド作品であり、公式運営・所属事務所とは
一切関係を持たない
・公式サイト等へのリンクは掲載するが、公式コンテンツの二次配信は行わない
・広告・課金・アカウント機能は無い
=LOVE
推し診断 / 03 Product Overview
03 / 13


# Page. 4

![Page Image](https://bcdn.docswell.com/page/LJLMRRXXER.jpg)

=LOVE 推し診断 / Technical Brief
非公式・ファンメイド
04 / VALUE &amp; SCOPE
提供価値 と やらないこと
In-scope / Out-of-scope を明示することで、実装の判断基準を残す。
✓ In-scope · 提供する機能
×
診断フローの進行
サーバーサイドのDB / API
30問4択を最後まで進める。2問目以降は前の質問へ戻れる。
静的TSデータ + クライアント計算のみで完結。REST/GraphQLエンドポイントなし。
1位選出と上位3人のランキング
回答 / 結果のサーバー保存
10人からスコア降順で並べ、上位3人を ratio と共に表示。
送信もしない。ユーザー端末の localStorage 内で完結する。
一致率と診断コメント
ユーザー登録・ログイン・履歴
matchRate (70–99%) を算出し、帯 (3段階) に応じたコメントを付与。
認証機構を持たない。過去の診断履歴の閲覧・共有機能もなし。
メンバー情報の提示
公式コンテンツの二次配信
プロフィール・SNS・センター曲・ソロ曲を結果画面から辿れる。
楽曲・映像・画像等の配信は行わない。SNS等への外部リンクのみ。
再診断
運営・ファンクラブ機能の代替
結果画面から answers をクリアし、/diagnosis へ戻す。
=LOVE
Out-of-scope · 意図的に提供しない
推し診断 / 04 Scope
公式との差別化・置換ではなく、あくまでファンによる遊びとして位置づける。
04 / 13


# Page. 5

![Page Image](https://bcdn.docswell.com/page/47MYRRL37W.jpg)

=LOVE 推し診断 / Technical Brief
非公式・ファンメイド
05 / SCREEN FLOW
画面構成 と ユーザーフロー
ルートは 3 つ。永続化タイミングと再診断時の挙動を明示する。
/
/diagnosis
TOP
CTA click
/result
診断
save &amp;
push
結果
・サービス説明
・30問を1問ずつ表示 / 進捗バー
・localStorage から answers を読込
・診断スタート CTA
・4択選択で自動遷移 (2問目以降は戻る可)
・一致率・コメント・ランキング・メンバー情報
・公式チャンネルリンク
・完走時に answers を localStorage 保存
・「もう一度診断」で再開
(公式サイト / YouTube / X / Instagram)
再診断 = answers をクリアして /diagnosis へ
BEHAVIOR SPEC ·
挙動仕様
診断中のリロード
/result 直リンク
再診断ボタン
別デバイス / 別ブラウザ
=LOVE
推し診断 / 05 Screen Flow
useState 上の answers は消え、/diagnosis は最初 (Q1) から始まる。
意図: 中断状態を残さない
localStorage に answers があれば結果を復元表示。無ければエラー/リダイレクト扱い。
answers をクリア (removeItem) してから router.push(&#039;/diagnosis&#039;)。前の結果は都度消える。
localStorage はオリジン + デバイス単位。結果は他デバイスに引き継がれない。
05 / 13


# Page. 6

![Page Image](https://bcdn.docswell.com/page/P7R9RRKRE9.jpg)

=LOVE 推し診断 / Technical Brief
非公式・ファンメイド
06 / SCREEN SPEC
画面ごとの責務
各画面が保持する状態、外部との依存、遷移先を明示する。
TOP
/
推奨)
/diagnosis
結果
/result
Server Component (
Client Component (&quot;use client&quot;)
Client Component (&quot;use client&quot;)
RESPONSIBILITY
RESPONSIBILITY
RESPONSIBILITY
サービス説明とスタートCTAを提示。外部リンク集を表示。
30問4択の対話フローを進行させ、完走時に answers を保存す
answers を読み、calculateDiagnosisResult を呼んで結果を提
UI ELEMENTS
る。
示する。
・ヒーロー (サービス名・キャッチ)
UI ELEMENTS
UI ELEMENTS
・診断スタートボタン (→ /diagnosis)
・進捗バー (step / 30)
・1位メンバーカード + 一致率
・現在の質問文 + 4つの選択肢ボタン
・診断コメント (matchRate 帯で3分岐)
・戻るボタン (Q2 以降で活性)
・上位3人のランキング (member × ratio)
・完了時のトランジション → /result
・プロフィール / SNS / センター曲 / ソロ曲
・公式チャンネル 4リンク
・非公式である旨のディスクレーマ
STATE
なし (純粋な静的ページ)
DEPENDENCIES
外部URL文字列のみ (メンバーデータ非依存)
=LOVE
診断
推し診断 / 06 Screen Spec
STATE
answers: Answer[] と currentIndex: number を useState で保
・「もう一度診断」ボタン
STATE
持
マウント時に localStorage.getItem → useState に格納
DEPENDENCIES
DEPENDENCIES
questions (静的) / localStorage (書込)
members / questions / 純関数 calculateDiagnosisResult
06 / 13


# Page. 7

![Page Image](https://bcdn.docswell.com/page/PJXQRRLY7X.jpg)

=LOVE 推し診断 / Technical Brief
非公式・ファンメイド
07 / DOMAIN MODEL &amp; DATA
ドメインモデル と データ
4つの型定義でアプリ全体の情報を表現する。左3つは静的入力、右1つは派生結果。
STATIC ·
入力
STATIC ·
Question
テーマ色
30問。intent は編集時のガイド用メタ。
has 4
入力
topMember: Member
matchRate: number // 70-99
comment: string
ranking: {
member: Member,
score: number,
ratio: number // 0-1
populates }[]
totalScore: number
し、外部APIを呼ばずに詳細を出せる設計。
MEMBERS
10
人
10人組アイドルグループ全員
QUESTIONS
30
問
× 4 choices
10人分。結果画面で必要となる表示要素をすべて保持
Choice
answers から都度計算する派生値。永続化しないため、
= 120 選択肢
ロジック改善が即座に反映される。
text: string
scores: Record&lt;
memberId, number&gt;
typeHint: string // UI
非表示
scores はメンバーID → 加点。
通常は 3 / 2 / 1 のいずれか、他は 0。
推し診断 / 07 Domain
派生 (非保存)
DiagnosisResult
id: string
name: string
description: string
color: string //
songs: string[]
profile: { ... }
sns: { ... }
officialUrl: string
非表示
=LOVE
COMPUTED ·
Member
id: string
question: string
intent: string // UI
choices: Choice[4]
STATIC ·
入力
references
by id
SCORE WEIGHT
3/2/1
primary / secondary /
tertiary (
0)
他は
07 / 13


# Page. 8

![Page Image](https://bcdn.docswell.com/page/3JK9RRL4JD.jpg)

=LOVE 推し診断 / Technical Brief
08 / CORE
非公式・ファンメイド
★ DIAGNOSIS ALGORITHM
診断アルゴリズム
本アプリのコア。純関数 calculateDiagnosisResult(answers) 内で 3 ステップで処理する。
1
集計 · Aggregate
全回答について、選ばれた Choice.scores をメンバーごとに合
算する。他メンバーは 0 なので実質的に「選択肢が持つ 3/2/1 の
加点を1〜3人に配る」構造。
メンバー → 合計スコア
//
ID
const total = {} as Record&lt;string, number&gt;;
for (const a of answers) {
const choice = questions
.find(q =&gt; q.id === a.questionId)!
.choices[a.choiceIndex];
for (const [id, s] of
Object.entries(choice.scores)) {
total[id] = (total[id] ?? 0) + s;
}
}
weight: primary=3 / secondary=2 / tertiary=1 / other=0
⚠ 用語の区別
=LOVE
ランキング · Sort
2
スコア降順で並べ、比率は1位スコアを基準とした相対比 (ratio
∈ [0, 1]) を各エントリに持たせる。上位3人を ranking に格
3
一致率クランプ · Clamp
理論最大点 answeredCount × 3 に対する 1位の到達度を求め、線
形写像で 70〜99% の帯に変換する。
納。
const entries = Object.entries(total)
.map(([id, score]) =&gt; ({
member: byId[id], score,
}));
entries.sort((a, b) =&gt; b.score - a.score);
const top = entries[0].score;
const ranking = entries.map(e =&gt; ({
...e,
ratio: e.score / top, // 0.0-1.0
})).slice(0, 3);
注: ratio は「1位比」であって「一致率」ではない (次のステップと別物)
const raw = topScore /
(answeredCount * 3); // 0.0-1.0
const matchRate = clamp(
70 + Math.round(raw * 29),
70, 99
); // always 70..99
数値例
//
// raw=0.30
// raw=0.60
comment
→ 70 + round(8.7) = 79%
→ 70 + round(17.4) = 87%
は 3 段階の閾値で切替 (例: high / mid / low)
matchRate (一致率, 70–99%) = 理論最大点への到達度を写像した表示値 ／ ratio (ランキング比率, 0–1) = 1位を1.0とした相対比。別の量である。
推し診断 / 08 Algorithm (Core)
08 / 13


# Page. 9

![Page Image](https://bcdn.docswell.com/page/LE3W883DE5.jpg)

=LOVE 推し診断 / Technical Brief
非公式・ファンメイド
09 / STATE &amp; PERSISTENCE
状態管理 と 永続化
「診断中はメモリ」「完了時のみ localStorage」「結果画面で再計算」の 3 原則で構成する。
SEQUENCE ·
診断フローと永続化タイミング
RULES ·
3 原則
User × DiagnosisFlow × localStorage
User
DiagnosisFlow (useState)
localStorage
① Choice を選択 (× N 回)
状態の置き場所
💭 診断中 · メモリのみ
回答は useState でメモリ保持。
リロード / ブラウザバック / タブ閉じで消える。
② setAnswers([...prev, a])
// React state のみ更新
③ answers.length === 30
localStorage.setItem(KEY, JSON)
④ router.push(&#039;/result&#039;)
⑤ getItem(KEY) → answers を復元
⑥ calculateDiagnosisResult()
// 純関数 / 結果は保存しない
→ 中途半端な進捗を残さない
💾 完了時のみ保存
30問全部揃った瞬間だけ setItem。
キー: equal-love-diagnosis-answers
値: Answer[] の JSON.stringify
🔄 結果画面で再計算
DiagnosisResult は永続化しない。
calculateDiagnosisResult をマウント時に呼ぶ。
→ ロジック更新が即座に反映される
サーバー送信 · DB 保存 · Cookie 使用はすべて無し
=LOVE
推し診断 / 09 State &amp; Persistence
09 / 13


# Page. 10

![Page Image](https://bcdn.docswell.com/page/8EDKMM427G.jpg)

=LOVE 推し診断 / Technical Brief
非公式・ファンメイド
10 / ARCHITECTURE
アーキテクチャ全体図
すべてブラウザ内で完結。ネットワークが関わるのは初回の静的アセット取得のみ。
— BROWSER (Client) —
サーバー送信 · D B 書込みなし
STATIC
COMPONENT · CSR
BROWSER STORAGE
COMPONENT · CSR
PURE FUNCTION
members.ts
questions.ts
DiagnosisFlow
localStorage
ResultView
/diagnosis で 30問進行。
useState で answers 保持。
完走時のみ localStorage 書込。
key:
equal-lovediagnosis-answers
value: Answer[] JSON
/result で表示。
DiagnosisResult は保存しない。
再診断で answers を removeItem。
calculateDiagnosis
Result
TS でコード同梱。
内容変更はコード修正+デプロイ
経由。
型は import 時に静的解決。
import
save on
complete
members / questions
read on
mount
を参照
EXTERNAL SERVICES · Vercel Analytics / Speed Insights (
DATA FLOW ·
call
集計 → ランキング
→ 一致率クランプ。
副作用なし / テスト容易。
計測のみ、診断ロジックは通らない )
一行サマリ
members / questions (静的TS) → DiagnosisFlow (state) → localStorage → ResultView → calculateDiagnosisResult()
特徴: (1) すべてクライアント内で完結 / (2) 静的データと純関数の間に副作用がない / (3) localStorage は「答えの受け渡しバッファ」であり結果自体は毎回計算
=LOVE
推し診断 / 10 Architecture
10 / 13


# Page. 11

![Page Image](https://bcdn.docswell.com/page/V7PKRRMLJ8.jpg)

=LOVE 推し診断 / Technical Brief
非公式・ファンメイド
11 / STACK &amp; OPS
技術スタック と 運用
採用ライブラリ / デプロイ経路 / 環境変数 / メタデータの一覧。特殊な運用要件は無い。
STACK ·
採用技術
DEPLOY &amp; OPS ·
技術スタック
運用
デプロイ / 運用
Framework
Next.js 16 (App Router)
UI Runtime
React 19 / TypeScript
Styling
Tailwind CSS 4 / shadcn/ui / lucide-react
Font
Zen Maru Gothic (本文) + Mochiy Pop One (見出し)
Layout
モバイルファースト / max-w-md 中央寄せ
Pkg Manager
pnpm (workspaces は使用しない)
Data Layer
静的 TS モジュール members.ts / questions.ts
Rendering
TOP は Server Component / 診断・結果は Client Component
Observability
Vercel Analytics / Speed Insights
Not used
DB / REST API / GraphQL / 認証基盤 / 状態管理ライブラリ
RUNTIME NOTE
診断ロジックは Server Action / Route Handler / API Route を経由せず、クライアント側で完結する。
HOSTING
Vercel
Gitリポジトリと連携したプッシュベース自動デプロイ。プレビューデプロイでレビュー可能。データ
更新もコード修正→デプロイのフロー。
METADATA / OGP
OGP画像: /og-top.png (静的画像)
Twitter card: summary_large_image
タイトル / 説明は Next.js の metadata API で定義
ENVIRONMENT VARIABLE
NEXT_PUBLIC_SITE_URL
Next.js の metadataBase に使用。OGP等の絶対URL生成用。
未設定時は http://localhost をフォールバックとし、本番環境では必ず設定する。
計測範囲
MONITORING ·
Vercel Analytics: アクセス数 / ページ別PV
Vercel Speed Insights: LCP / CLS / INP 等の Web Vitals
※ 個人特定情報 (回答内容・診断結果) は計測対象外
SSR/SSG は主に SEO・OGP 対応の側面で活用する。
=LOVE
推し診断 / 11 Stack &amp; Ops
11 / 13


# Page. 12

![Page Image](https://bcdn.docswell.com/page/2JVVRR96JQ.jpg)

=LOVE 推し診断 / Technical Brief
非公式・ファンメイド
12 / DESIGN RATIONALE
設計上の意図 と トレードオフ
各判断について「判断」「理由」「捨てたもの」を対にして残す。将来の拡張時に判断の再検討がしやすくなる。
DECISION 1
DECISION 2
DECISION 3
DBを持たない
一致率を 70–99% にクランプ
モバイル前提 · max-w-md
WHY
WHY
(1) 非公式サービスとして個人情報を扱う責任を負わない
(2) 診断が純関数で表現でき、サーバー計算の必然性がない
(3) Vercel の静的配信だけで運用が回り、コスト・障害面が減る
(1) raw値は10人に加点が分散するため 0〜33%程度 に収まる
(2) そのまま出すと「相性 12%」など体験として苦しい数字が出る
(3) 上限 99% にすることで「絶対100%」を避け、余白を演出
WHY
TRADE-OFF ·
捨てたもの
診断履歴の集計、シェア画像のサーバー生成、A/Bの中央測定はできな
TRADE-OFF ·
捨てたもの
数値の厳密さ。「相性」表現とセットで運用し、誤読を減らす。
い。
DECISION 4
静的TSデータをコード同梱
TRADE-OFF ·
捨てたもの
PCでの余白活用。大画面では「そっけない」印象を許容する。
拡張しやすいポイント
現行構成の伸ばしどころ
members.ts / questions.ts をソースコードに同梱。 これにより 型安全・PRレビュー可能・Git履歴が
◆ 別グループ版: members / questions を差し替えるだけで転用可能。UIロジックは不変。
残る を同時に得る。 CMSやJSONファイル分離を敢えて選ばない構成。
◆ A/B: calculateDiagnosisResult が純関数なので、別実装を並走させて比較しやすい。
TRADE-OFF ·
=LOVE
FUTURE ·
(1) 想定ユーザーがほぼモバイル (SNS流入)
(2) 4択タップ・戻る・進捗バーは片手UIと相性が良い
(3) SNSシェア時のスクショが縦長で映える
捨てたもの
◆ シェア画像: Next.js の ImageResponse による OG 動的生成なら、現在のクライアント計算境界を壊さず追
質問1問追加でも「コード修正 → ビルド → デプロイ」が必要。
加できる。
非エンジニアが単独で更新することはできない (現状想定内)。
◆ i18n: 質問文・コメントに翻訳リソースを噛ませる余地はあるが、現状スコープ外。
推し診断 / 12 Design Rationale
12 / 13


# Page. 13

![Page Image](https://bcdn.docswell.com/page/5EGLMMZ2JL.jpg)

=LOVE 推し診断 / Technical Brief
非公式・ファンメイド
13 / FAQ &amp; REFERENCES
よくある疑問 と 参考情報
本資料を読み終えた後、実装者が疑問に思いやすい点をあらかじめ整理する。
FAQ ·
実装上ありがちな疑問
Q&amp;A
Q1. 一致率が絶対に 100% にならないのは仕様ですか？
はい、意図的です。clamp(70 + round(raw × 29), 70, 99) により表示値は必ず 70〜99 に収まります。「絶対100%」を避け、余白を残しつつ体験の質を保つ狙い。
Q2. 診断中にリロードしたら進捗は残りますか？
残りません。回答は useState 上のメモリに保持され、完走時のみ localStorage に書き込まれます。中途離脱・タブ閉じ・リロードでは進捗はゼロに戻ります (仕様)。
GLOSSARY ·
用語
用語対応表
answers
ユーザーの回答配列 (Answer[])。localStorage の唯一の保存対象。
matchRate
一致率 (70〜99)。raw を線形写像した表示値。
ratio
Q3. 別のデバイス / 別のブラウザで結果を共有できますか？
できません。localStorage はブラウザローカルで、サーバー同期は行いません。共有したい場合はスクリーンショットを利用してください。
Q4. 質問や選択肢を追加・修正するにはどうすればよいですか？
questions.ts を編集して PR → Vercel にデプロイ、で反映されます。CMS は使っておらず、コード修正+デプロイが唯一の更新経路です。
ランキング内相対比 (0〜1)。1位のスコアを1.0とする。
intent / typeHint
データ編集時のガイド用メタ。UI には表示しない。
REFERENCES ·
Q5. 回答データが外部に送信されている心配はありますか？
回答内容の送信は一切ありません。Vercel Analytics / Speed Insights はページ単位のアクセスと Web Vitals のみを計測しており、回答内容・診断結果は計測対象外です。
Q6. このプロジェクトは公式が関与していますか？
していません。 個人開発のファンメイド作品で、公式運営・所属事務所とは一切関係ありません。公式サイト等へのリンクは掲載しますが、公式コンテンツの二次配信は
行いません。
参考
・Next.js App Router: nextjs.org/docs/app
・Web Storage API (localStorage): developer.mozilla.org
・Vercel Analytics / Speed Insights: vercel.com/docs
・shadcn/ui: ui.shadcn.com
DOCUMENT INFO
version: v1
pages: 13
target audience: FE / Web
=LOVE
推し診断 / 13 FAQ &amp; References — END
アプリ開発者
13 / 13


