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?

SitePruneという「コンテンツ整理判断」向けOSSをalpha公開した

1
Posted at

はじめに

Webサイトやブログを長く運営していると、記事が増えすぎてこういう問題が出てきます。

  • どの記事を残すべきか分からない
  • どの記事をリライトすべきか分からない
  • 似た記事を統合したい
  • noindexにすべきページを見つけたい
  • 削除候補を見つけたい
  • 内部リンクが足りないページを見つけたい

GA4やUmamiのようなアクセス解析ツールは便利ですが、主目的は「何人来たか」「どこから来たか」「何がクリックされたか」を見ることです。

一方で、SEO運営で本当に欲しいことは、

このページは残すべきか、改善すべきか、統合すべきか、noindexにすべきか、削除すべきか

という判断だったりします。

そこで、アクセス解析ではなくコンテンツ整理判断に寄せた軽量OSS として SitePrune を作りました。

npm alpha版も公開しています。

npm install site-prune@alpha

作ったもの

SitePruneは、Webサイト内のページごとの状態を集計し、以下のような判断を支援するライブラリです。

  • KEEP
  • IMPROVE
  • MERGE
  • NOINDEX
  • DELETE
  • LINK_MORE

リポジトリ:

npm:

npm install site-prune@alpha

alpha版のバージョンは以下です。

0.1.0-alpha.0

SitePruneがやること

SitePruneは大きく分けて、以下のデータを扱います。

1. ブラウザイベント

ブラウザ側で以下のイベントを収集します。

  • page_view
  • active_time
  • scroll_depth
  • internal_click
  • outbound_click
  • cta_click
  • custom_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運営で「記事が増えすぎて整理したい」という課題には合う方向だと思っています。

フィードバック歓迎です。

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?