Add an artist listing page and improve SEO for various pages
Introduces a new `/kuenstler` page, API endpoint `/api/artists`, and enhances meta tags on `index.html` and for crawler requests on `/kuenstler`. Adds `KuenstlerPage.tsx` and updates `routes.ts` and `index.html`. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 401e2ec0-e00d-4f10-9d0e-60f3d479f9a5 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 69ee4fdb-c617-4cdd-b9f6-d3fab268d533 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/60d372ff-2c10-46c7-b01b-10c3435136b0/401e2ec0-e00d-4f10-9d0e-60f3d479f9a5/qFrskyV
This commit is contained in:
+132
-2
@@ -20,6 +20,30 @@ import { setupAuth, isAuthenticated, isAdmin } from "./replitAuth";
|
||||
import { ObjectStorageService, ObjectNotFoundError } from "./objectStorage";
|
||||
import { generateVideoDescription, generateBulkDescriptions } from "./aiService";
|
||||
|
||||
// Extract unique artist names from video titles
|
||||
function extractArtists(videos: any[]): { name: string; videoCount: number; videos: any[] }[] {
|
||||
const artistMap = new Map<string, any[]>();
|
||||
for (const v of videos) {
|
||||
const title = v.title || '';
|
||||
let artist = '';
|
||||
if (title.includes('–')) {
|
||||
artist = title.split('–')[0].trim();
|
||||
} else if (title.includes(' - ')) {
|
||||
artist = title.split(' - ')[0].trim();
|
||||
}
|
||||
if (artist && !artist.toUpperCase().startsWith('FOLX')) {
|
||||
const normalized = artist.replace(/\s+/g, ' ').trim();
|
||||
if (!artistMap.has(normalized)) {
|
||||
artistMap.set(normalized, []);
|
||||
}
|
||||
artistMap.get(normalized)!.push(v);
|
||||
}
|
||||
}
|
||||
return Array.from(artistMap.entries())
|
||||
.map(([name, vids]) => ({ name, videoCount: vids.length, videos: vids }))
|
||||
.sort((a, b) => a.name.localeCompare(b.name, 'de'));
|
||||
}
|
||||
|
||||
// Find video by short or long ID - moved to top level for export
|
||||
export async function findVideoByAnyId(id: string) {
|
||||
try {
|
||||
@@ -439,6 +463,95 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
res.send(html);
|
||||
});
|
||||
|
||||
// API endpoint for artists list
|
||||
app.get('/api/artists', async (req, res) => {
|
||||
try {
|
||||
const allVideos = await storage.getVideos(600, 0);
|
||||
const artists = extractArtists(allVideos);
|
||||
res.json({ artists: artists.map(a => ({ name: a.name, videoCount: a.videoCount })), total: artists.length });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Error fetching artists' });
|
||||
}
|
||||
});
|
||||
|
||||
// Server-side meta tags for /kuenstler page (crawlers + SEO)
|
||||
app.get('/kuenstler', async (req, res, next) => {
|
||||
const userAgent = req.headers['user-agent']?.toLowerCase() || '';
|
||||
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) return next();
|
||||
|
||||
const baseUrl = 'https://video.folx.tv';
|
||||
const pageUrl = `${baseUrl}/kuenstler`;
|
||||
const allVideos = await storage.getVideos(600, 0);
|
||||
const artists = extractArtists(allVideos);
|
||||
const topArtists = artists.slice(0, 30).map(a => a.name).join(', ');
|
||||
const title = `Alle Künstler & Interpreten (${artists.length}) | Folx TV - Video`;
|
||||
const description = `Entdecken Sie ${artists.length} Volksmusik- und Schlager-Künstler auf Folx TV: ${topArtists} und viele mehr. Nummer 1 in Europa für Volksmusik und Schlager.`;
|
||||
const keywords = artists.map(a => a.name).join(', ') + ', Volksmusik, Schlager, Folx TV';
|
||||
|
||||
const escapeHtml = (s: string) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "CollectionPage",
|
||||
"name": title,
|
||||
"description": description,
|
||||
"url": pageUrl,
|
||||
"numberOfItems": artists.length,
|
||||
"publisher": { "@type": "Organization", "name": "FOLX.TV", "url": "https://folx.tv" },
|
||||
"inLanguage": "de",
|
||||
"mainEntity": {
|
||||
"@type": "ItemList",
|
||||
"numberOfItems": artists.length,
|
||||
"itemListElement": artists.map((a, i) => ({
|
||||
"@type": "ListItem",
|
||||
"position": i + 1,
|
||||
"item": {
|
||||
"@type": "MusicGroup",
|
||||
"name": a.name,
|
||||
"url": `${pageUrl}#letter-${a.name.charAt(0).toUpperCase()}`
|
||||
}
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
const html = `<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>${escapeHtml(title)}</title>
|
||||
<meta name="description" content="${escapeHtml(description)}">
|
||||
<meta name="keywords" content="${escapeHtml(keywords)}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="${escapeHtml(title)}">
|
||||
<meta property="og:description" content="${escapeHtml(description)}">
|
||||
<meta property="og:url" content="${pageUrl}">
|
||||
<meta property="og:site_name" content="Folx TV - Video">
|
||||
<meta property="og:locale" content="de_DE">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="${escapeHtml(title)}">
|
||||
<meta name="twitter:description" content="${escapeHtml(description)}">
|
||||
<link rel="canonical" href="${pageUrl}">
|
||||
<script type="application/ld+json">${JSON.stringify(jsonLd)}</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Alle Künstler & Interpreten auf Folx TV</h1>
|
||||
<p>${escapeHtml(description)}</p>
|
||||
<p>Insgesamt ${artists.length} Künstler mit ${allVideos.length} Musikvideos.</p>
|
||||
<ul>${artists.map(a => `<li id="${encodeURIComponent(a.name)}"><strong>${escapeHtml(a.name)}</strong> (${a.videoCount} Video${a.videoCount > 1 ? 's' : ''})</li>`).join('')}</ul>
|
||||
</body>
|
||||
</html>`;
|
||||
res.set('Content-Type', 'text/html');
|
||||
res.send(html);
|
||||
});
|
||||
|
||||
// Server-side meta tags for /folx-stadl page (crawlers + SEO)
|
||||
app.get('/folx-stadl', async (req, res, next) => {
|
||||
const userAgent = req.headers['user-agent']?.toLowerCase() || '';
|
||||
@@ -520,7 +633,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
const pageUrl = `${baseUrl}/geschichte-lied`;
|
||||
const title = 'Die Geschichte des Liedes - Musikdokumentation | Folx TV - Video';
|
||||
const description = 'Die Geschichte des Liedes - Entdecken Sie die Entstehung und Hintergründe der beliebtesten Volksmusik- und Schlagerlieder. Eine einzigartige Musikdokumentation auf Folx TV.';
|
||||
const keywords = 'Geschichte des Liedes, Musikdokumentation, Volksmusik Geschichte, Schlager Historie, Liedgeschichte, Folx TV, Musiksendung';
|
||||
const keywords = 'Geschichte des Liedes, Musikdokumentation, Volksmusik Geschichte, Schlager Historie, Liedgeschichte, Folx TV, Musiksendung, Kastelruther Spatzen, Monika Martin, Oswald Sattler, Angela Wiedl, Die Grubertaler';
|
||||
|
||||
const allVideos = await storage.getVideos(600, 0);
|
||||
const geschichteVideos = allVideos.filter(v => v.title.includes('Geschichte des Liedes'));
|
||||
@@ -585,7 +698,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
const pageUrl = `${baseUrl}/gipfelstammtisch`;
|
||||
const title = 'Gipfelstammtisch - Volksmusik Talkshow | Folx TV - Video';
|
||||
const description = 'Gipfelstammtisch - Die gemütliche Volksmusik-Talkshow auf Folx TV. Gespräche mit Stars der Volksmusik- und Schlagerszene in einzigartiger Alpenatmosphäre. Jetzt alle Folgen ansehen!';
|
||||
const keywords = 'Gipfelstammtisch, Volksmusik Talkshow, Schlager Talk, Alpen, Volksmusik Stars, Folx TV, Musiksendung';
|
||||
const keywords = 'Gipfelstammtisch, Volksmusik Talkshow, Schlager Talk, Alpen, Volksmusik Stars, Folx TV, Musiksendung, Kastelruther Spatzen, Monika Martin, Oswald Sattler, Angela Wiedl, Michael Hirte';
|
||||
|
||||
const allVideos = await storage.getVideos(600, 0);
|
||||
const gipfelVideos = allVideos.filter(v => v.title.toLowerCase().includes('gipfelstammtisch'));
|
||||
@@ -674,8 +787,25 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>${baseUrl}/kuenstler</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
`;
|
||||
|
||||
// Add artist letter-index anchors to sitemap
|
||||
const artists = extractArtists(videos);
|
||||
const artistLetters = Array.from(new Set(artists.map(a => a.name.charAt(0).toUpperCase()))).sort();
|
||||
for (const letter of artistLetters) {
|
||||
xml += ` <url>
|
||||
<loc>${baseUrl}/kuenstler#letter-${escapeXml(letter)}</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
`;
|
||||
}
|
||||
|
||||
for (const video of videos) {
|
||||
const shortId = video.id.replace(/-/g, '').substring(0, 8);
|
||||
const lastmod = video.createdAt ? new Date(video.createdAt).toISOString().split('T')[0] : new Date().toISOString().split('T')[0];
|
||||
|
||||
Reference in New Issue
Block a user