Home› Blog› SEO› How to Refresh Old Blog Posts for SEO and AI Search
SEO

How to Refresh Old Blog Posts for SEO and AI Search

Refresh old blog posts in six steps, with before and after data from nine MV3 posts that rose from 0.7 to 9.5 visuals per 1,000 words.

Vance Moore
Vance Moore
October 16, 2026
7 min read
1,633 words
How to Refresh Old Blog Posts for SEO and AI Search
Quick answer. Refresh an old blog post in six steps: pick pages with impressions but no clicks, measure visual density, rewrite the opening as a short answer, add a visual every 300 words, re-measure the live page, and log a Search Console baseline. MV3 did this on nine posts; ranking results are too early to report.

To refresh old blog posts well, start by admitting how they usually fail: a wall of text that nobody scans and no passage a search engine can lift cleanly. A post that was fine when you published it can read as dense and dated a year later, even when every fact still holds. This week we refreshed nine of our own posts, including our GEO citations post and our PPC budget framework, and measured the change. Disclosure: MV3 Marketing sells content services, so we gain if you hire help with this. The method below works without us.

Horizontal bar chart: visuals per 1,000 words rose from a mean of 0.7 to 9.5 across nine refreshed MV3 posts, every post above the target of 5
MEASURED: all nine posts crossed our 5-per-1,000-words target. Source: MV3 backups (2026-10-09) vs live content (2026-10-11), n = 9 posts.

Before you start

  • Time: about 30 minutes to measure and plan, plus writing time per post.
  • Tools: Search Console access, Python 3 with the requests package, an image or chart tool.
  • Level: beginner to intermediate.
  • You will end with: a measured before and after for each post and a dated baseline to judge results later.
Diagram of the six-step refresh loop: pick, measure, rewrite the top, add visuals, verify, log a baseline
The loop we ran on all nine posts. Each step leaves a number or file behind.

Step 1: Pick posts with impressions but no clicks

Open Search Console, filter by page, and set the last 28 days. Sort by impressions. Posts that show up in results but earn no clicks are your first candidates, because people already see them and skip them. Save the export, since it is your baseline.

Expected result: a list of pages with impressions, clicks and average position. Common mistake: choosing posts by age alone. Age tells you nothing about whether a post is seen.

Bar chart of 28-day search impressions for nine MV3 posts, from 106 to 504 each, all with zero clicks
MEASURED baseline: 2,376 impressions and 0 clicks across the nine posts. Source: Search Console API, sc-domain:mv3marketing.com, 2026-09-12 to 2026-10-09, n = 9 pages.

Step 2: Measure visual density

Count images, tables, code blocks and video per 1,000 words, plus the longest run of words with none of them. The script below does this for any URL. We ran it on 2026-10-11 against one refreshed post and one post we have not refreshed.

import re, sys, requests
from html.parser import HTMLParser

SKIP = {"script", "style", "nav", "footer", "aside", "form", "noscript"}

class Counter(HTMLParser):
    def __init__(self):
        super().__init__()
        self.words, self.marks, self.skip, self.art = 0, [], 0, 0
    def handle_starttag(self, tag, a):
        a = dict(a)
        if tag in SKIP: self.skip += 1
        if tag == "article": self.art += 1
        if not (self.art and not self.skip): return
        junk = re.search(r"logo|avatar|icon|emoji", (a.get("src") or "") + (a.get("class") or ""), re.I)
        small = (a.get("width") or "999").isdigit() and int(a.get("width") or 999) < 200
        if tag in ("img", "table", "pre", "video") and not junk and not small:
            self.marks.append(self.words)
    def handle_endtag(self, tag):
        if tag in SKIP and self.skip: self.skip -= 1
        if tag == "article" and self.art: self.art -= 1
    def handle_data(self, text):
        if self.art and not self.skip: self.words += len(text.split())

def visuals_per_1000(url):
    html = requests.get(url, headers={"User-Agent": "Mozilla/5.0 Chrome/124.0"}, timeout=60).text
    c = Counter(); c.feed(html)
    edges = [0] + sorted(c.marks) + [c.words]
    gap = max(b - a for a, b in zip(edges, edges[1:]))
    return c.words, len(c.marks), round(len(c.marks) / max(c.words, 1) * 1000, 2), gap

if __name__ == "__main__":
    for u in sys.argv[1:]:
        w, v, d, g = visuals_per_1000(u)
        print(f"{u.rstrip('/').split('/')[-1][:38]:<38} words={w} visuals={v} per_1k={d} longest_gap={g}")

Run it with python vpk.py URL1 URL2. Output:

ppc-budget-management-framework        words=2952 visuals=17 per_1k=5.76 longest_gap=430
seo-migration-checklist-b2b-saas       words=2359 visuals=2 per_1k=0.85 longest_gap=1072
b2b-seo-strategy-organic-pipeline      words=4261 visuals=28 per_1k=6.57 longest_gap=371

Expected result: the refreshed PPC post scores 5.76 with a longest gap of 430 words; the untouched migration checklist scores 0.85 with a gap of 1,072. Common mistake: counting logos and icons. The script skips small and icon-like images.

Step 3: Rewrite the top of the post

Put a 40 to 60 word answer first, in a labelled box. State the outcome, the time and the tools. Then make each H2 a plain question or a numbered step. A reader, and a machine, should be able to quote that opening without any other context.

On our PPC post the opening was a long paragraph. After the rewrite it starts with a quick answer box, then a short list of what you need.

Side-by-side screenshots of one MV3 post: before, a table and body text only; after, a bar chart with the source line printed inside the image and a caption beneath
Annotated screenshots of our PPC budget post at 1280 px. Box 1: the only visual before. Boxes 2 and 3: a sourced chart and its caption after. Captured 2026-10-09 and 2026-10-10.

Expected result: the first screen answers the question. Common mistake: burying the answer under a story. Lead with it and tell the story later.

Step 4: Add a visual every 300 words, evidence first

Add a visual wherever a claim could be shown. Prefer evidence: a chart drawn from data you hold, with the source and date printed in the image, or an annotated screenshot of something real. Use diagrams for sequences. Give each a one-sentence caption and a descriptive alt text. Keep images wide enough to read on mobile, at least 1,200 pixels.

Keep every existing fact, source and URL while you do this. The refresh changes how the page is built, not what it claims. If a number cannot be traced to a source and a date, remove it or label it as an estimate. A new chart built on a shaky figure makes the page worse, not better.

Three of our posts show the effect. The GEO post had one visual in 2,804 words, a 2,692 word stretch with none. After the refresh its longest gap was 238 words.

Strip chart showing visual positions in three posts before and after the refresh: longest gaps fell from 2,692 to 238, 1,005 to 382 and 738 to 359 words
MEASURED: the longest run of text without a visual, before and after. Source: MV3 backups (2026-10-09) vs live content (2026-10-11), n = 3 of the 9 posts.

Expected result: no gap longer than 450 words. Common mistake: decoration. A stock photo is not evidence and does not count.

Step 5: Re-measure the live page

Publish, clear the cache, and run the script again on the public URL. Check the page on a phone width as well. We report two counts because they answer different questions. The markup count leaves table and code text out of the word total. The rendered count uses all visible article text, so its ratio is lower.

Visuals per 1,000 words, nine MV3 posts
Post Before (markup) After (markup) After (rendered page)
Multi-client social media 1.1 12.2 7.9
AI to write SEO content 1.1 12.0 6.5
Customer journey automation 0.0 11.9 6.4
GEO citations by engine 0.4 10.5 7.1
WhatConverts review 1.5 9.0 7.0
B2B SEO strategy 0.0 8.5 6.2
What is an AI BDR 0.0 7.5 5.7
ABM: automate vs human 1.2 7.3 5.6
PPC budget framework 0.9 6.7 5.2
Mean of nine 0.7 9.5 6.4

Source: MV3 original.html backups (2026-10-09), live REST content and rendered pages (2026-10-11), n = 9 posts. Expected result: at least 5 on the rendered count. Common mistake: comparing a before measured one way with an after measured another way.

Step 6: Log a baseline, then wait

Record the date each refresh went live and keep the Search Console export from Step 1. Our refreshes went live on 2026-10-10 and 2026-10-11, but Search Console data ended on 2026-10-09 when we checked. That leaves no post-refresh days, so we cannot say anything about rankings or clicks yet.

Search Console data ran two days behind when we checked, and a short window proves little. Write down what you expect, pick a review date, and compare like with like: the same pages, the same 28 day length, the same query filters.

Line chart of daily search impressions for nine MV3 posts from 12 September to 9 October with a shaded band marking the October 10 to 11 refresh dates and no data after it
Baseline window only. Source: Search Console API, sc-domain:mv3marketing.com, 2026-09-12 to 2026-10-09, n = 9 pages.

Expected result: a dated baseline file. Common mistake: judging a refresh after two days. We will compare at least 28 days after launch.

Troubleshooting

Common problems when measuring a refresh
Symptom Likely cause Fix
Words = 0 The theme has no article tag Change the selector in the script to your content wrapper
Score looks too high Icons or tracking images counted Skip small images and icon class names
Browser count is lower than the script Images load by JavaScript Measure the rendered page with a headless browser
Charts unreadable on a phone Image narrower than 1,200 pixels Re-render wider and enlarge the labels

Checklist recap

  • Export Search Console pages and save the baseline.
  • Run the script and record words, visuals, per 1,000 and longest gap.
  • Put a 40 to 60 word answer first.
  • Add a visual at least every 450 words, with half as evidence.
  • Re-measure the live page and check mobile.
  • Log the go-live date and wait 28 days.

FAQ

Does refreshing a post get it cited by AI search?

We cannot show that yet. We have no post-refresh Search Console days and did not track AI citations. We wrote short self-contained answers and sourced charts, but whether engines cite them is untested. Our GEO citation gap framework covers how to check.

How many visuals should an old post have?

Our working target is 5 per 1,000 words, with no gap over 450 words. That is our own standard, not a Google rule. The nine posts reached a mean of 6.4 on the rendered count.

Should I change the URL when I refresh?

We kept every URL. A stable address keeps its history, and the refresh changes the content, not the location. For citation basics see how to get cited by ChatGPT and Perplexity.

Want this done across your whole archive? Book a call with MV3 Marketing.

Vance Moore
Vance Moore LinkedIn
Chief Growth Officer, MV3 Marketing

Vance Moore is Chief Growth Officer at MV3 Marketing and leads its team of SEO professionals, engineers and auditors.

Ready to audit your organic growth opportunity?

$2,500 flat. 5 business days. Six deliverables tied to pipeline , not rankings. No retainer required.

Get the Organic Growth Audit →

Turn Your Organic Channel into a Revenue Engine.

The MV3 SEO Audit maps your full organic opportunity in 5 business days.

Get the Audit →