The ship moment
The Content Decay Detector is now shipped. Not “perfect,” not “finished forever,” but live enough to use, break, fix, and improve with real data.
This one started as a scratchpad problem. I kept seeing older articles that used to pull steady traffic slowly fade out. Nothing dramatic. No big crash. Just a quiet slide over months. Those are easy to miss when you’re focused on publishing new stuff, and by the time you notice, the page has usually lost a decent chunk of its value.
So I built a small detector that looks for pages showing signs of content decay. The goal is simple: show me which URLs need attention before they turn into ghosts.
I built this in the usual vibe coding mode. Move fast. Keep the scope tight. Let the tool tell me what it wants to become once the first version is actually working. There were a few clean wins, a couple of annoying data problems, and one very predictable mistake where I tried to make the scoring system too clever too early.
What the detector does right now
The first shipped version checks content performance over time and flags pages that look like they are slipping. It is not trying to replace a full SEO audit. That would have made the build bloated and slow. I wanted one focused job: find pages that are likely decaying and rank them by priority.
- Compares recent performance against an earlier baseline period
- Looks for drops in clicks, impressions, and average position
- Assigns each URL a decay status
- Gives each page a priority score
- Suggests a likely next action, like refresh, merge, expand, or monitor
- Exports a simple working list so updates can move into the content queue
The output is intentionally plain. URL, decay signal, priority, and what to do next. I do not want a beautiful dashboard that hides the work. I want a list that makes the next editing session obvious.
The best version of this tool is not the one with the most charts. It is the one that gets a stale article updated before the traffic is gone.
Why I built it this way
I started with the smallest useful version because content decay can get messy fast. There are seasonality issues. Query shifts. Ranking volatility. New competitors. Algorithm updates. Content that drops because it is outdated, and content that drops because search demand disappeared.
If I tried to solve every edge case in version one, I would still be planning instead of shipping.
So the first version makes a practical bet. If a page used to get attention from search, and now it gets much less, I want to see it. Then I can decide whether the drop is real decay or just noise.
That choice kept the build moving. I did not need a giant model or a complex forecasting system. I needed clean comparisons, sensible thresholds, and a way to sort the mess into a useful action list.
The data shape that made sense
The first data model was boring on purpose. Each row represents a URL with two time windows attached to it: the recent period and the comparison period. From there, the detector calculates differences.
| Field | Why it exists |
|---|---|
| URL | The page being checked |
| Recent clicks | Shows current search traffic |
| Previous clicks | Creates the baseline |
| Recent impressions | Shows current visibility |
| Previous impressions | Helps spot fading demand or ranking loss |
| Recent position | Shows where the page sits now |
| Previous position | Shows whether rankings moved |
| Decay score | Turns the signals into a priority number |
| Suggested action | Turns the result into work |
This was enough to make the tool useful. I could add more fields later, like publish date, last modified date, word count, content type, conversions, or internal link count. But those are add-ons. The first pass needed to answer one question: which pages are slipping?
The scoring system started too fancy
My first scoring attempt was overbuilt. I had weighted signals, multipliers, caps, confidence bands, and a few rules that sounded smart while I was writing them. Then I tested it against a real export and the results felt off.
Some pages with tiny traffic got scary-looking scores because their percentage drops were huge. A page going from 4 clicks to 1 click had a 75 percent drop, but that is not where I want to spend an editing afternoon. Meanwhile, a page that lost 300 clicks but had a smaller percentage drop looked less urgent. That was backwards.
So I pulled it back.
The shipped score now balances percentage loss with actual volume. A page has to show a meaningful drop and enough historical activity to matter. That one change made the list feel much more human. The detector stopped yelling about tiny pages and started surfacing URLs I would actually want to refresh.
The first working flow
The first working version follows a simple path. Nothing magical. That is the point.
- Import performance data for a set of URLs.
- Split the numbers into recent and previous windows.
- Calculate click, impression, and position changes.
- Filter out pages with too little data to trust.
- Score the remaining pages based on decay signals.
- Label each page with a suggested action.
- Show the results in a table sorted by priority.
Once that worked, I stopped adding features for a bit. I used it on a real content set instead. That was the right move. The fastest way to find product problems is to use the thing when you are tired and trying to get actual work done.
What worked better than expected
The action labels were more helpful than I expected. At first I thought they might be too simple, but they gave the output a nice rhythm. Instead of staring at a list of declining URLs, I could see buckets of work.
- Refresh: The page still has search visibility but needs updated information, examples, screenshots, or structure.
- Expand: The page is ranking but probably does not answer enough of the search intent anymore.
- Merge: The page overlaps with another article and may be splitting attention.
- Monitor: The signal exists, but it is not strong enough to act on yet.
That made the detector feel less like a reporting tool and more like a content operations helper. Reports are easy to ignore. Work queues are harder to dodge.
What broke during the build
The messiest part was not the interface. It was the data cleanup.
Some URLs had tracking parameters. Some had trailing slash differences. Some had HTTP and HTTPS versions. Some had uppercase characters in weird places. The same page could show up as multiple rows if I did not normalize the URLs before comparing them.
That created fake decay signals. The detector would think one version of a URL lost traffic while another gained it, when really they were the same page wearing different costumes.
The fix was boring but necessary. Strip common tracking parameters. Normalize casing where safe. Treat trailing slash variants consistently. Remove fragments. Clean the URL before any scoring happens.
I also had to add guardrails for missing data. A blank value should not become a zero without thought. Zero means something happened. Blank might mean the data was not available. Mixing those up can create bad scores fast.
The UI stayed plain on purpose
I resisted the urge to turn this into a giant analytics screen. The first interface is mostly a table with filters. That sounds dull, but it works.
The main controls are the ones I kept reaching for while testing:
- Filter by decay status
- Sort by highest priority
- Hide low-volume pages
- Show only pages with ranking drops
- Export the work list
I had a chart in an early build. It looked nice. I removed it. It did not help me decide what to update next, and that is the whole job of the tool. Maybe charts come back later, but only if they earn the space.
The tiny win that made it feel real
The moment this became useful was when the detector flagged an older article that I had completely forgotten about. It still had impressions, but clicks were sliding and average position had softened. The page was not dead. It was asking for maintenance.
I opened the article and immediately saw the problem. Old examples. A missing section. A few outdated references. The bones were still good, but the page had not kept up.
That is the exact use case I wanted. Not a vague “content quality” score. A real nudge that says, “Hey, this page still has potential. Go fix it before it fades out.”
How I think about decay now
Building this changed how I think about old content. Decay is not always failure. Sometimes it is just a maintenance signal.
A page can decay because the search results changed. It can decay because competitors improved their articles. It can decay because the original angle aged out. It can also decay because the page was never fully matched to intent in the first place, and it only ranked for a while because the competition was weak.
The detector does not pretend to know every cause. It points to the pages where attention is needed. The human still makes the call.
That feels like the right balance. Let software find patterns across lots of pages. Let the editor decide what the page should become.
What I would build first if you are copying this
If you want to build your own version, do not start with automation. Start with a CSV and a local script or a simple app screen. Prove the scoring logic first.
- Pick two date ranges. I like a recent 28-day window compared with the previous matching window, but you can test longer ranges for slower sites.
- Group the data by URL.
- Calculate the change in clicks and impressions.
- Filter out tiny pages so noise does not take over.
- Sort by lost clicks, not just percentage drop.
- Add a simple label so the result turns into a task.
Once that gives you a useful list, then think about the next layer. Pull data automatically. Save history. Add projects. Add notes. Track whether refreshed pages recover. Those features matter more after the core signal works.
The next fixes on my list
Shipping gave me a better backlog than planning ever could. A few improvements are already obvious.
- Better seasonality handling: Some pages dip every year at the same time. The detector needs to recognize that pattern.
- Last updated tracking: A page that has not been touched in two years should get treated differently from one updated last month.
- Content type filters: Guides, reviews, tutorials, and landing pages decay in different ways.
- Recovery tracking: After a refresh, I want to see whether the page bounced back.
- Team notes: A small note field would help explain why a page was skipped, merged, or assigned.
I am trying to keep the next version honest. Every new feature has to help someone decide what to do with a page. If it only makes the product look more advanced, it can wait.
Lessons from shipping it
The biggest lesson was that content tools need restraint. It is very easy to add more signals. More data feels safer. More charts feel more serious. But the real value is usually in the last mile, where someone has to open the page and improve it.
I also learned to trust simple scoring for the first version. Not simplistic. Simple. There is a difference. A simple score can still be useful if the inputs are clean and the output matches the way people work.
The other lesson was that data hygiene is product work. It is not a side task. If URLs are messy, your insights are messy. If blanks and zeros are confused, your priorities are wrong. If low-volume pages are not filtered, your list becomes noisy.
None of that looks exciting in a demo, but it is the stuff that makes the tool usable.
Where it stands now
The Content Decay Detector is shipped as a working first version. It finds slipping pages, scores them, and turns them into a practical update queue. That is enough to start getting value from it.
I am sure the scoring will change. The filters will get sharper. The action labels will probably get smarter after more real use. But the core idea is already holding up: old content should not be invisible just because it is no longer new.
This was a good reminder that a useful tool does not have to start big. It has to catch a real problem at the right moment. This one does that now, and that is why it shipped.