<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>PruneSEO Blog</title>
        <link>https://docs.pruneseo.com/blog</link>
        <description>PruneSEO Blog</description>
        <lastBuildDate>Tue, 22 Dec 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Enterprise SEO At Scale: The Programme, Not The Audit]]></title>
            <link>https://docs.pruneseo.com/blog/enterprise-seo-at-scale</link>
            <guid>https://docs.pruneseo.com/blog/enterprise-seo-at-scale</guid>
            <pubDate>Tue, 22 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[What changes when indexation is managed across brands, domains, markets and teams: ownership, change control, international complexity and programme-level measurement.]]></description>
            <content:encoded><![CDATA[<p>An audit tells you what is wrong. A programme tells you what happens next. Most organisations that operate large websites have excellent audit capability and almost no programme capability, which is why the same findings recur annually, in a report nobody disagrees with and nobody acts on. Enterprise-scale SEO is not a harder version of the audit problem. It is a different problem entirely, made of ownership, change control and the slow arithmetic of systems that other teams own — and it fails or succeeds almost entirely on the first of those.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-distinction-that-matters">The distinction that matters<a href="https://docs.pruneseo.com/blog/enterprise-seo-at-scale#the-distinction-that-matters" class="hash-link" aria-label="Direct link to The distinction that matters" title="Direct link to The distinction that matters" translate="no">​</a></h2>
<p>An audit produces a list. A programme produces a system that stops the list from growing.</p>
<table><thead><tr><th>Audit</th><th>Programme</th></tr></thead><tbody><tr><td>Diagnostic snapshot</td><td>Continuous measurement</td></tr><tr><td>Owned by one specialist</td><td>Owned across four or five teams</td></tr><tr><td>Findings delivered as a report</td><td>Findings delivered as merged changes</td></tr><tr><td>Success measured by the audit</td><td>Success measured eighteen months later</td></tr><tr><td>Ends at a recommendation</td><td>Ends at a regression check</td></tr></tbody></table>
<p>The test of whether you have the second thing is simple and uncomfortable: ask who is responsible for the URL space. If the answer is a person in a marketing team with no ability to change a routing rule, you have an audit programme and not a management one. If the answer is a named engineering team with the URL surface in their remit, you have a programme.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ownership-is-where-it-actually-fails">Ownership is where it actually fails<a href="https://docs.pruneseo.com/blog/enterprise-seo-at-scale#ownership-is-where-it-actually-fails" class="hash-link" aria-label="Direct link to Ownership is where it actually fails" title="Direct link to Ownership is where it actually fails" translate="no">​</a></h2>
<p>On a large site the URL space is written by people who are not thinking about indexation: the merchandising team adds a colour filter because customers asked for it, the campaign tool appends a tracking parameter, the international team launches a new locale, the app team ships a client-side route. Every one of these is individually reasonable. Collectively they produced the 900,000.</p>
<p>Organisations attempt to fix this with policy. Policy fails because the people breaking the surface do not read it and are not measured by it. It works when ownership is explicit and when the teams that generate URLs have indexation in their objectives.</p>
<table><thead><tr><th>Team</th><th>What they generate</th><th>What they need to own</th></tr></thead><tbody><tr><td>Merchandising</td><td>Filters, sort options, category structure</td><td>Indexable facet definition</td></tr><tr><td>Platform engineering</td><td>Routing, templates, headers</td><td>robots.txt, sitemaps, canonical policy</td></tr><tr><td>Content</td><td>Editorial tags, archives, author pages</td><td>noindex rules for thin archives</td></tr><tr><td>International</td><td>Locales and hreflang pairs</td><td>Reciprocity and return-tag correctness</td></tr><tr><td>Marketing</td><td>Campaign and UTM URLs</td><td>A campaign URL policy and an expiry</td></tr><tr><td>App / mobile</td><td>Client-side routes</td><td>Server-render parity with web</td></tr></tbody></table>
<p>That last column is the change. Not "tell the teams about SEO" but "give each team one URL-space decision and make them accountable for it". Two decisions per team. It is achievable, and once each team owns decisions, the audit conversation becomes a review rather than a confrontation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="change-control-the-deployment-gate">Change control: the deployment gate<a href="https://docs.pruneseo.com/blog/enterprise-seo-at-scale#change-control-the-deployment-gate" class="hash-link" aria-label="Direct link to Change control: the deployment gate" title="Direct link to Change control: the deployment gate" translate="no">​</a></h2>
<p>Large sites generate URLs through deployment, which means the intervention point is the release process.</p>
<p>The pattern that works is unglamorous. A short, automated check runs against any change that touches templates, routing or URL generation:</p>
<table><thead><tr><th>Check</th><th>Threshold</th><th>Why</th></tr></thead><tbody><tr><td>Count of indexable URLs changed by the release</td><td>Above agreed delta</td><td>Catches accidental surface explosion</td></tr><tr><td>robots.txt and sitemap diffs reviewed</td><td>Always</td><td>These are global levers</td></tr><tr><td>Status code changes on existing URLs</td><td>Zero tolerance</td><td>Silent 200 to 302 happens</td></tr><tr><td>Canonical changes on template files</td><td>Always</td><td>Template-level blast radius</td></tr><tr><td>New query parameters in templates</td><td>Requires sign-off</td><td>The root of facet growth</td></tr></tbody></table>
<p>Nobody needs to write this by hand as a process document. It can be a set of assertions in CI against a crawled baseline. The value is that it moves the conversation from "we found this after the audit" to "the release was blocked and someone made a decision".</p>
<blockquote>
<p>At enterprise scale, the cheapest SEO win is not a fix. It is a check that stops the problem recurring, added once.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="multi-brand-and-multi-domain">Multi-brand and multi-domain<a href="https://docs.pruneseo.com/blog/enterprise-seo-at-scale#multi-brand-and-multi-domain" class="hash-link" aria-label="Direct link to Multi-brand and multi-domain" title="Direct link to Multi-brand and multi-domain" translate="no">​</a></h2>
<p>Groups operating several domains face a specific set of problems that single-site advice does not cover.</p>
<table><thead><tr><th>Situation</th><th>Risk</th><th>Standard answer</th></tr></thead><tbody><tr><td>Two brands on subdomains</td><td>Duplicate content, split authority</td><td>Separate subdomains with distinct purpose, not mirrors</td></tr><tr><td>Two brands on subdirectories</td><td>Cross-indexation of identical pages</td><td>Canonical or genuine differentiation</td></tr><tr><td>Legacy domain after acquisition</td><td>Duplication across the estate</td><td>Redirect at domain level, not page level</td></tr><tr><td>Regional domains</td><td>Maintenance multiplied by market</td><td>Usually worse than subdirectories plus hreflang</td></tr><tr><td>Microsites for campaigns</td><td>Orphaned, expired, indexed forever</td><td>Campaign registry with expiry dates</td></tr></tbody></table>
<p>The pattern to avoid is the one that produces the most waste: a microsite per campaign, each independently built, each with its own duplicate brand pages, each left alive after the campaign ends. A campaign registry is a dull artefact and it removes a surprisingly large share of a group's indexation problem. It needs three fields — owner, launch date, expiry date — and a report of expired campaigns still live.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="international-complexity">International complexity<a href="https://docs.pruneseo.com/blog/enterprise-seo-at-scale#international-complexity" class="hash-link" aria-label="Direct link to International complexity" title="Direct link to International complexity" translate="no">​</a></h2>
<p>Hreflang at enterprise scale is less about the syntax and more about the operational process, because a misconfigured implementation at this scale is worse than none.</p>
<table><thead><tr><th>Implementation model</th><th>Maintenance cost</th><th>Typical failure</th></tr></thead><tbody><tr><td>Subdirectories plus hreflang</td><td>Low</td><td>Return tags missing after a CMS migration</td></tr><tr><td>ccTLD per market</td><td>Very high</td><td>Divergent templates, partial deployments</td></tr><tr><td>Language subdomains plus hreflang</td><td>Medium</td><td>Orphaned pages in some locales</td></tr><tr><td>No international structure, translate only</td><td>Low</td><td>Duplicate pages in the wrong language indexed</td></tr></tbody></table>
<p>The recurring failures are specific and checkable: return tags missing, self-references absent, hreflang pointing to redirected URLs, and pages in the index for locales that no longer exist. Each is a pattern-level check rather than a URL-level one, which is fortunate, because on an enterprise estate the URL count makes URL-level checking impossible.</p>
<p>Also worth stating: hreflang is a hint about equivalence and language, not a directive. It does not make a page indexable, and it cannot rescue a page that is blocked or noindexed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="migrations-as-the-highest-risk-project">Migrations as the highest-risk project<a href="https://docs.pruneseo.com/blog/enterprise-seo-at-scale#migrations-as-the-highest-risk-project" class="hash-link" aria-label="Direct link to Migrations as the highest-risk project" title="Direct link to Migrations as the highest-risk project" translate="no">​</a></h2>
<p>Most enterprise SEO damage is caused not by neglect but by a migration. The reason is structural: a migration is the one moment when a large number of simultaneous URL changes are made with a partial view of the consequences.</p>
<table><thead><tr><th>Migration type</th><th>Principal risk</th><th>Mitigation</th></tr></thead><tbody><tr><td>Platform change</td><td>Template regressions, render failures</td><td>Staging crawl diff before cutover</td></tr><tr><td>Domain consolidation</td><td>Redirect chains, missed mappings</td><td>Mapping table with 100% coverage check</td></tr><tr><td>Reorganisation</td><td>Intent changes without redirect</td><td>Per-pattern redirect plan with review</td></tr><tr><td>Rendering change</td><td>Content lost from initial response</td><td>Verify rendered HTML, not source</td></tr><tr><td>CMS change</td><td>Metadata defaults reset</td><td>Post-deploy metadata audit</td></tr></tbody></table>
<p>The one mitigation that reliably reduces migration risk is a <strong>pre-cutover crawl of staging</strong>, compared against a crawl of production. Pattern-level differences surface in minutes, and they catch the majority of these failures before launch rather than after.</p>
<p>Redirect mapping is the other discipline worth insisting on. A mapping table with machine-checkable coverage — every old URL either maps to exactly one new URL or is explicitly retired — is worth more than any amount of post-launch analysis, because post-launch analysis happens after the equity is already gone.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="measuring-the-programme">Measuring the programme<a href="https://docs.pruneseo.com/blog/enterprise-seo-at-scale#measuring-the-programme" class="hash-link" aria-label="Direct link to Measuring the programme" title="Direct link to Measuring the programme" translate="no">​</a></h2>
<p>Programme-level measurement should be boring, and boring is the point. Quarterly, pattern-level, with sample sizes printed:</p>
<table><thead><tr><th>Metric</th><th>What it tells you</th><th>What it does not tell you</th></tr></thead><tbody><tr><td>Addressable surface</td><td>Is the URL space growing?</td><td>Whether growth was intended</td></tr><tr><td>Index coverage by pattern</td><td>Where is the gap?</td><td>Whether the indexed set is the right set</td></tr><tr><td>Crawl efficiency</td><td>Is the crawler being wasted?</td><td>Whether indexation decisions are correct</td></tr><tr><td>Remediation throughput</td><td>Is the programme working?</td><td>Whether the right things were chosen</td></tr><tr><td>Regression rate</td><td>Is it holding?</td><td>Long-term trend direction</td></tr></tbody></table>
<p>Throughput and regression rate are the two that distinguish a programme from a project. A project completes its list. A programme sustains a low regression rate, which is only measurable if you re-measure on a schedule and compare against a stored baseline.</p>
<p>The metric that gets left out is the one finance cares about most, and there is a reason: an indexation programme has a long and uncertain payoff that is very hard to attribute. Be honest about that rather than manufacturing attribution, and report the mechanism — URLs removed, patterns fixed, coverage moved — which is directly measurable and defensible. The <a class="" href="https://docs.pruneseo.com/docs/features/sampling-confidence-and-extrapolation">sampling confidence and extrapolation</a> page covers how to report those with stated intervals, which is what keeps the programme's credibility intact over years rather than quarters.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="treat-the-surface-as-debt">Treat the surface as debt<a href="https://docs.pruneseo.com/blog/enterprise-seo-at-scale#treat-the-surface-as-debt" class="hash-link" aria-label="Direct link to Treat the surface as debt" title="Direct link to Treat the surface as debt" translate="no">​</a></h2>
<p>The most useful mental model for an enterprise is that every generated URL is a liability with an ongoing carrying cost. It must be crawled, rendered, stored, migrated, analysed and — one day — removed.</p>
<p>That framing changes which arguments win in a planning meeting. "Add a colour filter" stops being a small feature request and becomes "commit the organisation to carrying roughly 4,000 additional URLs, each of which must be crawled, analysed and migrated at every future re-platform". For a filter that three percent of visitors use, that calculation usually ends the conversation, which is the correct outcome and one that is almost impossible to make with an audit report alone.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/enterprise-seo-at-scale#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Pick the two teams that generate the most URLs and give each one named ownership of one URL-surface decision. Add the release checks for robots.txt, sitemap and canonical changes. Then establish a quarterly pattern-level measurement with a stored baseline.</p>
<p>Everything else — tooling, automation, vendor selection — is secondary to those three things and will not compensate for their absence.</p>
<p>PruneSEO supports the measurement half of the programme across multiple properties. <a class="" href="https://docs.pruneseo.com/docs/features/agency-workspaces-and-permissions">Agency workspaces and permissions</a> covers running it across brands and clients without cross-contamination, <a class="" href="https://docs.pruneseo.com/docs/guides/health-and-reporting">health and reporting</a> covers the stored baseline and the regression comparison, and <a class="" href="https://docs.pruneseo.com/docs/features/remediation-campaigns">remediation campaigns</a> is how a pattern-level finding becomes a tracked change rather than a line in a report. For multi-site structures, <a class="" href="https://docs.pruneseo.com/docs/guides/plans-and-upgrading">plans and upgrading</a> covers how properties are counted on a plan.</p>]]></content:encoded>
            <category>Agencies</category>
            <category>Programmatic SEO</category>
        </item>
        <item>
            <title><![CDATA[SEO Troubleshooting Guide: Symptom To Cause]]></title>
            <link>https://docs.pruneseo.com/blog/seo-troubleshooting-guide</link>
            <guid>https://docs.pruneseo.com/blog/seo-troubleshooting-guide</guid>
            <pubDate>Mon, 21 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A symptom-first SEO troubleshooting guide for large sites, covering sudden crawl drops, whole templates failing to index, ignored canonicals, sitemap errors and post-release declines.]]></description>
            <content:encoded><![CDATA[<p>Most SEO troubleshooting advice is organised by mechanism, which is useless when you are the person holding a ticket at two in the morning and the symptom is "traffic halved overnight". Mechanism-first guidance assumes you already know which part of the system to look at. In practice you know one fact — what changed — and you need to work outward from it. This guide is organised the other way round: symptom first, then the checks that distinguish between causes, in the order that actually discriminates between them.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-five-checks-before-anything-else">The five checks before anything else<a href="https://docs.pruneseo.com/blog/seo-troubleshooting-guide#the-five-checks-before-anything-else" class="hash-link" aria-label="Direct link to The five checks before anything else" title="Direct link to The five checks before anything else" translate="no">​</a></h2>
<p>Almost every investigation starts by skipping these and looking at Search Console coverage, which is usually where the symptom is least visible.</p>
<ol>
<li class=""><strong>Did the site deploy?</strong> Check the release timeline. On large sites a deploy is the single most common cause of a sudden change, and it is invisible in the data you are looking at.</li>
<li class=""><strong>Did robots.txt change?</strong> Diff it against the previous version. Almost nothing else produces a step change in crawl traffic.</li>
<li class=""><strong>Did response times change?</strong> p95 for Googlebot, not average for humans. A CDN rule or a slow query added in a release will slow crawls without slowing users.</li>
<li class=""><strong>Did the sitemaps change?</strong> A sitemap index that lost a child file is a large, silent removal.</li>
<li class=""><strong>Is the WAF still letting Googlebot through?</strong> Check the logs for 403s by user agent. This is checked last because it is least likely, and it produces the most confusing symptoms.</li>
</ol>
<p>Run those five before opening a single coverage report. Each takes minutes and together they eliminate the majority of incidents.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-symptom-index">The symptom index<a href="https://docs.pruneseo.com/blog/seo-troubleshooting-guide#the-symptom-index" class="hash-link" aria-label="Direct link to The symptom index" title="Direct link to The symptom index" translate="no">​</a></h2>
<table><thead><tr><th>Symptom</th><th>Most likely causes</th><th>Go to</th></tr></thead><tbody><tr><td>Googlebot fetches drop over 80% overnight</td><td>WAF block, robots.txt change, origin failure</td><td>Crawl collapse</td></tr><tr><td>One template entirely unindexed</td><td>Render failure, noindex on the template, canonical pointing away</td><td>Template not indexing</td></tr><tr><td>Wrong URL indexed instead of the right one</td><td>Canonical not seen, duplicate cluster unresolved</td><td>Wrong URL indexed</td></tr><tr><td>Sitemap errors or coverage warnings</td><td>404s in the sitemap, blocked URLs, bad lastmod</td><td>Sitemap errors</td></tr><tr><td>Organic drop coinciding with a release</td><td>Redirect change, canonical change, template regression</td><td>Post-release decline</td></tr><tr><td>One section lost rankings, rest stable</td><td>Cannibalisation, indexation in that cluster, template change</td><td>Section-specific decline</td></tr><tr><td>Slow pages, crawl rate declining</td><td>Origin latency, uncached parameters, cache miss</td><td>Slow fetch behaviour</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="symptom-crawl-traffic-collapses">Symptom: crawl traffic collapses<a href="https://docs.pruneseo.com/blog/seo-troubleshooting-guide#symptom-crawl-traffic-collapses" class="hash-link" aria-label="Direct link to Symptom: crawl traffic collapses" title="Direct link to Symptom: crawl traffic collapses" translate="no">​</a></h2>
<p>A step change of more than half in Googlebot fetches is almost never a ranking decision. It is a mechanical failure and it is diagnosable from logs in a few minutes.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Fetch volume and status mix, day by day, by user agent</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">awk '{print $4, $0}' access.log \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | grep -E 'Googlebot|bingbot' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | sed -E 's#^([0-9/]+).*"([A-Z]+) ([^ ]+).* ([0-9]{3}) .*#\1 \2 \4#' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | sort | awk '{key=$1" "$3; c[key]++} END {for (k in c) print k, c[k]}' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | sort</span><br></div></code></pre></div></div>
<p>Read the status column. If the fetches are still arriving and returning 403, your web application firewall is the cause — check whether a bot-protection rule went live with the release, and whether the rule tests for a user agent header that your proxy strips. A WAF that inspects the wrong end of a proxied request is a classic.</p>
<p>If fetches have dropped to nothing and the remaining ones return 200, check robots.txt and check origin availability. A <code>Disallow: /</code> deployed by mistake takes a site off the crawl map within a day and looks identical from the outside.</p>
<p>If fetches are arriving and returning 429 or 503, the origin is the cause and the correct fix is to reduce demand, not to raise the limit.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="symptom-one-template-is-entirely-unindexed">Symptom: one template is entirely unindexed<a href="https://docs.pruneseo.com/blog/seo-troubleshooting-guide#symptom-one-template-is-entirely-unindexed" class="hash-link" aria-label="Direct link to Symptom: one template is entirely unindexed" title="Direct link to Symptom: one template is entirely unindexed" translate="no">​</a></h2>
<p>A whole template failing is far more common than a page failing, and it almost always has one of four causes.</p>
<table><thead><tr><th>Cause</th><th>How to confirm</th><th>Fix</th></tr></thead><tbody><tr><td><code>noindex</code> on the template</td><td>Fetch a sample URL and inspect the meta</td><td>Remove or scope correctly</td></tr><tr><td>Canonical points off-template</td><td>Fetch and read the canonical line</td><td>Point at self or at the intended parent</td></tr><tr><td>Content is client-rendered only</td><td>Fetch with JavaScript disabled</td><td>Server-render or pre-render</td></tr><tr><td>Cluster is internally duplicative</td><td>Compare titles, intros and body across a sample</td><td>Consolidate to one indexable candidate</td></tr></tbody></table>
<p>Sample at least 20 URLs from the pattern before concluding. A template of forty thousand URLs where you checked three will produce wrong answers at least twice a month, because a single misconfigured page in the sample looks identical to a template-wide fault.</p>
<p>The client-rendering case is the one that fools people most often. <code>curl</code> the URL, find the meta tags and the canonical, see them present, conclude the template is fine — when what the crawler received contained no article body at all. Check the response for actual text volume, not for tag presence.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="symptom-the-wrong-url-is-indexed">Symptom: the wrong URL is indexed<a href="https://docs.pruneseo.com/blog/seo-troubleshooting-guide#symptom-the-wrong-url-is-indexed" class="hash-link" aria-label="Direct link to Symptom: the wrong URL is indexed" title="Direct link to Symptom: the wrong URL is indexed" translate="no">​</a></h2>
<p>Canonical tags are routinely ignored for reasons that have nothing to do with whether they are correctly written.</p>
<table><thead><tr><th>Reason canonical is ignored</th><th>Diagnostic</th></tr></thead><tbody><tr><td>Render failure — tag never parsed</td><td>Fetch rendered HTML, not source</td></tr><tr><td>Conflicting signals — hreflang, sitemap, internal links disagree</td><td>Compare all three sources</td></tr><tr><td>Canonical points to a URL that redirects</td><td>Follow it; a redirecting canonical is ignored</td></tr><tr><td>Canonical points to a soft 404</td><td>Check the destination renders real content</td></tr><tr><td>Cluster is too weak to choose</td><td>Consolidate before canonicalising</td></tr><tr><td>robots.txt blocks the canonical target</td><td>Blocked URL cannot be canonicalised to</td></tr></tbody></table>
<p>The third of those is the most commonly missed and the easiest to check. If your canonical target 301s anywhere, every page pointing at it is being asked to canonicalise to a non-canonical URL, and the request is discarded.</p>
<p>And the last one links back to the earlier rule: a URL you want in the index must be crawlable. Canonicalising to a robots-blocked URL does nothing at all.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="symptom-sitemap-errors">Symptom: sitemap errors<a href="https://docs.pruneseo.com/blog/seo-troubleshooting-guide#symptom-sitemap-errors" class="hash-link" aria-label="Direct link to Symptom: sitemap errors" title="Direct link to Symptom: sitemap errors" translate="no">​</a></h2>
<p>Sitemap warnings look alarming and are usually cheap.</p>
<table><thead><tr><th>Reported issue</th><th>Meaning</th><th>Action</th></tr></thead><tbody><tr><td>Submitted URLs not found</td><td>404 or redirect</td><td>Remove; do not redirect sitemaps</td></tr><tr><td>URL blocked by robots.txt</td><td>Contradictory signals</td><td>Remove from sitemap</td></tr><tr><td>URL has a redirect</td><td>Sitemaps should list canonical URLs only</td><td>Replace with destination</td></tr><tr><td>URL marked noindex</td><td>Contradictory signals</td><td>Remove from sitemap</td></tr><tr><td>Sitemap is large</td><td>Not an error, but a signal</td><td>Split by pattern, not arbitrarily</td></tr><tr><td><code>lastmod</code> always changing</td><td>Trust destroyed</td><td>Emit real modification dates</td></tr></tbody></table>
<p>The two rows worth internalising are the second and the fourth. A URL that is blocked in robots.txt <em>and</em> listed in a sitemap is sending two contradictory instructions, and the general behaviour is to distrust both. The same applies to a <code>noindex</code> page listed in a sitemap. Both situations are usually invisible on small sites and represent tens of thousands of URLs on large ones.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="symptom-organic-decline-after-a-release">Symptom: organic decline after a release<a href="https://docs.pruneseo.com/blog/seo-troubleshooting-guide#symptom-organic-decline-after-a-release" class="hash-link" aria-label="Direct link to Symptom: organic decline after a release" title="Direct link to Symptom: organic decline after a release" translate="no">​</a></h2>
<p>If the decline began on a deployment date, treat it as a regression until proven otherwise. The check order:</p>
<ol>
<li class=""><strong>Redirects.</strong> Confirm that every retired URL still resolves. A dropped 301 becomes a 404 and a chain of 301s adds hops. Both are common migration regressions.</li>
<li class=""><strong>Canonicals.</strong> Check a sample across templates for a template-level tag that now points somewhere unintended.</li>
<li class=""><strong>Status codes.</strong> Any template now returning 302 where it returned 200, or a soft 404 introduced by a new layout.</li>
<li class=""><strong>Rendered output.</strong> A new client-side shell that drops content from the initial response.</li>
<li class=""><strong>Sitemap submission.</strong> A release that regenerated sitemaps with a different URL format invalidates the submitted set.</li>
</ol>
<p>The useful discipline is to diff the crawl, not to reason about it. Crawl the site before and after, compare by pattern, and the regressions are immediately visible as status or canonical changes on specific patterns. Diffing by pattern rather than by URL is what makes it tractable on a large site — the differences cluster into perhaps a dozen families.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="symptom-one-section-declines-the-rest-hold">Symptom: one section declines, the rest hold<a href="https://docs.pruneseo.com/blog/seo-troubleshooting-guide#symptom-one-section-declines-the-rest-hold" class="hash-link" aria-label="Direct link to Symptom: one section declines, the rest hold" title="Direct link to Symptom: one section declines, the rest hold" translate="no">​</a></h2>
<p>A localised drop is a strong diagnostic signal because the rest of the site is acting as a control group.</p>
<table><thead><tr><th>Check</th><th>What it rules in or out</th></tr></thead><tbody><tr><td>Indexation rate in that cluster</td><td>Nothing indexed, or indexed-but-not-visible</td></tr><tr><td>Cannibalisation</td><td>Several URLs ranking for the same query</td></tr><tr><td>Template change</td><td>Applies to that template only</td></tr><tr><td>Internal links lost</td><td>Section orphaned by a navigation change</td></tr><tr><td>External links lost</td><td>A migration, scraper or manual removal</td></tr><tr><td>Seasonal comparison</td><td>Not a fault at all</td></tr></tbody></table>
<p>Run the same question against the same cluster in Search Console for a wider date range. If the decline predates the current release by months, this is not an incident; it is a slow structural problem and it needs a pattern analysis rather than a rollback.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-reproducible-triage-sequence">A reproducible triage sequence<a href="https://docs.pruneseo.com/blog/seo-troubleshooting-guide#a-reproducible-triage-sequence" class="hash-link" aria-label="Direct link to A reproducible triage sequence" title="Direct link to A reproducible triage sequence" translate="no">​</a></h2>
<p>Whatever the symptom, the same four-step discipline resolves most cases:</p>
<ol>
<li class=""><strong>Establish a baseline.</strong> The state before the change, from logs and Search Console, not from memory.</li>
<li class=""><strong>Group by pattern.</strong> Every finding belongs to a URL family, not to an individual URL.</li>
<li class=""><strong>Sample and state the sample size.</strong> Every verdict carries the number of URLs inspected.</li>
<li class=""><strong>Change one thing, then wait long enough.</strong> Indexing changes take weeks. Changing five things means five weeks with no information about any of them.</li>
</ol>
<p>That last point is where most troubleshooting actually fails. Teams make a bundle of changes in a maintenance window, see no improvement in two weeks, conclude none of it worked, and revert everything including the parts that were working.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/seo-troubleshooting-guide#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Run the five checks at the top before you investigate anything else. Then, if the problem is not an incident but a structural one, go and get a pattern-level view of the site rather than continuing to work symptom by symptom.</p>
<p>PruneSEO is built for the second half of that sentence. <a class="" href="https://docs.pruneseo.com/docs/guides/troubleshooting">Troubleshooting</a> covers the in-product version of the checks above, <a class="" href="https://docs.pruneseo.com/docs/guides/running-and-monitoring-crawls">running and monitoring crawls</a> covers crawl execution and status breakdowns, and <a class="" href="https://docs.pruneseo.com/docs/features/triage-and-verdict-classification">triage and verdict classification</a> is where a sampled inspection result becomes a defensible verdict with a stated confidence. When you find the pattern, <a class="" href="https://docs.pruneseo.com/docs/features/remediation-campaigns">remediation campaigns</a> is how the change gets tracked to completion rather than forgotten in a ticket.</p>]]></content:encoded>
            <category>Product guides</category>
            <category>Technical SEO</category>
        </item>
        <item>
            <title><![CDATA[Where SEO And Accessibility Actually Overlap]]></title>
            <link>https://docs.pruneseo.com/blog/seo-accessibility-overlap</link>
            <guid>https://docs.pruneseo.com/blog/seo-accessibility-overlap</guid>
            <pubDate>Fri, 18 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A technical account of the real overlap between SEO and accessibility, where the two disciplines conflict, and the parts of accessibility that have no search effect at all.]]></description>
            <content:encoded><![CDATA[<p>SEO and accessibility share more than is usually admitted, and less than the overlap content suggests. The genuine shared surface is document structure — headings, link text, semantic containers, alternative text — because the same parse serves both a screen reader and a crawler, and because the accessibility community spent forty years arguing for semantic HTML that search engines have since adopted as ranking input. The overlap is real there and essentially absent in most of the rest: contrast ratios, ARIA attributes and tap targets have no measurable search consequence whatsoever. Confusing the two produces bad work in both disciplines, so it is worth drawing the line precisely.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-shared-surface-honestly">The shared surface, honestly<a href="https://docs.pruneseo.com/blog/seo-accessibility-overlap#the-shared-surface-honestly" class="hash-link" aria-label="Direct link to The shared surface, honestly" title="Direct link to The shared surface, honestly" translate="no">​</a></h2>
<table><thead><tr><th>Technique</th><th>Accessibility requirement</th><th>Search consequence</th><th>Strength of overlap</th></tr></thead><tbody><tr><td>Heading hierarchy</td><td>Navigable document outline</td><td>Topical structure, extraction anchors</td><td>Strong</td></tr><tr><td>Descriptive link text</td><td>Screen-reader context out of context</td><td>Anchor relevance signal</td><td>Strong</td></tr><tr><td>Semantic containers</td><td>Landmark regions</td><td>Element understanding, parsing</td><td>Strong</td></tr><tr><td>Alt text</td><td>Equivalent content for images</td><td>Image understanding, rich results eligibility</td><td>Strong</td></tr><tr><td>Table header cells</td><td>Data association for screen readers</td><td>Tabular data comprehension</td><td>Strong</td></tr><tr><td>Descriptive page titles</td><td>Page identification</td><td>The single strongest on-page signal</td><td>Strong</td></tr><tr><td>Form labels</td><td>Input identification</td><td>Form and field naming</td><td>Moderate</td></tr><tr><td>Language declaration</td><td>Pronunciation for screen readers</td><td>Language targeting, hreflang context</td><td>Moderate</td></tr><tr><td>Structured data</td><td>Machine-readable semantics</td><td>Rich result eligibility</td><td>Moderate</td></tr><tr><td>Heading order without skips</td><td>Outline integrity</td><td>Weak structural signal</td><td>Moderate</td></tr><tr><td>Contrast ratio</td><td>Visual legibility</td><td>None</td><td>None</td></tr><tr><td>ARIA roles and states</td><td>State announcement</td><td>None</td><td>None</td></tr><tr><td>Focus order and skip links</td><td>Keyboard navigation</td><td>None</td><td>None</td></tr><tr><td>Touch target sizing</td><td>Motor accessibility</td><td>None</td><td>None</td></tr><tr><td>Reduced motion preferences</td><td>Vestibular comfort</td><td>Marginal, via Core Web Vitals stability</td><td>Negligible</td></tr></tbody></table>
<p>Read that last five rows before the first nine. A surprising amount of published "SEO and accessibility" material is entirely about those five, and a surprising amount of the actual engineering value is in the first four.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-parse-is-the-shared-artefact">The parse is the shared artefact<a href="https://docs.pruneseo.com/blog/seo-accessibility-overlap#the-parse-is-the-shared-artefact" class="hash-link" aria-label="Direct link to The parse is the shared artefact" title="Direct link to The parse is the shared artefact" translate="no">​</a></h2>
<p>Both disciplines need to answer the same question: what is this page made of, and how is it arranged? A screen reader needs it to be answerable by an assistive technology reading linearly. A crawler needs it to be answerable by extracting entities and relationships. When a page answers it with headings and elements, both get what they need from the same markup.</p>
<p>When a page answers it with styling and JavaScript, both get nothing.</p>
<p>That is the whole argument, and it is why "make the HTML semantic" is simultaneously the correct accessibility advice and the correct SEO advice. It is not a compromise between the two. It is the single highest-leverage change available to most pages, and it is free.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="links-are-where-the-overlap-pays">Links are where the overlap pays<a href="https://docs.pruneseo.com/blog/seo-accessibility-overlap#links-are-where-the-overlap-pays" class="hash-link" aria-label="Direct link to Links are where the overlap pays" title="Direct link to Links are where the overlap pays" translate="no">​</a></h2>
<p>Link text is the clearest case. Two pages with identical anchor text everywhere have an accessibility defect and an SEO defect, and they are the same defect.</p>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- Both audiences lose. No context out of context. --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">Read </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">a</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">href</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">/specs/zinc-plating</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">more</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">a</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"> for details.</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">See </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">a</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">href</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">/specs/zinc-plating</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">our guide</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">a</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"> and</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">a</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">href</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">/specs/anodising</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">here</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">a</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">.</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- Descriptive, unique, and usable without surrounding context. --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">Read the </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">a</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">href</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">/specs/zinc-plating</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">zinc plating specification</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">a</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   for thickness tolerances.</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>On a large site this is a mechanical fix that applies across tens of thousands of pages at once. Crawl the site, group anchor text values by target pattern, and the ones appearing on many internal links are the defects. It is the same crawl you run for indexation work, which is the practical reason to do both together rather than commissioning two separate audits.</p>
<p>There is a second link-related rule that people get wrong in both directions. A link whose text is identical across a cluster is a problem; a link whose text is <em>keyword-stuffed</em> to compensate is a worse one, and it is a pattern you can detect in the same crawl. If the anchor text for every product link is "buy PRODUCT online cheap", you have traded an accessibility problem for a spam problem.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="alt-text-filenames-and-structured-data-three-different-jobs">Alt text, filenames and structured data: three different jobs<a href="https://docs.pruneseo.com/blog/seo-accessibility-overlap#alt-text-filenames-and-structured-data-three-different-jobs" class="hash-link" aria-label="Direct link to Alt text, filenames and structured data: three different jobs" title="Direct link to Alt text, filenames and structured data: three different jobs" translate="no">​</a></h2>
<p>Image optimisation gets conflated constantly, so it is worth separating three mechanisms that are frequently presented as alternatives when they are complementary.</p>
<table><thead><tr><th>Mechanism</th><th>Purpose</th><th>Who reads it</th></tr></thead><tbody><tr><td>Filename and path</td><td>Identity at rest, in logs and image search</td><td>Crawlers and humans browsing a directory</td></tr><tr><td><code>alt</code> text</td><td>Equivalent for the image's content</td><td>Screen readers, and image understanding</td></tr><tr><td>Structured data</td><td>Typed relationship to the page</td><td>Rich results and entity resolution</td></tr></tbody></table>
<p>A useful discipline: the filename names the thing, the alt text describes what it conveys, and the structured data declares the relationship. A product page with <code>industrial-pipe-fittings.jpg</code> as the filename, "Four-way galvanised pipe fittings arranged on a workshop bench" as the alt text and a <code>Product</code> block with an <code>image</code> property is doing all three jobs correctly.</p>
<p>Where accessibility and search genuinely diverge is decorative imagery. A divider or a tracking pixel needs empty <code>alt=""</code>, which is the correct accessibility markup, and several teams "fix" this by removing the attribute entirely. That produces an image with a synthesised, meaningless description read aloud to a screen-reader user, and it is one of the most common accessibility regressions on commercial sites. For search it makes almost no difference. Leave it empty.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-the-two-disciplines-collide">Where the two disciplines collide<a href="https://docs.pruneseo.com/blog/seo-accessibility-overlap#where-the-two-disciplines-collide" class="hash-link" aria-label="Direct link to Where the two disciplines collide" title="Direct link to Where the two disciplines collide" translate="no">​</a></h2>
<p>The genuine conflict is <strong>client-side rendering</strong>, and it is worth being direct about it.</p>
<p>A single-page app is often the accessible solution — focus management, routing announcements, dynamic content updates announced politely to assistive technology — and it is also, in its default configuration, the worst possible architecture for a crawler that does not execute JavaScript reliably or promptly.</p>
<p>The resolution exists and is well established: server-render or pre-render the initial view, hydrate the rest. Both audiences get what they need. The teams that do not do this typically argue that accessibility and SEO are in conflict, which is almost never true. It is usually that one of the two requirements has not been specified.</p>
<p>The second collision is more mundane and more common on large sites: <strong>accessibility remediation adds pages</strong>. A separate accessible version of a PDF. An accessible text alternative for a complex chart. A dedicated help page explaining keyboard navigation. Each is a defensible accessibility artefact, each is a new URL, and together they can add a few thousand pages of near-duplicate content to a site that already has an indexation problem.</p>
<p>The right answer is to govern them the way you govern any other URL family: decide the canonical version, noindex the alternates, keep them out of the sitemap, and check them in the same pattern analysis as everything else. Adding them to the accessibility backlog and adding them to the indexation inventory is the same hour of work, done once.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-has-no-search-effect-at-all">What has no search effect at all<a href="https://docs.pruneseo.com/blog/seo-accessibility-overlap#what-has-no-search-effect-at-all" class="hash-link" aria-label="Direct link to What has no search effect at all" title="Direct link to What has no search effect at all" translate="no">​</a></h2>
<p>Worth stating without hedging, because these get bundled into SEO strategy decks:</p>
<ul>
<li class=""><strong>Colour contrast ratios.</strong> Meet 4.5:1. It has no measurable ranking effect.</li>
<li class=""><strong>ARIA roles, states and live regions.</strong> These describe behaviour to assistive technology. The visual DOM they annotate carries the search signal.</li>
<li class=""><strong>Focus order and visible focus indicators.</strong> Purely interaction.</li>
<li class=""><strong>Skip links.</strong> Useful for keyboard users, invisible to crawlers beyond being a link.</li>
<li class=""><strong>Touch target sizes.</strong> A mobile usability concern.</li>
<li class=""><strong>Reduced-motion preferences.</strong> Relevant to rendering stability, which touches Core Web Vitals indirectly and marginally.</li>
</ul>
<blockquote>
<p>There is no ranking benefit for being WCAG compliant, and there is no accessibility benefit for being well indexed. Treating them as one budget produces a report that talks to neither audience.</p>
</blockquote>
<p>The correct framing is that they share a root cause and mostly separate from there. Semantic, correctly structured HTML serves both. Everything downstream of that is largely a single-discipline concern, and conflating them makes it harder to get either one right because the success criteria become mushy.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="auditing-both-from-one-crawl">Auditing both from one crawl<a href="https://docs.pruneseo.com/blog/seo-accessibility-overlap#auditing-both-from-one-crawl" class="hash-link" aria-label="Direct link to Auditing both from one crawl" title="Direct link to Auditing both from one crawl" translate="no">​</a></h2>
<p>There is real efficiency in doing this together, and the method is straightforward. A single crawl that captures response status, canonical, heading outline, alt-text presence and coverage, anchor text distribution and link destination quality can produce both an accessibility findings report and an indexation report from the same data.</p>
<table><thead><tr><th>Finding class</th><th>Feeds</th><th>Remediation owner</th></tr></thead><tbody><tr><td>Missing or duplicated <code>h1</code></td><td>Accessibility and structure</td><td>Design system, not per-page SEO</td></tr><tr><td>Empty headings</td><td>Accessibility</td><td>Template fix</td></tr><tr><td>Link text reuse across patterns</td><td>Accessibility and search</td><td>One change, two wins</td></tr><tr><td>Missing alt on content images</td><td>Accessibility and image search</td><td>CMS or template</td></tr><tr><td>Missing alt on decorative images</td><td>Accessibility only</td><td>Template fix</td></tr><tr><td>Unlinked pages</td><td>Search only</td><td>Internal linking work</td></tr><tr><td>Duplicate metadata</td><td>Both</td><td>Template fix</td></tr></tbody></table>
<p>The last row is the one teams find most valuable. Template-level defects are worth fixing in the template, once, and they tend to surface in both reports simultaneously because both are symptoms of a page that was assembled without reference to its structure.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/seo-accessibility-overlap#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Pick a high-traffic template rather than a page. Audit its heading outline, its link text and its alt coverage once, fix it in the component, and you have improved the accessibility of every URL using that template and the structure of every URL using it too. That is the argument for treating the overlap as real rather than rhetorical.</p>
<p>PruneSEO's crawl captures the structural signals that both reports need. <a class="" href="https://docs.pruneseo.com/docs/guides/triage-and-pattern-analysis">Triage and pattern analysis</a> groups findings by pattern so a template defect becomes one finding rather than forty thousand, <a class="" href="https://docs.pruneseo.com/docs/guides/site-configuration">site configuration</a> covers which signals you choose to capture per run, and <a class="" href="https://docs.pruneseo.com/docs/features/indexation-waste-detection">indexation waste detection</a> is where the accessibility artefacts you have added get counted against the URLs you actually want indexed. For how the crawl is scoped before it starts, see <a class="" href="https://docs.pruneseo.com/docs/guides/site-configuration">site configuration</a>.</p>]]></content:encoded>
            <category>Technical SEO</category>
            <category>SEO audits</category>
        </item>
        <item>
            <title><![CDATA[Googlebot Behaviour And Crawling: How It Actually Decides]]></title>
            <link>https://docs.pruneseo.com/blog/googlebot-behaviour-and-crawling</link>
            <guid>https://docs.pruneseo.com/blog/googlebot-behaviour-and-crawling</guid>
            <pubDate>Thu, 17 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How Googlebot prioritises, crawls and re-crawls, how crawl budget is really allocated, and the precise semantics and limits of robots.txt rules on a large site.]]></description>
            <content:encoded><![CDATA[<p>"Googlebot behaviour" is usually discussed as if it were one thing: a bot that follows rules, reads pages and puts things in an index. It is better understood as a pipeline with at least two queues, a prioritisation function nobody can query, and a set of protocol behaviours that surprise people who assume it is a conventional web client. Understanding that pipeline explains most of the odd results on large sites — pages fetched but never rendered, rules that appear to be ignored, and crawlers that keep returning to URLs you stopped sending to them.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="there-are-two-queues-not-one">There are two queues, not one<a href="https://docs.pruneseo.com/blog/googlebot-behaviour-and-crawling#there-are-two-queues-not-one" class="hash-link" aria-label="Direct link to There are two queues, not one" title="Direct link to There are two queues, not one" translate="no">​</a></h2>
<p>The most useful structural fact is that crawling and rendering are separate processes with separate backlogs.</p>
<p>A URL enters the <strong>crawl queue</strong> when it is discovered through links, sitemaps or prior knowledge. It is fetched. The response is parsed, links are extracted, and new URLs enter the crawl queue. If the HTML does not contain the page content, the URL is added to the <strong>render queue</strong>, where a separate service executes JavaScript and produces a rendered version.</p>
<table><thead><tr><th>Queue</th><th>What it holds</th><th>The failure it causes</th></tr></thead><tbody><tr><td>Crawl queue</td><td>URLs awaiting fetch</td><td>Pages discovered but never fetched</td></tr><tr><td>Render queue</td><td>Fetched URLs awaiting JS execution</td><td>Pages fetched but effectively empty when judged</td></tr></tbody></table>
<p>This explains a result that otherwise looks absurd: a page with perfect server logs, a healthy 200 response, correct content in the DOM after hydration, and no presence in the index. The crawl succeeded. The render queue is backed up — commonly on large JavaScript-heavy sites where the render queue is far deeper than the crawl queue — and the URL sits there.</p>
<p>It also explains why fixing render failures and fixing crawl waste are different projects with different owners. Crawl waste is a templates and robots problem. Render waste is a rendering architecture problem.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="crawl-budget-is-a-tolerance-not-an-allocation">Crawl budget is a tolerance, not an allocation<a href="https://docs.pruneseo.com/blog/googlebot-behaviour-and-crawling#crawl-budget-is-a-tolerance-not-an-allocation" class="hash-link" aria-label="Direct link to Crawl budget is a tolerance, not an allocation" title="Direct link to Crawl budget is a tolerance, not an allocation" translate="no">​</a></h2>
<p>The phrase "crawl budget" implies a fixed number of fetches per day, granted and revocable. That is a convenient mental model and it is not what is actually happening. What exists is a <strong>per-site tolerance</strong> that Google adjusts continuously based on how your site responds to being crawled and how much the pages it fetches appear to be worth.</p>
<p>Practical consequences:</p>
<ul>
<li class="">There is no setting and no report showing your number.</li>
<li class="">Tolerance is mostly a function of <em>fetch cost</em>, not page count. A site where the crawler fetches eight hundred thousand URLs a month and a site fetching eight million can sit at similar tolerances if the first one is fast and clean and the second is slow and dirty.</li>
<li class="">The fastest way to lose tolerance is to make the crawler fetch things that return nothing. A 404 from a crawler is a wasted fetch, and the accounting is unforgiving.</li>
<li class="">Adding pages does not lower tolerance by itself. Adding <em>bad</em> pages lowers it, because the crawler learns the site is not worth much attention.</li>
</ul>
<blockquote>
<p>Crawl budget is not the number of URLs you have. It is the rate at which Googlebot is willing to look, and you change that rate by changing what it finds when it looks.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="estimating-your-own">Estimating your own<a href="https://docs.pruneseo.com/blog/googlebot-behaviour-and-crawling#estimating-your-own" class="hash-link" aria-label="Direct link to Estimating your own" title="Direct link to Estimating your own" translate="no">​</a></h2>
<p>Since the number is not published, you estimate it from logs. The method:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Total daily Googlebot fetches over a fortnight</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">grep -c 'Googlebot' access.log.1 access.log</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Status breakdown — wasted fetches are the signal</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">grep 'Googlebot' access.log \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | awk '{print $9}' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | sort | uniq -c | sort -rn</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Which patterns absorb the fetches?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">grep 'Googlebot' access.log \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | grep -oE '"GET /[^ ?"]+' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | sed -E 's#"GET (/[a-z0-9_-]+).*#\1#' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | sort | uniq -c | sort -rn | head -20</span><br></div></code></pre></div></div>
<p>The status breakdown is the most important output and it is the one most teams never look at. A healthy large site might see 95% of Googlebot fetches returning 200. A site with a URL-space problem frequently sees 15 to 30% returning 404 or 410 from a facet that generates combinations which do not exist — and every one of those is a fetch that produced nothing.</p>
<p>Compare two weekly totals and you have your crawl rate. Compare that against the size of your intended indexable surface and you have the ratio worth putting in front of a stakeholder:</p>
<table><thead><tr><th>Metric</th><th>Site A</th><th>Site B</th></tr></thead><tbody><tr><td>Weekly Googlebot fetches</td><td>180,000</td><td>410,000</td></tr><tr><td>Fetch-to-200 ratio</td><td>97%</td><td>71%</td></tr><tr><td>Addressable surface</td><td>240,000</td><td>1,600,000</td></tr><tr><td>Weekly fetches per indexable URL</td><td>0.75</td><td>0.26</td></tr></tbody></table>
<p>Site B fetches more than twice as much and gets less than half the coverage. It is not being throttled. It is spending its tolerance on 29% of requests that return nothing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="crawl-traps-ranked-by-cost">Crawl traps, ranked by cost<a href="https://docs.pruneseo.com/blog/googlebot-behaviour-and-crawling#crawl-traps-ranked-by-cost" class="hash-link" aria-label="Direct link to Crawl traps, ranked by cost" title="Direct link to Crawl traps, ranked by cost" translate="no">​</a></h2>
<table><thead><tr><th>Trap</th><th>Mechanism</th><th>Fix</th></tr></thead><tbody><tr><td>Facet combinations</td><td>Unbounded parameter space</td><td>Disallow parameters; canonical to collection</td></tr><tr><td>Internal search results</td><td>Effectively infinite URL space</td><td>Disallow; noindex template; exclude from sitemap</td></tr><tr><td>Session and tracking parameters</td><td>Amplifies every URL N times</td><td>Strip before serving; disallow pattern</td></tr><tr><td>Calendar and date archives</td><td>Unbounded forward space</td><td>Limit to bounded window</td></tr><tr><td>Paginated views</td><td>Multiplies every collection</td><td>Disallow beyond a floor; keep crawlable links</td></tr><tr><td>Infinite scroll fallback</td><td>Session-specific infinite space</td><td>Canonical, robots, or remove</td></tr><tr><td>Sitemap recursion or a dynamic feed</td><td>Unbounded discovery</td><td>Filter to bounded set</td></tr><tr><td>Broken relative links</td><td>Unbounded 404 space</td><td>Fix links</td></tr></tbody></table>
<p>Date archives are the one that surprises people. A URL pattern containing <code>/archive/2026/12/day-31/</code> does not have a bound, because you will publish that URL again. With one archive link per page you have an unbounded discovery chain that a crawler will happily walk, indefinitely, producing mostly-empty pages.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-precise-semantics-of-robotstxt">The precise semantics of robots.txt<a href="https://docs.pruneseo.com/blog/googlebot-behaviour-and-crawling#the-precise-semantics-of-robotstxt" class="hash-link" aria-label="Direct link to The precise semantics of robots.txt" title="Direct link to The precise semantics of robots.txt" translate="no">​</a></h2>
<p>robots.txt is more often misapplied than any other file on a large site. The rules are exact and the precedence order matters.</p>
<table><thead><tr><th>Directive</th><th>Behaviour</th><th>Common mistake</th></tr></thead><tbody><tr><td><code>Disallow:</code></td><td>Blocks matching URLs from crawling</td><td>Assuming it de-indexes</td></tr><tr><td><code>Allow:</code></td><td>Overrides a broader Disallow</td><td>Assuming first-match wins</td></tr><tr><td><code>Sitemap:</code></td><td>Declares sitemap locations</td><td>Assuming it boosts crawling</td></tr><tr><td><code>$</code></td><td>End-of-URL anchor</td><td>Using <code>$</code> and breaking the prefix match</td></tr><tr><td><code>*</code></td><td>Any sequence of characters</td><td>Using <code>*</code> without anchoring and over-blocking</td></tr><tr><td><code>Host</code></td><td>Consolidates equivalent hosts</td><td>Assuming it is widely honoured</td></tr></tbody></table>
<div class="language-txt codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-txt codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">User-agent: *</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /*?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /search/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /cart/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /account/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /*?sort=</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /*?page=</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Refine: allow a parameterised family you DO want crawled.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Longest-match-wins, not first-match-wins.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Allow: /*?collection=</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Allow: /products/industrial/pipes/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Sitemap: https://example.com/sitemap-index.xml</span><br></div></code></pre></div></div>
<p>Two things about that file that are usually misunderstood. First, the more specific rule wins, not the first one matched — <code>Allow: /products/industrial/pipes/</code> will be crawled even though a broad <code>Disallow: /*?</code> would otherwise block it. Second, <code>$</code> anchors to the end of the URL <em>including</em> the query, so <code>Disallow: /*?$</code> behaves very differently from <code>Disallow: /*?</code>. Almost every over-blocking incident traces back to a missing anchor or an unanchored <code>*</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-robotstxt-cannot-do">What robots.txt cannot do<a href="https://docs.pruneseo.com/blog/googlebot-behaviour-and-crawling#what-robotstxt-cannot-do" class="hash-link" aria-label="Direct link to What robots.txt cannot do" title="Direct link to What robots.txt cannot do" translate="no">​</a></h2>
<p>This is the single most consequential misconception in the file, and it has its own category of folklore around it. Three things:</p>
<ol>
<li class=""><strong>It cannot remove anything from the index.</strong> A blocked URL that is already indexed stays indexed. It can simply be dropped at crawl time, and it may reappear served from the index without the crawler ever seeing it again — which looks like the rule "not working" and is not.</li>
<li class=""><strong>It cannot be used for de-indexing.</strong> That is <code>noindex</code>, which the crawler must fetch in order to see. The combination is contradictory: a robots.txt block prevents the fetch that would deliver the noindex.</li>
<li class=""><strong>Googlebot does not support <code>Crawl-delay</code>.</strong> It is ignored. Any number in a production robots.txt is documentation, not control.</li>
</ol>
<p>If you take one sentence away, take this one:</p>
<blockquote>
<p>A URL you want out of the index must be <strong>crawlable</strong>. A URL you want out of your crawl budget must be <strong>blocked</strong>. Those are different requests and they need different files.</p>
</blockquote>
<p>Which is why the correct sequence for retiring a large pattern is: block in robots.txt to stop further fetches, mark the template <code>noindex</code> so already-crawled pages can be seen and dropped, and remove it from the sitemap — all three, because any one alone leaves residue.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="mobile-first-and-the-user-agent-line">Mobile-first and the user-agent line<a href="https://docs.pruneseo.com/blog/googlebot-behaviour-and-crawling#mobile-first-and-the-user-agent-line" class="hash-link" aria-label="Direct link to Mobile-first and the user-agent line" title="Direct link to Mobile-first and the user-agent line" translate="no">​</a></h2>
<p>The <code>User-agent</code> line is a targeting mechanism for rules, not a signal about rendering. There is no supported way to ask Googlebot to render the desktop version, and the mobile-first indexing model means the mobile version of your site is the one that gets indexed and ranked.</p>
<p>The practical consequences for a large site are mundane and important: if your mobile template drops the canonical tag, if it strips structured data, or if it renders content client-side only, then indexing is being judged on the version that is missing those things. Auditing the rendered mobile output is not optional.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="server-behaviour-the-crawler-cannot-ignore">Server behaviour the crawler cannot ignore<a href="https://docs.pruneseo.com/blog/googlebot-behaviour-and-crawling#server-behaviour-the-crawler-cannot-ignore" class="hash-link" aria-label="Direct link to Server behaviour the crawler cannot ignore" title="Direct link to Server behaviour the crawler cannot ignore" translate="no">​</a></h2>
<table><thead><tr><th>Condition</th><th>What the crawler experiences</th><th>Result</th></tr></thead><tbody><tr><td>5xx above roughly 5% of fetches</td><td>Persistent failure</td><td>Crawl rate drops sharply</td></tr><tr><td>p95 response above roughly 500ms</td><td>Slow, expensive fetches</td><td>Fewer fetches per day</td></tr><tr><td>429 or 503 with <code>Retry-After</code></td><td>Politely refused</td><td>Honours the backoff, extends to all users</td></tr><tr><td>Soft 404 — 200 with no content</td><td>Success, no value</td><td>Weak signal, wasted fetch</td></tr><tr><td>Aggressive bot blocking by WAF</td><td>Looks like a crawl error</td><td>Crawl rate drops to near zero</td></tr></tbody></table>
<p>The last row causes real incidents. Security teams regularly block anything on a bot-blocking list, and a misconfigured rule that returns 403 to Googlebot looks exactly like a server outage. If crawl traffic suddenly falls by 80% and nothing on the server changed, check the WAF before you check anything else.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/googlebot-behaviour-and-crawling#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Pull two weeks of logs and produce three numbers: total Googlebot fetches, the fetch-to-200 ratio, and the top five patterns by fetch volume. That is usually enough to identify the trap, and the trap is usually one of the eight in the table above.</p>
<p>PruneSEO measures the same things without the log spelunking. <a class="" href="https://docs.pruneseo.com/docs/guides/running-and-monitoring-crawls">Running and monitoring crawls</a> covers crawl execution and status breakdown, <a class="" href="https://docs.pruneseo.com/docs/features/crawl-budgets-and-allowances">crawl budgets and allowances</a> covers how fetch volume is distributed across patterns and what a removal is expected to release, and <a class="" href="https://docs.pruneseo.com/docs/features/indexation-waste-detection">indexation waste detection</a> is where waste per pattern is quantified against what the site intended to be crawled. If you want the practical consequences worked through in detail, <a class="" href="https://docs.pruneseo.com/docs/guides/triage-and-pattern-analysis">triage and pattern analysis</a> is the next step.</p>]]></content:encoded>
            <category>Crawl budget</category>
            <category>Technical SEO</category>
        </item>
        <item>
            <title><![CDATA[Indexing Delay Explained: Why Pages Take So Long To Appear]]></title>
            <link>https://docs.pruneseo.com/blog/indexing-delay-explained</link>
            <guid>https://docs.pruneseo.com/blog/indexing-delay-explained</guid>
            <pubDate>Wed, 16 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Why new and changed pages take days to months to reach Google's index, how to measure your own crawl-to-index delay from server logs, and which supposed fixes do nothing.]]></description>
            <content:encoded><![CDATA[<p>"How long does it take to get indexed?" is the question that generates the most confident and least useful answers in SEO. The stock answer — a few days to a few weeks — is an average over sites most of which are small, and it is roughly useless for the case people actually ask it about, which is a page on a large site where the crawler is working through a backlog of several hundred thousand URLs. The honest answer is that indexing delay is a queueing problem, and your site's queue depth is a measurable property of your site that you can read directly from your logs.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="three-different-delays-wearing-one-name">Three different delays wearing one name<a href="https://docs.pruneseo.com/blog/indexing-delay-explained#three-different-delays-wearing-one-name" class="hash-link" aria-label="Direct link to Three different delays wearing one name" title="Direct link to Three different delays wearing one name" translate="no">​</a></h2>
<p>The word "delay" collapses at least three separate things, and confusing them produces most of the wrong advice.</p>
<table><thead><tr><th>Delay</th><th>What it is</th><th>Measured by</th><th>Typically</th></tr></thead><tbody><tr><td><strong>Crawl delay</strong></td><td>Time from URL discovery to first fetch</td><td>Server logs: discovery event to first hit</td><td>Hours to months</td></tr><tr><td><strong>Indexing delay</strong></td><td>Time from a successful fetch to index inclusion</td><td>Fetch timestamp to Search Console "indexed"</td><td>Days to weeks</td></tr><tr><td><strong>Ranking delay</strong></td><td>Time from index inclusion to competitive position</td><td>Search Console position history</td><td>Days to months</td></tr></tbody></table>
<p>On a large site with an indexation problem, the first one dominates and the other two are noise. A page published yesterday is not "not indexed yet" — it is most likely "not crawled yet", and no amount of index-related tuning will change that.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="crawl-delay-is-the-one-that-matters-at-scale">Crawl delay is the one that matters at scale<a href="https://docs.pruneseo.com/blog/indexing-delay-explained#crawl-delay-is-the-one-that-matters-at-scale" class="hash-link" aria-label="Direct link to Crawl delay is the one that matters at scale" title="Direct link to Crawl delay is the one that matters at scale" translate="no">​</a></h2>
<p>A crawler has a finite number of fetches and a prioritisation function. For a small site, the function never binds: everything is fetched within a day or two. For a site with 900,000 addressable URLs, the function binds constantly, and your new page is competing against a queue that may already contain hundreds of thousands of stale entries.</p>
<p>The queue is populated by whatever the crawler has been told to keep looking for. Internal links, sitemaps, and previously seen URLs all feed it. So the delay on any individual new page is a function of how much noise the site presents:</p>
<table><thead><tr><th>Source of crawl demand</th><th>Relative volume</th><th>Consequence for a new page</th></tr></thead><tbody><tr><td>Facet and filter combinations</td><td>Very high</td><td>Sits at the back of the queue indefinitely</td></tr><tr><td>Internal search results</td><td>Very high</td><td>Rarely fetched at all</td></tr><tr><td>Paginated archives</td><td>High</td><td>Slow, and low value</td></tr><tr><td>Blog and news index</td><td>Medium</td><td>Fetched within days</td></tr><tr><td>Product detail pages</td><td>Medium</td><td>Fetched, subject to server latency</td></tr><tr><td>Sitemap-only discovery</td><td>Low per URL</td><td>Fetched only if the sitemap is small</td></tr></tbody></table>
<p>The last row is the counter-intuitive one. <strong>A smaller sitemap gets crawled more thoroughly than a large one.</strong> Google treats sitemaps as a discovery hint and applies its own prioritisation on top, so an 8,000-URL sitemap of pages you actually want indexed is a stronger signal than a 400,000-URL sitemap of everything the CMS emits.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="measuring-your-own-delay-from-logs">Measuring your own delay from logs<a href="https://docs.pruneseo.com/blog/indexing-delay-explained#measuring-your-own-delay-from-logs" class="hash-link" aria-label="Direct link to Measuring your own delay from logs" title="Direct link to Measuring your own delay from logs" translate="no">​</a></h2>
<p>You do not need to guess. Server logs will tell you the crawl delay precisely, per pattern.</p>
<div class="language-nginx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-nginx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Sample access log format that makes this analysis possible</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">log_format seo_log '$remote_addr - $remote_user [$time_local] '</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                   '"$request" $status $body_bytes_sent '</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                   '"$http_referer" "$http_user_agent" '</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                   '"$remote_addr" $request_time';</span><br></div></code></pre></div></div>
<p>The two fields that matter for this analysis are the timestamp and the user agent. Split the log by crawler and look at what arrives:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Daily Googlebot fetch volume, by status</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">grep 'Googlebot' access.log \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | awk '{print $4, $9}' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | sort | uniq -c | sort -rn | head -40</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Fetch latency distribution — slow responses starve crawl</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">grep 'Googlebot' access.log \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | awk -F'"' '{print $1}' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | awk '{print $NF}' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | sort -n \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | awk '{a[NR]=$1} END {print "p50", a[int(NR*0.5)];</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        print "p95", a[int(NR*0.95)];</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        print "max", a[NR]}'</span><br></div></code></pre></div></div>
<p>The three numbers that come out of that second command matter more than most sites realise. <strong>p95 response time above roughly 500ms for Googlebot degrades crawl rate measurably.</strong> On a large catalogue where uncached facet URLs hit the origin and render a full page, p95 is routinely above two seconds. The crawler does not complain, does not send you a message, and simply fetches less of your site per day.</p>
<p>Now add pattern breakdown, which is where the delay becomes actionable:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Which URL patterns are actually being fetched?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">grep 'Googlebot' access.log \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | sed -E 's#.*"(GET|HEAD) ([^ ?"]+).*#\2#' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | sed -E 's#^(/[a-z0-9_-]+/[a-z0-9_-]+).*#\1#' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | sort | uniq -c | sort -rn | head -30</span><br></div></code></pre></div></div>
<p>Run that on a large site's logs and the shape of the problem is usually unmistakable: the top three lines are search results, filters and pagination, and your new article is nowhere near the top. That is not an indexing bug. It is a priority problem, and priority problems are solved by removing demand, not by submitting more.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-indexing-delay-itself-happens">Why indexing delay itself happens<a href="https://docs.pruneseo.com/blog/indexing-delay-explained#why-indexing-delay-itself-happens" class="hash-link" aria-label="Direct link to Why indexing delay itself happens" title="Direct link to Why indexing delay itself happens" translate="no">​</a></h2>
<p>Once a page <em>has</em> been fetched, a further delay applies before index inclusion. The common causes:</p>
<table><thead><tr><th>Cause</th><th>Signal</th><th>Difficulty to fix</th></tr></thead><tbody><tr><td>Near-duplicate cluster</td><td>Fetched repeatedly, never included</td><td>Medium — needs consolidation</td></tr><tr><td>Thin content on a competitive template</td><td>Included then dropped, or never included</td><td>Medium</td></tr><tr><td>Render failure — content behind JS</td><td>Fetched, no content extracted</td><td>High, needs SSR or pre-render</td></tr><tr><td>Weak internal support</td><td>Fetched once, not re-fetched</td><td>Low — add links</td></tr><tr><td>Sitemap not submitted or stale</td><td>Absent from the sitemap index</td><td>Low</td></tr><tr><td>Canonical points elsewhere</td><td>Included as the other URL</td><td>Low, once decided</td></tr><tr><td>Server errors on re-crawl</td><td>5xx in logs around the fetch</td><td>Depends on the error</td></tr></tbody></table>
<p>The render failure deserves emphasis because it is invisible in every tool you own. A page can return 200, contain its content only after JavaScript executes, and never enter the index. Search Console will report it generically. The server log will show it as a successful fetch, because from the server's point of view it <em>was</em> one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-fixes-that-do-nothing">The fixes that do nothing<a href="https://docs.pruneseo.com/blog/indexing-delay-explained#the-fixes-that-do-nothing" class="hash-link" aria-label="Direct link to The fixes that do nothing" title="Direct link to The fixes that do nothing" translate="no">​</a></h2>
<p>Worth stating plainly, because each is widely recommended:</p>
<ul>
<li class=""><strong>Submitting the URL to Search Console.</strong> Useful for a small number of URLs and for the diagnostic data it returns. It does not change crawl priority and does not shorten queueing.</li>
<li class=""><strong>Adding the URL to a 400,000-URL sitemap.</strong> Adding one URL to a large sitemap changes essentially nothing. Removing 380,000 does.</li>
<li class=""><strong>Ping services and "index now" third-party tools.</strong> At best neutral, and frequently a spam signal.</li>
<li class=""><strong>Changing <code>lastmod</code> frequently.</strong> A <code>lastmod</code> that changes on every request trains the crawler to distrust it. Accurate <code>lastmod</code> is valuable; a constantly-firing one is worse than none.</li>
<li class=""><strong>Internal links from every page.</strong> A sitewide navigation block that changes daily guarantees re-fetching of the whole navigation graph and nothing else.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-micro-sitemap-honestly-assessed">The micro-sitemap, honestly assessed<a href="https://docs.pruneseo.com/blog/indexing-delay-explained#the-micro-sitemap-honestly-assessed" class="hash-link" aria-label="Direct link to The micro-sitemap, honestly assessed" title="Direct link to The micro-sitemap, honestly assessed" translate="no">​</a></h2>
<p>The micro-sitemap technique is the one intervention with a real mechanism, so it deserves an honest description rather than a dismissal or an endorsement.</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token prolog" style="color:#999988;font-style:italic">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">urlset</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">xmlns</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">http://www.sitemaps.org/schemas/sitemap/0.9</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">url</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">loc</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">https://example.com/new-article/</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">loc</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">lastmod</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">2026-12-08</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">lastmod</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">url</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">url</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">loc</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">https://example.com/new-article/amp/</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">loc</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">lastmod</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">2026-12-08</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">lastmod</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">url</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">urlset</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>What it does: gives the crawler a small, high-priority set of specific URLs, which is a different input from a large sitemap or a link on an unlinked page. Combined with the Sitemaps API, you can submit and then remove it, which prompts re-evaluation.</p>
<p>What it does not do: compel an index decision, guarantee anything, or scale. It is a two-URL tool. Applying it to a site with 120,000 URLs to index is a waste of the mechanism, and some teams end up submitting a 50,000-URL "micro"-sitemap, at which point it is just a sitemap with a different name.</p>
<p>The one thing worth saying firmly: <strong>every URL in one of these ought to be a URL you would be content to see indexed</strong>, because submitting it is a statement of intent rather than a neutral technical act.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-remedy-that-actually-works">The remedy that actually works<a href="https://docs.pruneseo.com/blog/indexing-delay-explained#the-remedy-that-actually-works" class="hash-link" aria-label="Direct link to The remedy that actually works" title="Direct link to The remedy that actually works" translate="no">​</a></h2>
<p>The remedy is to reduce the queue. In order of impact on a large site:</p>
<ol>
<li class=""><strong>Remove demand at the source.</strong> Block internal search results, facets you do not want indexed, and deep pagination in robots.txt. Demand that never forms a fetch never occupies queue space.</li>
<li class=""><strong>Shrink the sitemap</strong> to the surface you actually want indexed. This is the single highest-leverage sitemap action and it is counterintuitive.</li>
<li class=""><strong>Fix response times</strong> on uncached and parameterised URLs. p95 is the number to watch.</li>
<li class=""><strong>Consolidate duplicates</strong> so a cluster returns one indexable candidate rather than forty.</li>
<li class=""><strong>Only then</strong> use micro-sitemaps for genuinely new, high-value URLs.</li>
</ol>
<p>Steps one and two are measured in crawl budget released. That is why <a class="" href="https://docs.pruneseo.com/docs/features/crawl-budgets-and-allowances">crawl budgets and allowances</a> is the first thing to look at when you are wondering why new pages are slow.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/indexing-delay-explained#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Run the log analysis above for a single week and produce one table: patterns fetched, share of total fetches, p50 and p95 response time. That table is the honest version of "why is my site slow to index", and it will usually identify the answer in the first ten lines.</p>
<p>PruneSEO does the pattern half of this for you. <a class="" href="https://docs.pruneseo.com/docs/guides/running-and-monitoring-crawls">Running and monitoring crawls</a> covers crawl execution and scheduling, <a class="" href="https://docs.pruneseo.com/docs/guides/setup-and-search-console">setup and Search Console</a> covers property connection and inspection quota, and <a class="" href="https://docs.pruneseo.com/docs/guides/triage-and-pattern-analysis">triage and pattern analysis</a> is where the fetched-versus-valuable comparison turns into a ranked list of patterns to fix. Start with the pattern list, because the queue is made of your own URLs.</p>]]></content:encoded>
            <category>Indexation</category>
            <category>Search Console</category>
        </item>
        <item>
            <title><![CDATA[SEO Attribution And Analytics On Large Sites]]></title>
            <link>https://docs.pruneseo.com/blog/seo-attribution-analytics</link>
            <guid>https://docs.pruneseo.com/blog/seo-attribution-analytics</guid>
            <pubDate>Tue, 15 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How to attribute organic revenue honestly on a large site, including seasonality adjustment, brand segmentation, bot filtering and the pre-post comparison trap.]]></description>
            <content:encoded><![CDATA[<p>Attribution is where most SEO reporting quietly stops being credible. Not because the numbers are obviously wrong, but because they are reported without the caveats that make them mean anything — a year-on-year percentage over a period when the site was also migrating, a "revenue from organic" figure that includes three unrelated brands, a comparison of a pruned site against its own pre-pruning peak. None of those are lies. All of them are useless, and worse, all of them are repeatable, which is how a team ends up making three years of decisions on a broken measurement.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-organic-is-harder-to-attribute-than-paid">Why organic is harder to attribute than paid<a href="https://docs.pruneseo.com/blog/seo-attribution-analytics#why-organic-is-harder-to-attribute-than-paid" class="hash-link" aria-label="Direct link to Why organic is harder to attribute than paid" title="Direct link to Why organic is harder to attribute than paid" translate="no">​</a></h2>
<p>Paid attribution is hard because of incrementality: you spend money and traffic moves, but how much would have happened anyway? Organic attribution is hard for the opposite reason — the input is not controlled, so there is no natural experiment to point at.</p>
<p>The structural problem is that organic revenue is a <strong>residual</strong>. It is what remains after direct, referral, paid, email and brand behaviour have taken their share of a total that moved for reasons unrelated to any of them. When total conversions drop, organic revenue "drops" proportionally even if organic sessions and conversion rate both held steady. That artefact alone accounts for a large share of the volatility in most organic revenue dashboards.</p>
<table><thead><tr><th>Measurement choice</th><th>What it under-counts</th><th>Direction of error</th></tr></thead><tbody><tr><td>Last non-direct click</td><td>Dark social, app, newsletter, offline</td><td>Understates the channel that seeded the click</td></tr><tr><td>Last click</td><td>Everything above</td><td>Overstates closing channels</td></tr><tr><td>Branded organic separated</td><td>Unbranded discovery that returned to brand search</td><td>Understates SEO's contribution</td></tr><tr><td>Branded and non-branded merged</td><td>Brand strength leaking into "SEO performance"</td><td>Overstates SEO, badly</td></tr><tr><td>Analytics-session based</td><td>Cross-device journeys, offline conversions</td><td>Understates on considered purchases</td></tr><tr><td>Server-log based</td><td>Anything that does not arrive as a page view</td><td>Overstates engagement</td></tr></tbody></table>
<p>No single method is correct. What matters is that you pick one, state it, hold it steady, and stop comparing numbers that were produced by different methods.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-four-problems-that-produce-wrong-numbers">The four problems that produce wrong numbers<a href="https://docs.pruneseo.com/blog/seo-attribution-analytics#the-four-problems-that-produce-wrong-numbers" class="hash-link" aria-label="Direct link to The four problems that produce wrong numbers" title="Direct link to The four problems that produce wrong numbers" translate="no">​</a></h2>
<p><strong>Bot and AI crawler sessions.</strong> Analytics tools filter bots with a user-agent blacklist that is several years out of date. Newly identified machine crawlers get logged as sessions, and on a site with a large crawlable surface that can be a meaningful share of recorded traffic. This inflates page views and deflates conversion rates across the whole dataset.</p>
<p><strong>Sampling.</strong> At higher traffic volumes analytics switches to statistical sampling, typically by session. The error is small in aggregate and non-trivial for any specific landing page, which is exactly where SEO reports draw conclusions.</p>
<p><strong>Consent mode and modelled data.</strong> Under consent regimes, analytics reconstructs some conversions. The reconstruction is good work and it is not observation. Segment performance figures by consent state before comparing segments against each other.</p>
<p><strong>Last-modelled channels.</strong> Anything with "modelled" in the name carries a confidence band. Those bands belong in the report, next to the number, not in a footnote.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-seasonality-trap">The seasonality trap<a href="https://docs.pruneseo.com/blog/seo-attribution-analytics#the-seasonality-trap" class="hash-link" aria-label="Direct link to The seasonality trap" title="Direct link to The seasonality trap" translate="no">​</a></h2>
<p>The most expensive analytical mistake in SEO is the before-and-after comparison, because before-and-after is intuitive, easy to present and almost always wrong on a site with any seasonal component.</p>
<p>Consider an indexation remediation that lands in November and removes 180,000 URLs over six weeks. Two months later, organic sessions are up 12%.</p>
<p>Three explanations are consistent with that:</p>
<ol>
<li class="">The remediation worked and lifted indexed coverage.</li>
<li class="">The site's seasonal peak began, as it does every year.</li>
<li class="">A competitor had a technical incident in the same period.</li>
</ol>
<p>You cannot distinguish these from a single two-point comparison. This is the reason the field has an entire genre of chart that ends with a red line — and, to be fair, usually that chart is honest about the seasonality.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-defensible-comparison-method">A defensible comparison method<a href="https://docs.pruneseo.com/blog/seo-attribution-analytics#a-defensible-comparison-method" class="hash-link" aria-label="Direct link to A defensible comparison method" title="Direct link to A defensible comparison method" translate="no">​</a></h2>
<p>The fix is not more data. It is a method that admits uncertainty. Three approaches, in increasing order of effort and reliability.</p>
<p><strong>Approach 1: Index-date alignment.</strong> Align the comparison period to the date of the change, not to a convenient reporting boundary. Compare the four weeks after the change against the eight weeks before, and against the same eight weeks in each of the previous two years. If the post-change period is up against both baselines, you have something. If it is up against one, you have a seasonal explanation.</p>
<p><strong>Approach 2: Year-on-year with change-point marking.</strong> Plot two years of weekly data and mark every technical change. Trend lines through periods containing a migration, a redesign or a plugin change are not comparable to lines that do not. This is unglamorous and it is the single highest-value thing you can add to an organic report.</p>
<p><strong>Approach 3: A matched holdout.</strong> If the remediation is reversible — a noindex deployment, for instance — hold a slice of the affected pattern out of the change for four to six weeks and compare the treated and untreated slices. This is the only approach that actually establishes incrementality, and it is why reversible remediations are worth preferring over irreversible ones even when the irreversible version is technically better.</p>
<div class="language-csv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">week,segment,indexed_urls,organic_sessions,organic_conversions,organic_revenue,note</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2026-04-06,all,131400,284100,1420,318400,baseline</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2026-04-13,all,131900,290400,1455,326100,baseline</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2026-04-20,all,130800,302900,1490,341800,pre-peak</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2026-04-27,all,129100,318600,1610,372200,pre-peak</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2026-05-04,all,117600,326400,1655,381900,noindex deployed 2026-05-02</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2026-05-11,all,114200,331800,1690,389400,post-change</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2026-05-18,all,113800,335200,1712,394100,post-change</span><br></div></code></pre></div></div>
<p>Look at that extract carefully. Sessions rose 18% across the window. Sessions were <em>already</em> rising 12% in the fortnight before the change. The honest conclusion is that the remediation did not visibly damage anything and probably helped at the margin — not that it added 6% of growth. If someone presents the 18% as an effect of the change, they have made a causal claim the data does not support.</p>
<p>The genuinely attributable signal in that extract is the <strong>first column</strong>. Indexed URLs fell 13%, which is the remediation doing exactly what it was designed to do. That number is direct, measurable and immune to seasonality. Measuring the mechanism directly is usually more honest than inferring it from revenue.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-self-attribution-filter">The self-attribution filter<a href="https://docs.pruneseo.com/blog/seo-attribution-analytics#the-self-attribution-filter" class="hash-link" aria-label="Direct link to The self-attribution filter" title="Direct link to The self-attribution filter" translate="no">​</a></h2>
<p>One measurement is cheap and worth doing on every large site: how much organic traffic is actually branded.</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">-- Conceptual, not vendor-specific</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  landing_page_group</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">SUM</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sessions</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">                                   </span><span class="token keyword" style="color:#00009f">AS</span><span class="token plain"> sessions</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">SUM</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sessions</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> FILTER </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">WHERE</span><span class="token plain"> is_branded_query</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">    </span><span class="token keyword" style="color:#00009f">AS</span><span class="token plain"> branded_sessions</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">SUM</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sessions</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> FILTER </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">WHERE</span><span class="token plain"> is_branded_query</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">SUM</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sessions</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">                                </span><span class="token keyword" style="color:#00009f">AS</span><span class="token plain"> branded_share</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">FROM</span><span class="token plain"> organic_sessions_daily</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">WHERE</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">date</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&gt;=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">current_date</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">INTERVAL</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'90 days'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">BY</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">BY</span><span class="token plain"> sessions </span><span class="token keyword" style="color:#00009f">DESC</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>The output that changes decisions looks like this:</p>
<table><thead><tr><th>Landing page group</th><th>Sessions</th><th>Branded share</th><th>Commercial value</th></tr></thead><tbody><tr><td><code>/products/*</code></td><td>412,000</td><td>0.34</td><td>High</td></tr><tr><td><code>/help/*</code></td><td>188,000</td><td>0.06</td><td>Low</td></tr><tr><td><code>/blog/*</code></td><td>96,000</td><td>0.02</td><td>Medium</td></tr><tr><td><code>/landing/*</code></td><td>41,000</td><td>0.71</td><td>Low</td></tr></tbody></table>
<p>A 71% branded share on a landing page group means most of that traffic would have arrived anyway — someone who typed your brand name was not a discovery. Reporting it as SEO revenue overstates the channel badly. Conversely, the 6% branded share on help content is a genuinely earned result, and help content often has low commercial value per session precisely because it resolves a question rather than selling something.</p>
<blockquote>
<p>If your branded share is above 50% across the site, your organic revenue figure is measuring brand demand rather than acquisition, and it will flatter every initiative equally.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bot-traffic-from-machine-consumers">Bot traffic from machine consumers<a href="https://docs.pruneseo.com/blog/seo-attribution-analytics#bot-traffic-from-machine-consumers" class="hash-link" aria-label="Direct link to Bot traffic from machine consumers" title="Direct link to Bot traffic from machine consumers" translate="no">​</a></h2>
<p>If PruneSEO imports server logs, or if you analyse them yourself, expect to find machine crawler traffic that your analytics tool counted as human. A quick way to see the scale is to compare log lines to analytics sessions for a sample day:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Conceptual sample, one day, grouped by user agent family</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">awk '{print $NF}' /var/log/nginx/access.log \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | sort | uniq -c | sort -rn \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  | head -20</span><br></div></code></pre></div></div>
<p>The lines worth reading are the identified machine crawlers — Googlebot, GPTBot, ClaudeBot, PerplexityBot and their siblings. On a large catalogue site these can be a substantial percentage of total fetches. Two consequences:</p>
<ul>
<li class=""><strong>Analytics is overstating your page views</strong>, which deflates engagement metrics and makes content look worse than it is.</li>
<li class=""><strong>Your capacity planning should include them.</strong> If four retrieval crawlers each pull a full crawl of a 900,000 URL site weekly, that is real origin load.</li>
</ul>
<p><a class="" href="https://docs.pruneseo.com/docs/guides/running-and-monitoring-crawls">Running and monitoring crawls</a> covers log import and how crawl frequency breaks down by pattern, which is where this shows up.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/seo-attribution-analytics#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Three things, in order. First, split branded from non-branded organic and never report them merged. Second, put change points on your trend chart so nobody attributes a seasonal peak to an SEO intervention. Third, measure the mechanism directly — indexed URLs, coverage rate, crawl efficiency — because those numbers are attributable, and let revenue be the lagging confirmation rather than the headline.</p>
<p>PruneSEO's <a class="" href="https://docs.pruneseo.com/docs/features/sampling-confidence-and-extrapolation">sampling confidence and extrapolation</a> page is the one to read before you put any of these numbers in front of finance, because the difference between a defensible estimate and a misleading one is almost entirely about sample size. For how to keep the underlying findings consistent between runs, <a class="" href="https://docs.pruneseo.com/docs/guides/health-and-reporting">health and reporting</a> covers scoring and history. And <a class="" href="https://docs.pruneseo.com/docs/guides/campaigns-and-artifacts">campaigns and artifacts</a> is where a remediation gets its own baseline and its own change-point marker, which is what makes Approach 3 above possible next time.</p>]]></content:encoded>
            <category>Data and sampling</category>
            <category>SEO audits</category>
        </item>
        <item>
            <title><![CDATA[Generative Engine Optimisation: The Parts That Are Real]]></title>
            <link>https://docs.pruneseo.com/blog/generative-engine-optimization</link>
            <guid>https://docs.pruneseo.com/blog/generative-engine-optimization</guid>
            <pubDate>Mon, 14 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A sceptical technical look at generative engine optimisation: which parts are genuinely new, which are rebranded SEO, and the concrete crawler, schema and structure changes worth making.]]></description>
            <content:encoded><![CDATA[<p>"Generative engine optimisation" is a phrase that arrived before the underlying evidence did, and the industry has spent the intervening time attaching it to a mixture of genuinely new requirements and old ones wearing a new hat. The useful response is to sort it: strip out the rebranding, keep the parts that describe a real change in how machines read the web, and be honest that on large sites the rebranded parts still dominate the impact. The new parts are narrow — machine crawler policy, entity clarity and extractable structure — and they matter, but they are an afternoon of work each rather than a programme.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sorting-the-claim-into-three-buckets">Sorting the claim into three buckets<a href="https://docs.pruneseo.com/blog/generative-engine-optimization#sorting-the-claim-into-three-buckets" class="hash-link" aria-label="Direct link to Sorting the claim into three buckets" title="Direct link to Sorting the claim into three buckets" translate="no">​</a></h2>
<table><thead><tr><th>Bucket</th><th>What it is</th><th>Worth the effort on a large site</th></tr></thead><tbody><tr><td>Genuinely new</td><td>Crawler policy for machine agents, entity disambiguation, extractable structure, provenance signals</td><td>Moderate, cheap, mostly done once</td></tr><tr><td>Repackaged SEO</td><td>Clarity, topical authority, internal linking, page speed, indexation</td><td>High, but this is just the existing job</td></tr><tr><td>Speculation</td><td>Brand mentions in model weights, "AI share of voice", prompt-level tuning of live content</td><td>Low, and mostly unmeasurable</td></tr></tbody></table>
<p>Most published GEO advice is bucket two wearing a bucket three label. That is not a criticism of the practitioners — a lot of it is good advice delivered badly — but it is a problem for planning, because it makes a modest engineering task look like a strategy.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-genuinely-new-part-crawler-policy">The genuinely new part: crawler policy<a href="https://docs.pruneseo.com/blog/generative-engine-optimization#the-genuinely-new-part-crawler-policy" class="hash-link" aria-label="Direct link to The genuinely new part: crawler policy" title="Direct link to The genuinely new part: crawler policy" translate="no">​</a></h2>
<p>For twenty years there was effectively one crawler that mattered and one robots.txt file that mattered. There are now several distinct machine consumers, and they are separated by purpose rather than by user agent string alone. The distinction that matters most for SEO is between crawlers that <strong>retrieve pages for search indexing</strong> and crawlers that <strong>collect content for training or grounding</strong>.</p>
<p>Google's own arrangement is the clearest illustration. <code>Googlebot</code> is the search index crawler and its behaviour is governed by <code>Google-Extended</code>, which is a separate control covering Gemini training and grounding. Blocking <code>Google-Extended</code> does not affect your rankings. Teams get this wrong constantly and occasionally block the wrong one.</p>
<p>A representative policy, with the reasoning attached:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Robots.txt — machine consumers</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 1. Search indexing. Do not touch.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">User-agent: Googlebot</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /search/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /*?*sort=</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /*?*page=</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Crawl-delay: 0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 2. Google grounding and Gemini training. Optional, reversible.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">User-agent: Google-Extended</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 3. Other AI retrieval crawlers. Decide per crawler.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">User-agent: GPTBot</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">User-agent: ClaudeBot</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Allow: /</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">User-agent: PerplexityBot</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Allow: /</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 4. Bulk training scrapers. Block as default.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">User-agent: Bytespider</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">User-agent: CCBot</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">User-agent: *</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /search/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /cart/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /account/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Allow: /</span><br></div></code></pre></div></div>
<table><thead><tr><th>Agent</th><th>Claimed purpose</th><th>Blocking effect on search</th><th>Recommendation for most sites</th></tr></thead><tbody><tr><td>Googlebot</td><td>Search indexing</td><td>Severe</td><td>Never block</td></tr><tr><td>Google-Extended</td><td>Gemini grounding and training</td><td>None</td><td>Business decision, usually allow</td></tr><tr><td>Bingbot</td><td>Search indexing</td><td>Severe for Microsoft surfaces</td><td>Never block</td></tr><tr><td>GPTBot</td><td>OpenAI training data</td><td>None</td><td>Business decision, commonly allow</td></tr><tr><td>OAI-SearchBot</td><td>OpenAI search retrieval</td><td>None</td><td>Allow if you want citations</td></tr><tr><td>ClaudeBot</td><td>Anthropic retrieval</td><td>None</td><td>Allow if you want citations</td></tr><tr><td>PerplexityBot</td><td>Search and citation</td><td>None</td><td>Allow if you want citations</td></tr><tr><td>CCBot, Bytespider</td><td>Bulk corpus collection</td><td>None</td><td>Block</td></tr></tbody></table>
<p>The important operational point: <strong>a crawler allowed in robots.txt still cannot fetch a URL that your server blocks, rate-limits or serves with a 429.</strong> Machine consumers amplify the failure modes you already have. If Googlebot is already struggling with a slow response time on facet URLs, adding four more respectful crawlers who each fetch a document set will not improve it.</p>
<p>Which is the GEO argument that actually lands on a large site, and it is not really about GEO at all.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-second-new-part-entity-clarity">The second new part: entity clarity<a href="https://docs.pruneseo.com/blog/generative-engine-optimization#the-second-new-part-entity-clarity" class="hash-link" aria-label="Direct link to The second new part: entity clarity" title="Direct link to The second new part: entity clarity" translate="no">​</a></h2>
<p>Retrieval systems need to resolve what a page is about and what it is about <em>in relation to other things</em>. Schema.org structured data is the least ambiguous way to express that, and it is genuinely under-used on large sites.</p>
<p>The most valuable thing a large catalogue site can declare is the relationship between a product, its brand, its category and its availability:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"@context"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://schema.org"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"@type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Product"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Heavy Duty Galvanised Steel Shelf, 1200mm"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"sku"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"HDG-1200-GAL"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"brand"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"@type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Brand"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Northgate Storage"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"category"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Industrial Shelving"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"material"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Galvanised steel"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"offers"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"@type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Offer"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"priceCurrency"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"GBP"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"price"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"184.00"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"availability"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://schema.org/InStock"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"itemCondition"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://schema.org/NewCondition"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"additionalProperty"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"@type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"PropertyValue"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Load capacity"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"value"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"270 kg per shelf"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"@type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"PropertyValue"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Depth"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"value"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"400 mm"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>The <code>additionalProperty</code> block is doing the work that prose usually does badly. A specification is a table. A table extracts cleanly. A sentence explaining the load rating in forty words of marketing copy does not.</p>
<p>The same logic applies site-wide: an <code>Organization</code> block that names the legal entity and links it consistently to your brand, a <code>BreadcrumbList</code> that matches the visible breadcrumb, and — on a large site — a rule that every template emits the same shape. Structured data that varies by template is worse than none, because it produces contradictions a system has to resolve.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-third-new-part-extractable-structure">The third new part: extractable structure<a href="https://docs.pruneseo.com/blog/generative-engine-optimization#the-third-new-part-extractable-structure" class="hash-link" aria-label="Direct link to The third new part: extractable structure" title="Direct link to The third new part: extractable structure" translate="no">​</a></h2>
<p>A retrieval system assembling an answer needs to lift a claim away from its context without losing the meaning. The design rules that follow are not exotic:</p>
<ol>
<li class=""><strong>Answer first.</strong> The specification, price or definition in the first paragraph, not after the hero image and the marketing subhead.</li>
<li class=""><strong>One idea per heading.</strong> A heading that contains three questions extracts as three ambiguities.</li>
<li class=""><strong>Tables for structured facts, prose for everything else.</strong> Bullet lists for genuinely enumerable items, not for paragraphs split into fragments.</li>
<li class=""><strong>Explicit naming.</strong> "Schedule 40 PVC pipe" beats "our premium piping solution".</li>
<li class=""><strong>Stable, descriptive URLs.</strong> <code>/products/schedule-40-pvc-pipe-50mm</code> is a better extraction target than <code>/p/88213</code>.</li>
</ol>
<p>None of these are new, which is the point. They are the same principles as good writing, good accessibility and good information architecture, arriving with a new justification.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="llmstxt-what-it-is-and-what-it-is-not"><code>llms.txt</code>: what it is and what it is not<a href="https://docs.pruneseo.com/blog/generative-engine-optimization#llmstxt-what-it-is-and-what-it-is-not" class="hash-link" aria-label="Direct link to llmstxt-what-it-is-and-what-it-is-not" title="Direct link to llmstxt-what-it-is-and-what-it-is-not" translate="no">​</a></h2>
<p><code>llms.txt</code> is a proposed convention: a plain-text markdown file at the site root intended as a curated entry point for language model retrieval. The proposal is sensible as documentation and weak as an optimisation.</p>
<p>What it is: a human- or curator-written summary of what the site contains, with links, that a system could read once and use as a map. Useful if you run a documentation site, a large catalogue or a knowledge base.</p>
<p>What it is not:</p>
<ul>
<li class="">It is not a ranking signal. No major engine has confirmed using it in retrieval.</li>
<li class="">It is not enforced. There is no mechanism that makes a crawler prefer it.</li>
<li class="">It is not a substitute for robots.txt, sitemaps or crawlable HTML.</li>
</ul>
<p>If you add one, keep it current. A stale <code>llms.txt</code> is worse than none, because it is an authoritative-looking document that is wrong. PruneSEO's <a class="" href="https://docs.pruneseo.com/docs/features/artifact-generation-and-deployment">artifact generation and deployment</a> is the natural home for a generated version of one, because the surface it describes is the same surface the crawl measures — but the judgement about what to include has to be yours.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-part-that-is-unmeasurable-and-how-to-handle-it">The part that is unmeasurable, and how to handle it<a href="https://docs.pruneseo.com/blog/generative-engine-optimization#the-part-that-is-unmeasurable-and-how-to-handle-it" class="hash-link" aria-label="Direct link to The part that is unmeasurable, and how to handle it" title="Direct link to The part that is unmeasurable, and how to handle it" translate="no">​</a></h2>
<p>There is no reliable way to measure "visibility inside a language model". Every tool claiming to do so is inferring from indirect signals, and their confidence intervals are not published. The honest position is:</p>
<table><thead><tr><th>Signal</th><th>Reliability</th><th>Use</th></tr></thead><tbody><tr><td>AI referral traffic in analytics</td><td>High, but incomplete</td><td>Directional only</td></tr><tr><td>Server logs for identified AI crawlers</td><td>High</td><td>Real, measurable fetch volume</td></tr><tr><td>Brand mentions in model outputs</td><td>Low, sampled, unstable</td><td>Do not build a target on it</td></tr><tr><td>Third-party "AI visibility share"</td><td>Very low</td><td>Do not report it</td></tr></tbody></table>
<p>Server logs are the only honest measurement of machine consumption, and they are worth looking at because the volume can be surprising. A site with four identified AI crawlers hitting 200,000 URLs per month is not a future problem, it is a current capacity line item. PruneSEO's <a class="" href="https://docs.pruneseo.com/docs/guides/running-and-monitoring-crawls">running and monitoring crawls</a> guidance covers log import and pattern-level crawl statistics, which is where this shows up first.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/generative-engine-optimization#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Do the cheap new things and keep doing the old important things. Concretely: make the machine crawler decision explicit in robots.txt and write down the reasoning; validate structured data per template rather than per URL; put the answer at the top of the page; and then go back to the indexation problem you already have, which is almost certainly worth more.</p>
<p>The <a class="" href="https://docs.pruneseo.com/docs/guides/site-configuration">site configuration</a> section covers crawl policy and how PruneSEO treats robots.txt and sitemaps during a crawl. For the structure-level question, <a class="" href="https://docs.pruneseo.com/docs/features/vertical-policy-presets">vertical policy presets</a> encodes what should be indexed by default for ecommerce, publishing and programmatic templates. And if you want the unglamorous version of this advice stated plainly, start with <a class="" href="https://docs.pruneseo.com/docs/guides/triage-and-pattern-analysis">triage and pattern analysis</a>, because the first thing any large site should check is how much of its machine-readable surface is worth reading in the first place.</p>]]></content:encoded>
            <category>Technical SEO</category>
            <category>Product guides</category>
        </item>
        <item>
            <title><![CDATA[AI Overviews And SEO: What Actually Changed For Large Sites]]></title>
            <link>https://docs.pruneseo.com/blog/ai-overviews-and-seo</link>
            <guid>https://docs.pruneseo.com/blog/ai-overviews-and-seo</guid>
            <pubDate>Fri, 11 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[What AI Overviews changed about click-through, indexing demand and content strategy on large sites, which popular responses do not work, and what the evidence does and does not support.]]></description>
            <content:encoded><![CDATA[<p>AI Overviews have changed how search results pages look and they have changed what a top position is worth, and those two facts are constantly conflated with a third claim that the evidence does not support: that indexing large numbers of pages no longer matters. It still matters. What has changed is the payoff function for the pages you choose to index, not the size of the index itself. The sites reporting genuine disruption are not those that indexed too much. They are those that indexed a great deal of shallow, substitutable content and depended on it to absorb a volume of query traffic that a single synthesised answer now absorbs on a page-one nobody visits.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-click-through-evidence-says-and-where-it-stops">What the click-through evidence says, and where it stops<a href="https://docs.pruneseo.com/blog/ai-overviews-and-seo#what-the-click-through-evidence-says-and-where-it-stops" class="hash-link" aria-label="Direct link to What the click-through evidence says, and where it stops" title="Direct link to What the click-through evidence says, and where it stops" translate="no">​</a></h2>
<p>There is a substantial body of research on AI Overview click-through, and it needs reading carefully rather than quoting selectively. The consistent findings:</p>
<ul>
<li class="">Click-through declines on <strong>informational queries</strong> with a strong single-answer intent. A query like "how do I remove a background from a photo" no longer needs a click.</li>
<li class="">Click-through is <strong>largely unchanged</strong> for navigational queries, where the user already knows the destination.</li>
<li class="">Declines are <strong>smaller and noisier</strong> on <strong>commercial queries</strong>, though the direction is consistent.</li>
<li class="">The effect is <strong>highly query-dependent</strong>. A median across all queries hides more than it reveals.</li>
</ul>
<p>The failure mode in how this gets reported is treating the median as the finding. If your site sells industrial pipe fittings, the AI Overview change to "what is schedule 40 pipe" is noise and the change to "schedule 40 pipe fittings supplier near me" is not. Nobody's dataset can tell you which of these dominates your portfolio without your data.</p>
<p>Which raises the obvious question, and the honest answer is that most published analysis cannot tell you either.</p>
<blockquote>
<p>Do not accept any AI Overview claim for your site that does not name the query set it was measured on. "Traffic dropped 30%" is not a finding; "traffic dropped 30% across our top 400 non-branded informational queries, measured year on year" is.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="three-models-of-how-citations-work">Three models of how citations work<a href="https://docs.pruneseo.com/blog/ai-overviews-and-seo#three-models-of-how-citations-work" class="hash-link" aria-label="Direct link to Three models of how citations work" title="Direct link to Three models of how citations work" translate="no">​</a></h2>
<p>The mechanism matters because the correct strategy differs between them, and almost nobody is clear on which one they are operating in.</p>
<table><thead><tr><th>Model</th><th>How a page gets cited</th><th>Implication</th></tr></thead><tbody><tr><td>Retrieval-augmented selection</td><td>Google's systems fetch your page, extract passages, attribute</td><td>Original, specific, citable content wins</td></tr><tr><td>Structural extraction</td><td>Structured data and clean structure make extraction easier</td><td>Schema and markup matter more than before</td></tr><tr><td>De-emphasis of classic ranking</td><td>Overview replaces the blue links for that query</td><td>Fewer clicks regardless of quality</td></tr></tbody></table>
<p>These are not mutually exclusive and the industry treats them as if they are, which is how you end up with contradictory advice. Retrieval is real: the systems that assemble an answer fetch pages, and the pages that can be quoted cleanly have a materially better chance of being selected. But extraction is an addition to ranking, not a replacement for it, and the pages that rank are still the pages that get fetched. Anything that damages ranking damages citation, and anything that damages fetching damages both.</p>
<p>The practical conclusion is unglamorous: the technical fundamentals that made a page rank — being reachable, being fast, being crawlable, having a coherent URL, having original content — are the same fundamentals that make it citable. There is no separate AI layer of optimisation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-this-intersects-with-indexation">Where this intersects with indexation<a href="https://docs.pruneseo.com/blog/ai-overviews-and-seo#where-this-intersects-with-indexation" class="hash-link" aria-label="Direct link to Where this intersects with indexation" title="Direct link to Where this intersects with indexation" translate="no">​</a></h2>
<p>This is the part most AI commentary skips entirely, and it is the part that matters most on a large site.</p>
<p>An AI system assembling an answer for a query does not typically read your whole site. It reads a small number of high-confidence sources. On a site with 900,000 addressable URLs and 120,000 indexed, the question of which 120,000 is not academic — it is the question of what is available to be retrieved from.</p>
<p>Three structural consequences follow:</p>
<ol>
<li class=""><strong>Crawl waste becomes citation waste.</strong> If Googlebot spends its fetches on facet combinations and internal search results, the pages you actually wanted to have in the retrievable pool are being discovered slowly or not at all.</li>
<li class=""><strong>Signal dilution is real but overstated.</strong> Pages that duplicate one another dilute relevance judgements on a cluster of topics. On a large site this is a genuine problem; on a small site it is a non-event.</li>
<li class=""><strong>Extraction favours clean, self-contained sections.</strong> A page that opens with a definition, a specification or a price table is easier to quote than one that requires eight paragraphs of context.</li>
</ol>
<p>The third point is genuinely useful and rarely stated correctly. It is not about writing for machines. It is about the well-established practice of putting the answer near the top, which is good practice for humans, for accessibility, and for extraction, for the same reason.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-three-responses-that-do-not-work">The three responses that do not work<a href="https://docs.pruneseo.com/blog/ai-overviews-and-seo#the-three-responses-that-do-not-work" class="hash-link" aria-label="Direct link to The three responses that do not work" title="Direct link to The three responses that do not work" translate="no">​</a></h2>
<p>Most sites responded to AI Overviews in one of three ways. Two of them are mistakes.</p>
<p><strong>FAQs on every page.</strong> Adding a schema-marked question-and-answer block to every product page does not make a page citable and does not make it rank. It produces duplicate, low-value sections, bloats the page, and in the cases where the FAQ is written by a machine it actively damages the page's quality assessment. Schema is a label on content that already exists, not a mechanism for creating it.</p>
<p><strong>Rewriting everything into listicles.</strong> The instinct to match the Overview's format is understandable and wrong. The Overview's list structure is generated <em>from</em> sources; it is an output shape, not a quality signal. Rewriting a technical reference page as six bullet points produces a page that reads worse, quotes worse and ranks no better.</p>
<p><strong>Publishing large volumes of AI-generated content.</strong> On a large site the failure mode is not that Google punishes machine text directly, because it does not reliably. The failure is combinatorial: the content is substitutable, so it competes with itself, and a large volume of near-identical pages across a cluster of topics is exactly the pattern that gets a cluster demoted as a whole.</p>
<p>The one response that works is unglamorous: <strong>fewer, better pages, each with something a summariser cannot invent.</strong> Original data, original photography, specific pricing, operational detail, first-hand experience, a document only you have. That was already the correct strategy before AI Overviews and it remains so.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-traffic-model-looks-like">What the traffic model looks like<a href="https://docs.pruneseo.com/blog/ai-overviews-and-seo#what-the-traffic-model-looks-like" class="hash-link" aria-label="Direct link to What the traffic model looks like" title="Direct link to What the traffic model looks like" translate="no">​</a></h2>
<p>For a large site, the useful reframe is to sort queries by what an Overview does to them, then work out what proportion of your sessions live in each bucket.</p>
<table><thead><tr><th>Query type</th><th>Overview effect</th><th>What to do</th></tr></thead><tbody><tr><td>Navigational</td><td>Minimal</td><td>Maintain it; brand and navigational pages still convert</td></tr><tr><td>Single-answer informational</td><td>Large click loss</td><td>Accept it; aim for citation rather than a session</td></tr><tr><td>Comparison and "best"</td><td>Partial click loss</td><td>Win by being the option they list, not the link they click</td></tr><tr><td>Commercial, branded</td><td>Low</td><td>Invest; this is where margin lives</td></tr><tr><td>Commercial, non-branded</td><td>Moderate and rising</td><td>Prioritise; this is the at-risk revenue</td></tr><tr><td>Support and how-to</td><td>Moderate</td><td>Often low commercial value; do not over-invest</td></tr></tbody></table>
<p>The row that should worry a large commercial site is "commercial, non-branded". That is where a synthesised answer that says "the leading suppliers are X, Y and Z" removes the click entirely, because the answer <em>is</em> the shopping list. The defence is not to write better listings. It is to make sure the brand is one of the names that appears.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-to-do-about-measurement">What to do about measurement<a href="https://docs.pruneseo.com/blog/ai-overviews-and-seo#what-to-do-about-measurement" class="hash-link" aria-label="Direct link to What to do about measurement" title="Direct link to What to do about measurement" translate="no">​</a></h2>
<p>The measurement question is genuinely awkward and worth handling honestly rather than pretending it is solved.</p>
<ul>
<li class=""><strong>Segment by query intent</strong>, not by traffic source. An AI Referral source appears in analytics; the queries it corresponds to are what matter.</li>
<li class=""><strong>Watch indexed page count against indexed query count.</strong> If page count is rising and non-branded impressions are flat, you are adding pages that are not being selected.</li>
<li class=""><strong>Do not chase AIO "rankings" as a discrete metric.</strong> Most teams cannot reliably identify them, and tools that claim to are extrapolating from third-party data.</li>
<li class=""><strong>Watch the branded-query trend.</strong> Rising brand search with flat non-branded traffic is the signature of a site being <em>mentioned</em> rather than <em>visited</em>. It is not necessarily bad, but it is a different business and should be planned for.</li>
</ul>
<p>And the metric that has not changed: the number of pages that are reachable, read and judged good. A site with 900,000 URLs and 12% coverage still has a structural problem, and AI Overviews do not fix it. They make it harder to ignore.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/ai-overviews-and-seo#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Separate the questions. Ask "which of my query categories have been affected" separately from "is my indexable surface efficient", and answer them with different data. Almost every badly-reasoned AI conversation collapses because those two questions got merged into one and answered with one chart.</p>
<p>PruneSEO handles the second question. The <a class="" href="https://docs.pruneseo.com/docs/features/indexation-waste-detection">indexation waste detection</a> page sets out how patterns are grouped and classified, and <a class="" href="https://docs.pruneseo.com/docs/features/crawl-budgets-and-allowances">crawl budgets and allowances</a> covers the part where waste directly consumes the fetches that retrieval systems need. For how to keep a report honest while the ground shifts under it, read <a class="" href="https://docs.pruneseo.com/docs/features/health-scoring-and-reporting">health scoring and reporting</a> alongside <a class="" href="https://docs.pruneseo.com/docs/features/sampling-confidence-and-extrapolation">sampling confidence and extrapolation</a>.</p>]]></content:encoded>
            <category>Indexation</category>
            <category>Data and sampling</category>
        </item>
        <item>
            <title><![CDATA[SEO Competitor Analysis That Goes Past Keyword Gaps]]></title>
            <link>https://docs.pruneseo.com/blog/seo-competitor-analysis</link>
            <guid>https://docs.pruneseo.com/blog/seo-competitor-analysis</guid>
            <pubDate>Thu, 10 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How to run a competitor analysis that measures indexation surface, crawl efficiency and routing decisions rather than only keyword gaps, with worked sampling method.]]></description>
            <content:encoded><![CDATA[<p>Every competitor analysis ever produced has the same shape: a keyword gap table, a backlink comparison, and a set of feature ideas. All three are weak signals, and the first one is the weakest of them. A competitor outranking you on a keyword tells you they produced a page you did not produce, which is a tautology rather than an insight. The useful comparison is structural: how many URLs does each site put in front of Google, how many does Google keep, and what routing decisions explain the difference. That comparison is measurable, it is available without paid data, and it tells you which engineering work is worth doing.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-keyword-gaps-mislead">Why keyword gaps mislead<a href="https://docs.pruneseo.com/blog/seo-competitor-analysis#why-keyword-gaps-mislead" class="hash-link" aria-label="Direct link to Why keyword gaps mislead" title="Direct link to Why keyword gaps mislead" translate="no">​</a></h2>
<p>A keyword gap report compares the queries you rank for against the queries a competitor ranks for. The output is a list of terms where they appear on page one and you appear on page thirty. Acting on it means writing content, and that is often the right advice and occasionally the wrong one.</p>
<p>The problem is that the gap report cannot tell you <em>why</em> they are on page one. There are at least five quite different reasons, and they call for five different responses:</p>
<table><thead><tr><th>Reason they rank</th><th>Diagnostic signal</th><th>Correct response</th></tr></thead><tbody><tr><td>They made a page you did not</td><td>Their URL exists, yours does not</td><td>Create the page, or accept you decided not to</td></tr><tr><td>Their page is better</td><td>Both pages exist, yours does not surface</td><td>Content or template work, not more URLs</td></tr><tr><td>They index far more of the surface</td><td>Index count 10x yours</td><td>Coverage and pruning work first</td></tr><tr><td>They have more authority</td><td>Referring domains 50x</td><td>Outreach, which is not an indexation fix</td></tr><tr><td>You are penalised or de-indexed</td><td>Coverage report full of exclusions</td><td>Fix the underlying technical fault</td></tr></tbody></table>
<p>Notice that four of those five have nothing to do with content volume. A team that reads a gap report as "we need 300 more articles" will spend a year producing pages on a site where 70% of its existing surface is waste, and the new pages will be discovered slowly, indexed slowly and cannibalised into the same handful of queries.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-structural-comparison">The structural comparison<a href="https://docs.pruneseo.com/blog/seo-competitor-analysis#the-structural-comparison" class="hash-link" aria-label="Direct link to The structural comparison" title="Direct link to The structural comparison" translate="no">​</a></h2>
<p>The comparison worth running takes four measurements per competitor. None require a crawl budget you do not have, because you only need to crawl enough to <em>estimate</em> surface, not to enumerate it precisely.</p>
<ol>
<li class=""><strong>Addressable surface.</strong> How many URLs the site can generate. Estimated by sampling URL patterns rather than by exhaustive crawling.</li>
<li class=""><strong>Index coverage.</strong> How many of those URLs Google keeps. Available from the competitor's own public signals — sitemap sizes, index estimates, or your own Search Console data if you have worked with the property.</li>
<li class=""><strong>Crawl efficiency.</strong> Of what Google fetched, what share produced a live result. Roughly, indexed over fetched.</li>
<li class=""><strong>Routing decisions.</strong> The structural choices that explain the gap: URL length, parameter handling, pagination style, facet exposure, redirect chains, canonical policy.</li>
</ol>
<p>Here is the shape of a useful comparison for a mid-size ecommerce site:</p>
<table><thead><tr><th>Metric</th><th>Your site</th><th>Competitor A</th><th>Competitor B</th></tr></thead><tbody><tr><td>Estimated addressable surface</td><td>620,000</td><td>90,000</td><td>1,400,000</td></tr><tr><td>Indexed (estimated)</td><td>130,000</td><td>74,000</td><td>180,000</td></tr><tr><td>Coverage</td><td>21%</td><td>82%</td><td>13%</td></tr><tr><td>Est. crawl efficiency</td><td>44%</td><td>79%</td><td>31%</td></tr><tr><td>Facet parameters exposed</td><td>11</td><td>0</td><td>26</td></tr><tr><td>Pagination style</td><td>rel next prev</td><td>Clean query pages</td><td>None</td></tr></tbody></table>
<p>Read the middle column first. Competitor A ranks for everything you rank for, has a <em>much</em> smaller surface than you, and keeps 82% of it. That is a site that made hard decisions. Competitor B has fourteen times your surface and keeps 13% of it. That site is spending enormous resources generating and discarding URLs, and its rankings are propped up by brand and domain authority rather than by index hygiene.</p>
<p>The lesson is not "be like A". It is that <em>A achieved its position with one seventh of your surface</em>, which means your content plan is competing against a structural inefficiency of your own making.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-estimate-a-competitors-surface">How to estimate a competitor's surface<a href="https://docs.pruneseo.com/blog/seo-competitor-analysis#how-to-estimate-a-competitors-surface" class="hash-link" aria-label="Direct link to How to estimate a competitor's surface" title="Direct link to How to estimate a competitor's surface" translate="no">​</a></h2>
<p>You cannot crawl a competitor's full site, and you should not try. What you can do is sample patterns and scale up, which is the same method you should be using on your own site anyway.</p>
<p>The practical procedure:</p>
<ol>
<li class=""><strong>Seed with the sitemap index.</strong> Read the root sitemap, note the child sitemaps, and record the file count and any lastmod dates. Sitemaps are a statement of intent, and the gap between stated and crawled is itself the finding.</li>
<li class=""><strong>Harvest URL shapes from the SERP and internal links.</strong> Take the top URLs for the site's main commercial queries and walk the internal link graph two levels. You are not trying to enumerate, you are trying to identify <em>pattern families</em>.</li>
<li class=""><strong>Sample each pattern.</strong> Take 30 to 50 URLs per pattern from the harvested set. Check status codes, canonical tags, robots meta, and whether the content differs between instances.</li>
<li class=""><strong>Scale by visibility, not by hope.</strong> For each pattern, estimate how many instances exist using available signals — sitemap file counts, filtered-navigation counts on collection pages, or your own site's known multipliers applied to comparable catalogue sizes.</li>
</ol>
<p>The confidence interval matters more than the headline number. If you have crawled 40 URLs from a pattern and 38 returned 200 with unique content, you can say with reasonable confidence that the pattern is healthy. You cannot say how many instances exist, and you should not pretend otherwise.</p>
<blockquote>
<p>A competitor surface estimate with a stated sample size is a useful artefact. The same estimate without one is a guess with a decimal point in it.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-you-can-legitimately-learn-from-a-competitors-robotstxt">What you can legitimately learn from a competitor's robots.txt<a href="https://docs.pruneseo.com/blog/seo-competitor-analysis#what-you-can-legitimately-learn-from-a-competitors-robotstxt" class="hash-link" aria-label="Direct link to What you can legitimately learn from a competitor's robots.txt" title="Direct link to What you can legitimately learn from a competitor's robots.txt" translate="no">​</a></h2>
<p>The robots.txt file is public, and it is often the most honest document a site publishes about its own indexation strategy. Fetching and reading a competitor's robots.txt is routine competitive analysis and tells you a great deal.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Competitor robots.txt, abridged</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">User-agent: *</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /search/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /*?sort=</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /*?view=</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /cart</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /account/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /tag/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Allow: /blog/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">User-agent: *</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /*?*color=</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /*?*size=</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Disallow: /*?*page=</span><br></div></code></pre></div></div>
<p>What that tells you, pattern by pattern:</p>
<table><thead><tr><th>Rule</th><th>What it signals</th><th>Trade-off they accepted</th></tr></thead><tbody><tr><td><code>Disallow: /search/</code></td><td>Internal search results are never crawled</td><td>Those pages produce no organic long-tail traffic</td></tr><tr><td><code>Disallow: /*?sort=</code></td><td>Sort order treated as duplicate</td><td>Sort variants cannot rank for sort-intent queries</td></tr><tr><td><code>Allow: /blog/</code></td><td>Content is a strategic surface</td><td>Editorial investment is protected</td></tr><tr><td>Two <code>User-agent: *</code> blocks</td><td>Appended rules without cleaning up</td><td>Robots files accumulate cruft; not necessarily a mistake</td></tr></tbody></table>
<p>The interesting comparison is not "they block search, we do not". It is that they have made a <em>decision</em> about search, and you have made an accidental one. Most sites have never chosen what happens to internal search results; they simply never got round to it, and internal search URLs quietly become the largest crawl sink on the property.</p>
<p>A second-order observation is worth making: blocking a pattern in robots.txt prevents crawling but does not remove the URL from the index if it was already indexed. If a competitor blocks <code>/tag/</code> they may still have thousands of thin tag pages sitting in the index. Robots and indexation are separate levers, and conflating them is the single most common misreading of a robots.txt file.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-comparison-that-is-worth-reporting">The comparison that is worth reporting<a href="https://docs.pruneseo.com/blog/seo-competitor-analysis#the-comparison-that-is-worth-reporting" class="hash-link" aria-label="Direct link to The comparison that is worth reporting" title="Direct link to The comparison that is worth reporting" translate="no">​</a></h2>
<p>If you do run a competitor analysis, the report should contain five things and not a sixth:</p>
<ol>
<li class=""><strong>Surface and coverage per competitor</strong>, with sample sizes printed next to every estimate.</li>
<li class=""><strong>Verdict distribution by pattern</strong>, not by URL, so the reader can see which families are being wasted.</li>
<li class=""><strong>Routing decisions</strong> — canonical policy, facet handling, pagination, parameter policy.</li>
<li class=""><strong>Server and crawl hygiene signals</strong> — response times on uncached facet URLs, redirect depth, whether the sitemap matches the intended surface.</li>
<li class=""><strong>A ranked list of structural differences</strong>, each with an estimated size in URLs and an estimated effort.</li>
</ol>
<p>What it should not contain is a traffic comparison. Competitor traffic estimates from third-party tools have error bars wide enough to drive any conclusion you want. Using them to decide a strategy is a category error, and it is the most common way competitor analysis turns into a budget justification exercise rather than an engineering one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="turn-the-comparison-into-a-backlog">Turn the comparison into a backlog<a href="https://docs.pruneseo.com/blog/seo-competitor-analysis#turn-the-comparison-into-a-backlog" class="hash-link" aria-label="Direct link to Turn the comparison into a backlog" title="Direct link to Turn the comparison into a backlog" translate="no">​</a></h2>
<p>The output you want is a prioritised list where each item has a URL count attached. Something like:</p>
<table><thead><tr><th>Priority</th><th>Finding</th><th>URLs affected</th><th>Effort</th><th>Action</th></tr></thead><tbody><tr><td>1</td><td>Internal search results crawlable</td><td>190,000</td><td>Low</td><td>Disallow in robots, noindex template, sitemap removal</td></tr><tr><td>2</td><td>Facet combinations exposed with content</td><td>240,000</td><td>Medium</td><td>Canonical to collection, noindex out-of-stock facets</td></tr><tr><td>3</td><td>Paginated archives beyond page 3 crawlable</td><td>85,000</td><td>Low</td><td>Noindex deep pages, keep paginated links</td></tr><tr><td>4</td><td>Competitor blocks sort params, we expose them</td><td>42,000</td><td>Low</td><td>Disallow <code>*?sort=</code></td></tr><tr><td>5</td><td>Competitor indexes all product variants, we do not</td><td>15,000</td><td>Medium</td><td>Deliberate decision, needs revenue input</td></tr></tbody></table>
<p>That is a backlog a developer can work through. The ordering is not by URL count alone — a 190,000-URL pattern behind a single robots.txt line beats a 240,000-URL pattern requiring a template rewrite and a search-quality argument.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/seo-competitor-analysis#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Run one comparison before you commission five. Pick your three closest competitors by query overlap, not by size, and measure surface, coverage and routing decisions for each. The finding that surprises people most often is not a keyword gap; it is that the competitor outranking them has a fraction of the surface and keeps almost all of it.</p>
<p>PruneSEO does the surface and clustering half of this. The <a class="" href="https://docs.pruneseo.com/docs/guides">User Guide</a> covers connecting your property and running a first crawl, and <a class="" href="https://docs.pruneseo.com/docs/features/url-pattern-clustering">URL pattern clustering</a> explains how several hundred thousand URLs collapse into a handful of decisions. For the sampling discipline behind every number in the comparison, read <a class="" href="https://docs.pruneseo.com/docs/features/sampling-confidence-and-extrapolation">sampling confidence and extrapolation</a> before you present the results, because a competitor comparison without stated intervals is just a rumour with a table around it.</p>]]></content:encoded>
            <category>SEO audits</category>
            <category>Data and sampling</category>
        </item>
        <item>
            <title><![CDATA[In-House SEO vs Agency: How To Decide For A Large Site]]></title>
            <link>https://docs.pruneseo.com/blog/in-house-seo-vs-agency</link>
            <guid>https://docs.pruneseo.com/blog/in-house-seo-vs-agency</guid>
            <pubDate>Wed, 09 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A practical framework for deciding between in-house SEO and an agency on a large site, based on what each side can actually change and what it costs to get wrong.]]></description>
            <content:encoded><![CDATA[<p>Should a site with a few hundred thousand URLs hire an agency or build the capability in-house? Almost every version of that debate starts with headcount and budget, and almost every version of it ends badly, because the axis is wrong. Budget and headcount tell you what you can afford to buy, not what you are failing to buy. The variable that actually decides the outcome is whether anyone on your side can hold a URL-space decision at scale, execute it against the codebase, and measure the result without arguing with it. If that person or team exists, an agency mostly buys you a second opinion. If they do not, an agency buys you the capability, and that is a perfectly good reason to buy it.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-each-side-can-actually-change">What each side can actually change<a href="https://docs.pruneseo.com/blog/in-house-seo-vs-agency#what-each-side-can-actually-change" class="hash-link" aria-label="Direct link to What each side can actually change" title="Direct link to What each side can actually change" translate="no">​</a></h2>
<p>Most comparisons flatten the two options into a list of tasks and then price the list. That approach misses the only thing that matters, which is that a large site's indexation problems are overwhelmingly <em>execution</em> problems rather than <em>strategy</em> problems. Almost nobody is short of ideas about which URLs are worthless. They are short of a mechanism that can remove sixty thousand of them safely and prove afterwards that nothing valuable was removed with them.</p>
<table><thead><tr><th>Capability</th><th>In-house</th><th>Agency</th><th>Joint</th></tr></thead><tbody><tr><td>Server, template and routing changes</td><td>Fast, no briefing cost</td><td>Slow, needs a client developer</td><td>In-house executes, agency specifies</td></tr><tr><td>Content and editorial judgement</td><td>Strong, if the editors are involved</td><td>Weak, and honestly so</td><td>In-house decides</td></tr><tr><td>URL-space analysis at scale</td><td>Depends entirely on tooling</td><td>Strong</td><td>Tooling plus pattern judgement</td></tr><tr><td>Prioritisation against dev capacity</td><td>Knows the roadmap</td><td>Does not</td><td>Joint</td></tr><tr><td>Keeping the result after the project ends</td><td>Permanent</td><td>Rarely</td><td>In-house</td></tr><tr><td>Objectivity about a site the team owns</td><td>Weak</td><td>Strong</td><td>Agency audits, in-house acts</td></tr></tbody></table>
<p>Read that as a whole rather than as a scoreboard. There is no row where agency is unambiguously better, and no row where in-house is unambiguously better. What the table describes is a division of labour, and most sites that get this wrong have hired an agency for the left column and expected results in the right column.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-in-house-genuinely-wins">Where in-house genuinely wins<a href="https://docs.pruneseo.com/blog/in-house-seo-vs-agency#where-in-house-genuinely-wins" class="hash-link" aria-label="Direct link to Where in-house genuinely wins" title="Direct link to Where in-house genuinely wins" translate="no">​</a></h2>
<p>The strongest argument for building the team in-house is <strong>latency</strong>. On a large site, most indexation wins come from a change to a template, a routing rule or a header, and each of those has to travel through a ticket. An in-house team with direct access to the repository makes that change the same week. An agency making the same change might make it in six weeks, and in those six weeks the crawl keeps hitting the same four hundred thousand unusable URLs.</p>
<p>That latency compounds in a specific way. Template fixes are high-leverage because one change affects every URL in a pattern. A pattern with eighty thousand URLs is not fixed by eighty thousand edits, it is fixed by one. If your path from decision to deployment is a month, you will only ever make a handful of those decisions a year, and you will spend them on the patterns that are easiest to explain rather than the ones that are largest.</p>
<p>The second genuine advantage is access to the people who know why the URL exists. Whether <code>/search?colour=red&amp;size=large</code> should ever be indexable is a question for the merchandising team, and nobody outside the business can answer it from the outside. Agencies handle this badly when they lack a client-side champion, and they handle it well when they have one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-an-agency-genuinely-wins">Where an agency genuinely wins<a href="https://docs.pruneseo.com/blog/in-house-seo-vs-agency#where-an-agency-genuinely-wins" class="hash-link" aria-label="Direct link to Where an agency genuinely wins" title="Direct link to Where an agency genuinely wins" translate="no">​</a></h2>
<p>The argument for agency is not expertise in the abstract. It is <strong>measurement discipline under load</strong>.</p>
<p>An experienced practitioner knows that "71% of your URLs are not indexed" is not a measurement, because it divides a surface you have not counted by an index count that Google reports as a sample. They know that the URL Inspection tool has a daily quota per property and that blowing through it produces a queue of failures rather than data. They know that a verdict drawn from six inspected URLs is an anecdote wearing a lab coat. None of that is difficult to learn and all of it is routinely forgotten by teams who are doing nine other things.</p>
<p>The second advantage is <strong>pattern recognition across sites</strong>. Indexation failures cluster into perhaps thirty archetypes: faceted navigation, expired campaign URLs, paginated archives, paginated product variants, internal search results, staged content leaking to production, locale fallbacks. A practitioner who has seen each of them four times recognises one from the URL shape before fetching a single page. That is not a research advantage, it is a recognition advantage, and recognition is bought much more cheaply by hiring than by developing.</p>
<p>The third is <strong>objectivity</strong>. A team that owns the site also owns the roadmap, the budget and the political position of everyone who created the URLs in question. That is a genuinely difficult position from which to say "delete sixty thousand pages somebody spent money building".</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-two-failure-modes">The two failure modes<a href="https://docs.pruneseo.com/blog/in-house-seo-vs-agency#the-two-failure-modes" class="hash-link" aria-label="Direct link to The two failure modes" title="Direct link to The two failure modes" translate="no">​</a></h2>
<p>Both options fail in characteristic ways, and the failure modes are more predictable than the outcomes.</p>
<table><thead><tr><th></th><th>In-house failure mode</th><th>Agency failure mode</th></tr></thead><tbody><tr><td>Symptom</td><td>Analysis without change</td><td>Recommendations nobody implements</td></tr><tr><td>Cause</td><td>No deployment path, no one owns it</td><td>No access to templates, staging or developers</td></tr><tr><td>Symptom</td><td>Templates fixed, nothing changes</td><td>Audit delivered, findings unverifiable</td></tr><tr><td>Cause</td><td>Fixing the surface, not the discovery</td><td>Recommendations not checked against real verdicts</td></tr><tr><td>Symptom</td><td>Permanent decline in non-brand organic</td><td>Wins disappear after the engagement ends</td></tr><tr><td>Cause</td><td>No institutional memory, no runbook</td><td>No handover by design</td></tr></tbody></table>
<p>The one that costs the most money is the second. A well-researched agency report that does not survive a spot check destroys more credibility than no report, because the next three recommendations will be discounted too. Every finding should carry the URL pattern it came from, the sample size behind it, and the Search Console status that supports it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-the-money-actually-leaks">Where the money actually leaks<a href="https://docs.pruneseo.com/blog/in-house-seo-vs-agency#where-the-money-actually-leaks" class="hash-link" aria-label="Direct link to Where the money actually leaks" title="Direct link to Where the money actually leaks" translate="no">​</a></h2>
<p>The cost of <em>not</em> fixing an indexation problem is rarely the server load, which is what most people estimate. The expensive parts are elsewhere.</p>
<p>Consider a retailer with 900,000 addressable URLs and 140,000 indexed. Assume the indexed set is broadly the right 140,000, and the remaining 760,000 are waste. What does that waste actually cost?</p>
<table><thead><tr><th>Cost line</th><th>Why it matters</th><th>Typical magnitude</th></tr></thead><tbody><tr><td>Crawl capacity</td><td>Googlebot spends fetches on URLs that cannot rank</td><td>Tens of thousands of fetches per month</td></tr><tr><td>Server and CDN</td><td>Facet and search URLs are uncacheable misses</td><td>Measurable origin cost</td></tr><tr><td>Analytics signal</td><td>Landing pages dilute conversion rates and session quality</td><td>High, and rarely attributed</td></tr><tr><td>Dev capacity</td><td>Every quarter of feature work carries legacy surface</td><td>The largest hidden line</td></tr><tr><td>Trust erosion</td><td>Site quality signals degrade as accessible volume grows</td><td>Slow, difficult to reverse</td></tr></tbody></table>
<p>The dev-capacity line is the one that matters most and gets measured least. A site that has spent nine years generating URLs pays for them forever: every migration needs a redirect strategy for them, every crawl-log analysis is noisier, every incident review asks what was on that URL. Pruning is not primarily a search tactic. It is a maintenance strategy, and the return compounds.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-overlap-that-is-actually-worth-paying-for">The overlap that is actually worth paying for<a href="https://docs.pruneseo.com/blog/in-house-seo-vs-agency#the-overlap-that-is-actually-worth-paying-for" class="hash-link" aria-label="Direct link to The overlap that is actually worth paying for" title="Direct link to The overlap that is actually worth paying for" translate="no">​</a></h2>
<p>The useful answer is usually neither/or. The split that works:</p>
<ol>
<li class=""><strong>In-house owns the surface inventory and the platform.</strong> Templates, routing, sitemaps, robots, headers and the change pipeline. These are engineering responsibilities and no one should rent them.</li>
<li class=""><strong>Agency or tool owns the measurement.</strong> Pattern clustering, sampling, verdict classification and the report. This is a measurement discipline and it is cheap to buy.</li>
<li class=""><strong>Joint owns the prioritisation.</strong> Which patterns to fix this quarter, ranked against dev capacity and revenue exposure.</li>
</ol>
<p>If you take one practical step from this article, take this: the buying decision is really a question about <em>where the bottleneck sits</em>. If nobody internally can turn a finding into a merged pull request, buy execution. If the pipeline is fine and the diagnosis is weak, buy diagnosis. Most sites are surprised by the answer, which is itself worth knowing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-a-good-handover-looks-like">What a good handover looks like<a href="https://docs.pruneseo.com/blog/in-house-seo-vs-agency#what-a-good-handover-looks-like" class="hash-link" aria-label="Direct link to What a good handover looks like" title="Direct link to What a good handover looks like" translate="no">​</a></h2>
<p>Whichever side you choose, insist on these four artefacts before the engagement ends:</p>
<ul>
<li class=""><strong>A runbook.</strong> The repeat procedure: how to crawl, how to sample, how to classify verdicts, how to generate the remediation files. Without it the knowledge leaves with the team.</li>
<li class=""><strong>A pattern inventory.</strong> Every URL pattern on the site, with size, verdict and recommended action. This is the durable asset. Individual URLs are disposable.</li>
<li class=""><strong>A prioritised backlog.</strong> Not "these 60,000 URLs are bad" but "these four patterns, in this order, because of these reasons".</li>
<li class=""><strong>A regression check.</strong> A scheduled re-measurement so the next quarter knows whether the last one held.</li>
</ul>
<p>The pattern inventory is the one teams underestimate. It is a spreadsheet today, but it is the artefact that lets a completely different set of people run the audit six months later and get comparable numbers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/in-house-seo-vs-agency#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Answer one question before you talk to any supplier: <strong>can your team turn a template-level decision into a deployed change this month?</strong> If yes, buy measurement. If no, buy the whole thing and plan to internalise it.</p>
<p>When you are ready to buy measurement rather than take it on trust, the <a class="" href="https://docs.pruneseo.com/docs/guides">User Guide</a> covers connecting a read-only Search Console property and running a first crawl. The <a class="" href="https://docs.pruneseo.com/docs/features/agency-workspaces-and-permissions">agency workspaces and permissions</a> page covers running this across multiple client properties without cross-contaminating data, which is the part that determines whether an agency model is workable at all. For pricing and how many properties a plan covers, see <a class="" href="https://docs.pruneseo.com/docs/guides/plans-and-upgrading">plans and upgrading</a>.</p>]]></content:encoded>
            <category>Agencies</category>
            <category>SEO audits</category>
        </item>
        <item>
            <title><![CDATA[Managing SEO Clients Who Did Not Ask for a Technical Audit]]></title>
            <link>https://docs.pruneseo.com/blog/seo-client-management</link>
            <guid>https://docs.pruneseo.com/blog/seo-client-management</guid>
            <pubDate>Tue, 08 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The hard part of an indexation engagement is not the analysis. It is the meeting where the client hears that most of their site is waste, and what happens next.]]></description>
            <content:encoded><![CDATA[<p>An indexation audit is a difficult thing to sell and a difficult thing to deliver, because the finding is almost always unwelcome and usually arrives as a number the client already suspected. The technical work is the part everybody prepares for. What actually determines whether an engagement succeeds is a smaller number of conversations: the one where they realise the scope is larger than they thought, the one where the developer defends the templates, and the one where the numbers do not move. Here is how to handle each of them without overstating anything.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-conversation-you-will-have-in-week-two">The conversation you will have in week two<a href="https://docs.pruneseo.com/blog/seo-client-management#the-conversation-you-will-have-in-week-two" class="hash-link" aria-label="Direct link to The conversation you will have in week two" title="Direct link to The conversation you will have in week two" translate="no">​</a></h2>
<p>Most indexation engagements hit a predictable wall. The client asked for an SEO audit, expected a list of things to improve, and receives a statement that a large fraction of the site's addressable surface does not need to exist. The reaction is usually one of two, and both are reasonable.</p>
<table><thead><tr><th>Reaction</th><th>What is underneath it</th><th>What actually works</th></tr></thead><tbody><tr><td>"We just need more content"</td><td>The plan is indexed by activity rather than by outcome</td><td>Show the sampled verdict for the pattern they mean to add to</td></tr><tr><td>"Our developer says that is intentional"</td><td>Defending a decision somebody made in 2019</td><td>Ask what the decision was protecting, and whether it still applies</td></tr><tr><td>"Can we just see the number?"</td><td>Wants a single figure for the board</td><td>Give the range with the sample, and one line on what it supports</td></tr><tr><td>"How much will this cost to fix?"</td><td>Wants to size it before committing</td><td>Scope the decision phase, not the implementation</td></tr></tbody></table>
<p>The first is the most common and the most worth taking seriously. If the pattern the client wants to add content to is at 12 per cent indexed, then the content budget is being spent inside a family Google has already declined at scale. That is not an argument against writing content. It is an argument about the order.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="managing-the-developer-who-is-usually-right-about-intent">Managing the developer, who is usually right about intent<a href="https://docs.pruneseo.com/blog/seo-client-management#managing-the-developer-who-is-usually-right-about-intent" class="hash-link" aria-label="Direct link to Managing the developer, who is usually right about intent" title="Direct link to Managing the developer, who is usually right about intent" translate="no">​</a></h2>
<p>There is a structural asymmetry in most engagements. The developer who built the tag system in 2019 knows exactly why it exists and is usually correct about the reason. They are frequently wrong about the scale of the consequence, because they have never seen the total URL count across all nine years of tag combinations.</p>
<p>That combination — accurate on intent, uninformed on scale — is workable, and the way to work it is to move the conversation from whether to why.</p>
<p><strong>Ask what the pattern was protecting.</strong> A faceted navigation exists because someone needed filters to work. A tag archive exists because an editor wanted visibility. A paginated listing exists because the CMS template was copied from a blog. Each has a legitimate origin, and each may no longer need one.</p>
<p><strong>Bring the count, not the argument.</strong> "This pattern is 180,000 URLs and Google has read about 40 per cent of the ones we sampled" lands very differently from "your filters are bad". The first is a measurement the developer can use in a planning meeting; the second is an insult that has to be resisted.</p>
<p><strong>Never frame removal as deletion.</strong> The distinction between retiring a URL and consolidating it into another is the difference between an engineering task and a business decision. Hand over redirects, retire the genuinely dead, and leave the editorial call with the person who owns the content.</p>
<blockquote>
<p>The developers who fight an indexation audit hardest are usually the ones who were never shown the counts. Show them the counts and a surprising number of them become the strongest supporters of the change.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-stakeholder-map">The stakeholder map<a href="https://docs.pruneseo.com/blog/seo-client-management#the-stakeholder-map" class="hash-link" aria-label="Direct link to The stakeholder map" title="Direct link to The stakeholder map" translate="no">​</a></h2>
<p>Large organisations have more than one person who thinks they approved this, and finding out who they are late is expensive. Map four roles in the first fortnight and record them in the working agreement:</p>
<table><thead><tr><th>Role</th><th>Wants</th><th>Needs from you</th><th>Talks to</th></tr></thead><tbody><tr><td>Executive sponsor</td><td>Confidence that it is under control</td><td>One page, trend against last period</td><td>The board</td></tr><tr><td>Budget holder</td><td>A defensible number for next quarter</td><td>Ranges, not a single percentage</td><td>Finance</td></tr><tr><td>Implementer</td><td>Files that do not break the site</td><td>Sequence, caveats, platform limits</td><td>Engineering</td></tr><tr><td>Day-to-day owner</td><td>Something to do this week</td><td>A named pattern and a named action</td><td>Everyone above</td></tr></tbody></table>
<p>Most failures are a communication failure in one of the last two rows. The implementer received a report instead of files, or the day-to-day owner received a finding without a decision attached.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-number-they-will-ask-for">The number they will ask for<a href="https://docs.pruneseo.com/blog/seo-client-management#the-number-they-will-ask-for" class="hash-link" aria-label="Direct link to The number they will ask for" title="Direct link to The number they will ask for" translate="no">​</a></h2>
<p>Someone will ask for a single figure in the first month. It is a reasonable request and it is nearly always the wrong deliverable, because the honest answer is a set of numbers with different denominators and different confidence behind them.</p>
<p>What to do is give them three, in this order:</p>
<ol>
<li class=""><strong>The surface.</strong> How many URLs the site generates, by pattern. This one is a census and is the easiest to defend.</li>
<li class=""><strong>What was assessed.</strong> How many patterns, how many URLs inspected, over what window. This is the credibility figure and it is the one to put on the slide.</li>
<li class=""><strong>The movement.</strong> Change in sampled indexed share on the same patterns, with the interval attached.</li>
</ol>
<p>If they still want one number, give them the middle one. "We have measured 38 per cent of your site and the measurement is defensible" is a sentence an executive can use, and it is more persuasive than a percentage they cannot interrogate.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-month-it-does-not-move">The month it does not move<a href="https://docs.pruneseo.com/blog/seo-client-management#the-month-it-does-not-move" class="hash-link" aria-label="Direct link to The month it does not move" title="Direct link to The month it does not move" translate="no">​</a></h2>
<p>Every engagement has one. The campaign shipped, the files went in, four weeks later the sampled share is flat, and the client asks the question you were dreading.</p>
<p>The answer is a checklist, applied out loud in front of the client rather than privately, because the process is as reassuring as the conclusion:</p>
<ul>
<li class="">Was this measured with the same sample size and method as the baseline?</li>
<li class="">Has enough time passed — four to six weeks for a large campaign — for crawl and index data to reflect it?</li>
<li class="">Did anything else change on the site in the same period?</li>
<li class="">Did the change actually reach production, or is something caching it?</li>
<li class="">Was the campaign ever big enough to move this figure at all?</li>
</ul>
<p>That last question is the one to ask out loud. A campaign touching 0.3 per cent of a surface cannot move a percentage measured on that surface, and saying so plainly converts a failure conversation into a scoping conversation. Nothing damages credibility faster than reporting a number that was never going to move.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scope-creep-and-the-audit-that-becomes-a-programme">Scope creep, and the audit that becomes a programme<a href="https://docs.pruneseo.com/blog/seo-client-management#scope-creep-and-the-audit-that-becomes-a-programme" class="hash-link" aria-label="Direct link to Scope creep, and the audit that becomes a programme" title="Direct link to Scope creep, and the audit that becomes a programme" translate="no">​</a></h2>
<p>The predictable expansion is this: an audit is commissioned, a quarter of the waste is fixed, and then somebody asks for the same analysis again on the same site to confirm progress. That is a reasonable instinct and an expensive way to satisfy it, because a full re-crawl and re-inspection on a large site competes for the same 2,000 inspections a day that the remediation work needs.</p>
<p>Structure it so that progress measurement is cheap and separate:</p>
<table><thead><tr><th>Need</th><th>Cost</th><th>Cadence</th></tr></thead><tbody><tr><td>Re-measure a pattern after a campaign</td><td>A sample, 40 to 100 URLs</td><td>Four to six weeks after each campaign</td></tr><tr><td>Confirm the overall trend</td><td>Same patterns, same samples as before</td><td>Quarterly</td></tr><tr><td>Full re-audit</td><td>Whole surface, fresh inventory</td><td>Annually, or after a platform change</td></tr></tbody></table>
<p>Agreeing that at kick-off removes the argument later. The pattern-level re-measurement is cheap and answers the question the client actually has, which is whether the thing we shipped worked.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-to-write-down-before-the-work-starts">What to write down before the work starts<a href="https://docs.pruneseo.com/blog/seo-client-management#what-to-write-down-before-the-work-starts" class="hash-link" aria-label="Direct link to What to write down before the work starts" title="Direct link to What to write down before the work starts" translate="no">​</a></h2>
<p>A short working agreement, agreed in the first meeting, prevents most of the friction in this post. It does not need to be a contract; it needs to be a page both sides have read.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">SCOPE</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Properties in scope: [list]        Each has its own 2,000/day inspection allowance</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Patterns to be measured: [n]       Remainder reported as not yet measured</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Inspection method: sampled per pattern, 95% confidence, plus/minus 10 points</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">DELIVERABLE</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  One-page summary, findings by pattern, generated files, open questions</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Findings do not change with plan or scope; the deliverable is not reduced</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">OUT OF SCOPE</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Applying changes to the website. We produce files; your team ships them.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Guaranteeing any indexation outcome. Google decides what it reads.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Ranking or traffic targets, which belong to a different programme</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">COMMITMENTS</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Client: named implementer, release window, access for verification</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Us:  working days to turn around re-measurements; fortnightly updates</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">CADENCE</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Weekly    crawl and delivery status</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Monthly   report and review call</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Quarterly trend on the same patterns</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Ad hoc    after each campaign, on the date agreed before it ships</span><br></div></code></pre></div></div>
<p>The out-of-scope block is the part that earns its place. Almost every difficult conversation in an indexation engagement is somebody asking for something that was never in scope, and the answer is much easier when the answer was agreed before anybody wanted it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/seo-client-management#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Ask your current client which percentage of their site you have actually measured. If nobody on your side can answer without looking it up, that is the conversation to have first, and it is a better one to have in week three than in month nine. The <a class="" href="https://docs.pruneseo.com/docs/guides">User Guide</a> covers what a defensible audit contains, and <a class="" href="https://docs.pruneseo.com/docs/guides/health-and-reporting">health and reporting</a> shows how the findings and their samples are structured for exactly this purpose.</p>]]></content:encoded>
            <category>Agencies</category>
        </item>
        <item>
            <title><![CDATA[Pricing SEO Work When the Work Is Technical]]></title>
            <link>https://docs.pruneseo.com/blog/seo-agency-pricing-models</link>
            <guid>https://docs.pruneseo.com/blog/seo-agency-pricing-models</guid>
            <pubDate>Mon, 07 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Indexation engagements are project-shaped with a maintenance tail, which fits retainer pricing badly. A look at the models, the real cost drivers and what to write down.]]></description>
            <content:encoded><![CDATA[<p>Most agency pricing models were built around work that recurs: content every month, links every month, a report every month. Indexation work is not that shape. It is a discovery phase, a decision phase, a build phase, and then a monitoring tail that lasts roughly as long as the surface takes to stabilise — and a large fraction of the failure mode is not pricing at all, it is pricing a project-shaped engagement as though it were a subscription. This post covers which models fit, which punish you for doing the work properly, and where the money actually goes.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-shape-of-the-work">The shape of the work<a href="https://docs.pruneseo.com/blog/seo-agency-pricing-models#the-shape-of-the-work" class="hash-link" aria-label="Direct link to The shape of the work" title="Direct link to The shape of the work" translate="no">​</a></h2>
<p>An indexation engagement has four phases and they have very different economics:</p>
<table><thead><tr><th>Phase</th><th>Duration</th><th>Cost driver</th><th>Billable as</th></tr></thead><tbody><tr><td>Discovery</td><td>Days</td><td>Crawl budget and crawl time</td><td>Fixed</td></tr><tr><td>Measurement</td><td>Weeks</td><td>Inspection quota, which is per property per day</td><td>Fixed, with the sample scoped in advance</td></tr><tr><td>Decision</td><td>Days</td><td>Senior judgement, pattern by pattern</td><td>Fixed</td></tr><tr><td>Remediation</td><td>Weeks to months</td><td>Depends entirely on the client's developer</td><td>Separate, or at cost</td></tr><tr><td>Monitoring</td><td>Ongoing, but finite</td><td>Re-sampling the same patterns</td><td>Retainer, small</td></tr></tbody></table>
<p>The mistake most agencies make is billing the first four phases as one monthly figure. It looks tidy on the proposal and it destroys the relationship in month three, when the client asks why they are paying the same for a phase that is genuinely finished as they were for the phase that is genuinely running.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-the-standard-models-fit">How the standard models fit<a href="https://docs.pruneseo.com/blog/seo-agency-pricing-models#how-the-standard-models-fit" class="hash-link" aria-label="Direct link to How the standard models fit" title="Direct link to How the standard models fit" translate="no">​</a></h2>
<table><thead><tr><th>Model</th><th>Fit on indexation work</th><th>What it punishes</th></tr></thead><tbody><tr><td>Monthly retainer</td><td>Poor for discovery, fine for monitoring</td><td>Punishes you for finishing a phase early</td></tr><tr><td>Fixed project fee</td><td>Good, with a scoped sample</td><td>Punishes you when the surface is bigger than the brief</td></tr><tr><td>Hourly</td><td>Safe for discovery, expensive for clients</td><td>Punishes the client, and they will notice</td></tr><tr><td>Performance-based</td><td>Hard to apply to indexation</td><td>Punishes you for Google not responding on your schedule</td></tr><tr><td>Hybrid, phased</td><td>Best fit</td><td>Requires discipline at proposal stage</td></tr></tbody></table>
<p><strong>The retainer</strong> is the default because it is easy to sell and easy to invoice. It works for the monitoring tail and it works badly for discovery. Charging a flat monthly fee for the measurement phase means the month where you spend 200 inspections a day on three patterns and the month where you spend 200 a day on thirty produce identical revenue.</p>
<p><strong>The fixed project fee</strong> is the right shape for discovery and decision, provided the sample is scoped in the contract rather than discovered during it. This matters more than it sounds: on a large site, 97 inspected URLs per pattern at 95 per cent confidence and plus or minus ten points is a defined quantity. "We will audit up to 40 patterns" is not, because patterns are discovered during the crawl and the tail is unbounded.</p>
<p><strong>Hourly</strong> is honest and is generally the wrong answer commercially. It prices the client's volume problem as your risk, and on a 400,000 URL site that conversation gets difficult in the second month.</p>
<p><strong>Performance-based</strong> sounds appealing and is close to unusable here. The outcome you would be paid on — indexation of a set of URLs — depends on Google's decisions, which is exactly the sort of thing that made indexation work worth pricing separately in the first place. The one defensible variant ties payment to delivery: files produced, patterns classified, campaign targets specified. That is within your control and a client will accept it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-real-cost-drivers">The real cost drivers<a href="https://docs.pruneseo.com/blog/seo-agency-pricing-models#the-real-cost-drivers" class="hash-link" aria-label="Direct link to The real cost drivers" title="Direct link to The real cost drivers" translate="no">​</a></h2>
<p>Knowing where the hours go is what makes a price defensible rather than aspirational.</p>
<p><strong>Inspection quota is a hard constraint and it is per property.</strong> Two thousand URLs per day, per property, on a Pacific-time reset. A full pass over a large site therefore takes weeks, not days, and ten client properties consume ten independent allowances rather than one shared pool. An agency that plans a month of audits across a book of business is planning against ten separate dailies, and the second client in a week is the one that slips.</p>
<p><strong>Crawl runs are a lifecycle cost.</strong> A crawl takes time and cannot be undone, so a sensible commercial model treats them as a finite resource rather than a monthly subscription. PruneSEO's own allowance structure makes that explicit: crawl allowances are lifetime totals for the workspace rather than monthly figures, with one crawl on the free tier, 25 on the next and 200 on the agency tier. The reasoning is the argument — a crawler cannot undo what it already found, so a crawl earns its cost once you have acted on the previous one, and charging monthly for rediscovery is charging for a service the client cannot use.</p>
<p><strong>Senior judgement is the scarce input.</strong> The mechanical parts — the crawl, the clustering, the artifact generation — should not consume senior hours. Where they do, the price is hiding an inefficiency rather than covering a cost.</p>
<p><strong>Rework is usually a client delay, and it is usually unpriced.</strong> The audit finishes, the files are delivered, and the campaign sits for six weeks because the client is waiting on a release train. That gap is where indexation engagements quietly lose money, because the retainer keeps billing and the work is not progressing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="price-discovery-and-remediation-separately">Price discovery and remediation separately<a href="https://docs.pruneseo.com/blog/seo-agency-pricing-models#price-discovery-and-remediation-separately" class="hash-link" aria-label="Direct link to Price discovery and remediation separately" title="Direct link to Price discovery and remediation separately" translate="no">​</a></h2>
<p>The cleanest structure for most of these engagements is two fees and one retainer.</p>
<ul>
<li class=""><strong>A discovery and measurement fee</strong>, fixed, with the sample scope written in: number of properties, number of patterns, number of inspected URLs per pattern, and an explicit statement of what will be reported as not yet measured rather than estimated.</li>
<li class=""><strong>A remediation delivery fee</strong>, per campaign or per band of targets, covering the classification, the destination decisions and the generated artifacts. Not the client's implementation.</li>
<li class=""><strong>A small monitoring retainer</strong>, starting at a fixed date after the last campaign ships and running for a defined number of months.</li>
</ul>
<p>That structure removes the two worst conversations. It cannot charge for a phase that is complete, and it cannot be blamed for a delay that happened after handover.</p>
<p>Something worth building into it: an implementation support line item, charged by the hour, for the period after handover. Not a retainer — a service the client can use when they are stuck on a specific file. It is cheap to provide, it converts the most common post-handover friction into paid work rather than into goodwill, and goodwill is where agency margin disappears.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-can-legitimately-be-tied-to-performance">What can legitimately be tied to performance<a href="https://docs.pruneseo.com/blog/seo-agency-pricing-models#what-can-legitimately-be-tied-to-performance" class="hash-link" aria-label="Direct link to What can legitimately be tied to performance" title="Direct link to What can legitimately be tied to performance" translate="no">​</a></h2>
<p>Some outcomes are within reach and some are not. Being clear about the difference is worth more than a creative attempt to tie fees to something.</p>
<table><thead><tr><th>Candidate measure</th><th>Controllable</th><th>Verdict</th></tr></thead><tbody><tr><td>Patterns classified with a verdict and a remedy</td><td>Yes</td><td>Defensible</td></tr><tr><td>Files produced and accepted by the client's team</td><td>Yes</td><td>Defensible</td></tr><tr><td>Campaign targets specified and shipped</td><td>Yes</td><td>Defensible</td></tr><tr><td>Reduction in inventoried surface</td><td>Partly</td><td>Depends on the client shipping</td></tr><tr><td>Improvement in indexed share of a pattern</td><td>No</td><td>Not defensible</td></tr><tr><td>Organic traffic change</td><td>No</td><td>Not defensible</td></tr></tbody></table>
<p>The third row from the bottom is the one clients ask for most and it is a trap. A pattern's indexed share is a function of Google's decisions on pages that also depend on the client's content and platform. Tying fees to it means carrying variance you did not create, and the variance is not symmetric: you get paid for noise on the way up and sued for it on the way down.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-clause-that-matters-most">The clause that matters most<a href="https://docs.pruneseo.com/blog/seo-agency-pricing-models#the-clause-that-matters-most" class="hash-link" aria-label="Direct link to The clause that matters most" title="Direct link to The clause that matters most" translate="no">​</a></h2>
<p>Whatever the pricing model, one clause should be in the contract in plain language:</p>
<blockquote>
<p>Remediation is complete when the specified files have been generated and handed over. Indexation outcomes are outside the scope of this engagement and are not a condition of payment.</p>
</blockquote>
<p>That sentence does two things. It removes the expectation that the tool or the agency will change the website, which is true — no tool that produces files should be understood to apply them. And it protects the person who is paying you, who will otherwise be asked by their own leadership why the numbers have not moved yet, with no contractual ground to stand on.</p>
<p>Worth pairing with an implementation clause that names what you need from the client, with dates: access for the developer, a release window, and a named owner. Most stalled engagements are not disputes about scope, they are schedules nobody agreed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/seo-agency-pricing-models#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Take the last indexation engagement you delivered and split the invoice into the four phases. Roughly half of the agencies that do this discover that discovery and decision were billed at monitoring rates. Rebuilding the proposal around that split is a day of work and it is the single highest-return change to pricing on this kind of engagement.</p>
<p>For what the delivery actually consists of, and what has to be in the handover for it to be accepted, <a class="" href="https://docs.pruneseo.com/docs/guides/campaigns-and-artifacts">campaigns and artifacts</a> in the <a class="" href="https://docs.pruneseo.com/docs/guides">User Guide</a> is the relevant section, and <a class="" href="https://docs.pruneseo.com/docs/features/agency-workspaces-and-permissions">agency workspaces and permissions</a> covers how the delivery and review split is handled across a team.</p>]]></content:encoded>
            <category>Agencies</category>
        </item>
        <item>
            <title><![CDATA[The SEO Tools Stack for a Large Site]]></title>
            <link>https://docs.pruneseo.com/blog/seo-tools-stack</link>
            <guid>https://docs.pruneseo.com/blog/seo-tools-stack</guid>
            <pubDate>Fri, 04 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Buy by job rather than by category logo. A layered tool stack for large-site indexation work, what genuinely overlaps, and the eight questions to ask before renewing.]]></description>
            <content:encoded><![CDATA[<p>The standard way to assemble an SEO tool stack is to work down a list of category names — crawler, rank tracker, log analyser, site audit, schema validator — and buy the best-looking option in each row. That produces a stack with seven subscriptions, three overlapping crawlers and no clear answer to the question anyone actually has, which is which of these told me something true about my site. The better method is to name the jobs first and let the tools follow, and to accept that on a large site most categories are one tool and one category is genuinely two.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="name-the-jobs-first">Name the jobs first<a href="https://docs.pruneseo.com/blog/seo-tools-stack#name-the-jobs-first" class="hash-link" aria-label="Direct link to Name the jobs first" title="Direct link to Name the jobs first" translate="no">​</a></h2>
<p>Before looking at any vendor, write down the questions your team needs answered and how often. Six jobs cover almost all of the work on a large site:</p>
<table><thead><tr><th>Job</th><th>Question it answers</th><th>Frequency</th><th>Non-negotiable requirement</th></tr></thead><tbody><tr><td>Discover</td><td>What URLs does this site generate</td><td>Monthly</td><td>Full inventory grouped into patterns</td></tr><tr><td>Measure</td><td>What verdict has Google given these URLs</td><td>Weekly</td><td>Per-URL verdicts, rationed, with sample sizes</td></tr><tr><td>Decide</td><td>Which pattern needs which remedy</td><td>Monthly</td><td>Verdicts grouped and classified, not listed</td></tr><tr><td>Deliver</td><td>What files do I hand to a developer</td><td>Per campaign</td><td>Machine-readable artifacts, not screenshots</td></tr><tr><td>Observe</td><td>What is the crawler actually doing</td><td>Daily</td><td>Server or CDN logs, not a synthetic crawl</td></tr><tr><td>Track</td><td>Where do we appear for queries</td><td>Weekly</td><td>Configured geography, volume bands reported separately</td></tr></tbody></table>
<p>Write these down before you look at a pricing page. It converts a shopping exercise into a specification, and it is the only reliable defence against buying a fourth tool that does a job two others already do.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-layers-and-where-they-genuinely-differ">The layers, and where they genuinely differ<a href="https://docs.pruneseo.com/blog/seo-tools-stack#the-layers-and-where-they-genuinely-differ" class="hash-link" aria-label="Direct link to The layers, and where they genuinely differ" title="Direct link to The layers, and where they genuinely differ" translate="no">​</a></h2>
<p><strong>Discovery.</strong> A crawler. This is the layer most likely to be bought twice, because site audit tools bundle a small crawler that will cheerfully report that a 400,000 URL site has 2,000 pages on it. The test is simple: does it inventory by pattern, and does it say how much of the site it did not reach? On a large site the pattern grouping is the product, not a side feature — measured on three real sites, clustering collapsed 162,000 URLs into two to five clusters.</p>
<p><strong>Measurement.</strong> Search Console inspection. Not optional and not replaceable, because it is the only source of a per-URL verdict from Google. It is also rationed: 2,000 inspections per property per day, 600 per minute, resetting on a Pacific-time day. Any stack whose design assumes unlimited inspection is a stack that will be wrong in month two.</p>
<p><strong>Decision.</strong> This is the layer that most stacks are missing entirely. Tools that produce lists of findings do not produce judgements about them. The distinction matters because the work stops at the decision: a finding with no remedy attached is a complaint, and a complaint is not something a developer can action. Classification into ghost pages, rejected quality, suspected duplicates, other findings and not yet measured is what converts data into a plan.</p>
<p><strong>Delivery.</strong> Artifacts. Redirect maps, robots fragments, noindex directive sets, canonical manifests, sitemap micro-sitemaps, internal-link plan CSVs. The requirement is that they are files the target system can read, because a report is read once and a file is applied.</p>
<p><strong>Observation.</strong> Log analysis, or genuine crawl monitoring. This is the layer that distinguishes a stack built for a large site from one built for a brochure. A synthetic crawl tells you what a crawler could fetch; the logs tell you what Google actually fetched, how often, with what status, and at what cost in response time.</p>
<p><strong>Ranking.</strong> Configured properly, with geography, device and volume bands stated. Covered separately, and the source of most disagreements between tools.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-the-overlap-really-is">Where the overlap really is<a href="https://docs.pruneseo.com/blog/seo-tools-stack#where-the-overlap-really-is" class="hash-link" aria-label="Direct link to Where the overlap really is" title="Direct link to Where the overlap really is" translate="no">​</a></h2>
<p>Two overlaps are worth naming because they are where budget goes.</p>
<p><strong>Crawler versus audit tool.</strong> If your audit tool's crawl is capped in the low thousands, it is not a crawler for your site, it is a sample. Keep it for page-level checks if you like, and run a real crawler separately for inventory. Running three site audit tools because each finds something the others missed is expensive and produces three slightly different URL counts, which undermines all three.</p>
<p><strong>Rank tracker versus indexation monitoring.</strong> They measure different systems on different cadences and cannot substitute for each other. What looks like redundancy in a stack audit is usually two tools being asked to answer the same question because nobody built the bridge between them.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="logs-versus-crawlers">Logs versus crawlers<a href="https://docs.pruneseo.com/blog/seo-tools-stack#logs-versus-crawlers" class="hash-link" aria-label="Direct link to Logs versus crawlers" title="Direct link to Logs versus crawlers" translate="no">​</a></h2>
<p>The cheapest upgrade available to most stacks is proper log analysis, and the reason is a structural one: a crawler tells you about the URLs it chose to fetch, and the interesting question is about the URLs it did not.</p>
<table><thead><tr><th>Question</th><th>Crawler answers it</th><th>Logs answer it</th></tr></thead><tbody><tr><td>How many URLs does the site generate</td><td>Yes, for reachable URLs</td><td>No</td></tr><tr><td>Which URLs did Google fetch</td><td>No</td><td>Yes, exactly</td></tr><tr><td>Which URLs did Google never fetch</td><td>No</td><td>Yes, by absence</td></tr><tr><td>How often is a URL refetched</td><td>No</td><td>Yes</td></tr><tr><td>Response time as Google experiences it</td><td>Approximation</td><td>Measured</td></tr><tr><td>Which templates dominate crawl spend</td><td>Estimated</td><td>Exact</td></tr></tbody></table>
<p>On a site where crawl budget is genuinely contested, the log file settles arguments that a crawl cannot. It is also the only place you will find the URL that is generated, linked from nowhere, and fetched twice a day.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cost-what-to-buy-what-to-build-what-to-drop">Cost: what to buy, what to build, what to drop<a href="https://docs.pruneseo.com/blog/seo-tools-stack#cost-what-to-buy-what-to-build-what-to-drop" class="hash-link" aria-label="Direct link to Cost: what to buy, what to build, what to drop" title="Direct link to Cost: what to buy, what to build, what to drop" translate="no">​</a></h2>
<table><thead><tr><th>Capability</th><th>Buy</th><th>Build</th><th>Drop</th></tr></thead><tbody><tr><td>Crawl and pattern grouping</td><td>Yes, at large-site scale</td><td>Rarely worth it</td><td>Do not buy two</td></tr><tr><td>URL inspection and verdict sampling</td><td>Yes, because the quota is the constraint</td><td>No</td><td>Do not buy a substitute</td></tr><tr><td>Log analysis</td><td>Yes, or buy managed hosting logs</td><td>Only if you have the pipeline</td><td>Do not replace with a synthetic crawl</td></tr><tr><td>Rank tracking</td><td>Yes</td><td>No</td><td>Do not use it as an indexation metric</td></tr><tr><td>Reporting and dashboards</td><td>Depends on your stack</td><td>Fine if you already have one</td><td>Do not build a BI layer before the data is right</td></tr><tr><td>Schema validation</td><td>Usually bundled</td><td>No</td><td>Do not buy a standalone tool for it</td></tr></tbody></table>
<p>The build column is short on purpose. Building a crawler is a career; building a reporting layer over data you do not trust is a distraction. The one thing worth building is the thin integration that joins verdicts to patterns to artifacts, because that is the piece every vendor leaves to the customer.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="record-the-stack">Record the stack<a href="https://docs.pruneseo.com/blog/seo-tools-stack#record-the-stack" class="hash-link" aria-label="Direct link to Record the stack" title="Direct link to Record the stack" translate="no">​</a></h2>
<p>Write down what the stack is and why, once a year and whenever something changes. The artefact is boring and it prevents the two most expensive mistakes in this category: renewing a tool nobody uses, and losing the reason a decision was made three years ago.</p>
<div class="language-csv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">layer,tool,purpose,renewal,owner,replacement_threshold</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">discovery,site crawler,URL inventory and pattern grouping,annual,technical lead,"cap below 50k URLs"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">measurement,URL inspection tool,per-URL verdicts from Search Console,monthly,seo lead,"sample fidelity"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">decision,indexation audit tool,verdict classification and remediation planning,annual,seo lead,"cannot distinguish inferred from observed"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">delivery,artifact generator,files for web server and CMS teams,annual,technical lead,"PDF-only output"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">observation,log analyser,actual crawler behaviour,annual,engineering,"sampled rather than full logs"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">tracking,rank tracker,query-level positions,monthly,seo lead,"geography not configurable"</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="eight-questions-before-you-renew">Eight questions before you renew<a href="https://docs.pruneseo.com/blog/seo-tools-stack#eight-questions-before-you-renew" class="hash-link" aria-label="Direct link to Eight questions before you renew" title="Direct link to Eight questions before you renew" translate="no">​</a></h2>
<ol>
<li class="">What does it tell me that nothing else in the stack tells me?</li>
<li class="">Can I state the denominator behind its headline number?</li>
<li class="">Does it distinguish an observation from an inference?</li>
<li class="">Does it have a state for "not measured", or does it render absence as a zero?</li>
<li class="">What is the ceiling on volume, and what happens when I hit it?</li>
<li class="">Can it export the raw data, or is the dashboard the product?</li>
<li class="">Does anything in the stack depend on this tool's proprietary format?</li>
<li class="">If this were removed tomorrow, which number would I stop being able to produce?</li>
</ol>
<p>Question seven is the one that determines whether you can ever leave. If the artefacts, the verdicts and the pattern map all live inside one vendor's format with no export, the tool is not a tool, it is a dependency, and it will be priced like one at renewal.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/seo-tools-stack#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Write the six jobs down, map every current subscription to one of them, and cancel anything that maps to two. That exercise usually finds at least one redundant line item and at least one job nobody is doing at all, and both are useful. To see what a properly specified measurement layer looks like before comparing it to anything, the <a class="" href="https://docs.pruneseo.com/docs/guides">User Guide</a> is the starting point and <a class="" href="https://docs.pruneseo.com/docs/guides/running-and-monitoring-crawls">running and monitoring crawls</a> covers the discovery layer in detail.</p>]]></content:encoded>
            <category>Product guides</category>
            <category>SEO audits</category>
        </item>
        <item>
            <title><![CDATA[SERP Volatility: What Causes It and What to Do About It]]></title>
            <link>https://docs.pruneseo.com/blog/serp-volatility-explained</link>
            <guid>https://docs.pruneseo.com/blog/serp-volatility-explained</guid>
            <pubDate>Thu, 03 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Search results change, but so does the way they are measured. A practical account of the four causes of apparent volatility and how to tell them apart.]]></description>
            <content:encoded><![CDATA[<p>When a monitoring chart gets noisy, the first explanation everybody reaches for is that Google changed something. Sometimes that is right. More often the movement is in the observation rather than in the results: a different location, a different device, a sample that happened to run over one quiet week. On an indexation programme this matters enormously, because the wrong explanation produces the wrong response — either panic, or a decision to change something that did not need changing. This post separates the causes and gives you a way to tell them apart with data you already have.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="four-causes-of-apparent-volatility">Four causes of apparent volatility<a href="https://docs.pruneseo.com/blog/serp-volatility-explained#four-causes-of-apparent-volatility" class="hash-link" aria-label="Direct link to Four causes of apparent volatility" title="Direct link to Four causes of apparent volatility" translate="no">​</a></h2>
<p>Almost everything that looks like volatility is one of these, sometimes in combination.</p>
<table><thead><tr><th>Cause</th><th>What actually happened</th><th>How you can tell</th></tr></thead><tbody><tr><td>Genuine ranking change</td><td>The page genuinely gained or lost positions</td><td>Sustained across devices, locations and weeks</td></tr><tr><td>Measurement change</td><td>Something in the observation changed</td><td>Appears on a specific date, often a config change</td></tr><tr><td>Demand and seasonality</td><td>The query population changed</td><td>Affects volume and click-through more than position</td></tr><tr><td>Index churn</td><td>Pages entering and leaving the index</td><td>Volume and coverage figures move together</td></tr></tbody></table>
<p>The second row is the one that gets misdiagnosed most often, because a measurement change produces exactly the same picture as a real change and is usually caused by something innocuous: a tracker reconfiguration, a vendor migration, a device added to the set, a city added to a location list. The chart moves on the day of the change and never settles.</p>
<blockquote>
<p>Before attributing a movement to Google, list everything that was deployed, reconfigured or re-scoped in the fortnight before it. On a large site that list is usually not empty.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-statistics-of-whether-something-moved">The statistics of whether something moved<a href="https://docs.pruneseo.com/blog/serp-volatility-explained#the-statistics-of-whether-something-moved" class="hash-link" aria-label="Direct link to The statistics of whether something moved" title="Direct link to The statistics of whether something moved" translate="no">​</a></h2>
<p>Indexation work is measured as a proportion of a sampled pattern, which makes the right test a simple one: two proportions, two samples, same pattern.</p>
<p>The worked example below uses two samples of 97 URLs from the same pattern, five weeks apart.</p>
<table><thead><tr><th></th><th>Indexed</th><th>Not indexed</th><th>Share</th></tr></thead><tbody><tr><td>Before</td><td>59</td><td>38</td><td>0.608</td></tr><tr><td>After</td><td>66</td><td>31</td><td>0.680</td></tr></tbody></table>
<p>The change looks like seven points of improvement. Whether it is real depends on the standard error of the difference:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">standard error = sqrt( (0.608 x 0.392 / 97) + (0.680 x 0.320 / 97) )</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">               = sqrt( 0.002457 + 0.002244 )</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">               = 0.0685</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">observed difference = 0.072</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ratio                = 0.072 / 0.0685 = 1.05</span><br></div></code></pre></div></div>
<p>A ratio near one means the movement is entirely consistent with sampling noise. The correct report is "no measurable change at this sample size", and it is a genuinely useful sentence — it tells you to keep sampling rather than to claim a win.</p>
<p>The same arithmetic on a large change behaves differently. If a pattern moves from 12 per cent indexed to 40 per cent across two samples of 97, the standard error of the difference is about 0.060 and the difference is 0.28, a ratio of 4.7. That is not noise, and it is worth reporting as movement.</p>
<p>Two practical consequences follow, and both are routinely ignored:</p>
<ul>
<li class=""><strong>Sample size sets your detection floor.</strong> You will not see a five-point improvement on a pattern sampled at 97 URLs, no matter how long you wait. If small movements matter, the only answer is a larger sample, which means more of your 2,000 daily inspections going to a pattern that may not deserve them.</li>
<li class=""><strong>One comparison is not a trend.</strong> A single interval either significant or not is weak evidence. Three consecutive intervals in the same direction is a claim you can defend in a meeting.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="index-churn-looks-like-volatility-but-is-not-about-positions">Index churn looks like volatility but is not about positions<a href="https://docs.pruneseo.com/blog/serp-volatility-explained#index-churn-looks-like-volatility-but-is-not-about-positions" class="hash-link" aria-label="Direct link to Index churn looks like volatility but is not about positions" title="Direct link to Index churn looks like volatility but is not about positions" translate="no">​</a></h2>
<p>On content-heavy and ecommerce sites there is a third pattern worth naming separately: pages entering and leaving the index for reasons that have nothing to do with ranking. A product goes out of stock and the page is deindexed. A seasonal range lands and lifts a cluster. A publisher's archive ages out. Coverage figures move, positions fluctuate as the page mix shifts, and the whole chart looks volatile while the pages that matter are perfectly stable.</p>
<p>It shows up as a correlation between coverage and position movement, and it is worth testing explicitly because the two causes have opposite responses. Genuine ranking decline responds to content and competitive change. Churn responds to inventory and editorial behaviour. A team that misreads churn as a ranking problem will spend a quarter on content work when the cause was a catalogue feed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="control-groups-the-cheapest-way-to-tell-change-from-drift">Control groups: the cheapest way to tell change from drift<a href="https://docs.pruneseo.com/blog/serp-volatility-explained#control-groups-the-cheapest-way-to-tell-change-from-drift" class="hash-link" aria-label="Direct link to Control groups: the cheapest way to tell change from drift" title="Direct link to Control groups: the cheapest way to tell change from drift" translate="no">​</a></h2>
<p>The most reliable way to attribute a movement is to keep something that should not move, and compare.</p>
<p>Patterns that make good controls share a template, a platform and a technology stack with the patterns you changed, but were not themselves changed. If the product detail pattern was pruned and the comparable brand listing pattern was not, the brand pattern is a control. Its movement over the same window is a direct estimate of the background noise on that surface.</p>
<div class="language-csv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">pattern,role,before_share,after_share,inspected</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">product-detail,changed,0.608,0.680,97</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">brand-listing,control,0.591,0.598,97</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">category-tree,control,0.932,0.941,97</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">legacy-tags,changed,0.124,0.402,97</span><br></div></code></pre></div></div>
<p>Read that as two stories. The changed patterns moved by 7.2 and 27.8 points. The controls moved by 0.7 and 0.9. The background drift on this surface is small, which strengthens both results and, just as importantly, would have told you to distrust the first pair of numbers if the controls had moved too.</p>
<p>Controls are also how you detect that something site-wide changed — a platform release, a template deployment, a tracking change — because everything moves together. A control set that drifts with the treated set is not a control set, it is a shared environment, and that is worth knowing as well.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-to-do-during-a-volatile-period">What to do during a volatile period<a href="https://docs.pruneseo.com/blog/serp-volatility-explained#what-to-do-during-a-volatile-period" class="hash-link" aria-label="Direct link to What to do during a volatile period" title="Direct link to What to do during a volatile period" translate="no">​</a></h2>
<p>Chaos is not a good time to make decisions, and indexation work is unusually susceptible to them because the changes are large. Four rules that hold up:</p>
<p><strong>Freeze the sample allocation.</strong> Do not change which patterns get inspected in the middle of a comparison window. Reallocating invalidates the comparison more reliably than any site change will.</p>
<p><strong>Widen intervals rather than narrowing claims.</strong> If the period is unstable, the honest response is to say the result is indicative and to sample again in four weeks, not to pick the reading you prefer.</p>
<p><strong>Separate the campaign from the period.</strong> A change shipped on day 3 of a five-week window has a different measurement problem than one shipped on day 30. Note the ship date against the measurement window every time.</p>
<p><strong>Look at the controls first.</strong> When a number surprises you, the control patterns are the fastest check on whether the surprise is about your site or about your observation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="volatility-that-is-worth-acting-on-anyway">Volatility that is worth acting on anyway<a href="https://docs.pruneseo.com/blog/serp-volatility-explained#volatility-that-is-worth-acting-on-anyway" class="hash-link" aria-label="Direct link to Volatility that is worth acting on anyway" title="Direct link to Volatility that is worth acting on anyway" translate="no">​</a></h2>
<p>Some movement should be responded to regardless of whether it is significant, because the underlying instability is the finding. A pattern that alternates between indexed and crawled but not indexed across successive samples is telling you something about the template, not about the sampling. Similarly, a pattern where verdicts come back mixed within a single sample usually has two sub-patterns hidden inside one, and the fix is to cluster more finely rather than to re-sample.</p>
<p>The rule is that uncertainty about <em>which</em> verdict applies is actionable, and uncertainty about <em>how much</em> applies is not. Investigate heterogeneity; measure magnitude.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/serp-volatility-explained#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Before attributing any movement, spend twenty minutes reconstructing what changed in your measurement setup over the preceding fortnight. Configurations, device sets, location lists, sample allocations, filter definitions. That check eliminates more false alarms than any amount of further analysis of the same data.</p>
<p>For the sampling discipline that makes these comparisons possible, <a class="" href="https://docs.pruneseo.com/docs/features/sampling-confidence-and-extrapolation">sampling and confidence</a> explains the interval arithmetic, and <a class="" href="https://docs.pruneseo.com/docs/guides/triage-and-pattern-analysis">triage and pattern analysis</a> covers how a mixed sample gets split into a judgement rather than averaged into one.</p>]]></content:encoded>
            <category>Data and sampling</category>
            <category>SEO audits</category>
        </item>
        <item>
            <title><![CDATA[Rank Tracking Accuracy: Why Your Numbers Disagree With Everyone Else's]]></title>
            <link>https://docs.pruneseo.com/blog/rank-tracking-accuracy</link>
            <guid>https://docs.pruneseo.com/blog/rank-tracking-accuracy</guid>
            <pubDate>Wed, 02 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Two tools tracking the same keywords will report different positions. A look at localisation, sampling, personalisation and how to configure a tracker that means something.]]></description>
            <content:encoded><![CDATA[<p>Ask two colleagues on the same team for the current position of a keyword and you will frequently get two different answers, each delivered with confidence. The instinct is that one of them is running a worse tool. In practice the disagreement is structural: rank trackers do not observe search results, they sample them, and each one samples differently. This post explains where the divergence comes from, how to configure a tracker whose output means something, and — importantly — which questions rank tracking should never be used to answer in the first place.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-tracker-is-a-sampler-not-an-observer">A tracker is a sampler, not an observer<a href="https://docs.pruneseo.com/blog/rank-tracking-accuracy#a-tracker-is-a-sampler-not-an-observer" class="hash-link" aria-label="Direct link to A tracker is a sampler, not an observer" title="Direct link to A tracker is a sampler, not an observer" translate="no">​</a></h2>
<p>The single most important fact about a rank tracker is that it does not see the search results page a user saw. It requests a SERP, from one location, on one device, with one set of parameters, at a scheduled time, and reads a position out of the response. A real search is a different set of conditions every time, and Google varies them deliberately.</p>
<p>That is not a flaw in the tools. It is a property of the thing being measured, and it means the honest unit is not "the rank" but "the rank observed under stated conditions at a stated time".</p>
<p>Four sources of divergence account for nearly all disagreement between trackers:</p>
<table><thead><tr><th>Source</th><th>What varies</th><th>Typical magnitude</th></tr></thead><tbody><tr><td>Geography</td><td>The city or country the request originates from</td><td>Several positions on local packs and local intent queries</td></tr><tr><td>Device and interface</td><td>Mobile versus desktop, vertical layout, SERP features</td><td>Large on queries with rich results</td></tr><tr><td>Personalisation and history</td><td>Signed-in state, recent search behaviour</td><td>Occasional, occasionally large</td></tr><tr><td>Sampling</td><td>Which subset of searches was actually observed</td><td>Proportional to low search volume</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="geography-is-the-biggest-single-lever">Geography is the biggest single lever<a href="https://docs.pruneseo.com/blog/rank-tracking-accuracy#geography-is-the-biggest-single-lever" class="hash-link" aria-label="Direct link to Geography is the biggest single lever" title="Direct link to Geography is the biggest single lever" translate="no">​</a></h2>
<p>A query issued from a data centre in one country is a different query to a person in your market. This is not a subtle effect, and it is the reason two agencies can look at the same keyword set for the same client and reach opposite conclusions.</p>
<p>The failure mode is specific: a UK-focused client whose keyword set is being tracked from United States infrastructure will see local packs, map results and location-modulated results that the client's customers never see. Position three in that configuration is not position three in London, and a report built on it will recommend work aimed at a SERP that does not exist for the audience.</p>
<p>Fix the geography before you compare anything, and record it in the configuration rather than in somebody's memory. A reasonable minimum configuration:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"domain"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"example.com"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"market"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gb"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"language"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"en"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"device"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"desktop"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"mobile"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"location_mode"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"city_level"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"cities"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"London"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Manchester"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Birmingham"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"personalisation"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"anonymous"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"schedule"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"daily"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"strip_features"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"answer_box"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"knowledge_panel"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"people_also_ask"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Two of those settings need justifying. <code>anonymous</code> is not cosmetic: a signed-in session changes the SERP, and most tracking is anonymous by default for good reason. And stripping SERP features changes the position, which is why it must be decided and stated rather than inherited from whichever tool the account was set up in. If a competitor occupies the map pack above you on mobile, that is a local visibility problem and not a ranking problem, and a tracker that counts the map as position one will misdirect the work.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-sampling-floor-nobody-checks">The sampling floor nobody checks<a href="https://docs.pruneseo.com/blog/rank-tracking-accuracy#the-sampling-floor-nobody-checks" class="hash-link" aria-label="Direct link to The sampling floor nobody checks" title="Direct link to The sampling floor nobody checks" translate="no">​</a></h2>
<p>Most keyword volume estimates are rounded to the nearest hundred and carry real uncertainty. If a query has, say, 100 searches a month and your tracker checks a fraction of searches, the number of actual observations behind a monthly average may be in single figures.</p>
<p>That produces a specific statistical artefact: the monthly position figure moves for reasons that have nothing to do with the site.</p>
<table><thead><tr><th>Monthly searches</th><th>Observations behind a figure</th><th>Movement that means nothing</th></tr></thead><tbody><tr><td>10</td><td>1 to 3</td><td>Almost any change at all</td></tr><tr><td>100</td><td>10 to 30</td><td>Under about four positions</td></tr><tr><td>1,000</td><td>100 to 300</td><td>Roughly one to two positions</td></tr><tr><td>10,000</td><td>1,000 to 3,000</td><td>Small enough to be worth acting on</td></tr></tbody></table>
<p>The practical consequence is that a low-volume keyword can look like it moved substantially in a month when the observed set simply did a good job of covering both weeks it was sampled from. If a keyword list is mostly terms under 1,000 searches a month, the aggregate movement across the set is substantially sampling noise and should be reported as such.</p>
<p>The correct habit is to separate the set into volume bands and report movement only for the bands where the observation count can carry it. This is unglamorous and it is the difference between a tracker as evidence and a tracker as decoration.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="personalisation-and-the-results-nobody-agreed-on">Personalisation, and the results nobody agreed on<a href="https://docs.pruneseo.com/blog/rank-tracking-accuracy#personalisation-and-the-results-nobody-agreed-on" class="hash-link" aria-label="Direct link to Personalisation, and the results nobody agreed on" title="Direct link to Personalisation, and the results nobody agreed on" translate="no">​</a></h2>
<p>Search engines personalise, and rank trackers exist partly because that makes manual checking unreliable. The honest position is not that personalisation is negligible — it occasionally produces a four-position difference — but that it is unfalsifiable, so the standard response is to control for it rather than argue about it.</p>
<p>Anonymous sessions, a fixed location, a fixed device, and a consistent schedule will not eliminate variation, but they will keep it inside a band narrow enough that you can still see a real move when one happens. Trackers that offer recorded SERPs with the variation noted are more useful than ones that give you a clean number with no provenance, because they let you see when a keyword has become unstable.</p>
<p>That instability is itself a signal. A query whose SERP differs substantially between consecutive daily checks is usually one with a strong personalised or location-sensitive component, and it should not be part of a set you report movement on.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-sanity-check-a-tracker">How to sanity check a tracker<a href="https://docs.pruneseo.com/blog/rank-tracking-accuracy#how-to-sanity-check-a-tracker" class="hash-link" aria-label="Direct link to How to sanity check a tracker" title="Direct link to How to sanity check a tracker" translate="no">​</a></h2>
<p>A tracker that has never been externally validated should be assumed wrong until tested. The test is cheap and takes an afternoon.</p>
<p>Pick twenty keywords across the volume bands. Search each one, manually, from your market, on a device matching the tracker's configuration, and record the observed position including or excluding features consistently with the configuration. Compare.</p>
<p>Expect agreement on high-volume, non-local, non-feature-dominated queries and disagreement everywhere else. The pattern of disagreement is diagnostic: systematic over-optimistic positions on one device point to a configuration problem, while random disagreement points to personalisation and to SERP features being counted differently.</p>
<p>Run it again after any configuration change, because configuration changes are where trackers quietly stop matching reality.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-complement-that-does-not-drift">The complement that does not drift<a href="https://docs.pruneseo.com/blog/rank-tracking-accuracy#the-complement-that-does-not-drift" class="hash-link" aria-label="Direct link to The complement that does not drift" title="Direct link to The complement that does not drift" translate="no">​</a></h2>
<p>Rank trackers sample the results surface. They tell you nothing about the pages underneath it. Two questions that a tracker cannot answer at all:</p>
<p><strong>Is the page indexed?</strong> A page that is not indexed cannot have a position, so the tracker either reports nothing or reports a stale last-known value. Both look like the same absence in a dashboard.</p>
<p><strong>Why was it crawled or rejected?</strong> That requires a per-URL verdict from Search Console, which is a different request, a different quota and a different unit of analysis. Google returns 2,000 URL inspections per property per day, capped at 600 a minute on a Pacific-time reset, and nothing else in the toolkit produces a per-URL verdict.</p>
<p>So a sensible division of labour looks like this:</p>
<table><thead><tr><th>Question</th><th>Right instrument</th></tr></thead><tbody><tr><td>Where do we appear for a query set</td><td>Rank tracker, properly configured</td></tr><tr><td>Is this URL in the index</td><td>Search Console URL Inspection, sampled per pattern</td></tr><tr><td>How many URLs is the site generating</td><td>Your own crawl inventory</td></tr><tr><td>Did our remediation change the index</td><td>Pattern-level sampled inspection, before and after</td></tr><tr><td>Which pages earn clicks for a query</td><td>Search Console, not the tracker</td></tr></tbody></table>
<p>The failure mode of mixing these up is predictable and common. A rank dashboard shows 4,000 keywords tracked, 2,900 with positions, and no signal that the site just lost indexation on a third of its product pages — because a page that drops out of the index simply stops reporting, and a stop looks the same as a pause in a chart.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-rank-tracking-cannot-tell-you">What rank tracking cannot tell you<a href="https://docs.pruneseo.com/blog/rank-tracking-accuracy#what-rank-tracking-cannot-tell-you" class="hash-link" aria-label="Direct link to What rank tracking cannot tell you" title="Direct link to What rank tracking cannot tell you" translate="no">​</a></h2>
<p>Three specific claims it does not support, all of which appear in reports every week:</p>
<ul>
<li class="">That a change in rankings caused a change in traffic. The causal arrow runs through pages and queries you would have to name, and the claim is usually made in the wrong direction.</li>
<li class="">That a page is well optimised because its average position is high. Average position across a query set is dominated by the queries that convert nobody.</li>
<li class="">That indexation is fine because positions are stable. Positions are a lagging surface metric about the pages Google already decided to read.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/rank-tracking-accuracy#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Check three things in your current tracker configuration: the geography, the device set, and the volume distribution of the keyword list. Any one of them can explain most of the disagreements you have had about performance. Once the tracker is trustworthy for what it does measure, the <a class="" href="https://docs.pruneseo.com/docs/guides">User Guide</a> covers the layer it cannot see — connecting a property and inspecting real URLs — and <a class="" href="https://docs.pruneseo.com/docs/features/search-console-inspection">search console inspection</a> explains how the per-URL verdicts are gathered and rationed.</p>]]></content:encoded>
            <category>Data and sampling</category>
            <category>Technical SEO</category>
        </item>
        <item>
            <title><![CDATA[SEO Reporting That Stands Up in the Room]]></title>
            <link>https://docs.pruneseo.com/blog/seo-reporting-that-stands-up</link>
            <guid>https://docs.pruneseo.com/blog/seo-reporting-that-stands-up</guid>
            <pubDate>Tue, 01 Dec 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How to structure an indexation report for three different readers, what to say when the numbers did not move, and the calendar that keeps it credible.]]></description>
            <content:encoded><![CDATA[<p>An indexation report is judged in one of two places: in a browser, where it is skimmed, or in a meeting, where somebody asks a question the report does not answer and the answer is improvised. The first failure is obvious and cheap to fix. The second is the one that damages a relationship, and it is usually caused by a report written as a document rather than as a set of answers to questions that get asked. This post covers the structure that works for both, what to write when the work has not moved the numbers, and why the calendar matters as much as the content.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="three-readers-three-documents">Three readers, three documents<a href="https://docs.pruneseo.com/blog/seo-reporting-that-stands-up#three-readers-three-documents" class="hash-link" aria-label="Direct link to Three readers, three documents" title="Direct link to Three readers, three documents" translate="no">​</a></h2>
<p>Almost every report written for a large site is really one document trying to serve three audiences with incompatible needs. Splitting them is the highest-leverage change available and costs no additional analysis, because the analysis has already been done.</p>
<table><thead><tr><th>Reader</th><th>Question they actually have</th><th>What they need</th></tr></thead><tbody><tr><td>Executive</td><td>Is this getting better, and is it under control</td><td>One page, no jargon, a comparison against last period</td></tr><tr><td>Practitioner</td><td>What exactly do I change, and what will break</td><td>Files, sequences, pattern-level detail, caveats</td></tr><tr><td>Analyst</td><td>What was actually measured, and what was not</td><td>Method, sample sizes, intervals, open questions</td></tr></tbody></table>
<p>The instinct is to write one document and let each reader find their section. That works until somebody forwards the whole thing to a board, or a developer reads the summary and acts on the least important line in it. Three documents, generated from the same findings, removes the ambiguity entirely.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-one-page-summary">The one-page summary<a href="https://docs.pruneseo.com/blog/seo-reporting-that-stands-up#the-one-page-summary" class="hash-link" aria-label="Direct link to The one-page summary" title="Direct link to The one-page summary" translate="no">​</a></h2>
<p>The executive document has one job, which is to make the current state legible in under two minutes. It should contain five things and nothing else:</p>
<ul>
<li class=""><strong>The population.</strong> What was in scope, how many URLs it contains, and when the inventory was taken. Without this every other line is unanchored.</li>
<li class=""><strong>What was assessed.</strong> How many patterns, how many URLs inspected, over what window. This is the line that determines whether the rest can be believed.</li>
<li class=""><strong>The movement.</strong> Two or three figures against the same figures last period, on the same patterns, measured the same way.</li>
<li class=""><strong>What was shipped.</strong> Campaigns deployed, their target counts, and the date each was applied.</li>
<li class=""><strong>What was not measured.</strong> A list, delivered as a list.</li>
</ul>
<p>That last item is the one teams skip and the one that protects the team later. When a number has not moved and the audience wants to know why, "this pattern was inventoried and not yet inspected" is an answer. Silence is not.</p>
<p>A real one-pager is short enough to paste into an email. Here is the shape:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">INDEPENDXATION AUDIT — [site] — [period]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">In scope            412,000 URLs inventoried, 61 patterns</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Assessed            38 patterns, 3,214 URLs inspected, 12 May – 9 Jun</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Not yet measured    23 patterns (38% of the surface)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Movement (same patterns, same method)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Indexed share, product detail      61% (n=97)  -&gt;  68% (n=97)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Indexed share, category tree       94% (n=97)  -&gt;  94% (n=97)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Indexed share, legacy tags         12% (n=97)  -&gt;  11% (n=97)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  Waste URLs in triaged patterns     149,400 est  -&gt;  131,200 est</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Shipped             3 campaigns, 41,180 targets, applied 2 Jun</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Awaiting re-measure Legacy tags, due 7 Jul</span><br></div></code></pre></div></div>
<p>Note that each percentage carries its sample size. That is not pedantry, it is what allows a reader to see that a move from 61 to 68 sits inside or outside the interval the samples support.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-delivery-pack">The delivery pack<a href="https://docs.pruneseo.com/blog/seo-reporting-that-stands-up#the-delivery-pack" class="hash-link" aria-label="Direct link to The delivery pack" title="Direct link to The delivery pack" translate="no">​</a></h2>
<p>The practitioner document should be boring. It exists so that nothing depends on somebody remembering a conversation, and it should be reconstructable by a developer who was never in any of the meetings.</p>
<p>It contains:</p>
<ul>
<li class=""><strong>Campaign definitions.</strong> What kind of change each campaign applies, and the target list. Campaign kinds in PruneSEO are Prune, Noindex, Canonical, Recrawl nudge and Internal links, with 301 and 410 as the redirect directives, and a ceiling of 100,000 targets per campaign.</li>
<li class=""><strong>Generated artifacts.</strong> An nginx map block, an Apache <code>.htaccess</code> file, a redirect CSV, a robots.txt fragment, a noindex directive set, a canonical manifest, a sitemap micro-sitemap or an internal-link plan CSV. Files, not screenshots of files.</li>
<li class=""><strong>Platform constraints, stated plainly.</strong> Shopify cannot serve a 410 and its redirects are 301 only, so a Shopify "prune" hides a page from sitemaps, from search indexing and from storefront search while the URL still returns 200. Saying otherwise in a handover document is a promise you cannot keep.</li>
<li class=""><strong>The sequence.</strong> Which campaign lands first, and what must be true before the next one is worth running.</li>
</ul>
<p>And one thing it never contains: a rationale. Practitioners do not need the argument, they need the change. Keep the argument in the one-pager.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-a-number-does-not-move">When a number does not move<a href="https://docs.pruneseo.com/blog/seo-reporting-that-stands-up#when-a-number-does-not-move" class="hash-link" aria-label="Direct link to When a number does not move" title="Direct link to When a number does not move" translate="no">​</a></h2>
<p>This is the situation that exposes weak reporting, because every instinct is to explain it away. The reliable approach is a fixed set of checks, applied in order, before any interpretation is offered.</p>
<table><thead><tr><th>Check</th><th>Question it answers</th><th>Typical finding</th></tr></thead><tbody><tr><td>Method parity</td><td>Was this measured the same way as last period</td><td>Different sample size, or a different window</td></tr><tr><td>Timing</td><td>Has enough time elapsed for a change to appear</td><td>Measured too soon after a campaign</td></tr><tr><td>Attribution</td><td>Did something else change at the same time</td><td>Release, migration, platform change, tracking break</td></tr><tr><td>Implementation</td><td>Did the change actually reach production</td><td>Deployment missed, cache stale, rule not applied</td></tr><tr><td>Expectation</td><td>Was a move even likely from this size of change</td><td>The campaign was too small to show</td></tr></tbody></table>
<p>The last row matters most and is the most often skipped. A campaign affecting 0.3 per cent of a surface cannot visibly move a percentage measured on that surface, and presenting it as a failed change is a category error rather than a disappointment. Say what the change was sized to do, say what it was not sized to do, and report the pattern-level figures where it can actually appear.</p>
<p>If after those checks the number genuinely has not moved, that is reportable, in the same register as a success. Pattern-level sampled share is the most honest lagging indicator available, and a flat line on a pattern you just repaired is information the client asked for.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="changing-the-story-without-changing-the-data">Changing the story without changing the data<a href="https://docs.pruneseo.com/blog/seo-reporting-that-stands-up#changing-the-story-without-changing-the-data" class="hash-link" aria-label="Direct link to Changing the story without changing the data" title="Direct link to Changing the story without changing the data" translate="no">​</a></h2>
<p>The temptation in a difficult month is to reach for a different metric. Traffic was down, so lead with response time. Indexation was flat, so lead with crawl efficiency. This is not dishonest in itself, but it is unstable, because the metric of the month becomes whatever is most favourable, and a client who notices the change stops believing the whole report.</p>
<p>The rule that works: <strong>the metric set is fixed for a period, and it is changed deliberately, in writing, with the reason.</strong> If a metric is dropped because it is now tracked in the quarterly report, say so, and give the date it will next appear. If a metric is added, do not let it replace one — add it, and hold both for two periods so the reader can see whether it is actually more informative.</p>
<p>Consistency of the metric set is what allows a trend line to exist at all. A trend requires that the thing being trended has not changed definition between points.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-calendar">The calendar<a href="https://docs.pruneseo.com/blog/seo-reporting-that-stands-up#the-calendar" class="hash-link" aria-label="Direct link to The calendar" title="Direct link to The calendar" translate="no">​</a></h2>
<p>Reporting problems are usually calendar problems wearing a reporting costume. This is the shape that tends to work:</p>
<table><thead><tr><th>Moment</th><th>What happens</th><th>Why</th></tr></thead><tbody><tr><td>Day 1 of the cycle</td><td>Crawl starts, inventory refreshes</td><td>The surface changes before the verdicts do</td></tr><tr><td>Days 2–7</td><td>Inspection runs against sampled patterns</td><td>Sample allocation is decided from the fresh inventory</td></tr><tr><td>Day 8</td><td>One-pager and delivery pack generated</td><td>Nobody is assembling this under deadline pressure</td></tr><tr><td>Day 10</td><td>Internal read-through, numbers checked by hand</td><td>Catches the pattern whose verdicts came back mixed</td></tr><tr><td>Day 14</td><td>Client review, 45 minutes, agenda sent in advance</td><td>A call with no agenda becomes a Q and A about the wrong thing</td></tr><tr><td>Day 21</td><td>Campaign decision, files handed over</td><td>Follows the review closely enough to be remembered</td></tr><tr><td>Day 60</td><td>Re-measurement of changed patterns</td><td>Fixed in advance so nobody measures the day after shipping</td></tr></tbody></table>
<p>The re-measurement interval on that last row is the one to hold. A large campaign needs roughly four to six weeks before crawl and index data reflects it, and measuring earlier produces a number that will fall, will be reported, and will mean nothing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/seo-reporting-that-stands-up#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Take your most recent report and split it into three files without changing a word of it. Delete the rationale from the delivery pack, add the missing "not yet measured" line to the summary, and fix the reporting calendar before the next cycle starts. That is a day of work and it changes how the whole engagement is received. For how the underlying findings and their samples are produced, <a class="" href="https://docs.pruneseo.com/docs/guides/health-and-reporting">health and reporting</a> is the relevant section of the <a class="" href="https://docs.pruneseo.com/docs/guides">User Guide</a>, and <a class="" href="https://docs.pruneseo.com/docs/guides/campaigns-and-artifacts">campaigns and artifacts</a> covers the files handed to whoever implements the work.</p>]]></content:encoded>
            <category>Agencies</category>
            <category>SEO audits</category>
        </item>
        <item>
            <title><![CDATA[SEO KPIs That Survive Contact With a Client Meeting]]></title>
            <link>https://docs.pruneseo.com/blog/seo-kpis-and-metrics</link>
            <guid>https://docs.pruneseo.com/blog/seo-kpis-and-metrics</guid>
            <pubDate>Mon, 30 Nov 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A working set of indexation and crawl KPIs for large sites, with the measurement each one requires, the failure mode it invites, and the cadence it belongs to.]]></description>
            <content:encoded><![CDATA[<p>Most SEO reporting fails in a predictable way: it is built from the metrics the tools export, rather than from the questions the business is actually asking. The result is a dashboard nobody can act on and a monthly call that re-explains what a chart meant. This post takes the opposite approach and works backwards — from the decisions that need to be made, to the smallest set of figures that supports them, and to the caveat each figure carries so it cannot be quoted without its context.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="start-from-the-decisions-not-the-exports">Start from the decisions, not the exports<a href="https://docs.pruneseo.com/blog/seo-kpis-and-metrics#start-from-the-decisions-not-the-exports" class="hash-link" aria-label="Direct link to Start from the decisions, not the exports" title="Direct link to Start from the decisions, not the exports" translate="no">​</a></h2>
<p>An indexation KPI is worth having only if some decision changes when it moves. That test eliminates most of what gets put on a dashboard. If a number moves and nobody would change what they do, it is not a KPI, it is a statistic.</p>
<p>On a large site there are really four decisions to support:</p>
<ol>
<li class="">What to fix next, out of everything currently broken.</li>
<li class="">Whether last month's remediation worked.</li>
<li class="">Whether the site is getting structurally worse between audits.</li>
<li class="">Whether the reporting itself is trustworthy enough to be acted on.</li>
</ol>
<p>Most metric sets cover the third, gesture at the second and ignore the first and fourth entirely. A set worth running covers all four, and it is not much larger than the one most teams already have.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-core-set">The core set<a href="https://docs.pruneseo.com/blog/seo-kpis-and-metrics#the-core-set" class="hash-link" aria-label="Direct link to The core set" title="Direct link to The core set" translate="no">​</a></h2>
<p>Six figures carry most of the weight, and each one has a stated denominator.</p>
<table><thead><tr><th>KPI</th><th>What it actually measures</th><th>Denominator required</th><th>Common failure mode</th></tr></thead><tbody><tr><td>Indexed share of generated surface</td><td>Google's kept proportion of what the site produces</td><td>Inventoried URL count, by pattern</td><td>Reported without the sample, so it reads as a census</td></tr><tr><td>Waste URLs in scope</td><td>URLs in patterns the audit judged non-viable</td><td>Pattern size and sampled interval</td><td>Quoted as a point estimate rather than a range</td></tr><tr><td>Crawl efficiency</td><td>Share of fetched URLs that returned a useful response</td><td>Crawl log or crawl tool fetch counts</td><td>Log stats measured over one day of a weekday</td></tr><tr><td>Coverage of assessment</td><td>Share of inventoried patterns inspected this cycle</td><td>Inventoried patterns, including the tail</td><td>The unmeasured tail is silently counted as healthy</td></tr><tr><td>Remediation shipped</td><td>Campaigns deployed and their target counts</td><td>Campaign targets</td><td>Reported as completion while the pattern is unchanged</td></tr><tr><td>Post-change indexed share</td><td>The verdict returned after a campaign landed</td><td>Same pattern, same sample size</td><td>Read too early, before a recrawl could plausibly happen</td></tr></tbody></table>
<p>The first row is the one everybody quotes and the one that most needs the second and fourth rows next to it. A jump in indexed share is the intended outcome of a campaign, and it is also the easiest thing in the category to produce by accident, for reasons that have nothing to do with the work: a platform migration, a bot misconfiguration elsewhere, a seasonal crawl shift. This is why the sixth row exists as a separate measure rather than a continuation of the first.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-metric-that-is-usually-missing-entirely">The metric that is usually missing entirely<a href="https://docs.pruneseo.com/blog/seo-kpis-and-metrics#the-metric-that-is-usually-missing-entirely" class="hash-link" aria-label="Direct link to The metric that is usually missing entirely" title="Direct link to The metric that is usually missing entirely" translate="no">​</a></h2>
<p>If your set has one gap, make sure it is this one: <strong>the share of inventoried patterns that were never inspected.</strong></p>
<p>Every site has a long tail of URL shapes that get inventoried and then dropped, because the sample was too small, because the verdicts came back mixed, or because the run was interrupted. That tail is neither a pass nor a fail. It is an open question, and its size is the honest measure of how much of your site any of the other numbers actually describes.</p>
<blockquote>
<p>If 20 per cent of your patterns were never judged, then no indexation percentage in the report describes more than four-fifths of your site, no matter how precisely it is written.</p>
</blockquote>
<p>Reporting that figure prominently is unusual and it costs almost nothing, because it is usually a fixed list. It is also the number that protects you when someone later points out the thing you did not check.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="leading-lagging-and-the-lag-that-matters">Leading, lagging and the lag that matters<a href="https://docs.pruneseo.com/blog/seo-kpis-and-metrics#leading-lagging-and-the-lag-that-matters" class="hash-link" aria-label="Direct link to Leading, lagging and the lag that matters" title="Direct link to Leading, lagging and the lag that matters" translate="no">​</a></h2>
<p>Metrics split into three groups, and the confusion between them causes most bad reporting.</p>
<p><strong>Lagging outcome metrics.</strong> Organic sessions, indexed URL count, non-brand clicks. Real, but they respond slowly and they respond to everything, not only to you. On a large site, indexation changes take weeks to months to show anywhere except the pattern-level inspection data.</p>
<p><strong>Leading technical metrics.</strong> Fetch error rate, crawl efficiency, depth distribution, orphan count, the share of a pattern that is indexed as measured by sampled inspection. These move within a crawl cycle, which makes them far more useful as evidence that something worked.</p>
<p><strong>Reporting integrity metrics.</strong> Sample size per pattern, coverage of assessment, number of unmeasured patterns, the date the inventory was last refreshed. These describe the report rather than the site, and they are the ones that make everything else defensible.</p>
<p>Most teams report only the first group, which is the worst of the three options, because the group that moves slowest is the one with the most external noise. Move the leading group into the monthly report and the third group into every report, and the first group becomes a quarterly paragraph rather than a monthly headline.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cadence-matters-more-than-the-set">Cadence matters more than the set<a href="https://docs.pruneseo.com/blog/seo-kpis-and-metrics#cadence-matters-more-than-the-set" class="hash-link" aria-label="Direct link to Cadence matters more than the set" title="Direct link to Cadence matters more than the set" translate="no">​</a></h2>
<p>A KPI measured weekly and reported monthly is fine. A KPI measured monthly and reported weekly is a chart that mostly shows sampling noise, and it does more damage than useless because people react to it.</p>
<table><thead><tr><th>Frequency</th><th>What belongs in it</th><th>Why</th></tr></thead><tbody><tr><td>Weekly</td><td>Fetch errors, uptime, crawl efficiency spikes, log anomalies</td><td>Fast-moving operational signals that justify intervention now</td></tr><tr><td>Monthly</td><td>Sampled indexed share by pattern, campaigns shipped, orphans, depth distribution</td><td>Aligns with one full crawl and inspection cycle</td></tr><tr><td>Quarterly</td><td>Trend against the same patterns, coverage of assessment, unmeasured tail</td><td>Needs enough elapsed time for a change to be real</td></tr><tr><td>Ad hoc</td><td>After every campaign, on a fixed interval</td><td>Never on the day the change shipped</td></tr></tbody></table>
<p>The last row is the one teams get wrong. Measuring a pattern the afternoon you deploy a 40,000 URL prune produces a number that will fall, will be quoted, and will mean nothing. Set a fixed re-measure date instead — typically four to six weeks, because that is roughly how long a large campaign needs before the crawl and index data reflects it — and write it down before the campaign ships, not after.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-the-sheet">Building the sheet<a href="https://docs.pruneseo.com/blog/seo-kpis-and-metrics#building-the-sheet" class="hash-link" aria-label="Direct link to Building the sheet" title="Direct link to Building the sheet" translate="no">​</a></h2>
<p>Keep the definitions in the file so nobody reinterprets a column later. A small, boring KPI sheet looks like this:</p>
<div class="language-csv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">metric,definition,denominator,source,frequency,caveat</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">indexed_share_pattern,share of sampled URLs returned as indexed,pattern URL count,URL Inspection sample,monthly,"n reported alongside; report lower bound"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">waste_urls_estimate,projected non-viable URLs in triaged patterns,pattern URL count,sample projection,monthly,"range only, never a point estimate"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">crawl_efficiency,2xx responses divided by total fetches,total fetches,crawl logs,weekly,"exclude monitoring and CDN paths"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">assessment_coverage,patterns inspected this cycle,patterns inventoried,audit run,quarterly,"unmeasured patterns listed separately"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">unmeasured_share,patterns inventoried and never inspected,patterns inventoried,audit run,quarterly,"report as a headline, not a footnote"</span><br></div></code></pre></div></div>
<p>The caveat column is the one that stops the report being misread six months later, when the person reading it is not the person who wrote it and has no memory of the caveats. It takes two minutes per metric and prevents the most common form of accidental overclaiming.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="metrics-to-drop">Metrics to drop<a href="https://docs.pruneseo.com/blog/seo-kpis-and-metrics#metrics-to-drop" class="hash-link" aria-label="Direct link to Metrics to drop" title="Direct link to Metrics to drop" translate="no">​</a></h2>
<p>Some things on most dashboards should be removed rather than caveated.</p>
<p><strong>Site health score as a single number.</strong> Covered at length elsewhere on this site, and in short: a single figure across a large site averages away the information it appears to summarise.</p>
<p><strong>Average response time across all URLs.</strong> The mean is dominated by your slowest template and tells you nothing about the pages that matter. Use the slowest-decile figure per template, or the proportion of fetches exceeding a threshold.</p>
<p><strong>Keyword rankings as an indexation KPI.</strong> Ranking movement and indexation are different systems with different cadences. Conflating them makes both harder to read. Rank tracking has its own set of accuracy limits, covered in a separate post.</p>
<p><strong>Any percentage computed by dividing Google's index count by your sitemap count.</strong> That division has a real numerator and an invented denominator, and it is the single most quoted number in the category.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/seo-kpis-and-metrics#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Write down the five decisions your monthly meeting actually makes, then check which of your current metrics would change one. If several do not, they can go, and the report gets shorter and more useful in the same afternoon. When you want the underlying measurement built properly, the <a class="" href="https://docs.pruneseo.com/docs/guides">User Guide</a> covers connecting a property and running the first crawl, and <a class="" href="https://docs.pruneseo.com/docs/guides/health-and-reporting">health and reporting</a> explains how the findings are structured, including what is deliberately labelled not yet measured.</p>]]></content:encoded>
            <category>SEO audits</category>
            <category>Data and sampling</category>
        </item>
        <item>
            <title><![CDATA[A Prioritisation Framework That Survives a Budget Conversation]]></title>
            <link>https://docs.pruneseo.com/blog/seo-prioritization-framework</link>
            <guid>https://docs.pruneseo.com/blog/seo-prioritization-framework</guid>
            <pubDate>Fri, 27 Nov 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Standard impact-effort scoring breaks down on indexation work. A framework that uses sample confidence, blast radius and prerequisites to order technical fixes.]]></description>
            <content:encoded><![CDATA[<p>Every technical SEO backlog eventually faces the same meeting, where somebody asks which of the forty findings to do first and the answer given is a spreadsheet with a score in one column. That method works reasonably well when the candidates are known in advance and comparable in size, and it fails in a specific way on indexation work: the impact of a pattern is unknown until you have sampled it, the effort estimate is dominated by a single template decision, and several findings are not independent because one blocks the meaning of another. This post sets out a framework that accounts for all three, and it is deliberately simple enough that a client can read it.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-impact-effort-scoring-fails-here">Why impact-effort scoring fails here<a href="https://docs.pruneseo.com/blog/seo-prioritization-framework#why-impact-effort-scoring-fails-here" class="hash-link" aria-label="Direct link to Why impact-effort scoring fails here" title="Direct link to Why impact-effort scoring fails here" translate="no">​</a></h2>
<p>The standard tools score candidates on impact and effort, sometimes with a confidence multiplier. On an indexation backlog that produces confident nonsense, for three reasons.</p>
<p><strong>Impact is unmeasured at the point of scoring.</strong> An analyst will happily score "fix the paginated archive" at impact 8 out of 10 from the URL count alone. If a sample of 97 of those URLs shows most are in fact indexed, the impact was 1 out of 10 and the team has spent three days on the wrong fix. The impact is not knowable until you have paid for the inspection, which means any framework that scores impact before sampling is scoring an estimate and labelling it a number.</p>
<p><strong>Effort is dominated by one decision, not by URL count.</strong> Retiring 41,000 URLs sounds enormous and is one decision made in one template, shipped as one file. Rewriting 200 titles is small in scope and touches twenty-five templates and a content team. Effort in this category is better estimated as the number of distinct systems you have to convince, not the number of URLs affected.</p>
<p><strong>Some findings are prerequisites for reading the others.</strong> You cannot sensibly prioritise "prune this pattern" until you know whether the URLs were blocked in robots.txt, because a blocked pattern may be excluded deliberately and permanently. Scoring them as peers produces work that has to be undone.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-four-inputs">The four inputs<a href="https://docs.pruneseo.com/blog/seo-prioritization-framework#the-four-inputs" class="hash-link" aria-label="Direct link to The four inputs" title="Direct link to The four inputs" translate="no">​</a></h2>
<p>Replace impact with something that is defensible before measurement, and add two things the standard frameworks omit.</p>
<table><thead><tr><th>Input</th><th>What it is</th><th>Where it comes from</th></tr></thead><tbody><tr><td>Surface</td><td>URLs in the pattern</td><td>Your crawl inventory, grouped by pattern</td></tr><tr><td>Confidence</td><td>How sure you are of the verdict for that pattern</td><td>Sample size and the interval it produced</td></tr><tr><td>Blast radius</td><td>How many templates or systems a change touches</td><td>Template analysis, not URL count</td></tr><tr><td>Prerequisite</td><td>Whether another fix must land first</td><td>Discovery, robots and rendering order</td></tr></tbody></table>
<p>Surface is the only one most teams already have, and it is the one most likely to be wrong. A pattern of 400,000 URLs at an average of eleven bytes of unique content is worth less attention than a pattern of 3,000 URLs where each one has distinct data, and surface alone cannot tell the difference.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="turning-those-into-a-score">Turning those into a score<a href="https://docs.pruneseo.com/blog/seo-prioritization-framework#turning-those-into-a-score" class="hash-link" aria-label="Direct link to Turning those into a score" title="Direct link to Turning those into a score" translate="no">​</a></h2>
<p>The ranking number does not need to be clever. It needs to be arithmetically honest, and the important move is to make the confidence term multiplicative rather than additive, because a large pattern you know nothing about should not outrank a small one you have measured.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">priority = surface × confidence_share × prerequisite_gate ÷ effort_units</span><br></div></code></pre></div></div>
<ul>
<li class=""><strong>surface</strong> is the count of URLs in the pattern, or better, an estimate of waste URLs rather than total URLs. For a pattern with 40,000 URLs where the sampled verdict is crawled but not indexed with a lower bound of 0.79, the waste estimate is about 31,600. That is the number the meeting cares about, not the 40,000.</li>
<li class=""><strong>confidence_share</strong> is the proportion of the pattern actually inspected. A pattern of 250 URLs or fewer audited in full scores 1.0. A large pattern sampled at 97 out of 40,000 scores near zero, which correctly reflects that you are ranking a guess.</li>
<li class=""><strong>prerequisite_gate</strong> is either 1 or zero. Zero when the fix depends on another landing first. This single term is what stops a team planning a prune on a pattern that is still blocked in robots.txt.</li>
<li class=""><strong>effort_units</strong> is a count of distinct systems: one template, two templates, one platform setting plus a developer ticket, and so on. Not days. Days are a guess about people you have not met.</li>
</ul>
<p>The result is not a universal truth. It is a number that everyone in the room can recompute, which is the actual requirement.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-worked-example">A worked example<a href="https://docs.pruneseo.com/blog/seo-prioritization-framework#a-worked-example" class="hash-link" aria-label="Direct link to A worked example" title="Direct link to A worked example" translate="no">​</a></h2>
<p>Four patterns from a mid-sized retail site, with the reasoning shown rather than just the score.</p>
<table><thead><tr><th>Pattern</th><th>Surface</th><th>Waste estimate</th><th>Sampled</th><th>Gate</th><th>Effort</th><th>Priority</th></tr></thead><tbody><tr><td>Faceted listings</td><td>180,000</td><td>149,400</td><td>97 of 180,000</td><td>0 until robots fixed</td><td>1</td><td>0</td></tr><tr><td>Paginated archive</td><td>41,000</td><td>31,600</td><td>97 of 41,000</td><td>1</td><td>1</td><td>High</td></tr><tr><td>Legacy tag pages</td><td>9,400</td><td>8,200</td><td>97 of 9,400</td><td>1</td><td>1</td><td>High</td></tr><tr><td>Out-of-stock products</td><td>22,000</td><td>0</td><td>97 of 22,000</td><td>1</td><td>1</td><td>None</td></tr></tbody></table>
<p>The facet pattern has the largest number in the entire audit and scores zero, because it is blocked in robots.txt and the decision about whether it should stay blocked has not been made. Until somebody decides that, any prune work on it is wasted. That is what the gate is for.</p>
<p>The out-of-stock pattern is the useful counter-example: 22,000 URLs, 97 sampled, and the verdict came back overwhelmingly indexed, which means those pages are doing something useful despite looking like classic waste. Scoring them high would have produced a large campaign to remove pages that were fine.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="order-of-operations-beats-order-of-value">Order of operations beats order of value<a href="https://docs.pruneseo.com/blog/seo-prioritization-framework#order-of-operations-beats-order-of-value" class="hash-link" aria-label="Direct link to Order of operations beats order of value" title="Direct link to Order of operations beats order of value" translate="no">​</a></h2>
<p>Beyond the score there is a sequence, and it is worth stating separately because it is the thing that most often gets ignored.</p>
<table><thead><tr><th>Order</th><th>Work</th><th>Why it goes first</th></tr></thead><tbody><tr><td>1</td><td>Discovery — sitemaps, internal link paths, rendering</td><td>A URL that cannot be found cannot be judged</td></tr><tr><td>2</td><td>Access — robots.txt and exclusions</td><td>Everything downstream is meaningless while the block stands</td></tr><tr><td>3</td><td>Response — status codes, redirects, timeouts</td><td>Wasted fetches are wasted crawl budget</td></tr><tr><td>4</td><td>Duplication — canonicals, parameter handling</td><td>Stops the same content competing with itself</td></tr><tr><td>5</td><td>Pruning — retire, noindex, consolidate</td><td>The expensive work, done last, on the clearest picture</td></tr><tr><td>6</td><td>Promotion — internal links, recrawl nudges</td><td>Only worth doing once there is something worth promoting</td></tr></tbody></table>
<p>Doing stage five before stage two is the common mistake, and it is expensive in a specific way: you ship 150,000 URL changes based on a pattern that was never actually crawlable, discover that a third of them were deliberately excluded two years ago, and carry the rollback.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="keeping-the-backlog-honest-between-audits">Keeping the backlog honest between audits<a href="https://docs.pruneseo.com/blog/seo-prioritization-framework#keeping-the-backlog-honest-between-audits" class="hash-link" aria-label="Direct link to Keeping the backlog honest between audits" title="Direct link to Keeping the backlog honest between audits" translate="no">​</a></h2>
<p>A prioritisation framework that is only run at the end of an audit will be out of date within a month, which is how technical backlogs fill with findings nobody re-examined.</p>
<p>Three habits keep it current:</p>
<ul>
<li class=""><strong>Re-run the score when new samples land.</strong> A pattern sampled at 40 URLs and later at 200 has a different confidence term, and its rank should move accordingly. The sample is a purchase and it should change the plan.</li>
<li class=""><strong>Keep a standing "not yet measured" list.</strong> Patterns inventoried but never inspected are open questions, and open questions do not get scores. They get a date and a name.</li>
<li class=""><strong>Re-gate after every shipped change.</strong> Campaign five may have removed the prerequisite for campaign two. If the gate is never revisited, the ordering quietly stops being true.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-the-numbers-come-from">Where the numbers come from<a href="https://docs.pruneseo.com/blog/seo-prioritization-framework#where-the-numbers-come-from" class="hash-link" aria-label="Direct link to Where the numbers come from" title="Direct link to Where the numbers come from" translate="no">​</a></h2>
<p>The confidence term is the one worth being precise about, because it is where the honest claim and the convenient one differ. Sampling in PruneSEO targets 95 per cent confidence at plus or minus 10 percentage points, which needs 97 URLs per pattern; at plus or minus 15 points it needs 43. Below those numbers the correct phrasing is that the result is indicative. Forty-five indexed out of fifty inspected supports a statement of "at least 79 per cent" at the 95 per cent level, and does not support a clean 90 per cent, which is a different and stronger claim than the sample carries.</p>
<p>If the framework is going to be used in front of a client, that distinction is the whole credibility of it. <a class="" href="https://docs.pruneseo.com/docs/features/sampling-confidence-and-extrapolation">Sampling and confidence</a> sets out how the extrapolation works, and <a class="" href="https://docs.pruneseo.com/docs/guides/triage-and-pattern-analysis">triage and pattern analysis</a> covers where the verdicts behind these estimates come from.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/seo-prioritization-framework#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Take your existing backlog and add two columns: the sample behind the impact figure, and the prerequisite. Most of the reordering will come from those two columns alone, and it costs an afternoon rather than a re-audit.</p>]]></content:encoded>
            <category>SEO audits</category>
            <category>Agencies</category>
        </item>
        <item>
            <title><![CDATA[Internal Linking Strategy on a Large Site]]></title>
            <link>https://docs.pruneseo.com/blog/internal-linking-strategy</link>
            <guid>https://docs.pruneseo.com/blog/internal-linking-strategy</guid>
            <pubDate>Thu, 26 Nov 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Internal links decide which URLs a crawler reaches first and which of them carry authority. A strategy for setting them at template level rather than page by page.]]></description>
            <content:encoded><![CDATA[<p>Every internal link is two instructions at once: a navigation instruction for a person, and a priority instruction for a crawler. On a small site nobody has to think about which the site is making, because there are not enough links for the second reading to matter. On a site with four hundred thousand URLs the site is constantly telling Google what matters, and most of the time it is telling it by accident. This post is about making those instructions deliberate: which pages receive links, from where, and how to decide that at template level rather than one decision at a time.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="links-are-the-only-part-of-the-architecture-you-control-directly">Links are the only part of the architecture you control directly<a href="https://docs.pruneseo.com/blog/internal-linking-strategy#links-are-the-only-part-of-the-architecture-you-control-directly" class="hash-link" aria-label="Direct link to Links are the only part of the architecture you control directly" title="Direct link to Links are the only part of the architecture you control directly" translate="no">​</a></h2>
<p>Crawl budget is usually explained as Google's decision about how much it will fetch, which makes it sound like weather. Internal links are the part of that system you can actually edit, and they are edited more casually than any other technical element on a large site.</p>
<p>Most internal linking on a big site is a by-product. A footer renders the same forty links on every page because a designer built a footer. A sidebar shows "related products" from a component nobody configured. A navigation block includes a section that was added in 2019 and has since been folded into three other sections. None of these were decisions about importance. They were decisions about layout, and they have since become decisions about what Google spends its attention on.</p>
<blockquote>
<p>A site with no internal linking strategy is not neutral. Its strategy is "whatever the templates happen to emit", and that is usually a weighting toward the newest and the most boilerplate-heavy.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-three-things-a-link-actually-transmits">The three things a link actually transmits<a href="https://docs.pruneseo.com/blog/internal-linking-strategy#the-three-things-a-link-actually-transmits" class="hash-link" aria-label="Direct link to The three things a link actually transmits" title="Direct link to The three things a link actually transmits" translate="no">​</a></h2>
<p>Before changing a link, it helps to be precise about what changes when you do.</p>
<table><thead><tr><th>Signal</th><th>Mechanism</th><th>What moves when you change one link</th></tr></thead><tbody><tr><td>Discoverability</td><td>Crawl path depth from the homepage</td><td>The time between publishing a page and a crawler finding it</td></tr><tr><td>Distribution</td><td>Signals flowing along the path</td><td>The relative prominence of source and target</td></tr><tr><td>Context</td><td>Anchor text and surrounding markup</td><td>How the target is characterised for retrieval</td></tr></tbody></table>
<p>The first is the one that gets overlooked and it is the one that causes the most damage on large sites. Crawl depth is a hard structural constraint. Anything more than a few clicks from a linked entry point is effectively unreachable at any realistic crawl rate, and no amount of internal linking <em>within</em> the deep section will fix it, because the deep section is not being entered at all.</p>
<p>So the first question about any cluster of pages is not "how should they link to each other" but "what is the shortest honest path from something a crawler already visits to the top of this cluster".</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="set-entry-points-at-template-level">Set entry points at template level<a href="https://docs.pruneseo.com/blog/internal-linking-strategy#set-entry-points-at-template-level" class="hash-link" aria-label="Direct link to Set entry points at template level" title="Direct link to Set entry points at template level" translate="no">​</a></h2>
<p>Most crawl-depth problems are template problems, which means they are fixable once rather than link by link. The usual situation is a large section reachable only through a JavaScript-rendered menu, an XML sitemap, or a footer link that appears below forty thousand links of other footer links.</p>
<p>Fixing it means choosing where the entry point lives, and the choice is a genuine trade-off:</p>
<table><thead><tr><th>Placement</th><th>Crawl benefit</th><th>Cost</th></tr></thead><tbody><tr><td>Primary navigation</td><td>Highest; visited on most pages</td><td>Nav bloat, and a slot you are spending on every page</td></tr><tr><td>Breadcrumb</td><td>High; contextual and close to the target</td><td>Requires correct hierarchy and consistent rendering</td></tr><tr><td>Section landing page with in-content links</td><td>Good; pairs entry with context</td><td>Needs editorial upkeep and a page that earns links</td></tr><tr><td>Footer only</td><td>Weak; one link, low priority, often below the fold in markup</td><td>Effectively invisible on very large sites</td></tr><tr><td>Sitemap only</td><td>None; a hint, not a crawl path</td><td>Should not be the only route to anything that matters</td></tr></tbody></table>
<p>The table is deliberately blunt about the sitemap. Submitting a URL in a sitemap tells Google it exists; it does not tell the crawler it is interesting, and a URL whose only route to it is a sitemap is a page nobody has vouched for. Sitemaps are for discovery of the unknown, not for the pages you already know you want read.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="distribute-links-by-decision-not-by-template-convenience">Distribute links by decision, not by template convenience<a href="https://docs.pruneseo.com/blog/internal-linking-strategy#distribute-links-by-decision-not-by-template-convenience" class="hash-link" aria-label="Direct link to Distribute links by decision, not by template convenience" title="Direct link to Distribute links by decision, not by template convenience" translate="no">​</a></h2>
<p>Once the entry points exist, the question is where internal authority lands. The default on a large site is that it pools in the template: the homepage links to eight categories, every category links to a hundred subcategories, and every product page inherits exactly the same footer.</p>
<p>The useful move is to make the distribution intentional. Three ways to do it, roughly in order of how much work they take:</p>
<ul>
<li class=""><strong>Template weighting.</strong> Decide which blocks render on which page types, and reduce the universal footer to links that genuinely apply everywhere. This is one change and it moves the whole graph.</li>
<li class=""><strong>Contextual links in body copy.</strong> The strongest links on any site are the ones an editor placed because the paragraph needed them. They cannot be generated, and on large sites there are not nearly enough of them.</li>
<li class=""><strong>Prioritised block insertion.</strong> Where an automated block does exist, decide what it points at. Showing the same forty URLs on every product page tells the crawler those forty are the site's most important destinations, and they are frequently the least important.</li>
</ul>
<p>The judgement to apply: an automated link block should promote something because it is relevant to the page, not because it fills a slot. If a component cannot be configured per page type, that is the finding.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="find-the-pages-that-were-never-linked-to">Find the pages that were never linked to<a href="https://docs.pruneseo.com/blog/internal-linking-strategy#find-the-pages-that-were-never-linked-to" class="hash-link" aria-label="Direct link to Find the pages that were never linked to" title="Direct link to Find the pages that were never linked to" translate="no">​</a></h2>
<p>The reverse problem is as common as the over-linked one. Orphan pages are ordinary in the output of any programmatic surface, and they are cheap to find once you have the crawl.</p>
<p>Take the site graph from your last crawl and count inbound internal links per URL. Then filter out the pages that are supposed to be orphans — login, legal, standalone campaign landing pages — and look at what is left. The results fall into two useful groups:</p>
<ul>
<li class="">Pages that genuinely deserve distribution and never got it. These are usually commercially valuable pages that were published and then forgotten, sitting in a category that has not been actively maintained since launch.</li>
<li class="">Pages with nothing to distribute. If a page has no plausible inbound link from anywhere on the site, it may be a page that should not exist, or a section that never had a place in the information architecture.</li>
</ul>
<p>The second group is the more valuable finding, because it points at a structural decision nobody has made rather than a gap somebody forgot to fill.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="turning-the-decisions-into-a-work-list">Turning the decisions into a work list<a href="https://docs.pruneseo.com/blog/internal-linking-strategy#turning-the-decisions-into-a-work-list" class="hash-link" aria-label="Direct link to Turning the decisions into a work list" title="Direct link to Turning the decisions into a work list" translate="no">​</a></h2>
<p>The output of an internal linking review should be a file, not a set of intentions. The format is deliberately minimal, and the empty anchor column is not an unfinished feature — the tool does not know what your product is called or which of two phrasings your customers use, so it proposes a source and leaves the wording to the person who owns the page.</p>
<div class="language-csv codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csv codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">target,suggested_source,suggested_anchor</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/guides/choosing-a-lens,/category/digital-cameras/lens-primes,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/guides/choosing-a-lens,/compare/lens-comparison-chart,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/products/lens-primes,/category/digital-cameras/lens-primes,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/products/lens-primes,/compare/lens-comparison-chart,</span><br></div></code></pre></div></div>
<p>Four rows. The first two repair links pointing at a consolidated page. The last two are new links, and their count is the interesting part: if a page only has two plausible sources on a site this size, it is not under-linked by accident, it has never had distribution.</p>
<p>The rows where no plausible source exists are the ones to read first. An empty result is a finding about the information architecture, and it will not be fixed by generating more links.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-not-to-do">What not to do<a href="https://docs.pruneseo.com/blog/internal-linking-strategy#what-not-to-do" class="hash-link" aria-label="Direct link to What not to do" title="Direct link to What not to do" translate="no">​</a></h2>
<p>Three common interventions look helpful and are not.</p>
<p><strong>Adding a block of links to every page in a cluster to raise its prominence.</strong> If the added links point at each other rather than at anything indexed or externally linked, you have built a closed loop. Crawlers gain nothing and the pages still carry no external signal.</p>
<p><strong>Rewriting anchors across the site to improve relevance.</strong> Bulk anchor rewriting is the kind of change that looks clever and reads as manipulation, particularly when applied to thousands of links at once. If you would not be comfortable seeing the before and after side by side in a review, do not ship it.</p>
<p><strong>Automatically linking every brand or product mention to the newest page of that type.</strong> This is how cannibalisation gets manufactured at scale. The newest page is not usually the canonical one, and every internal link is a vote for which page you think should rank.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="measure-the-structure-not-just-the-traffic">Measure the structure, not just the traffic<a href="https://docs.pruneseo.com/blog/internal-linking-strategy#measure-the-structure-not-just-the-traffic" class="hash-link" aria-label="Direct link to Measure the structure, not just the traffic" title="Direct link to Measure the structure, not just the traffic" translate="no">​</a></h2>
<p>Internal linking is one of the few technical changes whose effect can be checked without waiting months. Three measures, all available immediately after a crawl:</p>
<ul>
<li class=""><strong>Depth distribution.</strong> What share of your URLs sit within three clicks of a linked entry point, and has that moved?</li>
<li class=""><strong>Orphan count.</strong> How many URLs have zero inbound internal links, excluding the exceptions you declared?</li>
<li class=""><strong>Link concentration.</strong> What share of all internal links point at the top hundred URLs? A very high figure means the graph is a star and everything else is hanging off it.</li>
</ul>
<p>These three are structural, they change within a crawl cycle, and they do not depend on rankings moving. For a post about how to read them against the rest of the health picture, <a class="" href="https://docs.pruneseo.com/docs/guides/health-and-reporting">Health and reporting</a> is the relevant section of the <a class="" href="https://docs.pruneseo.com/docs/guides">User Guide</a>, and <a class="" href="https://docs.pruneseo.com/docs/features/crawl-budgets-and-allowances">crawl budgets and allowances</a> covers how the crawl that produces this graph is bounded.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/internal-linking-strategy#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Run one crawl, build the inbound link counts, and list the zero rows. That list is either your distribution backlog or your architecture problem, and it is usually cheaper to produce than any argument about authority flow will be.</p>]]></content:encoded>
            <category>Technical SEO</category>
            <category>Indexation</category>
        </item>
        <item>
            <title><![CDATA[Content Briefs That Survive Contact With a Crawler]]></title>
            <link>https://docs.pruneseo.com/blog/content-briefs-that-work</link>
            <guid>https://docs.pruneseo.com/blog/content-briefs-that-work</guid>
            <pubDate>Wed, 25 Nov 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Most content briefs describe a single page. On a large site a useful brief also decides what happens to the other 4,000 URLs in the same pattern.]]></description>
            <content:encoded><![CDATA[<p>A content brief is usually written as though the page it describes will be the only page of its kind: one URL, one target query, one word count, one canonical. That assumption is harmless on a site with two hundred pages and wrong on a site with four hundred thousand, because a brief for a single page is also a decision about every other page generated from the same template. This post covers what a brief has to settle besides the article itself — the pattern, the siblings, the internal links and the end state — and why the version written only for the writer is the one that quietly produces indexation waste at scale.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-brief-is-a-set-of-decisions-not-a-document">A brief is a set of decisions, not a document<a href="https://docs.pruneseo.com/blog/content-briefs-that-work#a-brief-is-a-set-of-decisions-not-a-document" class="hash-link" aria-label="Direct link to A brief is a set of decisions, not a document" title="Direct link to A brief is a set of decisions, not a document" translate="no">​</a></h2>
<p>Look at what most briefs actually contain: an intent summary, a target query set, a competitor list, a word count, some tone notes, and a handful of internal links to include. Every one of those fields is about the page. None of them is about the neighbourhood.</p>
<p>That gap matters because the page is never the only artefact. On a site with a location service, a comparison tool, a category tree or a jobs board, publishing one new page changes the size and the shape of a pattern that may already contain thousands of URLs. The writer is handed a brief for item number 4,001 in a family, and has no way of knowing that items 1 through 4,000 were mostly crawled and rejected.</p>
<blockquote>
<p>A brief that only describes the page is not incomplete. It is a brief that has quietly delegated the indexation decision to whatever the template happens to do.</p>
</blockquote>
<p>The fix is small. Add a section to the template that describes the pattern the page belongs to: how many siblings exist, what their shared verdict is, whether this one is an addition or a replacement, and what the total count will be after publication.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="put-the-indexation-verdict-in-the-brief">Put the indexation verdict in the brief<a href="https://docs.pruneseo.com/blog/content-briefs-that-work#put-the-indexation-verdict-in-the-brief" class="hash-link" aria-label="Direct link to Put the indexation verdict in the brief" title="Direct link to Put the indexation verdict in the brief" translate="no">​</a></h2>
<p>The single most valuable addition to a brief is one line of evidence about the pattern. Not an opinion, not a guideline. A measurement.</p>
<p>Suppose the location pattern has 12,400 URLs and a sampled inspection of 97 of them returns crawled but currently indexed for 71 per cent of them, with a 95 per cent Wilson lower bound of 0.61. That sentence tells an editor more than any content guideline, because it says the template is not the problem: Google is reading these pages and keeping most of them. It also tells the editor that a new page has a realistic chance of being indexed, which changes how much care the launch deserves.</p>
<p>Contrast it with the case where the same sample returns crawled but currently not indexed for 88 per cent of the family. Now the brief needs to say something very different: this page is a member of a pattern Google has already declined at scale, so publishing it is not a content decision, it is a decision to add one more URL to a group that needs fixing first.</p>
<table><thead><tr><th>Situation</th><th>What the brief should say</th></tr></thead><tbody><tr><td>Pattern is largely indexed</td><td>Proceed, link it properly, add it to the sitemap</td></tr><tr><td>Pattern is largely crawled but not indexed</td><td>Fix the pattern before publishing into it</td></tr><tr><td>Pattern was never crawled</td><td>Check discovery and robots before writing anything</td></tr><tr><td>Pattern is a mixed verdict</td><td>Treat as two briefs, one per sub-pattern, not one averaged rule</td></tr></tbody></table>
<p>The row that gets skipped is the last one. A heterogeneous sample does not average into a single editorial instruction, and a brief that tries to average produces a page written for a family that does not exist.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-template-decides-more-than-the-page">The template decides more than the page<a href="https://docs.pruneseo.com/blog/content-briefs-that-work#the-template-decides-more-than-the-page" class="hash-link" aria-label="Direct link to The template decides more than the page" title="Direct link to The template decides more than the page" translate="no">​</a></h2>
<p>On any large site, most of what a new page inherits is decided before a word is written. The template supplies the title tag construction, the heading hierarchy, the number of links in the footer, the pagination, the breadcrumb, the schema markup and the analytics parameters. Writers adjust within that; they do not control it.</p>
<p>Which means an editor arguing about headline length is optimising the wrong end of the system. If 80 per cent of a pattern's problems come from the template — a self-referencing canonical on a paginated view, a filter that adds a query string to every link, a footer link injected into all four thousand pages — then the brief for page 4,001 cannot fix any of it.</p>
<p>The practical instruction in the brief is therefore about routing, not writing: which template, which pattern, and what the shared verdict is. Where a page does not need to exist, the strongest editorial decision available is sometimes to not commission it, and a brief is the cheapest place to make that call because it happens before production rather than after it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-a-working-brief-looks-like">What a working brief looks like<a href="https://docs.pruneseo.com/blog/content-briefs-that-work#what-a-working-brief-looks-like" class="hash-link" aria-label="Direct link to What a working brief looks like" title="Direct link to What a working brief looks like" translate="no">​</a></h2>
<p>The template below is plain markdown on purpose. It is meant to be read by an editor, dropped into a project tool, and argued over.</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token title important punctuation" style="color:#393A34">#</span><span class="token title important"> Brief: [page title]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> Audience and intent</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Who is searching this and what are they trying to do</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> The single job this page does that no existing page does</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> Pattern</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Pattern name: [the pattern this URL belongs to]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Current size: [n] URLs</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Sampled verdict: [n of m inspected returned X]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Action: add to pattern / replace an existing URL / do not publish</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> End state</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Once live this pattern will contain [n + 1] URLs</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Sitemap: include in [which sitemap file]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Canonical: self-referencing</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Indexable: yes</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> Links out</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Links in from: [target pages]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Links out to: [priority pages]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Breadcrumb trail: [path]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> Never</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Do not link to this page from the global footer</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Do not add it to the legacy navigation block</span><br></div></code></pre></div></div>
<p>The last section is the one that does the work on a large site. Footer and navigation blocks are where orphan pages come from, and a brief that says where a page will be linked from — specifically — prevents the default outcome, which is that it gets linked from nowhere.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="volume-is-not-a-strategy">Volume is not a strategy<a href="https://docs.pruneseo.com/blog/content-briefs-that-work#volume-is-not-a-strategy" class="hash-link" aria-label="Direct link to Volume is not a strategy" title="Direct link to Volume is not a strategy" translate="no">​</a></h2>
<p>A large publisher will eventually have a conversation where the answer to every indexation complaint is to publish more. More long-tail pages, more locations, more comparisons, more glossary entries. Sometimes that is right. Often it is a way of avoiding the measurement that would tell you whether the existing set is worth keeping.</p>
<p>The arithmetic is unforgiving. If a pattern is at 30 per cent indexed and you add 50 per cent more URLs to it, the indexed count rises and the indexed share falls, and the crawl budget that feeds the pattern gets divided across a larger set of pages that mostly will not be rewarded. Nobody gets a better outcome. You have simply made the problem larger and the reporting window harder to read.</p>
<blockquote>
<p>Publishing more pages into a pattern Google is already declining converts a fixable problem into an intractable one.</p>
</blockquote>
<p>If the brief genuinely needs volume — a programmatic surface that has earned its place, with unique data per URL and a distinct query behind each one — the brief should say so and prove it. Otherwise the honest recommendation is fewer pages, better linked.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cannibalisation-is-a-brief-failure">Cannibalisation is a brief failure<a href="https://docs.pruneseo.com/blog/content-briefs-that-work#cannibalisation-is-a-brief-failure" class="hash-link" aria-label="Direct link to Cannibalisation is a brief failure" title="Direct link to Cannibalisation is a brief failure" translate="no">​</a></h2>
<p>Two briefs written for the same query set is a planning failure that shows up months later as two pages competing. By then the fix is expensive, because you are no longer deciding what to write, you are deciding which page to retire.</p>
<p>The cheap check is a set intersection: does the planned query set for page B substantially overlap page A's? If it does, one of them needs a different job. Not a different phrasing of the same job. A different job.</p>
<p>That check belongs in the brief review, before production, and it is easier when briefs live in one place. It is also easier when the pattern view is available next to them, because overlap is rarely confined to a single pattern. Two teams, two templates, one query set, and nobody noticed, is the normal way this happens on a large site.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-the-answer-is-not-to-write-it">When the answer is not to write it<a href="https://docs.pruneseo.com/blog/content-briefs-that-work#when-the-answer-is-not-to-write-it" class="hash-link" aria-label="Direct link to When the answer is not to write it" title="Direct link to When the answer is not to write it" translate="no">​</a></h2>
<p>The most useful brief on a large site is occasionally the one that concludes no new page should be produced. It is a legitimate output and it is almost never in anyone's reporting lines, which is part of why it is worth writing explicitly.</p>
<p>The justification is a measurement: this query is already served by a page that ranks; the pattern is at 94 per cent indexed and adding one more URL consumes crawl capacity; the same intent is reachable through an existing section. That is not a content failure. It is the brief doing the job it was commissioned for, one level higher than expected.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-start">Where to start<a href="https://docs.pruneseo.com/blog/content-briefs-that-work#where-to-start" class="hash-link" aria-label="Direct link to Where to start" title="Direct link to Where to start" translate="no">​</a></h2>
<p>Take the last brief that was commissioned on your site and check whether it contains a pattern, a sampled verdict and an end state. If it does not, that is the gap, and it is a template change rather than a research project.</p>
<p>To see what a sampled verdict looks like before you write one, the <a class="" href="https://docs.pruneseo.com/docs/guides">User Guide</a> covers connecting a property and reading the first results, and <a class="" href="https://docs.pruneseo.com/docs/guides/triage-and-pattern-analysis">Triage and pattern analysis</a> is where patterns, verdicts and the five result buckets are explained. For sites publishing at scale, <a class="" href="https://docs.pruneseo.com/docs/features/url-pattern-clustering">URL pattern clustering</a> sets out how a generated surface is grouped before anybody decides which part of it to keep.</p>]]></content:encoded>
            <category>Publishing</category>
            <category>Programmatic SEO</category>
        </item>
    </channel>
</rss>