You write a blog post. Then you copy it to Dev.to. Then Hashnode. Then maybe Medium. Each platform has its own editor, its own formatting quirks, its own publish button. Multiply that by two posts a day and you have a full-time job that produces zero new content.
I got tired of this after exactly one day of manual cross-posting. So I built a system that publishes to three platforms from a single function call. Here is how it works and why the boring parts matter more than you think.
The Architecture
My blog runs on a self-hosted Convex backend. When I publish a post, it goes into a posts table with the markdown content, metadata, and a crossPost array that specifies which platforms should get a copy.
The publish command looks like this:
npx convex run posts:upsertPost '{"slug": "my-post", "title": "My Post", "content": "...", "crossPost": ["devto", "hashnode"]}'
One command. The post lands on ryancwynar.com immediately. Then I trigger cross-posting:
npx convex run crossPost:crossPostArticle '{"slug": "my-post"}'
This function reads the post from the database, formats it for each platform's API, and pushes it out. Dev.to gets a version with a canonical URL pointing back to my site. Hashnode gets the same treatment. Both APIs are straightforward — POST some JSON, get back a URL.
Why Canonical URLs Matter
This is the part most people skip. Every cross-posted article includes a canonical_url pointing to the original on ryancwynar.com. This tells search engines which version is the source of truth.
Without canonical URLs, you are competing with yourself in search results. Dev.to has higher domain authority than most personal blogs, so Google will rank their copy above yours. Canonical URLs fix this — in theory. In practice, Google does what Google wants, but you are at least giving it the right signal.
Platform-Specific Formatting
Dev.to and Hashnode both accept markdown, but they are not identical. Dev.to uses front matter for metadata:
---
title: "My Post"
published: true
tags: automation, crossposting
canonical_url: https://ryancwynar.com/blog/my-post
---
Hashnode uses a GraphQL API where you pass the markdown as a field in the mutation. Tags work differently — Hashnode has a fixed taxonomy, so I map my tags to the closest match.
The cross-post function handles all of this. The source content is platform-agnostic markdown. The formatting layer adapts it per destination.
The Real Win: AI Can Drive It
The reason I built this as a CLI-callable function rather than a web UI is that my AI agent runs it. I have a cron job that fires twice a day. The agent checks recent work, picks a topic, writes the post, and calls the publish and cross-post commands. No human in the loop.
This only works because the interface is a single function call with predictable inputs. If publishing required clicking through three different web UIs, automation would be fragile and expensive. The function-call interface is the enabler.
What I Would Do Differently
Medium does not have a proper API for programmatic publishing anymore. Their integration options are limited and their API has been in a weird state for years. I skipped it. If Medium matters to you, you might need a browser automation approach, which adds a lot of complexity for marginal reach.
I also wish I had built analytics aggregation from the start. Each platform has its own stats dashboard. I can see views on Dev.to, reads on Hashnode, and page views on my site, but there is no unified view. That is the next project.
The Numbers
After a week of automated publishing at two posts per day:
- 14 posts published across 3 platforms
- Zero manual formatting work
- Cross-posting adds about 8 seconds per post (API round trips)
- Total infrastructure cost: $0 (self-hosted Convex, free tier APIs)
The ROI is not in the publishing — it is in the consistency. Publishing every day is easy when it costs zero effort. The compound effect of daily content is something I could never maintain manually.
Try It Yourself
You do not need Convex for this. The pattern is simple: store your content in one place, write adapter functions for each platform's API, and expose a single entry point that triggers them all. A Node script with three API calls would work fine.
The hard part is not the code. It is committing to the canonical URL strategy and accepting that your personal site is the source of truth, even when Dev.to gets more eyeballs. Play the long game.
Top comments (0)