A Val Town-optimized version of react-tweet for server-side rendering in Deno environments without bundlers.
- ✅ No Build Step Required - Works directly in Val Town's Deno runtime
- ✅ Tailwind v4 Styling - Uses Tailwind CDN for beautiful, responsive tweets
- ✅ Server-Side Only - Optimized for Val Town's server-side JSX rendering
- ✅ Full TypeScript Support - Complete type definitions included
- ✅ Twitter API Integration - Fetches tweets from Twitter's syndication API
- ✅ Dark Mode Support - Automatic dark mode via Tailwind
- ✅ Verified Badges - Shows verification status
- ✅ Media Support - Images, videos, and GIFs
- ✅ Quoted Tweets - Displays quoted tweets inline
- ✅ Reply Threads - Shows reply context
/** @jsxImportSource npm:react */
import { Tweet } from "./react-tweet-valtown/src/index.ts";
export default async function(req: Request) {
const url = new URL(req.url);
const tweetId = url.searchParams.get("id") || "1683899539984359424";
return new Response(
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tweet {tweetId}</title>
{/* Required: Tailwind v4 CDN */}
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body>
<Tweet id={tweetId} />
</body>
</html>,
{
headers: { "Content-Type": "text/html; charset=utf-8" }
}
);
}
Your tweet will be fetched and rendered server-side with full styling.
<div> <Tweet id="1683899539984359424" /> <Tweet id="1234567890" /> </div>
<Tweet
id="1683899539984359424"
onError={(error) => console.error("Failed to load tweet:", error)}
/>
<Tweet
id="1683899539984359424"
components={{
TweetNotFound: () => <div>This tweet could not be found</div>,
AvatarImg: (props) => <img {...props} loading="lazy" />,
MediaImg: (props) => <img {...props} loading="lazy" />
}}
/>
Main component for rendering tweets.
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | Required | The tweet ID to fetch and render |
components | TwitterComponents | undefined | Custom component overrides |
fetchOptions | RequestInit | undefined | Custom fetch options for the Twitter API |
fallback | JSX.Element | <TweetSkeleton /> | Fallback component while loading |
onError | (error: any) => any | undefined | Error handler callback |
Fetches a tweet from Twitter's syndication API.
import { fetchTweet } from "./react-tweet-valtown/src/index.ts";
const result = await fetchTweet("1683899539984359424");
if (result.data) {
console.log(result.data);
}
Returns:
{
data?: Tweet; // Tweet data if found
tombstone?: true; // True if tweet is private
notFound?: true; // True if tweet doesn't exist
}
Wrapper around fetchTweet that returns just the tweet data.
import { getTweet } from "./react-tweet-valtown/src/index.ts";
const tweet = await getTweet("1683899539984359424");
if (tweet) {
console.log(tweet.text);
}
Returns: Promise<Tweet | undefined>
Enriches a tweet with additional computed properties.
import { enrichTweet, getTweet } from "./react-tweet-valtown/src/index.ts";
const tweet = await getTweet("1683899539984359424");
if (tweet) {
const enriched = enrichTweet(tweet);
console.log(enriched.url); // https://x.com/user/status/123...
console.log(enriched.user.url); // https://x.com/user
console.log(enriched.like_url); // https://x.com/intent/like?tweet_id=123...
}
import { formatNumber, formatDate, getMediaUrl } from "./react-tweet-valtown/src/index.ts";
formatNumber(1234567); // "1.2M"
formatNumber(1234); // "1.2K"
const date = new Date("2024-01-15T12:00:00Z");
formatDate(date); // "12:00 PM · Jan 15, 2024"
getMediaUrl(media, "small"); // Formatted media URL
getMediaUrl(media, "medium");
getMediaUrl(media, "large");
The package uses Tailwind v4 for styling. Include the CDN in your HTML:
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
Dark mode is automatically supported via Tailwind's dark mode:
<html class="dark"> <!-- Tweets will use dark mode colors --> </html>
Or use system preference:
<!-- Automatically uses user's system preference --> <html> <head> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> </head> <body> <Tweet id="123" /> </body> </html>
Customize colors using Tailwind's theme configuration:
<style type="text/tailwindcss"> @theme { --color-tweet-blue: #your-color; --color-tweet-red: #your-color; } </style>
Add your own styles:
<style> .react-tweet { /* Override tweet container styles */ max-width: 700px; } </style>
All components are exported and can be used individually:
import {
Tweet, // Main component
EmbeddedTweet, // Tweet without data fetching
TweetContainer, // Container wrapper
TweetHeader, // Author info and avatar
TweetBody, // Tweet text
TweetMedia, // Images/videos
TweetActions, // Like, reply buttons
TweetInfo, // Date and metadata
TweetInReplyTo, // Reply context
TweetReplies, // Reply count
TweetNotFound, // Error state
TweetSkeleton, // Loading state
QuotedTweet, // Quoted tweet component
} from "./react-tweet-valtown/src/index.ts";
import { getTweet, enrichTweet, EmbeddedTweet } from "./react-tweet-valtown/src/index.ts";
export default async function() {
const tweet = await getTweet("123");
if (!tweet) return <div>Not found</div>;
return <EmbeddedTweet tweet={tweet} />;
}
Full TypeScript support with comprehensive type definitions:
import type {
Tweet,
EnrichedTweet,
TweetUser,
TwitterComponents,
TweetProps,
} from "./react-tweet-valtown/src/index.ts";
const customComponent: TwitterComponents = {
TweetNotFound: () => <div>Custom not found</div>
};
| Feature | react-tweet | react-tweet-valtown |
|---|---|---|
| Build Step | Required (SWC/Webpack) | Not required ✅ |
| Styling | CSS Modules | Tailwind v4 CDN |
| Runtime | Node.js/Browser | Deno (Val Town) |
| Client-Side | Supported (SWR) | Not supported ⚠️ |
| Server-Side | Supported (RSC) | Supported ✅ |
| Import Syntax | Standard npm | npm: prefix for npm packages |
| Bundler | Vite/Next/Webpack | None required |
- ❌ Client-side rendering - Val Town's JSX is server-only
- ❌ SWR variant - No
useTweet()hook - ❌ Interactive features - No onClick handlers or client state
- ❌ Custom fonts loading - Use CDN fonts instead
See example.tsx for a complete working example.
export default async function(req: Request) {
const url = new URL(req.url);
const theme = url.searchParams.get("theme") || "light";
return new Response(
<html className={theme}>
<head>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body>
<Tweet id="123" />
<a href="?theme=dark">Dark</a> | <a href="?theme=light">Light</a>
</body>
</html>,
{ headers: { "Content-Type": "text/html; charset=utf-8" } }
);
}
export default async function() {
return new Response(
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
{`
@theme {
--color-tweet-blue: #8b5cf6; /* Purple instead of blue */
}
`}
</style>
</head>
<body className="bg-gradient-to-br from-purple-100 to-pink-100 min-h-screen p-8">
<div className="max-w-2xl mx-auto">
<h1 className="text-4xl font-bold mb-8">My Tweets</h1>
<Tweet id="123" />
</div>
</body>
</html>,
{ headers: { "Content-Type": "text/html; charset=utf-8" } }
);
}
export default async function(req: Request) {
const tweetIds = [
"1683899539984359424",
"1234567890",
"9876543210",
];
return new Response(
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body>
<div className="max-w-2xl mx-auto space-y-4 p-4">
{tweetIds.map(id => (
<Tweet key={id} id={id} />
))}
</div>
</body>
</html>,
{ headers: { "Content-Type": "text/html; charset=utf-8" } }
);
}
Make sure the tweet ID is correct and the tweet is public. Private or deleted tweets will show the "not found" component.
Ensure you've included the Tailwind CDN script:
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
Make sure you're using the correct import syntax for Val Town:
/** @jsxImportSource npm:react */ // Required at top of file
import { Tweet } from "./react-tweet-valtown/src/index.ts"; // Local file
Ensure your Val Town is using TypeScript mode. Add the JSX pragma at the top of your file.
- Server-side rendering: Fast initial load, no client-side JavaScript needed for display
- Tailwind CDN: ~50KB (cached across sites)
- Tweet data: Fetched once per render from Twitter's CDN
- Caching: Implement your own caching layer if needed
const tweetCache = new Map();
export default async function(req: Request) {
const id = new URL(req.url).searchParams.get("id") || "123";
if (!tweetCache.has(id)) {
const tweet = await getTweet(id);
tweetCache.set(id, tweet);
}
const tweet = tweetCache.get(id);
// ... render tweet
}
This is an adaptation of the original react-tweet package. Please report Val Town-specific issues in this repository.
MIT - Same as the original react-tweet package.
- Original package: react-tweet by Vercel
- Adapted for Val Town by converting CSS Modules to Tailwind v4
- Twitter API integration maintained from original