DEV Community

Akhilesh Pothuri
Akhilesh Pothuri

Posted on

Write Once, Publish Everywhere: Build a Multi-Platform Dev Blog Pipeline with GitHub Actions

Zero to Published: Setting Up a Multi-Platform Dev Blog Pipeline

How to write once in Markdown and automatically publish to Dev.to, Hashnode, Medium, and your personal site without losing your sanity or your SEO

You spent four hours writing the perfect technical post, hit publish on Dev.to, then remembered you also need to post it to Hashnode. And Medium. And your personal blog. By the time you're done reformatting code blocks for the third platform, you've completely massacred your article.

Here's the uncomfortable math: developers who cross-post manually often spend roughly 30–45 minutes per platform adjusting formatting, re-uploading images, and fixing broken syntax highlighting — based on typical manual workflows. That can add up to two or three hours of busywork for every single article—time you could spend actually writing.

What if you could write once in Markdown, push to GitHub, and watch your words automatically appear everywhere your readers hang out—with proper formatting, canonical URLs that protect your SEO, and zero copy-paste gymnastics?

By the end of this guide, you'll have a working GitHub Actions pipeline that publishes to four platforms simultaneously, and you'll never manually cross-post again.

Why Your Blog Deserves More Than One Home

Picture this: you spend four hours crafting the perfect tutorial on async/await patterns. You publish it on your personal blog, feel accomplished, then remember you should probably post it on Dev.to. And Medium. And maybe Hashnode. By the time you've reformatted the code blocks three times and fixed the broken images twice, it's midnight and you're questioning every life choice that led you here.

You're not alone. Developer attention is scattered across a dozen platforms, and no single platform has clearly won broad developer mindshare. Your audience might discover you on Dev.to during their lunch break, stumble across your Medium article from a Google search, or find your personal blog through a conference talk. Missing any of these touchpoints means missing readers — and potential opportunities.

Here's the uncomfortable math: manual cross-posting typically takes roughly 30–45 minutes per platform, per article — based on typical manual workflows. That's not writing time — that's reformatting time. Fixing markdown quirks, re-uploading images, adjusting code syntax highlighting, setting canonical URLs so Google doesn't penalize you for duplicate content. Most developers maintain this discipline for exactly two weeks before their cross-posting ambitions quietly die in a browser tab labeled "Draft - Dev.to."

The pipeline we're building solves this with a simple principle: write once, publish everywhere. You'll create your content in a single markdown file, push to GitHub, and watch as automation handles the rest — deploying to your personal blog while simultaneously cross-posting to Dev.to, Medium, and Hashnode with proper formatting and canonical links intact.

No more copy-paste marathons. No more "I'll cross-post this tomorrow" lies we tell ourselves. Just write, commit, and let the robots handle distribution while you move on to your next article.

The Foundation: Git + Markdown as Your Single Source of Truth

Think of your blog content like source code. You wouldn't store your Python files in Google Docs, manually copying changes between team members' laptops. You'd use Git — because Git tracks every change, lets you branch and experiment, and never loses your work. Your writing deserves the same treatment.

Markdown is the plain-text format that makes this possible. Unlike WordPress or Medium's rich editor, a markdown file is just text. Open it in VS Code, Vim, or Notepad — it works everywhere. When you inevitably decide to switch from Hugo to Astro three years from now, your 47 articles come with you. Try exporting a hundred posts from WordPress sometime; I'll wait.

Frontmatter transforms plain markdown into smart content. Those few lines of YAML at the top of each file become your metadata layer:

---
title: "Building RAG Pipelines That Don't Hallucinate"
date: 2024-01-15
tags: [llm, rag, python]
canonical_url: https://yourdomain.com/posts/rag-pipelines
dev_to: true
medium: true
hashnode: true
---
Enter fullscreen mode Exit fullscreen mode

Platform-specific overrides live here too — maybe Dev.to needs different tags, or Medium requires a subtitle. One file holds everything.

Folder structure matters more than you think. Start simple:

content/
├── drafts/           # Work in progress
├── published/        # Live posts (dated folders)
│   └── 2024-01-15-rag-pipelines/
│       ├── index.md
│       └── images/   # Co-located assets
└── templates/        # Reusable frontmatter
Enter fullscreen mode Exit fullscreen mode

Co-locating images with posts means no broken links when you reorganize. Git tracks your drafts' evolution. Every published piece has a paper trail.

Understanding Canonical URLs (The SEO Magic That Makes This Work)

Imagine you have a favorite family recipe. You share photocopies with relatives, but you write "Original in Mom's cookbook, page 42" at the bottom of each copy. If anyone wants to know the real source, they know exactly where to look. That's a canonical URL — it's you telling search engines "this is my original content, everything else is an authorized copy."

Without this signal, Google sees your brilliant post appearing on your blog, Dev.to, Medium, and Hashnode and thinks: "Four identical articles? Someone's gaming the system." The result? All versions get penalized, or Google picks a random one as the "original" — often not your personal site.

Here's how each platform handles canonicals differently:

  • Dev.to makes it easy — add canonical_url to your frontmatter, and they automatically add the proper <link rel="canonical"> tag pointing back to your blog
  • Hashnode goes further, offering a dedicated "Originally published at" field that both sets the canonical AND displays a visible attribution link
  • Medium is trickier — you must import stories using their "Import a story" feature (not copy-paste) to set canonicals, or manually add it in story settings

The one frontmatter field that saves your SEO:

canonical_url: https://yourblog.com/posts/your-article-slug
Enter fullscreen mode Exit fullscreen mode

That single line is your insurance policy. Every platform in your pipeline should read this field and respect it. Your personal blog becomes the authoritative source, the copies drive traffic back to you, and Google rewards everyone appropriately.

No canonicals? You're essentially competing against yourself for rankings. With them? You're building a syndication network where every platform amplifies your original work.

Building Your Publishing Pipeline with GitHub Actions

Think of GitHub Actions as your personal publishing assistant who never sleeps. You write, you push, they handle the rest — formatting, authenticating, posting to three platforms before your coffee gets cold.

The Basic Trigger: Push and Publish

Your workflow starts simple. When you push to your main branch (specifically to the posts/ folder), the pipeline wakes up:

name: Publish to Platforms
on:
  push:
    branches: [main]
    paths:
      - 'posts/**'
Enter fullscreen mode Exit fullscreen mode

This prevents every tiny README change from triggering a publishing spree. Only new or updated posts start the machine.

Secrets: Your API Keys' Secure Home

Never commit tokens. Ever. GitHub's repository secrets are your vault:

  1. Navigate to Settings → Secrets and variables → Actions
  2. Add DEVTO_API_KEY, HASHNODE_TOKEN, and MEDIUM_TOKEN
  3. Reference them in workflows as ${{ secrets.DEVTO_API_KEY }}

Each platform's API authentication differs slightly — Dev.to uses a simple API key header, Hashnode requires a Personal Access Token with publication permissions, and Medium's integration token needs specific scopes. Store all three; your workflow will pull the right one for each platform.

The Quirks That Will Bite You

Here's where pipelines get messy:

  • Image URLs: Relative paths break everywhere. Convert all images to absolute URLs pointing to your hosted site or a CDN
  • Code blocks: Dev.to handles triple-backtick fencing beautifully; Medium sometimes mangles language hints. Test your syntax highlighting
  • Markdown flavors: Hashnode supports MDX components, Medium strips most formatting, Dev.to has liquid tags. Your pipeline needs platform-specific transforms

The solution? A preprocessing step that reads your canonical Markdown and outputs platform-flavored versions before each API call.

The blogpipe CLI: A Working Cross-Posting Tool

Think of blogpipe as a smart mail carrier that knows each recipient's preferences — it takes your single letter (Markdown post) and reformats the envelope appropriately for every destination.

The architecture is straightforward: Markdown in → frontmatter extraction → platform-specific transforms → API dispatch. When you run blogpipe publish ./posts/my-article.md, here's what actually happens:

  1. The parser reads your file and separates YAML frontmatter (title, tags, canonical URL) from content
  2. Transform functions modify the Markdown per platform — Medium gets simplified code blocks, Dev.to gets liquid tag conversions
  3. API handlers authenticate and POST to each enabled platform

The features that save your sanity:

Dry-run mode (--dry-run) previews exactly what would publish without touching any APIs. It shows you the transformed content for each platform, validates your frontmatter, and catches broken image links. Always run this first.

Canonical injection automatically sets the canonical URL on every platform pointing back to your primary site. This isn't optional — without it, you're creating duplicate content that hurts your SEO and confuses readers who find the same post multiple places.

Image URL transformation rewrites relative paths (./images/diagram.png) to absolute URLs (https://yourblog.dev/posts/my-article/images/diagram.png). Broken images are the fastest way to look unprofessional.

Atomic error handling is critical. If Dev.to publishes successfully but Medium fails, you shouldn't end up with a half-distributed post and no idea what happened. Blogpipe uses a transaction-like approach: it attempts all platforms, collects results, and gives you a clear report of what succeeded, what failed, and retry commands for failures.

When Things Break: Gotchas and Platform Limitations

Let's be honest: this pipeline will break, and usually at the worst possible time. Here's what's going to bite you.

Medium's API Is Basically Hostile

Medium deprecated their official API years ago and never brought it back. The "Integration tokens" in settings technically work but are severely limited — you can create posts, but you can't update them, can't delete them, and can't even reliably fetch your own content. The workaround everyone actually uses? RSS import. You publish to your canonical site, Medium pulls from your RSS feed, and you manually claim the post. It's clunky, but it works consistently. Some developers use unofficial API endpoints discovered through browser inspection, but these break without warning.

Rate Limits Will Find You

Dev.to allows 30 requests per 30 seconds — generous for publishing, but aggressive if you're also fetching to check existing posts. Hashnode's GraphQL API is more forgiving but has daily limits. The solution: implement exponential backoff with jitter. Don't just retry after 1 second, 2 seconds, 4 seconds — add randomness (1.2 seconds, 2.7 seconds, 4.1 seconds) to prevent thundering herd problems if you're running multiple pipelines.

Silent Code Block Destruction

This one hurts. Medium converts triple-backtick code blocks into their proprietary format, often stripping language identifiers and mangling indentation. LinkedIn's article editor is worse — it can completely flatten multi-line code into a single paragraph. Dev.to and Hashnode handle Markdown properly, but always verify after publishing. The safest approach: use GitHub Gist embeds for critical code samples. They render correctly everywhere and update automatically when you fix bugs.

Your First Week: A Practical Rollout Plan

Think of your first week like setting up a new kitchen. Days one and two, you're just getting organized — putting things in the right cabinets so you can actually cook later.

Day 1-2: Build Your Foundation

Create your repository with the folder structure we discussed: /content/posts/, /templates/, and /.github/workflows/. Write your first post in Markdown — pick something short, around 500 words. This isn't about creating your masterpiece; it's about having real content to test the pipeline. Include a code block, an image, and a link. These three elements break most cross-posting workflows, so you want to catch issues early.

Day 3-4: Wire Up Automation (But Don't Go Live)

Configure your GitHub Actions workflow with a critical flag: dry-run: true. This simulates publishing without actually posting anything. You'll see exactly what would happen — which API calls would fire, how your Markdown transforms for each platform, where images would upload. Run this at least three times with small tweaks to your post. Check the output logs obsessively. When everything looks right, manually publish to ONE platform (I recommend Dev.to — its API is the most predictable) and verify the result matches your dry-run preview.

Day 5+: Launch and Learn

Remove the dry-run flag. Publish a real post. Then immediately check all platforms. Something will be wrong — accept this now. Maybe Hashnode stripped a heading level, or Medium's code block lost syntax highlighting. Fix it, update your templates, and try again next week.

Set up basic tracking: which platform drives the most views? Most engagement? After a month of data, you'll know where to focus your energy.


Full working code: GitHub →



Building a multi-platform publishing pipeline isn't about chasing vanity metrics across every site—it's about writing once and letting automation handle the tedious copy-paste-reformat dance that kills most developer blogs before post three. The upfront investment feels steep (five days of setup for a blog post?), but you're not building infrastructure for one article. You're building infrastructure for the next hundred. Every post after this one takes fifteen minutes from draft to published-everywhere, and that changes the economics of writing completely.

Key Takeaways

  • Start with Markdown as your single source of truth — platform-specific quirks get handled in templates, not in your writing process
  • Dry-run mode is non-negotiable — test your pipeline with fake publishes until you trust it, then test it three more times
  • Track results from day one — a month of data will tell you which platforms deserve your attention and which are just noise

What surprised you most about multi-platform publishing? Drop a comment below—I'm especially curious if anyone's found clever workarounds for Medium's image hosting limitations.

Top comments (0)