SEO-Audit 2026-11-09: Meta-Komposition, Sitemap, Redirects, robots.txt

- VitecMetaHandler: Record-Metadaten fuer News-/Produkt-/Story-Details
  (Titel, Description, og:/twitter:-Tags, og:image), '| VITEC'-Suffix,
  Fallback-Description aus Seitenfeldern, twitter:card large_image,
  Story-Canonical auf die oeffentliche Kurz-URL
- PageJsonLdRenderer: Product- und NewsArticle-Nodes auf Detailseiten
  (News-Details hatten <title>article</title> und kein Schema)
- NewsJsonRenderer: Listen ohne Volltext-Body (JSON 2.6 MB -> ~0.6 MB)
- XML-Sitemap: News-Teil konfiguriert (Ordner 11+442, Detailseite 21),
  Record-Sitemaps fuer Produkte und Success Stories (kurze URLs),
  Route-Enhancer fuer alle Teile, Shell-/Altseiten ausgeschlossen
- Migration seo_fixes_20261109: 161 Self-Canonicals geleert (warfen u.a.
  Home, Markets, Solutions, Support aus der Sitemap), noindex fuer
  technische und alte News-Seiten, 71 Redirects Alt-URLs -> neue Ziele,
  51 tote Footer-Links ersetzt
- public/robots.txt mit Sitemap-Verweis

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
orascheandClaude Fable 5 committed 2026-11-09 22:11:01 +01:00
1 parent be519f9bc1
commit b9736b9ac7
11 files changed
+814 -25

No files matched your search

@@ -156,7 +156,7 @@ final class NewsJsonRenderer
}
$newsRows = $this->fetchNewsList($contentElement, $settings);
$items = array_map(fn (array $newsRow): array => $this->serializeNews($newsRow, $settings), $newsRows);
$items = array_map(fn (array $newsRow): array => $this->serializeNews($newsRow, $settings, 'list'), $newsRows);
$response = [
'mode' => 'list',
@@ -323,7 +323,7 @@ final class NewsJsonRenderer
* @param array<string,mixed> $settings
* @return array<string,mixed>
*/
private function serializeNews(array $news, array $settings = []): array
private function serializeNews(array $news, array $settings = [], string $mode = 'detail'): array
{
$uid = (int)($news['uid'] ?? 0);
$pathSegment = (string)($news['path_segment'] ?? '');
@@ -338,7 +338,14 @@ final class NewsJsonRenderer
'detailUrl' => $urls['detailUrl'],
'canonicalUrl' => $urls['canonicalUrl'],
'teaser' => (string)($news['teaser'] ?? ''),
'bodytext' => RteResolver::html($news['bodytext'] ?? ''),
// List payloads do not carry the full article body: with ~600
// records that inflated /_json/insights/news to 2.6 MB (and the
// SSR HTML to 2.8 MB). Cards render from `teaser`; only records
// WITHOUT a teaser get a plain-text excerpt in `bodytext` so no
// card goes blank. The detail mode is unchanged.
'bodytext' => $mode === 'list'
? (trim((string)($news['teaser'] ?? '')) !== '' ? '' : $this->listExcerpt((string)($news['bodytext'] ?? '')))
: RteResolver::html($news['bodytext'] ?? ''),
'datetime' => (int)($news['datetime'] ?? 0),
'archive' => (int)($news['archive'] ?? 0),
'istopnews' => (bool)($news['istopnews'] ?? false),
@@ -353,6 +360,23 @@ final class NewsJsonRenderer
];
}
/**
* Plain-text excerpt of an RTE body for list cards (teaser fallback).
*/
private function listExcerpt(string $html): string
{
$text = trim((string)preg_replace('/\s+/u', ' ', strip_tags($html)));
if (mb_strlen($text) <= 300) {
return $text;
}
$cut = mb_substr($text, 0, 300);
$lastSpace = mb_strrpos($cut, ' ');
if ($lastSpace !== false && $lastSpace > 180) {
$cut = mb_substr($cut, 0, $lastSpace);
}
return rtrim($cut, " .,;:") . '...';
}
/**
* Where a news item actually points.
*