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:
+4
-1
@@ -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) =>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user