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:
parent
ff0cc42fa2
commit
9ceb8d3fc6
@ -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
|
||||
|
||||
109
server/routes.ts
109
server/routes.ts
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user