SEO: host-aware sitemap.xml + dynamische robots.txt je Domain

Sitemap pro Host (1h-Cache): Hub listet Landing + Rechts-/Info-Seiten,
Spokes zusaetzlich Kategorien (lokalisierte Slugs), oeffentliche
Newsrooms und alle im Archiv sichtbaren PMs des Portals (explizit ohne
PortalScope, forPortalValues). Suche und Marketing-Platzhalterseiten
bleiben draussen. Statische public/robots.txt entfernt (haette die
Route verschattet); robots.txt jetzt dynamisch mit Sitemap-Verweis
und Disallow fuer /admin, /login, /register, Suche.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-03 12:34:12 +00:00
parent b855cbf7c0
commit e9643e620a
5 changed files with 251 additions and 2 deletions

View file

@ -0,0 +1,88 @@
<?php
use App\Enums\Portal;
use App\Models\Category;
use App\Models\Company;
use App\Models\PressRelease;
use Tests\TestCase;
test('robots.txt is served per host with the matching sitemap reference', function () {
/** @var TestCase $this */
$this->get('https://presseecho.test/robots.txt')
->assertSuccessful()
->assertHeader('Content-Type', 'text/plain; charset=UTF-8')
->assertSee('Disallow: /admin', false)
->assertSee('Sitemap: https://presseecho.test/sitemap.xml', false);
$this->get('https://pressekonto.test/robots.txt')
->assertSuccessful()
->assertSee('Sitemap: https://pressekonto.test/sitemap.xml', false);
});
test('the spoke sitemap lists static pages, categories, newsrooms and archive releases of its portal', function () {
/** @var TestCase $this */
$category = Category::factory()->create(['portal' => Portal::Presseecho, 'is_active' => true]);
$category->translations()->create(['locale' => 'de', 'name' => 'Technologie', 'slug' => 'technologie']);
$company = Company::factory()->presseecho()->create(['is_active' => true]);
PressRelease::factory()
->published()
->forPortal(Portal::Presseecho)
->for($category)
->for($company)
->create(['slug' => 'ki-report', 'published_at' => now()->subDay()]);
// Fremd-Portal-PM darf nicht in der presseecho-Sitemap auftauchen.
$bpCategory = Category::factory()->create(['portal' => Portal::Businessportal24]);
PressRelease::factory()
->published()
->forPortal(Portal::Businessportal24)
->for($bpCategory)
->for(Company::factory()->businessportal24()->create())
->create(['slug' => 'bp24-only', 'published_at' => now()->subDay()]);
$response = $this->get('https://presseecho.test/sitemap.xml')->assertSuccessful();
expect($response->headers->get('Content-Type'))->toContain('application/xml');
expect($response->getContent())
->toContain('<loc>https://presseecho.test/de</loc>')
->toContain('<loc>https://presseecho.test/de/preise</loc>')
->toContain('<loc>https://presseecho.test/de/impressum</loc>')
->toContain('<loc>https://presseecho.test/de/category/technologie</loc>')
->toContain('<loc>https://presseecho.test/de/newsroom/'.$company->slug.'</loc>')
->toContain('<loc>https://presseecho.test/de/press-release/ki-report</loc>')
->not->toContain('bp24-only')
// Suche und Marketing-Platzhalterseiten bleiben draußen.
->not->toContain('/de/suche')
->not->toContain('/de/team');
});
test('the hub sitemap lists the landing and legal pages but no archive content', function () {
/** @var TestCase $this */
PressRelease::factory()
->published()
->forPortal(Portal::Presseecho)
->for(Category::factory()->create(['portal' => Portal::Presseecho]))
->for(Company::factory()->presseecho()->create())
->create(['slug' => 'nicht-im-hub', 'published_at' => now()->subDay()]);
$response = $this->get('https://pressekonto.test/sitemap.xml')->assertSuccessful();
expect($response->getContent())
->toContain('<loc>https://pressekonto.test/</loc>')
->toContain('<loc>https://pressekonto.test/de/impressum</loc>')
->toContain('<loc>https://pressekonto.test/de/kuendigung</loc>')
->not->toContain('press-release')
->not->toContain('newsroom');
});
test('marketing pages carry brand-aware titles instead of the hardcoded legacy brand', function () {
/** @var TestCase $this */
$response = $this->get('https://presseecho.test/de/team')->assertSuccessful();
expect($response->getContent())
->toContain('<title>Unser Team – presseecho</title>')
->not->toContain('Business Portal 24');
});