はじめに — 「依頼が来たのに、どこに書いたか分からない」
フリーランスとして働いていると、依頼の管理が煩雑になる瞬間があります。LINEで来た案件、メールで来た見積もり依頼、口頭で頼まれた修正作業。それぞれ別の場所に記録していて、気づけば「あの依頼、どこにメモしたっけ?」と探し回る。同じ経験をしたことがある人も多いのではないでしょうか。
Wakulier はこの課題を解決するために生まれた、フリーランス向けの依頼管理ツールです。クライアントからの依頼を一元管理し、スケジュール調整から請求までをシンプルに完結させます。
この記事では、2025年12月27日から2026年1月8日にかけてWakulierを企画・開発した約2週間の舞台裏を、開発日記から振り返ります。RLS first の設計判断、タイムゾーン地獄、カレンダーUI自作、LINE内ブラウザとの戦い。順調だった部分と苦労した部分の両方を、できるだけ具体的に記録します。
2日間でMVPを構築した話
Day 1: フルスタックの骨格
2025年12月27日、Wakulier の開発をスタートしました。技術スタックは CancelNavi や BandBridge で実績のある Next.js 16 + Supabase の組み合わせです。
MVP構築開始。Next.js 16 + React 19 + Supabase で、クライアント管理・依頼フォーム・管理ダッシュボード・調整エディタ・履歴ページ・予約スケジューリング・Google Calendar連携を一気にClaude Codeで実装
PMとして最初に決めたのは「RLS first」の設計方針でした。
RLS first — セキュリティをデータベース層で担保する
Supabase の Row Level Security(RLS)は、データベースレベルでアクセス制御を行う仕組みです。BandBridge の開発で学んだ「セキュリティはアプリケーション層だけに任せると漏れが出る」という教訓を、Wakulierでは最初から適用しました。
ALTER TABLE ... ENABLE ROW LEVEL SECURITY を設定し、ロールごとの POLICY を定義します。たとえば依頼テーブルなら「依頼者は自分が作成した依頼のみ閲覧可能」「管理者は自組織の全依頼を閲覧可能」という POLICY を書きます。adminClient(Service Role Key)を使ってRLSをバイパスする設計にすれば、Server Components で安全に管理者データを取得できます。この方針でClaude Codeに実装を依頼し、初日でクライアント管理・依頼フォーム・管理ダッシュボードの骨格が完成しました。
Day 2: デザインとパフォーマンス
12月28日はUIの磨き込みに注力しました。Material Design 3を参考にしたGoogle Blue基調のデザイン、大きめのコーナーラディウス、ピル型ボタン。
Material Design 3 インスピレーションのUI。Google Blue、large corner radius、pill buttons。React.memo でリスト表示のパフォーマンスを劇的に改善
リスト表示のパフォーマンス問題もこの日に解決しました。依頼一覧が増えると再レンダリングで遅くなっていたのを、React.memo で個別カードのメモ化を入れることで解消。2日間でMVPの基盤が固まりました。
カレンダーUIを自作した判断
12月29日、予約スケジューリング機能のために週間カレンダーUIが必要になりました。
週間スケジュールピッカーを自作。FullCalendar や react-big-calendar は機能過多と判断。Flexboxベースの30分スロットグリッドをClaude Codeで構築
FullCalendar や react-big-calendar といったライブラリも検討しましたが、Wakulier に必要なのは「1週間の空き時間を30分刻みで選択する」というシンプルな機能だけでした。多機能なライブラリを入れるとバンドルサイズが増え、UIのカスタマイズにも制約が生まれます。
onClick で選択状態を管理する実装の方がシンプルです。結果として、Flexbox ベースの30分スロットグリッドは約200行のコンポーネントに収まり、バンドルサイズの増加はほぼゼロでした。「必要十分な機能だけを実装する」という判断が正しかったと感じています。
タイムゾーン地獄 — 3回ハマった話
Wakulier の開発で最も時間を費やしたのが、タイムゾーン問題でした。開発日記を振り返ると、12月30日・31日・1月5日と、少なくとも3回はタイムゾーンに関するバグに遭遇しています。
第1の罠: Google Calendar APIとUTC/JST変換
12月30日、Google Calendar連携で最初のタイムゾーン問題が発生しました。
タイムゾーン問題発生。Google Calendar API との UTC/JST 変換が間違ったレイヤーで行われていた。解決策: 入力時にJSTで正規化、DBは timestamptz で保存、表示時に toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' })
問題の原因は、UTC から JST への変換がアプリケーションの複数レイヤーで重複して行われていたことでした。入力→保存→取得→表示という流れのうち、入力時と表示時の両方で+9時間してしまい、結果として18時間ズレるという状況です。
第2の罠: 納期の二重変換
12月31日には、納期の日時表示がさらに8時間ズレるバグが見つかりました。
納期のタイムゾーン二重変換バグ。8時間のオフセットが発生していた
第3の罠: datetime-local の落とし穴
1月5日、HTML の <input type="datetime-local"> で3度目のタイムゾーン問題に遭遇しました。
3回目のタイムゾーン問題。datetime-local の value に手動で +09:00 を付与しないと二重変換される
datetime-local はブラウザのローカルタイムゾーンの日時文字列を返します。これを new Date() に渡すとUTCとして解釈される環境があり、明示的にタイムゾーンオフセット +09:00 を付与する必要がありました。
3回の試行錯誤を経て確立したルールは以下の3つです。
- 入力時にJSTで正規化する(
+09:00を明示的に付与) - データベースは
timestamptzで保存する(PostgreSQL がタイムゾーン情報を保持) - 表示時は
toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' })を使う
依頼ワークフローの設計転換
12月30日、依頼管理のワークフローを根本から見直す判断をしました。
「予約時間を指定するモデル」から「納期ベースのモデル」に転換。ユーザー中心の設計。1つの依頼に複数の作業予定を紐づけられるように拡張
当初は「クライアントが特定の日時を予約する」モデルで設計していました。美容室の予約のように、クライアントが日時を選んで予約し、フリーランスがその時間に作業するイメージです。
しかしフリーランスの実務を考えると、この設計には無理がありました。Web制作の依頼は「2月10日までにデザインカンプを出してください」という形で来ることが多く、フリーランスはその納期に向けて自分のスケジュールの空いている時間で作業を分割します。1つの依頼に対して3日に分けて作業するケースもあれば、集中して1日で終わらせるケースもある。「特定の日時に予約する」モデルでは、この柔軟性を表現できません。
PMとして「予約」から「納期」への設計転換を決断し、Claude Codeでデータモデルを修正しました。具体的には、依頼テーブルに deadline フィールドを追加し、1つの依頼に複数の作業予定(work_schedules)を紐づけられるリレーションに拡張しました。結果的にこの判断は正しく、ユーザーにとってより直感的なワークフローになりました。
LINE内ブラウザとの戦い
1月6日、LINE内ブラウザで Google OAuth が動作しない問題に直面しました。
LINE内ブラウザで OAuth 認証が動かない。User-Agent を見て LINE/Facebook/Instagram の WebView を検出し、外部ブラウザでの起動を案内する警告を表示
LINEでシェアされたリンクをタップすると、LINE内蔵のWebView(アプリ内ブラウザ)で開かれます。このWebViewはサードパーティCookieが制限されており、Google OAuthのリダイレクトフローが正常に完了しません。
対策として、User-Agent に含まれる Line、FBAN(Facebook)、Instagram といったキーワードを検出し、「Safari や Chrome で開き直してください」という案内モーダルをClaude Codeで実装しました。
この問題は CancelNavi でも同時期に発覚しており、2つのプロダクトで同じ対策を横展開することになりました。個人開発で複数プロダクトを運営していると、1つのプロダクトで得た知見がすぐに他に活かせる利点があります。
デュアルロール設計 — 同じ人が依頼者にも受注者にもなる
1月6日にはもう一つ、Wakulier 特有の設計課題がありました。
デュアルロールのデータ分離。同じユーザーが provider(受注者)と client(依頼者)の両方のロールを持てる設計
フリーランスの世界では、ある案件では受注者として仕事を受け、別の案件では他のフリーランスに仕事を依頼する、ということがあります。たとえば、Webデザイナーがクライアントから案件を受注し、コーディング部分だけ別のフリーランスに依頼するケースです。1人のユーザーが「受注者」と「依頼者」の両方のロールを持てるようにデータモデルを設計する必要がありました。
同じユーザーでもロールによって見えるデータが異なります。受注者としてログインしているときは自分に来た依頼だけ、依頼者としてログインしているときは自分が出した依頼だけが見えるべきです。RLSの POLICY をロールごとに分ける設計にしました。auth.uid() と依頼の provider_id / client_id を照合し、アクセス可能なレコードをデータベース層で制御します。RLS first の方針がここで威力を発揮し、データ分離はアプリケーションコードを書くことなくデータベース層で完結しています。
セキュリティ監査で22件の問題
1月7日、Wakulier のフルコードレビューを Claude Code に依頼しました。
セキュリティ監査で22件の問題を発見・修正。未使用の /api/settings エンドポイントを削除。beforeinstallprompt の早期キャプチャパターンを実装
CancelNavi で50件以上、BandBridge でも多数のセキュリティ問題を経験していたため、Wakulier では開発初期にセキュリティ監査を実施しました。22件の問題が見つかりましたが、RLS first の設計方針のおかげで「認証チェック漏れ」系の問題は少なく、主にクライアントサイドの入力バリデーションや未使用エンドポイントの削除が中心でした。
コードレビューで35件のバグ
1月8日にはClaude Codeによる包括的なコードレビューを実施し、35件のバグが見つかりました。
コードレビューで35件のバグを発見。Critical/High を優先修正。アクティビティカードのタップ時既読問題を修正
35件中、Critical と High の修正を優先的に対応しました。最も影響が大きかったのはアクティビティカードの既読処理で、タップしても既読にならないケースがあり、通知バッジが減らない問題が発生していました。
まとめ — Wakulier 開発で学んだこと
約2週間の開発を通じて得た学びをまとめます。
- RLS first は初期コストに見合う: セキュリティ監査での問題数が CancelNavi(50件以上)に比べて大幅に減った。データベース層でアクセス制御を完結させる方針は、アプリケーションが複雑になるほど効果を発揮する
- タイムゾーンは「入力時正規化 + timestamptz + 表示時変換」で統一する: 3回ハマって確立したルール。中間レイヤーでの変換を排除することが重要
- カレンダーUIの自作は「必要十分」で判断する: 高機能なライブラリが必要かどうかは、ユースケースに立ち返って判断する。200行で済む自作が最適なケースもある
- LINE内ブラウザのOAuth問題は複数プロダクトで横展開する: 個人開発で複数プロダクトを運営する利点は、1つの知見がすべてに活かせること
- セキュリティ監査とコードレビューは別物: セキュリティ問題と機能バグは発見の観点が異なる。両方を早い段階で実施することで、本番環境での障害リスクを下げられる
振り返ってみると、約2週間で最も時間を費やしたのはタイムゾーン問題でした。3回ハマるまで「入力時正規化 + timestamptz + 表示時変換」というルールに到達できなかったのは、問題の本質を最初から見抜けなかったからです。逆に、RLS first の設計判断は初日に決めたことが最後まで正しかったケースで、CancelNavi での経験が活きました。失敗から学び、その学びを次のプロダクトに持ち込む。この繰り返しが、個人開発の速度を上げていく唯一の方法だと感じています。
Wakulier は ZERONOVA Focus ブランドの主力プロダクトとして、フリーランスの「依頼管理の面倒さ」を解消するために進化を続けています。RLS first の設計思想は、その後の全プロダクトの基盤になりました。
関連記事:
- 1日でMVPを作る — Supabase フル活用の高速開発術 — MVP構築の詳細
- Supabase RLS の設計パターン — デフォルト拒否で安全に — RLS設計の深掘り
- カレンダー UI を自作した話 — カレンダー自作の技術的詳細
- LINE 内ブラウザで OAuth 認証が動かない問題への対処 — LINE WebViewの問題と解決策
Zeronova(ゼロノバ)
Product Manager / AI-Native Builder
Web/IT業界19年以上・20以上のWebサービスを担当したPdM。東証プライム上場企業の子会社代表として事業経営を経験。現在はAIを駆使して企画から実装まで完結させる個人開発を実践中。