はじめに
ブラウザのお気に入りを、パソコンとスマホの両方で同じように使いたい。そう思って、オンラインブックマークサービス「Marktree(マークツリー)」を作りました。2026年11月2日に公開しています。
開発は 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 のおかげが大きいです。
ブラウザのお気に入りの整理に困っている方は、ぜひ使ってみてください。感想や要望もお待ちしています。

