はじめに
Webサイトやブログを長く運営していると、記事が増えすぎてこういう問題が出てきます。
- どの記事を残すべきか分からない
- どの記事をリライトすべきか分からない
- 似た記事を統合したい
- noindexにすべきページを見つけたい
- 削除候補を見つけたい
- 内部リンクが足りないページを見つけたい
GA4やUmamiのようなアクセス解析ツールは便利ですが、主目的は「何人来たか」「どこから来たか」「何がクリックされたか」を見ることです。
一方で、SEO運営で本当に欲しいことは、
このページは残すべきか、改善すべきか、統合すべきか、noindexにすべきか、削除すべきか
という判断だったりします。
そこで、アクセス解析ではなくコンテンツ整理判断に寄せた軽量OSS として SitePrune を作りました。
npm alpha版も公開しています。
npm install site-prune@alpha
作ったもの
SitePruneは、Webサイト内のページごとの状態を集計し、以下のような判断を支援するライブラリです。
KEEPIMPROVEMERGENOINDEXDELETELINK_MORE
リポジトリ:
npm:
npm install site-prune@alpha
alpha版のバージョンは以下です。
0.1.0-alpha.0
SitePruneがやること
SitePruneは大きく分けて、以下のデータを扱います。
1. ブラウザイベント
ブラウザ側で以下のイベントを収集します。
page_viewactive_timescroll_depthinternal_clickoutbound_clickcta_clickcustom_event
初期化はこんな感じです。
import { createTracker } from "site-prune";
const tracker = createTracker({
endpoint: "/api/site-prune"
});
デフォルトで以下を自動計測します。
- 初回ページビュー
- SPA遷移時のページビュー
- アクティブ滞在時間
- スクロール到達率
- 内部リンククリック
- 外部リンククリック
-
data-site-prune-ctaのCTAクリック
CTAはこう書けます。
<button data-site-prune-cta="newsletter_signup">
Join newsletter
</button>
カスタムイベントも送れます。
tracker.trackEvent("lead_magnet_download", {
source: "article"
});
Next.js Route Handler
Next.js App Router向けに、イベント受信route helperを用意しています。
import { createSitePruneRouteHandler, isBotRequest } from "site-prune/next";
import { SupabaseSitePruneAdapter } from "site-prune/adapters/supabase";
export const POST = createSitePruneRouteHandler({
adapter: new SupabaseSitePruneAdapter(supabase),
isBot: isBotRequest
});
bot除外も入れられます。
import { isBotRequest } from "site-prune/next";
rate limitもライブラリ本体に抱え込まず、hookとして差し込めるようにしました。
export const POST = createSitePruneRouteHandler({
adapter,
rateLimit: async (request) => {
const allowed = await limiter.check(request);
return allowed
? { ok: true }
: {
ok: false,
retryAfter: 60,
message: "Too many SitePrune requests."
};
}
});
Supabase / PostgreSQL対応
SitePrune本体はDBを持ちません。
代わりに、保存先をadapterとして差し替えられるようにしています。
interface SitePruneModelAdapter {
insertEvents(events: SitePruneEvent[]): Promise<void>;
upsertCrawlPages(pages: CrawlPage[]): Promise<void>;
listPageDailyStats(range: { from: string; to: string }): Promise<PageDailyStats[]>;
listCrawlPages?(): Promise<CrawlPage[]>;
}
現時点では以下を用意しています。
- Memory adapter
- PostgreSQL adapter
- Supabase adapter
SQLも同梱しています。
schema/postgres.sql
schema/supabase.sql
Supabase版はRLSを有効化し、service role経由のサーバー利用を前提にしています。
Screaming Frog CSV importer
SitePruneで最初から入れておきたかったのが、Screaming Frog CSV importerです。
通常のアクセス解析だけだと、以下のようなSEO判断は弱くなりがちです。
- inlinksが少ない
- canonicalが別ページを向いている
- word countが少ない
- indexabilityに問題がある
- status codeが200ではない
そこで、Screaming FrogのCSVを取り込めるようにしました。
import { parseScreamingFrogCsvWithReport } from "site-prune";
const { pages, report } = parseScreamingFrogCsvWithReport(csv);
await adapter.upsertCrawlPages(pages);
reportには以下が入ります。
- total rows
- imported rows
- skipped rows
- missing columns
- duplicate paths
- row-level warnings
CSVを取り込んだ時に、
ちゃんと入ったのか?
何件スキップされたのか?
URL列が足りているのか?
重複pathはないか?
が分かるようにしています。
分析ロジック
現時点では、AIではなくルールベースで判定しています。
理由は、alpha段階では「なぜその判定になったか」が見える方がOSSとして信頼されやすいからです。
ざっくり以下のような指標を見ます。
- PV比率
- Active比率
- Click比率
- CTA比率
- 平均スクロール率
- 内部流入比率
- Exit率
- Screaming Frog由来のcrawl signal
分析はこうです。
import { analyzePages } from "site-prune";
const stats = await adapter.listPageDailyStats({
from: "2026-05-01",
to: "2026-05-21"
});
const crawl = await adapter.listCrawlPages?.();
const recommendations = analyzePages(stats, crawl ?? []);
Example App
Next.jsのexampleも用意しました。
cd examples/next-app
npm install
npm run dev
exampleには以下を入れています。
- tracker provider
-
useSitePrune()hook - CTA自動計測
- custom event
- 内部リンク計測
- 外部リンク計測
- bot filter
- rate limit hook
- Screaming Frog CSV upload
- import report表示
- dashboard
サンプル記事ページもあります。
/blog/react-seo
/blog/old-affiliate-post
/blog/internal-linking
これで、単なるライブラリAPIだけではなく、
自分のNext.jsサイトではどう置けばいいか
CTAはどう書けばいいか
custom eventはどう送ればいいか
がコードで見えるようにしています。
軽量性
SitePruneは軽量を重視しています。
client bundleの目標は以下です。
client < 10KB gzip
現在のalphaでは、client import graphは以下でした。
13051 bytes raw
3326 bytes gzip
CIでもサイズチェックします。
npm run size:client
Privacy方針
SitePruneはcookie-lessを前提にしています。
やらないこと:
- cookie必須化
- IP保存
- fingerprinting
- 永続ユーザーID
- 個人情報収集
ブラウザ側のsession idは sessionStorage を使った短命なものです。
なぜUmamiやGA4と違うのか
UmamiやGA4は「アクセス解析」として便利です。
SitePruneはそこを置き換えるものではありません。
SitePruneの目的は、
どの記事を残すか
どの記事を改善するか
どの記事を統合するか
どの記事をnoindexにするか
どの記事を削除するか
どの記事に内部リンクを足すか
という、SEO運営の整理判断です。
なので、Screaming Frog importerやcrawl modelを最初から入れています。
今後やりたいこと
alpha版の次にやりたいことは以下です。
- screenshots / GIF追加
- GitHub issue templates
- release workflow
- Search Console importer
- sitemap importer
- GA4 import
- content cannibalization検出
- AI改善提案
- dashboardの再利用パッケージ化
AIは入れたいですが、最初から必須にはしません。
まずはルールベースで透明性のある判定を作り、その上にAI suggestionを載せる方針です。
まとめ
SitePruneは、アクセス解析ではなく コンテンツ整理判断 に寄せたOSSです。
今はalphaですが、以下はすでに入っています。
- browser tracker
- Next.js route helper
- Supabase/Postgres adapter
- SQL schema
- Screaming Frog importer
- import diagnostics
- pruning recommendation
- example dashboard
- CI
- bundle size check
試す場合は以下です。
npm install site-prune@alpha
まだalphaなので、APIやpackage構成は変わる可能性があります。
また、現時点では「これだけでSEO判断が完結する」ものではありません。
あくまで、リライト・統合・noindex・削除候補を見つけるための補助ツールです。
ただ、SEO運営で「記事が増えすぎて整理したい」という課題には合う方向だと思っています。
フィードバック歓迎です。