Back to writing

I didn't plan this SEO pipeline, it kind of grew itself.

Chris

Chris /

I do SEO work for a few clients, and the monthly report is the thing they actually pay for. For ages I was hand assembling it.. which is fine once, miserable every month. This post is about how that turned into a little pipeline of Claude skills and scripts, and how each piece quietly made the next one easier to build. People call this compound engineering and honestly the name fits.

I want to be upfront though: I never sat down and designed this. I built one small thing, used it, got annoyed at the next bit of manual work, and built the thing that removed that. Repeat for three months. The git log tells the story better than I could, so I'm going to lean on it.

It started with a scraper

The first commit was a Python scraper and one skill, /seo-audit. Point it at a sitemap, it pulls every page's title and meta description, and flags the ones that are too long, too short or missing. Dead simple rules (title 30 to 60 characters, description 120 to 160).

The bit that mattered wasn't really the audit though, it was a tiny decision: the audit writes out a flagged-urls.txt alongside the human readable report. One URL per line, no markdown, nothing clever. At the time it felt like overkill. Turned out that little machine readable handle was the first link in the chain, the next tool just reads that file and gets on with it.

Pushing work down to scripts

Next thing I noticed was that I kept asking the model to do the same deterministic stuff over and over. Diff the old meta against the suggested meta. Check the new titles actually fit the length rules. That's not judgement, that's just rules, and the model is slower and a bit wobblier at it than a script would be.

So out came a bash script to diff the meta data, and a little python script to verify the generated meta data. This is the rule I sort of stumbled into and now use everywhere: if the LLM does the same exact thing twice and the output is deterministic, carve it out into a script. Keep the model for the fuzzy work (writing copy, making the judgement calls) and let bash and python handle the boring reliable bits. Every time I push that line down, the whole thing gets faster, cheaper, and I trust it a bit more.

This is what we mean when we say engineered AI rather than AI theatre. The clever bit isn't pointing a model at the problem and hoping, it's being honest about which parts genuinely need intelligence and which parts just need a script that does the same thing every time. Most of the reliability comes from being strict about that line.

The wall, and a slightly cheeky way round it

The real SEO signal lives in Ahrefs, not in my scraper. Trouble is Ahrefs sits behind Cloudflare, and a headless browser just gets the "verifying you are human" page every single time.

The fix I landed on still makes me grin. Instead of fighting it, /ahrefs-audit drives my own already logged in Brave browser over its debugging port. No new API keys, no extra cost, and the session is already authenticated because.. well, it's me. It reads the site audit issue list, then drills into each error and grabs the affected pages.

There's one nice iteration buried in here. The affected pages table is fine to scrape off the page when it's small, but the big issues have hundreds of rows and the DOM scraping got flaky fast. Then I realised the page itself fires an API call to load that table, so now I just grab that request and replay it asking for everything in one go. Loads more solid than scraping rendered HTML. That "replay the API instead of scraping the DOM" trick then showed up again and again once I'd seen it the first time.

The payoff, where it all comes together

Then /ahrefs-report, the thing this was all building towards. It eats everything underneath it: the issue data from the audit, the affected pages, plus live traffic and ranking numbers pulled from that same Brave session. Scripts work out which dates to compare, diff this month against last, and figure out what actually got fixed. The model does the part that's genuinely hard: deciding what counts as a "win", spotting that five separate broken link errors all trace back to one deleted product page (and writing that up as a single line rather than five), and turning the whole lot into something a client can actually read.

There's a split in here I'm quietly pleased with. It writes two files from the same data. One for the client, with no file paths or internal jargon, reads like a person wrote it. And one for me, with every slug, path and script reference I need to go and fix things. Same facts, two audiences.

[SCREENSHOT: the client-facing report in Notion, top section / visibility scorecard]

[SCREENSHOT: a per-issue block with the affected pages table and recommendation]

The messy middle is the actual point

Here's the bit I think matters for anyone building with agents. Look at one day in the log:

  • ship /ahrefs-report
  • same day, a real run turns up broken Site Explorer URLs and a flaky extractor, fix both
  • notice the scorecard is rendering every change as a blank.. backfill the month on month deltas off a history API
  • realise the reports need a baseline to compare against, so start capturing a snapshot on every crawl, and backfill last month
  • the data outgrew its folder layout, so refactor the whole lot into ahrefs/domain/date/

None of that was planned. Each fix only became obvious because the previous version was live and gently annoying me. That's the compounding right there. You don't design the pipeline up front, you build the smallest useful thing, run it for real, and let the friction tell you what to build next.

What I'd actually take away

A few patterns that turned out to be useful well beyond SEO:

  • design your outputs to be someone else's inputs (that little flagged-urls.txt)
  • keep pushing the line between "the model does it" and "a script does it" downwards
  • separate the slow moving stuff (reference docs) from the fast moving stuff (this month's crawl)
  • replay APIs instead of scraping the DOM the moment you can find the endpoint
  • when something can't be measured yet, leave an honest gap rather than inventing a number

Anyway, that's the tour. It's maybe a dozen scripts and three skills now, it gets a little cheaper and a little better every month, and the nicest part is I barely touch the monthly report any more.. it mostly assembles itself and I just polish the words.

And that's really the whole point of how we build at Pixelhop. Not a clever demo that falls over the second a client touches it, but a real working tool that earns its keep every month and quietly gets better. Human-led, AI-powered, and useful rather than just impressive.

0 Responses

Want to respond? Tweet this post!

Related posts.

All writing

Skeletonise yourself with pose detection

In this post we will show you how to transform your body into a skeleton in realtime by analysing a webcam feed with TensorFlow.js and pose detection. For maximum spooky effect we recommend following this tutorial in the dead of night on 31st October.

Read more

Trick or Treat design elements and animations

In this blog post, we will pull out some of the design elements and animations and talk about them. We really enjoyed this project because it allowed us to add in many fun things; we added a lot of animations to give that extra special ingredient in our cauldron to make it a potent potion.

Read more

AI in 2026: How We Learned to Build Smarter

From a side project to an AI-powered studio — what three years of building with AI taught us.

Read more