From 283f54c56c6c0a3ff407e7351dce9832cd11f639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Sat, 24 Oct 2026 19:05:21 +0200 Subject: [PATCH] feat: EXT:staticfilecache serves cached pages without PHP again, as on TYPO3 10 Every page view goes through TYPO3 13 since the switch; staticfilecache 15.2 writes a gzip copy of each cacheable page, and the TYPO3 10 rewrite rules in public/.htaccess let Apache deliver it directly (GET, no query string, no backend login). Pages answer in about 2 ms instead of 0.5-4 s; static and dynamic output are identical for a sample of 22 sitemap URLs. A flush of the pages cache group (travel data and snow report imports, "clear frontend caches", deploys) removes the files; a page's own cache lifetime still applies through the generated per-page .htaccess. X-SFC-Tags is not passed on: with every record of a page as a tag it reaches 100+ KB, more than Apache accepts from PHP-FPM, and it would expose internal record ids. Debug headers stay off for the same reason. Co-Authored-By: Claude Opus 5.5 --- composer.json | 1 + composer.lock | 90 ++++++++++++++++++++++++++++++- config/system/settings.php | 38 ++++++++++++++ public/.htaccess | 105 +++++++++++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1ad60aa..4d1a7a4 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ "ep/ep-theme": "@dev", "fluidtypo3/vhs": "^8.0", "georgringer/news": "^14.1", + "lochmueller/staticfilecache": "^15.2", "ssch/typo3-encore": "^7.0", "typo3/cms-backend": "^13.4", "typo3/cms-belog": "^13.4", diff --git a/composer.lock b/composer.lock index edb338a..dc703e4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "85a0f79761fbcaf517b130c2f7a3e114", + "content-hash": "f1134476988f3bf5d2a3a74092cdb285", "packages": [ { "name": "b13/container", @@ -1785,6 +1785,94 @@ ], "time": "2022-02-21T08:41:21+00:00" }, + { + "name": "lochmueller/staticfilecache", + "version": "15.2.3", + "source": { + "type": "git", + "url": "https://github.com/lochmueller/staticfilecache.git", + "reference": "21697e6b3ea3584995fefe9ab74615927a872744" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lochmueller/staticfilecache/zipball/21697e6b3ea3584995fefe9ab74615927a872744", + "reference": "21697e6b3ea3584995fefe9ab74615927a872744", + "shasum": "" + }, + "require": { + "ext-pdo": "*", + "ext-zlib": "*", + "php": "^8.1", + "typo3/cms-backend": "^12.4||^13.4", + "typo3/cms-core": "^12.4||^13.4", + "typo3/cms-extensionmanager": "^12.4||^13.4" + }, + "require-dev": { + "andersundsehr/phpstan-git-files": "^1.0", + "friendsofphp/php-cs-fixer": "^3.30", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.10", + "saschaegerer/phpstan-typo3": "^1.8", + "ssch/typo3-rector": "^2.2", + "typo3/testing-framework": "^8.0" + }, + "suggest": { + "friendsofsymfony/http-cache": "For HTTP proxy cache services like Varnish", + "lochmueller/seocli": "Scan websites against SEO criteria and/or trigger the indexing process and cache warming in deployment scripts" + }, + "type": "typo3-cms-extension", + "extra": { + "typo3/cms": { + "web-dir": ".Build/Web", + "extension-key": "staticfilecache", + "cms-package-dir": "{$vendor-dir}/typo3/cms" + } + }, + "autoload": { + "psr-4": { + "SFC\\Staticfilecache\\": "Classes" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Tim Lochmüller", + "email": "tim@fruit-lab.de", + "homepage": "https://github.com/lochmueller", + "role": "Developer" + } + ], + "description": "Transparent static file cache solution using mod_rewrite and mod_expires. Increase performance for static pages by a factor of 230!!", + "homepage": "https://extensions.typo3.org/extension/staticfilecache", + "keywords": [ + "StaticFileCache", + "TYPO3 CMS", + "cache", + "file", + "magic", + "speed", + "static" + ], + "support": { + "docs": "https://docs.typo3.org/p/lochmueller/staticfilecache/main/en-us/", + "issues": "https://github.com/lochmueller/staticfilecache/issues", + "source": "https://github.com/lochmueller/staticfilecache" + }, + "funding": [ + { + "url": "https://paypal.me/lochmueller", + "type": "custom" + }, + { + "url": "https://github.com/lochmueller", + "type": "github" + } + ], + "time": "2025-09-02T19:26:27+00:00" + }, { "name": "lolli42/finediff", "version": "1.1.2", diff --git a/config/system/settings.php b/config/system/settings.php index d7c0b03..edb2349 100644 --- a/config/system/settings.php +++ b/config/system/settings.php @@ -97,6 +97,44 @@ return [ 'scheduler' => [ 'maxLifetime' => '1440', ], + 'staticfilecache' => [ + 'backendDisplayMode' => 'both', + 'boostMode' => '0', + 'clearCacheForAllDomains' => '1', + 'cspGenerationOverride' => '0', + 'debugHeaders' => '0', + 'disableInDevelopment' => '0', + 'enableGeneratorBrotli' => '0', + 'enableGeneratorGzip' => '1', + 'enableGeneratorPhp' => '0', + 'enableGeneratorPlain' => '0', + 'hashUriInCache' => '0', + 'headerSizeBuffer' => '1.5', + 'htaccessTemplateName' => 'EXT:staticfilecache/Resources/Private/Templates/Htaccess.html', + 'inlineAssetsFileSize' => '50000', + 'inlineScriptMinify' => '0', + 'inlineServiceFavIcon' => '0', + 'inlineServiceScripts' => '0', + 'inlineServiceStyles' => '0', + 'inlineStyleAssets' => 'ico,png,woff2', + 'inlineStyleMinify' => '0', + 'largeIdentifierInCacheTable' => '0', + 'maxHeaderSize' => '8192', + 'overrideCacheDirectory' => '', + 'overrideClientUserAgent' => '', + 'phpTemplateName' => 'EXT:staticfilecache/Resources/Private/Templates/Php.html', + 'rawurldecodeCacheFileName' => '0', + 'renameTablesToOtherPrefix' => '0', + 'sendCacheControlHeaderRedirectAfterCacheTimeout' => '1', + 'sendHttp2PushEnable' => '0', + 'sendHttp2PushFileExtensions' => 'css,js', + 'sendHttp2PushFileLimit' => '10', + 'sendHttp2PushLimitToArea' => '', + 'useFallbackMiddleware' => '1', + 'useReverseUriLengthInPriority' => '1', + 'validFallbackHeaders' => 'Content-Type,Content-Language,Content-Security-Policy,Link', + 'validHtaccessHeaders' => 'Content-Type,Content-Language,Content-Security-Policy,Link', + ], ], 'FE' => [ 'cacheHash' => [ diff --git a/public/.htaccess b/public/.htaccess index 0427542..f133c6f 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -412,6 +412,111 @@ AddDefaultCharset utf-8 # NOTE: Add your additional local storages here RewriteRule ^(?:fileadmin/|typo3conf/|typo3temp/|uploads/) - [L] + ### Begin: StaticFileCache (preparation) #### + + # Document root configuration + RewriteRule .* - [E=SFC_ROOT:%{DOCUMENT_ROOT}] + + # Cleanup URI + RewriteCond %{REQUEST_URI} ^.*$ + RewriteRule .* - [E=SFC_URI:/%{REQUEST_URI}] + RewriteCond %{REQUEST_URI} ^/.*$ + RewriteRule .* - [E=SFC_URI:%{REQUEST_URI}] + RewriteCond %{REQUEST_URI} ^/?$ + RewriteRule .* - [E=SFC_URI:/] + + # Cleanup HOST + RewriteCond %{HTTP_HOST} ^([^:]+)(:[0-9]+)?$ + RewriteRule .* - [E=SFC_HOST:%1] + + # Get scheme + RewriteRule .* - [E=SFC_PROTOCOL:http] + RewriteCond %{SERVER_PORT} ^443$ [OR] + RewriteCond %{HTTP:X-Forwarded-Proto} https + RewriteRule .* - [E=SFC_PROTOCOL:https] + + # Get port + RewriteRule .* - [E=SFC_PORT:80] + RewriteCond %{ENV:SFC_PROTOCOL} ^https$ [NC] + RewriteRule .* - [E=SFC_PORT:443] + RewriteCond %{SERVER_PORT} ^[0-9]+$ + RewriteRule .* - [E=SFC_PORT:%{SERVER_PORT}] + RewriteCond %{HTTP:X-Forwarded-Port} ^[0-9]+$ + RewriteRule .* - [E=SFC_PORT:%{HTTP:X-Forwarded-Port}] + + # Full path for redirect + RewriteRule .* - [E=SFC_FULLPATH:typo3temp/assets/tx_staticfilecache/%{ENV:SFC_PROTOCOL}_%{ENV:SFC_HOST}_%{ENV:SFC_PORT}%{ENV:SFC_URI}/index] + + # Extension (Order: br, gzip, default) + RewriteRule .* - [E=SFC_EXT:] + RewriteCond %{HTTP:Accept-Encoding} br [NC] + RewriteRule .* - [E=SFC_EXT:.br] + RewriteCond %{ENV:SFC_ROOT}/%{ENV:SFC_FULLPATH}%{ENV:SFC_EXT} !-f + RewriteRule .* - [E=SFC_EXT:] + RewriteCond %{ENV:SFC_EXT} ^$ + RewriteCond %{HTTP:Accept-Encoding} gzip [NC] + RewriteRule .* - [E=SFC_EXT:.gz] + RewriteCond %{ENV:SFC_EXT} ^\.gz$ + RewriteCond %{ENV:SFC_ROOT}/%{ENV:SFC_FULLPATH}%{ENV:SFC_EXT} !-f + RewriteRule .* - [E=SFC_EXT:] + + # Write Extension to SFC_FULLPATH + RewriteRule .* - [E=SFC_FULLPATH:%{ENV:SFC_FULLPATH}%{ENV:SFC_EXT}] + + ### Begin: StaticFileCache (main) #### + + # We only redirect URI's without query strings + RewriteCond %{QUERY_STRING} ^$ + + # It only makes sense to do the other checks if a static file actually exists. + RewriteCond %{ENV:SFC_ROOT}/%{ENV:SFC_FULLPATH} -f + + # NO frontend or backend user is logged in. Logged in users may see different + # information than anonymous users. But the anonymous version is cached. So + # don't show the anonymous version to logged in users. + RewriteCond %{HTTP_COOKIE} !staticfilecache [NC] + + # Avoid cached versions for workspace preview links of users that are not logged in + RewriteCond %{HTTP_COOKIE} !ADMCMD_prev [NC] + + # We only redirect GET requests + RewriteCond %{REQUEST_METHOD} GET + + # Rewrite the request to the static file. + RewriteRule .* %{ENV:SFC_ROOT}/%{ENV:SFC_FULLPATH} [L] + + # Do not allow direct call the cache entries + RewriteCond %{ENV:SFC_URI} ^/typo3temp/assets/tx_staticfilecache/.* + RewriteCond %{ENV:REDIRECT_STATUS} ^$ + RewriteRule .* - [F,L] + + ### Begin: StaticFileCache (options) #### + + # Set proper content type and encoding for compressed html. + + SetEnv no-gzip 1 + SetEnv no-brotli 1 + + Header set Content-Encoding gzip + + ForceType text/html + + + SetEnv no-gzip 1 + SetEnv no-brotli 1 + + Header set Content-Encoding br + + ForceType text/html + + + # Avoid .br files being delivered with Content-Language: br headers + + RemoveLanguage .br + + + ### End: StaticFileCache ### + # If the file/symlink/directory does not exist but is below /typo3/, redirect to the main TYPO3 entry point. RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^typo3/(.*)$ %{ENV:CWD}index.php [QSA,L]