From bb5617b3949ec749b7b51f1aa5c02a75715b8d09 Mon Sep 17 00:00:00 2001 From: sebastjanartic <45803536-sebastjanartic@users.noreply.replit.com> Date: Fri, 6 Mar 2026 14:17:59 +0000 Subject: [PATCH] Add structured data for news and improved social sharing Implement NewsArticle schema and Open Graph tags on article pages for better search engine visibility and social media previews, and add WebSite schema to the homepage. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: b02ed261-705f-4f15-a1ae-b9b80b538081 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/koutl3W Replit-Helium-Checkpoint-Created: true --- client/src/hooks/use-page-meta.ts | 66 +++++++++++++++++++++++++++---- client/src/pages/article.tsx | 58 ++++++++++++++++++++++++++- client/src/pages/home.tsx | 33 ++++++++++++++++ 3 files changed, 149 insertions(+), 8 deletions(-) diff --git a/client/src/hooks/use-page-meta.ts b/client/src/hooks/use-page-meta.ts index 1c85749..8e20d6f 100644 --- a/client/src/hooks/use-page-meta.ts +++ b/client/src/hooks/use-page-meta.ts @@ -1,17 +1,69 @@ import { useEffect } from "react"; -export function usePageMeta(title: string, description?: string) { +interface OgMeta { + ogTitle?: string; + ogDescription?: string; + ogImage?: string; + ogType?: string; + ogUrl?: string; +} + +function setMetaTag(selector: string, content: string) { + let el = document.querySelector(selector) as HTMLMetaElement; + if (el) { + el.content = content; + } else { + el = document.createElement("meta"); + const [attr, value] = selector.match(/\[(.+?)="(.+?)"\]/)?.slice(1) || []; + if (attr && value) { + el.setAttribute(attr, value); + el.content = content; + document.head.appendChild(el); + } + } +} + +const DEFAULT_TITLE = "Volksmusik & Schlager | Folx Music Television"; +const DEFAULT_DESC = "FOLX TV – Ihr Fernsehsender für Volksmusik und Schlager. Musikvideos, Live-Shows, Interviews und aktuelle Nachrichten aus der Welt der volkstümlichen Musik. Jetzt einschalten!"; +const DEFAULT_OG_TITLE = "Volksmusik & Schlager | Folx Music Television"; +const DEFAULT_OG_DESC = "FOLX TV – Ihr Fernsehsender für Volksmusik und Schlager. Musikvideos, Live-Shows und aktuelle Nachrichten aus der volkstümlichen Musikszene."; +const DEFAULT_OG_IMAGE = "https://folx.tv/og-image.jpg"; + +export function usePageMeta(title: string, description?: string, og?: OgMeta) { useEffect(() => { const suffix = " | Folx Music Television"; document.title = title + suffix; if (description) { - let meta = document.querySelector('meta[name="description"]') as HTMLMetaElement; - if (meta) meta.content = description; + setMetaTag('meta[name="description"]', description); + } + if (og) { + if (og.ogTitle) setMetaTag('meta[property="og:title"]', og.ogTitle); + if (og.ogDescription) setMetaTag('meta[property="og:description"]', og.ogDescription); + if (og.ogImage) { + setMetaTag('meta[property="og:image"]', og.ogImage); + setMetaTag('meta[property="og:image:secure_url"]', og.ogImage); + setMetaTag('meta[name="twitter:image"]', og.ogImage); + } + if (og.ogType) setMetaTag('meta[property="og:type"]', og.ogType); + if (og.ogUrl) { + setMetaTag('meta[property="og:url"]', og.ogUrl); + setMetaTag('link[rel="canonical"]', og.ogUrl); + } + setMetaTag('meta[name="twitter:title"]', og.ogTitle || title + suffix); + setMetaTag('meta[name="twitter:description"]', og.ogDescription || description || DEFAULT_OG_DESC); } return () => { - document.title = "Volksmusik & Schlager | Folx Music Television"; - let meta = document.querySelector('meta[name="description"]') as HTMLMetaElement; - if (meta) meta.content = "FOLX TV – Ihr Fernsehsender für Volksmusik und Schlager. Musikvideos, Live-Shows, Interviews und aktuelle Nachrichten aus der Welt der volkstümlichen Musik. Jetzt einschalten!"; + document.title = DEFAULT_TITLE; + setMetaTag('meta[name="description"]', DEFAULT_DESC); + setMetaTag('meta[property="og:title"]', DEFAULT_OG_TITLE); + setMetaTag('meta[property="og:description"]', DEFAULT_OG_DESC); + setMetaTag('meta[property="og:image"]', DEFAULT_OG_IMAGE); + setMetaTag('meta[property="og:image:secure_url"]', DEFAULT_OG_IMAGE); + setMetaTag('meta[name="twitter:title"]', DEFAULT_OG_TITLE); + setMetaTag('meta[name="twitter:description"]', DEFAULT_OG_DESC); + setMetaTag('meta[name="twitter:image"]', DEFAULT_OG_IMAGE); + setMetaTag('meta[property="og:type"]', "website"); + setMetaTag('meta[property="og:url"]', "https://folx.tv/"); }; - }, [title, description]); + }, [title, description, og?.ogTitle, og?.ogDescription, og?.ogImage, og?.ogType, og?.ogUrl]); } diff --git a/client/src/pages/article.tsx b/client/src/pages/article.tsx index ce97661..d106db0 100644 --- a/client/src/pages/article.tsx +++ b/client/src/pages/article.tsx @@ -101,11 +101,67 @@ export default function ArticlePage() { queryKey: ["/api/articles", slug], }); + const articleUrl = article ? `${window.location.origin}/article/${article.slug}` : ""; + const articleImage = article?.coverImage + ? (article.coverImage.startsWith("http") ? article.coverImage : `${window.location.origin}${article.coverImage}`) + : ""; + usePageMeta( article ? `${article.title} - Volksmusik & Schlager` : "Volksmusik & Schlager Artikel", - 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 ? { + ogTitle: article.title, + ogDescription: article.excerpt, + ogImage: articleImage || undefined, + ogType: "article", + ogUrl: articleUrl, + } : undefined ); + useEffect(() => { + if (!article) return; + const jsonLd = { + "@context": "https://schema.org", + "@type": "NewsArticle", + "headline": article.title, + "description": article.excerpt, + "image": articleImage ? [articleImage] : [], + "datePublished": new Date(article.publishedAt).toISOString(), + "dateModified": new Date(article.publishedAt).toISOString(), + "author": { + "@type": "Organization", + "name": article.author || "Folx Music Television", + "url": "https://folx.tv" + }, + "publisher": { + "@type": "Organization", + "name": "Folx Music Television", + "url": "https://folx.tv", + "logo": { + "@type": "ImageObject", + "url": `${window.location.origin}/favicon.png` + } + }, + "mainEntityOfPage": { + "@type": "WebPage", + "@id": articleUrl + }, + "articleSection": article.category || "News", + "inLanguage": "de" + }; + const script = document.createElement("script"); + script.type = "application/ld+json"; + script.id = "article-jsonld"; + script.textContent = JSON.stringify(jsonLd); + const existing = document.getElementById("article-jsonld"); + if (existing) existing.remove(); + document.head.appendChild(script); + return () => { + const el = document.getElementById("article-jsonld"); + if (el) el.remove(); + }; + }, [article, articleUrl, articleImage]); + useEffect(() => { window.scrollTo(0, 0); }, [slug]); diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx index 8a18939..d5727d2 100644 --- a/client/src/pages/home.tsx +++ b/client/src/pages/home.tsx @@ -511,6 +511,39 @@ function BentoSkeleton() { export default function Home() { usePageMeta("Volksmusik & Schlager", "FOLX TV – Aktuelle Nachrichten, Musikvideos und Interviews aus der Welt der Volksmusik und des Schlagers."); + + useEffect(() => { + const jsonLd = { + "@context": "https://schema.org", + "@type": "WebSite", + "name": "Folx Music Television", + "alternateName": "FOLX TV", + "url": "https://folx.tv", + "description": "FOLX TV – Aktuelle Nachrichten, Musikvideos und Interviews aus der Welt der Volksmusik und des Schlagers.", + "publisher": { + "@type": "Organization", + "name": "Folx Music Television", + "url": "https://folx.tv", + "logo": { + "@type": "ImageObject", + "url": `${window.location.origin}/favicon.png` + } + }, + "inLanguage": "de" + }; + const script = document.createElement("script"); + script.type = "application/ld+json"; + script.id = "home-jsonld"; + script.textContent = JSON.stringify(jsonLd); + const existing = document.getElementById("home-jsonld"); + if (existing) existing.remove(); + document.head.appendChild(script); + return () => { + const el = document.getElementById("home-jsonld"); + if (el) el.remove(); + }; + }, []); + const { data: articles, isLoading } = useQuery({ queryKey: ["/api/articles"], });