- Image widths are 480, 720, 960, 1280, 1600 and 2400. A phone needed 721 pixels and got 960. Existing images receive the new steps from a task; stored pages pick them up at render time, so no document is rewritten. Mobile backgrounds use 720. - A variant served once is mirrored to public/media, and a rewrite rule lets the web server deliver it from there. var/media stays the source. - The hero preload follows the image each device really shows. - The post navigation reads the order and then two posts instead of up to 500 documents. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
60 lines
3.0 KiB
PHP
60 lines
3.0 KiB
PHP
<?php
|
|
|
|
/*
|
|
* Router for the PHP built-in server only.
|
|
*
|
|
* The built-in server serves existing files itself and answers 404 for any missing path that looks
|
|
* like a file, so /sitemap.xml, /robots.txt, /llms.txt and /feed.xml never reach the front
|
|
* controller. Real hosting rewrites everything that is not a file to public/index.php, which is
|
|
* what public/.htaccess does. This script reproduces that behaviour for local development.
|
|
*/
|
|
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
|
|
// Wie die Regel in public/.htaccess: Ein Bild der Mediathek, das im Abbild public/media/ liegt,
|
|
// geht ohne die Anwendung raus (ADR 129). Ohne das maesse man hier weiter den Weg ueber PHP.
|
|
if (1 === preg_match('#^/media/[a-f0-9]{32}/[0-9]{1,4}$#D', $path) && is_file(__DIR__.'/../public'.$path.'.webp')) {
|
|
header('Content-Type: image/webp');
|
|
header('Cache-Control: public, max-age=31536000, immutable');
|
|
header('X-Content-Type-Options: nosniff');
|
|
header('Content-Length: '.filesize(__DIR__.'/../public'.$path.'.webp'));
|
|
readfile(__DIR__.'/../public'.$path.'.webp');
|
|
|
|
exit;
|
|
}
|
|
$file = __DIR__.'/../public'.$path;
|
|
if ('/' !== $path && !str_contains($path, '..') && is_file($file)) {
|
|
// Dieselben Antwortkoepfe wie public/.htaccess sie auf einem Webserver setzt: Stylesheets,
|
|
// Skripte, Schriften und Bilder tragen ihre Fassung oder ihre Kennung in der Adresse und
|
|
// duerfen deshalb lange im Browser bleiben. Ohne das misst jede Messung hier etwas anderes
|
|
// als das, was spaeter ausgeliefert wird - und man optimiert an der falschen Stelle.
|
|
if (1 === preg_match('/\.(css|js|woff2|webp|png|jpe?g|gif|svg)$/', $path, $treffer) && !str_starts_with($path, '/assets/seite/')) {
|
|
header('Content-Type: '.match ($treffer[1]) {
|
|
'css' => 'text/css; charset=UTF-8',
|
|
'js' => 'text/javascript; charset=UTF-8',
|
|
'woff2' => 'font/woff2',
|
|
'svg' => 'image/svg+xml; charset=UTF-8',
|
|
'jpg', 'jpeg' => 'image/jpeg',
|
|
default => 'image/'.$treffer[1],
|
|
});
|
|
header('Cache-Control: public, max-age=31536000, immutable');
|
|
// Und ebenso die Komprimierung, die mod_deflate auf einem Webserver uebernimmt: ohne sie
|
|
// misst man hier eine Stylesheetgroesse, die es im Betrieb nie gibt.
|
|
$inhalt = (string) file_get_contents($file);
|
|
if (in_array($treffer[1], ['css', 'js', 'svg'], true) && str_contains($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '', 'gzip')) {
|
|
$inhalt = (string) gzencode($inhalt, 6);
|
|
header('Content-Encoding: gzip');
|
|
header('Vary: Accept-Encoding');
|
|
}
|
|
header('Content-Length: '.strlen($inhalt));
|
|
echo $inhalt;
|
|
|
|
exit;
|
|
}
|
|
|
|
return false; // Let the built-in server deliver the static file.
|
|
}
|
|
// Auch die Seiten selbst gehen komprimiert raus, wie auf einem Webserver mit mod_deflate.
|
|
ini_set('zlib.output_compression', '1');
|
|
ini_set('zlib.output_compression_level', '6');
|
|
|
|
require __DIR__.'/../public/index.php';
|