冷蔵庫管理・献立提案アプリ
冷蔵庫管理・献立提案アプリ MVP 設計仕様書
1. 概要
本アプリは、冷蔵庫内の在庫を管理し、食品ロスを減らし、日々の献立決定を簡単にすることを目的とする。
目的
冷蔵庫の中身を可視化する
賞味期限・消費期限切れによる廃棄を減らす
毎日の献立決定コストを下げる
想定ユーザー
忙しい中でも日常的に料理をする人
以下のような課題を持つ人
何が冷蔵庫に入っているか把握しづらい
賞味期限・消費期限の管理が面倒
今日何を作るか考えるのが負担
2. MVP のスコープ
含む機能
在庫の登録・更新・削除(CRUD)
賞味期限 / 消費期限の管理
期限が近い食品のアプリ内表示
レシピ提案(3件)
同じレシピが続けて提案されない制御
食品単位の集約表示とロット単位の詳細表示
含まない機能(将来対応)
健康状態に基づく推薦
OCR / バーコード読み取り
購入履歴との自動連携
Push 通知
買い物リスト
家族共有
3. コアユーザーフロー
1. ユーザーが食品を購入する
2. ユーザーが食品を登録する
3. 期限が自動入力される(必要なら編集可能)
4. ユーザーが在庫や期限を確認する
5. アプリがレシピを3件提案する
6. ユーザーがレシピを選択する
7. 使用する食材を確認する
8. 在庫が更新される
4. 画面構成
4.1 ホーム
表示内容:
期限が近い食品(当日 / 翌日)
在庫サマリ
おすすめレシピ 3 件
食品追加ボタン
表示優先度:
1. 期限リスク
2. 現在の在庫
3. レシピ提案
4.2 食品一覧
食品ごとに集約表示する
例:
卵 16個
展開表示(アコーディオン):
6個 / 期限: 3/20 / 自動設定
10個 / 期限: 3/28 / 手動設定
操作:
編集
削除
4.3 食品登録
入力項目:
食品名(検索 + オートコンプリート)
数量(整数)
購入日(初期値は当日)
期限日(自動入力または手動入力)
挙動:
期限は食品マスタから自動設定する
ユーザーは期限を上書きできる
以下を表示する
期限種別(賞味期限 / 消費期限)
期限の設定元(自動 / 手動)
4.4 レシピ画面
表示内容:
おすすめレシピ 3 件
各レシピの表示項目:
タイトル
提案理由(例: 期限が近い卵を使える)
レシピ選択時:
使用食材のチェックリストを表示
確定すると在庫を減算する
5. データモデル
5.1 food_masters
id
display_name
category
subcategory
recipe_match_key
parent_recipe_match_key
default_expiry_type
default_expiry_days
aliases
is_active
補足:
category は大分類(野菜、肉、魚など)
subcategory は詳細分類(鶏もも、豚バラ、鮭など)
recipe_match_key はレシピ照合用キー
parent_recipe_match_key は上位カテゴリ照合用キー
5.2 inventory_lots
id
food_master_id
quantity
purchased_at
expiry_date
expiry_type(best_before / use_by)
expiry_source(estimated / manual)
status(active / consumed / discarded)
created_at
updated_at
5.3 recipes
id
title
description
category
cooking_time_minutes
instructions
image_url
is_active
5.4 recipe_ingredients
id
recipe_id
recipe_match_key
is_required
5.5 recipe_recommendation_logs
id
recipe_id
recommended_on
6. レシピ一致判定
一致ルール
1. 完全一致を最優先とする
2. 上位カテゴリ一致も許可する
例:
chicken_thigh は chicken_thigh に一致する
chicken_thigh は chicken にも一致できる
7. レシピ提案ロジック
候補抽出条件
必須食材がすべて在庫内に存在する
調味料は判定対象から除外する
数量の厳密一致は見ない
active な在庫のみ対象とする
直近3日で提案済みのレシピは除外する
スコアリング
スコアの考え方:
期限が近い食材を使う: +20 × 件数
一致する在庫食材数: +10 × 件数
上位 3 件を表示する。
8. 期限管理ルール
内部では以下を区別して管理する
賞味期限(best-before)
消費期限(use-by)
UI 上はまとめて「期限」として表示する
初期デフォルト例
卵: 14日
牛乳: 7日
豆腐: 4日
鶏肉: 2日
豚肉: 3日
ほうれん草: 3日
もやし: 2日
※ 実際には食品ごとに default_expiry_type と default_expiry_days を持つ。
9. 在庫減算フロー
1. ユーザーがレシピを選択する
2. 使用食材のチェックリストを表示する
3. ユーザーが確認して確定する
4. 古いロットから優先して減算する
10. 技術スタック(MVP + 将来拡張)
フロントエンド
Next.js(App Router)
TypeScript
Tailwind CSS
TanStack Query(データ取得・キャッシュ)
React Hook Form(フォーム管理)
Zod(バリデーション)
バックエンド / データ
PostgreSQL
Supabase(MVPではDB・認証基盤として利用)
API
Next.js Route Handlers(HTTP API)
アーキテクチャ
フロントエンドとロジックの分離
ドメインロジックの独立
HTTP API ベース設計(Web / モバイル共通化を前提)
将来モバイル対応
Expo / React Native を想定
API はそのまま利用可能
型・ロジックの再利用を前提
11. 実装フェーズ
Phase 1
食品マスタ
在庫 CRUD
集約表示 + アコーディオン UI
Phase 2
期限自動設定ロジック
ホーム画面
Phase 3
レシピデータ
一致判定 + レシピ提案
Phase 4
在庫減算フロー
レシピ提案履歴管理
12. 将来拡張
不足食材の提案
「これを買えば作れる」提案
外部レシピ API 連携
Discord 通知
OCR / バーコード読み取り
栄養・健康ベース提案
13. 画面別詳細仕様
13.1 ホーム画面
目的
ユーザーが最初に見る画面として、期限リスク・在庫状況・今日の候補を短時間で把握できるようにする。
表示セクション
1. 期限が近い食品
2. 在庫サマリ
3. おすすめレシピ3件
4. 食品追加導線
期限が近い食品セクション
表示対象は active な在庫のみとする。
expiry_date が当日のものを最優先表示する。
expiry_date が翌日のものを次に表示する。
当日分は赤系、翌日分は黄系で表示する。
表示件数は最大5件程度とし、超える場合は「もっと見る」で食品一覧へ遷移させる。
在庫サマリ
現在の active 在庫数を表示する。
表示は食品種類数ベースとする。
例: 12 種類の食品があります
おすすめレシピセクション
推薦結果の上位3件を表示する。
各カードに以下を表示する。
レシピ名
簡単な提案理由
調理時間
提案理由の例:
期限が近い卵を使えます
今ある食材だけで作れます
状態
loading: スケルトン表示
empty:
在庫0件なら 食品を追加するとレシピ提案が表示されます と表示する
期限対象0件なら 期限が近い食品はありません と表示する
error: 再読み込み導線を表示する
13.2 食品一覧画面
目的
冷蔵庫内の食品を一覧で把握し、必要に応じてロット単位で編集・削除できるようにする。
一覧表示
表示単位は食品ごとの集約表示とする。
集約キーは food_master_id とする。
表示項目:
食品名
合計数量
最も近い期限
並び順
1. 期限当日
2. 期限翌日
3. 期限が近い順
4. 期限なし or 遠い順
アコーディオン詳細
各食品行をクリックすると、その場でロット一覧を展開する。
各ロットで表示する項目:
数量
購入日
期限日
期限種別
設定元(自動 / 手動)
編集ボタン
削除ボタン
状態
loading: 一覧スケルトン
empty: まだ食品が登録されていません
error: 食品一覧の取得に失敗しました
13.3 食品登録画面
目的
食品の追加を短時間で完了できるようにする。
入力項目
食品名
数量
購入日
期限日
食品名入力
文字入力時に food_masters を検索する。
前方一致 + 部分一致で候補表示する。
候補が存在しない場合は、将来拡張として food_masters 追加導線を置ける設計にするが、MVPでは未対応でもよい。
バリデーション
食品名: 必須
数量: 必須、1以上の整数
購入日: 必須
期限日: 任意
保存時の挙動
期限日が未入力の場合、food_masters.default_expiry_days をもとに自動設定する。
default_expiry_type を inventory_lots.expiry_type にコピーする。
自動設定した場合 expiry_source = estimated とする。
ユーザーが明示入力した場合 expiry_source = manual とする。
入力補助
購入日の初期値は当日とする。
数量入力は + - ボタン付きにしてもよい。
状態
saving: 保存中ボタン状態
success: 一覧またはホームへ戻す
error: フォーム下部にエラー表示
13.4 レシピ画面
目的
現在在庫から作れるレシピを提示し、そのまま在庫更新までつなげる。
一覧表示
表示件数は3件固定とする。
各レシピカードに表示する項目:
タイトル
提案理由
調理時間
詳細を見るボタン
レシピ選択後
レシピに紐づく recipe_ingredients をもとに、使用対象食材をチェックリスト表示する。
初期状態ではすべてチェック済みとする。
ユーザーが不要なものを外せるようにする。
確定時、チェック済み食材のみ在庫減算対象にする。
在庫減算ルール
同一食品の複数ロットがある場合、expiry_date が近いものから優先して減算する。
expiry_date が同じか未設定の場合は purchased_at が古いものを優先する。
数量が0になったロットは status = consumed に更新する。
状態
loading: レシピカードスケルトン
empty: 現在の在庫で作れるレシピがありません
error: レシピの取得に失敗しました
14. DB スキーマ詳細
14.1 food_masters
code:sql
create table food_masters (
id uuid primary key default gen_random_uuid(),
display_name text not null,
category text not null,
subcategory text,
recipe_match_key text not null,
parent_recipe_match_key text,
default_expiry_type text not null check (default_expiry_type in ('best_before', 'use_by')),
default_expiry_days integer not null check (default_expiry_days >= 0),
aliases jsonb not null default '[]'::jsonb,
is_active boolean not null default true,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index idx_food_masters_display_name on food_masters (display_name);
create index idx_food_masters_recipe_match_key on food_masters (recipe_match_key);
14.2 inventory_lots
code:sql
create table inventory_lots (
id uuid primary key default gen_random_uuid(),
food_master_id uuid not null references food_masters(id),
quantity integer not null check (quantity >= 0),
purchased_at date not null,
expiry_date date,
expiry_type text not null check (expiry_type in ('best_before', 'use_by')),
expiry_source text not null check (expiry_source in ('estimated', 'manual')),
status text not null check (status in ('active', 'consumed', 'discarded')) default 'active',
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index idx_inventory_lots_food_master_id on inventory_lots (food_master_id);
create index idx_inventory_lots_expiry_date on inventory_lots (expiry_date);
create index idx_inventory_lots_status on inventory_lots (status);
14.3 recipes
code:sql
create table recipes (
id uuid primary key default gen_random_uuid(),
title text not null,
description text,
category text,
cooking_time_minutes integer,
instructions text,
image_url text,
is_active boolean not null default true,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
14.4 recipe_ingredients
code:sql
create table recipe_ingredients (
id uuid primary key default gen_random_uuid(),
recipe_id uuid not null references recipes(id) on delete cascade,
recipe_match_key text not null,
is_required boolean not null default true,
created_at timestamptz not null default now()
);
create index idx_recipe_ingredients_recipe_id on recipe_ingredients (recipe_id);
create index idx_recipe_ingredients_recipe_match_key on recipe_ingredients (recipe_match_key);
14.5 recipe_recommendation_logs
code:sql
create table recipe_recommendation_logs (
id uuid primary key default gen_random_uuid(),
recipe_id uuid not null references recipes(id) on delete cascade,
recommended_on date not null,
created_at timestamptz not null default now()
);
create index idx_recipe_recommendation_logs_recipe_id on recipe_recommendation_logs (recipe_id);
create index idx_recipe_recommendation_logs_recommended_on on recipe_recommendation_logs (recommended_on);
15. API 仕様(MVP)
15.1 食品マスタ
GET /api/food-masters?query=
食品候補検索
GET /api/food-masters/:id
食品詳細取得
15.2 在庫
GET /api/inventory
在庫一覧取得(集約済み)
GET /api/inventory/lots
ロット一覧取得
POST /api/inventory
食品追加
PATCH /api/inventory/:lotId
ロット更新
DELETE /api/inventory/:lotId
ロット削除
POST /api/inventory/consume-from-recipe
レシピ実行に伴う在庫減算
15.3 レシピ
GET /api/recipes/recommended
おすすめレシピ3件取得
GET /api/recipes/:id
レシピ詳細取得
16. フロントエンド実装メモ
状態管理
一覧取得・作成・更新・削除は TanStack Query などの利用を想定する。
フォームは React Hook Form + Zod で扱うとよい。
推奨コンポーネント分割
HomeExpirationSection
HomeRecipeSection
InventoryList
InventoryAccordionItem
FoodForm
RecipeCard
RecipeConsumeDialog
型の方針
DB 由来の型と UI 用の型を分ける。
例:
FoodMasterRow
InventoryLotRow
InventorySummaryItem
RecommendedRecipe
17. 残課題
17.1 食品マスタ seed データ
初期 100〜150 件の食品マスタを定義する必要がある。
各食品に対して以下を決める必要がある。
category
subcategory
recipe_match_key
parent_recipe_match_key
default_expiry_type
default_expiry_days
17.2 レシピ seed データ
MVP 用に最低 30〜50 件程度のレシピが必要。
recipe_ingredients との整合が必要。
17.3 推薦ロジックの詳細化
同一レシピを 3 日除外する以外に、同カテゴリ料理の連続提案を避けるかを後続で検討する。
17.4 通知拡張
MVP では画面内表示のみ。
将来的に Web Push または Discord 通知へ拡張可能な設計にする。
18. アーキテクチャ方針
本アプリは、将来的なモバイル対応と拡張性を考慮し、以下の方針で設計する。
方針
UI とドメインロジックを明確に分離する
フロントエンドは API 経由でのみデータアクセスを行う
HTTP API を共通インターフェースとする
ドメインロジックは純粋関数として実装する
目的
Web とモバイルの共存を可能にする
ロジックの再利用性を高める
将来的な技術変更(DB / フロント)を容易にする
19. レイヤー構成
本アプリは以下の4層構造で実装する。
1. UI層
責務:
画面表示
ユーザー入力
API 呼び出し
例:
ホーム画面
食品一覧画面
食品登録画面
レシピ画面
2. API層
責務:
HTTP リクエストの受付
入力バリデーション
ドメインロジックの呼び出し
レスポンス生成
例:
/api/inventory
/api/recipes/recommended
3. ドメイン層
責務:
アプリのコアロジック
例:
期限判定
レシピ一致判定
スコアリング
在庫減算ロジック
特徴:
UI / DB / HTTP に依存しない
純粋関数として実装する
4. インフラ層
責務:
DBアクセス
外部サービス連携
例:
food_masters の取得
inventory_lots の CRUD
recipes の取得
20. ディレクトリ構成案
初期は単一アプリ構成とし、将来的に monorepo へ移行可能な形にする。
code:_
src/
app/
components/
features/
domain/
server/
lib/
各ディレクトリの役割
app/
Next.js のルーティングと API
components/
汎用 UI コンポーネント
features/
機能単位の UI + hooks
domain/
ビジネスロジック(純粋関数)
server/
DB / repository
lib/
API client / 共通処理
21. API 設計方針
基本方針
すべてのデータアクセスは HTTP API 経由で行う
フロントエンドから DB を直接操作しない
理由
モバイルアプリからも同一 API を利用可能
責務分離が明確になる
セキュリティ制御を API 層に集約できる
実装
Next.js Route Handlers を使用する
22. バックエンド選定方針
MVP
Supabase(無料枠)を利用
理由:
立ち上がりが速い
PostgreSQL がそのまま使える
管理が容易
注意点
UI から Supabase を直接呼ばない
repository 層を介してアクセスする
将来
必要に応じて以下へ移行可能
自前 PostgreSQL(Neon / Railway 等)
専用バックエンド
23. 将来モバイル対応方針
想定
Expo / React Native
方針
Web と Mobile は UI のみ分離する
API は完全に共通化する
再利用対象
型定義
バリデーション
ドメインロジック
API schema
非共有部分
UI コンポーネント
画面構成
24. 設計原則
原則1
UI は DB を直接操作しない
原則2
API はロジックを持ちすぎない
原則3
ドメインロジックは純粋関数として実装する
原則4
DBアクセスは repository 層に閉じる
原則5
HTTP API を唯一のインターフェースとする