folx-tv/server/vite.ts
sebastjanartic 4ece9e4ccd Add exclusive photos from recordings to the gallery and improve site metadata
Integrate AdSense ads into the photo gallery, introduce SSR meta tags for the gallery page, and improve layout stability by reserving space for ads and dynamic content.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 81f3d8bc-2328-4374-a06e-57257dbb713b
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/OPD8Ro3
Replit-Helium-Checkpoint-Created: true
2026-03-08 07:33:00 +00:00

196 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { type Express } from "express";
import { createServer as createViteServer, createLogger } from "vite";
import { type Server } from "http";
import viteConfig from "../vite.config";
import fs from "fs";
import path from "path";
import { nanoid } from "nanoid";
import { storage } from "./storage";
const viteLogger = createLogger();
function escapeHtml(str: string): string {
return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function ogImageUrl(coverImage: string, baseUrl: string): string {
if (!coverImage) return "";
let imgPath = coverImage;
if (imgPath.endsWith(".webp")) {
imgPath = imgPath.replace(/\.webp$/, ".jpg");
}
return imgPath.startsWith("http") ? imgPath : `${baseUrl}${imgPath}`;
}
function stripExistingMeta(html: string): string {
html = html.replace(/<meta\s+property="og:[^"]*"[^>]*>\s*/gi, "");
html = html.replace(/<meta\s+name="twitter:[^"]*"[^>]*>\s*/gi, "");
html = html.replace(/<meta\s+name="description"[^>]*>\s*/gi, "");
html = html.replace(/<meta\s+name="keywords"[^>]*>\s*/gi, "");
html = html.replace(/<link\s+rel="canonical"[^>]*>\s*/gi, "");
return html;
}
export async function setupVite(server: Server, app: Express) {
const serverOptions = {
middlewareMode: true,
hmr: { server, path: "/vite-hmr" },
allowedHosts: true as const,
};
const vite = await createViteServer({
...viteConfig,
configFile: false,
customLogger: {
...viteLogger,
error: (msg, options) => {
viteLogger.error(msg, options);
process.exit(1);
},
},
server: serverOptions,
appType: "custom",
});
app.use(vite.middlewares);
app.use("/{*path}", async (req, res, next) => {
const url = req.originalUrl;
const canonicalBase = "https://folx.tv";
try {
const clientTemplate = path.resolve(
import.meta.dirname,
"..",
"client",
"index.html",
);
let template = await fs.promises.readFile(clientTemplate, "utf-8");
template = template.replace(
`src="/src/main.tsx"`,
`src="/src/main.tsx?v=${nanoid()}"`,
);
const articleMatch = url.match(/^\/article\/([^?#]+)/);
if (articleMatch) {
try {
const slug = decodeURIComponent(articleMatch[1]);
const article = await storage.getArticleBySlug(slug);
if (article) {
const articleUrl = `${canonicalBase}/article/${article.slug}`;
const imageUrl = ogImageUrl(article.coverImage || "", canonicalBase);
const finalImage = imageUrl || `${canonicalBase}/og-image.jpg`;
template = stripExistingMeta(template);
const ogTags = [
`<meta property="og:title" content="${escapeHtml(article.title)}" />`,
`<meta property="og:description" content="${escapeHtml(article.excerpt)}" />`,
`<meta property="og:type" content="article" />`,
`<meta property="og:url" content="${escapeHtml(articleUrl)}" />`,
`<meta property="og:image" content="${escapeHtml(finalImage)}" />`,
`<meta property="og:image:secure_url" content="${escapeHtml(finalImage)}" />`,
`<meta property="og:image:width" content="800" />`,
`<meta property="og:image:height" content="450" />`,
`<meta property="og:image:type" content="image/jpeg" />`,
`<meta property="og:site_name" content="Folx Music Television" />`,
`<meta property="og:locale" content="de_DE" />`,
`<meta property="fb:pages" content="1428520781492675" />`,
`<meta name="twitter:card" content="summary_large_image" />`,
`<meta name="twitter:title" content="${escapeHtml(article.title)}" />`,
`<meta name="twitter:description" content="${escapeHtml(article.excerpt)}" />`,
`<meta name="twitter:image" content="${escapeHtml(finalImage)}" />`,
`<meta name="description" content="${escapeHtml(article.excerpt)}" />`,
`<meta name="keywords" content="Volksmusik, Schlager, ${escapeHtml(article.title)}, ${escapeHtml(article.category || 'News')}, FOLX TV" />`,
`<link rel="canonical" href="${escapeHtml(articleUrl)}" />`,
`<title>${escapeHtml(article.title)} - Volksmusik & Schlager | Folx Music Television</title>`,
].join("\n ");
template = template.replace(/<title>[^<]*<\/title>/, ogTags);
}
} catch (e) {
}
}
if (url.match(/^\/gallery(\/|$|\?)/)) {
template = stripExistingMeta(template);
const galTitle = "Fotogalerie \u2013 Exklusive Backstage- & Konzertfotos | FOLX TV";
const galDesc = "Einzigartige Fotos direkt von unseren Aufzeichnungen und Sendungen: Backstage-Momente, Konzertbilder und exklusive Aufnahmen der Volksmusik- und Schlager-Stars bei FOLX TV.";
const galUrl = `${canonicalBase}/gallery`;
const galImage = `${canonicalBase}/images/og-image.jpg`;
const galTags = [
`<meta property="og:title" content="${escapeHtml(galTitle)}" />`,
`<meta property="og:description" content="${escapeHtml(galDesc)}" />`,
`<meta property="og:type" content="website" />`,
`<meta property="og:url" content="${escapeHtml(galUrl)}" />`,
`<meta property="og:image" content="${escapeHtml(galImage)}" />`,
`<meta property="og:image:secure_url" content="${escapeHtml(galImage)}" />`,
`<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:site_name" content="Folx Music Television" />`,
`<meta property="og:locale" content="de_DE" />`,
`<meta name="twitter:card" content="summary_large_image" />`,
`<meta name="twitter:title" content="${escapeHtml(galTitle)}" />`,
`<meta name="twitter:description" content="${escapeHtml(galDesc)}" />`,
`<meta name="twitter:image" content="${escapeHtml(galImage)}" />`,
`<meta name="description" content="${escapeHtml(galDesc)}" />`,
`<meta name="keywords" content="Fotogalerie, Volksmusik Fotos, Schlager Bilder, Konzertfotos, Backstage, FOLX TV, Volksmusik Stars" />`,
`<link rel="canonical" href="${escapeHtml(galUrl)}" />`,
`<title>${escapeHtml(galTitle)}</title>`,
].join("\n ");
template = template.replace(/<title>[^<]*<\/title>/, galTags);
}
if (url.match(/^\/horoskop(\/|$|\?)/)) {
template = stripExistingMeta(template);
const signMatch = url.match(/^\/horoskop\/([^?#]+)/);
const signName = signMatch ? decodeURIComponent(signMatch[1]) : null;
const capitalSign = signName ? signName.charAt(0).toUpperCase() + signName.slice(1) : null;
const title = capitalSign
? `${capitalSign} Horoskop Tages-, Wochen- & Monatshoroskop | Folx Music Television`
: "Horoskop Tages-, Wochen- & Monatshoroskop für alle Sternzeichen | Folx Music Television";
const desc = capitalSign
? `${capitalSign} Horoskop: Tägliches, wöchentliches und monatliches Horoskop. Liebe, Beruf & Gesundheit bei FOLX TV.`
: "Kostenloses Tageshoroskop, Wochenhoroskop & Monatshoroskop für alle 12 Sternzeichen. Liebe, Beruf, Gesundheit bei FOLX TV.";
const horoUrl = `${canonicalBase}${signName ? `/horoskop/${signName}` : "/horoskop"}`;
const horoImage = `${canonicalBase}/images/horoskop-og.jpg`;
const horoTags = [
`<meta property="og:title" content="${escapeHtml(title)}" />`,
`<meta property="og:description" content="${escapeHtml(desc)}" />`,
`<meta property="og:type" content="website" />`,
`<meta property="og:url" content="${escapeHtml(horoUrl)}" />`,
`<meta property="og:image" content="${escapeHtml(horoImage)}" />`,
`<meta property="og:image:secure_url" content="${escapeHtml(horoImage)}" />`,
`<meta property="og:image:width" content="1200" />`,
`<meta property="og:image:height" content="675" />`,
`<meta property="og:image:type" content="image/jpeg" />`,
`<meta property="og:site_name" content="Folx Music Television" />`,
`<meta property="og:locale" content="de_DE" />`,
`<meta name="twitter:card" content="summary_large_image" />`,
`<meta name="twitter:title" content="${escapeHtml(title)}" />`,
`<meta name="twitter:description" content="${escapeHtml(desc)}" />`,
`<meta name="twitter:image" content="${escapeHtml(horoImage)}" />`,
`<meta name="description" content="${escapeHtml(desc)}" />`,
`<meta name="keywords" content="Horoskop, Tageshoroskop, Wochenhoroskop, Monatshoroskop, Sternzeichen, Volksmusik, FOLX TV${capitalSign ? `, ${capitalSign}` : ""}" />`,
`<link rel="canonical" href="${escapeHtml(horoUrl)}" />`,
`<title>${escapeHtml(title)}</title>`,
].join("\n ");
template = template.replace(/<title>[^<]*<\/title>/, horoTags);
}
const page = await vite.transformIndexHtml(url, template);
res.status(200).set({ "Content-Type": "text/html" }).end(page);
} catch (e) {
vite.ssrFixStacktrace(e as Error);
next(e);
}
});
}