家計管理 Web アプリ

限定公開2024年から稼働

Next.js / Supabase / Cloudflare Workers / TanStack Query

家族が日々の支出を記録し、資産まで含めて管理するための Web アプリです。 公開はしていませんが、2024年12月から今日まで、家族が実際に使い続けています。

家計管理アプリのホーム画面。月を切り替える見出しの下に今月の収支と貯蓄率、収入と支出の合計、その下に食費・その他・趣味・電気代・交通費が金額と割合の帯で並んでいる。

なぜ既存のアプリを使わなかったか

口座やカードと連携させたくなかったからです。

多くの家計簿アプリは、金融機関と連携して明細を自動で取り込むことを主要な機能にしています。 便利であることは分かります。ただしそれは、 金融機関の認証情報を第三者のサービスに預けるということでもあります。 自分の判断として、そこは預けたくありませんでした。

もう一つは、多機能すぎたことです。 使わない機能のために、複雑さと預ける情報を引き受けることになります。

それでも収支の管理はしたい。 なら、必要な機能だけの家計簿を作ればいいと考えました。

主な機能

  • 支出・収入の登録と編集
  • 取引の一覧と詳細
  • カテゴリ管理(並べ替えに対応)
  • サブスクリプションの管理
  • 資産の管理
  • レポート(月次 / 年次 / カレンダー表示)
  • ダークモード
  • 家族以外はアクセスできない権限制御

技術スタック

  • フレームワーク — Next.js 16(App Router)/ React 19 / TypeScript
  • スタイリング — Tailwind CSS v4 / Radix UI(shadcn/ui 構成)
  • 状態管理 — TanStack Query(サーバー状態)/ Zustand(クライアント状態)
  • フォーム — React Hook Form + Zod
  • バックエンド — Supabase(認証・PostgreSQL)
  • インフラ — Cloudflare Workers(OpenNext でデプロイ)
  • グラフ — Recharts
  • UI 補助 — dnd-kit(並べ替え)/ next-themes / sonner

Firebase から Supabase へ、全面的に作り直した

初期バージョンは Firebase で作っていました。 ところが集計や絞り込みを増やすたびに、データの持ち方を作り直す必要が出てきました。

家計は「カテゴリ・口座・期間をまたいで集計する」用途です。 これはリレーショナルデータベースのほうが合うと判断し、 作り直しにあわせてスキーマを設計し直しました。

移行スクリプトは TypeScript で書き、firebase-admin で読み出して Supabase へ投入しています。 家族が日常的に使っているため、記録を1件も失わないことが前提条件でした。

作り直しの判断は、機能が足りなくなったからではありません。 「この先も機能を足し続けられるか」で決めました。

サーバーの状態と、画面の状態を分けた

TanStack Query でサーバーから取得するデータを、 Zustand で画面上の一時的な状態を管理し、責務を分けています。

どちらか一方に寄せると、キャッシュの整合性か記述量のどちらかが破綻します。 両方を1つの仕組みで扱おうとしないほうが、結果的に短く済みました。

フォームは Zod でスキーマを定義し、 バリデーションと型定義の両方で同じ定義を使い回しています。 検査の規則と型がずれる余地を作らないためです。

入力の速さを最優先にした

毎日使うものなので、記録が面倒だと続きません。

入力専用の画面を独立させ、開いてから登録し終わるまでの操作数を最小にしました。 機能を足すときも、この画面の手数を増やさないことを条件にしています。

見せ方を3通り用意した

レポートは月次・年次・カレンダーの3つに分けています。

数字の一覧だけでは、家計の状態がつかめませんでした。 「いつ、何に使ったか」がカレンダーの上で見えるようにしたことで、 家族のあいだで振り返りの会話が生まれるようになりました。

作った側として気に入っているのは、この画面と資産管理です。

使う人から出てきた要望

自分ひとりで使うつもりで作った初期バージョンには、無かったものです。

  • クレジットカードで払ったかどうかを記録したい
  • 自由に書けるメモ欄がほしい
  • カテゴリを人ごとに変えたい(同じ支出でも、見たい分類の単位が違う)

いずれも「無くても動く」機能です。しかし、無いと記録が続きませんでした。

作る前に要望を全部聞き出すことはできませんでした。 使ってもらってから出てくる、という前提で設計するようになっています。

この記事を共有