diff --git a/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php b/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php
index d724c74acc18870415ba87a3ea4b8beea3baa22b..bd382fe3b259a01ce7dd0a8552984af1c847eb92 100644
--- a/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php
+++ b/typo3/sysext/frontend/Classes/Middleware/PrepareTypoScriptFrontendRendering.php
@@ -33,19 +33,13 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
  */
 class PrepareTypoScriptFrontendRendering implements MiddlewareInterface
 {
-    /**
-     * @var TypoScriptFrontendController
-     */
-    protected $controller;
-
     /**
      * @var TimeTracker
      */
     protected $timeTracker;
 
-    public function __construct(TypoScriptFrontendController $controller, TimeTracker $timeTracker)
+    public function __construct(TimeTracker $timeTracker)
     {
-        $this->controller = $controller;
         $this->timeTracker = $timeTracker;
     }
 
@@ -58,23 +52,26 @@ class PrepareTypoScriptFrontendRendering implements MiddlewareInterface
      */
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
+        /** @var TypoScriptFrontendController */
+        $controller = $request->getAttribute('frontend.controller');
+
         // as long as TSFE throws errors with the global object, this needs to be set, but
         // should be removed later-on once TypoScript Condition Matcher is built with the current request object.
         $GLOBALS['TYPO3_REQUEST'] = $request;
         // Get from cache
         $this->timeTracker->push('Get Page from cache');
         // Locks may be acquired here
-        $this->controller->getFromCache($request);
+        $controller->getFromCache($request);
         $this->timeTracker->pull();
         // Get config if not already gotten
         // After this, we should have a valid config-array ready
-        $this->controller->getConfigArray($request);
+        $controller->getConfigArray($request);
 
         // Convert POST data to utf-8 for internal processing if metaCharset is different
-        if ($this->controller->metaCharset !== 'utf-8' && $request->getMethod() === 'POST') {
+        if ($controller->metaCharset !== 'utf-8' && $request->getMethod() === 'POST') {
             $parsedBody = $request->getParsedBody();
             if (is_array($parsedBody) && !empty($parsedBody)) {
-                $this->convertCharsetRecursivelyToUtf8($parsedBody, $this->controller->metaCharset);
+                $this->convertCharsetRecursivelyToUtf8($parsedBody, $controller->metaCharset);
                 $request = $request->withParsedBody($parsedBody);
             }
         }
@@ -86,7 +83,7 @@ class PrepareTypoScriptFrontendRendering implements MiddlewareInterface
          * However, when some middlewares returns early (e.g. Shortcut and MountPointRedirect,
          * which both skip inner middlewares), or due to Exceptions, locks still need to be released explicitly.
          */
-        $this->controller->releaseLocks();
+        $controller->releaseLocks();
 
         return $response;
     }
diff --git a/typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php b/typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php
index 315c2955e22ad4e494d25aafbcbf163ee8be91dc..27256e0c37e7ff4d8806b3984f8e1f000e026e18 100644
--- a/typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php
+++ b/typo3/sysext/frontend/Classes/Middleware/ShortcutAndMountPointRedirect.php
@@ -35,16 +35,6 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
  */
 class ShortcutAndMountPointRedirect implements MiddlewareInterface
 {
-    /**
-     * @var TypoScriptFrontendController
-     */
-    private $controller;
-
-    public function __construct(TypoScriptFrontendController $controller)
-    {
-        $this->controller = $controller;
-    }
-
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
         $exposeInformation = $GLOBALS['TYPO3_CONF_VARS']['FE']['exposeRedirectInformation'] ?? false;
@@ -63,14 +53,16 @@ class ShortcutAndMountPointRedirect implements MiddlewareInterface
         }
 
         // See if the current page is of doktype "External URL", if so, do a redirect as well.
-        if (empty($this->controller->config['config']['disablePageExternalUrl'] ?? null)
-            && PageRepository::DOKTYPE_LINK === (int)$this->controller->page['doktype']) {
+        /** @var TypoScriptFrontendController */
+        $controller = $request->getAttribute('frontend.controller');
+        if (empty($controller->config['config']['disablePageExternalUrl'] ?? null)
+            && PageRepository::DOKTYPE_LINK === (int)$controller->page['doktype']) {
             $externalUrl = $this->prefixExternalPageUrl(
-                $this->controller->page['url'],
+                $controller->page['url'],
                 $request->getAttribute('normalizedParams')->getSiteUrl()
             );
             if (!empty($externalUrl)) {
-                $message = 'TYPO3 External URL' . ($exposeInformation ? ' at page with ID ' . $this->controller->page['uid'] : '');
+                $message = 'TYPO3 External URL' . ($exposeInformation ? ' at page with ID ' . $controller->page['uid'] : '');
                 return new RedirectResponse(
                     $externalUrl,
                     303,
@@ -84,11 +76,13 @@ class ShortcutAndMountPointRedirect implements MiddlewareInterface
 
     protected function getRedirectUri(ServerRequestInterface $request): ?string
     {
-        $redirectToUri = $this->controller->getRedirectUriForShortcut($request);
+        /** @var TypoScriptFrontendController */
+        $controller = $request->getAttribute('frontend.controller');
+        $redirectToUri = $controller->getRedirectUriForShortcut($request);
         if ($redirectToUri !== null) {
             return $redirectToUri;
         }
-        return $this->controller->getRedirectUriForMountPoint($request);
+        return $controller->getRedirectUriForMountPoint($request);
     }
 
     /**
diff --git a/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php b/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
index d8319111630b0e756b557fe5082b2f2badc13336..017e44b794bfa5a708530f579dc70d3ffadde866 100644
--- a/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
+++ b/typo3/sysext/frontend/Classes/Middleware/TypoScriptFrontendInitialization.php
@@ -96,7 +96,10 @@ class TypoScriptFrontendInitialization implements MiddlewareInterface
 
         $controller->determineId($request);
 
+        $request = $request->withAttribute('frontend.controller', $controller);
         // Make TSFE globally available
+        // @todo deprecate $GLOBALS['TSFE'] once TSFE is retrieved from the
+        //       PSR-7 request attribute frontend.controller throughout TYPO3 core
         $GLOBALS['TSFE'] = $controller;
         return $handler->handle($request);
     }
diff --git a/typo3/sysext/frontend/Configuration/Services.yaml b/typo3/sysext/frontend/Configuration/Services.yaml
index 8c070b32e3dbed7cfa5fed79d761547c9947f7db..dc0765ac62abcbdbcd3641615a045133eb54e57c 100644
--- a/typo3/sysext/frontend/Configuration/Services.yaml
+++ b/typo3/sysext/frontend/Configuration/Services.yaml
@@ -7,15 +7,22 @@ services:
   TYPO3\CMS\Frontend\:
     resource: '../Classes/*'
 
+  # @deprecated since v11, will be removed in v12 - TypoScriptFrontendController is stateful and must be retrieved from the request attribute frontend.controller.
   TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController:
     factory: ['TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController', getGlobalInstance]
     shared: false
     autoconfigure: false
     autowire: false
+    deprecated:
+      package: 'typo3/cms-core'
+      version: '11.5'
+      message: 'Injection of "%service_id%" breaks subrequests. Please use the PSR-7 request attribute "frontend.controller".'
 
   TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer:
     public: true
     shared: false
+    arguments:
+      $typoScriptFrontendController: null
 
   TYPO3\CMS\Frontend\ContentObject\Exception\ProductionExceptionHandler:
     public: true