Skip to main content
Scripts

X (formerly Twitter) is a social media platform for sharing posts.

<ScriptXEmbed> fetches post data through your Nuxt server and exposes it through slots. Post JSON and images pass through your origin rather than loading X's widget JavaScript.

X Embed

View source

Nuxt Config Setup

Add this to your nuxt.config.ts to register the server routes that power <ScriptXEmbed>:

export default defineNuxtConfig({
  scripts: {
    registry: {
      xEmbed: {},
    }
  }
})

This script's proxy endpoints use HMAC URL signing when you configure a NUXT_SCRIPTS_PROXY_SECRET. See the security guide for setup instructions.

This registers the required server API routes (/_scripts/embed/x and /_scripts/embed/x-image) that handle fetching tweet data and proxying images.

<ScriptXEmbed>

The post endpoint caches syndication responses for 10 minutes and rewrites profile photos, attached photos, entity media, quoted-post images, and video posters through the image endpoint. Video variant URLs remain unchanged, so rendering one in a <video> element makes a direct browser request to X.

The image proxy validates the initial hostname but currently follows redirects without validating each destination. Do not treat that allowlist as a complete SSRF boundary until redirect targets are checked too.

Demo

Matthew Prince 🌥
Matthew Prince 🌥

@eastdakota

I'm told there are some who read my music choices while writing @Cloudflare's quarterly earnings for hints. Unfortunately, this time I'm sitting in a New York hotel lobby and don't have control over the music. But I do have a crackling real-wood fire — which is pretty awesome!

2:47 AM · Feb 5, 202413 replies125 likes

Slot Props

The default slot receives the following props:

interface SlotProps {
  // Raw data
  tweet: XEmbedTweetData
  // User info
  userName: string
  userHandle: string
  userAvatar: string // Proxied URL
  isVerified: boolean | undefined
  // Tweet content
  text: string
  // Formatted values
  datetime: string // "12:47 PM · Feb 5, 2024"
  createdAt: Date
  likes: number
  likesFormatted: string // "1.2K"
  replies: number
  repliesFormatted: string // "234"
  // Media
  photos?: Array<NonNullable<XEmbedTweetData['photos']>[number] & {
    proxiedUrl: string
  }>
  video: {
    poster: string
    posterProxied: string
    variants: Array<{ type: string, src: string }>
  } | null
  // Links
  tweetUrl: string
  userUrl: string
  // Quote tweet
  quotedTweet?: XEmbedTweetData
  // Reply context
  isReply: boolean
  replyToUser?: string
  // Helpers
  proxyImage: (imageUrl: string) => string
}

Named Slots

SlotDescription
defaultMain content with slot props
loadingShown while fetching tweet data
errorShown if tweet fetch fails, receives { error }

Data flow

The implementation follows Cloudflare Zaraz's server-rendered embed approach. No X JavaScript runs in the page, and X does not receive the visitor's IP address for post JSON or proxied images. Rendered video variants and links to X still contact X directly.

tweetIdstring required

The tweet ID to embed.

apiEndpointstring = '/_scripts/embed/x'

Optional: Custom API endpoint for fetching tweet data.

imageProxyEndpointstring = '/_scripts/embed/x-image'

Optional: Custom image proxy endpoint.