FREENOTE を終了しました

#Nextjs#生成AI#SaaS#Firebase
FREENOTE を終了しました

FREENOTE というノートアプリの提供を、2026年9月30日をもって終了しました。
1月9日に create-next-app を実行してから9か月弱、コミット数は392件でした。

開発のきっかけは、1月に公開された思考記録ツールの CuraQ に触れた経験でした。移動中の車内で触り続けるうちに、自分でも新しいサービスを作りたいと考え始めました。構想を練る過程で自身の思考を整理する必要が生じたものの、LLM を活用して思考を構造化しながら記録できる既存サービスは見当たりませんでした。そこで、思考の整理と記録を支援する道具として FREENOTE の開発を始めました。

書くだけで、つながる

コンセプトは「書くだけで、つながる」でした。

Zettelkasten を実践しようとすると、メモを書く行為よりも、メモ同士を関連づける作業に手間がかかります。どのメモとどのメモに関連があるのかを記録者が記憶し続ける必要があり、この認知負荷が運用の障壁になりがちでした。そこで、メモ同士の関連づけを埋め込みモデルによる類似度計算に委ねる設計にしました。

この方針に伴い、FREENOTE には関連づけのための手動操作をほとんど用意していません。

  • メモは300文字までに制限し、思いついた内容を短く書く設計にしました。
  • メモを保存すると、LLM が関連キーワードを抽出し、埋め込みベクトルを生成します。
  • ベクトルの意味的類似度に基づいて、関連するメモ同士が自動的にリンクされます。
  • 長文にまとめる段階になったら、ノート(Markdown)として再構成できます。

ただし、単に意味の近さだけで接続すると、表面的な類似にとどまる傾向があります。そこで、メモ間の関係性に「発展」「反論」「具体例」「補足」「関連」といったラベルを付与できる思考チェーンの仕組みも導入しました。LLM が候補を信頼度付きで提示し、人間が採否を選択する構成です。

技術構成

採用した技術スタックは次のとおりです。

  • フレームワーク: Nextjs 16(App Router)、React 19
  • 言語: TypeScript
  • スタイル: Tailwind CSS v4、Radix UI
  • データベース: Drizzle ORM、Turso(libSQL)
  • 認証: Firebase Authentication(Google 認証)
  • AI 連携: Vercel AI SDK(Google、OpenAI、Anthropic)
  • 決済: Stripe
  • エディタ: Tiptap
  • グラフ描画: React Flow、dagre
  • リンター・フォーマッター: Biome
  • ホスティング: Vercel

ディレクトリ構成は src/features/<機能名>/ 配下に server、client、components を配置し、"use server" と "use client" で境界を明示しました。機能の追加に伴い、最終的には features が30個ほどに達しました。

つながりの計算

埋め込みベクトルには 768 次元の text-embedding-004 を採用しました。Turso はベクトル型をサポートしているため、F32_BLOB カラムにインデックスを作成し、vector_top_k 関数を用いて類似するメモを検索しました。

FROM vector_top_k('memo_embeddings_vec_idx', vector32('...'), 20) AS vt

しかし、ベクトルの近さだけで判定すると、短いメモ同士では文脈の異なる不自然な組み合わせが抽出される場合がありました。そこで、LLM が抽出した関連キーワードの一致度も考慮し、埋め込みベクトルの類似度とタグの一致スコアを 0.5 ずつ加重平均した合成スコアで判定しました。類似度のしきい値は当初 0.6 に設定していましたが、リンクが生成されにくかったため、検証を経て 0.4 に調整しました。

モデルのプロバイダは Vercel AI SDK を用いて抽象化し、ユーザーごとにモデルを切り替えられる構成にしました。モデルの利用回数はカテゴリごとに保存し、契約プランに応じた上限管理を行いました。

書いたものを外に出す

作成したノートは、外部のブログサービスへ直接公開できる仕組みを用意しました。

  • WordPress(REST API 連携)
  • Hugo(GitHub へのコミット連携)
  • はてなブログ(AtomPub 連携)
  • note.com(投稿用下書き画面への連携 URL 生成とクリップボード転送)

各連携先の設定と、どのノートがどの外部記事に対応するかを管理するリレーションテーブルを保持していました。外部連携としては、メモを登録できる API(API キー発行機能付き)や、思考記録ツールの CuraQ との連携機能も実装しました。

マルチテナント化の試行と撤回

最も実装時間を費やしながら、最終的に廃棄したのがマルチテナント機能でした。

初期設計では、単一のデータベース内で全ユーザーのデータを行単位で分離していました。そこから、有料プランのユーザーに対してデータの独立性を高め、個別エクスポートや削除の確実性を担保する目的で、Turso 上で専用データベースを動的に払い出す構成へと移行しました(1月24日実装)。

しかし、この個別データベース運用の実装には多くの課題が伴いました。

  • Platform API のレスポンスに含まれるフィールド名が PascalCase であり、修正対応が2度発生しました。
  • データベース作成直後は反映が完了しておらず即座には接続できないため、プロビジョニングの完了を待機する処理が必要でした。
  • プラン変更時に既存メモを専用データベースへ移行する際、SQLite のバインド変数上限に抵触しました。バッチサイズを 50 レコード(150 変数)まで絞り込み、最終的にはカーソルを用いて段階的に移行する方式へ改修しました。
  • ダウングレード時に共有データベースへデータを書き戻す処理も必要となり、同期状態の可視化や移行履歴の管理まで実装することになりました。

結果として運用の複雑さが増大したため、2月7日にマルチテナント関連の実装をすべて削除し、元の構成に戻しました。

終わらせかた

8月13日にサービス終了の告知を掲載し、同日付けで新規登録の受付を停止しました。同時にデータエクスポート機能を提供しました。サービスが終了しても、ユーザーが蓄積したメモやノートを手元に保存できるようにするためです。

終了日は単一の定数(SERVICE_CLOSURE_DATE)として定義し、LP、ダッシュボードのバナー、登録ページの各画面で同一の値を参照させました。通常のお知らせ機能はデータベースで管理していましたが、終了案内については障害等による非表示を防ぐため、コード内に直接定義しました。

そしてサービスの提供終了日にあたる本日、全画面を終了案内に切り替えました。

サービス運用を終えて

9か月間の開発と運用を通じて、LLM を組み込んだ機能設計と、初期開発におけるインフラ境界の選択について具体的な知見が得られました。

検索精度の調整に関しては、FREENOTE で検証したベクトル類似度とキーワード一致度を加重平均する知見が、自社で開発している問い合わせ管理 SaaS「inqbox」の関連情報検索機能にそのまま引き継がれています。

一方、個別データベースを用いたマルチテナント化の試行と撤回からは、需要の検証前にインフラ境界を細分化する難しさを学びました。動的なプロビジョニングや双方向のデータ移行は、アプリケーション層に多くの例外処理と監視運用を要求します。初期段階では行レベルのデータ分離を選択し、構造の単純さを維持したほうが手戻りを防ぎやすくなります。

この試行錯誤を通じてプロビジョニングの非同期性や移行時の境界条件をあらかじめ把握できたため、inqbox では要件に合わせたマルチテナント構成を設計段階から安定して成立させられました。

パーティーハード株式会社では、こうした自社プロダクトでの設計・検証・撤退の経験を、受託開発や AI ソリューションのアーキテクチャ設計に反映させていきます。

望月 涼太 / polidog

望月 涼太 / polidog

Co-Founder / Web Engineer