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, "&").replace(/"/g, """).replace(//g, ">"); } 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(/]*>\s*/gi, ""); html = html.replace(/]*>\s*/gi, ""); html = html.replace(/]*>\s*/gi, ""); html = html.replace(/]*>\s*/gi, ""); html = html.replace(/]*>\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 = [ ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, `${escapeHtml(article.title)} - Volksmusik & Schlager | Folx Music Television`, ].join("\n "); template = template.replace(/[^<]*<\/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)}`, ].join("\n "); template = template.replace(/[^<]*<\/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)}`, ].join("\n "); template = template.replace(/[^<]*<\/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); } }); }