diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php
index 7453ce921de5fa6d9f6de148f93e9258c67233dd..320f276bd25f8c6ac626437f21f26ff07e4dc29d 100644
--- a/.phpstorm.meta.php
+++ b/.phpstorm.meta.php
@@ -88,6 +88,7 @@ namespace PHPSTORM_META {
         'moduleData',
         'frontend.controller',
         'frontend.typoscript',
+        'frontend.cache.instruction',
     );
     override(\Psr\Http\Message\ServerRequestInterface::getAttribute(), map([
         'frontend.user' => \TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication::class,
@@ -99,6 +100,7 @@ namespace PHPSTORM_META {
         'moduleData' => \TYPO3\CMS\Backend\Module\ModuleData::class,
         'frontend.controller' => \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class,
         'frontend.typoscript' => \TYPO3\CMS\Core\TypoScript\FrontendTypoScript::class,
+        'frontend.cache.instruction' => \TYPO3\CMS\Frontend\Cache\CacheInstruction::class,
     ]));
 
     expectedArguments(
diff --git a/typo3/sysext/adminpanel/Classes/Modules/CacheModule.php b/typo3/sysext/adminpanel/Classes/Modules/CacheModule.php
index 85c86ac43c228206ca7b0500d49684c94d7f487e..5163f340b4afc1b4b86f59be7e4832af08b9282a 100644
--- a/typo3/sysext/adminpanel/Classes/Modules/CacheModule.php
+++ b/typo3/sysext/adminpanel/Classes/Modules/CacheModule.php
@@ -26,6 +26,7 @@ use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Core\Routing\PageArguments;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
+use TYPO3\CMS\Frontend\Cache\CacheInstruction;
 
 class CacheModule extends AbstractModule implements PageSettingsProviderInterface, RequestEnricherInterface, ResourceProviderInterface
 {
@@ -84,7 +85,9 @@ class CacheModule extends AbstractModule implements PageSettingsProviderInterfac
     public function enrich(ServerRequestInterface $request): ServerRequestInterface
     {
         if ($this->configurationService->getConfigurationOption('cache', 'noCache')) {
-            $request = $request->withAttribute('noCache', true);
+            $cacheInstruction = $request->getAttribute('frontend.cache.instruction', new CacheInstruction());
+            $cacheInstruction->disableCache('EXT:adminpanel: "No caching" disables cache.');
+            $request = $request->withAttribute('frontend.cache.instruction', $cacheInstruction);
         }
         return $request;
     }
diff --git a/typo3/sysext/adminpanel/Classes/Modules/Debug/PageTitle.php b/typo3/sysext/adminpanel/Classes/Modules/Debug/PageTitle.php
index a6b53f05a3ecd0685dfa9a14c6b44ad5f2ab9009..30cfce1375b8cf9eab26c17f381a246689e2a47b 100644
--- a/typo3/sysext/adminpanel/Classes/Modules/Debug/PageTitle.php
+++ b/typo3/sysext/adminpanel/Classes/Modules/Debug/PageTitle.php
@@ -24,7 +24,6 @@ use TYPO3\CMS\Adminpanel\ModuleApi\DataProviderInterface;
 use TYPO3\CMS\Adminpanel\ModuleApi\ModuleData;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
-use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
 /**
  * Admin Panel Page Title module for showing the Page title providers
@@ -62,13 +61,12 @@ class PageTitle extends AbstractSubModule implements DataProviderInterface
         $data = [
             'cacheEnabled' => true,
         ];
-        if ($this->isNoCacheEnabled()) {
+        if (!$this->isCachingAllowed($request)) {
             $data = [
                 'orderedProviders' => [],
                 'usedProvider' => null,
                 'skippedProviders' => [],
             ];
-
             $logRecords = GeneralUtility::makeInstance(InMemoryLogWriter::class)->getLogEntries();
             foreach ($logRecords as $logEntry) {
                 if ($logEntry->getComponent() === self::LOG_COMPONENT) {
@@ -100,13 +98,8 @@ class PageTitle extends AbstractSubModule implements DataProviderInterface
         return $view->render();
     }
 
-    protected function isNoCacheEnabled(): bool
-    {
-        return (bool)$this->getTypoScriptFrontendController()->no_cache;
-    }
-
-    protected function getTypoScriptFrontendController(): TypoScriptFrontendController
+    protected function isCachingAllowed(ServerRequestInterface $request): bool
     {
-        return $GLOBALS['TSFE'];
+        return $request->getAttribute('frontend.cache.instruction')->isCachingAllowed();
     }
 }
diff --git a/typo3/sysext/adminpanel/Classes/Modules/Info/GeneralInformation.php b/typo3/sysext/adminpanel/Classes/Modules/Info/GeneralInformation.php
index 91e7dbd6ef2aaca6dfde9a536efb2911b77908a9..c3f0c0f51c343d26dbd7cb919e377abe24d8b651 100644
--- a/typo3/sysext/adminpanel/Classes/Modules/Info/GeneralInformation.php
+++ b/typo3/sysext/adminpanel/Classes/Modules/Info/GeneralInformation.php
@@ -53,15 +53,16 @@ class GeneralInformation extends AbstractSubModule implements DataProviderInterf
                     'pageUid' => $tsfe->id,
                     'pageType' => $tsfe->getPageArguments()->getPageType(),
                     'groupList' => implode(',', $frontendUserAspect->getGroupIds()),
-                    'noCache' => $this->isNoCacheEnabled(),
+                    'noCache' => !$this->isCachingAllowed($request),
+                    'noCacheReasons' => $request->getAttribute('frontend.cache.instruction')->getDisabledCacheReasons(),
                     'countUserInt' => count($tsfe->config['INTincScript'] ?? []),
                     'totalParsetime' => $this->getTimeTracker()->getParseTime(),
                     'feUser' => [
                         'uid' => $frontendUserAspect->get('id') ?: 0,
                         'username' => $frontendUserAspect->get('username') ?: '',
                     ],
-                    'imagesOnPage' => $this->collectImagesOnPage(),
-                    'documentSize' => $this->collectDocumentSize(),
+                    'imagesOnPage' => $this->collectImagesOnPage($request),
+                    'documentSize' => $this->collectDocumentSize($request),
                 ],
             ]
         );
@@ -106,7 +107,7 @@ class GeneralInformation extends AbstractSubModule implements DataProviderInterf
      * Collects images from TypoScriptFrontendController and calculates the total size.
      * Returns human-readable image sizes for fluid template output
      */
-    protected function collectImagesOnPage(): array
+    protected function collectImagesOnPage(ServerRequestInterface $request): array
     {
         $imagesOnPage = [
             'files' => [],
@@ -115,7 +116,7 @@ class GeneralInformation extends AbstractSubModule implements DataProviderInterf
             'totalSizeHuman' => GeneralUtility::formatSize(0),
         ];
 
-        if ($this->isNoCacheEnabled() === false) {
+        if ($this->isCachingAllowed($request)) {
             return $imagesOnPage;
         }
 
@@ -138,21 +139,20 @@ class GeneralInformation extends AbstractSubModule implements DataProviderInterf
     }
 
     /**
-     * Gets the document size from the current page in a human readable format
+     * Gets the document size from the current page in a human-readable format
      */
-    protected function collectDocumentSize(): string
+    protected function collectDocumentSize(ServerRequestInterface $request): string
     {
         $documentSize = 0;
-        if ($this->isNoCacheEnabled() === true) {
+        if (!$this->isCachingAllowed($request)) {
             $documentSize = mb_strlen($this->getTypoScriptFrontendController()->content, 'UTF-8');
         }
-
         return GeneralUtility::formatSize($documentSize);
     }
 
-    protected function isNoCacheEnabled(): bool
+    protected function isCachingAllowed(ServerRequestInterface $request): bool
     {
-        return (bool)$this->getTypoScriptFrontendController()->no_cache;
+        return $request->getAttribute('frontend.cache.instruction')->isCachingAllowed();
     }
 
     protected function getTypoScriptFrontendController(): TypoScriptFrontendController
diff --git a/typo3/sysext/adminpanel/Classes/Modules/PreviewModule.php b/typo3/sysext/adminpanel/Classes/Modules/PreviewModule.php
index d6d7688556cba189d7fde45e6eb7ce666867ca94..ed8ebbf3a9b532c3203c1dd8ba20f7b2cd9e1401 100644
--- a/typo3/sysext/adminpanel/Classes/Modules/PreviewModule.php
+++ b/typo3/sysext/adminpanel/Classes/Modules/PreviewModule.php
@@ -37,6 +37,7 @@ use TYPO3\CMS\Core\Routing\PageArguments;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Frontend\Aspect\PreviewAspect;
+use TYPO3\CMS\Frontend\Cache\CacheInstruction;
 
 /**
  * Admin Panel Preview Module
@@ -45,8 +46,6 @@ class PreviewModule extends AbstractModule implements RequestEnricherInterface,
 {
     use LoggerAwareTrait;
 
-    protected CacheManager $cacheManager;
-
     /**
      * module configuration, set on initialize
      *
@@ -61,10 +60,9 @@ class PreviewModule extends AbstractModule implements RequestEnricherInterface,
      */
     protected array $config;
 
-    public function injectCacheManager(CacheManager $cacheManager): void
-    {
-        $this->cacheManager = $cacheManager;
-    }
+    public function __construct(
+        protected readonly CacheManager $cacheManager,
+    ) {}
 
     public function getIconIdentifier(): string
     {
@@ -98,8 +96,11 @@ class PreviewModule extends AbstractModule implements RequestEnricherInterface,
         ];
         if ($this->config['showFluidDebug']) {
             // forcibly unset fluid caching as it does not care about the tsfe based caching settings
+            // @todo: Useless?! CacheManager is initialized via bootstrap already, TYPO3_CONF_VARS is not read again?
             unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['fluid_template']['frontend']);
-            $request = $request->withAttribute('noCache', true);
+            $cacheInstruction = $request->getAttribute('frontend.cache.instruction', new CacheInstruction());
+            $cacheInstruction->disableCache('EXT:adminpanel: "Show fluid debug output" disables cache.');
+            $request = $request->withAttribute('frontend.cache.instruction', $cacheInstruction);
         }
         $this->initializeFrontendPreview(
             $this->config['showHiddenPages'],
diff --git a/typo3/sysext/adminpanel/Classes/Modules/TsDebug/TypoScriptWaterfall.php b/typo3/sysext/adminpanel/Classes/Modules/TsDebug/TypoScriptWaterfall.php
index 50cc86c43b86273b26a64f3d64e20f8261d7edaf..1add7bd0c289e3812266cab6a72d3424bb10f3fe 100644
--- a/typo3/sysext/adminpanel/Classes/Modules/TsDebug/TypoScriptWaterfall.php
+++ b/typo3/sysext/adminpanel/Classes/Modules/TsDebug/TypoScriptWaterfall.php
@@ -26,6 +26,7 @@ use TYPO3\CMS\Adminpanel\Service\ConfigurationService;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
+use TYPO3\CMS\Frontend\Cache\CacheInstruction;
 
 /**
  * Class TypoScriptWaterfall
@@ -53,7 +54,9 @@ class TypoScriptWaterfall extends AbstractSubModule implements RequestEnricherIn
     public function enrich(ServerRequestInterface $request): ServerRequestInterface
     {
         if ($this->getConfigurationOption('forceTemplateParsing')) {
-            $request = $request->withAttribute('noCache', true);
+            $cacheInstruction = $request->getAttribute('frontend.cache.instruction', new CacheInstruction());
+            $cacheInstruction->disableCache('EXT:adminpanel: "Force TS rendering" disables cache.');
+            $request = $request->withAttribute('frontend.cache.instruction', $cacheInstruction);
         }
         $this->getTimeTracker()->LR = $this->getConfigurationOption('LR');
         return $request;
diff --git a/typo3/sysext/adminpanel/Resources/Private/Templates/Modules/Info/General.html b/typo3/sysext/adminpanel/Resources/Private/Templates/Modules/Info/General.html
index e3f8b1cf767b8d20779674246accadea780809a9..6ce5aa7b025e41fe735efe8d0ab4cd28df6dd1ed 100644
--- a/typo3/sysext/adminpanel/Resources/Private/Templates/Modules/Info/General.html
+++ b/typo3/sysext/adminpanel/Resources/Private/Templates/Modules/Info/General.html
@@ -74,6 +74,10 @@
     \'LLL:EXT:adminpanel/Resources/Private/Language/locallang_info.xlf:noCache\': isCachedInfo
     }'}" debug="false"/>
 
+<f:if condition="{info.noCache}">
+    <f:render partial="Data/TableKeyValue" arguments="{label: 'Disabled Cache reasons', languageKey: languageKey, data: info.noCacheReasons}" debug="false"/>
+</f:if>
+
 <f:render partial="Data/TableKeyValue" arguments="{label: 'UserIntObjects', languageKey: languageKey, data: '{
     \'LLL:EXT:adminpanel/Resources/Private/Language/locallang_info.xlf:countUserInt\': info.countUserInt
     }'}" debug="false"/>
diff --git a/typo3/sysext/adminpanel/Tests/Unit/Modules/PreviewModuleTest.php b/typo3/sysext/adminpanel/Tests/Unit/Modules/PreviewModuleTest.php
index 1b6e361eab4af17783e29c7f515c0dd7e64891f7..0e1e539ec5e549b1ee6f2b936b96f1b077d9a291 100644
--- a/typo3/sysext/adminpanel/Tests/Unit/Modules/PreviewModuleTest.php
+++ b/typo3/sysext/adminpanel/Tests/Unit/Modules/PreviewModuleTest.php
@@ -19,6 +19,7 @@ namespace TYPO3\CMS\Adminpanel\Tests\Unit\Modules;
 
 use TYPO3\CMS\Adminpanel\Modules\PreviewModule;
 use TYPO3\CMS\Adminpanel\Service\ConfigurationService;
+use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Http\ServerRequest;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -63,7 +64,7 @@ final class PreviewModuleTest extends UnitTestCase
         ];
         $configurationService->method('getConfigurationOption')->withAnyParameters()->willReturnMap($valueMap);
 
-        $previewModule = new PreviewModule();
+        $previewModule = new PreviewModule($this->createMock(CacheManager::class));
         $previewModule->injectConfigurationService($configurationService);
         $previewModule->enrich(new ServerRequest());
 
@@ -102,7 +103,7 @@ final class PreviewModuleTest extends UnitTestCase
             });
         GeneralUtility::setSingletonInstance(Context::class, $context);
 
-        $previewModule = new PreviewModule();
+        $previewModule = new PreviewModule($this->createMock(CacheManager::class));
         $previewModule->injectConfigurationService($configurationService);
         $previewModule->enrich($request);
     }
diff --git a/typo3/sysext/core/Documentation/Changelog/13.0/Breaking-102621-MostTSFEMembersMarkedInternalOrRead-only.rst b/typo3/sysext/core/Documentation/Changelog/13.0/Breaking-102621-MostTSFEMembersMarkedInternalOrRead-only.rst
index 4a903d722065ddd723a4964b808200999d39e31b..d100bf6f466df6dcc713fe6b5de9867e136d5037 100644
--- a/typo3/sysext/core/Documentation/Changelog/13.0/Breaking-102621-MostTSFEMembersMarkedInternalOrRead-only.rst
+++ b/typo3/sysext/core/Documentation/Changelog/13.0/Breaking-102621-MostTSFEMembersMarkedInternalOrRead-only.rst
@@ -59,6 +59,7 @@ The following public class properties have been marked "read only":
 The following public class properties have been marked :php:`@internal` - in general
 all properties not listed above:
 
+* :php:`TypoScriptFrontendController->no_cache` - Use Request attribute :php:`frontend.cache.instruction` instead
 * :php:`TypoScriptFrontendController->additionalHeaderData`
 * :php:`TypoScriptFrontendController->additionalFooterData`
 * :php:`TypoScriptFrontendController->register`
diff --git a/typo3/sysext/core/Documentation/Changelog/13.0/Feature-102628-CacheInstructionMiddleware.rst b/typo3/sysext/core/Documentation/Changelog/13.0/Feature-102628-CacheInstructionMiddleware.rst
new file mode 100644
index 0000000000000000000000000000000000000000..45b33153b41d7210d42f1bb27a11515f5b737c5b
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/13.0/Feature-102628-CacheInstructionMiddleware.rst
@@ -0,0 +1,42 @@
+.. include:: /Includes.rst.txt
+
+.. _feature-102628-1702031683:
+
+===============================================
+Feature: #102628 - Cache instruction middleware
+===============================================
+
+See :issue:`102628`
+
+Description
+===========
+
+TYPO3 v13 introduces the new Frontend related Request attribute :php`frontend.cache.instruction`
+implemented by class :php:`TYPO3\CMS\Frontend\Cache\CacheInstruction`. This replaces the
+previous :php:`TyposcriptFrontendController->no_cache` property and boolean php:`noCache` Request
+attribute.
+
+Impact
+======
+
+The attribute can be used by middlewares to disable cache mechanics of the Frontend rendering.
+
+In early middlewares before :php:`typo3/cms-frontend/tsfe`, the attribute may or may not exist
+already. A safe way to interact with it is like this:
+
+.. code-block:: php
+
+    $cacheInstruction = $request->getAttribute('frontend.cache.instruction', new CacheInstruction());
+    $cacheInstruction->disableCache('EXT:my-extension: My-reason disables caches.');
+    $request = $request->withAttribute('frontend.cache.instruction', $cacheInstruction);
+
+Extension with middlewares or other code after :php:`typo3/cms-frontend/tsfe` can assume the attribute to
+be set already. Usage example:
+
+.. code-block:: php
+
+    $cacheInstruction = $request->getAttribute('frontend.cache.instruction');
+    $cacheInstruction->disableCache('EXT:my-extension: My-reason disables caches.');
+
+
+.. index:: Frontend, PHP-API, ext:frontend
diff --git a/typo3/sysext/frontend/Classes/Cache/CacheInstruction.php b/typo3/sysext/frontend/Classes/Cache/CacheInstruction.php
new file mode 100644
index 0000000000000000000000000000000000000000..87c2338233f83dce2cbf5db045912e5db1b7addd
--- /dev/null
+++ b/typo3/sysext/frontend/Classes/Cache/CacheInstruction.php
@@ -0,0 +1,71 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Cache;
+
+/**
+ * This class contains cache details and is created or updated in middlewares of the
+ * Frontend rendering chain and added as Request attribute "frontend.cache.instruction".
+ *
+ * Its main goal is to *disable* the Frontend cache mechanisms in various scenarios, for
+ * instance when the admin panel is used to simulate access times, or when security
+ * mechanisms like cHash evaluation do not match.
+ */
+final class CacheInstruction
+{
+    private bool $allowCaching = true;
+    private array $disabledCacheReasons = [];
+
+    /**
+     * Instruct the core Frontend rendering to disable Frontend caching. Extensions with
+     * custom middlewares may set this.
+     *
+     * Note multiple cache layers are involved during Frontend rendering: For instance multiple
+     * TypoScript layers, the page cache and potentially others. Those caches are read from and
+     * written to within various middlewares. Depending on the position of a call to this method
+     * within the middleware stack, it can happen that some or all caches have already been
+     * read of written.
+     *
+     * Extensions that use this method should keep an eye on their middleware positions in the
+     * stack to estimate the performance impact of this call. It's of course best to not use
+     * the 'disable cache' mechanic at all, but to handle caching properly in extensions.
+     */
+    public function disableCache(string $reason): void
+    {
+        if (empty($reason)) {
+            throw new \RuntimeException(
+                'A non-empty reason must be given to disable cache. At least mention the extension name that triggers it.',
+                1701528694
+            );
+        }
+        $this->allowCaching = false;
+        $this->disabledCacheReasons[] = $reason;
+    }
+
+    public function isCachingAllowed(): bool
+    {
+        return $this->allowCaching;
+    }
+
+    /**
+     * @internal Typically only consumed by extensions like EXT:adminpanel
+     */
+    public function getDisabledCacheReasons(): array
+    {
+        return $this->disabledCacheReasons;
+    }
+}
diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
index 038f0d57c24e4129cc3afc4a71696c2885d104b6..60186e970edfb48a5ab59712eac6c7b6bd757d9a 100644
--- a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
+++ b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
@@ -689,7 +689,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
         }
 
         // Store cache
-        if ($cacheConfiguration !== null && !$this->getTypoScriptFrontendController()->no_cache) {
+        if ($cacheConfiguration !== null && $this->getRequest()->getAttribute('frontend.cache.instruction')->isCachingAllowed()) {
             $key = $this->calculateCacheKey($cacheConfiguration);
             if (!empty($key)) {
                 $cacheFrontend = GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
@@ -5497,7 +5497,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      */
     protected function getFromCache(array $configuration)
     {
-        if ($this->getTypoScriptFrontendController()->no_cache) {
+        if (!$this->getRequest()->getAttribute('frontend.cache.instruction')->isCachingAllowed()) {
             return false;
         }
         $cacheKey = $this->calculateCacheKey($configuration);
diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
index 7bcc7e742f638c75b7f9ca5538289489170806a4..65a6b7991cba38f84e39d1d5190e976444dd1356 100644
--- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
+++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
@@ -74,7 +74,6 @@ use TYPO3\CMS\Core\Utility\HttpUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Utility\PathUtility;
 use TYPO3\CMS\Core\Utility\RootlineUtility;
-use TYPO3\CMS\Frontend\Aspect\PreviewAspect;
 use TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator;
 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
 use TYPO3\CMS\Frontend\Event\AfterCacheableContentIsGeneratedEvent;
@@ -113,14 +112,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     protected SiteLanguage $language;
     protected PageArguments $pageArguments;
 
-    /**
-     * Page will not be cached. Write only TRUE. Never clear value (some other
-     * code might have reasons to set it TRUE).
-     * @var bool
-     * @internal
-     */
-    public $no_cache = false;
-
     /**
      * Rootline of page records all the way to the root.
      *
@@ -399,7 +390,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      */
     public function __construct(Context $context, Site $site, SiteLanguage $siteLanguage, PageArguments $pageArguments)
     {
-        $this->initializeContext($context);
+        $this->context = $context;
         $this->site = $site;
         $this->language = $siteLanguage;
         $this->setPageArguments($pageArguments);
@@ -408,14 +399,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         $this->initCaches();
     }
 
-    private function initializeContext(Context $context): void
-    {
-        $this->context = $context;
-        if (!$this->context->hasAspect('frontend.preview')) {
-            $this->context->setAspect('frontend.preview', GeneralUtility::makeInstance(PreviewAspect::class));
-        }
-    }
-
     protected function initPageRenderer(): void
     {
         if ($this->pageRenderer !== null) {
@@ -870,7 +853,8 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     }
 
     /**
-     * Analysing $this->pageAccessFailureHistory into a summary array telling which features disabled display and on which pages and conditions. That data can be used inside a page-not-found handler
+     * Analysing $this->pageAccessFailureHistory into a summary array telling which features disabled display and on which pages and conditions.
+     * That data can be used inside a page-not-found handler
      *
      * @param string|null $failureReasonCode the error code to be attached (optional), see PageAccessFailureReasons list for details
      * @return array Summary of why page access was not allowed.
@@ -890,7 +874,8 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             $accessVoter = GeneralUtility::makeInstance(RecordAccessVoter::class);
             foreach ($combinedRecords as $k => $pagerec) {
                 // If $k=0 then it is the very first page the original ID was pointing at and that will get a full check of course
-                // If $k>0 it is parent pages being tested. They are only significant for the access to the first page IF they had the extendToSubpages flag set, hence checked only then!
+                // If $k>0 it is parent pages being tested. They are only significant for the access to the first page IF they had the
+                // extendToSubpages flag set, hence checked only then!
                 if (!$k || $pagerec['extendToSubpages']) {
                     if ($pagerec['hidden'] ?? false) {
                         $output['hidden'][$pagerec['uid']] = true;
@@ -1009,6 +994,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         }
 
         $site = $this->getSite();
+        $isCachingAllowed = $request->getAttribute('frontend.cache.instruction')->isCachingAllowed();
 
         $tokenizer = new LossyTokenizer();
         $treeBuilder = GeneralUtility::makeInstance(SysTemplateTreeBuilder::class);
@@ -1017,9 +1003,9 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         $cacheManager = GeneralUtility::makeInstance(CacheManager::class);
         /** @var PhpFrontend|null $typoscriptCache */
         $typoscriptCache = null;
-        if (!$this->no_cache) {
-            // $this->no_cache = true might have been set by earlier TypoScriptFrontendInitialization middleware.
-            // This means we don't do fancy cache stuff, calculate full TypoScript and ignore page cache.
+        if ($isCachingAllowed) {
+            // disableCache() might have been called by earlier middlewares. This means we don't do fancy cache
+            // stuff, calculate full TypoScript and don't get() from nor set() to typoscript and page cache.
             /** @var PhpFrontend|null $typoscriptCache */
             $typoscriptCache = $cacheManager->getCache('typoscript');
         }
@@ -1048,7 +1034,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         $flatConstants = [];
         $serializedConstantConditionList = '';
         $gotConstantFromCache = false;
-        if (!$this->no_cache && $constantConditionIncludeTree = $typoscriptCache->require($constantConditionIncludeListCacheIdentifier)) {
+        if ($isCachingAllowed && $constantConditionIncludeTree = $typoscriptCache->require($constantConditionIncludeListCacheIdentifier)) {
             // We got the flat list of all constants conditions for this TypoScript combination from cache. Good. We traverse
             // this list to calculate "current" condition verdicts. With a hash of this list together with a hash of the
             // TypoScript sys_templates, we try to retrieve the full constants TypoScript from cache.
@@ -1068,12 +1054,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                 $gotConstantFromCache = true;
             }
         }
-        if ($this->no_cache || !$gotConstantFromCache) {
+        if (!$isCachingAllowed || !$gotConstantFromCache) {
             // We did not get constants from cache, or are not allowed to use cache. We have to build constants from scratch.
             // This means we'll fetch the full constants include tree (from cache if possible), register the condition
             // matcher and register the AST builder and traverse include tree to retrieve constants AST and calculate
             // 'flat constants' from it. Both are cached if allowed afterwards for the 'if' above to kick in next time.
-            // $typoscriptCache can be null here with no_cache=1.
             $constantIncludeTree = $treeBuilder->getTreeBySysTemplateRowsAndSite('constants', $sysTemplateRows, $tokenizer, $site, $typoscriptCache);
             $conditionMatcherVisitor = GeneralUtility::makeInstance(IncludeTreeConditionMatcherVisitor::class);
             $conditionMatcherVisitor->initializeExpressionMatcherWithVariables($expressionMatcherVariables);
@@ -1085,12 +1070,12 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             // children for not matching conditions, which is important to create the correct AST.
             $includeTreeTraverserConditionVerdictAware->traverse($constantIncludeTree, $includeTreeTraverserConditionVerdictAwareVisitors);
             $constantsAst = $constantAstBuilderVisitor->getAst();
-            // @internal Dispatch and experimental event allowing listeners to still change the constants AST,
+            // @internal Dispatch an experimental event allowing listeners to still change the constants AST,
             //           to for instance implement nested constants if really needed. Note this event may change
             //           or vanish later without further notice.
             $constantsAst = GeneralUtility::makeInstance(EventDispatcherInterface::class)->dispatch(new ModifyTypoScriptConstantsEvent($constantsAst))->getConstantsAst();
             $flatConstants = $constantsAst->flatten();
-            if (!$this->no_cache) {
+            if ($isCachingAllowed) {
                 // We are allowed to cache and can create both the full list of conditions, plus the constant AST and flat constant
                 // list cache entry. To do that, we need all (!) conditions, but the above ConditionVerdictAwareIncludeTreeTraverser
                 // did not find nested conditions if an upper condition did not match. We thus have to traverse include tree a
@@ -1121,7 +1106,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         $setupConditionIncludeListCacheIdentifier = 'setup-condition-include-list-' . sha1($serializedSysTemplateRows . $serializedConstantConditionList);
         $setupConditionList = [];
         $gotSetupConditionsFromCache = false;
-        if (!$this->no_cache && $setupConditionIncludeTree = $typoscriptCache->require($setupConditionIncludeListCacheIdentifier)) {
+        if ($isCachingAllowed && $setupConditionIncludeTree = $typoscriptCache->require($setupConditionIncludeListCacheIdentifier)) {
             // We got the flat list of all setup conditions for this TypoScript combination from cache. Good. We traverse
             // this list to calculate "current" condition verdicts, which we need as hash to be part of page cache identifier.
             $includeTreeTraverserVisitors = [];
@@ -1138,12 +1123,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             $gotSetupConditionsFromCache = true;
         }
         $setupIncludeTree = null;
-        if ($this->no_cache || !$gotSetupConditionsFromCache) {
+        if (!$isCachingAllowed || !$gotSetupConditionsFromCache) {
             // We did not get setup condition list from cache, or are not allowed to use cache. We have to build setup
             // condition list from scratch. This means we'll fetch the full setup include tree (from cache if possible),
             // register the constant substitution visitor, and register condition matcher and register the condition
             // accumulator visitor.
-            // $typoscriptCache can be null here with no_cache=1.
             $setupIncludeTree = $treeBuilder->getTreeBySysTemplateRowsAndSite('setup', $sysTemplateRows, $tokenizer, $site, $typoscriptCache);
             $includeTreeTraverserVisitors = [];
             $setupConditionConstantSubstitutionVisitor = new IncludeTreeSetupConditionConstantSubstitutionVisitor();
@@ -1167,7 +1151,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         // obviously the page id.
         $this->lock = GeneralUtility::makeInstance(ResourceMutex::class);
         $this->newHash = $this->createHashBase($sysTemplateRows, $constantConditionList, $setupConditionList);
-        if (!$this->no_cache) {
+        if ($isCachingAllowed) {
             if ($this->shouldAcquireCacheData($request)) {
                 // Try to get a page cache row.
                 $this->getTimeTracker()->push('Cache Row');
@@ -1213,16 +1197,15 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             $this->lock->acquireLock('pages', $this->newHash);
         }
 
-        if ($this->no_cache || empty($this->config) || $this->isINTincScript()) {
-            // We don't need the full setup AST in many cached scenarios. However, if no_cache is set, if no page cache
-            // entry could be loaded, if the page cache entry has _INT object, or if the user forced template
-            // parsing (adminpanel), then we still need the full setup AST. If there is "just" an _INT object, we can
-            // use a possible cache entry for the setup AST, which speeds up _INT parsing quite a bit. In other cases
-            // we calculate full setup AST and cache it if allowed.
+        if (!$isCachingAllowed || empty($this->config) || $this->isINTincScript()) {
+            // We don't need the full setup AST in many cached scenarios. However, if caching is not allowed, if no page
+            // cache entry could be loaded or if the page cache entry has _INT object, then we still need the full setup AST.
+            // If there is "just" an _INT object, we can use a possible cache entry for the setup AST, which speeds up _INT
+            // parsing quite a bit. In other cases we calculate full setup AST and cache it if allowed.
             $setupTypoScriptCacheIdentifier = 'setup-' . sha1($serializedSysTemplateRows . $serializedConstantConditionList . serialize($setupConditionList));
             $gotSetupFromCache = false;
             $setupArray = [];
-            if (!$this->no_cache) {
+            if ($isCachingAllowed) {
                 // We need AST, but we are allowed to potentially get it from cache.
                 if ($setupTypoScriptCache = $typoscriptCache->require($setupTypoScriptCacheIdentifier)) {
                     $frontendTypoScript->setSetupTree($setupTypoScriptCache['ast']);
@@ -1230,11 +1213,10 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                     $gotSetupFromCache = true;
                 }
             }
-            if ($this->no_cache || !$gotSetupFromCache) {
+            if (!$isCachingAllowed || !$gotSetupFromCache) {
                 // We need AST and couldn't get it from cache or are now allowed to. We thus need the full setup
                 // IncludeTree, which we can get from cache again if allowed, or is calculated a-new if not.
-                if ($this->no_cache || $setupIncludeTree === null) {
-                    // $typoscriptCache can be null here with no_cache=1.
+                if (!$isCachingAllowed || $setupIncludeTree === null) {
                     $setupIncludeTree = $treeBuilder->getTreeBySysTemplateRowsAndSite('setup', $sysTemplateRows, $tokenizer, $site, $typoscriptCache);
                 }
                 $includeTreeTraverserConditionVerdictAwareVisitors = [];
@@ -1279,7 +1261,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                     $setupAst->addChild($typesNode);
                 }
                 $setupArray = $setupAst->toArray();
-                if (!$this->no_cache) {
+                if ($isCachingAllowed) {
                     // Write cache entry for AST and its array representation, we're allowed to do it.
                     $typoscriptCache->set($setupTypoScriptCacheIdentifier, 'return unserialize(\'' . addcslashes(serialize(['ast' => $setupAst, 'array' => $setupArray]), '\'\\') . '\');');
                 }
@@ -1322,9 +1304,10 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             $frontendTypoScript->setSetupArray($setupArray);
         }
 
-        // Set $this->no_cache TRUE if the config.no_cache value is set!
-        if (!$this->no_cache && ($this->config['config']['no_cache'] ?? false)) {
-            $this->set_no_cache('config.no_cache is set', true);
+        // Disable cache if config.no_cache is set!
+        if ($this->config['config']['no_cache'] ?? false) {
+            $cacheInstruction = $request->getAttribute('frontend.cache.instruction');
+            $cacheInstruction->disableCache('EXT:frontend: Disabled cache due to TypoScript "config.no_cache = 1"');
         }
 
         // Auto-configure settings when a site is configured
@@ -1389,8 +1372,8 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      */
     protected function shouldAcquireCacheData(ServerRequestInterface $request): bool
     {
-        // Trigger event for possible by-pass of requiring of page cache (for re-caching purposes)
-        $event = new ShouldUseCachedPageDataIfAvailableEvent($request, $this, !$this->no_cache);
+        // Trigger event for possible by-pass of requiring of page cache.
+        $event = new ShouldUseCachedPageDataIfAvailableEvent($request, $this, $request->getAttribute('frontend.cache.instruction')->isCachingAllowed());
         GeneralUtility::makeInstance(EventDispatcherInterface::class)->dispatch($event);
         return $event->shouldUseCachedPageData();
     }
@@ -1812,7 +1795,8 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     {
         $this->setAbsRefPrefix();
         $eventDispatcher = GeneralUtility::makeInstance(EventDispatcherInterface::class);
-        $event = new AfterCacheableContentIsGeneratedEvent($request, $this, $this->newHash, !$this->no_cache);
+        $usePageCache = $request->getAttribute('frontend.cache.instruction')->isCachingAllowed();
+        $event = new AfterCacheableContentIsGeneratedEvent($request, $this, $this->newHash, $usePageCache);
         $event = $eventDispatcher->dispatch($event);
 
         // Processing if caching is enabled
@@ -2070,7 +2054,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      *
      * @internal
      */
-    public function applyHttpHeadersToResponse(ResponseInterface $response): ResponseInterface
+    public function applyHttpHeadersToResponse(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
     {
         $response = $response->withHeader('Content-Type', $this->contentType);
         // Set header for content language unless disabled
@@ -2085,7 +2069,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         }
 
         // Set cache related headers to client (used to enable proxy / client caching!)
-        $headers = $this->getCacheHeaders();
+        $headers = $this->getCacheHeaders($request);
         foreach ($headers as $header => $value) {
             $response = $response->withHeader($header, $value);
         }
@@ -2108,11 +2092,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     /**
      * Get cache headers good for client/reverse proxy caching.
      */
-    protected function getCacheHeaders(): array
+    protected function getCacheHeaders(ServerRequestInterface $request): array
     {
         $headers = [];
         // Getting status whether we can send cache control headers for proxy caching:
-        $doCache = $this->isStaticCacheble();
+        $doCache = $this->isStaticCacheble($request);
         $isBackendUserLoggedIn = $this->context->getPropertyFromAspect('backend.user', 'isLoggedIn', false);
         $isInWorkspace = $this->context->getPropertyFromAspect('workspace', 'isOffline', false);
         // Finally, when backend users are logged in, do not send cache headers at all (Admin Panel might be displayed for instance).
@@ -2139,8 +2123,8 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                     $this->getTimeTracker()->setTSlogMessage('Cache-headers with max-age "' . ($this->cacheExpires - $GLOBALS['EXEC_TIME']) . '" would have been sent');
                 } else {
                     $reasonMsg = [];
-                    if ($this->no_cache) {
-                        $reasonMsg[] = 'Caching disabled (no_cache).';
+                    if (!$request->getAttribute('frontend.cache.instruction')->isCachingAllowed()) {
+                        $reasonMsg[] = 'Caching disabled.';
                     }
                     if ($this->isINTincScript()) {
                         $reasonMsg[] = '*_INT object(s) on page.';
@@ -2169,9 +2153,10 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      *
      * @internal
      */
-    public function isStaticCacheble(): bool
+    public function isStaticCacheble(ServerRequestInterface $request): bool
     {
-        return !$this->no_cache && !$this->isINTincScript() && !$this->context->getAspect('frontend.user')->isUserOrGroupSet();
+        $isCachingAllowed = $request->getAttribute('frontend.cache.instruction')->isCachingAllowed();
+        return $isCachingAllowed && !$this->isINTincScript() && !$this->context->getAspect('frontend.user')->isUserOrGroupSet();
     }
 
     /**
@@ -2258,9 +2243,9 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      * Sets the cache-flag to 1. Could be called from user-included php-files in order to ensure that a page is not cached.
      *
      * @param string $reason An optional reason to be written to the log.
-     * @param bool $internalRequest Whether the request is internal or not (true should only be used by core calls).
+     * @todo: deprecate
      */
-    public function set_no_cache(string $reason = '', bool $internalRequest = false): void
+    public function set_no_cache(string $reason = ''): void
     {
         $warning = '';
         $context = [];
@@ -2284,24 +2269,19 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             }
             $context['line'] = $trace[0]['line'];
         }
-        if (!$internalRequest && $GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter']) {
+        if ($GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter']) {
             $warning .= ' However, $TYPO3_CONF_VARS[\'FE\'][\'disableNoCacheParameter\'] is set, so it will be ignored!';
             $this->getTimeTracker()->setTSlogMessage($warning, LogLevel::NOTICE);
         } else {
             $warning .= ' Caching is disabled!';
-            $this->disableCache();
+            /** @var ServerRequestInterface $request */
+            $request = $GLOBALS['TYPO3_REQUEST'];
+            $cacheInstruction = $request->getAttribute('frontend.cache.instruction');
+            $cacheInstruction->disableCache('EXT:frontend: Caching disabled using deprecated set_no_cache().');
         }
         $this->logger->notice($warning, $context);
     }
 
-    /**
-     * Disables caching of the current page.
-     */
-    protected function disableCache(): void
-    {
-        $this->no_cache = true;
-    }
-
     /**
      * Sets the cache-timeout in seconds
      *
diff --git a/typo3/sysext/frontend/Classes/Event/AfterCacheableContentIsGeneratedEvent.php b/typo3/sysext/frontend/Classes/Event/AfterCacheableContentIsGeneratedEvent.php
index 77a34659fc6370932220abe22dca68f14f833810..22c89be2f0ebe36d22d4162e79715863eb8173ce 100644
--- a/typo3/sysext/frontend/Classes/Event/AfterCacheableContentIsGeneratedEvent.php
+++ b/typo3/sysext/frontend/Classes/Event/AfterCacheableContentIsGeneratedEvent.php
@@ -21,7 +21,7 @@ use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
 /**
- * Event that allows to enhance or change content (also depending if caching is enabled).
+ * Event that allows to enhance or change content (also depending on enabled caching).
  * Think of $this->isCachingEnabled() as the same as $TSFE->no_cache.
  * Depending on disable or enabling caching, the cache is then not stored in the pageCache.
  */
diff --git a/typo3/sysext/frontend/Classes/Event/AfterCachedPageIsPersistedEvent.php b/typo3/sysext/frontend/Classes/Event/AfterCachedPageIsPersistedEvent.php
index 18543c1a6a03131b954250481b65cacab2527af8..fd77aded595dc245c1039a40042b00449a88c89a 100644
--- a/typo3/sysext/frontend/Classes/Event/AfterCachedPageIsPersistedEvent.php
+++ b/typo3/sysext/frontend/Classes/Event/AfterCachedPageIsPersistedEvent.php
@@ -21,12 +21,12 @@ use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
 /**
- * Event that is used directly after all cached content is stored in
- * the page cache.
+ * Event that is used directly after all cached content is stored in the page cache.
  *
- * If a page is called from the cache, this event is NOT fired.
- * This event is also NOT FIRED when $TSFE->no_cache (or manipulated via AfterCacheableContentIsGeneratedEvent)
- * is set.
+ * NOT fired, if:
+ * * A page is called from the cache
+ * * Caching is disabled using 'frontend.cache.instruction' request attribute, which can
+ *   be set by various middlewares or AfterCacheableContentIsGeneratedEvent
  */
 final class AfterCachedPageIsPersistedEvent
 {
diff --git a/typo3/sysext/frontend/Classes/Event/ShouldUseCachedPageDataIfAvailableEvent.php b/typo3/sysext/frontend/Classes/Event/ShouldUseCachedPageDataIfAvailableEvent.php
index ba58f88dab7af601d707d14e5cb47d54d155f3b1..b5602b3d7f2db76b3d94325d71a38cc790f85f8c 100644
--- a/typo3/sysext/frontend/Classes/Event/ShouldUseCachedPageDataIfAvailableEvent.php
+++ b/typo3/sysext/frontend/Classes/Event/ShouldUseCachedPageDataIfAvailableEvent.php
@@ -22,7 +22,7 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
 /**
  * Event to allow listeners to disable the loading of cached page data when a page is requested.
- * Does not have any effect if "no_cache" is activated, or if there is no cached version of a page.
+ * Does not have any effect if caching is disabled, or if there is no cached version of a page.
  */
 final class ShouldUseCachedPageDataIfAvailableEvent
 {
diff --git a/typo3/sysext/frontend/Classes/Http/RequestHandler.php b/typo3/sysext/frontend/Classes/Http/RequestHandler.php
index 4624459a6264087137b4499e06f298df07f1b217..84618a74ba4e14acd76434e5a4c080b9f3b28fb9 100644
--- a/typo3/sysext/frontend/Classes/Http/RequestHandler.php
+++ b/typo3/sysext/frontend/Classes/Http/RequestHandler.php
@@ -179,7 +179,7 @@ class RequestHandler implements RequestHandlerInterface
 
         // Create a default Response object and add headers and body to it
         $response = new Response();
-        $response = $controller->applyHttpHeadersToResponse($response);
+        $response = $controller->applyHttpHeadersToResponse($request, $response);
         $this->displayPreviewInfoMessage($controller);
         $response->getBody()->write($controller->content);
         return $response;
diff --git a/typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php b/typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php
index dd2ee67f000ab2fffbb9e9e60f8eb84104fbe163..039b06befcff207a37f846bcd1ddbe4a3d980f5b 100644
--- a/typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php
+++ b/typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php
@@ -28,6 +28,7 @@ use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Http\NormalizedParams;
 use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Cache\CacheInstruction;
 
 /**
  * This middleware authenticates a Backend User (be_user) (pre)-viewing a frontend page.
@@ -70,9 +71,11 @@ class BackendUserAuthenticator extends \TYPO3\CMS\Core\Middleware\BackendUserAut
                 && (strtolower($request->getServerParams()['HTTP_CACHE_CONTROL'] ?? '') === 'no-cache'
                     || strtolower($request->getServerParams()['HTTP_PRAGMA'] ?? '') === 'no-cache')
             ) {
-                // Detecting if shift-reload has been clicked to set noCache attribute if so.
+                // Detecting if shift-reload has been clicked to disable caching if so.
                 // This is only done if a backend user is logged in to prevent DoS-attacks for "casual" requests.
-                $request = $request->withAttribute('noCache', true);
+                $cacheInstruction = $request->getAttribute('frontend.cache.instruction', new CacheInstruction());
+                $cacheInstruction->disableCache('EXT:frontend: Logged in backend user forced reload disabled cache.');
+                $request = $request->withAttribute('frontend.cache.instruction', $cacheInstruction);
             }
         }
 
diff --git a/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php b/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php
index 3d2b91b3b63415600cd9b9dcd7afb8ba37a8e313..ee2b25c46539eb9015cbfdab25ac4419797c9213 100644
--- a/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php
+++ b/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php
@@ -23,12 +23,11 @@ use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
-use Psr\Log\LogLevel;
 use TYPO3\CMS\Core\Http\RedirectResponse;
 use TYPO3\CMS\Core\Routing\PageArguments;
-use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\HttpUtility;
+use TYPO3\CMS\Frontend\Cache\CacheInstruction;
 use TYPO3\CMS\Frontend\Controller\ErrorController;
 use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
 use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
@@ -40,14 +39,8 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
 {
     use LoggerAwareTrait;
 
-    /**
-     * @var bool will be used to set $TSFE->no_cache later-on
-     */
-    protected bool $disableCache = false;
-
     public function __construct(
-        protected readonly CacheHashCalculator $cacheHashCalculator,
-        protected readonly TimeTracker $timeTracker
+        private readonly CacheHashCalculator $cacheHashCalculator,
     ) {}
 
     /**
@@ -55,10 +48,10 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
      */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
-        $this->disableCache = (bool)$request->getAttribute('noCache', false);
+        $cacheInstruction = $request->getAttribute('frontend.cache.instruction', new CacheInstruction());
+        $request = $request->withAttribute('frontend.cache.instruction', $cacheInstruction);
         $pageNotFoundOnValidationError = (bool)($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError'] ?? true);
-        /** @var PageArguments $pageArguments */
-        $pageArguments = $request->getAttribute('routing', null);
+        $pageArguments = $request->getAttribute('routing');
         if (!($pageArguments instanceof PageArguments)) {
             // Page Arguments must be set in order to validate. This middleware only works if PageArguments
             // is available, and is usually combined with the Page Resolver middleware
@@ -68,12 +61,12 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
                 ['code' => PageAccessFailureReasons::INVALID_PAGE_ARGUMENTS]
             );
         }
-        if ($GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter'] ?? true) {
-            $cachingDisabledByRequest = false;
-        } else {
-            $cachingDisabledByRequest = $pageArguments->getArguments()['no_cache'] ?? $request->getParsedBody()['no_cache'] ?? false;
+        if (!($GLOBALS['TYPO3_CONF_VARS']['FE']['disableNoCacheParameter'] ?? true)
+            && ($pageArguments->getArguments()['no_cache'] ?? $request->getParsedBody()['no_cache'] ?? false)
+        ) {
+            $cacheInstruction->disableCache('EXT:frontend: Caching disabled by no_cache query argument.');
         }
-        if (($cachingDisabledByRequest || $this->disableCache) && !$pageNotFoundOnValidationError) {
+        if (!$cacheInstruction->isCachingAllowed() && !$pageNotFoundOnValidationError) {
             // No need to test anything if caching was already disabled.
             return $handler->handle($request);
         }
@@ -84,15 +77,14 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
             $relevantParametersForCacheHashArgument = $this->getRelevantParametersForCacheHashCalculation($pageArguments);
             if ($cHash !== '') {
                 if (empty($relevantParametersForCacheHashArgument)) {
-                    // cHash was given, but nothing to be calculated, so let's do a redirect to the current page
-                    // but without the cHash
+                    // cHash was given, but nothing to be calculated, so let's do a redirect to the current page but without the cHash
                     $this->logger->notice('The incoming cHash "{hash}" is given but not needed. cHash is unset', ['hash' => $cHash]);
                     $uri = $request->getUri();
                     unset($queryParams['cHash']);
                     $uri = $uri->withQuery(HttpUtility::buildQueryString($queryParams));
                     return new RedirectResponse($uri, 308);
                 }
-                if (!$this->evaluateCacheHashParameter($cHash, $relevantParametersForCacheHashArgument, $pageNotFoundOnValidationError)) {
+                if (!$this->evaluateCacheHashParameter($cacheInstruction, $cHash, $relevantParametersForCacheHashArgument, $pageNotFoundOnValidationError)) {
                     return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
                         $request,
                         'Request parameters could not be validated (&cHash comparison failed)',
@@ -100,7 +92,7 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
                     );
                 }
             // No cHash given but was required
-            } elseif (!$this->evaluatePageArgumentsWithoutCacheHash($pageArguments, $pageNotFoundOnValidationError)) {
+            } elseif (!$this->evaluatePageArgumentsWithoutCacheHash($cacheInstruction, $pageArguments, $pageNotFoundOnValidationError)) {
                 return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
                     $request,
                     'Request parameters could not be validated (&cHash empty)',
@@ -109,7 +101,6 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
             }
         }
 
-        $request = $request->withAttribute('noCache', $this->disableCache);
         return $handler->handle($request);
     }
 
@@ -134,7 +125,7 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
      * @param bool $pageNotFoundOnCacheHashError see $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError']
      * @return bool if false, then a PageNotFound response is triggered
      */
-    protected function evaluateCacheHashParameter(string $cHash, array $relevantParameters, bool $pageNotFoundOnCacheHashError): bool
+    protected function evaluateCacheHashParameter(CacheInstruction $cacheInstruction, string $cHash, array $relevantParameters, bool $pageNotFoundOnCacheHashError): bool
     {
         $calculatedCacheHash = $this->cacheHashCalculator->calculateCacheHash($relevantParameters);
         if (hash_equals($calculatedCacheHash, $cHash)) {
@@ -145,8 +136,8 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
             return false;
         }
         // Caching is disabled now (but no 404)
-        $this->disableCache = true;
-        $this->timeTracker->setTSlogMessage('The incoming cHash "' . $cHash . '" and calculated cHash "' . $calculatedCacheHash . '" did not match, so caching was disabled. The fieldlist used was "' . implode(',', array_keys($relevantParameters)) . '"', LogLevel::ERROR);
+        $cacheInstruction->disableCache('EXT:frontend: Incoming cHash "' . $cHash . '" and calculated cHash "' . $calculatedCacheHash . '" did not match.' .
+            ' The field list used was "' . implode(',', array_keys($relevantParameters)) . '". Caching is disabled.');
         return true;
     }
 
@@ -156,9 +147,8 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
      * Should only be called if NO cHash parameter is given.
      *
      * @param array<string, string|array> $dynamicArguments
-     * @param bool $pageNotFoundOnCacheHashError
      */
-    protected function evaluateQueryParametersWithoutCacheHash(array $dynamicArguments, bool $pageNotFoundOnCacheHashError): bool
+    protected function evaluateQueryParametersWithoutCacheHash(CacheInstruction $cacheInstruction, array $dynamicArguments, bool $pageNotFoundOnCacheHashError): bool
     {
         if (!$this->cacheHashCalculator->doParametersRequireCacheHash(HttpUtility::buildQueryString($dynamicArguments))) {
             return true;
@@ -168,8 +158,7 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
             return false;
         }
         // Caching is disabled now (but no 404)
-        $this->disableCache = true;
-        $this->timeTracker->setTSlogMessage('TSFE->reqCHash(): No &cHash parameter was sent for GET vars though required so caching is disabled', LogLevel::ERROR);
+        $cacheInstruction->disableCache('EXT:frontend: No cHash query argument was sent for GET vars though required. Caching is disabled.');
         return true;
     }
 
@@ -179,11 +168,11 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
      *
      * Is only called if NO cHash parameter is given.
      */
-    protected function evaluatePageArgumentsWithoutCacheHash(PageArguments $pageArguments, bool $pageNotFoundOnCacheHashError): bool
+    protected function evaluatePageArgumentsWithoutCacheHash(CacheInstruction $cacheInstruction, PageArguments $pageArguments, bool $pageNotFoundOnCacheHashError): bool
     {
         // legacy behaviour
         if (!($GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['enforceValidation'] ?? false)) {
-            return $this->evaluateQueryParametersWithoutCacheHash($pageArguments->getDynamicArguments(), $pageNotFoundOnCacheHashError);
+            return $this->evaluateQueryParametersWithoutCacheHash($cacheInstruction, $pageArguments->getDynamicArguments(), $pageNotFoundOnCacheHashError);
         }
         $relevantParameters = $this->getRelevantParametersForCacheHashCalculation($pageArguments);
         // There are parameters that would be needed for the current page, but no cHash is given.
@@ -198,8 +187,7 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
             return true;
         }
         // Caching is disabled now (but no 404)
-        $this->disableCache = true;
-        $this->timeTracker->setTSlogMessage('No &cHash parameter was sent for given query parameters, so caching is disabled', LogLevel::ERROR);
+        $cacheInstruction->disableCache('EXT:frontend: No cHash query argument was sent for given query parameters. Caching is disabled');
         return true;
     }
 }
diff --git a/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php b/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
index 9a7b98b01cf387e8563e0db70e725576ece4950d..98ed0694204b1ca7ea87846484fef671cf55dfe7 100644
--- a/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
+++ b/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
@@ -26,6 +26,8 @@ use TYPO3\CMS\Core\Routing\PageArguments;
 use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Aspect\PreviewAspect;
+use TYPO3\CMS\Frontend\Cache\CacheInstruction;
 use TYPO3\CMS\Frontend\Controller\ErrorController;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
@@ -50,10 +52,25 @@ final class TypoScriptFrontendInitialization implements MiddlewareInterface
      */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
+        // The cache information attribute may be set by previous middlewares already. Make sure we have one from now on.
+        $cacheInstruction = $request->getAttribute('frontend.cache.instruction', new CacheInstruction());
+        $request = $request->withAttribute('frontend.cache.instruction', $cacheInstruction);
+
+        // Make sure frontend.preview is given from now on.
+        if (!$this->context->hasAspect('frontend.preview')) {
+            $this->context->setAspect('frontend.preview', new PreviewAspect());
+        }
+        // If the frontend is showing a preview, caching MUST be disabled.
+        if ($this->context->getPropertyFromAspect('frontend.preview', 'isPreview', false)) {
+            // @todo: To disentangle this, the preview aspect could be dropped and middlewares that set isPreview true
+            //        could directly set $cacheInstruction->disableCache() instead.
+            $cacheInstruction->disableCache('EXT:frontend: Disabled cache due to enabled frontend.preview aspect isPreview.');
+        }
+
         $GLOBALS['TYPO3_REQUEST'] = $request;
         /** @var Site $site */
-        $site = $request->getAttribute('site', null);
-        $pageArguments = $request->getAttribute('routing', null);
+        $site = $request->getAttribute('site');
+        $pageArguments = $request->getAttribute('routing');
         if (!$pageArguments instanceof PageArguments) {
             // Page Arguments must be set in order to validate. This middleware only works if PageArguments
             // is available, and is usually combined with the Page Resolver middleware
@@ -71,17 +88,6 @@ final class TypoScriptFrontendInitialization implements MiddlewareInterface
             $request->getAttribute('language', $site->getDefaultLanguage()),
             $pageArguments
         );
-        if ($pageArguments->getArguments()['no_cache'] ?? $request->getParsedBody()['no_cache'] ?? false) {
-            $controller->set_no_cache('&no_cache=1 has been supplied, so caching is disabled! URL: "' . (string)$request->getUri() . '"');
-        }
-        // Usually only set by the PageArgumentValidator
-        if ($request->getAttribute('noCache', false)) {
-            $controller->no_cache = true;
-        }
-        // If the frontend is showing a preview, caching MUST be disabled.
-        if ($this->context->getPropertyFromAspect('frontend.preview', 'isPreview', false)) {
-            $controller->set_no_cache('Preview active', true);
-        }
         $directResponse = $controller->determineId($request);
         if ($directResponse) {
             return $directResponse;
diff --git a/typo3/sysext/frontend/Tests/Functional/Controller/TypoScriptFrontendControllerTest.php b/typo3/sysext/frontend/Tests/Functional/Controller/TypoScriptFrontendControllerTest.php
index b6a77fa28708ea80daf6733899fda5c4ef5076e6..f896a400dce9eded0b9a1ec8fe4b847aeab1a840 100644
--- a/typo3/sysext/frontend/Tests/Functional/Controller/TypoScriptFrontendControllerTest.php
+++ b/typo3/sysext/frontend/Tests/Functional/Controller/TypoScriptFrontendControllerTest.php
@@ -19,6 +19,7 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\Controller;
 
 use TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend;
 use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
+use TYPO3\CMS\Frontend\Cache\CacheInstruction;
 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Internal\TypoScriptInstruction;
 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
@@ -390,7 +391,9 @@ alert(yes);', $body);
 
         $request = (new InternalRequest('https://website.local/en/'))->withPageId($pid);
         if ($nocache) {
-            $request = $request->withAttribute('noCache', true);
+            $cacheInstruction = new CacheInstruction();
+            $cacheInstruction->disableCache('EXT:frontend: Testing disables caching.');
+            $request = $request->withAttribute('frontend.cache.instruction', $cacheInstruction);
         }
         $this->executeFrontendSubRequest($request);
         self::assertSame($expectedRootLine, $GLOBALS['TSFE']->rootLine);
diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
index 9404fe53bfc0a68089678a56c2293d1fb2dc3e02..79107e053ac6474f9e02e5c2bd535af980d2395b 100644
--- a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
+++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
@@ -62,6 +62,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
+use TYPO3\CMS\Frontend\Cache\CacheInstruction;
 use TYPO3\CMS\Frontend\ContentObject\AbstractContentObject;
 use TYPO3\CMS\Frontend\ContentObject\CaseContentObject;
 use TYPO3\CMS\Frontend\ContentObject\ContentContentObject;
@@ -2709,6 +2710,7 @@ final class ContentObjectRendererTest extends UnitTestCase
             ContentObjectRenderer::class,
             [
                 'calculateCacheKey',
+                'getRequest',
                 'getTypoScriptFrontendController',
             ]
         );
@@ -2717,13 +2719,18 @@ final class ContentObjectRendererTest extends UnitTestCase
             ->method('calculateCacheKey')
             ->with($conf)
             ->willReturn($cacheKey);
+        $request = (new ServerRequest())->withAttribute('frontend.cache.instruction', new CacheInstruction());
+        $subject
+            ->expects(self::once())
+            ->method('getRequest')
+            ->willReturn($request);
         $typoScriptFrontendController = $this->createMock(TypoScriptFrontendController::class);
         $typoScriptFrontendController
             ->expects(self::exactly($times))
             ->method('addCacheTags')
             ->with($tags);
         $subject
-            ->expects(self::exactly($times + 1))
+            ->expects(self::exactly($times))
             ->method('getTypoScriptFrontendController')
             ->willReturn($typoScriptFrontendController);
         $cacheFrontend = $this->createMock(CacheFrontendInterface::class);
diff --git a/typo3/sysext/frontend/Tests/Unit/Middleware/PageArgumentValidatorTest.php b/typo3/sysext/frontend/Tests/Unit/Middleware/PageArgumentValidatorTest.php
index 62a473275b0762b66cef235a68931089d60023cd..7810e27eb5dace865f0b9e9d340d362589a1df18 100644
--- a/typo3/sysext/frontend/Tests/Unit/Middleware/PageArgumentValidatorTest.php
+++ b/typo3/sysext/frontend/Tests/Unit/Middleware/PageArgumentValidatorTest.php
@@ -25,29 +25,20 @@ use TYPO3\CMS\Core\Http\Response;
 use TYPO3\CMS\Core\Http\ServerRequest;
 use TYPO3\CMS\Core\Information\Typo3Information;
 use TYPO3\CMS\Core\Routing\PageArguments;
-use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Middleware\PageArgumentValidator;
-use TYPO3\CMS\Frontend\Middleware\PageResolver;
 use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
-use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
 final class PageArgumentValidatorTest extends UnitTestCase
 {
     protected bool $resetSingletonInstances = true;
 
-    protected CacheHashCalculator $cacheHashCalculator;
-    protected TimeTracker $timeTrackerStub;
-    protected RequestHandlerInterface $responseOutputHandler;
-    protected PageResolver&AccessibleObjectInterface $subject;
+    private RequestHandlerInterface $responseOutputHandler;
 
     protected function setUp(): void
     {
         parent::setUp();
-        $this->timeTrackerStub = new TimeTracker(false);
-        $this->cacheHashCalculator = new CacheHashCalculator();
-
         // A request handler which only runs through
         $this->responseOutputHandler = new class () implements RequestHandlerInterface {
             public function handle(ServerRequestInterface $request): ResponseInterface
@@ -70,7 +61,7 @@ final class PageArgumentValidatorTest extends UnitTestCase
         $request = new ServerRequest($incomingUrl, 'GET');
         $request = $request->withAttribute('routing', $pageArguments);
 
-        $subject = new PageArgumentValidator($this->cacheHashCalculator, $this->timeTrackerStub);
+        $subject = new PageArgumentValidator(new CacheHashCalculator());
         $subject->setLogger(new NullLogger());
 
         $response = $subject->process($request, $this->responseOutputHandler);
@@ -90,7 +81,7 @@ final class PageArgumentValidatorTest extends UnitTestCase
         $request = new ServerRequest($incomingUrl, 'GET');
         $request = $request->withAttribute('routing', $pageArguments);
 
-        $subject = new PageArgumentValidator($this->cacheHashCalculator, $this->timeTrackerStub);
+        $subject = new PageArgumentValidator(new CacheHashCalculator());
         $typo3InformationMock = $this->getMockBuilder(Typo3Information::class)->disableOriginalConstructor()->getMock();
         $typo3InformationMock->expects(self::once())->method('getCopyrightYear')->willReturn('1999-20XX');
         GeneralUtility::addInstance(Typo3Information::class, $typo3InformationMock);
@@ -107,7 +98,7 @@ final class PageArgumentValidatorTest extends UnitTestCase
         $incomingUrl = 'https://king.com/lotus-flower/en/mr-magpie/bloom/';
         $request = new ServerRequest($incomingUrl, 'GET');
 
-        $subject = new PageArgumentValidator($this->cacheHashCalculator, $this->timeTrackerStub);
+        $subject = new PageArgumentValidator(new CacheHashCalculator());
         $typo3InformationMock = $this->getMockBuilder(Typo3Information::class)->disableOriginalConstructor()->getMock();
         $typo3InformationMock->expects(self::once())->method('getCopyrightYear')->willReturn('1999-20XX');
         GeneralUtility::addInstance(Typo3Information::class, $typo3InformationMock);
@@ -127,7 +118,7 @@ final class PageArgumentValidatorTest extends UnitTestCase
         $request = new ServerRequest($incomingUrl, 'GET');
         $request = $request->withAttribute('routing', $pageArguments);
 
-        $subject = new PageArgumentValidator($this->cacheHashCalculator, $this->timeTrackerStub);
+        $subject = new PageArgumentValidator(new CacheHashCalculator());
         $response = $subject->process($request, $this->responseOutputHandler);
         self::assertEquals(200, $response->getStatusCode());
     }
@@ -144,7 +135,7 @@ final class PageArgumentValidatorTest extends UnitTestCase
         $request = new ServerRequest($incomingUrl, 'GET');
         $request = $request->withAttribute('routing', $pageArguments);
 
-        $subject = new PageArgumentValidator($this->cacheHashCalculator, $this->timeTrackerStub);
+        $subject = new PageArgumentValidator(new CacheHashCalculator());
         $typo3InformationMock = $this->getMockBuilder(Typo3Information::class)->disableOriginalConstructor()->getMock();
         $typo3InformationMock->expects(self::once())->method('getCopyrightYear')->willReturn('1999-20XX');
         GeneralUtility::addInstance(Typo3Information::class, $typo3InformationMock);
diff --git a/typo3/sysext/redirects/Classes/Service/RedirectService.php b/typo3/sysext/redirects/Classes/Service/RedirectService.php
index d0af47f1dc140a68670dbd99d059d8363f9afa75..1bedbb7197c59587f07d50b9ed94d1da68509b8d 100644
--- a/typo3/sysext/redirects/Classes/Service/RedirectService.php
+++ b/typo3/sysext/redirects/Classes/Service/RedirectService.php
@@ -37,6 +37,7 @@ use TYPO3\CMS\Core\Site\Entity\SiteInterface;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\HttpUtility;
+use TYPO3\CMS\Frontend\Cache\CacheInstruction;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 use TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder;
 use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;
@@ -386,6 +387,8 @@ class RedirectService implements LoggerAwareInterface
      */
     protected function bootFrontendController(SiteInterface $site, array $queryParams, ServerRequestInterface $originalRequest): TypoScriptFrontendController
     {
+        $cacheInstruction = $originalRequest->getAttribute('frontend.cache.instruction', new CacheInstruction());
+        $originalRequest = $originalRequest->withAttribute('frontend.cache.instruction', $cacheInstruction);
         $controller = GeneralUtility::makeInstance(
             TypoScriptFrontendController::class,
             GeneralUtility::makeInstance(Context::class),
diff --git a/typo3/sysext/workspaces/Classes/Middleware/WorkspacePreview.php b/typo3/sysext/workspaces/Classes/Middleware/WorkspacePreview.php
index 47b1001705012947741ac55ee41891db5aad36bf..c1f922ceb69d7e0f2338432a9cf861d135055696 100644
--- a/typo3/sysext/workspaces/Classes/Middleware/WorkspacePreview.php
+++ b/typo3/sysext/workspaces/Classes/Middleware/WorkspacePreview.php
@@ -38,6 +38,7 @@ use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
 use TYPO3\CMS\Core\Routing\RouteResultInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Cache\CacheInstruction;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 use TYPO3\CMS\Workspaces\Authentication\PreviewUserAuthentication;
 
@@ -74,7 +75,6 @@ class WorkspacePreview implements MiddlewareInterface
      */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
-        $addInformationAboutDisabledCache = false;
         $keyword = $this->getPreviewInputCode($request);
         $setCookieOnCurrentRequest = false;
         $normalizedParams = $request->getAttribute('normalizedParams');
@@ -118,20 +118,16 @@ class WorkspacePreview implements MiddlewareInterface
             $GLOBALS['BE_USER']->setTemporaryWorkspace(0);
             // Register the backend user as aspect
             $this->setBackendUserAspect($context, $GLOBALS['BE_USER']);
-            // Caching is disabled, because otherwise generated URLs could include the keyword parameter
-            $request = $request->withAttribute('noCache', true);
-            $addInformationAboutDisabledCache = true;
+            $cacheInstruction = $request->getAttribute('frontend.cache.instruction', new CacheInstruction());
+            $cacheInstruction->disableCache('ext:workspaces: Disabled FE cache with BE_USER previewing live workspace');
+            $request = $request->withAttribute('frontend.cache.instruction', $cacheInstruction);
             $setCookieOnCurrentRequest = false;
         }
 
         $response = $handler->handle($request);
 
-        $tsfe = $this->getTypoScriptFrontendController();
-        if ($tsfe !== null && $addInformationAboutDisabledCache) {
-            $tsfe->set_no_cache('GET Parameter ADMCMD_prev=LIVE was given', true);
-        }
-
         // Add an info box to the frontend content
+        $tsfe = $this->getTypoScriptFrontendController();
         if ($tsfe !== null && $context->getPropertyFromAspect('workspace', 'isOffline', false)) {
             $previewInfo = $this->renderPreviewInfo($tsfe, $request->getUri());
             $body = $response->getBody();