diff --git a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
index 7f10d6f51f..96e2e055bf 100644
--- a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
+++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
@@ -2,10 +2,15 @@
 
 namespace Drupal\layout_builder\Entity;
 
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Entity\Entity\EntityViewDisplay as BaseEntityViewDisplay;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Plugin\Context\Context;
+use Drupal\Core\Plugin\Context\ContextDefinition;
 use Drupal\Core\Plugin\Context\EntityContext;
+use Drupal\Core\Render\Element;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
@@ -238,48 +243,106 @@ public function buildMultiple(array $entities) {
       return $build_list;
     }
 
-    /** @var \Drupal\Core\Entity\EntityInterface $entity */
     foreach ($entities as $id => $entity) {
-      $sections = $this->getRuntimeSections($entity);
-      if ($sections) {
+      $build_list[$id]['_layout_builder'] = $this->buildSections($entity);
+
+      // If there are any sections, remove all display configurable fields from
+      // the existing build.
+      if (!Element::isEmpty($build_list[$id]['_layout_builder'])) {
         foreach ($build_list[$id] as $name => $build_part) {
           $field_definition = $this->getFieldDefinition($name);
           if ($field_definition && $field_definition->isDisplayConfigurable($this->displayContext)) {
             unset($build_list[$id][$name]);
           }
         }
-
-        // Bypass ::getContexts() in order to use the runtime entity, not a
-        // sample entity.
-        $contexts = $this->contextRepository()->getAvailableContexts();
-        $label = new TranslatableMarkup('@entity being viewed', [
-          '@entity' => $entity->getEntityType()->getSingularLabel(),
-        ]);
-        $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity, $label);
-        foreach ($sections as $delta => $section) {
-          $build_list[$id]['_layout_builder'][$delta] = $section->toRenderArray($contexts);
-        }
       }
     }
 
     return $build_list;
   }
 
+  /**
+   * Builds the render array for the sections of a given entity.
+   *
+   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
+   *   The entity.
+   *
+   * @return array
+   *   The render array representing the sections of the entity.
+   */
+  private function buildSections(FieldableEntityInterface $entity) {
+    $contexts = $this->getContextsForEntity($entity);
+    // @todo Remove in https://www.drupal.org/project/drupal/issues/3018782.
+    $label = new TranslatableMarkup('@entity being viewed', [
+      '@entity' => $entity->getEntityType()->getSingularLabel(),
+    ]);
+    $contexts['layout_builder.entity'] = EntityContext::fromEntity($entity, $label);
+
+    $cacheability = new CacheableMetadata();
+    $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability);
+
+    $build = [];
+    if ($storage) {
+      foreach ($storage->getSections() as $delta => $section) {
+        $build[$delta] = $section->toRenderArray($contexts);
+      }
+    }
+    // The render array is built based on decisions made by @SectionStorage
+    // plugins and therefore it needs to depend on the accumulated
+    // cacheability of those decisions.
+    $cacheability->applyTo($build);
+    return $build;
+  }
+
+  /**
+   * Gets the available contexts for a given entity.
+   *
+   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
+   *   The entity.
+   *
+   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
+   *   An array of context objects for a given entity.
+   */
+  protected function getContextsForEntity(FieldableEntityInterface $entity) {
+    return [
+      'view_mode' => new Context(ContextDefinition::create('string'), $this->getMode()),
+      'entity' => EntityContext::fromEntity($entity),
+      'display' => EntityContext::fromEntity($this),
+    ] + $this->contextRepository()->getAvailableContexts();
+  }
+
   /**
    * Gets the runtime sections for a given entity.
    *
    * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
    *   The entity.
+   * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface|null $cacheability
+   *   (optional) Refinable cacheability object, which will be populated based
+   *   on the cacheability of each section storage candidate.
+   * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
+   *   (optional) The contexts which should be used to determine which storage
+   *   to return.
    *
    * @return \Drupal\layout_builder\Section[]
    *   The sections.
+   *
+   * @deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0.
+   *   \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext()
+   *   should be used instead. See https://www.drupal.org/node/3022574.
    */
-  protected function getRuntimeSections(FieldableEntityInterface $entity) {
-    if ($this->isOverridable() && !$entity->get(OverridesSectionStorage::FIELD_NAME)->isEmpty()) {
-      return $entity->get(OverridesSectionStorage::FIELD_NAME)->getSections();
+  protected function getRuntimeSections(FieldableEntityInterface $entity, RefinableCacheableDependencyInterface &$cacheability = NULL, array &$contexts = []) {
+    @trigger_error('\Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::getRuntimeSections() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext() should be used instead. See https://www.drupal.org/node/3022574.', E_USER_DEPRECATED);
+    // For backwards compatibility mirror the functionality of ::buildSections()
+    // by constructing a cacheable metadata object and retrieving the
+    // entity-based contexts.
+    if (!$cacheability) {
+      $cacheability = new CacheableMetadata();
     }
-
-    return $this->getSections();
+    if (!$contexts) {
+      $contexts = $this->getContextsForEntity($entity);
+    }
+    $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability);
+    return $storage ? $storage->getSections() : [];
   }
 
   /**
@@ -399,4 +462,14 @@ protected function getDefaultSection() {
     return $this->getSection(0);
   }
 
+  /**
+   * Gets the section storage manager.
+   *
+   * @return \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface
+   *   The section storage manager.
+   */
+  private function sectionStorageManager() {
+    return \Drupal::service('plugin.manager.layout_builder.section_storage');
+  }
+
 }
diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php
index f51a4653c2..b18b17767d 100644
--- a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php
+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -21,8 +22,15 @@
 /**
  * Defines the 'defaults' section storage type.
  *
+ * DefaultsSectionStorage uses a positive weight because:
+ * - It must be picked after
+ *   \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage.
+ * - The default weight is 0, so other custom implementations will also take
+ *   precedence unless otherwise specified.
+ *
  * @SectionStorage(
  *   id = "defaults",
+ *   weight = 20,
  *   context_definitions = {
  *     "display" = @ContextDefinition("entity:entity_view_display"),
  *   },
@@ -444,4 +452,12 @@ public function access($operation, AccountInterface $account = NULL, $return_as_
     return $return_as_object ? $result : $result->isAllowed();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isApplicable(RefinableCacheableDependencyInterface $cacheability) {
+    // Defaults are always applicable.
+    return TRUE;
+  }
+
 }
diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
index af2aca668d..138eb69f83 100644
--- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
@@ -3,6 +3,7 @@
 namespace Drupal\layout_builder\Plugin\SectionStorage;
 
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -19,8 +20,15 @@
 /**
  * Defines the 'overrides' section storage type.
  *
+ * OverridesSectionStorage uses a negative weight because:
+ * - It must be picked before
+ *   \Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage.
+ * - The default weight is 0, so custom implementations will not take
+ *   precedence unless otherwise specified.
+ *
  * @SectionStorage(
  *   id = "overrides",
+ *   weight = -20,
  *   context_definitions = {
  *     "entity" = @ContextDefinition("entity"),
  *     "view_mode" = @ContextDefinition("string", required = FALSE),
@@ -335,4 +343,14 @@ public function access($operation, AccountInterface $account = NULL, $return_as_
     return $return_as_object ? $result : $result->isAllowed();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isApplicable(RefinableCacheableDependencyInterface $cacheability) {
+    $default_section_storage = $this->getDefaultSectionStorage();
+    $cacheability->addCacheableDependency($default_section_storage)->addCacheableDependency($this);
+    // Check that overrides are enabled and have at least one section.
+    return $default_section_storage->isOverridable() && count($this);
+  }
+
 }
diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php
index 0bf52bf43d..fd14bac830 100644
--- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php
+++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\Context\ContextHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
@@ -92,12 +93,16 @@ public function load($type, array $contexts = []) {
   /**
    * {@inheritdoc}
    */
-  public function findByContext($operation, array $contexts) {
+  public function findByContext(array $contexts, RefinableCacheableDependencyInterface $cacheability) {
     $storage_types = array_keys($this->contextHandler->filterPluginDefinitionsByContexts($contexts, $this->getDefinitions()));
 
+    // Add the manager as a cacheable dependency in order to vary by changes to
+    // the plugin definitions.
+    $cacheability->addCacheableDependency($this);
+
     foreach ($storage_types as $type) {
       $plugin = $this->load($type, $contexts);
-      if ($plugin && $plugin->access($operation)) {
+      if ($plugin && $plugin->isApplicable($cacheability)) {
         return $plugin;
       }
     }
diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php
index 4e09e2721a..82e7dafbe8 100644
--- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php
+++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php
@@ -3,6 +3,7 @@
 namespace Drupal\layout_builder\SectionStorage;
 
 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 
 /**
  * Provides the interface for a plugin manager of section storage types.
@@ -30,15 +31,21 @@ public function load($type, array $contexts = []);
   /**
    * Finds the section storage to load based on available contexts.
    *
-   * @param string $operation
-   *   The access operation. See \Drupal\Core\Access\AccessibleInterface.
    * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
    *   The contexts which should be used to determine which storage to return.
+   * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheability
+   *   Refinable cacheability object, which will be populated based on the
+   *   cacheability of each section storage candidate. After calling this method
+   *   this parameter will reflect the cacheability information used to
+   *   determine the correct section storage. This must be associated with any
+   *   output that uses the result of this method.
    *
    * @return \Drupal\layout_builder\SectionStorageInterface|null
    *   The section storage if one matched all contexts, or NULL otherwise.
+   *
+   * @see \Drupal\Core\Cache\RefinableCacheableDependencyInterface
    */
-  public function findByContext($operation, array $contexts);
+  public function findByContext(array $contexts, RefinableCacheableDependencyInterface $cacheability);
 
   /**
    * Loads a section storage with no associated section list.
diff --git a/core/modules/layout_builder/src/SectionStorageInterface.php b/core/modules/layout_builder/src/SectionStorageInterface.php
index 6dfd4793a3..7e172dcade 100644
--- a/core/modules/layout_builder/src/SectionStorageInterface.php
+++ b/core/modules/layout_builder/src/SectionStorageInterface.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Core\Access\AccessibleInterface;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
 use Symfony\Component\Routing\RouteCollection;
 
@@ -164,4 +165,23 @@ public function label();
    */
   public function save();
 
+  /**
+   * Determines if this section storage is applicable for the current contexts.
+   *
+   * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheability
+   *   Refinable cacheability object, typically provided by the section storage
+   *   manager. When implementing this method, populate $cacheability with any
+   *   information that affects whether this storage is applicable.
+   *
+   * @return bool
+   *   TRUE if this section storage is applicable, FALSE otherwise.
+   *
+   * @internal
+   *   This method is intended to be called by
+   *   \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext().
+   *
+   * @see \Drupal\Core\Cache\RefinableCacheableDependencyInterface
+   */
+  public function isApplicable(RefinableCacheableDependencyInterface $cacheability);
+
 }
diff --git a/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/layout_builder_overrides_test.info.yml b/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/layout_builder_overrides_test.info.yml
new file mode 100644
index 0000000000..f9ed711707
--- /dev/null
+++ b/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/layout_builder_overrides_test.info.yml
@@ -0,0 +1,6 @@
+name: 'Layout Builder overrides test'
+type: module
+description: 'Support module for testing overriding layout building.'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/SimpleConfigSectionStorage.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/SimpleConfigSectionStorage.php
index c92bb2d1ed..7e15336519 100644
--- a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/SimpleConfigSectionStorage.php
+++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/SimpleConfigSectionStorage.php
@@ -3,6 +3,7 @@
 namespace Drupal\layout_builder_test\Plugin\SectionStorage;
 
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\Context\Context;
@@ -213,4 +214,11 @@ public function extractIdFromRoute($value, $definition, $name, array $defaults)
     return $value ?: $defaults['id'];
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isApplicable(RefinableCacheableDependencyInterface $cacheability) {
+    return TRUE;
+  }
+
 }
diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/TestStateBasedSectionStorage.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/TestStateBasedSectionStorage.php
new file mode 100644
index 0000000000..d51cdc72c0
--- /dev/null
+++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/TestStateBasedSectionStorage.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Drupal\layout_builder_test\Plugin\SectionStorage;
+
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\layout_builder\Plugin\SectionStorage\SectionStorageBase;
+use Drupal\layout_builder\Section;
+use Drupal\layout_builder\SectionComponent;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Provides a test section storage that is controlled by state.
+ *
+ * @SectionStorage(
+ *   id = "layout_builder_test_state",
+ * )
+ */
+class TestStateBasedSectionStorage extends SectionStorageBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSections() {
+    // Return a custom section.
+    $section = new Section('layout_onecol');
+    $section->appendComponent(new SectionComponent('fake-uuid', 'content', [
+      'id' => 'system_powered_by_block',
+      'label' => 'Test block title',
+      'label_display' => 'visible',
+    ]));
+    return [$section];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isApplicable(RefinableCacheableDependencyInterface $cacheability) {
+    $cacheability->mergeCacheMaxAge(0);
+    return \Drupal::state()->get('layout_builder_test_state', FALSE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getSectionList() {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getStorageId() {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSectionListFromId($id) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRoutes(RouteCollection $collection) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRedirectUrl() {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLayoutBuilderUrl($rel = 'view') {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extractIdFromRoute($value, $definition, $name, array $defaults) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deriveContextsFromRoute($value, $definition, $name, array $defaults) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function label() {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save() {}
+
+}
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php
new file mode 100644
index 0000000000..f701c76926
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderSectionStorageTest.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests the UI aspects of section storage.
+ *
+ * @group layout_builder
+ */
+class LayoutBuilderSectionStorageTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'layout_builder',
+    'node',
+    'layout_builder_test',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // @todo The Layout Builder UI relies on local tasks; fix in
+    //   https://www.drupal.org/project/drupal/issues/2917777.
+    $this->drupalPlaceBlock('local_tasks_block');
+
+    $this->createContentType(['type' => 'bundle_with_section_field']);
+    $this->createNode([
+      'type' => 'bundle_with_section_field',
+      'title' => 'The first node title',
+      'body' => [
+        [
+          'value' => 'The first node body',
+        ],
+      ],
+    ]);
+  }
+
+  /**
+   * Tests that section loading is delegated to plugins during rendering.
+   *
+   * @see \Drupal\layout_builder_test\Plugin\SectionStorage\TestStateBasedSectionStorage
+   */
+  public function testRenderByContextAwarePluginDelegate() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+      'administer node display',
+    ]));
+
+    // No blocks exist on the node by default.
+    $this->drupalGet('node/1');
+    $assert_session->pageTextNotContains('Defaults block title');
+    $assert_session->pageTextNotContains('Test block title');
+
+    // Enable Layout Builder.
+    $this->drupalPostForm('admin/structure/types/manage/bundle_with_section_field/display/default', ['layout[enabled]' => TRUE], 'Save');
+
+    // Add a block to the defaults.
+    $page->clickLink('Manage layout');
+    $page->clickLink('Add Block');
+    $page->clickLink('Powered by Drupal');
+    $page->fillField('settings[label]', 'Defaults block title');
+    $page->checkField('settings[label_display]');
+    $page->pressButton('Add Block');
+    $page->clickLink('Save Layout');
+
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('Defaults block title');
+    $assert_session->pageTextNotContains('Test block title');
+
+    // Enable the test section storage.
+    $this->container->get('state')->set('layout_builder_test_state', TRUE);
+    $this->drupalGet('node/1');
+    $assert_session->pageTextNotContains('Defaults block title');
+    $assert_session->pageTextContains('Test block title');
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php
index a554fb526f..7242839dda 100644
--- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php
+++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php
@@ -2,7 +2,9 @@
 
 namespace Drupal\Tests\layout_builder\Kernel;
 
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
 use Drupal\Core\Config\Schema\SchemaIncompleteException;
+use Drupal\entity_test\Entity\EntityTest;
 use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
 
 /**
@@ -40,4 +42,40 @@ public function testInvalidConfiguration() {
     $this->sectionStorage->save();
   }
 
+  /**
+   * @covers ::getRuntimeSections
+   * @group legacy
+   * @expectedDeprecation \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::getRuntimeSections() is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. \Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface::findByContext() should be used instead. See https://www.drupal.org/node/3022574.
+   */
+  public function testGetRuntimeSections() {
+    $this->container->get('current_user')->setAccount($this->createUser());
+
+    $entity = EntityTest::create();
+    $entity->save();
+    \Drupal::setContainer($this->container);
+
+    $reflection = new \ReflectionMethod($this->sectionStorage, 'getRuntimeSections');
+    $reflection->setAccessible(TRUE);
+
+    $cacheability = NULL;
+    $contexts = [];
+    $args = [$entity, &$cacheability, &$contexts];
+    $result = $reflection->invokeArgs($this->sectionStorage, $args);
+
+    $this->assertInstanceOf(RefinableCacheableDependencyInterface::class, $cacheability);
+    $this->assertSame([], $cacheability->getCacheContexts());
+    $this->assertSame([], $cacheability->getCacheTags());
+    $this->assertSame(-1, $cacheability->getCacheMaxAge());
+    $expected_contexts = [
+      'view_mode' => $this->sectionStorage->getMode(),
+      'entity' => $entity,
+      'display' => $this->sectionStorage,
+    ];
+    foreach ($expected_contexts as $key => $expected_context) {
+      $this->assertArrayHasKey($key, $contexts);
+      $this->assertEquals($expected_context, $contexts[$key]->getContextValue());
+    }
+    $this->assertEquals($this->sectionStorage->getSections(), $result);
+  }
+
 }
diff --git a/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php b/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php
index 155799c9e0..b3b99d1fcd 100644
--- a/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php
+++ b/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php
@@ -6,6 +6,8 @@
 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Component\Plugin\Factory\FactoryInterface;
+use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\Context\Context;
@@ -203,10 +205,11 @@ public function testFindDefinitions() {
    *
    * @dataProvider providerTestFindByContext
    *
-   * @param bool $plugin_access
-   *   The result for the plugin's access method to return.
+   * @param bool $plugin_is_applicable
+   *   The result for the plugin's isApplicable() method to return.
    */
-  public function testFindByContext($plugin_access) {
+  public function testFindByContext($plugin_is_applicable) {
+    $cacheability = new CacheableMetadata();
     $contexts = [
       'foo' => new Context(new ContextDefinition('foo')),
     ];
@@ -218,10 +221,10 @@ public function testFindByContext($plugin_access) {
     $this->discovery->getDefinitions()->willReturn($definitions);
 
     $provider_access = $this->prophesize(SectionStorageInterface::class);
-    $provider_access->access('test_operation')->willReturn($plugin_access);
+    $provider_access->isApplicable($cacheability)->willReturn($plugin_is_applicable);
 
     $no_access = $this->prophesize(SectionStorageInterface::class);
-    $no_access->access('test_operation')->willReturn(FALSE);
+    $no_access->isApplicable($cacheability)->willReturn(FALSE);
 
     $missing_contexts = $this->prophesize(SectionStorageInterface::class);
 
@@ -235,8 +238,8 @@ public function testFindByContext($plugin_access) {
     $this->factory->createInstance('missing_contexts', [])->willReturn($missing_contexts->reveal());
     $this->factory->createInstance('provider_access', [])->willReturn($provider_access->reveal());
 
-    $result = $this->manager->findByContext('test_operation', $contexts);
-    if ($plugin_access) {
+    $result = $this->manager->findByContext($contexts, $cacheability);
+    if ($plugin_is_applicable) {
       $this->assertSame($provider_access->reveal(), $result);
     }
     else {
@@ -249,11 +252,59 @@ public function testFindByContext($plugin_access) {
    */
   public function providerTestFindByContext() {
     // Data provider values are:
-    // - the result for the plugin's access method to return.
+    // - the result for the plugin's isApplicable() method to return.
     $data = [];
     $data['plugin access: true'] = [TRUE];
     $data['plugin access: false'] = [FALSE];
     return $data;
   }
 
+  /**
+   * @covers ::findByContext
+   */
+  public function testFindByContextCacheableSectionStorage() {
+    $cacheability = new CacheableMetadata();
+    $contexts = [
+      'foo' => new Context(new ContextDefinition('foo')),
+    ];
+
+    $definitions = [
+      'first' => new SectionStorageDefinition(),
+      'second' => new SectionStorageDefinition(),
+    ];
+    $this->discovery->getDefinitions()->willReturn($definitions);
+
+    // Create a plugin that has cacheability info itself as a cacheable object
+    // and from within ::isApplicable() but is not applicable.
+    $first_plugin = $this->prophesize(SectionStorageInterface::class);
+    $first_plugin->willImplement(CacheableDependencyInterface::class);
+    $first_plugin->getCacheContexts()->shouldNotBeCalled();
+    $first_plugin->getCacheTags()->shouldNotBeCalled();
+    $first_plugin->getCacheMaxAge()->shouldNotBeCalled();
+    $first_plugin->isApplicable($cacheability)->will(function ($arguments) {
+      $arguments[0]->addCacheTags(['first_plugin']);
+      return FALSE;
+    });
+
+    // Create a plugin that adds cacheability info from within ::isApplicable()
+    // and is applicable.
+    $second_plugin = $this->prophesize(SectionStorageInterface::class);
+    $second_plugin->isApplicable($cacheability)->will(function ($arguments) {
+      $arguments[0]->addCacheTags(['second_plugin']);
+      return TRUE;
+    });
+
+    $this->factory->createInstance('first', [])->willReturn($first_plugin->reveal());
+    $this->factory->createInstance('second', [])->willReturn($second_plugin->reveal());
+
+    // Do not do any filtering based on context.
+    $this->contextHandler->filterPluginDefinitionsByContexts($contexts, $definitions)->willReturnArgument(1);
+    $this->contextHandler->applyContextMapping($first_plugin, $contexts)->shouldBeCalled();
+    $this->contextHandler->applyContextMapping($second_plugin, $contexts)->shouldBeCalled();
+
+    $result = $this->manager->findByContext($contexts, $cacheability);
+    $this->assertSame($second_plugin->reveal(), $result);
+    $this->assertSame(['first_plugin', 'second_plugin'], $cacheability->getCacheTags());
+  }
+
 }
