diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index 41ca7f1..9a8d9e9 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -186,12 +186,8 @@ function book_menu() {
     'type' => MENU_SUGGESTED_ITEM,
   );
   $items['book/export/%/%node'] = array(
-    'page callback' => 'book_export',
-    'page arguments' => array(2, 3),
-    'access callback' => 'book_export_access',
-    'access arguments' => array(3),
+    'route_name' => 'book_export',
     'type' => MENU_CALLBACK,
-    'file' => 'book.pages.inc',
   );
   $items['node/%node/outline'] = array(
     'title' => 'Outline',
diff --git a/core/modules/book/book.pages.inc b/core/modules/book/book.pages.inc
index 67c4c99..2ba5a15 100644
--- a/core/modules/book/book.pages.inc
+++ b/core/modules/book/book.pages.inc
@@ -10,80 +10,6 @@
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
- * Page callback: Generates representations of a book page and its children.
- *
- * The function delegates the generation of output to helper functions. The
- * function name is derived by prepending 'book_export_' to the given output
- * type. So, e.g., a type of 'html' results in a call to the function
- * book_export_html().
- *
- * @param $type
- *   A string encoding the type of output requested. The following types are
- *   currently supported in book module:
- *   - html: Printer-friendly HTML.
- *   Other types may be supported in contributed modules.
- * @param \Drupal\node\Plugin\Core\Entity\EntityInterface $node
- *   The node to export.
- *
- * @return
- *   A string representing the node and its children in the book hierarchy in a
- *   format determined by the $type parameter.
- *
- * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- *
- * @see book_menu()
- */
-function book_export($type, EntityInterface $node) {
-  $type = drupal_strtolower($type);
-
-  $export_function = 'book_export_' . $type;
-
-  if (function_exists($export_function)) {
-    print call_user_func($export_function, $node);
-  }
-  else {
-    drupal_set_message(t('Unknown export format.'));
-    throw new NotFoundHttpException();
-  }
-}
-
-/**
- * Generates HTML for export when invoked by book_export().
- *
- * The given node is embedded to its absolute depth in a top level section. For
- * example, a child node with depth 2 in the hierarchy is contained in
- * (otherwise empty) <div> elements corresponding to depth 0 and depth 1.
- * This is intended to support WYSIWYG output - e.g., level 3 sections always
- * look like level 3 sections, no matter their depth relative to the node
- * selected to be exported as printer-friendly HTML.
- *
- * @param \Drupal\node\Plugin\Core\Entity\Node
- *   The node to export.
- *
- * @return
- *   A string containing HTML representing the node and its children in
- *   the book hierarchy.
- *
- * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
- * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- */
-function book_export_html(EntityInterface $node) {
-  if (user_access('access printer-friendly version')) {
-    if (isset($node->book)) {
-      $tree = book_menu_subtree_data($node->book);
-      $contents = book_export_traverse($tree, 'book_node_export');
-      return theme('book_export_html', array('title' => $node->label(), 'contents' => $contents, 'depth' => $node->book['depth']));
-    }
-    else {
-      throw new NotFoundHttpException();
-    }
-  }
-  else {
-    throw new AccessDeniedHttpException();
-  }
-}
-
-/**
  * Page callback: Shows the outline form for a single node.
  *
  * @param \Drupal\Core\Entity\EntityInterface $node
diff --git a/core/modules/book/book.routing.yml b/core/modules/book/book.routing.yml
index 6c8b010..2717791 100644
--- a/core/modules/book/book.routing.yml
+++ b/core/modules/book/book.routing.yml
@@ -18,3 +18,10 @@ book_settings:
     _form: 'Drupal\book\BookSettingsForm'
   requirements:
     _permission: 'administer site configuration'
+
+book_export:
+  pattern: '/book/export/{type}/{node}'
+  defaults:
+    _content: '\Drupal\book\Controller\BookController::bookExport'
+  requirements:
+    _permission: 'access printer-friendly version'
diff --git a/core/modules/book/lib/Drupal/book/Controller/BookController.php b/core/modules/book/lib/Drupal/book/Controller/BookController.php
index d21607d..33dcdd7 100644
--- a/core/modules/book/lib/Drupal/book/Controller/BookController.php
+++ b/core/modules/book/lib/Drupal/book/Controller/BookController.php
@@ -8,8 +8,11 @@
 
 use Drupal\Core\ControllerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 use Drupal\book\BookManager;
+use Drupal\node\Plugin\Core\Entity\Node;
 
 /**
  * Controller routines for book routes.
@@ -86,4 +89,75 @@ public function bookRender() {
     return theme('item_list', array('items' => $book_list));
   }
 
+  /**
+   * Generates representations of a book page and its children.
+   *
+   * The function delegates the generation of output to helper functions. The
+   * function name is derived by prepending 'book_export_' to the given output
+   * type. So, e.g., a type of 'html' results in a call to the function
+   * book_export_html().
+   *
+   * @param $type
+   *   A string encoding the type of output requested. The following types are
+   *   currently supported in book module:
+   *   - html: Printer-friendly HTML.
+   *   Other types may be supported in contributed modules.
+   * @param \Drupal\node\Plugin\Core\Entity\EntityInterface $node
+   *   The node to export.
+   *
+   * @return
+   *   A string representing the node and its children in the book hierarchy in a
+   *   format determined by the $type parameter.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+   */
+  public function bookExport($type, EntityInterface $node) {
+    $type = drupal_strtolower($type);
+
+    $export_function = 'book_export_' . $type;
+
+    if (function_exists($export_function)) {
+      print call_user_func($export_function, $node);
+    }
+    else {
+      drupal_set_message(t('Unknown export format.'));
+      throw new NotFoundHttpException();
+    }
+  }
+
+  /**
+   * Generates HTML for export when invoked by book_export().
+   *
+   * The given node is embedded to its absolute depth in a top level section. For
+   * example, a child node with depth 2 in the hierarchy is contained in
+   * (otherwise empty) <div> elements corresponding to depth 0 and depth 1.
+   * This is intended to support WYSIWYG output - e.g., level 3 sections always
+   * look like level 3 sections, no matter their depth relative to the node
+   * selected to be exported as printer-friendly HTML.
+   *
+   * @param \Drupal\node\Plugin\Core\Entity\Node
+   *   The node to export.
+   *
+   * @return
+   *   A string containing HTML representing the node and its children in
+   *   the book hierarchy.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+   */
+  public function book_export_html(EntityInterface $node) {
+    if (user_access('access printer-friendly version')) {
+      if (isset($node->book)) {
+        $tree = book_menu_subtree_data($node->book);
+        $contents = book_export_traverse($tree, 'book_node_export');
+        return theme('book_export_html', array('title' => $node->label(), 'contents' => $contents, 'depth' => $node->book['depth']));
+      }
+      else {
+        throw new NotFoundHttpException();
+      }
+    }
+    else {
+      throw new AccessDeniedHttpException();
+    }
+  }
 }
