From a20c72e65e4c24b9ef149c9e3829fdae8d21480f Mon Sep 17 00:00:00 2001 From: Folx Ops Date: Sun, 14 Jun 2026 06:43:33 +0000 Subject: [PATCH] SEO: JSON-LD NewsArticle + sitemap.xml + news sitemap + robots.txt + RSS feed --- server/routes.ts | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ server/static.ts | 33 ++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/server/routes.ts b/server/routes.ts index 85c7f80..4fc56d3 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -71,6 +71,85 @@ export async function registerRoutes( ); }); + // ---- SEO: robots.txt, sitemap.xml, news sitemap, RSS ---- + const SITE = "https://folx.tv"; + const xmlEscape = (s: string) => + String(s || "") + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + + app.get("/robots.txt", (_req, res) => { + res.type("text/plain").send( + [ + "User-agent: *", + "Allow: /", + "", + `Sitemap: ${SITE}/sitemap.xml`, + `Sitemap: ${SITE}/news-sitemap.xml`, + "", + ].join("\n") + ); + }); + + app.get("/sitemap.xml", async (_req, res) => { + try { + const articles = await storage.getArticles(); + const staticPaths = ["/", "/news", "/video", "/galerie", "/horoskop", "/rezepte", "/kontakt", "/impressum", "/datenschutz"]; + const urls: string[] = []; + for (const p of staticPaths) { + urls.push(` \n ${SITE}${p}\n daily\n ${p === "/" ? "1.0" : "0.7"}\n `); + } + for (const a of articles) { + const lastmod = a.publishedAt ? new Date(a.publishedAt as any).toISOString() : new Date().toISOString(); + urls.push(` \n ${SITE}/article/${xmlEscape(a.slug)}\n ${lastmod}\n weekly\n 0.8\n `); + } + const xml = `\n\n${urls.join("\n")}\n`; + res.type("application/xml").send(xml); + } catch (e) { + res.status(500).type("application/xml").send(''); + } + }); + + app.get("/news-sitemap.xml", async (_req, res) => { + try { + const articles = await storage.getArticles(); + const now = Date.now(); + const twoDays = 48 * 60 * 60 * 1000; + const recent = articles.filter((a) => { + const t = a.publishedAt ? new Date(a.publishedAt as any).getTime() : now; + return now - t <= twoDays; + }); + const urls = recent.map((a) => { + const pub = a.publishedAt ? new Date(a.publishedAt as any).toISOString() : new Date().toISOString(); + return ` \n ${SITE}/article/${xmlEscape(a.slug)}\n \n \n Folx Music Television\n de\n \n ${pub}\n ${xmlEscape(a.title)}\n \n `; + }); + const xml = `\n\n${urls.join("\n")}\n`; + res.type("application/xml").send(xml); + } catch (e) { + res.status(500).type("application/xml").send(''); + } + }); + + app.get(["/rss.xml", "/feed", "/feed.xml"], async (_req, res) => { + try { + const articles = (await storage.getArticles()).slice(0, 30); + const items = articles.map((a) => { + const pub = a.publishedAt ? new Date(a.publishedAt as any).toUTCString() : new Date().toUTCString(); + const link = `${SITE}/article/${xmlEscape(a.slug)}`; + const img = a.coverImage ? (a.coverImage.startsWith("http") ? a.coverImage : SITE + a.coverImage) : ""; + const enclosure = img ? `\n ` : ""; + return ` \n ${xmlEscape(a.title)}\n ${link}\n ${link}\n ${pub}\n ${xmlEscape(a.category || "News")}\n ${xmlEscape(a.excerpt || "")}${enclosure}\n `; + }); + const xml = `\n\n \n Folx Music Television – News\n ${SITE}\n \n Aktuelle Nachrichten aus der Welt der Volksmusik und des Schlagers.\n de-DE\n ${new Date().toUTCString()}\n${items.join("\n")}\n \n`; + res.type("application/rss+xml").send(xml); + } catch (e) { + res.status(500).type("application/rss+xml").send(''); + } + }); + app.get("/api/focal-points", (_req, res) => { res.json(getCachedFocalPoints()); }); diff --git a/server/static.ts b/server/static.ts index 30a5e70..0f02bbd 100644 --- a/server/static.ts +++ b/server/static.ts @@ -63,6 +63,38 @@ export function serveStatic(app: Express) { let template = await fs.promises.readFile(indexPath, "utf-8"); template = stripExistingMeta(template); + const pubDate = article.publishedAt + ? new Date(article.publishedAt as any).toISOString() + : new Date().toISOString(); + const jsonLd = { + "@context": "https://schema.org", + "@type": "NewsArticle", + "headline": article.title, + "description": article.excerpt, + "image": [finalImage], + "datePublished": pubDate, + "dateModified": pubDate, + "articleSection": article.category || "News", + "author": { + "@type": "Organization", + "name": article.author || "Folx Music Television", + "url": canonicalBase, + }, + "publisher": { + "@type": "Organization", + "name": "Folx Music Television", + "logo": { + "@type": "ImageObject", + "url": `${canonicalBase}/og-image.jpg`, + }, + }, + "mainEntityOfPage": { + "@type": "WebPage", + "@id": articleUrl, + }, + }; + const jsonLdTag = ``; + const ogTags = [ ``, ``, @@ -84,6 +116,7 @@ export function serveStatic(app: Express) { ``, ``, `${escapeHtml(article.title)} - Volksmusik & Schlager | Folx Music Television`, + jsonLdTag, ].join("\n "); template = template.replace(/[^<]*<\/title>/, ogTags);