diff --git a/core/core.services.yml b/core/core.services.yml
index ed1341c..d60f42c 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1503,6 +1503,20 @@ services:
     arguments: ['@controller_resolver', '@renderer']
     tags:
       - { name: event_subscriber }
+  # Placeholder strategies for rendering placeholders.
+  html_response.placeholder_strategy_subscriber:
+    class: Drupal\Core\EventSubscriber\HtmlResponsePlaceholderStrategySubscriber
+    tags:
+      - { name: event_subscriber }
+    arguments: ['@placeholder_strategy_manager']
+  placeholder_strategy_manager:
+    class: Drupal\Core\Render\Placeholder\PlaceholderStrategyManager
+    tags:
+      - { name: service_collector, tag: placeholder_strategy, call: addPlaceholderStrategy }
+  placeholder_strategy.single_flush:
+    class: Drupal\Core\Render\Placeholder\SingleFlushStrategy
+    tags:
+      - { name: placeholder_strategy, priority: -1000 }
   email.validator:
     class: Egulias\EmailValidator\EmailValidator
 
diff --git a/core/lib/Drupal/Core/EventSubscriber/HtmlResponsePlaceholderStrategySubscriber.php b/core/lib/Drupal/Core/EventSubscriber/HtmlResponsePlaceholderStrategySubscriber.php
new file mode 100644
index 0000000..147d51f
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/HtmlResponsePlaceholderStrategySubscriber.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\HtmlResponsePlaceholderStrategySubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\Render\HtmlResponse;
+use Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * HTML response subscriber to allow for different  placeholder strategies.
+ */
+class HtmlResponsePlaceholderStrategySubscriber implements EventSubscriberInterface {
+
+  /**
+   * The placeholder strategy manager service.
+   *
+   * @var \Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface
+   */
+  protected $placeholderStrategyManager;
+
+  /**
+   * Constructs a HtmlResponsePlaceholderStrategySubscriber object.
+   *
+   * @param \Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface $placeholder_strategy_manager
+   *   The placeholder strategy manager service.
+   */
+  public function __construct(PlaceholderStrategyInterface $placeholder_strategy_manager) {
+    $this->placeholderStrategyManager = $placeholder_strategy_manager;
+  }
+
+  /**
+   * Processes placeholders for HTML responses.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
+   *   The event to process.
+   */
+  public function onRespond(FilterResponseEvent $event) {
+    if (!$event->isMasterRequest()) {
+      return;
+    }
+
+    $response = $event->getResponse();
+    if (!$response instanceof HtmlResponse) {
+      return;
+    }
+
+    $attachments = $response->getAttachments();
+    if (empty($attachments['placeholders'])) {
+      return;
+    }
+
+    $attachments['placeholders'] = $this->placeholderStrategyManager->processPlaceholders($attachments['placeholders']);
+
+    $response->setAttachments($attachments);
+    $event->setResponse($response);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    // Run shortly before HtmlResponseSubscriber.
+    $events[KernelEvents::RESPONSE][] = ['onRespond', 5];
+    return $events;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Placeholder/PlaceholderStrategyInterface.php b/core/lib/Drupal/Core/Render/Placeholder/PlaceholderStrategyInterface.php
new file mode 100644
index 0000000..b6474d2
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Placeholder/PlaceholderStrategyInterface.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface.
+ */
+
+namespace Drupal\Core\Render\Placeholder;
+
+/**
+ * Provides an interface for defining a placeholder strategy service.
+ */
+interface PlaceholderStrategyInterface {
+
+  /**
+   * Processes placeholders to render them with different strategies.
+   *
+   * @param array $placeholders
+   *   The placeholders to process, with the keys being the markup for the
+   *   placeholders and the values the corresponding render array describing the
+   *   data to be rendered.
+   *
+   * @return array
+   *   The resulting placeholders, with a subset of the keys of $placeholders
+   *   (and those being the markup for the placeholders) but with the
+   *   corresponding render array being potentially modified to render e.g. an
+   *   ESI or BigPipe placeholder.
+   */
+  public function processPlaceholders(array $placeholders);
+
+}
diff --git a/core/lib/Drupal/Core/Render/Placeholder/PlaceholderStrategyManager.php b/core/lib/Drupal/Core/Render/Placeholder/PlaceholderStrategyManager.php
new file mode 100644
index 0000000..b24ecc2
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Placeholder/PlaceholderStrategyManager.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Placeholder\PlaceholderStrategyManager.
+ */
+
+namespace Drupal\Core\Render\Placeholder;
+
+/**
+ * Renders placeholders using different strategies depending on priorities.
+ */
+class PlaceholderStrategyManager implements PlaceholderStrategyInterface {
+
+  /**
+   * An ordered list of placeholder strategy services.
+   *
+   * Ordered according to service priority.
+   *
+   * @var \Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface[]
+   */
+  protected $placeholderStrategies = [];
+
+  /**
+   * Adds a placeholder strategy to use.
+   *
+   * @param \Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface $strategy
+   *   The strategy to add to the placeholder strategies.
+   */
+  public function addPlaceholderStrategy(PlaceholderStrategyInterface $strategy) {
+    $this->placeholderStrategies[] = $strategy;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processPlaceholders(array $placeholders) {
+    if (empty($placeholders)) {
+      return [];
+    }
+
+    // Assert that there is at least one strategy.
+    assert('!empty($this->placeholderStrategies)', 'At least one placeholder strategy needs to be present.');
+
+    $new_placeholders = [];
+
+    // Give each placeholder strategy a chance to replace all not-yet replaced
+    // placeholders. The order of placeholder strategies is well defined
+    // and this uses a variation of the "chain of responsibility" design pattern.
+    foreach ($this->placeholderStrategies as $strategy) {
+      $processed_placeholders = $strategy->processPlaceholders($placeholders);
+      assert('array_intersect_key($processed_placeholders, $placeholders) === $processed_placeholders', 'Processed placeholders need to be a subset of all placeholders.');
+      $placeholders = array_diff_key($placeholders, $processed_placeholders);
+      $new_placeholders += $processed_placeholders;
+
+      if (empty($placeholders)) {
+        break;
+      }
+    }
+
+    return $new_placeholders;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Placeholder/SingleFlushStrategy.php b/core/lib/Drupal/Core/Render/Placeholder/SingleFlushStrategy.php
new file mode 100644
index 0000000..61fea0f
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Placeholder/SingleFlushStrategy.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Placeholder\SingleFlushStrategy
+ */
+
+namespace Drupal\Core\Render\Placeholder;
+
+/**
+ * Defines the 'single_flush' placeholder strategy.
+ *
+ * This is designed to be the fallback strategy, so should have the lowest
+ * priority.
+ */
+class SingleFlushStrategy implements PlaceholderStrategyInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processPlaceholders(array $placeholders) {
+    // Return all placeholders as is; they should be rendered directly.
+    return $placeholders;
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/Render/Placeholder/PlaceholderStrategyManagerTest.php b/core/tests/Drupal/Tests/Core/Render/Placeholder/PlaceholderStrategyManagerTest.php
new file mode 100644
index 0000000..9e2d0f7
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Render/Placeholder/PlaceholderStrategyManagerTest.php
@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Render\Placeholder\PlaceholderStrategyManagerTest.
+ */
+
+namespace Drupal\Tests\Core\Render\Placeholder;
+
+use Drupal\Core\Render\Placeholder\PlaceholderStrategyManager;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Render\Placeholder\PlaceholderStrategyManager
+ * @group Render
+ */
+class PlaceholderStrategyManagerTest extends UnitTestCase {
+
+  /**
+   * @covers ::addPlaceholderStrategy
+   * @covers ::processPlaceholders
+   *
+   * @dataProvider providerProcessPlaceholders
+   */
+  public function testProcessPlaceholdersSingleFlush($strategies, $placeholders, $result) {
+    $placeholder_strategy_manager = new PlaceholderStrategyManager();
+
+    foreach ($strategies as $strategy) {
+      $placeholder_strategy_manager->addPlaceholderStrategy($strategy);
+    }
+
+    $this->assertEquals($result, $placeholder_strategy_manager->processPlaceholders($placeholders));
+  }
+
+  /**
+   * Provides a list of render strategies, placeholders and results.
+   *
+   * @return array
+   */
+  public function providerProcessPlaceholders() {
+    $data = [];
+
+    // Empty placeholders.
+    $data[] = [[], [], []];
+
+    // Placeholder removing strategy.
+    $placeholders = [
+      'remove-me' => ['#markup' => 'I-am-a-llama-that-will-be-removed-sad-face.'],
+    ];
+
+    $prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
+    $prophecy->processPlaceholders($placeholders)->willReturn([]);
+    $dev_null_strategy = $prophecy->reveal();
+
+    $data[] = [[$dev_null_strategy], $placeholders, []];
+
+    // Fake Single Flush strategy.
+    $placeholders = [
+      '67890' => ['#markup' => 'special-placeholder'],
+    ];
+
+    $prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
+    $prophecy->processPlaceholders($placeholders)->willReturn($placeholders);
+    $single_flush_strategy = $prophecy->reveal();
+
+    $data[] = [[$single_flush_strategy], $placeholders, $placeholders];
+
+    // Fake ESI strategy.
+    $placeholders = [
+      '12345' => ['#markup' => 'special-placeholder-for-esi'],
+    ];
+    $result = [
+      '12345' => ['#markup' => '<esi:include src="/fragment/12345" />'],
+    ];
+
+    $prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
+    $prophecy->processPlaceholders($placeholders)->willReturn($result);
+    $esi_strategy = $prophecy->reveal();
+
+    $data[] = [[$esi_strategy], $placeholders, $result];
+
+    // ESI + SingleFlush strategy (ESI replaces all).
+    $prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
+    $prophecy->processPlaceholders($placeholders)->willReturn($result);
+    $esi_strategy = $prophecy->reveal();
+
+    $prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
+    $prophecy->processPlaceholders($placeholders)->shouldNotBeCalled();
+    $prophecy->processPlaceholders($result)->shouldNotBeCalled();
+    $prophecy->processPlaceholders([])->shouldNotBeCalled();
+    $single_flush_strategy = $prophecy->reveal();
+
+    $data[] = [[$esi_strategy, $single_flush_strategy], $placeholders, $result];
+
+    // ESI + SingleFlush strategy (mixed).
+    $placeholders = [
+      '12345' => ['#markup' => 'special-placeholder-for-ESI'],
+      '67890' => ['#markup' => 'special-placeholder'],
+      'foo' => ['#markup' => 'bar'],
+    ];
+
+    $esi_result = [
+      '12345' => ['#markup' => '<esi:include src="/fragment/12345" />'],
+    ];
+
+    $normal_result = [
+      '67890' => ['#markup' => 'special-placeholder'],
+      'foo' => ['#markup' => 'bar'],
+    ];
+
+    $result = $esi_result + $normal_result;
+
+    $prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
+    $prophecy->processPlaceholders($placeholders)->willReturn($esi_result);
+    $esi_strategy = $prophecy->reveal();
+
+    $prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
+    $prophecy->processPlaceholders($normal_result)->willReturn($normal_result);
+    $single_flush_strategy = $prophecy->reveal();
+
+    $data[] = [[$esi_strategy, $single_flush_strategy], $placeholders, $result];
+
+    return $data;
+  }
+
+  /**
+   * @covers ::processPlaceholders
+   *
+   * @expectedException \AssertionError
+   * @expectedExceptionMessage At least one placeholder strategy needs to be present.
+   */
+  public function testProcessPlaceholdersNoStrategies() {
+    // Placeholders but no strategies defined.
+    $placeholders = [
+      'assert-me' => ['#markup' => 'I-am-a-llama-that-will-lead-to-an-assertion-by-the-placeholder-strategy-manager.'],
+    ];
+
+    $placeholder_strategy_manager = new PlaceholderStrategyManager();
+    $placeholder_strategy_manager->processPlaceholders($placeholders);
+  }
+
+  /**
+   * @covers ::processPlaceholders
+   *
+   * @expectedException \AssertionError
+   * @expectedExceptionMessage Processed placeholders need to be a subset of all placeholders.
+   */
+  public function testProcessPlaceholdersWithRoguePlaceholderStrategy() {
+    // Placeholders but no strategies defined.
+    $placeholders = [
+      'assert-me' => ['#markup' => 'llama'],
+    ];
+
+    $result = [
+      'assert-me' => ['#markup' => 'llama'],
+      'new-placeholder' => ['#markup' => 'rogue llama'],
+    ];
+
+    $prophecy = $this->prophesize('\Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface');
+    $prophecy->processPlaceholders($placeholders)->willReturn($result);
+    $rogue_strategy = $prophecy->reveal();
+
+    $placeholder_strategy_manager = new PlaceholderStrategyManager();
+    $placeholder_strategy_manager->addPlaceholderStrategy($rogue_strategy);
+    $placeholder_strategy_manager->processPlaceholders($placeholders);
+  }
+
+
+}
