Skip to main content

On This Page

Automating Medium Portfolio Sync to Static Site Generators

2 min read
Share

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