diff --git a/core/core.services.yml b/core/core.services.yml
index 7f9ed9b..4731d77 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -297,6 +297,11 @@ services:
     parent: container.trait
     tags:
       - { name: plugin_manager_cache_clear }
+  entity_route_subscriber:
+    class: Drupal\Core\EventSubscriber\EntityRouteProviderSubscriber
+    arguments: ['@entity.manager']
+    tags:
+      - { name: event_subscriber }
   entity.definitions.installed:
     class: Drupal\Core\KeyValueStore\KeyValueStoreInterface
     factory_method: get
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 128d21d..b0a7f0a 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -303,6 +303,20 @@ public function getFormObject($entity_type, $operation) {
   /**
    * {@inheritdoc}
    */
+  public function getRouteProviders($entity_type) {
+    if (!isset($this->handlers['route_provider'][$entity_type])) {
+      $route_provider_classes = $this->getDefinition($entity_type, TRUE)->getRouteProviderClasses();
+
+      foreach ($route_provider_classes as $type => $class) {
+        $this->handlers['route_provider'][$entity_type][$type] = $this->instantiateHandler($class, $this->getDefinition($entity_type));
+      }
+    }
+    return isset($this->handlers['route_provider'][$entity_type]) ? $this->handlers['route_provider'][$entity_type] : [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getViewBuilder($entity_type) {
     return $this->getHandler($entity_type, 'view_builder');
   }
@@ -324,19 +338,7 @@ public function getHandler($entity_type, $handler_type) {
       if (!$class) {
         throw new InvalidPluginDefinitionException($entity_type, sprintf('The "%s" entity type did not specify a %s handler.', $entity_type, $handler_type));
       }
-      if (is_subclass_of($class, 'Drupal\Core\Entity\EntityHandlerInterface')) {
-        $handler = $class::createInstance($this->container, $definition);
-      }
-      else {
-        $handler = new $class($definition);
-      }
-      if (method_exists($handler, 'setModuleHandler')) {
-        $handler->setModuleHandler($this->moduleHandler);
-      }
-      if (method_exists($handler, 'setStringTranslation')) {
-        $handler->setStringTranslation($this->translationManager);
-      }
-      $this->handlers[$handler_type][$entity_type] = $handler;
+      $this->handlers[$handler_type][$entity_type] = $this->instantiateHandler($class, $definition);
     }
     return $this->handlers[$handler_type][$entity_type];
   }
@@ -1231,4 +1233,32 @@ protected function deleteLastInstalledFieldStorageDefinition(FieldStorageDefinit
     $this->setLastInstalledFieldStorageDefinitions($entity_type_id, $definitions);
   }
 
+  /**
+   * Create an instance of a handler from the class of the handler.
+   *
+   * @param string $class
+   *   The class to instantiation.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $definition
+   *   The entity type definition.
+   *
+   * @return object
+   *   The instantiated handler object.
+   */
+  protected function instantiateHandler($class, EntityTypeInterface $definition) {
+    if (is_subclass_of($class, 'Drupal\Core\Entity\EntityHandlerInterface')) {
+      $handler = $class::createInstance($this->container, $definition);
+    }
+    else {
+      $handler = new $class($definition);
+    }
+    if (method_exists($handler, 'setModuleHandler')) {
+      $handler->setModuleHandler($this->moduleHandler);
+    }
+    if (method_exists($handler, 'setStringTranslation')) {
+      $handler->setStringTranslation($this->translationManager);
+      return $handler;
+    }
+    return $handler;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
index 8cf95ee..639f515 100644
--- a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php
@@ -224,6 +224,16 @@ public function getListBuilder($entity_type);
   public function getFormObject($entity_type, $operation);
 
   /**
+   * Gets all route provider instances.
+   *
+   * @param string $entity_type
+   *   The entity type for this route providers.
+   *
+   * @return \Drupal\Core\Entity\Routing\EntityRouteProviderInterface[]
+   */
+  public function getRouteProviders($entity_type);
+
+  /**
    * Checks whether a certain entity type has a certain handler.
    *
    * @param string $entity_type
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index 5ea863d..f737571 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -479,6 +479,13 @@ public function hasViewBuilderClass() {
   /**
    * {@inheritdoc}
    */
+  public function getRouteProviderClasses() {
+    return !empty($this->handlers['route_provider']) ? $this->handlers['route_provider'] : [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getAccessControlClass() {
     return $this->getHandlerClass('access');
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
index f8237a5..d6640b1 100644
--- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
@@ -280,6 +280,15 @@ public function setFormClass($operation, $class);
   public function hasFormClasses();
 
   /**
+   * Gets all the route provide handlers.
+   *
+   * Much like forms you can define multiple route provider handlers.
+   *
+   * @return string[]
+   */
+  public function getRouteProviderClasses();
+
+  /**
    * Returns the list class.
    *
    * @return string
diff --git a/core/lib/Drupal/Core/Entity/Routing/EntityRouteProviderInterface.php b/core/lib/Drupal/Core/Entity/Routing/EntityRouteProviderInterface.php
new file mode 100644
index 0000000..1f99cc7
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Routing/EntityRouteProviderInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Routing\EntityRouteProviderInterface.
+ */
+
+namespace Drupal\Core\Entity\Routing;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Allows entity types to provide routes.
+ */
+interface EntityRouteProviderInterface {
+
+  /**
+   * Provides routes for entities.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type
+   *
+   * @return \Symfony\Component\Routing\RouteCollection|\Symfony\Component\Routing\Route[]
+   *   Returns a route collection or an array of routes keyed by name, like
+   *   route_callbacks inside 'routing.yml' files.
+   */
+  public function routes(EntityTypeInterface $entity_type);
+
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/EntityRouteProviderSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/EntityRouteProviderSubscriber.php
new file mode 100644
index 0000000..98a6a49
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/EntityRouteProviderSubscriber.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\EntityRouteProviderSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Routing\RouteBuildEvent;
+use Drupal\Core\Routing\RoutingEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Ensures that routes can be provided by entity types.
+ */
+class EntityRouteProviderSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a new EntityRouteProviderSubscriber instance.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(EntityManagerInterface $entity_manager) {
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+   * Provide routes on route rebuild time.
+   */
+  public function onDynamic(RouteBuildEvent $event) {
+    $route_collection = $event->getRouteCollection();
+    foreach ($this->entityManager->getDefinitions() as $entity_type) {
+      if ($entity_type->hasHandlerClass('route_provider', TRUE)) {
+        foreach ($this->entityManager->getRouteProviders($entity_type->id()) as $route_provider) {
+          // Allow to both return an array of routes or a route collection,
+          // like route_callbacks in the routing.yml file.
+          $routes = $route_provider->routes($entity_type);
+          if ($routes instanceof RouteCollection) {
+            $route_collection->addCollection($routes);
+          }
+          elseif (is_array($routes)) {
+            foreach ($routes as $route_name => $route) {
+              $route_collection->add($route_name, $route);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[RoutingEvents::DYNAMIC][] = ['onDynamic'];
+    return $events;
+  }
+
+}
+
diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml
index 26a4d07..efd756a 100644
--- a/core/modules/node/node.routing.yml
+++ b/core/modules/node/node.routing.yml
@@ -5,15 +5,6 @@ node.multiple_delete_confirm:
   requirements:
     _permission: 'administer nodes'
 
-entity.node.edit_form:
-  path: '/node/{node}/edit'
-  defaults:
-    _entity_form: 'node.edit'
-  requirements:
-    _entity_access: 'node.update'
-  options:
-    _node_operation_route: TRUE
-
 node.add_page:
   path: '/node/add'
   defaults:
@@ -48,24 +39,6 @@ entity.node.preview:
       node_preview:
         type: 'node_preview'
 
-entity.node.canonical:
-  path: '/node/{node}'
-  defaults:
-    _content: '\Drupal\node\Controller\NodeViewController::view'
-    _title_callback: '\Drupal\node\Controller\NodeViewController::title'
-  requirements:
-    _entity_access: 'node.view'
-
-entity.node.delete_form:
-  path: '/node/{node}/delete'
-  defaults:
-    _entity_form: 'node.delete'
-    _title: 'Delete'
-  requirements:
-    _entity_access: 'node.delete'
-  options:
-    _node_operation_route: TRUE
-
 entity.node.version_history:
   path: '/node/{node}/revisions'
   defaults:
diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php
index 5948103..9e0f3b2 100644
--- a/core/modules/node/src/Entity/Node.php
+++ b/core/modules/node/src/Entity/Node.php
@@ -34,6 +34,9 @@
  *       "delete" = "Drupal\node\Form\NodeDeleteForm",
  *       "edit" = "Drupal\node\NodeForm"
  *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\node\Entity\NodeRouteProvider",
+ *     },
  *     "list_builder" = "Drupal\node\NodeListBuilder",
  *     "translation" = "Drupal\node\NodeTranslationHandler"
  *   },
diff --git a/core/modules/node/src/Entity/NodeRouteProvider.php b/core/modules/node/src/Entity/NodeRouteProvider.php
new file mode 100644
index 0000000..7041900
--- /dev/null
+++ b/core/modules/node/src/Entity/NodeRouteProvider.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Entity\NodeRouteProvider.
+ */
+
+namespace Drupal\node\Entity;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Provides routes for nodes.
+ */
+class NodeRouteProvider implements EntityRouteProviderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function routes( EntityTypeInterface $entity_type) {
+    $route_collection = new RouteCollection();
+    $route = (new Route('/node/{node}'))
+      ->addDefaults([
+        '_content' => '\Drupal\node\Controller\NodeViewController::view',
+        '_title_callback' => '\Drupal\node\Controller\NodeViewController::title',
+      ])
+      ->setRequirement('_entity_access', 'node.view');
+    $route_collection->add('entity.node.canonical', $route);
+
+    $route = (new Route('/node/{node}/delete'))
+      ->addDefaults([
+        '_entity_form' => 'node.delete',
+        '_title' => 'Delete',
+      ])
+      ->setRequirement('_entity_access', 'node.delete')
+      ->setOption('_node_operation_route', TRUE);
+    $route_collection->add('entity.node.delete_form', $route);
+
+    $route = (new Route('/node/{node}/edit'))
+      ->setDefault('_entity_form', 'node.edit')
+      ->setRequirement('_entity_access', 'node.update')
+      ->setOption('_node_operation_route', TRUE);
+    $route_collection->add('entity.node.edit_form', $route);
+
+    return $route_collection;
+  }
+
+}
diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php
index c003db2..47d4c87 100644
--- a/core/modules/user/src/Entity/User.php
+++ b/core/modules/user/src/Entity/User.php
@@ -30,6 +30,9 @@
  *     "list_builder" = "Drupal\user\UserListBuilder",
  *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
  *     "views_data" = "Drupal\user\UserViewsData",
+ *     "route_provider" = {
+ *       "html" = "Drupal\user\Entity\UserRouteProvider",
+ *     },
  *     "form" = {
  *       "default" = "Drupal\user\ProfileForm",
  *       "cancel" = "Drupal\user\Form\UserCancelForm",
diff --git a/core/modules/user/src/Entity/UserRouteProvider.php b/core/modules/user/src/Entity/UserRouteProvider.php
new file mode 100644
index 0000000..abeaeb1
--- /dev/null
+++ b/core/modules/user/src/Entity/UserRouteProvider.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Entity\UserRouteProvider.
+ */
+
+namespace Drupal\user\Entity;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Provides routes for the user entity.
+ */
+class UserRouteProvider implements EntityRouteProviderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function routes(EntityTypeInterface $entity_type) {
+    $route_collection = new RouteCollection();
+    $route = (new Route('/user/{user}'))
+      ->setDefaults([
+        '_entity_view' => 'user.full',
+        '_title_callback' => 'Drupal\user\Controller\UserController::userTitle',
+      ])
+      ->setRequirement('_entity_access', 'user.view');
+    $route_collection->add('entity.user.canonical', $route);
+
+    $route = (new Route('/user/{user}/edit'))
+      ->setDefaults([
+        '_entity_form' => 'user.default',
+        '_title_callback' => 'Drupal\user\Controller\UserController::userTitle',
+      ])
+      ->setOption('_admin_route', TRUE)
+      ->setRequirement('_entity_access', 'user.update');
+    $route_collection->add('entity.user.edit_form', $route);
+
+    $route = (new Route('/user/{user}/cancel'))
+      ->setDefaults([
+        '_title' => 'Cancel account',
+        '_entity_form' => 'user.cancel',
+      ])
+      ->setOption('_admin_route', TRUE)
+      ->setRequirement('_entity_access', 'user.delete');
+    $route_collection->add('entity.user.cancel_form', $route);
+
+    return $route_collection;
+  }
+
+}
diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml
index a036d4b..789cd83 100644
--- a/core/modules/user/user.routing.yml
+++ b/core/modules/user/user.routing.yml
@@ -133,14 +133,6 @@ user.page:
   requirements:
     _access: 'TRUE'
 
-entity.user.canonical:
-  path: '/user/{user}'
-  defaults:
-    _entity_view: 'user.full'
-    _title_callback: 'Drupal\user\Controller\UserController::userTitle'
-  requirements:
-    _entity_access: 'user.view'
-
 user.login:
   path: '/user/login'
   defaults:
@@ -151,26 +143,6 @@ user.login:
   options:
     _maintenance_access: TRUE
 
-entity.user.edit_form:
-  path: '/user/{user}/edit'
-  defaults:
-    _entity_form: 'user.default'
-    _title_callback: 'Drupal\user\Controller\UserController::userTitle'
-  options:
-    _admin_route: TRUE
-  requirements:
-    _entity_access: 'user.update'
-
-entity.user.cancel_form:
-  path: '/user/{user}/cancel'
-  defaults:
-    _title: 'Cancel account'
-    _entity_form: 'user.cancel'
-  options:
-    _admin_route: TRUE
-  requirements:
-    _entity_access: 'user.delete'
-
 user.cancel_confirm:
   path: '/user/{user}/cancel/confirm/{timestamp}/{hashed_pass}'
   defaults:
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
index 3b17502..8d1add5 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php
@@ -1364,6 +1364,24 @@ public function testGetEntityTypeFromClassAmbiguous() {
   }
 
   /**
+   * @covers ::getRouteProviders
+   */
+  public function testGetRouteProviders() {
+    $apple = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
+    $apple->expects($this->once())
+      ->method('getRouteProviderClasses')
+      ->willReturn(['default' => 'Drupal\Tests\Core\Entity\TestRouteProvider']);
+    $this->setUpEntityManager(array(
+      'apple' => $apple,
+    ));
+
+    $apple_route_provider = $this->entityManager->getRouteProviders('apple');
+    $this->assertInstanceOf('Drupal\Tests\Core\Entity\TestRouteProvider', $apple_route_provider['default']);
+    $this->assertAttributeInstanceOf('Drupal\Core\Extension\ModuleHandlerInterface', 'moduleHandler', $apple_route_provider['default']);
+    $this->assertAttributeInstanceOf('Drupal\Core\StringTranslation\TranslationInterface', 'stringTranslation', $apple_route_provider['default']);
+  }
+
+  /**
    * Gets a mock controller class name.
    *
    * @return string
@@ -1544,6 +1562,14 @@ public static function create(ContainerInterface $container) {
 }
 
 /**
+ * Provides a test entity route provider.
+ */
+class TestRouteProvider extends EntityHandlerBase {
+
+}
+
+
+/**
  * Provides a test config entity storage for base field overrides.
  */
 class TestConfigEntityStorage extends ConfigEntityStorage {
