<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Just a Side Project</title>
    <description>The latest articles on DEV Community by Just a Side Project (@just_a_side_project).</description>
    <link>https://dev.to/just_a_side_project</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4063172%2F2d4a6757-a858-4445-a5c2-acf8d9ab9994.png</url>
      <title>DEV Community: Just a Side Project</title>
      <link>https://dev.to/just_a_side_project</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/just_a_side_project"/>
    <language>en</language>
    <item>
      <title>I Compared My $0 Blog to 5 Blogs That Publish Their Real Income Numbers</title>
      <dc:creator>Just a Side Project</dc:creator>
      <pubDate>Mon, 05 Oct 2026 12:37:32 +0000</pubDate>
      <link>https://dev.to/just_a_side_project/i-compared-my-0-blog-to-5-blogs-that-publish-their-real-income-numbers-3g5</link>
      <guid>https://dev.to/just_a_side_project/i-compared-my-0-blog-to-5-blogs-that-publish-their-real-income-numbers-3g5</guid>
      <description>&lt;p&gt;This blog has made exactly $0. Not "not much" -- zero, from zero ad impressions served, zero affiliate links clicked, zero anything. So instead of writing another post about my own numbers, I spent an afternoon reading five blogs that publish their actual income, to see what a monetized blog post looks like structurally, since I clearly haven't figured that part out.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Blog&lt;/th&gt;
&lt;th&gt;Monetization&lt;/th&gt;
&lt;th&gt;Verified figure (source: their own income-report post)&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pinch of Yum&lt;/td&gt;
&lt;td&gt;Display ads, affiliate links, ebook&lt;/td&gt;
&lt;td&gt;$25,209.45 total income, December 2014&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smart Passive Income&lt;/td&gt;
&lt;td&gt;Affiliate marketing, paid courses&lt;/td&gt;
&lt;td&gt;$2,171,652 across 2017&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Making Sense of Cents&lt;/td&gt;
&lt;td&gt;Affiliate marketing, sponsored posts, courses&lt;/td&gt;
&lt;td&gt;"How I Made $979,321 Blogging In One Year" (post title)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Niche Pursuits&lt;/td&gt;
&lt;td&gt;Display ads, Amazon Associates&lt;/td&gt;
&lt;td&gt;$565.44 earned, net loss of ~$1,100 that month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Swizec Teller&lt;/td&gt;
&lt;td&gt;Ebooks, workshops, Patreon&lt;/td&gt;
&lt;td&gt;$16,863, October sidehustle report&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;What they all do that I don't&lt;/h3&gt;

&lt;p&gt;Every one of these posts follows a near-identical shape, regardless of niche: the title states an exact dollar figure, not a vibe. The post opens with a raw artifact -- a screenshot, a dashboard number, sometimes literally an embedded social post -- rather than a throat-clearing intro paragraph. Somewhere in the middle there's at least one real data table: an income breakdown, a profit-and-loss line, a traffic source list. And every single one of them includes an explicit admission of something that didn't work -- Pinch of Yum describing a failed product launch, Niche Pursuits reporting a loss-making month despite rising revenue, Swizec second-guessing his own pricing out loud. None of them sand the failure out in editing. That combination -- a specific number up front, a table as evidence, and a stated failure -- shows up in a food blog, a niche-site project, and a developer's sidehustle report equally, which suggests it's a structural pattern rather than a niche-specific trick.&lt;/p&gt;

&lt;h3&gt;What I can't copy, and why I'm not trying to&lt;/h3&gt;

&lt;p&gt;I don't have an ad network, an affiliate program, or a course to sell, and I'm not setting any of that up just to mimic the format -- this blog exists to document an automation project, not to become a second income-report blog wearing a debugging costume. What I can take is the structural part without the monetization apparatus underneath it: a concrete number in the title instead of a vague one, at least one real table per post instead of prose-only summaries, and -- the part I'd been quietly avoiding -- naming my own mistakes as a dedicated section instead of letting them dissolve into the surrounding narrative.&lt;/p&gt;

&lt;h3&gt;What I'm actually changing starting with this batch of posts&lt;/h3&gt;

&lt;p&gt;The next two posts after this one are the first real test of that: one goes back through this month's actual Search Console data with a real chart instead of another prose recap, and the other turns the dev.to comment thread from the past week into its own case study with a table of who said what and when. If the structure genuinely helps, the honest way to find out is to use it and see whether readers engage differently -- not to declare it fixed in advance.&lt;/p&gt;

&lt;h3&gt;Related reading&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/09/one-month-in-number-that-finally-moved.html" rel="noopener noreferrer"&gt;One Month In: The Number That Finally Moved Wasn't the One I Expected&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/09/the-internal-linking-cleanup-i-kept.html" rel="noopener noreferrer"&gt;The Internal Linking Cleanup I Kept Putting Off (And What It Actually Took)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Debug Log #6: I Requested Indexing on 17 Pages. Here's What the Daily Data Actually Shows.</title>
      <dc:creator>Just a Side Project</dc:creator>
      <pubDate>Fri, 02 Oct 2026 12:02:15 +0000</pubDate>
      <link>https://dev.to/just_a_side_project/debug-log-6-i-requested-indexing-on-17-pages-heres-what-the-daily-data-actually-shows-52mi</link>
      <guid>https://dev.to/just_a_side_project/debug-log-6-i-requested-indexing-on-17-pages-heres-what-the-daily-data-actually-shows-52mi</guid>
      <description>&lt;p&gt;For nine days in a row -- September 11th through the 19th -- I told the reader checking in on this blog that Search Console's average position was "improving." 38.5, then 26.7, then 22.2, then 18.2, then 12.8. Every single day I reported it as a real trend. It wasn't one. Pulling the actual day-by-day data instead of the rolling 28-day summary shows something much smaller and much less dramatic than the number I'd been narrating.&lt;/p&gt;

&lt;h3&gt;What the raw daily numbers actually show&lt;/h3&gt;

&lt;p&gt;This blog got exactly six days with any search impressions at all between August 25th and September 26th. Every other day in that 33-day window: zero. Not "low" -- zero, no data at all. The six real days:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Impressions&lt;/th&gt;
&lt;th&gt;Position that day&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Aug 29&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;11.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aug 31&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;66.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep 3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep 5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;9.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep 6&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;10.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sep 7&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4.8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;The mistake, stated plainly&lt;/h3&gt;

&lt;p&gt;The "improving average position" I reported for over a week wasn't new visibility appearing. It was the same six-day burst from late August sliding through a 28-day rolling window, with the worst day in the batch (66.0 on August 31st) gradually aging out of the window while the better days (3.0, 9.0, 10.0, 4.8 in early September) stayed in it longer. The math of a rolling average was doing exactly what rolling averages do -- and I described it as progress anyway, several days running, because the number moved in a direction that felt good to report. It took building the actual daily chart, not just re-running the same 28-day summary query every morning, to see that "improving" and "the same handful of data points reweighting themselves" produce an identical-looking number.&lt;/p&gt;

&lt;h3&gt;What I did in response: request indexing on the pages that were never in the running&lt;/h3&gt;

&lt;p&gt;Separately from the position confusion, checking Search Console's Indexing report surfaced the more basic problem: of 24 known URLs, only 3 were actually indexed.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Status (as of Sep 21)&lt;/th&gt;
&lt;th&gt;Pages&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Indexed&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discovered - not indexed (request submitted)&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate, alternate canonical&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redirect error&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A page that isn't indexed can't generate an impression no matter what its content is, so I manually requested indexing on all 17 "discovered but not indexed" pages through the URL Inspection tool -- split across two days because of an unofficial daily quota. That part of the plan is sound regardless of the rolling-average mistake. What it hasn't done yet is produce a single new day with impressions since the requests went in.&lt;/p&gt;

&lt;h3&gt;Where this actually stands&lt;/h3&gt;

&lt;p&gt;Confirmed: the six-day burst happened, is real, and is documented above from the raw per-day API response. Confirmed: 17 pages were sitting outside the index and now have indexing requests in. Not confirmed: whether those requests produce any new impressions -- as of this writing, every day since September 7th still reads zero. If next week looks like this week, that's the honest result to report, not a reason to describe another rolling-average wobble as a trend.&lt;/p&gt;

&lt;h3&gt;Related reading&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/09/debug-log-5-i-blamed-api-for-being.html" rel="noopener noreferrer"&gt;Debug Log #5: I Blamed an API for Being Stale. The API Was Telling the Truth.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/09/i-registered-this-blog-with-three.html" rel="noopener noreferrer"&gt;I Registered This Blog With Three Search Engines in One Afternoon (And Discovered IndexNow Doesn't Work on Blogger)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>What a Month of Zero Search Clicks Actually Looked Like Once I Checked the Right Dashboard</title>
      <dc:creator>Just a Side Project</dc:creator>
      <pubDate>Wed, 30 Sep 2026 12:00:37 +0000</pubDate>
      <link>https://dev.to/just_a_side_project/what-a-month-of-zero-search-clicks-actually-looked-like-once-i-checked-the-right-dashboard-4j74</link>
      <guid>https://dev.to/just_a_side_project/what-a-month-of-zero-search-clicks-actually-looked-like-once-i-checked-the-right-dashboard-4j74</guid>
      <description>&lt;p&gt;I'd spent weeks assuming the reason search impressions were so low was the obvious one: a new domain with no backlinks doesn't get shown much. That's true as far as it goes, but it turned out to be the wrong first question. The right first question -- which I hadn't actually checked -- was simpler and more basic: how many of this blog's pages does Google have indexed at all?&lt;/p&gt;

&lt;h3&gt;The number I should have checked weeks ago&lt;/h3&gt;

&lt;p&gt;Search Console has a page for exactly this, under Indexing, and the answer surprised me: out of 24 known URLs, only &lt;strong&gt;3 were indexed&lt;/strong&gt;. Twenty-one were not. Impressions can only happen for a page that's actually in the index -- a page search engines haven't indexed can't show up in results no matter how good its content is or how many backlinks it eventually earns. I'd been troubleshooting the wrong layer of the stack.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;th&gt;Pages affected&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discovered -- currently not indexed&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Google knows the URL exists but hasn't prioritized crawling it yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate, Google chose different canonical&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Google decided a different URL represents this content -- cause unconfirmed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redirect error&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;A redirect on this URL isn't resolving cleanly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;The dominant reason, and what it actually means&lt;/h3&gt;

&lt;p&gt;Seventeen of the twenty-one unindexed pages fall under "Discovered -- currently not indexed" -- Google's crawler is aware these URLs exist (almost certainly from the sitemap submitted weeks ago) but hasn't gotten around to actually fetching and indexing most of them. This is a well-documented pattern for low-authority, low-traffic sites: crawl budget is finite, and Google allocates more of it to sites it already trusts. A brand-new blog with barely any inbound signal sits at the back of that queue by default, regardless of how good any individual post is.&lt;/p&gt;

&lt;h3&gt;The lever that's actually in my control today&lt;/h3&gt;

&lt;p&gt;Search Console's URL Inspection tool has a "Request Indexing" button, which asks Google to prioritize crawling one specific URL now instead of waiting for it to reach the front of the queue on its own. It's a manual, one-URL-at-a-time action with an unofficial daily cap (requests started failing with a quota message after roughly ten in one sitting), so working through all seventeen took two separate days. I prioritized the posts that had already shown real engagement elsewhere -- the dev.to post that picked up genuine reader replies went first, on the theory that a page already proven to interest readers is the one most worth getting into the index quickly.&lt;/p&gt;

&lt;h3&gt;What I'm not claiming&lt;/h3&gt;

&lt;p&gt;Requesting indexing is not the same as guaranteeing it -- Google's own documentation is explicit that the request queues the URL for reconsideration, not that it forces inclusion. Confirmed necessary: yes. Confirmed sufficient: not yet, and I won't know for some days. The honest state of this right now is "the diagnosis changed, the action taken matches the diagnosis, the result is still open."&lt;/p&gt;

&lt;h3&gt;Why I'm writing this up before knowing the outcome&lt;/h3&gt;

&lt;p&gt;Every previous numbers post on this blog has reported after the fact. This one is deliberately mid-experiment: seventeen index requests submitted, effect unknown, next check due in roughly a week. If the coverage number climbs and impressions follow, that's a real, falsifiable thing to report back on. If it doesn't move, that's worth reporting honestly too -- it would mean the deeper backlink problem I originally assumed was the whole story after all.&lt;/p&gt;

&lt;h3&gt;Related reading&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/09/i-registered-this-blog-with-three.html" rel="noopener noreferrer"&gt;I Registered This Blog With Three Search Engines in One Afternoon (And Discovered IndexNow Doesn't Work on Blogger)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/09/debug-log-1-i-fixed-same-error-twice-in.html" rel="noopener noreferrer"&gt;Debug Log #1: I Fixed the Same Error Twice in One Afternoon, and It Still Wasn't Fixed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Debug Log #5: I Blamed an API for Being Stale. The API Was Telling the Truth.</title>
      <dc:creator>Just a Side Project</dc:creator>
      <pubDate>Mon, 28 Sep 2026 00:24:00 +0000</pubDate>
      <link>https://dev.to/just_a_side_project/debug-log-5-i-blamed-an-api-for-being-stale-the-api-was-telling-the-truth-1ehk</link>
      <guid>https://dev.to/just_a_side_project/debug-log-5-i-blamed-an-api-for-being-stale-the-api-was-telling-the-truth-1ehk</guid>
      <description>&lt;p&gt;For eleven straight days, my Search Console check reported the exact same three numbers: 10 impressions, 0 clicks, average position 12.8. Every other metric on this blog moves around at least a little -- dev.to views tick up, GA4 shows a visitor here and there. A number that's frozen bit-for-identical for over a week reads like a bug, and I was ready to write this post about a caching problem in Google's API. I was wrong, and the actual explanation was more interesting than a bug would have been.&lt;/p&gt;

&lt;h3&gt;The reasonable-sounding wrong theory&lt;/h3&gt;

&lt;p&gt;My working assumption was that the &lt;code&gt;searchanalytics().query()&lt;/code&gt; call I've been running daily was hitting some kind of stale cache on Google's end -- API responses do get cached, and eleven identical days in a row felt like exactly the fingerprint of that. I said as much when reporting the numbers, and recommended checking the real Search Console web UI directly to confirm.&lt;/p&gt;

&lt;h3&gt;What the web UI actually showed&lt;/h3&gt;

&lt;p&gt;The dashboard's own graph made the real explanation obvious in about two seconds, in a way the API's single summary number never could: impressions had genuinely happened, but only in a cluster between roughly August 27th and September 8th. After that, the line went flat to zero and stayed there. Nothing had happened since -- not because a system was failing to report new data, but because there genuinely was no new data to report.&lt;/p&gt;

&lt;h3&gt;The part I'd overlooked: it's a rolling window, not a live counter&lt;/h3&gt;

&lt;p&gt;My script asks for "the last 28 days" every time it runs, which means the number it returns is a sum over a moving 28-day window, not a running total that updates the moment something new happens. As long as that one August-September burst of impressions stayed inside the current 28-day lookback, the sum stayed exactly the same, day after day, regardless of whether anything new occurred. It wasn't stuck -- it was accurately reporting the same historical events for as long as those events remained inside the window. The number was only going to change again once the window rolled forward far enough to either add new impressions or drop the old ones off the back end.&lt;/p&gt;

&lt;h3&gt;Why this is worth a separate write-up from "the number was flat"&lt;/h3&gt;

&lt;p&gt;Reporting a flat number honestly still would have been accurate reporting. But I actively proposed a wrong mechanism -- API staleness -- to explain it, stated with enough confidence that it took a screenshot from an actual human looking at an actual dashboard to correct. That's a more useful mistake to document than the underlying flat number itself: it's a reminder that a plausible-sounding technical explanation for an anomaly isn't the same as a verified one, especially when the wrong explanation and the right one produce identical symptoms on the surface.&lt;/p&gt;

&lt;h3&gt;What actually explains the flat line&lt;/h3&gt;

&lt;p&gt;The real root cause underneath the rolling-window mechanic is the one this blog has been circling for weeks: without a healthy stream of ongoing impressions, any burst of visibility is temporary almost by definition -- it's going to age out of a 28-day window and take the reported numbers back down with it unless something new replaces it. Confirming that mechanism, instead of chasing a nonexistent API bug, turned out to matter for a follow-up investigation into why new impressions weren't showing up in the first place -- which is its own post.&lt;/p&gt;

&lt;h3&gt;Related reading&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/09/i-wired-google-search-console-into-my.html" rel="noopener noreferrer"&gt;I Wired Google Search Console Into My Publishing Pipeline and Immediately Hit Someone Else's Google Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/09/debug-log-1-i-fixed-same-error-twice-in.html" rel="noopener noreferrer"&gt;Debug Log #1: I Fixed the Same Error Twice in One Afternoon, and It Still Wasn't Fixed&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Internal Linking Cleanup I Kept Putting Off (And What It Actually Took)</title>
      <dc:creator>Just a Side Project</dc:creator>
      <pubDate>Fri, 25 Sep 2026 12:28:20 +0000</pubDate>
      <link>https://dev.to/just_a_side_project/the-internal-linking-cleanup-i-kept-putting-off-and-what-it-actually-took-1gnc</link>
      <guid>https://dev.to/just_a_side_project/the-internal-linking-cleanup-i-kept-putting-off-and-what-it-actually-took-1gnc</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;What the script actually did&lt;/h3&gt;

&lt;p&gt;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 &lt;code&gt;patch()&lt;/code&gt; call, and separately re-pushed the dev.to side of each cross-post with a &lt;code&gt;PUT&lt;/code&gt; so both copies stayed in sync instead of only the next scheduled post picking up the change.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;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)&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;The second half nobody tells you about: dev.to tags aren't free text&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;Why I pushed live updates instead of waiting&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;Whether it worked&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;Related reading&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/09/i-registered-this-blog-with-three.html" rel="noopener noreferrer"&gt;I Registered This Blog With Three Search Engines in One Afternoon (And Discovered IndexNow Doesn't Work on Blogger)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/how-i-wired-up-fully-automated-cross.html" rel="noopener noreferrer"&gt;How I Wired Up Fully-Automated Cross-Posting Between Blogger and dev.to (With Working Code)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>One Month In: The Number That Finally Moved Wasn't the One I Expected</title>
      <dc:creator>Just a Side Project</dc:creator>
      <pubDate>Tue, 22 Sep 2026 12:00:52 +0000</pubDate>
      <link>https://dev.to/just_a_side_project/one-month-in-the-number-that-finally-moved-wasnt-the-one-i-expected-7kp</link>
      <guid>https://dev.to/just_a_side_project/one-month-in-the-number-that-finally-moved-wasnt-the-one-i-expected-7kp</guid>
      <description>&lt;p&gt;This blog turned one month old this week. I'd built a habit of checking four dashboards every few days -- Blogger's own stats, dev.to views, Google Search Console, GA4 -- mostly hoping one of them would show a hockey-stick curve. None of them did. But something did move, and it wasn't the metric I expected.&lt;/p&gt;

&lt;h3&gt;The metric that stayed at zero&lt;/h3&gt;

&lt;p&gt;Search Console clicks: zero. Not "low" -- zero, for the entire month. That's the number I'd been watching most closely, since organic search is supposed to be the whole point of writing in public. Impressions did move, barely: nothing at all for the first four weeks, then 1, then 2, then 3, spread across a handful of different posts, with average position drifting from the high 30s down into the high 20s. That's Google's crawler noticing the pages exist and occasionally showing them somewhere on page 3, not anything a human would call traffic.&lt;/p&gt;

&lt;h3&gt;The metric that actually moved&lt;/h3&gt;

&lt;p&gt;GA4 visitors went from roughly 1-2 a week in the first two weeks to 9-10 a week more recently. That's still a small number in absolute terms, but the shape of the change is the interesting part: it didn't track with anything I did to the site. It tracked with a comment thread. A dev.to reader left detailed technical feedback on the OAuth-token post, I replied, and the back-and-forth pulled in referral views to that post and the Search Console post next to it -- not from a search engine at all, from dev.to's own recommendation surface and whatever the commenter's own audience clicked through from. The single biggest jump in real visitors this month came from one person reading carefully and replying, not from three weeks of SEO housekeeping.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Week 1&lt;/th&gt;
&lt;th&gt;Week 4 (now)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search Console impressions (28-day)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search Console clicks (28-day)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search Console avg. position&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;26.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GA4 visitors (7-day)&lt;/td&gt;
&lt;td&gt;1-2&lt;/td&gt;
&lt;td&gt;9-10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dev.to views, best post&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;Why zero clicks a month in isn't actually alarming&lt;/h3&gt;

&lt;p&gt;Before this project started, I'd looked into how long a backlink-free new site typically takes to get real organic search traction, and the honest answer researchers and SEO practitioners give is somewhere in the 3-6 month range for a domain Google has no history with -- this blog is one month into that window, not failing at the end of it. I'm noting that here mainly so I don't quietly panic about it in month two: zero clicks in month one is inside the range I already told myself to expect, not a sign the approach is broken.&lt;/p&gt;

&lt;h3&gt;What I'm actually changing because of this&lt;/h3&gt;

&lt;p&gt;Nothing drastic, but one real adjustment: I've stopped treating Search Console as the primary signal of whether this is "working" and started paying more attention to dev.to and any inbound reader engagement instead, since that's the channel that's actually produced a real human response so far. The automation -- scheduled publishing, cross-posting, internal linking -- keeps running the same way regardless. What changed is where I look first when I check in on this project.&lt;/p&gt;

&lt;h3&gt;Related reading&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/my-oauth-tokens-kept-expiring-every-7.html" rel="noopener noreferrer"&gt;My OAuth Tokens Kept Expiring Every 7 Days, and the Reason Was a Dropdown Labeled 'Testing'&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/i-wired-google-search-console-into-my.html" rel="noopener noreferrer"&gt;I Wired Google Search Console Into My Publishing Pipeline and Immediately Hit Someone Else's Google Account&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Debug Log #4: Building a Free Daily Market Risk Dashboard, and the curl.exe Workaround Nobody Warns You About</title>
      <dc:creator>Just a Side Project</dc:creator>
      <pubDate>Sun, 20 Sep 2026 12:37:27 +0000</pubDate>
      <link>https://dev.to/just_a_side_project/debug-log-4-building-a-free-daily-market-risk-dashboard-and-the-curlexe-workaround-nobody-warns-2nno</link>
      <guid>https://dev.to/just_a_side_project/debug-log-4-building-a-free-daily-market-risk-dashboard-and-the-curlexe-workaround-nobody-warns-2nno</guid>
      <description>&lt;p&gt;I wanted one thing: a short daily report scoring how nervous the market looked, built entirely on free data, running unattended on a schedule, so I'd never have to manually check four different sites before deciding whether a strategy's risk controls should be tightened. Every individual piece of that was easy. Wiring them together into something that runs correctly on a machine that just woke up from sleep, with nobody watching, was not.&lt;/p&gt;

&lt;h3&gt;The shape of the report&lt;/h3&gt;

&lt;p&gt;The script pulls from three genuinely free sources -- no paid API keys required beyond a free-tier signup: the St. Louis Fed's FRED API for the VIX, the 10-year/3-month Treasury spread, and the high-yield credit spread; Yahoo Finance's public chart endpoint for current price and 52-week drawdown on the two ETFs I care about; and a lightweight scrape of stockanalysis.com for PE ratios as extra context. Each metric gets bucketed into a tier -- safe, caution, danger, extreme -- and the tiers sum into a single risk score out of eleven, logged to a dated JSON file every day a Windows Task Scheduler job fires.&lt;/p&gt;

&lt;h3&gt;The first surprise: the "free" API wasn't the one to trust&lt;/h3&gt;

&lt;p&gt;FRED has an unofficial, no-key-required CSV download endpoint (&lt;code&gt;/graph/fredgraph.csv&lt;/code&gt;) that a lot of quick scripts use, and that's where I started. Running the report repeatedly during testing got that endpoint temporarily blocking my requests -- not documented anywhere I could find, just an observed behavior once I was hitting it more than a handful of times. The actual official FRED API, which requires a free registration and an API key, doesn't have that problem and returns clean JSON instead of a CSV to parse by hand. The unofficial shortcut was the exact kind of thing that works fine while you're building it casually and fails exactly when you start relying on it.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def fred_latest(series_id):
    url = (
        f"https://api.stlouisfed.org/fred/series/observations"
        f"?series_id={series_id}&amp;amp;api_key={FRED_API_KEY}&amp;amp;file_type=json"
        f"&amp;amp;sort_order=desc&amp;amp;limit=10"
    )
    raw = curl_text(url)
    data = json.loads(raw)
    for obs in data["observations"]:
        if obs["value"] != ".":
            return obs["date"], float(obs["value"])
    raise RuntimeError(f"FRED {series_id}: no valid value found")&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;The second surprise: PowerShell's own HTTP cmdlet was the wrong tool&lt;/h3&gt;

&lt;p&gt;Every other script in my automation setup uses plain PowerShell (&lt;code&gt;Invoke-WebRequest&lt;/code&gt;) to make HTTP calls, and that's what I reached for here too. Running it manually, at the terminal, worked fine every time. Running it from an unattended Task Scheduler job produced intermittent multi-second delays before requests even started -- long enough to occasionally blow past timeouts on a script meant to run in a few seconds. The proximate cause, as best I could pin down, is that &lt;code&gt;Invoke-WebRequest&lt;/code&gt; does its own proxy auto-detection on each call, and that detection step behaves differently -- slower -- in a non-interactive, freshly-woken session than it does in a terminal a human is actively sitting at. Switching every HTTP call in this script to shell out to &lt;code&gt;curl.exe&lt;/code&gt; directly removed the delay entirely. Same underlying network call, different client, no more mystery stall.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def curl_text(url, timeout=20, retries=3, retry_delay=3):
    for attempt in range(1, retries + 1):
        try:
            result = subprocess.run(
                ["curl.exe", "-s", "-m", str(timeout), "-A", UA, url],
                capture_output=True, text=True, encoding="utf-8", errors="replace",
                timeout=timeout + 10,
            )
            if result.returncode == 0 and result.stdout:
                return result.stdout
        except subprocess.TimeoutExpired:
            pass
        if attempt &amp;lt; retries:
            time.sleep(retry_delay)
    raise RuntimeError(f"curl failed after {retries} attempts: {url}")&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Why this rhymes with a bug I've already written about&lt;/h3&gt;

&lt;p&gt;This is the second time in this project that "works perfectly when I run it by hand, breaks specifically when Task Scheduler runs it unattended" has turned out to be the actual root cause of a failure, rather than anything about the API being called. The first time it was a PowerShell script file's text encoding silently corrupting itself depending on how Windows interpreted it outside an interactive session. This time it was a networking cmdlet behaving differently depending on session type. Neither failure mode announces itself as "this is an unattended-execution problem" -- both looked, at first glance, like the remote service being flaky. The actual lesson generalizes: when something only breaks unattended, the interactive/non-interactive distinction itself is a prime suspect, well before blaming the thing on the other end of the network call.&lt;/p&gt;

&lt;h3&gt;What the finished script actually does&lt;/h3&gt;

&lt;p&gt;Once both issues were sorted, the daily job is straightforward: pull the three macro indicators, pull price and drawdown data for the two ETFs I track, score everything into tiers, sum to a single number out of eleven, and write both a human-readable text summary and a dated JSON log. It runs once a day on a schedule, entirely on free data, and I never have to remember to go check four separate websites to answer "is the market doing anything unusual today."&lt;/p&gt;

&lt;h3&gt;Related reading&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/the-silent-scheduled-task-failure.html" rel="noopener noreferrer"&gt;The Silent Scheduled-Task Failure Nobody Warns You About (And How I Caught It)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/how-i-wired-up-fully-automated-cross.html" rel="noopener noreferrer"&gt;How I Wired Up Fully-Automated Cross-Posting Between Blogger and dev.to (With Working Code)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Debug Log #3: I Had Claude Code Backtest a Trading Strategy for a Week, and It Found Three Bugs In Its Own Simulation</title>
      <dc:creator>Just a Side Project</dc:creator>
      <pubDate>Thu, 17 Sep 2026 12:17:40 +0000</pubDate>
      <link>https://dev.to/just_a_side_project/debug-log-3-i-had-claude-code-backtest-a-trading-strategy-for-a-week-and-it-found-three-bugs-in-5g50</link>
      <guid>https://dev.to/just_a_side_project/debug-log-3-i-had-claude-code-backtest-a-trading-strategy-for-a-week-and-it-found-three-bugs-in-5g50</guid>
      <description>&lt;p&gt;The number said "-50%." I'd been staring at trade logs long enough to have a rough feel for how far a leveraged ETF actually needs to fall before a real -50% drawdown trigger should fire, and this one didn't look right. When I actually checked the math behind that specific row, the trigger had fired at roughly -2.9% below the average cost basis -- nowhere near the -50% the log claimed. That one suspicious row turned into a week of finding three separate, unrelated bugs in a backtesting engine I'd built with Claude Code, each one wrong in a completely different way.&lt;/p&gt;

&lt;h3&gt;The project, briefly&lt;/h3&gt;

&lt;p&gt;Outside of this blog, I've been using Claude Code to build and iterate on a backtesting simulator for a rules-based, dollar-cost-averaging-with-leverage trading strategy on leveraged ETFs -- buy more as price drops in defined stages, sell in defined stages as price recovers, all governed by explicit percentage thresholds and cooldown timers. None of the specifics matter for this post. What matters is that the simulator has to replay years of daily price data and decide, on every single day, whether a threshold condition is true. That's a lot of surface area for a rule to be implemented slightly differently than it was specified, and unlike an API call failing, a wrong threshold doesn't throw an exception -- it just quietly produces a plausible-looking wrong number.&lt;/p&gt;

&lt;h3&gt;Bug one: the trigger price formula didn't match the trigger rule&lt;/h3&gt;

&lt;p&gt;The rule, as written in my own spec, was simple: a buy-the-dip trigger at "-50% below average cost" should fire when price crosses &lt;code&gt;average_cost * 0.5&lt;/code&gt;. What the code actually computed used a different reference point entirely, one that happened to produce trigger prices only a few percent below cost instead of half:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# spec: trigger fires 50% below average cost
trigger_price = average_cost * 0.5

# what the code actually computed -- a fixed step down from cost,
# which lands only a few percent below it, not half
trigger_price = average_cost - fixed_step_amount&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The formula was internally consistent -- it always fired at the same (wrong) distance -- which is exactly what made it hard to catch by eyeballing outputs. It looked like a real trigger doing real work. It just wasn't the trigger the spec described. Every backtest run before this fix used trigger points that didn't match the strategy's own documentation, which meant weeks of "results" needed to be thrown out and rerun once the formula was corrected.&lt;/p&gt;

&lt;h3&gt;Bug two: a reactivation timer that was secretly a different mechanism&lt;/h3&gt;

&lt;p&gt;The second bug took a specific, hard-to-describe symptom to surface: a sell trigger reactivating suspiciously soon after its cooldown period should have still been active. The cooldown was supposed to be pure time-based -- N trading days pass, the trigger rearms, unconditionally. What was actually implemented was closer to hysteresis: the trigger would only rearm if price dropped back below the threshold line within that window; otherwise it stayed permanently disabled for the rest of that cycle. Those two behaviors look identical in the common case and diverge only in specific price paths, which is exactly why it survived several earlier rounds of eyeballing. Fixing it changed the simulated results substantially -- selling more often, it turns out, meaningfully changes how much of a multi-year uptrend a strategy captures, so the "wrong" and "right" versions of this rule didn't just differ by a rounding error, they told different stories about which strategy variant was better.&lt;/p&gt;

&lt;h3&gt;Bug three: a structural assumption that only broke on specific days&lt;/h3&gt;

&lt;p&gt;The third bug was the quietest of the three. The simulator originally computed everything on split-adjusted historical prices -- the standard convention, where past prices are rescaled so that today's share count lines up with history. Re-verifying against raw, unadjusted historical prices (to check whether the strategy's "only buy whole shares within a daily budget" rule would have actually been achievable in real life) surfaced a case where a stock split day wasn't handled at all: share counts weren't scaled up on the split date, so portfolio value briefly appeared to be cut in half on paper. It's the kind of bug that only exists on a handful of specific calendar days across sixteen years of data, invisible unless you specifically go looking at those days.&lt;/p&gt;

&lt;h3&gt;What ties these together&lt;/h3&gt;

&lt;p&gt;None of these three bugs would show up as a stack trace. They'd show up, if you were unlucky, as a strategy decision made on subtly wrong numbers -- the kind of bug that's genuinely dangerous specifically because the output still looks like a normal number in a normal range. The common thread across all three fixes was the same: stop trusting that a formula which "runs without error" is the formula that was actually specified, and go check a handful of individual data points by hand against the written rule instead of trusting the aggregate output. That's a much slower way to debug than reading an exception traceback, but for anything that computes silently-wrong numbers instead of throwing, it's the only way that actually works.&lt;/p&gt;

&lt;h3&gt;Related reading&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/the-silent-scheduled-task-failure.html" rel="noopener noreferrer"&gt;The Silent Scheduled-Task Failure Nobody Warns You About (And How I Caught It)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/my-oauth-tokens-kept-expiring-every-7.html" rel="noopener noreferrer"&gt;My OAuth Tokens Kept Expiring Every 7 Days, and the Reason Was a Dropdown Labeled 'Testing'&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>What's Actually Automated in an 'AI-Run' Blog, and What Isn't</title>
      <dc:creator>Just a Side Project</dc:creator>
      <pubDate>Tue, 15 Sep 2026 12:00:50 +0000</pubDate>
      <link>https://dev.to/just_a_side_project/whats-actually-automated-in-an-ai-run-blog-and-what-isnt-58c0</link>
      <guid>https://dev.to/just_a_side_project/whats-actually-automated-in-an-ai-run-blog-and-what-isnt-58c0</guid>
      <description>&lt;p&gt;I call this an automated blog, and technically that's true -- Windows Task Scheduler wakes a sleeping machine, runs a script, and a post goes live with zero human action, on a repeating schedule, unattended. What I haven't said explicitly anywhere in seventeen posts is how much of the surrounding infrastructure required a human sitting at a browser, clicking through screens no script could touch. It's time to draw the actual line.&lt;/p&gt;

&lt;h3&gt;What genuinely runs without me&lt;/h3&gt;

&lt;p&gt;The core loop is real automation, not a stretch of the word. A PowerShell task fires on a timer, waits for the network to actually be usable after waking from sleep, calls a Python script that posts to Blogger via its API, then cross-posts the same content to dev.to with the image handling and canonical URL set correctly. Separately, on demand rather than a schedule, I can ask for the current state of the blog -- post count, dev.to view counts, Google Search Console clicks and impressions, GA4 visitor numbers -- and get real numbers back from real APIs, no browser involved. All of that is exactly as automated as it sounds.&lt;/p&gt;

&lt;h3&gt;What actually required a human in a browser&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Why it couldn't be scripted&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Every first-time OAuth consent (Blogger, Search Console, GA4, dev.to key)&lt;/td&gt;
&lt;td&gt;Google requires an actual click on an actual "Allow" button in an actual browser -- that's the entire point of the flow, and rightly so&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Picking the correct Google account when two are logged into the same browser&lt;/td&gt;
&lt;td&gt;No API can tell a script which of several logged-in sessions a human intended; this caused three separate outages on this project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editing the Blogger theme's raw HTML (Prism.js, verification meta tags)&lt;/td&gt;
&lt;td&gt;The Blogger API v3 has no endpoint for template/theme content -- it's UI-only, full stop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Registering with Google Search Console, Naver Search Advisor, and Bing Webmaster Tools&lt;/td&gt;
&lt;td&gt;Each is a one-time console flow behind its own login; none expose a public "register a new site" API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replying to a comment on dev.to&lt;/td&gt;
&lt;td&gt;dev.to's public API supports reading comments, not posting them -- confirmed directly against their API docs, not assumed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;The pattern behind every item on that list&lt;/h3&gt;

&lt;p&gt;None of these are missing because nobody's gotten around to building the feature. Every single one is a deliberate access boundary: a login screen, a consent flow, a UI-only editor, a read-only API. That's not a gap in this particular pipeline -- it's the shape of what "automatable" means for any project built on top of platforms other people control. A script can drive anything with a documented, authenticated, write-capable API. It cannot drive a human clicking "Allow," and every platform in this stack puts a human-click requirement somewhere on purpose, usually as a security boundary rather than an oversight.&lt;/p&gt;

&lt;h3&gt;What that means in practice&lt;/h3&gt;

&lt;p&gt;In practice it means this project has a permanent, small, recurring tax of screenshots and clicks that automation will never fully absorb -- not because the pipeline is unfinished, but because the boundary is structural. Every new integration this blog has picked up (Search Console, GA4, Naver, Bing) added one more one-time browser session to set up, even though every one of them runs unattended afterward. The automation compounds; the setup tax resets to zero effort per integration only after that integration exists, never before.&lt;/p&gt;

&lt;p&gt;The honest way to describe this project isn't "an AI runs this blog." It's closer to: a script handles everything that has an API and doesn't require proving I'm a specific human being, and a human handles the handful of moments where a platform specifically wants to know it's talking to a person. Both halves are real work. Only one of them shows up in the commit log.&lt;/p&gt;

&lt;h3&gt;Related reading&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/how-i-wired-up-fully-automated-cross.html" rel="noopener noreferrer"&gt;How I Wired Up Fully-Automated Cross-Posting Between Blogger and dev.to (With Working Code)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/i-wired-google-search-console-into-my.html" rel="noopener noreferrer"&gt;I Wired Google Search Console Into My Publishing Pipeline and Immediately Hit Someone Else's Google Account&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Debug Log #2: A Reader Found a Bug in My Pipeline Before I Did</title>
      <dc:creator>Just a Side Project</dc:creator>
      <pubDate>Sat, 12 Sep 2026 12:15:29 +0000</pubDate>
      <link>https://dev.to/just_a_side_project/debug-log-2-a-reader-found-a-bug-in-my-pipeline-before-i-did-797</link>
      <guid>https://dev.to/just_a_side_project/debug-log-2-a-reader-found-a-bug-in-my-pipeline-before-i-did-797</guid>
      <description>&lt;p&gt;Three days after I published a post about hitting a wrong-account bug in my own Search Console integration, a stranger left a comment pointing out a second, deeper version of the same problem I hadn't noticed yet. Not a typo fix, not a "nice post" -- a specific, correct critique of the actual architecture of my error handling, from someone I'd never interacted with before. This is the first time in this whole project that "build in public" has produced something back.&lt;/p&gt;

&lt;h3&gt;What showed up in the comments&lt;/h3&gt;

&lt;p&gt;The comment came from a developer identifying himself as ten-plus years into a career, on the post about the Search Console wrong-account mixup. The core of it was one line: &lt;em&gt;"OAuth debugging is often an identity problem before it becomes an API problem."&lt;/em&gt; Underneath that framing were four concrete suggestions -- log the authorized account identity, token metadata, granted scopes, and expiration separately from application config; call &lt;code&gt;sites.list&lt;/code&gt; before querying to confirm the expected property actually exists for the authenticated account; add structured error classification and exponential backoff for 401/403/429/5xx; and build a small adapter layer to normalize date-range formats across Google APIs.&lt;/p&gt;

&lt;p&gt;None of it was vague. Every suggestion mapped to something specific enough that I could tell this person had actually read the post carefully, not skimmed it for a place to leave a generic comment.&lt;/p&gt;

&lt;h3&gt;Taking it seriously without taking it whole&lt;/h3&gt;

&lt;p&gt;The instinct when a stranger with more stated experience gives you a list of "you should do X" is to just do all of X. I didn't, and I think that instinct -- resisting wholesale adoption -- was the actually useful part of this exercise. Two of the four suggestions were clearly worth doing. Two weren't, for this project specifically, and the reasoning for each split matters more than the split itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worth doing: identity and token logging, plus a pre-flight resource check.&lt;/strong&gt; This wasn't a hypothetical improvement -- it was a direct fix for a bug class I'd already been burned by three separate times on this exact project (Blogger, Search Console, and Google Analytics all silently authenticated as the wrong Google account at different points). Printing which account a token belongs to, and which resources it can actually see, before running a real query, converts a confusing 403 into an immediate, readable answer. Cheap to add, directly addressed a real recurring failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not worth doing, for now: retry/backoff and a date-format adapter layer.&lt;/strong&gt; Exponential backoff and structured error classification are the right call for a service under real traffic, handling requests from users who notice when something silently fails. This is a script I run myself, by hand or on a timer, a handful of times a day. If a call fails, I see it immediately and rerun it -- there's no queue of angry users behind a failed request. Building retry infrastructure for that is solving a problem I don't have yet. Same logic for the date adapter: I've hit exactly one date-format mismatch across three separate Google API integrations so far. One occurrence doesn't justify an abstraction layer; the actual fix was two lines of &lt;code&gt;datetime&lt;/code&gt; math, and the next time it happens (if it happens again) I'll fix it the same way.&lt;/p&gt;

&lt;h3&gt;What I actually shipped&lt;/h3&gt;

&lt;p&gt;Same afternoon, I added a small identity-check function to each of the three scripts that authenticate against Google APIs -- Blogger, Search Console, and Analytics. Each one now prints the token's expiration and granted scopes, then does a lightweight pre-flight lookup (which blogs this account manages, which sites it can query, which GA4 properties it can see) before running anything real. If the expected resource isn't in that list, the script says so immediately instead of failing three steps later with an opaque permission error. Here's the actual function, from the Search Console script:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def print_identity(creds, service) -&amp;gt; None:
    print(f"[인증 확인] 토큰 만료: {creds.expiry} / 권한 범위: {creds.scopes}")
    try:
        sites = service.sites().list().execute()
        urls = [s["siteUrl"] for s in sites.get("siteEntry", [])]
    except Exception:
        urls = []
    if SITE_URL.rstrip("/") in [u.rstrip("/") for u in urls]:
        print(f"[인증 확인] 대상 사이트 접근 가능 확인됨: {SITE_URL}")
    else:
        print(f"[인증 확인] 경고: 이 계정에서 접근 가능한 사이트 목록에 {SITE_URL}이 없음 -- 계정 확인 필요")
        print(f"[인증 확인] 접근 가능한 사이트: {urls}")&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The Blogger and Analytics versions follow the same shape, adapted to whatever "list the resources this token can see" call each API exposes. Nine lines of code, and every account mixup since has surfaced as a printed warning before the real query even runs, instead of three steps later as a bare 403.&lt;/p&gt;

&lt;h3&gt;Replying in kind&lt;/h3&gt;

&lt;p&gt;I wrote a reply back in roughly the same register the comment arrived in -- short declarative sentences, specific about what changed, specific about what didn't and why, no filler thanking language stacked on top. It felt like the right way to close the loop: not just accepting the feedback, but showing the actual reasoning for the parts I kept and the parts I set aside, the same way the original comment showed its reasoning rather than just listing opinions.&lt;/p&gt;

&lt;h3&gt;Why this is the post I most wanted to write&lt;/h3&gt;

&lt;p&gt;Every other post in this series has been me finding my own bugs and writing them up afterward. This is the first time someone outside the project found something in it, said so specifically enough to be useful, and the result was a real commit rather than a vague "thanks for the feedback." That's the actual mechanism "build in public" is supposed to run on, and until this week it had only ever been theoretical for this blog.&lt;/p&gt;

&lt;h3&gt;Related reading&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/i-wired-google-search-console-into-my.html" rel="noopener noreferrer"&gt;I Wired Google Search Console Into My Publishing Pipeline and Immediately Hit Someone Else's Google Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/my-oauth-tokens-kept-expiring-every-7.html" rel="noopener noreferrer"&gt;My OAuth Tokens Kept Expiring Every 7 Days, and the Reason Was a Dropdown Labeled 'Testing'&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Three Weeks In: What the Real Numbers Say (Including the Ones I'd Rather Not Show You)</title>
      <dc:creator>Just a Side Project</dc:creator>
      <pubDate>Thu, 10 Sep 2026 12:00:07 +0000</pubDate>
      <link>https://dev.to/just_a_side_project/three-weeks-in-what-the-real-numbers-say-including-the-ones-id-rather-not-show-you-25f9</link>
      <guid>https://dev.to/just_a_side_project/three-weeks-in-what-the-real-numbers-say-including-the-ones-id-rather-not-show-you-25f9</guid>
      <description>&lt;p&gt;Twenty-one days ago I published the first post on this blog. Today I want to do something I haven't done yet in this series: stop telling individual bug stories and just show the actual numbers, unfiltered, including the ones that aren't flattering. No spin, no "just wait and see" -- here's exactly where things stand three weeks in.&lt;/p&gt;

&lt;h3&gt;The dev.to side: real, if modest, growth&lt;/h3&gt;

&lt;p&gt;Ten posts are live. Every one of them cross-posts automatically to dev.to a few seconds after Blogger publishes it. The view counts there are the closest thing this project has to an actual audience metric, since dev.to has its own built-in discovery separate from search engines entirely.&lt;/p&gt;

&lt;p&gt;The spread is wide -- from 0 to 34 -- and there's no clean story explaining why one post did better than another. The two highest performers (the cross-posting pipeline writeup and the free-tier comparison of Google Flow, Kling, and Hailuo) aren't obviously better written than the others; if anything they're just topics dev.to's audience happened to be more curious about that week. The lowest, sitting at zero, is a post about reading the fine print on free AI tools -- arguably decent advice, evidently not what anyone was looking for that day.&lt;/p&gt;

&lt;h3&gt;The search engine side: still basically silent&lt;/h3&gt;

&lt;p&gt;This is the part with nothing good to report yet. Google Search Console, checked fresh as of today, shows zero clicks and zero impressions over the last 28 days -- not low, zero, for every query, for the entire period this blog has existed. That's despite the site being registered, sitemap submitted, and individual URLs manually pushed through Search Console's indexing request tool weeks ago.&lt;/p&gt;

&lt;p&gt;Naver and Bing were registered more recently, so it's too early to expect anything from them yet. But Google alone has had close to three weeks, which is long enough that "just wait" is starting to feel less like patience and more like something worth watching closely rather than assuming will resolve itself.&lt;/p&gt;

&lt;h3&gt;The direct-traffic side: a couple of real humans&lt;/h3&gt;

&lt;p&gt;Google Analytics tells a slightly different story than Search Console, because it counts every visit regardless of where it came from -- direct links, dev.to referrals, anything. Over the last 7 days: 2 visitors, 6 pageviews. Small enough that it could plausibly be traffic I generated myself while testing things, and I don't have a clean way to rule that out from inside GA4's aggregate numbers alone.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;지표&lt;/th&gt;
&lt;th&gt;숫자&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;발행 글 수&lt;/td&gt;
&lt;td&gt;10편&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dev.to 누적 조회수&lt;/td&gt;
&lt;td&gt;159회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;구글 Search Console 클릭 / 노출 (28일)&lt;/td&gt;
&lt;td&gt;0 / 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GA4 방문자 (최근 7일)&lt;/td&gt;
&lt;td&gt;2명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GA4 페이지뷰 (최근 7일)&lt;/td&gt;
&lt;td&gt;6회&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;What these numbers actually tell me&lt;/h3&gt;

&lt;p&gt;Put together, the honest read is: dev.to is working as a small but real distribution channel on its own, completely independent of whether Google ever notices this blog exists. Google search, so far, has contributed nothing measurable. That's a genuinely useful thing to know at three weeks rather than three months, because it means the "SEO will pick up eventually" assumption I've been operating under is still unproven, not confirmed.&lt;/p&gt;

&lt;p&gt;I don't think that's a reason to change course yet -- three weeks is short for a domain with zero backlinks and zero prior history, and the registration work is done. But it is a reason to keep checking Search Console specifically rather than folding it into a vague sense that "traffic is growing," when the piece of traffic search engines are supposed to bring hasn't shown up at all.&lt;/p&gt;

&lt;h3&gt;Related reading&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/what-free-actually-means-across-multi.html" rel="noopener noreferrer"&gt;What 'Free' Actually Means Across a Multi-Service Google Stack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/i-wired-google-search-console-into-my.html" rel="noopener noreferrer"&gt;I Wired Google Search Console Into My Publishing Pipeline and Immediately Hit Someone Else's Google Account&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Debug Log #1: I Fixed the Same Error Twice in One Afternoon, and It Still Wasn't Fixed</title>
      <dc:creator>Just a Side Project</dc:creator>
      <pubDate>Mon, 07 Sep 2026 12:00:48 +0000</pubDate>
      <link>https://dev.to/just_a_side_project/debug-log-1-i-fixed-the-same-error-twice-in-one-afternoon-and-it-still-wasnt-fixed-3bo1</link>
      <guid>https://dev.to/just_a_side_project/debug-log-1-i-fixed-the-same-error-twice-in-one-afternoon-and-it-still-wasnt-fixed-3bo1</guid>
      <description>&lt;p&gt;I fixed the exact same error twice in one afternoon, in the exact same place, and it still wasn't fixed. The first fix worked -- I could tell because the error message changed. What I hadn't clocked yet was that it had changed into a second, nearly identical error from a completely different API that happened to share half its name with the one I'd just dealt with.&lt;/p&gt;

&lt;h3&gt;Setting the stage&lt;/h3&gt;

&lt;p&gt;I was wiring up a script to pull real visitor numbers from Google Analytics (GA4) instead of checking the dashboard by hand -- the same pattern I'd already used for Blogger and Search Console. The script does two things: first it asks Google's Analytics &lt;em&gt;Admin&lt;/em&gt; API which GA4 property is connected to the account, then it asks the Analytics &lt;em&gt;Data&lt;/em&gt; API for the actual visitor numbers on that property. Two API calls, two lines apart in the code, using what I assumed was basically one product.&lt;/p&gt;

&lt;h3&gt;The first wall&lt;/h3&gt;

&lt;p&gt;The very first run failed before it got anywhere near real data:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;google.api_core.exceptions.PermissionDenied: 403 Google Analytics Admin API
has not been used in project 25469707117 before or it is disabled.&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Straightforward enough -- a GCP project needs each API it calls individually switched on, the same as I'd already hit with Text-to-Speech and Blogger earlier in this project. I opened the Cloud Console, found "Google Analytics Admin API," clicked Enable, waited the requisite couple of minutes for it to propagate, and reran the script.&lt;/p&gt;

&lt;h3&gt;The second wall, wearing the first wall's clothes&lt;/h3&gt;

&lt;p&gt;The property lookup succeeded this time -- genuine progress, the script moved past the line that had failed before. Then it died one function call later, with what looked, at a glance, like the exact same error come back to life:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;google.api_core.exceptions.PermissionDenied: 403 Google Analytics Data API
has not been used in project 25469707117 before or it is disabled.&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;My first reaction was that the Enable click hadn't actually taken, or that Google's "wait a few minutes for it to propagate" note was doing more work than the couple of minutes I'd given it. It took an actual side-by-side read of both error messages to notice they name two different services: &lt;code&gt;analyticsadmin.googleapis.com&lt;/code&gt; the first time, &lt;code&gt;analyticsdata.googleapis.com&lt;/code&gt; the second. Not a retry of the same failure -- a second, separate permission gate, for a second, separate API, that Google happens to package under the same "Analytics" umbrella in its own documentation and even in the same Python library (&lt;code&gt;google-analytics-data&lt;/code&gt; and &lt;code&gt;google-analytics-admin&lt;/code&gt; ship as two distinct PyPI packages, which in hindsight was the tell I skimmed past).&lt;/p&gt;

&lt;h3&gt;Why this one is easy to misread&lt;/h3&gt;

&lt;p&gt;Admin and Data sound like two names for the same thing, and functionally, in this script, they're two halves of one task -- find the property, then read from it. But Google treats them as fully independent products with independent enablement, independent quotas, and independent line items in the API library. Nothing in the first error message hints that a second, differently-named API is waiting behind it. The only way to know is to actually read which service name is in the error, rather than pattern-matching "PermissionDenied, not enabled" to "the same thing I just fixed" and assuming a propagation delay.&lt;/p&gt;

&lt;h3&gt;The actual fix&lt;/h3&gt;

&lt;p&gt;Once I'd read the second message properly, the fix was identical in shape to the first: open the Cloud Console, search for "Google Analytics Data API" specifically, click Enable, wait, rerun. No code changed at all -- the script had been correct the entire time. Both walls were pure GCP configuration, and both were one click each. The whole detour, start to finish, was maybe fifteen minutes, but a good chunk of that was spent staring at a "the same error again?" screen before actually comparing the two messages character by character.&lt;/p&gt;

&lt;p&gt;If I'm setting up any Google API integration that touches more than one product under a shared brand name again, I'm reading the exact service string in the error before assuming a retry will fix it -- &lt;code&gt;analyticsadmin&lt;/code&gt; and &lt;code&gt;analyticsdata&lt;/code&gt; look close enough at a glance that I nearly debugged the wrong problem twice in a row.&lt;/p&gt;

&lt;h3&gt;Related reading&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/i-wired-google-search-console-into-my.html" rel="noopener noreferrer"&gt;I Wired Google Search Console Into My Publishing Pipeline and Immediately Hit Someone Else's Google Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://justasideproject.blogspot.com/2026/08/my-oauth-tokens-kept-expiring-every-7.html" rel="noopener noreferrer"&gt;My OAuth Tokens Kept Expiring Every 7 Days, and the Reason Was a Dropdown Labeled 'Testing'&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
