<?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: Utkarsh</title>
    <description>The latest articles on DEV Community by Utkarsh (@rajput_karsh).</description>
    <link>https://dev.to/rajput_karsh</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%2F4015679%2F37dd1687-8361-4cd2-8fff-05a16385d64f.jpg</url>
      <title>DEV Community: Utkarsh</title>
      <link>https://dev.to/rajput_karsh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rajput_karsh"/>
    <language>en</language>
    <item>
      <title>I Shipped 284k Lines of TypeScript Solo in Two Months. The Code Was the Easy Part.</title>
      <dc:creator>Utkarsh</dc:creator>
      <pubDate>Mon, 06 Jul 2026 04:30:00 +0000</pubDate>
      <link>https://dev.to/rajput_karsh/i-shipped-284k-lines-of-typescript-solo-in-two-months-the-code-was-the-easy-part-1eid</link>
      <guid>https://dev.to/rajput_karsh/i-shipped-284k-lines-of-typescript-solo-in-two-months-the-code-was-the-easy-part-1eid</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;📎 This is a cross-post. The original lives on my blog: &lt;strong&gt;&lt;a href="https://astroniq.app/blog/what-we-learned-from-building-astroniq" rel="noopener noreferrer"&gt;What we learned from building Astroniq&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On May 2nd I made the first commit to an empty repo. Two months later it held &lt;strong&gt;832 commits&lt;/strong&gt; and roughly &lt;strong&gt;284,000 lines of TypeScript&lt;/strong&gt;: five frontend apps, four backend services, fifteen shared packages — all written, deployed, and maintained by one person.&lt;/p&gt;

&lt;p&gt;The product is &lt;a href="https://astroniq.app" rel="noopener noreferrer"&gt;Astroniq&lt;/a&gt;, an AI-powered Vedic astrology platform. Not a horoscope widget with a chat skin — a real computational engine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Divisional charts down to D150 (Nadi Amsha)&lt;/strong&gt;, computed from raw ephemeris math.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An agentic AI chat&lt;/strong&gt; that doesn't hallucinate your chart — it &lt;em&gt;calls the engine&lt;/em&gt; and reasons over real numbers (more on this below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Birth-time refinement&lt;/strong&gt; that reverse-engineers an unknown birth time from the timeline of your life events, scoring candidates against dasha/transit patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm not listing this to flex. It's the setup for the actual lesson:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If one person can ship all of that in two months, building is no longer the hard part of a startup. Something else is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let me show you the engineering first, because that's the fun part — then the thing that blindsided me.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks when you're the entire engineering team
&lt;/h2&gt;

&lt;p&gt;Solo dev with AI tooling is a new sport. The bottleneck isn't typing code — it's judgment and catching what breaks. And things break in ways no tutorial warns you about.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Your deploy pipeline will try to kill your product
&lt;/h3&gt;

&lt;p&gt;My first deploy was the one-liner every tutorial teaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks harmless. It has a latent outage baked in. &lt;code&gt;up --build&lt;/code&gt; &lt;strong&gt;recreates&lt;/strong&gt; the app containers, then gates their startup on the migration container via &lt;code&gt;depends_on: { migrate: { condition: service_completed_successfully } }&lt;/code&gt;. So the sequence is: tear down the running app → run migrations → start new app. If a migration exits non-zero, the old containers are already gone and the new ones are stuck in &lt;code&gt;Created&lt;/code&gt;. &lt;strong&gt;Full outage — caused by the safety check itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The fix was to invert the order so a bad deploy is invisible instead of fatal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# 1. Build first. A build failure changes zero running containers.&lt;/span&gt;
docker compose build

&lt;span class="c"&gt;# 2. Run migrations as an explicit one-shot gate. A non-zero exit aborts&lt;/span&gt;
&lt;span class="c"&gt;#    the script here (set -e) → the LIVE containers keep serving the old image.&lt;/span&gt;
docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; migrate

&lt;span class="c"&gt;# 3. Only now roll the app onto the new images.&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--no-deps&lt;/span&gt; api-service-1 api-service-2 api-service-3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same tools. Completely different failure semantics. A broken migration now stays invisible to users instead of taking the site down.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Disks fill up at 10:49 PM on a Friday
&lt;/h3&gt;

&lt;p&gt;One evening Redis started failing its background saves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;redis | Write error while saving DB to disk: No space left on device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The host had 787 GB free. The container's VM did not. &lt;code&gt;docker system df&lt;/code&gt; told the story:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;TYPE          TOTAL   ACTIVE   SIZE      RECLAIMABLE
Images        9       6        19.12GB   3.89GB (20%)
Build Cache   66      0        8.09GB    8.09GB (100%)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not logs. Not data. &lt;strong&gt;8 GB of build cache + 3.9 GB of orphaned images&lt;/strong&gt;, one dangling image left behind by every &lt;code&gt;--build&lt;/code&gt; deploy, quietly accumulating on a 47 GB disk until a deploy tipped it to 100% and starved Redis mid-&lt;code&gt;BGSAVE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The permanent fix was one line added after a successful rollout — each deploy rebuilds the app image under the same tag, orphaning the previous one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# After the roll: reclaim the now-dangling previous image. `-f` = dangling only,&lt;/span&gt;
&lt;span class="c"&gt;# so images backing running containers are untouched.&lt;/span&gt;
docker image prune &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nobody puts "your CI leaves a 4 GB corpse on the server every deploy" in the getting-started docs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The internet attacks you on day one — and Redis is the sharp edge
&lt;/h3&gt;

&lt;p&gt;Within days of exposing Postgres on a VPS, my logs filled with credential-stuffing — dictionaries of usernames, around the clock, from scanners that hunt fresh servers.&lt;/p&gt;

&lt;p&gt;Redis is scarier than Postgres here. Default Redis has &lt;strong&gt;no auth&lt;/strong&gt;, and once you're connected it's a one-shot RCE: &lt;code&gt;CONFIG SET dir /root/.ssh&lt;/code&gt; + &lt;code&gt;SAVE&lt;/code&gt; writes an attacker-controlled &lt;code&gt;authorized_keys&lt;/code&gt;. So Redis never gets published to the host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7&lt;/span&gt;
  &lt;span class="c1"&gt;# No `ports:` — reachable ONLY on the internal docker network.&lt;/span&gt;
  &lt;span class="c1"&gt;# requirepass is belt-and-braces so a popped app container still can't&lt;/span&gt;
  &lt;span class="c1"&gt;# read/write the cache. `:?` makes compose refuse to boot if it's unset.&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-server"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--requirepass"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${REDIS_PASSWORD:?must&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;set}"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;expose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6379"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus &lt;code&gt;ufw deny 6379/tcp&lt;/code&gt; re-asserted on &lt;strong&gt;every deploy&lt;/strong&gt;, so one careless compose edit can't silently re-open the door.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. APIs lie, or at least their docs do
&lt;/h3&gt;

&lt;p&gt;This one cost me an afternoon. An image-generation API's docs said the bytes come back at &lt;code&gt;output_image.data&lt;/code&gt;. My extraction kept throwing "no image data" even though the request clearly succeeded and billed me for the tokens.&lt;/p&gt;

&lt;p&gt;The live response didn't have an &lt;code&gt;output_image&lt;/code&gt; object at all. The bytes were nested inside a &lt;code&gt;steps[]&lt;/code&gt; array, on a field called &lt;code&gt;signature&lt;/code&gt;, one level deeper than documented:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// What the docs promised:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_image&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ← always undefined&lt;/span&gt;

&lt;span class="c1"&gt;// What the endpoint actually returns: steps[] wrapping a content[] with the&lt;/span&gt;
&lt;span class="c1"&gt;// media part. So walk the tree instead of trusting a fixed shape.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findPart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findPart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// depth-first&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;nested&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findPart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My git history has five consecutive commits with the &lt;em&gt;identical&lt;/em&gt; message. That's what debugging a third-party API in production looks like when you're the whole team.&lt;/p&gt;

&lt;h3&gt;
  
  
  The agentic chat: don't let the model invent your Moon sign
&lt;/h3&gt;

&lt;p&gt;The one piece I'm proudest of: the AI chat never guesses chart data. LLMs will happily hallucinate "your Saturn is in the 7th house" with total confidence. So the model doesn't get to compute anything astrological — it gets &lt;em&gt;tools&lt;/em&gt;, and the tools run the real ephemeris:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_planet_positions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sidereal planet longitudes + house placements for a chart.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;input_schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* birth data */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}];&lt;/span&gt;

&lt;span class="c1"&gt;// Flow: user asks a question&lt;/span&gt;
&lt;span class="c1"&gt;//   → model calls get_planet_positions(birthData)&lt;/span&gt;
&lt;span class="c1"&gt;//   → the deterministic engine computes real positions&lt;/span&gt;
&lt;span class="c1"&gt;//   → model reasons over ACTUAL numbers, then writes the answer.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interpretation is generated; the &lt;em&gt;facts&lt;/em&gt; are computed. That boundary is the whole product.&lt;/p&gt;




&lt;p&gt;Here's the inspirational bit, and I mean it: &lt;strong&gt;every one of these was solvable in hours or days.&lt;/strong&gt; Hostile internet, fragile deploys, full disks, lying docs — each has a deterministic answer. You find it, you fix it, it &lt;em&gt;stays fixed&lt;/em&gt;. If you're a builder sitting on an idea: the technical mountain is climbable, solo, faster than you think.&lt;/p&gt;

&lt;p&gt;Which is exactly why the next thing blindsided me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The realization: development is easier than distribution
&lt;/h2&gt;

&lt;p&gt;Around week six I noticed something uncomfortable. Shipping a new divisional-chart lens — real ephemeris math, a whole analysis layer — took about a week. Getting anyone to &lt;em&gt;know it existed&lt;/em&gt; was eating more than that. Every week. Forever.&lt;/p&gt;

&lt;p&gt;The asymmetry is structural, and naming it changed how I think about shipping:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Development compounds. Distribution — the way solo devs do it — doesn't.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When I write code I stand on forty years of compounding tooling. The compiler catches type errors. Tests catch regressions. CI catches broken builds. Every safety net I build &lt;em&gt;stays built&lt;/em&gt; — my 200th feature ships faster than my 20th because the infrastructure remembers.&lt;/p&gt;

&lt;p&gt;When I did distribution, I was a caveman with a stick. Every morning, from zero: what do I post? Is anything trending? Which article is decaying in search? Did that reel do well — make a part two? Nothing compounded. The 200th post took exactly as long as the 20th.&lt;/p&gt;

&lt;p&gt;And distribution &lt;em&gt;is&lt;/em&gt; engineering — I just wasn't treating it that way. Case in point, a bug I shipped in my own sitemap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The sitemap fetched every published slug to emit &amp;lt;url&amp;gt; entries:&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchBlogList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// …but the public list API capped `limit` at 50:&lt;/span&gt;
&lt;span class="nl"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 200 → 400 Bad Request&lt;/span&gt;

&lt;span class="c1"&gt;// The fetch swallowed the error and returned []. Every blog post silently&lt;/span&gt;
&lt;span class="c1"&gt;// vanished from the sitemap. Google literally could not discover them.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A distribution failure that was really an off-by-a-Zod-constraint engineering bug. Fixed by paginating within the cap. It made the point for me: &lt;strong&gt;your distribution hours are your most expensive hours, because they're the only ones without power tools.&lt;/strong&gt; So I gave them power tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treating growth like infrastructure
&lt;/h2&gt;

&lt;p&gt;The question I needed answered every morning was embarrassingly simple: &lt;em&gt;"What should I do today to grow this?"&lt;/em&gt; Not a content calendar. Not 40 AI posts to wade through. One ranked list of concrete actions, each with a reason and a one-click resolution.&lt;/p&gt;

&lt;p&gt;The abstraction I landed on is the same shape I'd use for any event pipeline — a detected &lt;strong&gt;signal → recommended action → one-click resolution&lt;/strong&gt;, ranked by impact-per-minute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trend signals ──┐
 (astro cal,    ├─▶ Detector plugins ─▶ Opportunity {          ┐
  reddit,       │       (one per          signal,              │ ranked by
  search)       │        growth lever)    action,              │ impact ÷
Engine insights ┘                         resolution,          │ effort_minutes
                                          provenance }          ┘
                                                │
                                                ▼
                                   Morning brief  →  human approves
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three engineering decisions that mattered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timely × True.&lt;/strong&gt; Generic AI content is spam and readers smell it. But the product sits on a &lt;em&gt;deterministic trend calendar&lt;/em&gt; — planetary events are computable months ahead from the same ephemeris engine. Cross a timely hook with a grounded, engine-computed insight and the content is both relevant and true. Your product probably has an equivalent: &lt;strong&gt;the data only you have is the content only you can make.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything carries provenance.&lt;/strong&gt; Every draft records exactly what it was generated from — which engine insight, which trend signal. If AI drafts in your name, receipts are non-negotiable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A human approves everything.&lt;/strong&gt; The system detects, ranks, drafts, and routes; I decide. It removes the &lt;em&gt;searching and blank-page-staring&lt;/em&gt;, not the judgment. Everything upstream of judgment is a pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Daily growth work went from hours of scattered anxiety to a ~15-minute morning ritual — and those minutes now compound the way my dev hours always have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things I'd tell you on day one
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The technical mountain is climbable, alone.&lt;/strong&gt; A quarter-million lines, an engine, an AI layer, mobile apps, billing, infra — one person, two months. Whatever you're afraid you can't build: you probably can. Start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notice which of your hours compound.&lt;/strong&gt; Dev hours compound because the tooling remembers. If your distribution hours evaporate at midnight, that's not a discipline problem — it's missing infrastructure. Build it or find it; don't just try harder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship the product, then engineer the megaphone.&lt;/strong&gt; Distribution deserves the same rigor as your deploy pipeline: detection, prioritization, provenance, repeatability.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And keep an eye on your disk space. Redis will pick a Friday night. It always picks a Friday night.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Full disclosure: I built the growth system above for my own product, so I'm biased about the approach. But the engineering lessons stand on their own — and I'd genuinely love to hear how other solo devs are attacking the distribution half. What's compounding for you, and what still isn't?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://astroniq.app/blog/what-we-learned-from-building-astroniq" rel="noopener noreferrer"&gt;the Astroniq blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>astroniq</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
