1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【個人開発】AI(Claude Code)と4週間で作った、ツリー表示のブックマークサービス『Marktree』

1
Posted at

はじめに

ブラウザのお気に入りを、パソコンとスマホの両方で同じように使いたい。そう思って、オンラインブックマークサービス「Marktree(マークツリー)」を作りました。2026年11月2日に公開しています。

Marktree の画面

開発は AI コーディングエージェントの Claude Code と二人三脚で進めました。最初の設計書のコミットが 10月7日、公開が 11月2日なので、およそ4週間です。

この記事では、次の3つを書きます。

  • どんなサービスか
  • 技術構成と、実装でハマったところ
  • AI と一緒に開発してみて、うまくいったやり方

どんなサービスか

Edge や Chrome のお気に入りと同じ「フォルダのツリー」で、ブックマークを整理するサービスです。

  • 1つのツリーで見渡せる:開いたフォルダの中身がその場に並ぶので、複数のフォルダを見比べられます
  • どの端末からでも同じ:PC で登録したブックマークを、スマホからもそのまま使えます(PWA 対応。ホーム画面に追加するとアプリのように使えます)
  • リスト表示とカード表示:フォルダごとに切り替えられます。カードには OGP 画像を出します
  • メモ:ブックマークと同じ並びに、色付きのメモを置けます
  • フォルダの共有:選んだフォルダだけをリンクで共有できます。受け取った人は、自分のツリーで最新の内容を見られます
  • インポート・エクスポート:ブラウザのお気に入り(HTML)を取り込み、いつでも書き出せます

料金は無料です。

パソコンとスマホの画面

技術構成

項目 採用技術
バックエンド PHP 8.4 / Laravel 13(API 専用)/ Sanctum(SPA 認証)/ Fortify
フロントエンド Vue 3(Composition API)/ TypeScript / Vite / Pinia / Tailwind CSS
PWA vite-plugin-pwa(Workbox)/ IndexedDB(Dexie.js)
DB MariaDB 10.11
テスト Pest / Vitest / Playwright
サーバー Xserver(共用レンタルサーバー)
開発環境 Windows 11 + WSL2 + Docker(PHP・Node・MariaDB・Mailpit をすべてコンテナで)

SPA と API を同じドメインに置き、Cookie セッションで認証しています。CORS やトークンの管理が要らなくなるので、個人開発ではかなり楽になります。

Laravel と Vue を選んだ理由の一つは「AI と相性が良いこと」です。情報量が多い技術は、AI の提案の精度も高くなります。TypeScript も「AI が改修したときに壊れにくくするため」という理由で採用しました。

実装で工夫したところ・ハマったところ

1. 無制限の階層と ON DELETE CASCADE の落とし穴

フォルダは隣接リスト(parent_id)で持ち、子孫は再帰 CTE で取ります。

WITH RECURSIVE tree AS (
  SELECT id, 0 AS depth FROM folders WHERE id = ? AND user_id = ?
  UNION ALL
  SELECT f.id, t.depth + 1 FROM folders f JOIN tree t ON f.parent_id = t.id
)
SELECT id, depth FROM tree;

フォルダの削除は、最初は外部キーの ON DELETE CASCADE に任せるつもりでした。ところが InnoDB の連鎖削除は 15 階層までしかたどれません。深い階層のフォルダを消すとエラーになります。

そこで外部キーは ON DELETE RESTRICT(整合性の保険)にしました。削除はアプリ側で、1つのトランザクションの中で「配下のブックマークを削除 → フォルダを深い順に削除」と行っています。

なお、テストも本番と同じ MariaDB で動かしています。再帰 CTE などの SQL を、本番と同じ DB で確かめるためです。

2. 共用サーバーでキューを動かす

ブックマークを登録すると、ページの OGP(タイトル・サムネイル・ファビコン)を非同期で取りに行きます。ただ、共用サーバーでは常駐プロセスを動かせません。

そこで cron で毎分 schedule:run を起動し、その中で queue:work --stop-when-empty --max-time=50 を実行しています。SPA 側は、取得中の項目だけを 10 秒ごとに取り直して表示を更新します。

3. OGP 取得の SSRF 対策

ユーザーが入力した URL にサーバーからアクセスするので、SSRF 対策は必須です。

  • スキームは http / https、ポートは 80 / 443 のみ
  • 接続前に DNS を解決し、プライベート・ループバック・リンクローカルなどのアドレスなら拒否
  • 解決した IP に接続を固定(DNS リバインディング対策)
  • リダイレクトは 3 回までで、毎回同じ検査をする
  • タイムアウト 5 秒、読み込みは先頭 1MB まで、HTML 以外なら中断

4. iPhone 版 Chrome で画面が 68px ずれる

いちばん悩んだのがこれです。iOS の iPhone 版 Chrome で、ページ全体をスクロールする作りにすると、ときどき表示が約 68px ずれてヘッダーが隠れました(WebKit の不具合 297779)。

実機で検証用ページを作って調べたところ、ページ側からは防げないことが分かりました。そこで端末によって作りを分けています。

  • PC と iPhone 版 Chrome:ヘッダーを固定し、画面の高さに固定した枠の中だけをスクロールする
  • それ以外のスマホ:ページ全体をスクロールする(ツールバーが隠れて画面を広く使えるため)

5. ドラッグ&ドロップは自前で実装

「フォルダの中へ入れる」操作は、SortableJS などのライブラリでは扱いにくかったので自前で実装しました。

  • PC:HTML5 Drag and Drop
  • スマホ:Pointer Events で長押ししてつかむ。画面の上下端で自動スクロールし、閉じたフォルダの上で 0.8 秒止めると開く

AI(Claude Code)と開発してみて

設計書を先に書き、それを「正」にする

最初に 要件定義書・技術選定書・基本設計書 を AI と一緒に書き、リポジトリの doc/ に置きました。プロジェクトの CLAUDE.md には「仕様は doc/ の設計書を正とする」と書いてあります。

仕様を変えたときは、コードと一緒に設計書も更新します。「なぜこうしたか」を日付つきで残しておくと、数日後に AI が同じ所を直すときに、過去の判断を踏まえて提案してくれます。

実行環境はすべて Docker に寄せる

PHP・Composer・Node はホストに入れず、すべてコンテナの中で実行しています。AI に「docker compose exec app ... で実行する」と覚えさせておけば、環境の違いで悩むことがほとんどありません。

画面の確認やヘルプの図も AI に任せる

画面の確認には Playwright を使っています。ヘルプページの図(番号付きのスクリーンショット)も、デモ用のアカウントを作って撮影するスクリプトを AI が書きました。画面を変えたらスクリプトを流すだけで、図がすべて撮り直されます。

決めるのは人間

一方で、AI に任せきりにしなかったこともあります。

  • 何を作るか、何を作らないか(たとえば「ブックマークの内容を広告会社に渡さない」など)
  • 実機での確認(iPhone の表示ずれは、実機でないと分かりませんでした)
  • 公開前の最終判断

AI はたくさん手を動かしてくれますが、方向を決めて結果を確かめるのは自分、という分担がうまくいったと感じています。

おわりに

4週間で、ツリー表示・PWA・フォルダの共有・管理画面まで作りきれたのは、AI のおかげが大きいです。

ブラウザのお気に入りの整理に困っている方は、ぜひ使ってみてください。感想や要望もお待ちしています。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?