How to Embed Medium Articles on Your Website (Without Scrapers)
Every “read on Medium” link is a leak: you lose session time, brand control, and often search visibility because Google treats outbound teasers differently from full content on your domain.
This guide is a production playbook for embedding Medium posts on your site—architecture first, then code you can paste into a Node or edge function.
Tool outcome: By the end you have a small article cache + render pipeline you can drop behind React, Astro, or WordPress.
Why scraping Medium HTML fails
Teams often start with fetch(mediumUrl) and cheerio. It works until Medium ships a layout change and your pipeline returns empty <article> nodes at 2 a.m.
A durable approach treats Medium as a content source with stable identifiers (article_id), not as a DOM you own.
- Medium’s retired official API is why most developers look for unofficial REST layers today.
- Google’s guidance on syndicated content still applies: pick a canonical strategy and add value on your URLs.
What a real embed looks like
A real embed is not a favicon + title link. It is the full post—headings, images, code blocks—inside your layout, nav, and CTA.
| Format | Best for |
|---|---|
| HTML | Drop-in rendering in React, Next.js, WordPress themes |
| Markdown | Hugo, Astro, Eleventy, static pipelines |
Pattern that scales:
-
Catalog — discover
article_idvalues (search, user feed, editorial list). - Content — fetch body once; store in DB, S3, or edge KV.
- Render — serve from cache on page views (no API call per visitor).
Step 1 — Discover articles
Example: list posts for a writer (replace handle after resolving user_id—see find Medium writers and user IDs).
const API = 'https://api.zenndra.com';
const headers = { Authorization: `Bearer ${process.env.ZENNDRA_API_KEY}` };
// After you have user_id from /user/id_for/{username}
const userId = 'YOUR_USER_ID';
const listRes = await fetch(`${API}/user/${userId}/articles`, { headers });
const { articles } = await listRes.json();
// articles[].id → article_id for step 2
Step 2 — Fetch HTML and cache it
const articleId = 'f5ef1da2850d';
const res = await fetch(`${API}/article/${articleId}/html`, { headers });
if (!res.ok) throw new Error(`Medium API: ${res.status}`);
const { html } = await res.json();
// Persist html keyed by articleId (Redis, Postgres, R2, etc.)
await cache.set(`article:${articleId}:html`, html, { ttl: 86400 });
For static sites, swap /html for /markdown and write files into content/posts/.
Step 3 — Render safely
- Run HTML through a sanitizer (DOMPurify in the browser, or server-side equivalent) if untrusted authors exist.
- Map Medium classes to your design tokens once in CSS.
- Keep a visible “Originally on Medium” link—good for authors and trust.
What to avoid
- DOM scrapers that break silently when markup shifts.
- Title-only embeds—users and crawlers notice.
- Fetching on every request—burns latency and API quota.
Keywords this solves
embed medium articles, medium article html, medium api alternative, syndicate medium blog, medium seo on own domain.
Further reading
- MDN: Fetch API for the HTTP layer
- web.dev: Cache-Control for CDN headers
- Zenndra guide: Embed Medium articles on your website (same flow, product docs)
Top comments (0)