Introduction
vue-better-tweet allows you to embed tweets in your Vue application when using Nuxt, Vite, and more. This library does not require using the Twitter API. Tweets can be rendered statically, preventing the need to include an iframe and additional client-side JavaScript.
You can see how it in action in vue-better-tweet-nuxt-app.vercel.app/light/2014481742875521242 (opens in a new tab). Replace the tweet ID in the URL to see other tweets.
This library is fully compatible with Nuxt Server Components. Learn more (opens in a new tab).
Installation
Install vue-better-tweet using your package manager of choice:
pnpm add vue-better-tweetyarn add vue-better-tweetnpm install vue-better-tweetNow follow the usage instructions for your framework or builder:
Important: Before going to production, we recommend enabling cache for the Twitter API as server IPs might get rate limited by Twitter.
Choosing a theme
The prefers-color-scheme (opens in a new tab) CSS media feature is used to select the theme of the tweet.
Toggling theme manually
The closest data-theme attribute on a parent element can determine the theme of the tweet. You can set it to light or dark, like so:
<div data-theme="dark">
<Tweet id="2014481742875521242" />
</div>Alternatively, a parent with the class light or dark will also work:
<div className="dark">
<Tweet id="2014481742875521242" />
</div>Updating the theme
In CSS Modules, you can use the :global selector to update the CSS variables used by themes:
.my-class :global(.vue-better-tweet-theme) {
--tweet-body-font-size: 1rem;
}For Global CSS the usage of :global is not necessary.
Enabling cache for the Twitter API
Rendering tweets requires making a call to Twitter's syndication API. Getting rate limited by that API is very hard but it's possible if you're relying only on the endpoint we provide for SWR (vue-better-tweet.vercel.app/api/tweet/:id) as the IPs of the server are making many requests to the syndication API. This also applies to RSC where the API endpoint is not required but the server is still making the request from the same IP.
To prevent this, you can use a db like Redis or Vercel KV (opens in a new tab) to cache the tweets. For example using Unstorage (opens in a new tab):
import { fetchTweet, type Tweet } from 'vue-better-tweet/api'
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
if (!id) {
throw createError({ statusCode: 400, statusMessage: 'Missing tweet id' })
}
const storage = useStorage<Tweet>("data")
const cacheKey = `tweet:${id}`
const cachedTweet = await storage.getItem(cacheKey)
if (cachedTweet) return { data: cachedTweet}
try {
const { data, tombstone, notFound } = await fetchTweet(id)
if (data) {
await storage.setItem(cacheKey, data)
return { data }
}
if (tombstone || notFound) {
await storage.removeItem(cacheKey)
throw createError({ statusCode: 404, statusMessage: 'Tweet not found' })
}
} catch (error) {
console.error('fetching the tweet failed with:', error)
}
throw createError({ statusCode: 404, statusMessage: 'Tweet not found' })
})<script setup lang="ts">
import { computed } from 'vue'
import { EmbeddedTweet, TweetNotFound } from 'vue-better-tweet'
const route = useRoute()
const tweetId = computed(() => {
const value = route.params.tweet
return Array.isArray(value) ? value[0] : value
})
const { data, error } = await useAsyncData(
() => `tweet-${tweetId.value}`,
() => $fetch(`/api/tweet/${tweetId.value}`)
)
</script>
<template>
<TweetPage theme="light">
<TweetNotFound v-if="error || !data" :error="error" />
<EmbeddedTweet v-else :tweet="data" />
</TweetPage>
</template>You can see it working at vue-better-tweet-nuxt-app.vercel.app/light/kv/2014481742875521242 (opens in a new tab) (source (opens in a new tab)).