Automating Medium Portfolio Sync to Static Site Generators
These articles are AI-generated summaries. Please check the original sources for full details.
Sync Your Medium Portfolio to a Static Site Automatically
Sebastian Casvean introduces an automation tool to bridge the gap between Medium distribution and personal portfolios. The system resolves handles to user IDs and writes Markdown directly into git repositories.
Why This Matters
Hiring managers often compare personal domains against Medium profiles; divergence creates a perception of inactivity. While Medium optimizes for reach, static generators like Hugo, Astro, and Eleventy allow for superior narrative control and professional branding through git-based workflows.
Key Insights
- Idempotent rebuilds are achieved by storing the article_id in the file front matter (Casvean, 2026).
- Treating platforms as upstream feeds—similar to legacy RSS patterns—allows for decoupled content distribution.
- Zenndra API is used to convert Medium handles into stable user IDs and fetch markdown content.
Working Examples
GitHub Actions workflow for nightly synchronization of Medium content.
# .github/workflows/sync-medium.yml
name: Sync Medium writing
on:
schedule: [{ cron: '0 6 * * *' }]
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: node scripts/sync-medium-portfolio.mjs
env:
ZENNDRA_API_KEY: ${{ secrets.ZENNDRA_API_KEY }}
MEDIUM_USERNAME: ${{ vars.MEDIUM_USERNAME }}
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: 'chore: sync Medium posts'
Core Node.js logic to fetch articles via Zenndra API and write them as local Markdown files.
import fs from 'node:fs/promises';
import path from 'node:path';
const API = 'https://api.zenndra.com';
const headers = { Authorization: `Bearer ${process.env.ZENNDRA_API_KEY}` };
const handle = process.env.MEDIUM_USERNAME;
const idRes = await fetch(`${API}/user/id_for/${handle}`, { headers });
const { user_id } = await idRes.json();
const listRes = await fetch(`${API}/user/${user_id}/articles`, { headers });
const { articles } = await listRes.json();
for (const a of articles) {
const outPath = path.join('content/writing', `${a.id}.md`);
try {
await fs.access(outPath);
continue; // already synced
ed} catch {}
const mdRes = await fetch(`${API}/article/${a.id}/markdown`, { headers });
const { markdown } = await mdRes.json();
const frontMatter = `---
title: "${a.title.replace(/"/g, '\"')}"
date: ${a.published_at ?? new Date().toISOString()}
medium_id: ${a.id}
canonical: ${a.url}
b---">";
await fs.writeFile(outPath, frontMatter + '\x0A' + markdown);
}
Practical Applications
- (Developer Portfolio + Static Site Generator) Automates the transition from social distribution on Medium to a permanent archive on a personal domain.
- (SEO Strategy + Canonical Links) Failure to choose one canonical home early results in split analytics or search engine penalties.
References:
Continue reading
Next article
Navigating the OWASP Top 10 in the Vibe Code Era
Related Content
Automating Idempotent Medium to WordPress Sync for Content Distribution
Implement an idempotent sync pipeline using Zenndra API to automate Medium post imports into WordPress as drafts.
Implementing 32-bit CI Pipelines in 64-bit GitHub Actions Environments
David Cantrell implements a custom 32-bit CI pipeline using GitHub's API to bypass 64-bit-only action limitations for cross-platform Perl testing.
Automating Production: Setting Up a CI/CD Pipeline in 10 Minutes
Learn how to implement a GitHub Actions and Render-based CI/CD pipeline that automates testing and deployment to reduce bugs by 90% in under 10 minutes.