web: Doppelveröffentlichung — Prüfsumme, kanonische Zuordnung (aus bis E9), Quellenauskunft, noindex im Portal (Anhang 2)

Über die API kommt praktisch jede Meldung zweimal, je Portal eine Zeile
mit gleichem Text: 9.092 Paare, 53 % des Archivs, bisher jede Fassung
self-canonical und in beiden Sitemaps — Google wählte.

1c.7: press_releases.content_hash (SHA-256 über Titel + Text ohne Markup),
gesetzt beim Speichern, Bestand per press-releases:hash-content
(Query-Builder, Blöcke). Migration in zwei Statements — ADD COLUMN INSTANT,
ADD INDEX INPLACE, kein Rebuild. press-releases:duplicates-report zählt
Paare je Hauptkategorie.

1c.8: canonical_press_release_id + CanonicalAssigner. Erstquelle folgt der
Kategorie (config/canonical.php, Vorschlag zu E9), sonst die zuerst
veröffentlichte Fassung. Zuordnung bei jeder Veröffentlichung und über den
Bestand (press-releases:assign-canonicals). Die Kopie trägt rel=canonical
und mainEntityOfPage auf die Erstquelle; Sitemap und News-Sitemap nennen
nur sie; Leser sehen weiterhin beide. Alles hinter
CANONICAL_ASSIGNMENT_ENABLED (false), bis E9 entschieden ist.

1c.9: user_canonical in den Index-Prüfungen, sourceVerdict() unterscheidet
eigene Adresse / andere eigene Fassung / fremdes Portal; Anzeige im Block
„Google-Index" hinter GSC_CUSTOMER_VISIBLE, ohne Aussage über das Warum.

Dazu: Abschnitt zur einen Quelle auf /gute-pressemitteilung (Verteiler →
viele Kopien, eigene Website zuerst, keine Garantie), noindex in Admin-,
Kunden- und Auth-Layouts, UmamiSnippetTest von der lokalen .env gelöst.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-17 16:09:16 +00:00
parent 447dc4c61e
commit ee267c622e
28 changed files with 1231 additions and 11 deletions

View file

@ -1,6 +1,8 @@
@extends('web.layouts.web-master')
@php
use App\Enums\Portal;
use App\Services\PressRelease\CanonicalAssigner;
use App\Support\StructuredData;
use Illuminate\Support\Str;
@ -89,6 +91,14 @@
$shareUrl = $isPreview ? request()->fullUrl() : route('release.detail', ['slug' => $release->slug]);
$brandLabel = config('app.theme') === 'presseecho' ? 'presseecho' : 'businessportal24';
// Doppelveröffentlichung (Anhang 2, 1c.8): rel=canonical zeigt auf die
// Erstquelle — die eigene Adresse, oder bei zweiter Fassung/portal=both
// das Portal, das laut Kategorie-Zuordnung führt. Geteilt wird weiterhin
// die Adresse, auf der der Leser gerade ist.
$canonicalUrl = $isPreview
? $shareUrl
: app(CanonicalAssigner::class)->canonicalUrlFor($release, $brandLabel === 'presseecho' ? Portal::Presseecho : Portal::Businessportal24);
// SEO: NewsArticle + BreadcrumbList als JSON-LD — nur für die öffentliche
// Seite (die Vorschau ist noindex und hat keine stabile URL).
$jsonLd = null;
@ -116,7 +126,7 @@
'@graph' => [
array_filter([
'@type' => 'NewsArticle',
'mainEntityOfPage' => ['@type' => 'WebPage', '@id' => $shareUrl],
'mainEntityOfPage' => ['@type' => 'WebPage', '@id' => $canonicalUrl],
'headline' => Str::limit($release->title, 110),
'description' => filled($lead) ? $lead : $teaser,
'image' => $imageUrl ? [$imageUrl] : null,
@ -160,7 +170,7 @@
@if ($isPreview)
@section('robots', 'noindex,nofollow')
@else
@section('canonical', $shareUrl)
@section('canonical', $canonicalUrl)
@section('meta_extra')
@if ($publishedAt)