Update video sharing to display correct images and text

Introduce a crawler detection middleware to serve Open Graph and Twitter Card metadata for social media sharing, and update the client-side sharing logic to utilize a dedicated share endpoint.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 401e2ec0-e00d-4f10-9d0e-60f3d479f9a5
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 1a172d71-eb2c-471b-871f-cbf561747bbf
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/60d372ff-2c10-46c7-b01b-10c3435136b0/401e2ec0-e00d-4f10-9d0e-60f3d479f9a5/lDvepVp
This commit is contained in:
sebastjanartic 2026-01-10 16:29:52 +00:00
parent ff0cc42fa2
commit 9ceb8d3fc6
2 changed files with 122 additions and 12 deletions

View File

@ -308,23 +308,24 @@ export default function VideoPage() {
};
// Share URL uses special endpoint with proper OG meta tags for social media previews
// This URL shows thumbnail, title, and description when shared on Facebook, Viber, WhatsApp, etc.
const getShareUrl = () => {
if (!currentVideo?.id) return window.location.origin;
// Use custom domain if set, otherwise current domain
const baseUrl = import.meta.env.VITE_SHARE_DOMAIN || window.location.origin;
return `${baseUrl}/video/${currentVideo.id}`;
};
// Facebook share URL uses special endpoint with proper OG meta tags
const getFacebookShareUrl = () => {
if (!currentVideo?.id) return window.location.origin;
const baseUrl = 'https://video.folx.tv';
return `${baseUrl}/share/video/${currentVideo.id}`;
};
// Direct video URL (for display purposes)
const getVideoUrl = () => {
if (!currentVideo?.id) return window.location.origin;
const baseUrl = 'https://video.folx.tv';
return `${baseUrl}/video/${currentVideo.id}`;
};
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(getFacebookShareUrl());
await navigator.clipboard.writeText(getShareUrl());
const notification = document.createElement('div');
notification.textContent = 'Link kopiert!';
notification.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg z-50 transition-opacity duration-300';
@ -739,19 +740,19 @@ export default function VideoPage() {
{showShareMenu && (
<div className="absolute right-0 top-full mt-2 bg-gray-800 rounded-lg shadow-lg py-2 z-50 min-w-[200px]">
<FacebookShareButton url={getFacebookShareUrl()}>
<FacebookShareButton url={getShareUrl()}>
<div className="w-full px-4 py-2 text-left text-white hover:bg-gray-700 flex items-center gap-2 cursor-pointer">
<FacebookIcon size={16} round />
Facebook
</div>
</FacebookShareButton>
<TwitterShareButton url={getFacebookShareUrl()} title={`Watch "${currentVideo.title}" on video.folx.tv`}>
<TwitterShareButton url={getShareUrl()} title={`Watch "${currentVideo.title}" on video.folx.tv`}>
<div className="w-full px-4 py-2 text-left text-white hover:bg-gray-700 flex items-center gap-2 cursor-pointer">
<TwitterIcon size={16} round />
Twitter
</div>
</TwitterShareButton>
<WhatsappShareButton url={getFacebookShareUrl()} title={`Watch "${currentVideo.title}" on video.folx.tv`}>
<WhatsappShareButton url={getShareUrl()} title={`Watch "${currentVideo.title}" on video.folx.tv`}>
<div className="w-full px-4 py-2 text-left text-white hover:bg-gray-700 flex items-center gap-2 cursor-pointer">
<WhatsappIcon size={16} round />
WhatsApp

View File

@ -83,6 +83,115 @@ export async function registerRoutes(app: Express): Promise<Server> {
// Setup Replit Auth first
await setupAuth(app);
// Social media crawler detection middleware for /video/:id routes
// This serves proper OG meta tags to crawlers while letting regular users get the SPA
app.get('/video/:id', async (req, res, next) => {
const userAgent = req.headers['user-agent']?.toLowerCase() || '';
// List of social media crawler user agents
const crawlers = [
'facebookexternalhit',
'facebot',
'twitterbot',
'whatsapp',
'telegrambot',
'linkedinbot',
'pinterest',
'slackbot',
'viberbot',
'discordbot',
'applebot',
'googlebot',
'bingbot',
'yandex',
'baiduspider',
'duckduckbot'
];
const isCrawler = crawlers.some(crawler => userAgent.includes(crawler));
if (!isCrawler) {
// Not a crawler, let the SPA handle it
return next();
}
try {
const { id } = req.params;
// Find video from cache
const allVideos = await storage.getVideos(600, 0);
let video;
if (id.length === 36 && id.includes('-')) {
video = allVideos.find(v => v.id === id);
} else if (id.length === 8) {
video = allVideos.find(v => v.id.replace(/-/g, '').substring(0, 8) === id);
} else {
video = allVideos.find(v => v.id.includes(id));
}
if (!video) {
return next(); // Let SPA handle 404
}
const baseUrl = 'https://video.folx.tv';
const videoUrl = `${baseUrl}/video/${video.id}`;
// Get high-quality thumbnail for sharing (1200x630 is ideal for Facebook)
const thumbnailUrl = video.thumbnailUrl
? video.thumbnailUrl.replace('width=400&height=225', 'width=1200&height=630').replace('format=webp', 'format=jpg')
: `${baseUrl}/api/social-image`;
// Clean description for meta tags
const description = video.description
? video.description.substring(0, 200).replace(/[<>"']/g, '')
: `Schauen Sie ${video.title} auf video.folx.tv - Die beste Musik`;
const title = video.title || 'video.folx.tv';
// Return HTML page with OG tags for crawlers
const html = `<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title} - video.folx.tv</title>
<!-- Open Graph meta tags for Facebook/Social Media -->
<meta property="og:type" content="video.other">
<meta property="og:title" content="${title}">
<meta property="og:description" content="${description}">
<meta property="og:image" content="${thumbnailUrl}">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:type" content="image/jpeg">
<meta property="og:url" content="${videoUrl}">
<meta property="og:site_name" content="video.folx.tv">
<meta property="og:locale" content="de_DE">
<!-- Twitter Card meta tags -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${title}">
<meta name="twitter:description" content="${description}">
<meta name="twitter:image" content="${thumbnailUrl}">
<link rel="canonical" href="${videoUrl}">
</head>
<body>
<h1>${title}</h1>
<p>${description}</p>
<p><a href="${videoUrl}">Watch on video.folx.tv</a></p>
</body>
</html>`;
res.set('Content-Type', 'text/html');
res.send(html);
} catch (error) {
console.error('Error generating OG tags for crawler:', error);
next(); // Let SPA handle errors
}
});
// Add compression middleware for better performance
app.use(compression({
level: 6,