diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php
index 37550a4..b2e3062 100644
--- a/core/lib/Drupal/Core/Url.php
+++ b/core/lib/Drupal/Core/Url.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core;
 
 use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Routing\UrlGeneratorInterface;
@@ -213,7 +214,7 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) {
    *   (optional) An associative array of additional URL options, with the
    *   following elements:
    *   - 'query': An array of query key/value-pairs (without any URL-encoding)
-   *     to append to the URL. Merged with the parameters array.
+   *     to append to the URL.
    *   - 'fragment': A fragment identifier (named anchor) to append to the URL.
    *     Do not include the leading '#' character.
    *   - 'absolute': Defaults to FALSE. Whether to force the output to be an
@@ -240,21 +241,37 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) {
    *
    * @see static::fromRoute()
    */
-  public static function fromUri($uri, $options = array()) {
+  public static function fromUri($uri, $options = []) {
     $uri_parts = parse_url($uri);
     if ($uri_parts === FALSE) {
       throw new \InvalidArgumentException(String::format('The URI "@uri" is malformed.', ['@uri' => $uri]));
     }
     if (empty($uri_parts['scheme'])) {
-      throw new \InvalidArgumentException(String::format('The URI "@uri" is invalid. You must use a valid URI scheme. Use base: for items like a static file that needs the base path. Use user-path: for user input without a scheme. Use entity: for referencing the canonical route of a content entity.', ['@uri' => $uri]));
+      throw new \InvalidArgumentException(String::format('The URI "@uri" is invalid. You must use a valid URI scheme. Use base: for items like a static file that needs the base path. Use user-path: for user input without a scheme. Use entity: for referencing the canonical route of a content entity. Use route: for directly representing a route name and parameters.', ['@uri' => $uri]));
     }
-
     $uri_parts += ['path' => ''];
+    // Extract query parameters and fragment and merge them into $uri_options,
+    // but preserve the original $options for the fallback case.
+    $uri_options = $options;
+    if (!empty($uri_parts['fragment'])) {
+      $uri_options += ['fragment' => $uri_parts['fragment']];
+    }
+    unset($uri_parts['fragment']);
+    if (!empty($uri_parts['query'])) {
+      $uri_query = [];
+      parse_str($uri_parts['query'], $uri_query);
+      $uri_options['query'] = isset($uri_options['query']) ? $uri_options['query'] + $uri_query : $uri_query;
+      unset($uri_parts['query']);
+    }
+
     if ($uri_parts['scheme'] === 'entity') {
-      $url = static::fromEntityUri($uri);
+      $url = static::fromEntityUri($uri_parts, $uri_options, $uri);
     }
     elseif ($uri_parts['scheme'] === 'user-path') {
-      $url = static::fromUserPathUri($uri, $options);
+      $url = static::fromUserPathUri($uri_parts, $uri_options);
+    }
+    elseif ($uri_parts['scheme'] === 'route') {
+      $url = static::fromRouteUri($uri_parts, $uri_options, $uri);
     }
     else {
       $url = new static($uri, array(), $options);
@@ -271,8 +288,13 @@ public static function fromUri($uri, $options = array()) {
   /**
    * Create a new Url object for entity URIs.
    *
+   * @param array $uri_parts
+   *   Parts from an URI of the form entity:{entity_type}/{entity_id} as from
+   *   parse_url().
+   * @param array $options
+   *   An array of options, see static::fromUri() for details.
    * @param string $uri
-   *   An URI of the form entity:{entity_type}/{entity_id}.
+   *   The original entered URI.
    *
    * @return \Drupal\Core\Url
    *   A new Url object for an entity's canonical route.
@@ -280,38 +302,68 @@ public static function fromUri($uri, $options = array()) {
    * @throws \InvalidArgumentException
    *   Thrown if the entity URI is invalid.
    */
-  protected static function fromEntityUri($uri) {
-    $uri_parts = parse_url($uri);
+  protected static function fromEntityUri(array $uri_parts, $options, $uri) {
     list($entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 2);
     if ($uri_parts['scheme'] != 'entity' || $entity_id === '') {
       throw new \InvalidArgumentException(String::format('The entity URI "@uri" is invalid. You must specify the entity id in the URL. e.g., entity:node/1 for loading the canonical path to node entity with id 1.', ['@uri' => $uri]));
     }
 
-    return new static("entity.$entity_type_id.canonical", [$entity_type_id => $entity_id]);
+    return new static("entity.$entity_type_id.canonical", [$entity_type_id => $entity_id], $options);
   }
 
   /**
    * Creates a new Url object for 'user-path:' URIs.
    *
-   * @param string $uri
-   *   An URI of the form user-path:{path}.
+   * @param array $uri_parts
+   *   Parts from an URI of the form user-path:{path} as from parse_url().
    * @param array $options
    *   An array of options, see static::fromUri() for details.
    *
    * @return \Drupal\Core\Url
    *   A new Url object for a 'user-path:' URI.
    */
-  protected static function fromUserPathUri($uri, array $options) {
-    $uri_reference = explode(':', $uri, 2)[1];
+  protected static function fromUserPathUri(array $uri_parts, array $options) {
     $url = \Drupal::pathValidator()
-      ->getUrlIfValidWithoutAccessCheck($uri_reference) ?: static::fromUri('base:' . $uri_reference);
-    // Allow to specify additional or override options from the path.
+      ->getUrlIfValidWithoutAccessCheck($uri_parts['path']) ?: static::fromUri('base:' . $uri_parts['path'], $options);
+    // Allow specifying additional options.
     $url->setOptions($options + $url->getOptions());
 
     return $url;
   }
 
   /**
+   * Creates a new Url object for 'route:' URIs.
+   *
+   * @param array $uri_parts
+   *   Parts from an URI of the form route:{route_name};{route_parameters} as
+   *   from parse_url(), where the path is the route name optionally followed by
+   *   a ";" followed by route parameters in key=value format with & separators.
+   * @param array $options
+   *   An array of options, see static::fromUri() for details.
+   * @param string $uri
+   *   The original passed in URI.
+   *
+   * @return \Drupal\Core\Url
+   *   A new Url object for a 'route:' URI.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown when the route URI does not have a route name.
+   */
+  protected static function fromRouteUri(array $uri_parts, array $options, $uri) {
+    $route_parts = explode(';', $uri_parts['path'], 2);
+    $route_name = $route_parts[0];
+    if ($route_name === '') {
+      throw new \InvalidArgumentException(String::format('The route URI "@uri" is invalid. You must have a route name in the URI. e.g., route:system.admin', ['@uri' => $uri]));
+    }
+    $route_parameters = [];
+    if (!empty($route_parts[1])) {
+      parse_str($route_parts[1], $route_parameters);
+    }
+
+    return new static($route_name, $route_parameters, $options);
+  }
+
+  /**
    * Returns the Url object matching a request.
    *
    * SECURITY NOTE: The request path is not checked to be valid and accessible
@@ -358,6 +410,32 @@ protected function setUnrouted() {
   }
 
   /**
+   * Return a URI string that represents tha data in the Url object.
+   *
+   * The URI will typically have the scheme of route: even if the object was
+   * constructed using an entity: or user-path: scheme.  A user-path: URI
+   * that does not match a Drupal route with be returned here with the base:
+   * scheme, and external URLs will be returned in their original form.
+   *
+   * @return string
+   *   A URI representation of the Url object data.
+   */
+  public function toUriString() {
+    if ($this->isRouted()) {
+      $uri = 'route:' . $this->routeName;
+      if ($this->routeParameters) {
+        $uri .= ';' . UrlHelper::buildQuery($this->routeParameters);
+      }
+    }
+    else {
+      $uri = $this->uri;
+    }
+    $query = !empty($this->options['query']) ? ('?' . UrlHelper::buildQuery($this->options['query'])) : '';
+    $fragment = !empty($this->options['fragment']) ? '#' . $this->options['fragment'] : '';
+    return $uri . $query . $fragment;
+  }
+
+  /**
    * Indicates if this Url is external.
    *
    * @return bool
diff --git a/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php b/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php
index 66433c7..592b76d 100644
--- a/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php
+++ b/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php
@@ -81,6 +81,9 @@ public static function schema(FieldStorageDefinitionInterface $field_definition)
           'serialize' => TRUE,
         ),
       ),
+      'indexes' => array(
+        'uri' => array(array('uri', 30)),
+      ),
     );
   }
 
diff --git a/core/modules/menu_link_content/menu_link_content.install b/core/modules/menu_link_content/menu_link_content.install
new file mode 100644
index 0000000..43c75ec
--- /dev/null
+++ b/core/modules/menu_link_content/menu_link_content.install
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the menu_link_content module.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function menu_link_content_install() {
+  // Add a higher weight so that menu_link_content_path_update() is called after
+  // system_path_update() clears the path alias cache.
+  // @todo remove this when the cache clearing is moved to path module or if
+  //   caching is removed for path aliases due to
+  //   https://www.drupal.org/node/1965074
+  module_set_weight('menu_link_content', 1);
+}
diff --git a/core/modules/menu_link_content/menu_link_content.links.menu.yml b/core/modules/menu_link_content/menu_link_content.links.menu.yml
new file mode 100644
index 0000000..9f4fed2
--- /dev/null
+++ b/core/modules/menu_link_content/menu_link_content.links.menu.yml
@@ -0,0 +1,4 @@
+menu_link_content:
+  class: \Drupal\menu_link_content\Plugin\Menu\MenuLinkContent
+  form_class: \Drupal\menu_link_content\Form\MenuLinkContentForm
+  deriver: \Drupal\menu_link_content\Plugin\Deriver\MenuLinkContentDeriver
diff --git a/core/modules/menu_link_content/menu_link_content.module b/core/modules/menu_link_content/menu_link_content.module
index 6d33dce..79c9935 100644
--- a/core/modules/menu_link_content/menu_link_content.module
+++ b/core/modules/menu_link_content/menu_link_content.module
@@ -36,3 +36,50 @@ function menu_link_content_menu_delete(MenuInterface $menu) {
   $menu_links = $storage->loadByProperties(array('menu_name' => $menu->id()));
   $storage->delete($menu_links);
 }
+
+/**
+ * Implements hook_path_insert().
+ */
+function menu_link_content_path_insert($path) {
+  _menu_link_content_update_path_alias($path['alias']);
+}
+
+/**
+ * Helper function to update plugin definition using user-path scheme.
+ *
+ * @param string $path
+ *   The path alias.
+ *
+ */
+function _menu_link_content_update_path_alias($path) {
+  /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
+  $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
+  // @todo Use link.uri once https://www.drupal.org/node/2391217 is in.
+  /** @var \Drupal\menu_link_content\MenuLinkContentInterface[] $entities */
+  $entities = \Drupal::entityManager()
+    ->getStorage('menu_link_content')
+    ->loadByProperties(['link__uri' => 'user-path:' . $path]);
+  foreach ($entities as $menu_link) {
+    $menu_link_manager->updateDefinition($menu_link->getPluginId(), $menu_link->getPluginDefinition(), FALSE);
+  }
+}
+
+/**
+ * Implements hook_path_update().
+ */
+function menu_link_content_path_update($path) {
+  if ($path['alias'] != $path['original']['alias']) {
+    _menu_link_content_update_path_alias($path['alias']);
+    _menu_link_content_update_path_alias($path['original']['alias']);
+  }
+  elseif ($path['source'] != $path['original']['source']) {
+    _menu_link_content_update_path_alias($path['alias']);
+  }
+}
+
+/**
+ * Implements hook_path_delete().
+ */
+function menu_link_content_path_delete($path) {
+  _menu_link_content_update_path_alias($path['alias']);
+}
diff --git a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
index c9a7fca..3ae1f44 100644
--- a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
+++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
@@ -11,7 +11,6 @@
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
-use Drupal\Core\Url;
 use Drupal\link\LinkItemInterface;
 use Drupal\menu_link_content\MenuLinkContentInterface;
 
@@ -19,12 +18,14 @@
  * Defines the menu link content entity class.
  *
  * @property \Drupal\link\LinkItemInterface link
+ * @property \Drupal\Core\Field\FieldItemList rediscover
  *
  * @ContentEntityType(
  *   id = "menu_link_content",
  *   label = @Translation("Custom menu link"),
  *   handlers = {
  *     "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage",
+ *     "storage_schema" = "Drupal\menu_link_content\MenuLinkContentStorageSchema",
  *     "access" = "Drupal\menu_link_content\MenuLinkContentAccessControlHandler",
  *     "form" = {
  *       "default" = "Drupal\menu_link_content\Form\MenuLinkContentForm",
@@ -182,6 +183,19 @@ public static function preCreate(EntityStorageInterface $storage, array &$values
   /**
    * {@inheritdoc}
    */
+  public function preSave(EntityStorageInterface $storage) {
+    parent::preSave($storage);
+
+    if (parse_url($this->link->uri, PHP_URL_SCHEME) === 'user-path') {
+      $this->setRequiresRediscovery(TRUE);
+    }
+    else {
+      $this->setRequiresRediscovery(FALSE);
+    }
+  }
+  /**
+   * {@inheritdoc}
+   */
   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
     parent::postSave($storage, $update);
 
@@ -297,6 +311,10 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setDescription(t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'))
       ->setSetting('default_value', FALSE);
 
+    $fields['rediscover'] = BaseFieldDefinition::create('boolean')
+      ->setLabel(t('Indicates whether the menu link should be rediscovered'))
+      ->setSetting('default_value', FALSE);
+
     $fields['weight'] = BaseFieldDefinition::create('integer')
       ->setLabel(t('Weight'))
       ->setDescription(t('Link weight among links in the same menu at the same depth. In the menu, the links with high weight will sink and links with a low weight will be positioned nearer the top.'))
@@ -361,4 +379,19 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     return $fields;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function requiresRediscovery() {
+    return $this->get('rediscover')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRequiresRediscovery($rediscovery) {
+    $this->set('rediscover', $rediscovery);
+    return $this;
+  }
+
 }
diff --git a/core/modules/menu_link_content/src/MenuLinkContentInterface.php b/core/modules/menu_link_content/src/MenuLinkContentInterface.php
index 650b3d2..d2b8254 100644
--- a/core/modules/menu_link_content/src/MenuLinkContentInterface.php
+++ b/core/modules/menu_link_content/src/MenuLinkContentInterface.php
@@ -102,4 +102,35 @@ public function getWeight();
    */
   public function getPluginDefinition();
 
+  /**
+   * Returns whether the menu link requires rediscovery.
+   *
+   * If a menu-link points to a user-supplied path such as /blog then the route
+   * this resolves to needs to be rediscovered as the module or route providing
+   * a given path might change over time.
+   *
+   * For example: at the time a menu-link is created, the /blog path might be
+   * provided by a route in Views module, but later this path may be served by
+   * the Panels module. Flagging a link as requiring rediscovery ensures that if
+   * the route that provides a user-entered path changes over time, the link is
+   * flexible enough to update to reflect these changes.
+   *
+   * @return bool
+   *   TRUE if the menu link requires rediscovery during route rebuilding.
+   */
+  public function requiresRediscovery();
+
+  /**
+   * Flags a link as requiring rediscovery.
+   *
+   * @param bool $rediscovery
+   *   Whether or not the link requires rediscovery.
+   *
+   * @return $this
+   *   The instance on which the method was called.
+   *
+   * @see \Drupal\menu_link_content\MenuLinkContentInterface::requiresRediscovery()
+   */
+  public function setRequiresRediscovery($rediscovery);
+
 }
diff --git a/core/modules/menu_link_content/src/MenuLinkContentStorageSchema.php b/core/modules/menu_link_content/src/MenuLinkContentStorageSchema.php
new file mode 100644
index 0000000..96d39b9
--- /dev/null
+++ b/core/modules/menu_link_content/src/MenuLinkContentStorageSchema.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\MenuLinkContentStorageSchema.
+ */
+
+namespace Drupal\menu_link_content;
+
+use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+
+/**
+ * Defines the menu_link_content schema handler.
+ */
+class MenuLinkContentStorageSchema extends SqlContentEntityStorageSchema {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
+    $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
+    $field_name = $storage_definition->getName();
+
+    if ($table_name == 'menu_link_content') {
+      switch ($field_name) {
+        case 'rediscover':
+          $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
+          break;
+      }
+    }
+
+    return $schema;
+  }
+
+}
diff --git a/core/modules/menu_link_content/src/Plugin/Deriver/MenuLinkContentDeriver.php b/core/modules/menu_link_content/src/Plugin/Deriver/MenuLinkContentDeriver.php
new file mode 100644
index 0000000..76c2d33
--- /dev/null
+++ b/core/modules/menu_link_content/src/Plugin/Deriver/MenuLinkContentDeriver.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Plugin\Deriver\MenuLinkContentDeriver.
+ */
+
+namespace Drupal\menu_link_content\Plugin\Deriver;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\Core\Menu\MenuLinkManagerInterface;
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a deriver for user entered paths of menu links.
+ *
+ * The assumption is that the number of manually entered menu links are lower
+ * compared to entity referenced ones.
+ */
+class MenuLinkContentDeriver extends DeriverBase implements ContainerDeriverInterface {
+
+  /**
+   * The query factory.
+   *
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  protected $queryFactory;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * The menu link manager.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkManagerInterface
+   */
+  protected $menuLinkManager;
+
+  /**
+   * Constructs a MenuLinkContentDeriver instance.
+   *
+   * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
+   *   The query factory.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
+   *   The menu link manager.
+   */
+  public function __construct(QueryFactory $query_factory, EntityManagerInterface $entity_manager, MenuLinkManagerInterface $menu_link_manager) {
+    $this->queryFactory = $query_factory;
+    $this->entityManager = $entity_manager;
+    $this->menuLinkManager = $menu_link_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static(
+      $container->get('entity.query'),
+      $container->get('entity.manager'),
+      $container->get('plugin.manager.menu.link')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    // Get all custom menu links which should be rediscovered.
+    $entity_ids = $this->queryFactory->get('menu_link_content')
+      ->condition('rediscover', TRUE)
+      ->execute();
+    $plugin_definitions = [];
+    $menu_link_content_entities = $this->entityManager->getStorage('menu_link_content')->loadMultiple($entity_ids);
+    /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link_content */
+    foreach ($menu_link_content_entities as $menu_link_content) {
+      $plugin_definitions[$menu_link_content->uuid()] = $menu_link_content->getPluginDefinition();
+    }
+    return $plugin_definitions;
+  }
+
+}
diff --git a/core/modules/menu_link_content/src/Tests/MenuLinkContentDeriverTest.php b/core/modules/menu_link_content/src/Tests/MenuLinkContentDeriverTest.php
new file mode 100644
index 0000000..d18a092
--- /dev/null
+++ b/core/modules/menu_link_content/src/Tests/MenuLinkContentDeriverTest.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Tests\MenuLinkContentDeriverTest.
+ */
+
+namespace Drupal\menu_link_content\Tests;
+
+use Drupal\Core\Menu\MenuTreeParameters;
+use Drupal\menu_link_content\Entity\MenuLinkContent;
+use Drupal\simpletest\KernelTestBase;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Tests the menu link content deriver.
+ *
+ * @group menu_link_content
+ */
+class MenuLinkContentDeriverTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['menu_link_content', 'link', 'system', 'menu_link_content_dynamic_route'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('menu_link_content');
+    $this->installSchema('system', 'router');
+  }
+
+  /**
+   * Tests the rediscovering.
+   */
+  public function testRediscover() {
+    \Drupal::state()->set('menu_link_content_dynamic_route.routes', [
+      'route_name_1' => new Route('/example-path'),
+    ]);
+    \Drupal::service('router.builder')->rebuild();
+
+    // Set up a custom menu link pointing to a specific path.
+    MenuLinkContent::create([
+      'title' => 'Example',
+      'link' => [['uri' => 'user-path:example-path']],
+      'menu_name' => 'tools',
+    ])->save();
+    $menu_tree = \Drupal::menuTree()->load('tools', new MenuTreeParameters());
+    $this->assertEqual(1, count($menu_tree));
+    /** @var \Drupal\Core\Menu\MenuLinkTreeElement $tree_element */
+    $tree_element = reset($menu_tree);
+    $this->assertEqual('route_name_1', $tree_element->link->getRouteName());
+
+    // Change the underlying route and trigger the rediscovering.
+    \Drupal::state()->set('menu_link_content_dynamic_route.routes', [
+      'route_name_2' => new Route('/example-path'),
+    ]);
+    \Drupal::service('router.builder')->rebuild();
+
+    // Ensure that the new route name / parameters are captured by the tree.
+    $menu_tree = \Drupal::menuTree()->load('tools', new MenuTreeParameters());
+    $this->assertEqual(1, count($menu_tree));
+    /** @var \Drupal\Core\Menu\MenuLinkTreeElement $tree_element */
+    $tree_element = reset($menu_tree);
+    $this->assertEqual('route_name_2', $tree_element->link->getRouteName());
+  }
+
+}
diff --git a/core/modules/menu_link_content/src/Tests/PathAliasMenuLinkContentTest.php b/core/modules/menu_link_content/src/Tests/PathAliasMenuLinkContentTest.php
new file mode 100644
index 0000000..78fe0e3
--- /dev/null
+++ b/core/modules/menu_link_content/src/Tests/PathAliasMenuLinkContentTest.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Tests\PathAliasMenuLinkContentTest.
+ */
+
+namespace Drupal\menu_link_content\Tests;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Menu\MenuTreeParameters;
+use Drupal\menu_link_content\Entity\MenuLinkContent;
+use Drupal\simpletest\KernelTestBase;
+
+/**
+ * Ensures that the menu tree adapts to path alias changes.
+ *
+ * @group menu_link_content
+ */
+class PathAliasMenuLinkContentTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['menu_link_content', 'system', 'link', 'test_page_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('menu_link_content');
+    $this->installSchema('system', ['url_alias', 'router']);
+
+    // Ensure that the weight of module_link_content is higher than system.
+    // @see menu_link_content_install()
+    module_set_weight('menu_link_content', 1);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function containerBuild(ContainerBuilder $container) {
+    parent::containerBuild($container);
+
+    $definition = $container->getDefinition('path_processor_alias');
+    $definition
+      ->addTag('path_processor_inbound', ['priority' => 100]);
+  }
+
+
+  /**
+   * Tests the path aliasing changing.
+   */
+  public function testPathAliasChange() {
+    \Drupal::service('router.builder')->rebuild();
+
+    /** @var \Drupal\Core\Path\AliasStorageInterface $path_alias_storage */
+    $path_alias_storage = \Drupal::service('path.alias_storage');
+    $alias = $path_alias_storage->save('test-page', 'my-blog');
+    $pid = $alias['pid'];
+
+    $menu_link_content = MenuLinkContent::create([
+      'title' => 'Menu title',
+      'link' => ['uri' => 'user-path:my-blog'],
+      'menu_name' => 'tools',
+    ]);
+    $menu_link_content->save();
+
+    $tree = \Drupal::menuTree()->load('tools', new MenuTreeParameters());
+    $this->assertEqual('test_page_test.test_page', $tree[$menu_link_content->getPluginId()]->link->getPluginDefinition()['route_name']);
+
+    // Saving an alias should clear the alias manager cache.
+    $path_alias_storage->save('test-render-title', 'my-blog', LanguageInterface::LANGCODE_NOT_SPECIFIED, $pid);
+
+    $tree = \Drupal::menuTree()->load('tools', new MenuTreeParameters());
+    $this->assertEqual('test_page_test.render_title', $tree[$menu_link_content->getPluginId()]->link->getPluginDefinition()['route_name']);
+
+    // Delete the alias.
+    $path_alias_storage->delete(['pid' => $pid]);
+    $tree = \Drupal::menuTree()->load('tools', new MenuTreeParameters());
+    $this->assertTrue(isset($tree[$menu_link_content->getPluginId()]));
+    $this->assertEqual('', $tree[$menu_link_content->getPluginId()]->link->getRouteName());
+    // Verify the plugin now references a path that does not match any route.
+    $this->assertEqual('base:my-blog', $tree[$menu_link_content->getPluginId()]->link->getUrlObject()->getUri());
+  }
+
+}
diff --git a/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.info.yml b/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.info.yml
new file mode 100644
index 0000000..fa9a0fb
--- /dev/null
+++ b/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.info.yml
@@ -0,0 +1,6 @@
+name: 'Menu link content dynamic route'
+type: module
+core: 8.x
+hidden: true
+dependencies:
+  - menu_link_content
diff --git a/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.routing.yml b/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.routing.yml
new file mode 100644
index 0000000..959cb09
--- /dev/null
+++ b/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/menu_link_content_dynamic_route.routing.yml
@@ -0,0 +1,2 @@
+route_callbacks:
+  - 'Drupal\menu_link_content_dynamic_route\Routes::dynamic'
diff --git a/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/src/Routes.php b/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/src/Routes.php
new file mode 100644
index 0000000..979790f
--- /dev/null
+++ b/core/modules/menu_link_content/tests/menu_link_content_dynamic_route/src/Routes.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content_dynamic_route\Routes.
+ */
+
+namespace Drupal\menu_link_content_dynamic_route;
+
+/**
+ * Provides dynamic routes for test purposes.
+ */
+class Routes {
+
+  public function dynamic() {
+    return \Drupal::state()->get('menu_link_content_dynamic_route.routes', []);
+  }
+
+}
diff --git a/core/modules/node/config/install/views.view.content_recent.yml b/core/modules/node/config/install/views.view.content_recent.yml
index 9456436..4bbe54f 100644
--- a/core/modules/node/config/install/views.view.content_recent.yml
+++ b/core/modules/node/config/install/views.view.content_recent.yml
@@ -62,7 +62,6 @@ display:
           description: ''
           columns:
             title: title
-            timestamp: title
             name: title
             edit_node: edit_node
             delete_node: delete_node
@@ -74,13 +73,6 @@ display:
               separator: ''
               empty_column: false
               responsive: ''
-            timestamp:
-              sortable: false
-              default_sort_order: asc
-              align: ''
-              separator: ''
-              empty_column: false
-              responsive: ''
             name:
               sortable: false
               default_sort_order: asc
@@ -111,38 +103,26 @@ display:
           id: title
           table: node_field_data
           field: title
-          relationship: none
-          group_type: group
-          admin_label: ''
+          entity_type: node
+          entity_field: title
           label: ''
           exclude: false
           alter:
             alter_text: false
-            text: ''
             make_link: false
-            path: ''
             absolute: false
-            external: false
-            replace_spaces: false
-            path_case: none
-            trim_whitespace: false
-            alt: ''
-            rel: ''
-            link_class: ''
-            prefix: ''
-            suffix: ''
-            target: ''
-            nl2br: false
-            max_length: ''
+            trim: false
             word_boundary: false
             ellipsis: false
-            more_link: false
-            more_link_text: ''
-            more_link_path: ''
             strip_tags: false
-            trim: false
-            preserve_tags: ''
             html: false
+          hide_empty: false
+          empty_zero: false
+          link_to_node: true
+          plugin_id: node
+          relationship: none
+          group_type: group
+          admin_label: ''
           element_type: ''
           element_class: ''
           element_label_type: ''
@@ -152,13 +132,7 @@ display:
           element_wrapper_class: ''
           element_default_classes: true
           empty: ''
-          hide_empty: false
-          empty_zero: false
           hide_alter_empty: true
-          link_to_node: true
-          plugin_id: node
-          entity_type: node
-          entity_field: title
         name:
           id: name
           table: users_field_data
@@ -211,9 +185,9 @@ display:
           overwrite_anonymous: false
           anonymous_text: ''
           format_username: true
-          plugin_id: user_name
           entity_type: user
           entity_field: name
+          plugin_id: user_name
         edit_node:
           id: edit_node
           table: node
@@ -262,9 +236,9 @@ display:
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
-          text: edit
-          plugin_id: node_link_edit
+          text: Edit
           entity_type: node
+          plugin_id: node_link_edit
         delete_node:
           id: delete_node
           table: node
@@ -313,9 +287,9 @@ display:
           hide_empty: false
           empty_zero: false
           hide_alter_empty: true
-          text: delete
-          plugin_id: node_link_delete
+          text: Delete
           entity_type: node
+          plugin_id: node_link_delete
       filters:
         status_extra:
           id: status_extra
@@ -329,7 +303,7 @@ display:
           group: 1
           exposed: false
           expose:
-            operator_id: '0'
+            operator_id: ''
             label: ''
             description: ''
             use_operator: false
@@ -352,8 +326,8 @@ display:
             default_group: All
             default_group_multiple: {  }
             group_items: {  }
-          plugin_id: node_status
           entity_type: node
+          plugin_id: node_status
         langcode:
           id: langcode
           table: node_field_data
@@ -391,9 +365,9 @@ display:
             default_group: All
             default_group_multiple: {  }
             group_items: {  }
-          plugin_id: language
           entity_type: node
           entity_field: langcode
+          plugin_id: language
       sorts:
         changed:
           id: changed
@@ -407,9 +381,9 @@ display:
           expose:
             label: ''
           granularity: second
-          plugin_id: date
           entity_type: node
           entity_field: changed
+          plugin_id: date
       title: 'Recent content'
       header: {  }
       footer: {  }
@@ -434,24 +408,24 @@ display:
           group_type: group
           admin_label: author
           required: true
+          entity_type: node
+          entity_field: uid
           plugin_id: standard
       arguments: {  }
-      filter_groups:
-        operator: AND
-        groups: {  }
+      display_extenders: {  }
+      field_langcode: '***LANGUAGE_language_content***'
+      field_langcode_add_to_query: null
       use_more: true
       use_more_always: true
       use_more_text: More
+      link_url: admin/content
       link_display: custom_url
-      field_langcode: '***LANGUAGE_language_content***'
-      field_langcode_add_to_query: null
   block_1:
     display_plugin: block
     id: block_1
     display_title: Block
     position: 1
     display_options:
-      link_url: admin/content
-      block_category: 'Lists (Views)'
+      display_extenders: {  }
       field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
diff --git a/core/modules/node/src/Tests/NodeBlockFunctionalTest.php b/core/modules/node/src/Tests/NodeBlockFunctionalTest.php
index 2ff5981..5f241c0 100644
--- a/core/modules/node/src/Tests/NodeBlockFunctionalTest.php
+++ b/core/modules/node/src/Tests/NodeBlockFunctionalTest.php
@@ -39,7 +39,7 @@ protected function setUp() {
     parent::setUp();
 
     // Create users and test node.
-    $this->adminUser = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer blocks'));
+    $this->adminUser = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer blocks', 'access content overview'));
     $this->webUser = $this->drupalCreateUser(array('access content', 'create article content'));
   }
 
@@ -99,6 +99,12 @@ public function testRecentNodeBlock() {
     $this->drupalLogout();
     $this->drupalLogin($this->adminUser);
 
+    // Verify that the More link is shown and leads to the admin content page.
+    $this->drupalGet('');
+    $this->clickLink('More');
+    $this->assertResponse('200');
+    $this->assertUrl('admin/content');
+
     // Set the number of recent nodes to show to 10.
     $block->getPlugin()->setConfigurationValue('items_per_page', 10);
     $block->save();
diff --git a/core/modules/system/src/Tests/Common/UrlTest.php b/core/modules/system/src/Tests/Common/UrlTest.php
index 45ae2d3..0b8a223 100644
--- a/core/modules/system/src/Tests/Common/UrlTest.php
+++ b/core/modules/system/src/Tests/Common/UrlTest.php
@@ -265,7 +265,7 @@ function testExternalUrls() {
     // Verify external URL can contain a query string.
     $url = $test_url . '?drupal=awesome';
     $result = Url::fromUri($url)->toString();
-    $this->assertEqual($url, $result, 'External URL with query string works without a query string in $options.');
+    $this->assertEqual($url, $result);
 
     // Verify external URL can be extended with a query string.
     $url = $test_url;
@@ -277,6 +277,6 @@ function testExternalUrls() {
     $url = $test_url . '?drupal=awesome';
     $query = array($this->randomMachineName(5) => $this->randomMachineName(5));
     $result = Url::fromUri($url, array('query' => $query))->toString();
-    $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, 'External URL query string can be extended with a custom query string in $options.');
+    $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result);
   }
 }
diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php
index 9d883d7..b26feb8 100644
--- a/core/tests/Drupal/Tests/Core/UrlTest.php
+++ b/core/tests/Drupal/Tests/Core/UrlTest.php
@@ -9,7 +9,6 @@
 
 use Drupal\Core\Access\AccessManagerInterface;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\Core\Entity\EntityType;
 use Drupal\Core\Routing\RouteMatch;
 use Drupal\Core\Url;
 use Drupal\Tests\UnitTestCase;
@@ -18,7 +17,6 @@
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Exception\InvalidParameterException;
 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
-use Symfony\Component\Routing\Exception\RouteNotFoundException;
 use Symfony\Component\Routing\Route;
 
 /**
@@ -481,14 +479,50 @@ public function testFromRouteMatch() {
   }
 
   /**
+   * Data provider for testing entity URIs
+   */
+  public function providerTestEntityUris() {
+    return [
+      [
+        'entity:test_entity/1',
+        [],
+        'entity.test_entity.canonical',
+        ['test_entity' => '1'],
+        NULL,
+        NULL,
+      ],
+      [
+        'entity:test_entity/2?page=1&foo=bar#bottom',
+        [], 'entity.test_entity.canonical',
+        ['test_entity' => '2'],
+        ['page' => '1', 'foo' => 'bar'],
+        'bottom',
+      ],
+      [
+        'entity:test_entity/2?page=1&foo=bar#bottom',
+        ['fragment' => 'top', 'query' => ['foo' => 'yes', 'focus' => 'no']],
+        'entity.test_entity.canonical',
+        ['test_entity' => '2'],
+        ['page' => '1', 'foo' => 'yes', 'focus' => 'no'],
+        'top',
+      ],
+
+    ];
+  }
+
+  /**
    * Tests the fromUri() method with an entity: URI.
    *
    * @covers ::fromUri
+   *
+   * @dataProvider providerTestEntityUris
    */
-  public function testEntityUris() {
-    $url = Url::fromUri('entity:test_entity/1');
-    $this->assertSame('entity.test_entity.canonical', $url->getRouteName());
-    $this->assertEquals(['test_entity' => '1'], $url->getRouteParameters());
+  public function testEntityUris($uri, $options, $route_name, $route_parameters, $query, $fragment) {
+    $url = Url::fromUri($uri, $options);
+    $this->assertSame($route_name, $url->getRouteName());
+    $this->assertEquals($route_parameters, $url->getRouteParameters());
+    $this->assertEquals($url->getOption('query'), $query);
+    $this->assertSame($url->getOption('fragment'), $fragment);
   }
 
   /**
@@ -508,6 +542,89 @@ public function testInvalidEntityUriParameter() {
   }
 
   /**
+   * Tests the toUriString() method with entity: URIs.
+   *
+   * @covers ::toUriString
+   *
+   * @dataProvider providerTestToUriStringForEntity
+   */
+  public function testToUriStringForEntity($uri, $options, $uri_string) {
+    $url = Url::fromUri($uri, $options);
+    $this->assertSame($url->toUriString(), $uri_string);
+  }
+
+  /**
+   * Data provider for testing string entity URIs
+   */
+  public function providerTestToUriStringForEntity() {
+    return [
+      ['entity:test_entity/1', [], 'route:entity.test_entity.canonical;test_entity=1'],
+      ['entity:test_entity/1', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
+      ['entity:test_entity/1?page=2#top', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
+    ];
+  }
+
+  /**
+   * Tests the toUriString() method with user-path: URIs.
+   *
+   * @covers ::toUriString
+   *
+   * @dataProvider providerTestToUriStringForUserPath
+   */
+  public function testToUriStringForUserPath($uri, $options, $uri_string) {
+    $url = Url::fromRoute('entity.test_entity.canonical', ['test_entity' => '1']);
+    $this->pathValidator->expects($this->any())
+      ->method('getUrlIfValidWithoutAccessCheck')
+      ->with('test-entity/1')
+      ->willReturn($url);
+    $url = Url::fromUri($uri, $options);
+    $this->assertSame($url->toUriString(), $uri_string);
+  }
+
+  /**
+   * Data provider for testing user-path URIs
+   */
+  public function providerTestToUriStringForUserPath() {
+    return [
+      ['user-path:test-entity/1', [], 'route:entity.test_entity.canonical;test_entity=1'],
+      ['user-path:test-entity/1', ['fragment' => 'top'], 'route:entity.test_entity.canonical;test_entity=1#top'],
+      ['user-path:test-entity/1', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
+      ['user-path:test-entity/1?page=2#top', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
+    ];
+  }
+
+  /**
+   * Tests the toUriString() method with route: URIs.
+   *
+   * @covers ::toUriString
+   *
+   * @dataProvider providerTestToUriStringForRoute
+   */
+  public function testToUriStringForRoute($uri, $options, $uri_string) {
+    $url = Url::fromUri($uri, $options);
+    $this->assertSame($url->toUriString(), $uri_string);
+  }
+
+  /**
+   * Data provider for testing user-path URIs
+   */
+  public function providerTestToUriStringForRoute() {
+    return [
+      ['route:entity.test_entity.canonical;test_entity=1', [], 'route:entity.test_entity.canonical;test_entity=1'],
+      ['route:entity.test_entity.canonical;test_entity=1', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
+      ['route:entity.test_entity.canonical;test_entity=1?page=2#top', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
+    ];
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   * @expectedExceptionMessage The route URI "route:" is invalid.
+   */
+  public function testFromRouteUriWithMissingRouteName() {
+    Url::fromUri('route:');
+  }
+
+  /**
    * Creates a mock access manager for the access tests.
    *
    * @param bool $access
