The Internal Linking Cleanup I Kept Putting Off (And What It Actually Took)

Every post on this blog was, until recently, an island. Fifteen-plus articles, each one only reachable by whoever landed on it directly from a search result or a dev.to link -- nothing pointed a reader from one post to another, and nothing told a search crawler that these pages were part of the same body of work. I'd been meaning to fix that for weeks and kept treating it as a "later" task. It finally became a today task once I looked into how new sites actually build any search visibility, and found the same recommendation everywhere: internal links matter more, earlier, than almost anything else you can do without waiting for backlinks from other sites.

What the script actually did

I wrote a one-off script rather than hand-editing fifteen files. For every published post, it picked two or three thematically related posts -- grouped roughly by theme (OAuth/auth bugs, free-tier comparisons, automation-pipeline bugs) rather than just by date -- and appended a short "Related reading" block with real links to those posts. Then it pushed the updated HTML to the live post via the Blogger API's patch() call, and separately re-pushed the dev.to side of each cross-post with a PUT so both copies stayed in sync instead of only the next scheduled post picking up the change.

for post in all_posts:
    related = pick_related(post, all_posts, max_items=3)
    block = build_related_html(related)
    updated_content = post["content"] + block
    blogger.posts().patch(
        blogId=BLOG_ID, postId=post["id"],
        body={"content": updated_content},
    ).execute()
    if post.get("devto_id"):
        devto_put(post["devto_id"], updated_content)

The second half nobody tells you about: dev.to tags aren't free text

While I was in there, I also looked at how dev.to's own discovery works, since that's the one channel already showing more traffic than search. Its tags aren't arbitrary strings -- they're drawn from an existing controlled vocabulary, and picking a tag that's slightly off (a made-up compound word, a too-specific one nobody else uses) means the post never surfaces on any tag page a reader is actually browsing. Several of my earlier posts had tags I'd invented on the spot without checking whether they existed in dev.to's tag list at all. I rebalanced all of them to a smaller, consistent set of tags dev.to actually recognizes and other people use, instead of leaving each post with its own improvised vocabulary.

Why I pushed live updates instead of waiting

Every post on this blog gets published once through a scheduled task and never touched again after that -- except this time. The honest reason I broke that pattern is that waiting for the natural republish cycle would have meant weeks before all fifteen live posts had links between them, and the whole point was to give search crawlers and dev.to's own recommendation system more to work with sooner rather than later. It's a one-time retroactive fix; going forward, new posts get their related-reading block at publish time like everything else.

Whether it worked

Too early to say with any honesty. Search Console impressions have ticked up slightly since -- from zero for a month to a handful spread across a few pages -- but that overlaps with normal crawl timing too closely to credit the internal links specifically. I'm logging this here mostly so that if the numbers move more clearly next month, there's a dated record of exactly what changed and when, instead of me trying to reconstruct it from memory.

Related reading

Comments

Popular posts from this blog

My OAuth Tokens Kept Expiring Every 7 Days, and the Reason Was a Dropdown Labeled 'Testing'

How I Wired Up Fully-Automated Cross-Posting Between Blogger and dev.to (With Working Code)

I Compared the Free Tiers of Google Flow, Kling AI, and Hailuo So You Don't Have To