SEO: /category/<ime> dobi svoj naslov, opis, canonical, CollectionPage in seznam clankov v HTML (prej HTML naslovnice s canonical / — soft 404); clanki s targetUrl (dnevni horoskop, recept) ne gredo vec v sitemap, ker so samo preusmeritve

This commit is contained in:
folx bot
2026-08-24 22:18:01 +02:00
parent ed8872226b
commit cee85ef210
2 changed files with 81 additions and 1 deletions
+4 -1
View File
@@ -92,7 +92,10 @@ export async function registerRoutes(
// Rubrike z onemusic.news imajo isto besedilo kot tam, zato jih ne oddajamo // 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. // 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 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 SITE = "https://folx.tv";
const xmlEscape = (s: string) => const xmlEscape = (s: string) =>
+77
View File
@@ -204,6 +204,83 @@ export function serveStatic(app: Express) {
} }
} }
// ---- /category/<ime>: 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>[^<]*<\/title>/, [
'<meta name="robots" content="noindex, follow" />',
`<title>${escapeHtml(cat)} | Folx Music Television</title>`,
].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 = [
`<meta property="og:title" content="${escapeHtml(catTitle)}" />`,
`<meta property="og:description" content="${escapeHtml(catDesc)}" />`,
`<meta property="og:type" content="website" />`,
`<meta property="og:url" content="${escapeHtml(catUrl)}" />`,
`<meta property="og:image" content="${escapeHtml(firstImg)}" />`,
`<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(catTitle)}" />`,
`<meta name="twitter:image" content="${escapeHtml(firstImg)}" />`,
`<meta name="description" content="${escapeHtml(catDesc)}" />`,
`<link rel="canonical" href="${escapeHtml(catUrl)}" />`,
`<title>${escapeHtml(catTitle)}</title>`,
`<script type="application/ld+json">${JSON.stringify(itemLd).replace(/</g, "\\u003c")}</script>`,
].join("\n ");
template = template.replace(/<title>[^<]*<\/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\/([^?#]+)/); const articleMatch = url.match(/^\/article\/([^?#]+)/);
if (articleMatch) { if (articleMatch) {
try { try {