Files
folx-tv/server/vite.ts
T
Claude Opus 5andClaude Opus 5 48f8e16cda SEO: keywords in articleSection sledita znacki kosa (folx-badge marker)
Stran je vsakemu clanku v <meta name=keywords> pisala Volksmusik, Schlager --
tudi neglasbenim (Becker, Geissens, Ana Ivanovic) -- in v JSON-LD dajala
articleSection iz kategorije, ki je pri vseh nasih clankih News.

Zdaj SSR prebere marker <!--folx-badge:STAR NEWS--> iz vsebine clanka (isti
vzorec kot <!--folx-updated:...-->, ker tabela articles nima stolpca za to):
  MUSIC NEWS (ali brez markerja) -> Volksmusik, Schlager, Musik News / Musik News
  karkoli drugega               -> Star News, Promi News, Promi / Star News

Brez markerja ostane vedenje nespremenjeno, da starejsi clanki ne nazadujejo.
Besedilne zaznave namenoma ni: preizkus na 30 clankih jo je zgresil pri
ZDF-Fernsehgartnu in pri Wayne Carpendaleju.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HcwnGD7Je9Z4sKiHDJhEwP
2026-09-15 12:22:24 +02:00

212 lines
10 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";
// 15. 9. 2026 (Sebastjan): stran je v keywords vsakemu clanku pisala
// "Volksmusik, Schlager" — tudi Beckerju, Geissensom in Ivanovic. Cevovod ze ve,
// kaj kos je (znacka MUSIC NEWS / STAR NEWS), zato jo zapise v vsebino kot
// marker <!--folx-badge:STAR NEWS-->, enako kot <!--folx-updated:...-->.
// Brez markerja ostane vse kot prej (glasbeno), da starejsi clanki ne nazadujejo.
// Besedilne zaznave namenoma NI: preizkus na 30 clankih jo je zgresil pri
// Fernsehgartnu in Carpendaleju.
function folxGenre(article: any): { words: string[]; section: string } {
const m = /<!--\s*folx-badge:\s*([^>]*?)\s*-->/i.exec(String(article?.content || ""));
const badge = (m ? m[1] : "MUSIC NEWS").toUpperCase();
return badge === "MUSIC NEWS"
? { words: ["Volksmusik", "Schlager", "Musik News"], section: "Musik News" }
: { words: ["Star News", "Promi News", "Promi"], section: "Star News" };
}
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="281200958640258" />`,
`<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="${escapeHtml(folxGenre(article).words.join(", "))}, ${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 Einzigartige Momente von unseren Aufzeichnungen | FOLX TV";
const galDesc = "Spannende Schnappsch\u00fcsse und besondere Augenblicke, eingefangen bei unseren Aufzeichnungen. Authentische Bilder der Volksmusik- und Schlager-Stars in einzigartigen Momenten 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, Aufzeichnungen, Schnappschüsse, 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);
}
});
}