 src/Render/BigPipe.php                             | 37 ++++++++++++++-
 src/Tests/BigPipePlaceholderTestCases.php          | 41 +++++++++++++++++
 src/Tests/BigPipeTest.php                          | 52 +++++++++++++++++++---
 .../big_pipe_test/big_pipe_test.routing.yml        |  6 +--
 .../big_pipe_test/src/BigPipeTestController.php    | 19 ++++++++
 .../Render/Placeholder/BigPipeStrategyTest.php     |  3 ++
 6 files changed, 146 insertions(+), 12 deletions(-)

diff --git a/src/Render/BigPipe.php b/src/Render/BigPipe.php
index cb30d2a..bf002ee 100644
--- a/src/Render/BigPipe.php
+++ b/src/Render/BigPipe.php
@@ -191,6 +191,12 @@ class BigPipe implements BigPipeInterface {
    * @param \Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets
    *   The cumulative assets sent so far; to be updated while rendering no-JS
    *   BigPipe placeholders.
+   *
+   * @throws \Exception
+   *   If an exception is thrown during the rendering of a placeholder, it is
+   *   caught to allow the other placeholders to still be replaced. But when
+   *   error logging is configured to be verbose, the exception is rethrown to
+   *   simplify debugging.
    */
   protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets) {
     // Split the HTML on every no-JS placeholder string.
@@ -224,7 +230,18 @@ class BigPipe implements BigPipeInterface {
           ],
         ],
       ];
-      $elements = $this->renderPlaceholder($placeholder, $placeholder_plus_cumulative_settings);
+      try {
+        $elements = $this->renderPlaceholder($placeholder, $placeholder_plus_cumulative_settings);
+      }
+      catch (\Exception $e) {
+        if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
+          throw $e;
+        }
+        else {
+          continue;
+        }
+      }
+
 
       // Create a new HtmlResponse. Ensure the CSS and (non-bottom) JS is sent
       // before the HTML they're associated with. In other words: ensure the
@@ -276,6 +293,12 @@ class BigPipe implements BigPipeInterface {
    * @param \Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets
    *   The cumulative assets sent so far; to be updated while rendering BigPipe
    *   placeholders.
+   *
+   * @throws \Exception
+   *   If an exception is thrown during the rendering of a placeholder, it is
+   *   caught to allow the other placeholders to still be replaced. But when
+   *   error logging is configured to be verbose, the exception is rethrown to
+   *   simplify debugging.
    */
   protected function sendPlaceholders(array $placeholders, array $placeholder_order, AttachedAssetsInterface $cumulative_assets) {
     // Return early if there are no BigPipe placeholders to send.
@@ -305,7 +328,17 @@ class BigPipe implements BigPipeInterface {
 
       // Render the placeholder.
       $placeholder_render_array = $placeholders[$placeholder_id];
-      $elements = $this->renderPlaceholder($placeholder_id, $placeholder_render_array);
+      try {
+        $elements = $this->renderPlaceholder($placeholder_id, $placeholder_render_array);
+      }
+      catch (\Exception $e) {
+        if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
+          throw $e;
+        }
+        else {
+          continue;
+        }
+      }
 
       // Create a new AjaxResponse.
       $ajax_response = new AjaxResponse();
diff --git a/src/Tests/BigPipePlaceholderTestCases.php b/src/Tests/BigPipePlaceholderTestCases.php
index 5514eb7..6459631 100644
--- a/src/Tests/BigPipePlaceholderTestCases.php
+++ b/src/Tests/BigPipePlaceholderTestCases.php
@@ -260,12 +260,53 @@ class BigPipePlaceholderTestCases {
     $current_time->embeddedHtmlResponse = '<time datetime=1991-03-14"></time>';
 
 
+    // 6. Edge case: #lazy_builder that throws an exception.
+    $exception = new BigPipePlaceholderTestCase(
+      [
+        '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
+        '#create_placeholder' => TRUE,
+      ],
+      '<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::exception" arguments="0=llamas&amp;1=suck" token="68a75f1a"></drupal-render-placeholder>',
+      [
+        '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
+      ]
+    );
+    $exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=68a75f1a';
+    $exception->bigPipePlaceholderRenderArray = [
+      '#markup' => '<div data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=68a75f1a"></div>',
+      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
+      '#attached' => [
+        'library' => ['big_pipe/big_pipe'],
+        'drupalSettings' => [
+          'bigPipePlaceholderIds' => [
+            'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=68a75f1a' => TRUE,
+          ],
+        ],
+        'big_pipe_placeholders' => [
+          'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=68a75f1a' => $exception->placeholderRenderArray,
+        ],
+      ],
+    ];
+    $exception->embeddedAjaxResponseCommands = NULL;
+    $exception->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&amp;args[0]=llamas&amp;args[1]=suck&amp;token=68a75f1a"></div>';
+    $exception->bigPipeNoJsPlaceholderRenderArray = [
+      '#markup' => $exception->bigPipeNoJsPlaceholder,
+      '#cache' => $cacheability_depends_on_session_and_nojs_cookie,
+      '#attached' => [
+        'big_pipe_nojs_placeholders' => [
+          $exception->bigPipeNoJsPlaceholder => $exception->placeholderRenderArray,
+        ],
+      ],
+    ];
+    $exception->embeddedHtmlResponse = NULL;
+
     return [
       'html' => $status_messages,
       'html_attribute_value' => $form_action,
       'html_attribute_value_subset' => $csrf_token,
       'edge_case__invalid_html' => $hello,
       'edge_case__html_non_lazy_builder' => $current_time,
+      'edge_case__html_exception' => $exception,
     ];
   }
 
diff --git a/src/Tests/BigPipeTest.php b/src/Tests/BigPipeTest.php
index b30d27b..dc84ca0 100644
--- a/src/Tests/BigPipeTest.php
+++ b/src/Tests/BigPipeTest.php
@@ -115,12 +115,12 @@ class BigPipeTest extends WebTestBase {
     $this->cookies = [];
 
     // Edge case: route with '_no_big_pipe' option.
-    $this->drupalGet(Url::fromRoute('big_pipe_test.no_big_pipe'));
+    $this->drupalGet(Url::fromRoute('no_big_pipe'));
     $this->assertSessionCookieExists(FALSE);
     $this->assertBigPipeNoJsCookieExists(FALSE);
     $this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
     $this->drupalLogin($this->rootUser);
-    $this->drupalGet(Url::fromRoute('big_pipe_test.no_big_pipe'));
+    $this->drupalGet(Url::fromRoute('no_big_pipe'));
     $this->assertSessionCookieExists(TRUE);
     $this->assertBigPipeNoJsCookieExists(FALSE);
     $this->assertNoRaw('<noscript><meta http-equiv="Refresh" content="0; URL=');
@@ -137,6 +137,9 @@ class BigPipeTest extends WebTestBase {
    * @see \Drupal\big_pipe\Tests\BigPipePlaceholderTestCases
    */
   public function testBigPipe() {
+    // Simulate production.
+    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
+
     $this->drupalLogin($this->rootUser);
     $this->assertSessionCookieExists(TRUE);
     $this->assertBigPipeNoJsCookieExists(FALSE);
@@ -159,11 +162,23 @@ class BigPipeTest extends WebTestBase {
     $this->assertBigPipePlaceholders([
       $cases['html']->bigPipePlaceholderId                             => Json::encode($cases['html']->embeddedAjaxResponseCommands),
       $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder']->embeddedAjaxResponseCommands),
+      $cases['edge_case__html_exception']->bigPipePlaceholderId        => NULL,
     ]);
 
     $this->pass('Verifying BigPipe assets are present…', 'Debug');
     $this->assertFalse(empty($this->getDrupalSettings()), 'drupalSettings present.');
     $this->assertTrue(in_array('big_pipe/big_pipe', explode(',', $this->getDrupalSettings()['ajaxPageState']['libraries'])), 'BigPipe asset library is present.');
+
+    // Simulate development.
+    $this->pass('Verifying BigPipe provides useful error output when an error occurs while rendering a placeholder if verbose error logging is enabled.', 'Debug');
+    $this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
+    $this->drupalGet(Url::fromRoute('big_pipe_test'));
+    // The 'edge_case__html_exception' case throws an exception.
+    $this->assertRaw('The website encountered an unexpected error. Please try again later');
+    $this->assertRaw('You are not allowed to say llamas are not cool!');
+    $this->assertNoRaw(static::STOP_SIGNAL, 'BigPipe stop signal absent: error occurred before then.');
+    // The exception is expected. Do not interpret it as a test failure.
+    unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
   }
 
   /**
@@ -177,6 +192,9 @@ class BigPipeTest extends WebTestBase {
    * @see \Drupal\big_pipe\Tests\BigPipePlaceholderTestCases
    */
   public function testBigPipeNoJs() {
+    // Simulate production.
+    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
+
     $this->drupalLogin($this->rootUser);
     $this->assertSessionCookieExists(TRUE);
     $this->assertBigPipeNoJsCookieExists(FALSE);
@@ -199,6 +217,7 @@ class BigPipeTest extends WebTestBase {
       $cases['html_attribute_value_subset']->bigPipeNoJsPlaceholder       => $cases['html_attribute_value_subset']->embeddedHtmlResponse,
       $cases['html']->bigPipeNoJsPlaceholder                              => $cases['html']->embeddedHtmlResponse,
       $cases['edge_case__html_non_lazy_builder']->bigPipeNoJsPlaceholder  => $cases['edge_case__html_non_lazy_builder']->embeddedHtmlResponse,
+      $cases['edge_case__html_exception']->bigPipePlaceholderId           => NULL,
     ]);
 
     $this->pass('Verifying there are no BigPipe placeholders & replacements…', 'Debug');
@@ -209,6 +228,16 @@ class BigPipeTest extends WebTestBase {
 
     $this->pass('Verifying BigPipe assets are absent…', 'Debug');
     $this->assertFalse(empty($this->getDrupalSettings()), 'drupalSettings and BigPipe asset library absent.');
+
+    // Simulate development.
+    $this->pass('Verifying BigPipe provides useful error output when an error occurs while rendering a placeholder if verbose error logging is enabled.', 'Debug');
+    $this->config('system.logging')->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)->save();
+    $this->drupalGet(Url::fromRoute('big_pipe_test'));
+    // The 'edge_case__html_exception' case throws an exception.
+    $this->assertRaw('The website encountered an unexpected error. Please try again later');
+    $this->assertRaw('You are not allowed to say llamas are not cool!');
+    // The exception is expected. Do not interpret it as a test failure.
+    unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
   }
 
   protected function assertBigPipeResponseHeadersPresent() {
@@ -230,7 +259,10 @@ class BigPipeTest extends WebTestBase {
     $this->assertSetsEqual(array_keys($expected_big_pipe_nojs_placeholders), array_map('rawurldecode', explode(' ', $this->drupalGetHeader('BigPipe-Test-No-Js-Placeholders'))));
     foreach ($expected_big_pipe_nojs_placeholders as $big_pipe_nojs_placeholder => $expected_replacement) {
       $this->pass('Checking whether the replacement for the BigPipe no-JS placeholder "' . $big_pipe_nojs_placeholder . '" is present:');
-      $this->assertRaw($expected_replacement);
+      $this->assertNoRaw($big_pipe_nojs_placeholder);
+      if ($expected_replacement !== NULL) {
+        $this->assertRaw($expected_replacement);
+      }
     }
   }
 
@@ -253,9 +285,14 @@ class BigPipeTest extends WebTestBase {
       $pos = strpos($this->getRawContent(), $expected_placeholder_html);
       $placeholder_positions[$pos] = $big_pipe_placeholder_id;
       // Verify expected placeholder replacement.
+      $expected_placeholder_replacement = '<script type="application/json" data-big-pipe-replacement-for-placeholder-with-id="' . $big_pipe_placeholder_id . '">';
       $result = $this->xpath('//script[@data-big-pipe-replacement-for-placeholder-with-id=:id]', [':id' => Html::decodeEntities($big_pipe_placeholder_id)]);
+      if ($expected_ajax_response === NULL) {
+        $this->assertEqual(0, count($result));
+        $this->assertNoRaw($expected_placeholder_replacement);
+        continue;
+      }
       $this->assertEqual($expected_ajax_response, trim((string) $result[0]));
-      $expected_placeholder_replacement = '<script type="application/json" data-big-pipe-replacement-for-placeholder-with-id="' . $big_pipe_placeholder_id . '">';
       $this->assertRaw($expected_placeholder_replacement);
       $pos = strpos($this->getRawContent(), $expected_placeholder_replacement);
       $placeholder_replacement_positions[$pos] = $big_pipe_placeholder_id;
@@ -263,8 +300,9 @@ class BigPipeTest extends WebTestBase {
     ksort($placeholder_positions, SORT_NUMERIC);
     $this->assertEqual(array_keys($expected_big_pipe_placeholders), array_values($placeholder_positions));
     $this->assertEqual(count($expected_big_pipe_placeholders), preg_match_all('/' . preg_quote('<div data-big-pipe-placeholder-id="', '/') . '/', $this->getRawContent()));
-    $this->assertEqual(array_keys($expected_big_pipe_placeholders), array_values($placeholder_replacement_positions));
-    $this->assertEqual(count($expected_big_pipe_placeholders), preg_match_all('/' . preg_quote('<script type="application/json" data-big-pipe-replacement-for-placeholder-with-id="', '/') . '/', $this->getRawContent()));
+    $expected_big_pipe_placeholders_with_replacements = array_filter($expected_big_pipe_placeholders);
+    $this->assertEqual(array_keys($expected_big_pipe_placeholders_with_replacements), array_values($placeholder_replacement_positions));
+    $this->assertEqual(count($expected_big_pipe_placeholders_with_replacements), preg_match_all('/' . preg_quote('<script type="application/json" data-big-pipe-replacement-for-placeholder-with-id="', '/') . '/', $this->getRawContent()));
 
     $this->pass('Verifying BigPipe start/stop signals…', 'Debug');
     $this->assertRaw(static::START_SIGNAL, 'BigPipe start signal present.');
@@ -274,7 +312,7 @@ class BigPipeTest extends WebTestBase {
     $this->assertTrue($start_signal_position < $stop_signal_position, 'BigPipe start signal appears before stop signal.');
 
     $this->pass('Verifying BigPipe placeholder replacements and start/stop signals were streamed in the correct order…', 'Debug');
-    $expected_stream_order = array_keys($expected_big_pipe_placeholders);
+    $expected_stream_order = array_keys($expected_big_pipe_placeholders_with_replacements);
     array_unshift($expected_stream_order, static::START_SIGNAL);
     array_push($expected_stream_order, static::STOP_SIGNAL);
     $actual_stream_order = $placeholder_replacement_positions + [
diff --git a/tests/modules/big_pipe_test/big_pipe_test.routing.yml b/tests/modules/big_pipe_test/big_pipe_test.routing.yml
index e573913..4979406 100644
--- a/tests/modules/big_pipe_test/big_pipe_test.routing.yml
+++ b/tests/modules/big_pipe_test/big_pipe_test.routing.yml
@@ -6,11 +6,11 @@ big_pipe_test:
   requirements:
     _access: 'TRUE'
 
-big_pipe_test.no_big_pipe:
+no_big_pipe:
   path: '/no_big_pipe'
   defaults:
-    _controller: '\Drupal\big_pipe_test\BigPipeTestController::test'
-    _title: 'BigPipe test'
+    _controller: '\Drupal\big_pipe_test\BigPipeTestController::nope'
+    _title: '_no_big_pipe route option test'
   options:
     _no_big_pipe: TRUE
   requirements:
diff --git a/tests/modules/big_pipe_test/src/BigPipeTestController.php b/tests/modules/big_pipe_test/src/BigPipeTestController.php
index 3a663fc..6200929 100644
--- a/tests/modules/big_pipe_test/src/BigPipeTestController.php
+++ b/tests/modules/big_pipe_test/src/BigPipeTestController.php
@@ -34,10 +34,20 @@ class BigPipeTestController {
     // 5. Edge case: non-#lazy_builder placeholder.
     $build['edge_case__html_non_lazy_builder'] = $cases['edge_case__html_non_lazy_builder']->renderArray;
 
+    // 6. Edge case: #lazy_builder that throws an exception.
+    $build['edge_case__html_exception'] = $cases['edge_case__html_exception']->renderArray;
+
     return $build;
   }
 
   /**
+   * @return array
+   */
+  public function nope() {
+    return ['#markup' => '<p>Nope.</p>'];
+  }
+
+  /**
    * #lazy_builder callback; builds <time> markup with current time.
    *
    * Note: does not actually use current time, that would complicate testing.
@@ -63,4 +73,13 @@ class BigPipeTestController {
     ];
   }
 
+  /**
+   * #lazy_builder callback; throws exception.
+   *
+   * @throws \Exception
+   */
+  public static function exception() {
+    throw new \Exception('You are not allowed to say llamas are not cool!');
+  }
+
 }
diff --git a/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php b/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php
index d726a83..04e0257 100644
--- a/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php
+++ b/tests/src/Unit/Render/Placeholder/BigPipeStrategyTest.php
@@ -75,6 +75,7 @@ class BigPipeStrategyTest extends UnitTestCase {
       $cases['html_attribute_value_subset']->placeholder      => $cases['html_attribute_value_subset']->placeholderRenderArray,
       $cases['edge_case__invalid_html']->placeholder          => $cases['edge_case__invalid_html']->placeholderRenderArray,
       $cases['edge_case__html_non_lazy_builder']->placeholder => $cases['edge_case__html_non_lazy_builder']->placeholderRenderArray,
+      $cases['edge_case__html_exception']->placeholder        => $cases['edge_case__html_exception']->placeholderRenderArray,
     ];
 
     return [
@@ -90,6 +91,7 @@ class BigPipeStrategyTest extends UnitTestCase {
         $cases['html_attribute_value_subset']->placeholder      => $cases['html_attribute_value_subset']->bigPipeNoJsPlaceholderRenderArray,
         $cases['edge_case__invalid_html']->placeholder          => $cases['edge_case__invalid_html']->bigPipeNoJsPlaceholderRenderArray,
         $cases['edge_case__html_non_lazy_builder']->placeholder => $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderRenderArray,
+        $cases['edge_case__html_exception']->placeholder        => $cases['edge_case__html_exception']->bigPipePlaceholderRenderArray,
       ]],
       '_no_big_pipe absent, session, no-JS cookie present: no-JS BigPipe placeholder used for HTML placeholders' => [$placeholders, FALSE, TRUE, TRUE, [
         $cases['html']->placeholder                             => $cases['html']->bigPipeNoJsPlaceholderRenderArray,
@@ -97,6 +99,7 @@ class BigPipeStrategyTest extends UnitTestCase {
         $cases['html_attribute_value_subset']->placeholder      => $cases['html_attribute_value_subset']->bigPipeNoJsPlaceholderRenderArray,
         $cases['edge_case__invalid_html']->placeholder          => $cases['edge_case__invalid_html']->bigPipeNoJsPlaceholderRenderArray,
         $cases['edge_case__html_non_lazy_builder']->placeholder => $cases['edge_case__html_non_lazy_builder']->bigPipeNoJsPlaceholderRenderArray,
+        $cases['edge_case__html_exception']->placeholder        => $cases['edge_case__html_exception']->bigPipeNoJsPlaceholderRenderArray,
       ]],
     ];
   }
