 core/modules/book/book.services.yml                |  5 ++
 .../book/src/Cache/BookNavigationCacheContext.php  | 71 ++++++++++++++++++++++
 .../book/src/Plugin/Block/BookNavigationBlock.php  | 26 ++------
 3 files changed, 82 insertions(+), 20 deletions(-)

diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml
index 9aeda19..440d021 100644
--- a/core/modules/book/book.services.yml
+++ b/core/modules/book/book.services.yml
@@ -23,3 +23,8 @@ services:
     arguments: ['@book.manager']
     tags:
       - { name: access_check, applies_to: _access_book_removable }
+  cache_context.book.navigation:
+    class: Drupal\book\Cache\BookNavigationCacheContext
+    arguments: ['@request_stack']
+    tags:
+      - { name: cache.context}
diff --git a/core/modules/book/src/Cache/BookNavigationCacheContext.php b/core/modules/book/src/Cache/BookNavigationCacheContext.php
new file mode 100644
index 0000000..531611f
--- /dev/null
+++ b/core/modules/book/src/Cache/BookNavigationCacheContext.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\book\Cache\BookNavigationCacheContext.
+ */
+
+namespace Drupal\book\Cache;
+
+use Drupal\Core\Cache\CacheContextInterface;
+use Symfony\Component\DependencyInjection\ContainerAware;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Defines the book navigation cache context service.
+ *
+ * This allows for book navigation location-aware caching. It depends on:
+ * - whether the current route represents a book node at all
+ * - and if so, where in the book hierarchy we are
+ *
+ * This class is container-aware to avoid initializing the 'book.manager'
+ * service when it is not necessary.
+ */
+class BookNavigationCacheContext extends ContainerAware implements CacheContextInterface {
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * Constructs a new BookNavigationCacheContext service.
+   *
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   */
+  public function __construct(RequestStack $request_stack) {
+    $this->requestStack = $request_stack;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t("Book navigation");
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext() {
+    // Find the current book's ID.
+    $current_bid = 0;
+    if ($node = $this->requestStack->getCurrentRequest()->get('node')) {
+      $current_bid = empty($node->book['bid']) ? 0 : $node->book['bid'];
+    }
+
+    // If we're not looking at a book node, then we're not navigating a book.
+    if ($current_bid === 0) {
+      return 'book.none';
+    }
+
+    // If we're looking at a book node, get the trail for that node.
+    $active_trail = $this->container->get('book.manager')
+      ->getActiveTrailIds($node->book['bid'], $node->book);
+    return 'book.' . implode('|', $active_trail);
+  }
+
+}
diff --git a/core/modules/book/src/Plugin/Block/BookNavigationBlock.php b/core/modules/book/src/Plugin/Block/BookNavigationBlock.php
index 4369756..f95161a 100644
--- a/core/modules/book/src/Plugin/Block/BookNavigationBlock.php
+++ b/core/modules/book/src/Plugin/Block/BookNavigationBlock.php
@@ -181,27 +181,13 @@ public function build() {
   /**
    * {@inheritdoc}
    */
-  public function getCacheKeys() {
-    // Add a key for the active book trail.
-    $current_bid = 0;
-    if ($node = $this->requestStack->getCurrentRequest()->get('node')) {
-      $current_bid = empty($node->book['bid']) ? 0 : $node->book['bid'];
-    }
-    if ($current_bid === 0) {
-      return parent::getCacheKeys();
-    }
-    $active_trail = $this->bookManager->getActiveTrailIds($node->book['bid'], $node->book);
-    $active_trail_key = 'trail.' . implode('|', $active_trail);
-    return array_merge(parent::getCacheKeys(), array($active_trail_key));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   protected function getRequiredCacheContexts() {
-    // The "Book navigation" block must be cached per role: different roles may
-    // have access to different menu links.
-    return array('cache_context.user.roles');
+    // The "Book navigation" block must be cached per role and book navigation
+    // context.
+    return [
+      'cache_context.user.roles',
+      'cache_context.book.navigation',
+    ];
   }
 
 }
