← all articles

How to run a content refresh that actually works

Most content refreshes are a waste of an afternoon. Someone changes the year in the title, swaps a stat, bumps the “last updated” date and republishes. Nothing moves. Refreshing does work, but only when you refresh the right pages for the right reason and change the parts of the page that caused the decline.

This tutorial is for people who run a site with at least a few dozen published pages and some search history. If you have 12 articles and no traffic data, go and read topic clusters on a site with twelve articles first. A refresh needs a baseline to beat.

By the end you’ll have a repeatable process: a ranked list of pages worth touching, a diagnosis for each, a rewrite brief, a clean republish, and a measurement window so you know if it worked. I run this on theseodesk.com and the sister sites roughly monthly.

What you need

  • Google Search Console access for the property, with at least 6 months of data. it’s free.
  • GA4 or any analytics tool that shows sessions and conversions per landing page. also free.
  • Python 3.10+ and the google-api-python-client and pandas packages, if you want the export script below.
  • A crawler. Screaming Frog is free up to 500 URLs, which covers most sites this size. Ahrefs Webmaster Tools is a free alternative for backlink checks on your own domain.
  • A spreadsheet. Google Sheets is fine.
  • Edit access to your CMS or repo, and the ability to see revision history so you can roll back.
  • Two to three hours per batch of five pages, more if you are rewriting.

Step by step

1. Export 16 months of page-level data

Action: pull clicks, impressions, CTR and average position per page for two windows, the last 90 days and the same 90 days a year earlier (or the 90 days before that if your site is younger). The Search Console UI works, but the API removes the 1,000 row cap on exports. The Search Analytics query reference documents the request shape.

from googleapiclient.discovery import build
from google.oauth2 import service_account
import pandas as pd

SITE = "sc-domain:example.com"
creds = service_account.Credentials.from_service_account_file(
    "sa.json", scopes=["https://www.googleapis.com/auth/webmasters.readonly"])
svc = build("searchconsole", "v1", credentials=creds)

def pull(start, end):
    rows, start_row = [], 0
    while True:
        body = {"startDate": start, "endDate": end,
                "dimensions": ["page"], "rowLimit": 25000,
                "startRow": start_row}
        r = svc.searchanalytics().query(siteUrl=SITE, body=body).execute()
        batch = r.get("rows", [])
        rows += [{"page": x["keys"][0], "clicks": x["clicks"],
                  "impr": x["impressions"], "ctr": x["ctr"],
                  "pos": x["position"]} for x in batch]
        if len(batch) < 25000:
            break
        start_row += 25000
    return pd.DataFrame(rows)

new = pull("2026-07-01", "2026-09-28")
old = pull("2025-07-01", "2025-09-28")
df = old.merge(new, on="page", how="outer", suffixes=("_old", "_new")).fillna(0)
df.to_csv("gsc_compare.csv", index=False)

Expected output: a CSV with one row per page and old versus new columns side by side. add the service account email as a user on the Search Console property first, otherwise you get a 403.

If it breaks: a 403 means the service account isn’t a property user. an empty result usually means the SITE string is wrong. Domain properties need the sc-domain: prefix, URL-prefix properties need the full URL with trailing slash.

2. Rank candidates by recoverable clicks

Action: in the spreadsheet add click_change = clicks_new - clicks_old and impr_change. Sort ascending on click_change. The pages you want sit in one of three groups:

  • pages that lost clicks and impressions together: the query demand or your ranking fell.
  • pages that kept impressions but lost clicks: CTR problem, often a title or a SERP feature that ate the result.
  • pages sitting at position 8 to 20 with decent impressions: almost ranking, cheapest to push over.

Expected output: a shortlist of 5 to 10 pages. Ignore anything with under a couple of hundred impressions in the window. the data is too noisy to diagnose.

If it breaks: if every page looks like it lost traffic, check whether your whole site dropped. A sitewide decline isn’t a refresh problem.

3. Diagnose why each page fell

Action: open each page and search its main query yourself in a private window. Note who ranks above you, what format they use (guide, list, tool, forum thread) and whether the SERP has changed shape. Then check the page for the usual suspects: outdated screenshots, dead outbound links, claims that were true in 2024 and aren’t now, missing sections that every competitor has.

Write one line per page in a “diagnosis” column. Keep it blunt: “intent shifted to comparison table”, “pricing wrong”, “thin, 400 words, competitors 1,800”.

Expected output: a diagnosis for every shortlisted page. If you can’t write one, don’t refresh that page yet.

If it breaks: sometimes the honest diagnosis is “this page should not exist” or “this page needs a full rewrite rather than an edit”. I wrote up that decision in when to rewrite a page instead of building links. If the page is thin across the board, the process in how to audit and fix thin content at scale fits better than a refresh.

4. Check what Google says about quality before you write

Action: reread Google’s own guidance once per batch. The helpful content guidance in Search Central asks whether the content shows first-hand experience, whether it gives a complete answer, and whether a reader would feel they learned enough. Use those as the checklist for your brief rather than word count targets.

Expected output: a short brief per page with three columns: what to add, what to cut, what to correct. For a typical tutorial that’s 3 to 6 concrete changes.

If it breaks: if your brief is just “make it longer”, stop. Length isn’t a fix. Find the missing question the reader has.

5. Do the rewrite, and keep the URL

Action: edit the existing page rather than publishing a new one. Keep the URL, keep the slug, keep the internal links pointing at it. Fix the title and meta description if CTR was the problem, and write them for a human, not for a keyword.

If you must change the URL, set a 301 and read what a redirect does to a link before you do. Google’s own redirect documentation covers the mechanics.

Expected output: an updated draft in staging or a branch, with the revision history intact.

If it breaks: if you catch yourself rewriting more than 60 percent of the page, you are writing a new article. That’s fine, but then check that no other page on the site targets the same query. If one does, see canonical tags explained before you end up with two pages competing.

Action: crawl the page with Screaming Frog in list mode. Check outbound links for 404s and redirects, confirm the canonical points at itself, confirm it’s indexable, and confirm the internal links to it still resolve. Then look at Core Web Vitals for that template, since a rewrite that adds four uncompressed images can undo the work. The background is in what are Core Web Vitals in 2026.

# quick status check on a list of refreshed URLs
while read -r url; do
  code=$(curl -s -o /dev/null -w "%{http_code}" -L "$url")
  echo "$code $url"
done < refreshed.txt

Expected output: every URL returns 200 and every outbound link on the page is clean.

If it breaks: a 200 in curl but “Crawled, currently not indexed” in Search Console means the page needs a stronger signal. add internal links from your best pages and request indexing once via the URL Inspection tool.

7. Update the date honestly

Action: change the visible “last updated” date only if the change is substantive. Google’s guidance on publication dates says to show a visible date and keep structured data consistent with it, and it’s not a place to game. My rule: if I changed facts, structure or advice, the date moves. If I fixed a typo, it doesn’t.

Expected output: the on-page date, the dateModified in your structured data and the sitemap lastmod all agree.

If it breaks: if your CMS stamps a new date on every save, turn that off or override it. otherwise your sitemap tells Google every page changes daily, and it learns to ignore you.

8. Log it and measure at 6 weeks

Action: record the date, the URL, the diagnosis and the changes made in a refresh log (a plain Google Sheet tab is enough). Capture the baseline clicks, impressions and average position for the previous 28 days. Then leave it alone. Re-pull the same numbers at 2 weeks and 6 weeks.

Expected output: a before and after row per page. At 6 weeks you’ll usually see one of three things: clear improvement, flat, or worse. Flat after 6 weeks with a solid rewrite tells you the page was capped by something else, most often links or intent.

If it breaks: if a page got worse, roll back from revision history and compare the two versions against the query intent again. It happens, and a rollback log is cheaper than guessing.

Common pitfalls

  • Refreshing by date instead of by data. “Everything older than 12 months” is a calendar, not a diagnosis. Some old pages are fine and some new ones are broken.
  • Touching too many pages at once. If you change 40 pages in a week you can’t tell which change helped, and you can’t separate it from an algorithm update. Batches of five give you a readable result.
  • Faking freshness. Bumping dates without real changes is easy to spot and Google’s docs are explicit about publication dates being accurate.
  • Ignoring links. A page can be perfectly written and still sit at position 11 because a competitor has ten times the referring domains. If the diagnosis says links, the refresh is only half the job. My view on what those links are worth is in what a link is worth on a page with no traffic.
  • Changing the URL for no reason. Every redirect is a small tax. Keep the URL unless it’s genuinely wrong.

Scaling this

At 10 pages a month you can do all of this by hand in a spreadsheet. The API export is optional and the whole process fits in one working session.

At 100 pages a month the bottleneck moves from analysis to editing. You need a written brief template, a second person to check facts, and the Python export running on a schedule so the shortlist builds itself. I’ve used LLM tools to summarise SERP changes into draft briefs, and the practical notes on that live on our sister site AI Tool Gazette. Treat the output as a first draft of the brief, never the finished page.

At 1000 pages you stop refreshing individually. You segment by template and intent, refresh the template first (headings, schema, internal link blocks, image handling) and only then hand-edit the top pages by revenue. You also need guardrails in code: a check that blocks a date change when the diff is under a set number of words, and a monitor that alerts when a refreshed cohort underperforms an untouched control group.

Where to go next

The full index is at /blog/.

Written by Xavier Fok

disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-09-30.

for SEOs
Tracking rankings or scraping SERPs at scale?

Rank checkers and SERP crawlers get blocked and geo-skewed fast on datacenter IPs. Singapore Mobile Proxy runs real 4G/5G mobile IPs that search engines still trust, so your position data stays clean.

see plans →
read on
More from The SEO Desk

Technical SEO, link building, content and SERP strategy, and tool reviews for people who ship growth.

browse all articles →