Present FOLX as a music news publisher and unify site SEO

This commit is contained in:
Codex
2026-10-07 13:42:48 +02:00
parent c5c3fe290e
commit cc306921d7
14 changed files with 173 additions and 115 deletions
+6 -6
View File
@@ -7,11 +7,11 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="google-site-verification" content="_rO4EdfjGp2lPdm3fpxkWv8k1id0wkthn9GxwsXLpeo" /> <meta name="google-site-verification" content="_rO4EdfjGp2lPdm3fpxkWv8k1id0wkthn9GxwsXLpeo" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>Volksmusik & Schlager TV | FOLX Music Television – Nachrichten, Videos & mehr</title> <title>Schlager &amp; Volksmusik News | FOLX TV</title>
<meta name="description" content="FOLX TV – Ihr Fernsehsender Nr. 1 für Volksmusik und Schlager. Aktuelle Nachrichten, Musikvideos, Künstlerporträts, Live-Shows und Interviews aus der Welt der volkstümlichen Musik. Jetzt einschalten!" /> <meta name="description" content="Aktuelle Schlager- und Volksmusik-News, Künstler-Interviews und Nachrichten aus der Musikszene. Jetzt Artikel und Videos auf FOLX TV entdecken." />
<meta name="keywords" content="Volksmusik, Schlager, Volksmusik TV, Schlager TV, Folx TV, FOLX Music Television, volkstümliche Musik, Musiksender, Alpenmusik, Volksmusik Nachrichten, Schlager News, Musikvideos, Live Shows, Volksmusik Künstler, Schlager Stars, Oberkrainer, Volksmusik Sender, Schlager Hits, volkstümliche Schlager" /> <meta name="keywords" content="Volksmusik, Schlager, Volksmusik TV, Schlager TV, Folx TV, FOLX Music Television, volkstümliche Musik, Musiksender, Alpenmusik, Volksmusik Nachrichten, Schlager News, Musikvideos, Live Shows, Volksmusik Künstler, Schlager Stars, Oberkrainer, Volksmusik Sender, Schlager Hits, volkstümliche Schlager" />
<meta property="og:title" content="Volksmusik & Schlager TV | FOLX Music Television" /> <meta property="og:title" content="Schlager &amp; Volksmusik News | FOLX TV" />
<meta property="og:description" content="FOLX TV – Ihr Fernsehsender Nr. 1 für Volksmusik und Schlager. Aktuelle Nachrichten, Musikvideos, Künstlerporträts und Live-Shows." /> <meta property="og:description" content="Aktuelle Schlager- und Volksmusik-News, Künstler-Interviews und Nachrichten aus der Musikszene. Jetzt Artikel und Videos auf FOLX TV entdecken." />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:site_name" content="Folx Music Television" /> <meta property="og:site_name" content="Folx Music Television" />
<meta property="og:url" content="https://folx.tv/" /> <meta property="og:url" content="https://folx.tv/" />
@@ -23,8 +23,8 @@
<meta property="og:image:alt" content="FOLX Network - Volksmusik & Schlager TV" /> <meta property="og:image:alt" content="FOLX Network - Volksmusik & Schlager TV" />
<meta property="og:locale" content="de_DE" /> <meta property="og:locale" content="de_DE" />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Volksmusik & Schlager TV | FOLX Music Television" /> <meta name="twitter:title" content="Schlager &amp; Volksmusik News | FOLX TV" />
<meta name="twitter:description" content="FOLX TV – Ihr Fernsehsender Nr. 1 für Volksmusik und Schlager. Aktuelle Nachrichten, Musikvideos und Live-Shows." /> <meta name="twitter:description" content="Aktuelle Schlager- und Volksmusik-News, Künstler-Interviews und Nachrichten aus der Musikszene. Jetzt Artikel und Videos auf FOLX TV entdecken." />
<meta name="twitter:image" content="https://folx.tv/og-image.jpg" /> <meta name="twitter:image" content="https://folx.tv/og-image.jpg" />
<meta property="fb:pages" content="281200958640258" /> <meta property="fb:pages" content="281200958640258" />
<meta name="facebook-domain-verification" content="b4y482htm54uybm563tfr1ucb1n46w" /> <meta name="facebook-domain-verification" content="b4y482htm54uybm563tfr1ucb1n46w" />
+2 -2
View File
@@ -23,8 +23,8 @@ export default function Footer({ narrow }: { narrow?: boolean }) {
<div> <div>
<h3 className="font-bold text-card-foreground text-lg mb-3">Folx Music Television</h3> <h3 className="font-bold text-card-foreground text-lg mb-3">Folx Music Television</h3>
<p className="text-muted-foreground text-sm leading-relaxed mb-4"> <p className="text-muted-foreground text-sm leading-relaxed mb-4">
FOLX TV – Ihr Fernsehsender Nr. 1 für Volksmusik und Schlager. Aktuelle Nachrichten, FOLX TV – Aktuelle Schlager- und Volksmusik-News, Künstlerporträts und Interviews.
Musikvideos, Künstlerporträts, Live-Shows und Interviews aus der Welt der volkstümlichen Musik. Entdecken Sie Nachrichten, Artikel und Videos aus der Musikszene sowie Informationen zu unserem TV-Angebot.
</p> </p>
<div className="flex gap-3"> <div className="flex gap-3">
{SOCIAL_LINKS.map((s) => ( {SOCIAL_LINKS.map((s) => (
+1 -1
View File
@@ -43,7 +43,7 @@ export default function Header() {
<div className="flex items-center justify-between gap-4 h-16"> <div className="flex items-center justify-between gap-4 h-16">
<Link href="/"> <Link href="/">
<div className="flex items-center gap-2 cursor-pointer" data-testid="link-logo"> <div className="flex items-center gap-2 cursor-pointer" data-testid="link-logo">
<img src={folxLogo} alt="FOLX TV – Volksmusik & Schlager Fernsehsender" className="h-32 w-auto object-contain py-1" /> <img src={folxLogo} alt="FOLX TV – Schlager & Volksmusik News" className="h-32 w-auto object-contain py-1" />
</div> </div>
</Link> </Link>
+25 -6
View File
@@ -1,3 +1,4 @@
import { HOME_TITLE, HOME_DESCRIPTION, INFO_SEO, publisherSchema } from "@shared/seo";
import { useEffect } from "react"; import { useEffect } from "react";
interface OgMeta { interface OgMeta {
@@ -8,6 +9,7 @@ interface OgMeta {
ogType?: string; ogType?: string;
ogUrl?: string; ogUrl?: string;
articlePublishedTime?: string; articlePublishedTime?: string;
articleModifiedTime?: string;
articleAuthor?: string; articleAuthor?: string;
articleSection?: string; articleSection?: string;
} }
@@ -32,10 +34,10 @@ function removeMetaTag(selector: string) {
if (el) el.remove(); if (el) el.remove();
} }
const DEFAULT_TITLE = "Volksmusik & Schlager TV | FOLX Music Television – Nachrichten, Videos & mehr"; const DEFAULT_TITLE = `${HOME_TITLE} | FOLX TV`;
const DEFAULT_DESC = "FOLX TV – Ihr Fernsehsender Nr. 1 für Volksmusik und Schlager. Aktuelle Nachrichten, Musikvideos, Künstlerporträts, Live-Shows und Interviews aus der Welt der volkstümlichen Musik. Jetzt einschalten!"; const DEFAULT_DESC = HOME_DESCRIPTION;
const DEFAULT_OG_TITLE = "Volksmusik & Schlager TV | FOLX Music Television"; const DEFAULT_OG_TITLE = `${HOME_TITLE} | FOLX TV`;
const DEFAULT_OG_DESC = "FOLX TV – Ihr Fernsehsender Nr. 1 für Volksmusik und Schlager. Aktuelle Nachrichten, Musikvideos, Künstlerporträts und Live-Shows."; const DEFAULT_OG_DESC = HOME_DESCRIPTION;
const DEFAULT_OG_IMAGE = "https://folx.tv/og-image.jpg"; const DEFAULT_OG_IMAGE = "https://folx.tv/og-image.jpg";
const CANONICAL_BASE = "https://folx.tv"; const CANONICAL_BASE = "https://folx.tv";
@@ -62,8 +64,24 @@ export function usePageMeta(title: string, description?: string, og?: OgMeta) {
const canonicalPath = og?.ogUrl || `${CANONICAL_BASE}${window.location.pathname}`; const canonicalPath = og?.ogUrl || `${CANONICAL_BASE}${window.location.pathname}`;
const canonicalUrl = canonicalPath.startsWith("http") ? canonicalPath : `${CANONICAL_BASE}${canonicalPath}`; const canonicalUrl = canonicalPath.startsWith("http") ? canonicalPath : `${CANONICAL_BASE}${canonicalPath}`;
setCanonical(canonicalUrl); setCanonical(canonicalUrl);
const info = INFO_SEO[window.location.pathname.replace(/\/+$/, "")];
document.getElementById("info-jsonld")?.remove();
if (info) {
const script = document.createElement("script");
script.id = "info-jsonld";
script.type = "application/ld+json";
script.textContent = JSON.stringify({ "@context": "https://schema.org", "@type": info.type,
"@id": `${canonicalUrl}#webpage`, url: canonicalUrl, name: title + suffix,
description: info.description, inLanguage: "de-DE", publisher: publisherSchema() });
document.head.appendChild(script);
}
setMetaTag('meta[property="og:url"]', canonicalUrl); setMetaTag('meta[property="og:url"]', canonicalUrl);
setMetaTag('meta[property="og:title"]', og?.ogTitle || title + suffix);
setMetaTag('meta[property="og:description"]', og?.ogDescription || description || DEFAULT_DESC);
setMetaTag('meta[property="og:type"]', og?.ogType || "website");
setMetaTag('meta[name="twitter:title"]', og?.ogTitle || title + suffix);
setMetaTag('meta[name="twitter:description"]', og?.ogDescription || description || DEFAULT_DESC);
if (og) { if (og) {
if (og.ogTitle) setMetaTag('meta[property="og:title"]', og.ogTitle); if (og.ogTitle) setMetaTag('meta[property="og:title"]', og.ogTitle);
if (og.ogDescription) setMetaTag('meta[property="og:description"]', og.ogDescription); if (og.ogDescription) setMetaTag('meta[property="og:description"]', og.ogDescription);
@@ -78,7 +96,7 @@ export function usePageMeta(title: string, description?: string, og?: OgMeta) {
if (og.articlePublishedTime) { if (og.articlePublishedTime) {
setMetaTag('meta[property="article:published_time"]', og.articlePublishedTime); setMetaTag('meta[property="article:published_time"]', og.articlePublishedTime);
setMetaTag('meta[property="article:modified_time"]', og.articlePublishedTime); setMetaTag('meta[property="article:modified_time"]', og.articleModifiedTime || og.articlePublishedTime);
} }
if (og.articleAuthor) { if (og.articleAuthor) {
setMetaTag('meta[property="article:author"]', og.articleAuthor); setMetaTag('meta[property="article:author"]', og.articleAuthor);
@@ -88,6 +106,7 @@ export function usePageMeta(title: string, description?: string, og?: OgMeta) {
} }
} }
return () => { return () => {
document.getElementById("info-jsonld")?.remove();
document.title = DEFAULT_TITLE; document.title = DEFAULT_TITLE;
setMetaTag('meta[name="description"]', DEFAULT_DESC); setMetaTag('meta[name="description"]', DEFAULT_DESC);
setMetaTag('meta[property="og:title"]', DEFAULT_OG_TITLE); setMetaTag('meta[property="og:title"]', DEFAULT_OG_TITLE);
@@ -105,5 +124,5 @@ export function usePageMeta(title: string, description?: string, og?: OgMeta) {
removeMetaTag('meta[property="article:author"]'); removeMetaTag('meta[property="article:author"]');
removeMetaTag('meta[property="article:section"]'); removeMetaTag('meta[property="article:section"]');
}; };
}, [title, description, og?.siteName, og?.ogTitle, og?.ogDescription, og?.ogImage, og?.ogType, og?.ogUrl, og?.articlePublishedTime, og?.articleAuthor, og?.articleSection]); }, [title, description, og?.siteName, og?.ogTitle, og?.ogDescription, og?.ogImage, og?.ogType, og?.ogUrl, og?.articlePublishedTime, og?.articleModifiedTime, og?.articleAuthor, og?.articleSection]);
} }
+2 -4
View File
@@ -1,3 +1,4 @@
import { INFO_SEO } from "@shared/seo";
import { Tv, Youtube, Facebook, Instagram, PlayCircle, Radio } from "lucide-react"; import { Tv, Youtube, Facebook, Instagram, PlayCircle, Radio } from "lucide-react";
import Header from "@/components/header"; import Header from "@/components/header";
import Footer from "@/components/footer"; import Footer from "@/components/footer";
@@ -43,10 +44,7 @@ const KANAELE = [
]; ];
export default function AboutPage() { export default function AboutPage() {
usePageMeta( usePageMeta(INFO_SEO["/ueber-uns"].title, INFO_SEO["/ueber-uns"].description, { siteName: "FOLX TV" });
"Über FOLX TV - Volksmusik & Schlager Fernsehsender",
"Alles über FOLX TV – Ihren Volksmusik & Schlager Fernsehsender seit 2013. Die Künstler, der FOLX Stadl und alle Kanäle: Livestream, FOLX Plus, YouTube, Facebook und Instagram.",
);
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<Header /> <Header />
+10 -14
View File
@@ -1,3 +1,4 @@
import { publisherSchema, articleModifiedTime, articleGenre } from "@shared/seo";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useParams, Link, useLocation } from "wouter"; import { useParams, Link, useLocation } from "wouter";
import { type Article } from "@shared/schema"; import { type Article } from "@shared/schema";
@@ -160,17 +161,19 @@ export default function ArticlePage() {
: ""; : "";
usePageMeta( usePageMeta(
article ? `${article.title} - Volksmusik & Schlager` : "Volksmusik & Schlager Artikel", article ? article.title : "Nachrichten",
article?.excerpt || "Aktuelle Nachrichten aus der Volksmusik- und Schlagerszene bei FOLX TV.", article?.excerpt || "Aktuelle Nachrichten aus der Volksmusik- und Schlagerszene bei FOLX TV.",
article ? { article ? {
siteName: "FOLX TV",
ogTitle: article.title, ogTitle: article.title,
ogDescription: article.excerpt, ogDescription: article.excerpt,
ogImage: articleImage || undefined, ogImage: articleImage || undefined,
ogType: "article", ogType: "article",
ogUrl: articleUrl, ogUrl: articleUrl,
articlePublishedTime: new Date(article.publishedAt).toISOString(), articlePublishedTime: new Date(article.publishedAt).toISOString(),
articleModifiedTime: articleModifiedTime(article.content || "", new Date(article.publishedAt).toISOString()),
articleAuthor: article.author || "Folx Music Television", articleAuthor: article.author || "Folx Music Television",
articleSection: article.category || "News", articleSection: articleGenre(article.content || "").section,
} : undefined } : undefined
); );
@@ -179,32 +182,25 @@ export default function ArticlePage() {
const jsonLd = { const jsonLd = {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "NewsArticle", "@type": "NewsArticle",
"@id": `${articleUrl}#article`,
"headline": article.title, "headline": article.title,
"description": article.excerpt, "description": article.excerpt,
"image": articleImage ? [articleImage] : [], "image": articleImage ? [articleImage] : [],
"datePublished": new Date(article.publishedAt).toISOString(), "datePublished": new Date(article.publishedAt).toISOString(),
"dateModified": new Date(article.publishedAt).toISOString(), "dateModified": articleModifiedTime(article.content || "", new Date(article.publishedAt).toISOString()),
"author": { "author": {
"@type": "Organization", "@type": "Organization",
"name": article.author || "Folx Music Television", "name": article.author || "Folx Music Television",
"url": "https://folx.tv" "url": "https://folx.tv"
}, },
"publisher": { "publisher": publisherSchema(),
"@type": "Organization",
"name": "Folx Music Television",
"url": "https://folx.tv",
"logo": {
"@type": "ImageObject",
"url": `${window.location.origin}/favicon.png`
}
},
"mainEntityOfPage": { "mainEntityOfPage": {
"@type": "WebPage", "@type": "WebPage",
"@id": articleUrl "@id": articleUrl
}, },
"articleSection": article.category || "News", "articleSection": articleGenre(article.content || "").section,
"inLanguage": "de", "inLanguage": "de",
"keywords": `Volksmusik, Schlager, ${article.category || "News"}, ${article.title}` "keywords": articleGenre(article.content || "").words.join(", ")
}; };
const breadcrumbLd = { const breadcrumbLd = {
"@context": "https://schema.org", "@context": "https://schema.org",
+2 -1
View File
@@ -1,3 +1,4 @@
import { INFO_SEO } from "@shared/seo";
import { Shield } from "lucide-react"; import { Shield } from "lucide-react";
import Header from "@/components/header"; import Header from "@/components/header";
import Footer from "@/components/footer"; import Footer from "@/components/footer";
@@ -5,7 +6,7 @@ import { usePageMeta } from "@/hooks/use-page-meta";
import { PageSideAds } from "@/components/adsense"; import { PageSideAds } from "@/components/adsense";
export default function DatenschutzPage() { export default function DatenschutzPage() {
usePageMeta("Datenschutz - FOLX TV Volksmusik Sender", "Datenschutzerklärung von FOLX TV – Ihrem Volksmusik & Schlager Fernsehsender."); usePageMeta(INFO_SEO["/datenschutz"].title, INFO_SEO["/datenschutz"].description, { siteName: "FOLX TV" });
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<Header /> <Header />
+2 -4
View File
@@ -1,3 +1,4 @@
import { INFO_SEO } from "@shared/seo";
import { Tv, MapPin, PlayCircle, Youtube, Smartphone, Radio, ExternalLink } from "lucide-react"; import { Tv, MapPin, PlayCircle, Youtube, Smartphone, Radio, ExternalLink } from "lucide-react";
import Header from "@/components/header"; import Header from "@/components/header";
import Footer from "@/components/footer"; import Footer from "@/components/footer";
@@ -107,10 +108,7 @@ const APPS = [
]; ];
export default function EmpfangPage() { export default function EmpfangPage() {
usePageMeta( usePageMeta(INFO_SEO["/empfang-folx-tv"].title, INFO_SEO["/empfang-folx-tv"].description, { siteName: "FOLX TV" });
"Empfang FOLX TV - Volksmusik & Schlager Sender empfangen",
"So empfangen Sie FOLX TV – den Volksmusik & Schlager Sender: kostenloser Livestream auf folx.live, die FOLX TV App für Apple, Android, Samsung und Fire TV sowie Kabel und IPTV in Deutschland, Österreich und der Schweiz.",
);
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<Header /> <Header />
+4 -31
View File
@@ -1,3 +1,4 @@
import { HOME_TITLE, HOME_DESCRIPTION, homeSchema } from "@shared/seo";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { artistGroup } from "@/lib/artists"; import { artistGroup } from "@/lib/artists";
import { objectPositionFor } from "@/lib/focal"; import { objectPositionFor } from "@/lib/focal";
@@ -756,38 +757,10 @@ function BentoSkeleton() {
} }
export default function Home() { export default function Home() {
usePageMeta("Volksmusik & Schlager TV", "FOLX TV – Ihr Fernsehsender Nr. 1 für Volksmusik und Schlager. Aktuelle Nachrichten, Musikvideos, Künstlerporträts, Live-Shows und Interviews aus der Welt der volkstümlichen Musik."); usePageMeta(HOME_TITLE, HOME_DESCRIPTION, { siteName: "FOLX TV" });
useEffect(() => { useEffect(() => {
const jsonLd = { const jsonLd = homeSchema();
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Folx Music Television",
"alternateName": ["FOLX TV", "Volksmusik TV"],
"url": "https://folx.tv",
"description": "FOLX TV – Ihr Fernsehsender Nr. 1 für Volksmusik und Schlager. Aktuelle Nachrichten, Musikvideos, Künstlerporträts, Live-Shows und Interviews aus der Welt der volkstümlichen Musik.",
"publisher": {
"@type": "Organization",
"name": "Folx Music Television",
"url": "https://folx.tv",
"logo": {
"@type": "ImageObject",
"url": `${window.location.origin}/favicon.png`
},
"sameAs": [
"https://www.facebook.com/folxtvmusic",
"https://www.instagram.com/folxtv",
"https://www.youtube.com/@FolxTV",
"https://www.tiktok.com/@folx.tv"
]
},
"potentialAction": {
"@type": "SearchAction",
"target": "https://folx.tv/search?q={search_term_string}",
"query-input": "required name=search_term_string"
},
"inLanguage": "de"
};
const script = document.createElement("script"); const script = document.createElement("script");
script.type = "application/ld+json"; script.type = "application/ld+json";
script.id = "home-jsonld"; script.id = "home-jsonld";
@@ -1033,7 +1006,7 @@ export default function Home() {
<Header /> <Header />
<PageSideAds /> <PageSideAds />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 space-y-4"> <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 space-y-4">
<h1 className="sr-only">FOLX TV – Volksmusik & Schlager Fernsehsender: Nachrichten, Videos und Live-Shows</h1> <h1 className="sr-only">FOLX TV – Schlager & Volksmusik News</h1>
{/* Prej lastna kopija pasu s trdo visino 90 px in responsive=false: {/* Prej lastna kopija pasu s trdo visino 90 px in responsive=false:
Google je vanjo postavil 320x50 in okrog je ostal siv rob. Google je vanjo postavil 320x50 in okrog je ostal siv rob.
+2 -1
View File
@@ -1,3 +1,4 @@
import { INFO_SEO } from "@shared/seo";
import { Scale } from "lucide-react"; import { Scale } from "lucide-react";
import Header from "@/components/header"; import Header from "@/components/header";
import Footer from "@/components/footer"; import Footer from "@/components/footer";
@@ -5,7 +6,7 @@ import { usePageMeta } from "@/hooks/use-page-meta";
import { PageSideAds } from "@/components/adsense"; import { PageSideAds } from "@/components/adsense";
export default function ImpressumPage() { export default function ImpressumPage() {
usePageMeta("Impressum - FOLX TV Volksmusik Sender", "Impressum und rechtliche Informationen zu FOLX TV – Ihrem Volksmusik & Schlager Fernsehsender."); usePageMeta(INFO_SEO["/impressum"].title, INFO_SEO["/impressum"].description, { siteName: "FOLX TV" });
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
<Header /> <Header />
+2 -1
View File
@@ -1,3 +1,4 @@
import { INFO_SEO } from "@shared/seo";
import { useState } from "react"; import { useState } from "react";
import { Mail, Phone, Send, CheckCircle, AlertCircle, Smartphone, Printer } from "lucide-react"; import { Mail, Phone, Send, CheckCircle, AlertCircle, Smartphone, Printer } from "lucide-react";
import { SiFacebook, SiInstagram, SiYoutube, SiTiktok } from "react-icons/si"; import { SiFacebook, SiInstagram, SiYoutube, SiTiktok } from "react-icons/si";
@@ -8,7 +9,7 @@ import { PageSideAds, TopBannerAd } from "@/components/adsense";
import { apiRequest } from "@/lib/queryClient"; import { apiRequest } from "@/lib/queryClient";
export default function KontaktPage() { export default function KontaktPage() {
usePageMeta("Kontakt - FOLX TV Volksmusik Sender", "Kontaktieren Sie FOLX TV – Ihren Fernsehsender Nr. 1 für Volksmusik und Schlager. Wir freuen uns auf Ihre Nachricht."); usePageMeta(INFO_SEO["/kontakt"].title, INFO_SEO["/kontakt"].description, { siteName: "FOLX TV" });
const [formData, setFormData] = useState({ name: "", email: "", subject: "", message: "" }); const [formData, setFormData] = useState({ name: "", email: "", subject: "", message: "" });
const [status, setStatus] = useState<"idle" | "sending" | "success" | "error">("idle"); const [status, setStatus] = useState<"idle" | "sending" | "success" | "error">("idle");
+20
View File
@@ -0,0 +1,20 @@
import assert from "node:assert/strict";
import { homeSchema, publisherSchema, articleModifiedTime, articleGenre, INFO_SEO } from "../shared/seo";
const home = homeSchema();
assert.equal(home["@graph"].filter(x => x["@type"] === "WebSite").length, 1);
assert.equal(publisherSchema()["@type"], "NewsMediaOrganization");
assert.ok(JSON.stringify(home).includes("/search?q="));
assert.ok(!JSON.stringify(home).includes("/suche"));
assert.ok(publisherSchema().sameAs.includes("https://www.youtube.com/@folxmtv"));
const pub = "2026-01-01T12:00:00.000Z";
assert.equal(articleModifiedTime("", pub), pub);
assert.equal(articleModifiedTime("<!--folx-updated:invalid-->", pub), pub);
assert.equal(articleModifiedTime("<!--folx-updated:2025-01-01-->", pub), pub);
assert.equal(articleModifiedTime("<!--folx-updated:2099-01-01-->", pub), pub);
assert.equal(articleModifiedTime("<!--folx-updated:2026-02-01-->", pub), "2026-02-01T00:00:00.000Z");
assert.equal(articleGenre("<!--folx-badge:STAR NEWS-->").section, "Star News");
assert.ok(!articleGenre("<!--folx-badge:STAR NEWS-->").words.includes("Schlager"));
assert.equal(articleGenre("").section, "Musik News");
assert.equal(INFO_SEO["/ueber-uns"].type, "AboutPage");
assert.equal(INFO_SEO["/kontakt"].type, "ContactPage");
console.log("SEO metadata checks passed");
+42 -44
View File
@@ -1,3 +1,4 @@
import { publisherSchema, homeSchema, INFO_SEO, articleModifiedTime, articleGenre } from "../shared/seo";
import express, { type Express } from "express"; import express, { type Express } from "express";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
@@ -11,13 +12,7 @@ import sharp from "sharp";
// Brez markerja ostane vse kot prej (glasbeno), da starejsi clanki ne nazadujejo. // Brez markerja ostane vse kot prej (glasbeno), da starejsi clanki ne nazadujejo.
// Besedilne zaznave namenoma NI: preizkus na 30 clankih jo je zgresil pri // Besedilne zaznave namenoma NI: preizkus na 30 clankih jo je zgresil pri
// Fernsehgartnu in Carpendaleju. // Fernsehgartnu in Carpendaleju.
function folxGenre(article: any): { words: string[]; section: string } { const folxGenre = (article: any) => articleGenre(String(article?.content || ""));
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" };
}
function escapeHtml(str: string): string { function escapeHtml(str: string): string {
@@ -88,15 +83,7 @@ const ARTICLE_SLUG_REDIRECTS: Record<string, string> = {
// v bazi in v /api/articles ostanejo, ker jih bere sidebar na TV kanalih. // v bazi in v /api/articles ostanejo, ker jih bere sidebar na TV kanalih.
// Izdajatelj: doslej samo ime in logo. Brez registrirane druzbe, naslova in // Izdajatelj: doslej samo ime in logo. Brez registrirane druzbe, naslova in
// profilov Google znamke nima s cim povezati. // profilov Google znamke nima s cim povezati.
const PUBLISHER = (base: string) => ({ const PUBLISHER = publisherSchema;
// 4. 9. 2026: izdajatelj spet samo "Folx Music Television" z logom, kot do
// 26. 8. - brez registrirane druzbe, naslova in kontaktov.
"@type": "Organization",
"@id": `${base}/#organization`,
"name": "Folx Music Television",
"url": base,
"logo": { "@type": "ImageObject", "url": `${base}/og-image.jpg` },
});
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"]);
@@ -294,28 +281,10 @@ export function serveStatic(app: Express) {
if (url === "/" || url.startsWith("/?")) { if (url === "/" || url.startsWith("/?")) {
try { try {
let template = await fs.promises.readFile(indexPath, "utf-8"); let template = await fs.promises.readFile(indexPath, "utf-8");
const homeLd = { const homeLd = homeSchema(canonicalBase);
"@context": "https://schema.org",
"@graph": [
PUBLISHER(canonicalBase),
{
"@type": "WebSite",
"@id": `${canonicalBase}/#website`,
"url": `${canonicalBase}/`,
"name": "FOLX Music Television",
"inLanguage": "de-DE",
"publisher": { "@id": `${canonicalBase}/#organization` },
"potentialAction": {
"@type": "SearchAction",
"target": { "@type": "EntryPoint", "urlTemplate": `${canonicalBase}/suche?q={search_term_string}` },
"query-input": "required name=search_term_string",
},
},
],
};
template = template.replace( template = template.replace(
"</head>", "</head>",
`<script type="application/ld+json">${JSON.stringify(homeLd).replace(/</g, "\\u003c")}</script>\n</head>`); `<script id="home-jsonld" type="application/ld+json">${JSON.stringify(homeLd).replace(/</g, "\\u003c")}</script>\n</head>`);
// 31. 8. 2026: naslovnica je React aplikacija, zato je robot dobil samo // 31. 8. 2026: naslovnica je React aplikacija, zato je robot dobil samo
// lupino brez ene same povezave na clanek. Clanki se do Googla pridejo // lupino brez ene same povezave na clanek. Clanki se do Googla pridejo
@@ -325,7 +294,8 @@ export function serveStatic(app: Express) {
try { try {
const vsi = await storage.getArticles(); const vsi = await storage.getArticles();
const zadnji = [...vsi] const zadnji = [...vsi]
.sort((a: any, b: any) => String(b.publishedAt).localeCompare(String(a.publishedAt))) .filter((a: any) => !a.targetUrl && !ONE_RUBRIKEN.has(a.category || ""))
.sort((a: any, b: any) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime())
.slice(0, 40); .slice(0, 40);
if (zadnji.length) { if (zadnji.length) {
const seznam = zadnji const seznam = zadnji
@@ -503,11 +473,7 @@ export function serveStatic(app: Express) {
: new Date().toISOString(); : new Date().toISOString();
// dateModified: ce je clanek dopolnjen (enrich), preberi marker <!--folx-updated:ISO--> // dateModified: ce je clanek dopolnjen (enrich), preberi marker <!--folx-updated:ISO-->
const modDate = (() => { const modDate = articleModifiedTime(article.content || "", pubDate);
const mm = /<!--folx-updated:([^>]+?)-->/.exec(article.content || "");
if (mm) { try { return new Date(mm[1]).toISOString(); } catch (e) { /* ignore */ } }
return pubDate;
})();
// VideoObject: ce je v clanku vgrajen YouTube video // VideoObject: ce je v clanku vgrajen YouTube video
const videoId = (() => { const videoId = (() => {
@@ -518,6 +484,8 @@ export function serveStatic(app: Express) {
const jsonLd = { const jsonLd = {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "NewsArticle", "@type": "NewsArticle",
"@id": `${articleUrl}#article`,
"inLanguage": "de-DE",
"headline": article.title, "headline": article.title,
"description": article.excerpt, "description": article.excerpt,
"image": [finalImage], "image": [finalImage],
@@ -536,7 +504,7 @@ export function serveStatic(app: Express) {
"@id": articleUrl, "@id": articleUrl,
}, },
}; };
const jsonLdTag = `<script type="application/ld+json">${JSON.stringify(jsonLd).replace(/</g, "\\u003c")}</script>`; const jsonLdTag = `<script id="article-jsonld" type="application/ld+json">${JSON.stringify(jsonLd).replace(/</g, "\\u003c")}</script>`;
const videoLd = videoId ? { const videoLd = videoId ? {
"@context": "https://schema.org", "@context": "https://schema.org",
@@ -557,6 +525,8 @@ export function serveStatic(app: Express) {
`<meta property="og:title" content="${escapeHtml(article.title)}" />`, `<meta property="og:title" content="${escapeHtml(article.title)}" />`,
`<meta property="og:description" content="${escapeHtml(article.excerpt)}" />`, `<meta property="og:description" content="${escapeHtml(article.excerpt)}" />`,
`<meta property="og:type" content="article" />`, `<meta property="og:type" content="article" />`,
`<meta property="article:published_time" content="${pubDate}" />`,
`<meta property="article:modified_time" content="${modDate}" />`,
`<meta property="og:url" content="${escapeHtml(articleUrl)}" />`, `<meta property="og:url" content="${escapeHtml(articleUrl)}" />`,
`<meta property="og:image" content="${escapeHtml(finalImage)}" />`, `<meta property="og:image" content="${escapeHtml(finalImage)}" />`,
`<meta property="og:image:secure_url" content="${escapeHtml(finalImage)}" />`, `<meta property="og:image:secure_url" content="${escapeHtml(finalImage)}" />`,
@@ -575,7 +545,7 @@ export function serveStatic(app: Express) {
`<meta name="description" content="${escapeHtml(article.excerpt)}" />`, `<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" />`, `<meta name="keywords" content="${escapeHtml(folxGenre(article).words.join(", "))}, ${escapeHtml(article.title)}, ${escapeHtml(article.category || 'News')}, FOLX TV" />`,
`<link rel="canonical" href="${escapeHtml(articleUrl)}" />`, `<link rel="canonical" href="${escapeHtml(articleUrl)}" />`,
`<title>${escapeHtml(article.title)} - Volksmusik & Schlager | Folx Music Television</title>`, `<title>${escapeHtml(article.title)} | FOLX TV</title>`,
jsonLdTag, jsonLdTag,
videoLdTag, videoLdTag,
].join("\n "); ].join("\n ");
@@ -765,6 +735,34 @@ export function serveStatic(app: Express) {
return; return;
} }
const infoPath = pathOnly.replace(/\/+$/, "");
const info = INFO_SEO[infoPath];
if (info) {
let template = stripExistingMeta(await fs.promises.readFile(indexPath, "utf-8"));
const pageUrl = `${canonicalBase}${infoPath}`;
const title = `${info.title} | FOLX TV`;
const ld = { "@context": "https://schema.org", "@type": info.type,
"@id": `${pageUrl}#webpage`, url: pageUrl, name: title, description: info.description,
inLanguage: "de-DE", isPartOf: { "@id": `${canonicalBase}/#website` }, publisher: PUBLISHER(canonicalBase) };
template = template.replace(/<title>[^<]*<\/title>/, [
`<title>${escapeHtml(title)}</title>`,
`<meta name="description" content="${escapeHtml(info.description)}" />`,
`<link rel="canonical" href="${pageUrl}" />`,
`<meta property="og:title" content="${escapeHtml(title)}" />`,
`<meta property="og:description" content="${escapeHtml(info.description)}" />`,
`<meta property="og:type" content="website" />`,
`<meta property="og:url" content="${pageUrl}" />`,
`<meta property="og:image" content="${canonicalBase}/og-image.jpg" />`,
`<meta name="twitter:card" content="summary_large_image" />`,
`<meta name="twitter:title" content="${escapeHtml(title)}" />`,
`<meta name="twitter:description" content="${escapeHtml(info.description)}" />`,
`<meta name="twitter:image" content="${canonicalBase}/og-image.jpg" />`,
`<script id="info-jsonld" type="application/ld+json">${JSON.stringify(ld).replace(/</g, "\\u003c")}</script>`,
].join("\n"));
res.status(200).type("html").send(template);
return;
}
// Neznana pot mora vrniti PRAVI 404, sicer Google dobi soft 404 (HTTP 200 // Neznana pot mora vrniti PRAVI 404, sicer Google dobi soft 404 (HTTP 200
// z naslovnico). Seznam mora ustrezati potem v client/src/App.tsx. // z naslovnico). Seznam mora ustrezati potem v client/src/App.tsx.
const KNOWN = [ const KNOWN = [
+53
View File
@@ -0,0 +1,53 @@
export const SITE_URL = "https://folx.tv";
export const HOME_TITLE = "Schlager & Volksmusik News";
export const HOME_DESCRIPTION = "Aktuelle Schlager- und Volksmusik-News, Künstler-Interviews und Nachrichten aus der Musikszene. Jetzt Artikel und Videos auf FOLX TV entdecken.";
export function publisherSchema(base = SITE_URL) {
return {
"@type": "NewsMediaOrganization",
"@id": `${base}/#organization`,
name: "Folx Music Television",
alternateName: "FOLX TV",
url: base,
description: HOME_DESCRIPTION,
logo: { "@type": "ImageObject", url: `${base}/og-image.jpg` },
sameAs: ["https://www.facebook.com/folxtv", "https://www.instagram.com/folxtv", "https://www.youtube.com/@folxmtv"],
};
}
export function homeSchema(base = SITE_URL) {
return { "@context": "https://schema.org", "@graph": [
publisherSchema(base),
{ "@type": "WebSite", "@id": `${base}/#website`, url: `${base}/`,
name: "FOLX TV", alternateName: "Folx Music Television", description: HOME_DESCRIPTION,
inLanguage: "de-DE", publisher: { "@id": `${base}/#organization` },
potentialAction: { "@type": "SearchAction", target: { "@type": "EntryPoint", urlTemplate: `${base}/search?q={search_term_string}` }, "query-input": "required name=search_term_string" } },
{ "@type": "CollectionPage", "@id": `${base}/#webpage`, url: `${base}/`,
name: `${HOME_TITLE} | FOLX TV`, description: HOME_DESCRIPTION, inLanguage: "de-DE",
isPartOf: { "@id": `${base}/#website` }, publisher: { "@id": `${base}/#organization` },
hasPart: { "@type": "CollectionPage", "@id": `${base}/category/News#webpage`, url: `${base}/category/News`, name: "News" } }
] };
}
export function articleModifiedTime(content: string, published: string): string {
const marker = /<!--folx-updated:([^>]+?)-->/.exec(content || "");
const time = marker ? new Date(marker[1]).getTime() : NaN;
return Number.isFinite(time) && time >= new Date(published).getTime() && time <= Date.now()
? new Date(time).toISOString() : published;
}
export function articleGenre(content: string) {
const marker = /<!--\s*folx-badge:\s*([^>]*?)\s*-->/i.exec(content || "");
const badge = (marker ? marker[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" };
}
export const INFO_SEO: Record<string, { title: string; description: string; type: string }> = {
"/ueber-uns": { title: "Über FOLX TV – Musik-News, Interviews und Fernsehen", description: "FOLX TV verbindet aktuelle Schlager- und Volksmusik-News mit Künstler-Interviews, Videos und eigenen TV-Produktionen. Mehr über unser Angebot.", type: "AboutPage" },
"/kontakt": { title: "Kontakt", description: "Kontakt zu FOLX TV: Fragen zu Artikeln, Musik-News, Interviews und unserem Programm. Schreiben Sie uns über das Kontaktformular.", type: "ContactPage" },
"/impressum": { title: "Impressum", description: "Anbieterangaben und rechtliche Informationen zu FOLX TV und dem Nachrichtenangebot auf folx.tv.", type: "WebPage" },
"/datenschutz": { title: "Datenschutz", description: "Informationen zum Datenschutz und zur Verarbeitung personenbezogener Daten auf folx.tv.", type: "WebPage" },
"/empfang-folx-tv": { title: "FOLX TV empfangen – Livestream, App, Kabel und IPTV", description: "Informationen zum Empfang des FOLX TV Fernsehprogramms: Livestream, Apps sowie Kabel und IPTV in Deutschland, Österreich und der Schweiz.", type: "WebPage" }
};