TwitterCard (Component)
Description
The TwitterCard component displays real-time Twitter/X posts as testimonials or social proof. It fetches live tweet data using the X API and presents it in a beautiful, modern card format perfect for showcasing customer feedback and testimonials.
Preview
*Modern Twitter card displaying real tweet data with author info, content, and engagement metrics*Features
- Real-time data fetched from X API
- Author information with profile and username
- Formatted dates in French locale
- Engagement metrics (likes, retweets)
- Direct links to original tweets
- Responsive design with hover effects
- Loading states with skeleton animation
- Modern styling with shadcn/ui design system
Usage
Basic Usage
vue
<template>
<TwitterCard tweet-url="https://x.com/mhdevfr/status/1963265765341040667" />
</template>In Testimonials Section
vue
<template>
<section class="py-16 bg-gray-50">
<div class="max-w-6xl mx-auto px-4">
<h2 class="text-3xl font-bold text-center mb-12">
What Our Users Say
</h2>
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
<TwitterCard
tweet-url="https://x.com/user1/status/1234567890"
/>
<TwitterCard
tweet-url="https://x.com/user2/status/1234567891"
/>
<TwitterCard
tweet-url="https://x.com/user3/status/1234567892"
/>
</div>
</div>
</section>
</template>With Custom Styling
vue
<template>
<div class="testimonials-grid">
<TwitterCard
tweet-url="https://x.com/mhdevfr/status/1963265765341040667"
class="testimonial-card"
/>
</div>
</template>
<style>
.testimonial-card {
transform: scale(1.05);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
}
.testimonial-card:hover {
transform: scale(1.08);
}
</style>Props
| Prop | Type | Required | Description |
|---|---|---|---|
tweetUrl | string | ✅ | Full Twitter/X URL of the tweet |
API Configuration
X API Setup
To use the TwitterCard component, you need to configure the X API:
1. Create X Developer Account
- Go to X Developer Portal
- Create a new project
- Generate API keys and tokens
2. Environment Variables
Add these to your .env file:
bash
# X API Configuration
X_API_KEY=your_x_api_key
X_API_SECRET=your_x_api_secret
X_BEARER_TOKEN=your_x_bearer_token3. API Routes
The component uses these API endpoints:
typescript
// server/api/twitter/tweet/[id].get.ts
export default defineEventHandler(async (event) => {
const tweetId = getRouterParam(event, 'id')
const config = useRuntimeConfig()
const response = await $fetch(`https://api.twitter.com/2/tweets/${tweetId}`, {
params: {
'tweet.fields': 'created_at,public_metrics,text',
'user.fields': 'name,username,profile_image_url',
'expansions': 'author_id'
},
headers: {
'Authorization': `Bearer ${config.xBearerToken}`
}
})
return response
})Data Structure
The component fetches and displays:
typescript
interface TwitterData {
id: string
text: string
author: {
name: string
username: string
profile_image_url?: string
}
created_at: string
public_metrics?: {
like_count: number
retweet_count: number
reply_count: number
quote_count: number
}
}Examples
Customer Testimonials
vue
<template>
<section class="testimonials">
<div class="container">
<h2>Customer Love</h2>
<div class="testimonials-grid">
<TwitterCard
tweet-url="https://x.com/customer1/status/1234567890"
/>
<TwitterCard
tweet-url="https://x.com/customer2/status/1234567891"
/>
<TwitterCard
tweet-url="https://x.com/customer3/status/1234567892"
/>
</div>
</div>
</section>
</template>
<style>
.testimonials-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
</style>Product Reviews
vue
<template>
<div class="reviews-section">
<h3>Product Reviews</h3>
<div class="reviews-list">
<TwitterCard
v-for="review in reviews"
:key="review.id"
:tweet-url="review.tweetUrl"
/>
</div>
</div>
</template>
<script setup>
const reviews = [
{
id: 1,
tweetUrl: 'https://x.com/reviewer1/status/1234567890'
},
{
id: 2,
tweetUrl: 'https://x.com/reviewer2/status/1234567891'
}
]
</script>Social Proof Section
vue
<template>
<section class="social-proof">
<div class="max-w-4xl mx-auto text-center">
<h2 class="text-4xl font-bold mb-8">
Trusted by Developers Worldwide
</h2>
<p class="text-xl text-gray-600 mb-12">
See what developers are saying about ClawPlate
</p>
<div class="grid md:grid-cols-2 gap-8">
<TwitterCard
tweet-url="https://x.com/dev1/status/1963265765341040667"
/>
<TwitterCard
tweet-url="https://x.com/dev2/status/1963265765341040668"
/>
</div>
<div class="mt-8">
<a
href="https://x.com/search?q=clawplate"
target="_blank"
class="inline-flex items-center px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
<Icon name="simple-icons:x" class="w-5 h-5 mr-2" />
See More on X
</a>
</div>
</div>
</section>
</template>Styling
The component uses Tailwind CSS with shadcn/ui design tokens:
Custom Themes
vue
<template>
<TwitterCard
tweet-url="https://x.com/user/status/1234567890"
class="custom-twitter-card"
/>
</template>
<style>
.custom-twitter-card {
@apply bg-gradient-to-br from-blue-50 to-indigo-50;
@apply border-blue-200;
}
.custom-twitter-card:hover {
@apply shadow-2xl;
@apply transform scale-105;
}
</style>Dark Mode Support
The component automatically adapts to dark mode:
css
.dark .twitter-card {
background-color: #1f2937;
border-color: #374151;
}Error Handling
The component includes fallback mechanisms:
- Primary API: X API v2 for full tweet data
- Fallback API: oEmbed API for basic tweet info
- Loading States: Skeleton animation during fetch
- Error States: Graceful degradation if APIs fail
Performance
Caching Strategy
typescript
// server/api/twitter/tweet/[id].get.ts
const cache = new Map()
export default defineEventHandler(async (event) => {
const tweetId = getRouterParam(event, 'id')
// Check cache first
if (cache.has(tweetId)) {
return cache.get(tweetId)
}
// Fetch from API
const data = await fetchTweetData(tweetId)
// Cache for 5 minutes
cache.set(tweetId, data)
setTimeout(() => cache.delete(tweetId), 5 * 60 * 1000)
return data
})Rate Limiting
typescript
// Implement rate limiting
const rateLimiter = new Map()
export default defineEventHandler(async (event) => {
const clientIP = getClientIP(event)
const now = Date.now()
if (rateLimiter.has(clientIP)) {
const lastRequest = rateLimiter.get(clientIP)
if (now - lastRequest < 1000) { // 1 second cooldown
throw createError({
statusCode: 429,
statusMessage: 'Too Many Requests'
})
}
}
rateLimiter.set(clientIP, now)
// ... rest of handler
})Best Practices
For Testimonials
- Curate Quality Tweets: Choose tweets that highlight specific benefits
- Mix Content Types: Include different types of feedback (reviews, use cases, etc.)
- Regular Updates: Refresh testimonials regularly to show recent feedback
- Permission: Always ask permission before featuring customer tweets
For Performance
- Cache Responses: Implement caching to reduce API calls
- Lazy Loading: Load Twitter cards only when they're visible
- Error Boundaries: Handle API failures gracefully
- Rate Limiting: Implement proper rate limiting
For Accessibility
- Alt Text: Ensure images have proper alt text
- Keyboard Navigation: Make sure all links are keyboard accessible
- Screen Readers: Test with screen readers
- Color Contrast: Ensure sufficient color contrast
Troubleshooting
Common Issues
API Rate Limits
- Implement proper caching
- Use fallback oEmbed API
- Consider pre-fetching popular tweets
Tweet Not Loading
- Check if tweet is public
- Verify API credentials
- Check network connectivity
Styling Issues
- Ensure Tailwind CSS is loaded
- Check for CSS conflicts
- Verify dark mode classes
This component is part of the ClawPlate suite and is perfect for showcasing real customer testimonials and social proof.
