diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
index 0d3fcf421ce3216504889f99cd020dd5ff73ee9b..8892926ccb741802490f0ab6f55299b18a272ed5 100644
--- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
+++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php
@@ -9,6 +9,7 @@
 use Drupal\Core\Field\FieldItemInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Logger\LoggerChannelTrait;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Security\TrustedCallbackInterface;
 use Drupal\Core\Theme\Registry;
@@ -22,6 +23,8 @@
  */
 class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterface, EntityViewBuilderInterface, TrustedCallbackInterface {
 
+  use LoggerChannelTrait;
+
   /**
    * The type of entities for which this view builder is instantiated.
    *
@@ -80,6 +83,15 @@ class EntityViewBuilder extends EntityHandlerBase implements EntityHandlerInterf
    */
   protected $singleFieldDisplays;
 
+  /**
+   * A collection of keys.
+   *
+   * It identifies rendering in progress, used to prevent recursion.
+   *
+   * @var array
+   */
+  protected array $recursionKeys = [];
+
   /**
    * Constructs a new EntityViewBuilder.
    *
@@ -107,13 +119,15 @@ public function __construct(EntityTypeInterface $entity_type, EntityRepositoryIn
    * {@inheritdoc}
    */
   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    return new static(
+    $instance = new static(
       $entity_type,
       $container->get('entity.repository'),
       $container->get('language_manager'),
       $container->get('theme.registry'),
       $container->get('entity_display.repository')
     );
+    $instance->setLoggerFactory($container->get('logger.factory'));
+    return $instance;
   }
 
   /**
@@ -136,7 +150,12 @@ public function view(EntityInterface $entity, $view_mode = 'full', $langcode = N
    * {@inheritdoc}
    */
   public static function trustedCallbacks() {
-    return ['build', 'buildMultiple'];
+    return [
+      'build',
+      'buildMultiple',
+      'setRecursiveRenderProtection',
+      'unsetRecursiveRenderProtection',
+    ];
   }
 
   /**
@@ -188,24 +207,25 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode) {
         'max-age' => $entity->getCacheMaxAge(),
       ],
     ];
+    // Add callbacks to protect from recursive rendering.
+    $build['#pre_render'] = [[$this, 'setRecursiveRenderProtection']];
+    $build['#post_render'] = [[$this, 'unsetRecursiveRenderProtection']];
 
     // Add the default #theme key if a template exists for it.
     if ($this->themeRegistry->getRuntime()->has($this->entityTypeId)) {
       $build['#theme'] = $this->entityTypeId;
     }
 
-    $keys = [
-      'entity_view',
-      $this->entityTypeId,
-      $entity->id(),
-      $view_mode,
-    ];
-
     // Cache the rendered output if permitted by the view mode and global entity
     // type configuration.
     if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) {
       $build['#cache'] += [
-        'keys' => $keys,
+        'keys' => [
+          'entity_view',
+          $this->entityTypeId,
+          $entity->id(),
+          $view_mode,
+        ],
         'bin' => $this->cacheBin,
       ];
 
@@ -214,15 +234,6 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode) {
       }
     }
 
-    // Add keys for the renderer to use to identify recursive rendering.
-    $build['#recursion_keys'] = $keys;
-    if ($entity instanceof RevisionableInterface) {
-      $build['#recursion_keys'][] = $entity->getRevisionId();
-    }
-    if ($entity instanceof TranslatableDataInterface && count($entity->getTranslationLanguages()) > 1) {
-      $build['#recursion_keys'][] = $entity->language()->getId();
-    }
-
     return $build;
   }
 
@@ -545,4 +556,38 @@ protected function getSingleFieldDisplay($entity, $field_name, $display_options)
     return $display;
   }
 
+  /**
+   * Entity render array #pre_render callback.
+   */
+  public function setRecursiveRenderProtection(array $build): array {
+    // Checks whether entity render array with matching cache keys is being
+    // recursively rendered. If not already being rendered, add an entry to track
+    // that it is.
+    $recursion_key = implode(':', $build['#cache']['keys'] ?? []);
+    if (isset($this->recursionKeys[$recursion_key])) {
+      $this->getLogger('entity')
+        ->error('Recursive rendering attempt aborted for %key. In progress: %guards', [
+          '%key' => $recursion_key,
+          '%guards' => print_r($this->recursionKeys, TRUE),
+        ]);
+      $build['#printed'] = TRUE;
+    }
+    else {
+      $this->recursionKeys[$recursion_key] = $recursion_key;
+    }
+    return $build;
+  }
+
+  /**
+   * Entity render array #post_render callback.
+   */
+  public function unsetRecursiveRenderProtection(string $renderedEntity, array $build): string {
+    // Removes rendered entity matching cache keys from recursive render
+    // tracking, once the entity has been rendered.
+    $recursion_key = implode(':', $build['#cache']['keys'] ?? []);
+    unset($this->recursionKeys[$recursion_key]);
+
+    return $renderedEntity;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
index 9e0025eb8da677efab8b3a84469edde1a23c0d2a..63d3a68be05601d4a5e392059337cfb867a18497 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceEntityFormatter.php
@@ -29,10 +29,11 @@ class EntityReferenceEntityFormatter extends EntityReferenceFormatterBase {
    *
    * @var int
    *
-   * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0.
-   * Use #recursion_keys in render arrays to prevent recursion.
+   * @deprecated in drupal:9.4.0 and is removed from drupal:11.0.0.
+   * EntityViewBuilder #pre_render and #post_render callbacks prevent recursion.
    *
    * @see https://www.drupal.org/node/2940605
+   * @see \Drupal\Core\Entity\EntityViewBuilder::getBuildDefaults()
    */
   const RECURSIVE_RENDER_LIMIT = 20;
 
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 4dfd3d4c4510a66aef97b8e2ccd33ce9e225ad7c..fec1cf215c5a32ae3bf26c41779b047b58b422ab 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -96,15 +96,6 @@ class Renderer implements RendererInterface {
    */
   protected static $contextCollection;
 
-  /**
-   * A collection of keys.
-   *
-   * It identifies rendering in progress, used to prevent recursion.
-   *
-   * @var array
-   */
-  protected $recursionKeys = [];
-
   /**
    * Constructs a new Renderer.
    *
@@ -246,7 +237,6 @@ public function render(&$elements, $is_root_call = FALSE) {
     catch (\Exception $e) {
       // Mark the ::rootRender() call finished due to this exception & re-throw.
       $this->isRenderingRoot = FALSE;
-      $this->recursionKeys = [];
       throw $e;
     }
   }
@@ -437,24 +427,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     $elements['#cache']['max-age'] = $elements['#cache']['max-age'] ?? Cache::PERMANENT;
     $elements['#attached'] = $elements['#attached'] ?? [];
 
-    // Guard against recursive rendering now that all other early return
-    // possibilities are exhausted and it's time to render children.
-    $recursion_key = implode(':', $elements['#recursion_keys'] ?? []);
-    if ($recursion_key) {
-      if (isset($this->recursionKeys[$recursion_key])) {
-        \Drupal::logger('render')
-          ->error('Recursive rendering attempt aborted for %key. In progress: %guards', [
-            '%key' => $recursion_key,
-            '%guards' => print_r($this->recursionKeys, TRUE),
-          ]);
-        $elements['#printed'] = TRUE;
-      }
-      else {
-        $this->recursionKeys[$recursion_key] = $recursion_key;
-      }
-    }
-
-    // Allow #pre_render or #recursion_keys to abort rendering.
+    // Allow #pre_render to abort rendering.
     if (!empty($elements['#printed'])) {
       // The #printed element contains all the bubbleable rendering metadata for
       // the subtree.
@@ -626,7 +599,6 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     // Rendering is finished, all necessary info collected!
     $context->bubble();
 
-    unset($this->recursionKeys[$recursion_key]);
     $elements['#printed'] = TRUE;
     return $elements['#markup'];
   }
diff --git a/core/modules/comment/src/CommentViewBuilder.php b/core/modules/comment/src/CommentViewBuilder.php
index 0e4e024dd574300e854de0bb3303d74f0eff55d8..b0a7af4f800edb22129d3256ae76a1085bd8fb20 100644
--- a/core/modules/comment/src/CommentViewBuilder.php
+++ b/core/modules/comment/src/CommentViewBuilder.php
@@ -61,7 +61,7 @@ public function __construct(EntityTypeInterface $entity_type, EntityRepositoryIn
    * {@inheritdoc}
    */
   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    return new static(
+    $instance = new static(
       $entity_type,
       $container->get('entity.repository'),
       $container->get('language_manager'),
@@ -70,6 +70,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
       $container->get('entity_display.repository'),
       $container->get('entity_type.manager')
     );
+    $instance->setLoggerFactory($container->get('logger.factory'));
+    return $instance;
   }
 
   /**
diff --git a/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php
index 73b452d5f21a2d566530e9e9defc15d8373230f9..ea84fda9d84ce51db40453cc97a4c795498a9a4a 100644
--- a/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php
+++ b/core/modules/field/tests/src/Kernel/EntityReference/EntityReferenceFormatterTest.php
@@ -6,7 +6,6 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
-use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\filter\Entity\FilterFormat;
@@ -262,7 +261,7 @@ public function testEntityFormatterRecursiveRenderingFailing() {
     ]);
     $referencing_entity->save();
 
-    $count = EntityReferenceEntityFormatter::RECURSIVE_RENDER_LIMIT + 1;
+    $count = 21;
     $build = $view_builder->viewMultiple(array_fill(0, $count, $referencing_entity), 'default');
     $output = $renderer->renderRoot($build);
     // The title of entity_test entities is printed twice by default, so we have