diff --git a/server/routes.ts b/server/routes.ts index 1fdd20a..7318d0a 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -92,7 +92,10 @@ export async function registerRoutes( // Rubrike z onemusic.news imajo isto besedilo kot tam, zato jih ne oddajamo // Googlu. V bazi in v /api/articles ostanejo — sidebar na TV jih potrebuje. const ONE_RUBRIKEN = new Set(["Politik", "Promis", "Sport", "Musik & Film", "Wetter", "Finanzen", "Aktuelles"]); - const isOwn = (a: any) => !ONE_RUBRIKEN.has(a?.category || ""); + // targetUrl = clanek je samo preusmeritev (dnevni horoskop, recept). Taki + // ne smejo v sitemap: Google jih oznaci kot "stran s preusmeritvijo" in + // porabi crawl budget za nic. + const isOwn = (a: any) => !ONE_RUBRIKEN.has(a?.category || "") && !a?.targetUrl; const SITE = "https://folx.tv"; const xmlEscape = (s: string) => diff --git a/server/static.ts b/server/static.ts index 3e1a92f..ab913b2 100644 --- a/server/static.ts +++ b/server/static.ts @@ -204,6 +204,83 @@ export function serveStatic(app: Express) { } } + // ---- /category/: prej je vrnil HTML naslovnice s canonical "/" in + // brez vsebine, kar je Google bral kot soft 404 / podvojeno naslovnico ---- + const catMatch = url.match(/^\/category\/([^?#\/]+)/); + if (catMatch) { + try { + const cat = decodeURIComponent(catMatch[1]); + const all = await storage.getArticles(); + const list = all + .filter((a: any) => (a.category || "").toLowerCase() === cat.toLowerCase() && !a.targetUrl) + .slice(0, 40); + let template = await fs.promises.readFile(indexPath, "utf-8"); + template = stripExistingMeta(template); + if (!list.length) { + template = template.replace(/[^<]*<\/title>/, [ + '<meta name="robots" content="noindex, follow" />', + `<title>${escapeHtml(cat)} | Folx Music Television`, + ].join("\n ")); + res.status(404).set({ "Content-Type": "text/html" }).end(template); + return; + } + const catUrl = `${canonicalBase}/category/${encodeURIComponent(cat)}`; + const catTitle = `${cat} – Volksmusik & Schlager | Folx Music Television`; + const catDesc = `Alle Beiträge aus der Rubrik ${cat} bei FOLX TV: aktuelle Meldungen, Interviews und Videos aus Volksmusik und Schlager.`; + const firstImg = ogImageUrl(list[0]?.coverImage || "", canonicalBase) || `${canonicalBase}/og-image.jpg`; + const itemLd = { + "@context": "https://schema.org", + "@type": "CollectionPage", + "name": catTitle, + "description": catDesc, + "url": catUrl, + "mainEntity": { + "@type": "ItemList", + "itemListElement": list.slice(0, 20).map((a: any, i: number) => ({ + "@type": "ListItem", + "position": i + 1, + "url": `${canonicalBase}/article/${a.slug}`, + "name": a.title, + })), + }, + }; + const tags = [ + ``, + ``, + ``, + ``, + ``, + ``, + ``, + ``, + ``, + ``, + ``, + ``, + `${escapeHtml(catTitle)}`, + ``, + ].join("\n "); + template = template.replace(/[^<]*<\/title>/, tags); + const ssr = [ + '<section class="ssr-content">', + `<h1>${escapeHtml(cat)}</h1>`, + "<ul>", + ...list.map((a: any) => { + const img = ogImageUrl(a.coverImage || "", canonicalBase); + return `<li><a href="/article/${escapeHtml(a.slug)}">` + + (img ? `<img src="${escapeHtml(img)}" alt="${escapeHtml(a.title)}" width="320" style="max-width:100%;height:auto;" /> ` : "") + + `<strong>${escapeHtml(a.title)}</strong></a><p>${escapeHtml(a.excerpt || "")}</p></li>`; + }), + "</ul>", + "</section>", + ].join("\n"); + template = template.replace('<div id="root"></div>', () => `<div id="root">${ssr}</div>`); + res.status(200).set({ "Content-Type": "text/html" }).end(template); + return; + } catch (e) { + } + } + const articleMatch = url.match(/^\/article\/([^?#]+)/); if (articleMatch) { try {