Asaduzzaman Pavel

Notes on My Blog's 'Self-Publishing' Pipeline

I’ve always liked static site generators because they’re fast and I don't have to worry about a database exploding at 3 AM. But for a long time, my blog felt like a "manual transmission" car. If I wanted to publish a post, I had to be at my desk, merge a PR, and wait for a build. If I wanted to schedule a post for next Tuesday? I had to remember to click "Merge" on Tuesday morning.

The Build-Time Filter

The heart of the system is Vite's import.meta.glob. Since I'm using MDsveX, my posts are basically Svelte components that get pulled in at build time. I added a filter in src/lib/posts.ts that checks the metadata date against new Date(). If the post is in the future, it just doesn't get included in the production array.

const now = new Date().getTime();
// ... inside the loop ...
const isVisible = !post.metadata.hidden && postDate <= now;
if (showDrafts || isVisible) {
    // include the post
}

...And this works fine, but it means the post technically exists in the repository, just not in the rendered HTML. Since I keep this repo private, it’s not a huge deal, but if I ever decided to open-source the code, I’d have to actually move the drafts to a separate branch or something to keep them truly hidden. Right now, I'm just relying on the fact that only I can see the source, which feels a bit like cheating.

GitHub Actions: The Cron Suggestion

To make the "scheduled" posts actually go live, I set up a GitHub Actions cron job to rebuild the site every night.

on:
  schedule:
    - cron: "0 0 * * *"

Here’s the thing no one tells you: GitHub's schedule is more of a suggestion than a command. I’ve seen my "midnight" builds trigger at 1:45 AM or even 3 AM depending on how busy their runners are. If you’re trying to time a post for a specific launch event, don't rely on this. It’s fine for a personal blog, but it’s definitely one of those "you get what you pay for" situations.

Nix and the "Works on My Machine" Problem

I use Nix Flakes to keep my build environment sane. My deploy.yml doesn't just run npm install; it enters a nix develop shell to get the exact pnpm and Node versions I used locally.

I assumed this would solve all my CI issues, but I still ran into a weird bug where a specific version of a remark plugin worked on my macOS Nix shell but failed on the Linux GitHub runner because of some obscure glibc difference. Nix gets you 95% of the way to "identical environments," but that last 5% is where you spend your Saturday afternoons.

A Hacky Preview Mode

I implemented a "preview" mode using a shared secret in the query string. If SvelteKit sees ?preview=secret, it bypasses the date filter.

export const GET: RequestHandler = async ({ url }) => {
    const previewToken = url.searchParams.get('preview');
    const isPreview = previewToken === process.env.PREVIEW_TOKEN;
    const posts = importPosts(isPreview);
    return json(posts);
};

It’s simple, it works, and it’s better than having to run pnpm dev every time I want to see if a layout tweak messed up a specific post. I’m not 100% sure if there's a more "idiomatic" SvelteKit way to do this—maybe using a separate Vercel/Netlify preview branch—but for a site hosted on Cloudflare Workers, this "token in the URL" approach is the least amount of friction.

By turning this into an automated pipeline, I've removed the "deployment anxiety." I just write the post, set a date, and trust that GitHub will (eventually) get around to building it.

Asaduzzaman Pavel

About the Author

Asaduzzaman Pavel is a Software Engineer who actually enjoys the friction of a well-architected system. He has over 15 years of experience building high-performance backends and infrastructure that can actually handle the real-world chaos of scale.

Currently looking for new opportunities to build something amazing.