---
title: A Website That Does Its Own SEO
date: '2026-10-25'
description: 'Google says to review every AI-written page by hand. I''m building a loop that won''t: it reads my analytics, edits my sites and measures what worked.'
author: Pablo Fernandez
tags:
  - SEO
  - AI Agents
  - Analytics
  - Automation
  - Search Console
source: 'https://www.pablofr.com/blog/a-website-that-does-its-own-seo'
---
Every Monday at six in the morning, an email tells me the state of my two websites. It comes from [one command that joins five analytics tools](/blog/five-analytics-tools-one-report), and it ends with a list of things to do: a broken link, a page sitting at position 11 for a query people search for, a keyword with thousands of searches a month that I haven't written about. Then I read it, and most weeks I do none of it.

The obvious next step is to stop being the bottleneck. The data is already in one place, each finding already says what's wrong, and a model can write a title, a landing page or a post. So the system I'm building next closes the loop: it reads the report, picks what to change, changes the site, waits, measures, and uses what it learned to pick the next change. No human approves anything.

Google's documentation on AI-generated content says this, in so many words:

> It is critical to manually factcheck and review all AI-generated content for accuracy and trustworthiness before publishing.
>
> — Google Search Central, Using generative AI content

That review is exactly the step my loop removes. And the same documentation points at a spam policy, [scaled content abuse](https://developers.google.com/search/docs/essentials/spam-policies#scaled-content), whose first example is "using generative AI tools or other similar tools to generate many pages without adding value for users". So the question this post has to answer is whether a fully automatic SEO loop can be built at all without turning into the thing Google penalises, and on traffic as small as mine, whether it can learn anything.

## The loop

Written down, the loop is six steps, and only one of them is new.

```
   ┌─▶  read     the weekly report, every source
   │      │
   │      ▼
   │    decide   one change, and what it should move
   │      │
   │      ▼
   │    change   commit, gates, deploy
   │      │
   │      ▼
   │    wait     until Google has seen it
   │      │
   │      ▼
   │    measure  the page before vs after
   │      │
   └──────┘      the result goes into the next decision
```

*The weekly report already does the reading; the loop adds deciding, changing and measuring.*

Reading is done: [pfrctl analytics](/tools/pfrctl) already turns onedollarstats, Search Console, Ahrefs, Keyword Planner and Cloudflare into one report per site, critical first. Changing is done too, in a sense: the posts on this site are MDX files in a repository, and a push to `main` deploys them. What doesn't exist yet is the part in the middle that decides, and the part at the end that knows whether a decision was any good.

## Four kinds of change

Not every change carries the same risk, and the loop shouldn't treat them the same. I've split what it may do into four kinds, ordered by how easy a mistake is to see and to undo.

| Change | Comes from | How it can go wrong | How it's undone |
|---|---|---|---|
| Technical fixes | Ahrefs Site Audit, Cloudflare | almost never: a link is broken or it isn't | the next crawl confirms it |
| Titles and meta descriptions | Search Console positions 5 to 20, low CTR | a worse title for a page that already ranks | revert the commit |
| Buzontemporal landing pages | Keyword Planner, per language | thin pages in many languages: the scaled pattern | delete the page, or `noindex` it |
| New posts | Keyword Planner gaps on this site's topics | a wrong fact, an invented number, a post nobody needed | unpublish |

Technical fixes are the easy start. Today's report for this site has one page linking to a broken page and four URLs on the apex domain or plain http that redirect. A fix is deterministic and the next crawl either confirms it or doesn't. There's some judgment even here: some external links answer 403 only to crawlers' user agents, which is the target's anti-bot and not a broken link, and the loop has to know to leave those alone.

Titles and descriptions are the most promising of the four for a small site, because they act on pages that already have impressions. [Alternatives to Jev](/blog/alternatives-to-jev) had 41 impressions in the last 28 days at an average position of 11.6, and no clicks. A title that matches the query better is the textbook change for a page like that. The one rule the loop can never break here is the slug: a title changes, the URL doesn't.

Landing pages for [Buzontemporal](/blog/building-a-temporary-email-service) are where scaled content abuse stops being a hypothetical. The site is already live in 28 languages, and the report runs six Keyword Planner searches for it, one per language that matters. A loop that answers every keyword idea with a page, in every language, is exactly the pattern the spam policy describes, translation included: its examples name "automated transformations like synonymizing, translating". So a new landing page has to be a page someone would want even if Google didn't exist, and there has to be a limit on how many appear.

New posts carry the most risk, for a reason this blog's own rules make plain.

## The review becomes the gates

My writing guide for this blog has a checklist, and two of its rules were written for exactly this failure: never invent a number, and never invent an API. Every figure in a post must trace back to a measurement or a named source; every code sample must be real. Those are the two things a language model gets wrong most confidently, and they're what Google means by "factcheck".

A fully automatic loop doesn't drop that review. It has to turn it into checks that run before every deploy, and reject the change if any fails:

- **It compiles:** the post builds with the same MDX pipeline production uses. That check already runs on every push.
- **Every link is live:** each URL is fetched, and a redirect is replaced by its final address.
- **Every number has a source:** a figure in the text must appear in the data the loop read (the report, a Keyword Planner response, an Ahrefs export) or in a page it cites. A number that matches nothing is an invented number.
- **It follows the house style:** the description fits in a search result, no dashes, no emojis, no hype words. These are mechanical and cheap.
- **It stays within budget:** a fixed number of new pages per week per site, because the spam policy is about scale, and the loop's natural tendency is to scale.
- **It says who wrote it:** a page the loop wrote says so. Google suggests telling readers how a piece of content was created; it's also the only honest option on a blog where every other post is mine.

The gates don't make a fully automatic post as good as one I wrote. They make the specific failures that Google and my own guide warn about fail the deploy instead of reaching the page. Whether that's enough is what the measuring is for.

```
  change
    │
    ├─ compiles?          no ─▶ reject
    ├─ links live?        no ─▶ reject
    ├─ numbers sourced?   no ─▶ reject
    ├─ house style?       no ─▶ reject
    ├─ within budget?     no ─▶ next week
    ▼
  deploy
```

*Every change the loop makes passes the same checks a careful reviewer would run, or doesn't ship.*

## Seven clicks are not a signal

The second problem is quieter and harder. A self-improving loop needs to know whether a change helped, and the plan is the simplest version: for each page it changed, compare the page's numbers in Search Console before and after.

In the last 28 days, this site got 7 clicks and 271 impressions from Google. Buzontemporal's Search Console data starts on 23 October, with 4 clicks and 15 impressions. With numbers like that, a new title that "lifted clicks from 1 to 2" says nothing. Worse, the whole site moves when Google changes something, and a before/after on one page can't tell that from its own change.

So the measuring has to be more careful than the idea:

- **Measure what has volume.** Impressions and average position move long before clicks do, and there are about forty times more of them.
- **Log every change.** The page, what changed, when it shipped, and what it was meant to move. Without the log, there is nothing to learn from.
- **Wait long enough.** Search Console runs two or three days behind and Google takes its time to recrawl and re-rank. A change is judged after weeks, not days.
- **Compare against pages that didn't change.** If every page on the site lost a position the same week, the change didn't cause it.
- **Undo what made things worse.** A title that lost impressions goes back, and that result is recorded too.

The "self-improving" part is that log. Over time it says which kinds of change worked on which kinds of page: whether rewriting titles of pages at positions 8 to 12 helps more than adding a section, whether a landing page in Italian earns impressions as fast as one in Spanish. With this little traffic it will learn slowly. That's a reason to start with the changes whose results show up even at low volume: a fixed broken link and a page that finally gets indexed don't need statistics.

## What Ahrefs Lite adds

The next step is Ahrefs' [Lite plan](https://ahrefs.com/pricing), which changes two things for the loop.

The first is data the loop can't get anywhere else. Site Explorer and Keywords Explorer show who ranks for a query and with which page, which is what a decision about a new post or landing page actually needs: not just "people search for this", but "these are the pages I'd be competing with". Rank Tracker follows up to 750 keywords, and [by default it updates them weekly](https://help.ahrefs.com/en/articles/604245-how-frequently-do-my-tracked-keywords-get-updated), which matches the loop's rhythm and gives positions for queries where Search Console has too few impressions to say much. History goes back six months.

The second is access. Lite includes API and [MCP](https://docs.ahrefs.com/en/mcp/docs/introduction) access, billed in API units from a monthly allowance. That gives the loop a key it can use from a server, and it means the model can ask Ahrefs its own follow-up questions instead of working only from what the report printed.

```
  onedollarstats      visits, events, errors      ─┐
  Search Console      impressions, positions      ─┤
  Keyword Planner     volumes, trends             ─┤
  Cloudflare          5xx, cache, firewall        ─┼─▶  loop
  Ahrefs Site Audit   broken links, slow pages    ─┤
  Ahrefs Lite         who ranks, tracked keywords ─┘
```

*The loop reads everything the weekly report reads, plus what Ahrefs Lite adds: competitors, tracked positions and an API.*

## What this is a bet on

Writing it down, the loop is less about AI than about the pieces around it. The model's part is small: choose a change from a list of findings and write a title or a page. Everything that makes that safe to run without me is ordinary code: a report that puts the critical first, gates that check numbers and links, a budget, a log, a revert.

I'm making it fully automatic on purpose, because a loop that waits for my approval is the Monday email again, with more steps. The bet is that the review Google asks for can be written as checks, and that the measurements will say early if it can't. If a few months of logged changes show the loop's pages earning impressions and its titles holding their positions, it worked. If they show pages Google ignores and titles that slide, the log will say that too, and every change it made can be undone from a commit.
