react-x-embed
v1.0.1
Published
Embed X (Twitter) posts in React — link previews, responsive media, and payload validation. No API key required.
Maintainers
Readme
react-x-embed
Embed X (Twitter) posts in React. No API key, no iframe, no client-side JavaScript required.
A fork of vercel/react-tweet that fixes the outstanding crashes, matches X's own embed layout, and renders the data the syndication API returns but upstream ignored.
pnpm add react-x-embedimport { Tweet } from 'react-x-embed'
export default function Page() {
return <Tweet id="1628832338187636740" />
}Fully compatible with React Server Components — tweets render statically, so nothing ships to the client.
Why this fork
Every change below is anchored to a real payload. The repo ships eight captured syndication responses plus X's own rendered HTML, CSS, and computed geometry for each, so layout decisions are measured against X rather than guessed at.
Crashes fixed
- Videos with no mp4 rendition.
getMp4Videoreturnedundefinedand the player dereferenced.urlon it. Those tweets took down the page. - Malformed payloads. A deleted or suspended account returns a tweet-shaped object with no
user, which threwCannot read properties of undefined (reading 'screen_name')on the first property access. Responses are now validated before rendering and degrade to the "not found" state. (#135, #144, #197)
Bugs fixed
- Video in Safari (#191) — X ships an HLS rendition alongside the mp4s, and Safari plays it far more reliably than X's mp4 endpoints, which don't always honour byte-range requests. It's now offered as a
<source>, listed first so Safari prefers it. AbortErroron play (#213) — the click handler resolved the video throughpreviousSibling, but hiding the play button re-renders the subtree and could detach that node beforeplay()settled. Now resolved through a ref.- Portrait media dominating the embed (#159) — a 9:16 video rendered at 177.8% padding, nearly twice as tall as the embed is wide. Capped at 100%, matching X.
- Blurry and missing avatars (#201) — the API only reports the 48×48
_normalrendition, which is soft on retina and the one X purges most aggressively. Upgraded to_400x400. - Infinite loading state —
useTweetreportedisLoading: trueforever when given noid, since SWR never fetches without a key. Common with ids from routes that haven't resolved. - Build failure in React Router / Vite (#206) — Rollup resolves
export *through a'use client'module inconsistently and reporteduseTweetas missing. Now re-exported by name. - Touch targets below WCAG minimum (#173) — the author name, handle and Follow link rendered 20px tall against WCAG 2.2's 24px floor. Measured in Chrome: three failures before, zero after, with no visual change.
- Unoverridable container styles (#192) —
marginandmax-widthtied on specificity with consumer utility classes, so source order decided, and consumer CSS usually loads first. Both moved to:where()for zero specificity.
Added
- Link preview cards. The syndication API returns a fully populated
cardfor any tweet sharing a link — title, description, domain, and up to seven image renditions. Upstream rendered none of it. This was the largest visible gap against X's own embeds. - Responsive images. A
srcsetbuilt from the renditions X advertises, instead of always requesting the 680px one. - Colour placeholders. The dominant colour X computed for each image fills its box while it loads, rather than flashing empty.
- Withheld media handling. DMCA takedowns and region blocks still appear in the payload but 404 on fetch; they're filtered instead of rendering broken.
- Video quality selection.
getMp4Video(media, 'low' | 'medium' | 'high')instead of a hardcoded rendition.
Documentation
Full docs live in apps/site. Start with the introduction, then the guide for your framework:
Caching
Rendering a tweet calls X's syndication API. Getting rate limited is hard but possible, particularly if you rely on the default SWR endpoint — it's a shared service this project doesn't operate. Before production, set up caching and your own API route.
What this cannot do
The syndication API is the only endpoint that works without credentials, and it returns exactly one tweet at a time. Timelines, user profiles, search, threads, and reposts are not reachable — every other endpoint is either credential-gated or returns nothing. A repost also resolves to the original tweet, so it cannot be distinguished from it.
Engagement data is limited to favorite_count and conversation_count. There is no retweet, reply, quote, bookmark, or view count on the tweet itself.
Contributing
See the contributing docs. Tests run against captured fixtures, so pnpm test needs no network access.
License
MIT. Originally created by Luis Alvarez at Vercel; see license.md.
