Dev.to WebDev 🛠 Dev 👁 0 📖 3 min read

My site publishes a blog post a day while I sleep — no cron, no CMS (week 6 build-log)

Part 3 of building PipAtlas in public. Part 2 covered the first five weeks of real search data. This week I wired the thing every content site eventually needs: a blog that publishes on schedule, generates its own illust

Part 3 of building PipAtlas in public. Part 2 covered the first five weeks of real search data. This week I wired the thing every content site eventually needs: a blog that publishes on schedule, generates its own illustrations, and heals its own broken links — running inside a single Node process, no cron jobs, no CMS, no AI slop.

The constraint that shaped everything
The blog had one job: one post per day, from a queue of markdown drafts, published to static HTML with a regenerated index and sitemap. The obvious tool is cron. I built it on cron first — and then watched it do nothing for a day because a hosting panel quietly didn't save the crontab entry.

Lesson: if uptime of a feature matters, put it in the process that's already supervised by your host. The app server restarts when I deploy, gets watched by the panel, and logs somewhere I actually look. So the scheduler moved inside the server:

JavaScript

(function at30(){
const now = new Date(); const t = new Date(now);
t.setMinutes(30, 0, 0);
if (t <= now) t.setHours(t.getHours() + 1);
setTimeout(function(){ runBlog(); setInterval(runBlog, 3600000); }, t - now);
})();
Aligned timers, busy-flags to prevent overlap, and a boot burst 90 seconds after every restart so pending drafts publish immediately. The child process gets the site root handed to it explicitly:

JavaScript

const env = folderMode
? { ...process.env, GROWTH_PUB: SITE_ROOT } // never let bots guess paths
: process.env;
execFile(process.execPath, [botPath], { cwd: growthDir, env });
That GROWTH_PUB handoff exists because of my favorite failure this week: the publishing bot spent days failing with public_html not found — because I'd assumed a directory layout the host doesn't use. Folder-mode hosting serves the site from the app folder itself. One log line from the owner's screenshot, one env variable, fixed. Bots should never infer paths when the running process knows them.

Illustrations without an image API
Every post gets a chart — generated, not stock. A tiny SVG module renders bar charts, curve charts and session clocks in the site's exact brand palette (navy/teal/gold), writing static files the posts reference. No API keys, no rate limits, no 40 KB of chart library shipped to readers — the whole figure is ~2 KB of markup that scales perfectly.

The drafting side is deliberately not an AI text spinner. Topic packs are hand-authored (real numbers, real tables, an FAQ, an internal link plan); the bot assembles, illustrates and runs hard validation — minimum word count, mandatory internal links, a placeholder sweep — and refuses to queue anything that fails. An optional LLM pass exists behind a config flag, but it's gated by the same validation and falls back to the authored blocks. Quality is a floor, not a hope.

The bug the users caught before I did
Two days in, a link check found posts 404ing from the blog index. Cause: the index was served at both /blog.html and the clean URL /blog, and the card links were relative — so at /blog (no trailing slash) the browser resolved them against the site root. Classic.

The durable fix was to make every internal link in bot-generated pages root-absolute, plus a self-healing reskin pass that relinks older published pages on every run. And because some broken URLs were already loose in the wild, the server now 301s any legacy root-level post URL straight into its new home. Broken links aren't a to-do list; they're a state you repair automatically or they come back.

What the numbers say so far
Six weeks in: ~140 pages indexed, position data on dozens of queries, organic impressions climbing week over week — and now three posts live with one landing per day, each interlinked into the calculators and glossary that already rank. The blog isn't a content play, it's an internal-linking engine for pages that were previously islands.

Everything here runs on ~$2 hosting, one Node file, and markdown drafts. Series continues with the link-building chapter — journalist platforms, not spam — once there's data worth reporting.

Building the same thing? The one principle I'd export: anything that must run daily belongs to a process that's already being kept alive — not to a crontab you have to remember to save.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.