【個人開発】契約中のサブスクリプションを管理できるサービスをリリースしました【Next.js×Neon×Clerk×Netlify】

## はじめに

以前からの不満を解消し、ポートフォリオを充実させるため、正式にユーザー向けにリリースするアプリを個人開発しました。この記事では、サービスの紹介と開発を通じて得られた知見を共有させていただきます。

https://substrack.jp/

## サービス概要

タイトルにある通り、「契約中のサブスクリプションを管理するサービス」です。主に以下のような機能を提供しています。

![サービス概要](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456603/ecf1ad3d-ae92-4b3c-adc2-cc7a31dbb771.png)

## サービス作成の目的

私自身、いくつかのサブスクリプションを契約しており、読者の方々も同様の状況である方が多いと思います。しかし、個人向けでPCから契約中のサブスクリプションを把握できるサービスはほとんどなく、これまではNotionにメモすることで管理していました。

その管理方法には以下のような課題がありました：

- ドルやユーロで支払っているサブスクリプションを日本円に換算する手間
- 次回更新日の手動更新の煩雑さ
- 契約日を忘れてしまい、不要な契約更新をしてしまうリスク

これらの課題を解決するため、React学習の成果発表も兼ねて、本サービスを開発しました。

## 製作者の経歴

Reactの学習を始めて半年程度の初心者です。

それまでReactに限らず新しい技術への関心はありましたが、アウトプットとなると手が動かず、自分を動かす理由を作るために半年ほど前にJISOU（プログラミングスクール）に入って学習を始めました。そして、今回React学習の集大成として本サービスを開発しています。

プログラミング自体も、2年前に社会人になってから本格的に触れ始めたばかりです。

https://lapras.com/public/PHZLS5N

## 機能一覧

### サブスクリプションの管理

#### 登録

以下のページから登録できます。

![サブスクリプション登録画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456603/e6360227-2903-4abb-a8eb-2847fa0b3dd0.png)

通貨・請求サイクルは、現在は以下のものから選択できます。

- 通貨
    - 日本円
    - 人民元
    - 英ポンド
    - 米ドル
    - ユーロ
    - ビットコイン
- 請求サイクル
  - 月毎
  - 年毎

![ドロップダウン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456603/2c69b827-a9a4-4f6b-8373-887db163f1ab.png)

#### 閲覧

登録したサブスクリプションの、金額（日本円換算）や、次回更新日などを一覧で確認できます。

![ダッシュボード画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456603/27d8abde-1c66-4f6c-9ed2-490da4d59d2d.png)

また、名前を押下することで、詳細な情報を確認できます。

![名前押下](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456603/b10d2bbb-9c08-423d-b85b-4887681be7f8.png)

![サブスクリプション詳細画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456603/73235890-4d5b-440f-8f75-726db911d180.png)

#### 登録内容の削除

以下のアイコンなどから削除できます。

![削除アイコン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456603/ba8ab3ec-8808-463f-a07a-70110e416f6b.png)

### 更新日の通知

毎朝、翌日更新予定のサブスクリプションがある場合、更新通知メールが登録されたメールアドレスに送信されます。

![通知メール](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456603/ed932f3c-9efe-4f82-b216-c9a8e9286cac.png)

通知先のメールアドレスは、設定画面から変更できます。

![設定ページ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456603/f60833f7-1430-4ed2-a64f-dd6a53fdbf27.png)

### 解約方法の投稿・紐付け

以下のページから解約方法を投稿できます。

![解約方法投稿ページ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456603/0ff57330-ecdd-46bf-8b0d-4d607168c80d.png)

また、既に登録済みのサブスクリプションと結びつけることもできます。

![結びつけ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456603/af90f810-9945-44ef-916b-1a3ad63a38c4.png)

その他、解約方法の一覧ページからも結びつけることもできます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3456603/c7f62f0f-23c2-42fc-8cdc-fb71ff050c67.png)

## 技術スタック

| カテゴリー | 技術 |
|------------|------|
| **フロントエンド** | Next.js |
| | React |
| | Tailwind CSS |
| | shadcn/ui |
| | Jotai |
| | Tanstack Form |
| **バックエンド** | Drizzle ORM |
| | Bun   |
| | Clerk |
| **インフラ** | Netlify |
| | Neon |
| **開発ツール** | TypeScript |
| | Biome |
| **その他** | ArkType |
| | Nodemailer |
| | React Markdown |
| | Date-fns |

パフォーマンスや開発生産性のため、基本的に最新バージョンや新しいライブラリを使用しています。

ホスティング先は無料/商用利用可能なものの中で探し、Netlifyを選択しました。

|サービス|採用可否理由|
|-|-|
|Vercel|商用利用不可|
|CloudFlare Pages|Next.jsをデプロイするのが非推奨の方向になっている|
|CloudFlare Workers|ファイルサイズ制限が厳しい|
|Netlify|サービスが小規模なうちは問題がなさそう|

課金する場合はまた話が変わってくるので、サービスが大きくなって無料プランが厳しくなったら、CloudFlare Workersあたりに移行するつもりです。

### テーブル構成

```mermaid
erDiagram
    users ||--o{ subscriptions : ""
    users ||--o{ cancellation_methods : ""
    users ||--o{ cancellation_method_bookmarks : ""
    users ||--o{ cancellation_method_goods : ""
    subscriptions }o--o| cancellation_methods : ""
    subscriptions }o--|| currencies : ""
    cancellation_methods ||--o{ cancellation_steps : ""
    cancellation_methods ||--o{ cancellation_method_bookmarks : ""
    cancellation_methods ||--o{ cancellation_method_goods : ""

    users {
        uuid id PK "ユーザーID"
        varchar mail_address UK "メールアドレス"
        varchar clerk_id UK "ClerkのID"
    }

    subscriptions {
        uuid id PK "ID"
        varchar name "サービス名"
        boolean active "アクティブ状態"
        uuid user_id FK "ユーザーID"
        numeric amount "金額"
        smallint currency_id FK "通貨ID"
        date next_update "次回更新日"
        smallint update_cycle_number "更新周期数"
        smallint update_cycle_unit "更新周期単位"
        uuid linked_cancellation_method_id FK "リンクされた解約方法ID"
    }

    currencies {
        smallint id PK "通貨ID"
        real exchange_rate "為替レート"
    }

    cancellation_methods {
        uuid id PK "ID"
        varchar subscription_name "サービス名"
        boolean is_private "プライベート設定"
        text precautions "注意事項"
        text free_text "その他"
        varchar url_to_cancel "解約先URL"
        uuid created_user_id FK "作成者ユーザーID"
        timestamp updated_at "更新日時"
    }

    cancellation_steps {
        uuid cancellation_method_id PK,FK "解約方法ID"
        smallint sequential_order PK "順序"
        varchar procedure "解約手順"
    }

    cancellation_method_bookmarks {
        uuid cancellation_method_id PK,FK "解約方法ID"
        uuid user_id PK,FK "ユーザーID"
    }

    cancellation_method_goods {
        uuid cancellation_method_id PK,FK "解約方法ID"
        uuid user_id PK,FK "ユーザーID"
    }
```

後から拡張することは簡単でも、縮小するのは難しそうなのでできる限り小さめに制約をつけてます。

将来的に複数のSSOを実装した際、同じユーザにログインできる様にするかもしれないので、ClerkのIDとサービスで使用するユーザIDは分けています。

### ユーザー操作と関係なく定期実行する処理

定期実行する必要の処理は以下の2つがあります。

- 為替レート更新
- 更新通知メールの送信

どちらもスクリプトで簡単に実行できる処理なので、Github Actionsを使用して0時に定期実行しています。

## 力を入れたポイント

## 初めて正式なサービスを開発・リリースした上での学び

### 「設計」の重要性

今回のサービス開発では、0から100まで完全に自分で開発を行いました。その中で強く実感したのは、後になればなるほどミスの修正にコストがかかるということです。

UIについては、とりあえずv0を使用して作成し、後から変更しようと考えていました。しかし、いざ修正を試みると既存のコンポーネント構成を見直す必要が生じ、結局現時点ではv0で作成したそのままのUIを採用しています。

一方、クリーンアーキテクチャを採用して後から変更しやすいように設計したバックエンド側は、リリース直前の今でもテーブル定義などの変更に大きなコストがかからない状態を維持できています。実際、今週も最終調整として何度かテーブル定義を変更することができました。

### 実装前の検討の重要性

当初、ドメインオブジェクトをtypeを使用して表現しようと考えていました。TypeScriptのtypeではプライベートコンストラクターと同様の機能が再現できないと考え、classを使用して実装していました。

しかし、後からBranded Typeを使用すればtypeでほぼ同様の機能を持たせられることを知り、実装を変更することになりました。

参考: https://qiita.com/uhyo/items/de4cb2085fdbdf484b83

その他、Server Actionsやそれによるデータフェッチ手法なども、実装後に知ることになり、無駄な手戻りが発生してしまいました。これらの技術は「先に」調べておくべきでした。

## おまけ

### 特に参考にした文献

- [Next.jsの考え方](https://zenn.dev/akfm/books/nextjs-basic-principle)
- [関数型ドメインモデリング](https://www.amazon.co.jp/%E9%96%A2%E6%95%B0%E5%9E%8B%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%A2%E3%83%87%E3%83%AA%E3%83%B3%E3%82%B0-%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E9%A7%86%E5%8B%95%E8%A8%AD%E8%A8%88%E3%81%A8F-%E3%81%A7%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AE%E8%A4%87%E9%9B%91%E3%81%95%E3%81%AB%E7%AB%8B%E3%81%A1%E5%90%91%E3%81%8B%E3%81%8A%E3%81%86-%E3%82%A2%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%89%E3%83%AF%E3%83%B3%E3%82%B4-Scott-Wlaschin-ebook/dp/B0DDBPNBVJ)

### 開発にかけた時間

- 開発開始 3/22
- 平日 平均2時間くらい開発
- 休日 平均4時間くらい開発
- 合計200時間程度でリリース

### 開発に使用したツール

|サービス|用途|
|-|-|
|[v0](https://v0.dev)|UIの検討|
|[Windsurf](https://windsurf.com)|コーディング用のエディタ|
|[Felo](https://felo.ai/ja/search)|検索|
|[Perplexity](https://www.perplexity.ai)|検索・英語サイトの翻訳|
|[Github](https://github.com)|コード管理|

Windsurfは、基本はClaude 3.7で使用して、わからないことがあったらo3を使用してました。

## おわりに

今回、このサービスを開発するにあたって、途中で詰まったりしても淡々と継続できたのは、JISOU（同じ意志を持つコミュニティ）に所属しているおかげでした。もし、読者の方の中で意欲があっても行動できずに悩んでいる方がいらっしゃれば、ぜひ最下部のリンクから相談してみてください。

また、今後このサービスはパフォーマンス改善の後、順次機能を拡張していく予定ですので、ぜひ使い続けていただければと思います。

ここまでお読みいただきありがとうございました。

## JISOUのメンバー募集中

プログラミングコーチングJISOUではメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか？
気になる方はぜひHPからLINE登録をお願いします！👇

https://projisou.jp/