Die robots.txt dieser Website wird erzeugt, nicht von Hand gepflegt. Die Route war geschrieben, 37 Feature-Tests dazu waren grün — und ausgeliefert wurde trotzdem eine ganz andere Datei. Der Fehler lag nicht im Code, sondern in der Lücke zwischen dem, was ein Test prüft, und dem, was ein Crawler bekommt.

Was ausgeliefert wurde und was hätte ausgeliefert werden sollen

Die Route erzeugt eine robots.txt mit genau einer Gruppe für alle Crawler, einigen gesperrten Pfaden und einem Verweis auf die Sitemap. Ein echter HTTP-Abruf gegen den laufenden Server zeigte stattdessen das hier:

Text · robots.txt
User-agent: *
Disallow:

Das ist die Datei, die Laravel beim Anlegen eines neuen Projekts unter public/robots.txt mitbringt. Sie ist nicht falsch — sie erlaubt alles —, aber sie hat mit der erzeugten Datei nichts zu tun. Weder die Sitemap noch die gesperrten Pfade standen darin.

Warum der Webserver die Route nie erreicht

Ein Laravel-Projekt wird so ausgeliefert, dass der Webserver zuerst im Ordner public/ nachsieht. Existiert der angefragte Pfad dort als Datei, liefert er sie direkt aus. Erst wenn keine Datei passt, geht der Request an index.php — und damit an Laravels Routing.

Das gilt in der üblichen Konfiguration für Caddy, nginx und Apache ebenso wie für den Entwicklungsserver. php artisan serve nutzt dafür ein kleines Router-Skript, dessen entscheidende Zeile so aussieht:

PHP · vendor/laravel/framework/src/Illuminate/Foundation/resources/server.php
if ($uri !== '/' && file_exists($publicPath.$uri)) {
    return false;
}

return false bedeutet: Der eingebaute PHP-Server liefert die Datei selbst aus. Die Anwendung wird für diesen Request gar nicht erst gestartet.

Warum 37 grüne Tests nichts davon bemerkt haben

Laravels Feature-Tests schicken keinen echten HTTP-Request. $this->get('/robots.txt') ruft den HTTP-Kernel der Anwendung direkt auf — und der kennt nur Routen, keine Dateien in public/. Der Test fragt also genau die Stelle, die im Betrieb nie gefragt wird.

Prüfweg Was er sieht Hätte den Fehler gefunden
Pest-Feature-Test den HTTP-Kernel nein
curl gegen den laufenden Server die tatsächliche Auslieferung ja
Playwright gegen den laufenden Server die tatsächliche Auslieferung ja

Die Tests waren nicht falsch. Sie haben nur eine andere Frage beantwortet als die, die gestellt werden musste.

Die Behebung in zwei Schritten

Der erste Schritt ist trivial: public/robots.txt löschen. Danach erreicht der Request das Routing, und die erzeugte Datei wird ausgeliefert.

Der zweite Schritt ist der wichtigere. Ein neuer Browser-Test ruft jeden Maschinen-Endpunkt über den laufenden Server ab und prüft Inhalt und Content-Type. Für robots.txt gibt es zusätzlich eine gezielte Prüfung auf die Laravel-Standarddatei:

JavaScript · tests/e2e/machine.spec.js
test('robots.txt stammt aus der Anwendung, nicht aus public/', async ({ request }) => {
    // Laravels Standarddatei beginnt ohne Kommentarzeile und enthält ein
    // leeres `Disallow:`. Findet sich das, ist die Route überschattet.
    const body = await (await request.get('/robots.txt')).text();

    expect(body).toContain('# alexander-seidler.com');
    expect(body).not.toMatch(/^Disallow:\s*$/m);
});

Der entscheidende Unterschied zu den Feature-Tests: Playwright startet einen echten Server und stellt echte HTTP-Anfragen. Liegt je wieder eine Datei im Weg, schlägt dieser Test an, egal wie viele Feature-Tests grün sind.

Was ich daraus mitnehme

Ein grüner Feature-Test ist eine Aussage über die Anwendung, nicht über die Auslieferung. Für die meisten Seiten ist das dasselbe. Für Pfade, die auch als Datei existieren können, ist es das nicht — und genau dort liegen die Dateien, die Suchmaschinen und KI-Crawler zuerst abrufen.

Wie die übrigen Tests dieser Website aufgebaut sind, beschreibt die Fallstudie zu alexander-seidler.com. Um Cookies und Middleware-Gruppen geht es im Artikel Warum meine Laravel-Startseite kein Cookie setzt.

Quellen: PHP — Eingebauter Webserver · Laravel 13 — HTTP-Tests

Häufige Fragen zum Artikel

Warum ignoriert Laravel meine Route für robots.txt?

Weil im Ordner public eine gleichnamige Datei liegt. Der Webserver prüft zuerst, ob der Pfad als Datei existiert, und liefert sie direkt aus. Erst wenn keine Datei passt, geht der Request an index.php und damit an das Routing. Die Route existiert also, wird aber nie erreicht.

Warum sind meine Pest-Tests grün, obwohl die falsche Datei ausgeliefert wird?

Feature-Tests rufen den HTTP-Kernel von Laravel direkt auf. Die Dateiauslieferung des Webservers liegt davor und wird dabei übersprungen. Ein grüner Feature-Test sagt deshalb nur etwas über die Anwendung aus, nicht darüber, was ein Crawler vom laufenden Server tatsächlich bekommt.

Welche Pfade sind außer robots.txt betroffen?

Jeder Pfad, den es auch als Datei in public geben könnte, etwa sitemap.xml, humans.txt, llms.txt oder favicon.ico. Wer einen dieser Pfade als Route erzeugt, sollte sicherstellen, dass keine gleichnamige Datei existiert, und das mit einem echten HTTP-Abruf prüfen.

Verwandte Artikel