diff --git a/config/schema/page_manager.schema.yml b/config/schema/page_manager.schema.yml
index 649a25f..1fe3464 100644
--- a/config/schema/page_manager.schema.yml
+++ b/config/schema/page_manager.schema.yml
@@ -102,6 +102,14 @@ page_manager.block_plugin.*:
       sequence:
         - type: string
 
+# @todo Move to core in https://www.drupal.org/node/2838130.
+display_variant.plugin.*:
+  type: display_variant.plugin
+  label: 'Variant settings'
+condition.plugin.*:
+  type: condition.plugin
+  label: 'Condition settings'
+
 display_variant.plugin.block_display:
   type: display_variant.plugin
   label: 'Block variant plugin'
diff --git a/page_manager.services.yml b/page_manager.services.yml
index 43f99ab..b1669e7 100644
--- a/page_manager.services.yml
+++ b/page_manager.services.yml
@@ -19,9 +19,10 @@ services:
       - { name: 'event_subscriber' }
   page_manager.variant_route_filter:
     class: Drupal\page_manager\Routing\VariantRouteFilter
-    arguments: ['@entity_type.manager', '@path.current']
+    arguments: ['@entity_type.manager', '@path.current', '@request_stack']
     tags:
-      - { name: route_filter }
+      # Run as late as possible to allow all other filters to run first.
+      - { name: non_lazy_route_filter, priority: -1024 }
       - { name: service_collector, tag: non_lazy_route_enhancer, call: addRouteEnhancer }
   page_manager.route_name_response_subscriber:
     class: Drupal\page_manager\EventSubscriber\RouteNameResponseSubscriber
diff --git a/src/Entity/Page.php b/src/Entity/Page.php
index f77ed26..7558231 100644
--- a/src/Entity/Page.php
+++ b/src/Entity/Page.php
@@ -367,7 +367,9 @@ public function getContexts() {
           }
           else {
             $this->contexts[$machine_name]->getContextDefinition()->setDataType($configuration['type']);
-            $this->contexts[$machine_name]->getContextDefinition()->setLabel($configuration['label']);
+            if (isset($configuration['label'])) {
+              $this->contexts[$machine_name]->getContextDefinition()->setLabel($configuration['label']);
+            }
           }
         }
       }
diff --git a/src/EventSubscriber/RouteParamContext.php b/src/EventSubscriber/RouteParamContext.php
index d7d279c..8680878 100644
--- a/src/EventSubscriber/RouteParamContext.php
+++ b/src/EventSubscriber/RouteParamContext.php
@@ -71,7 +71,7 @@ public function onPageContext(PageManagerContextEvent $event) {
         }
 
         $parameter = $page->getParameter($route_context_name);
-        $context_name = $parameter['label'] ?: $this->t('{@name} from route', ['@name' => $route_context_name]);
+        $context_name = !empty($parameter['label']) ? $parameter['label'] : $this->t('{@name} from route', ['@name' => $route_context_name]);
         if ($request->attributes->has($route_context_name)) {
           $value = $request->attributes->get($route_context_name);
         }
diff --git a/src/Routing/PageManagerRoutes.php b/src/Routing/PageManagerRoutes.php
index 793d3c8..d4d6799 100644
--- a/src/Routing/PageManagerRoutes.php
+++ b/src/Routing/PageManagerRoutes.php
@@ -62,18 +62,17 @@ protected function alterRoutes(RouteCollection $collection) {
 
       $parameters = [];
       $requirements = [];
-      if ($route_name = $this->findPageRouteName($entity, $collection)) {
-        $this->cacheTagsInvalidator->invalidateTags(["page_manager_route_name:$route_name"]);
+      $route_name = "page_manager.page_view_$entity_id";
+      if ($base_route_name = $this->findBaseRouteName($entity, $collection)) {
+        $this->cacheTagsInvalidator->invalidateTags(["page_manager_route_name:$base_route_name"]);
 
-        $collection_route = $collection->get($route_name);
+        $collection_route = $collection->get($base_route_name);
         $path = $collection_route->getPath();
         $parameters = $collection_route->getOption('parameters') ?: [];
         $requirements = $collection_route->getRequirements();
-
-        $collection->remove($route_name);
       }
       else {
-        $route_name = "page_manager.page_view_$entity_id";
+        $base_route_name = $route_name;
         $path = $entity->getPath();
       }
 
@@ -89,7 +88,6 @@ protected function alterRoutes(RouteCollection $collection) {
       $requirements['_page_access'] = 'page_manager_page.view';
 
       $page_id = $entity->id();
-      $first = TRUE;
       foreach ($entity->getVariants() as $variant_id => $variant) {
         // Construct and add a new route.
         $route = new Route(
@@ -103,7 +101,7 @@ protected function alterRoutes(RouteCollection $collection) {
             // When adding multiple variants, the variant ID is added to the
             // route name. In order to convey the base route name for this set
             // of variants, add it as a parameter.
-            'base_route_name' => $route_name,
+            'base_route_name' => $base_route_name,
           ],
           $requirements,
           [
@@ -111,14 +109,13 @@ protected function alterRoutes(RouteCollection $collection) {
             '_admin_route' => $entity->usesAdminTheme(),
           ]
         );
-        $collection->add($first ? $route_name : $route_name . '_' . $variant_id, $route);
-        $first = FALSE;
+        $collection->add($route_name . '_' . $variant_id, $route);
       }
     }
   }
 
   /**
-   * Finds the overridden route name.
+   * Finds the base route name.
    *
    * @param \Drupal\page_manager\PageInterface $entity
    *   The page entity.
@@ -128,7 +125,7 @@ protected function alterRoutes(RouteCollection $collection) {
    * @return string|null
    *   Either the route name if this is overriding an existing path, or NULL.
    */
-  protected function findPageRouteName(PageInterface $entity, RouteCollection $collection) {
+  protected function findBaseRouteName(PageInterface $entity, RouteCollection $collection) {
     // Get the stored page path.
     $path = $entity->getPath();
 
@@ -139,8 +136,11 @@ protected function findPageRouteName(PageInterface $entity, RouteCollection $col
       $route_path_outline = RouteCompiler::getPatternOutline($route_path);
 
       // Match either the path or the outline, e.g., '/foo/{foo}' or '/foo/%'.
-      if ($path === $route_path || $path === $route_path_outline) {
-        // Return the overridden route name.
+      // The route must be a GET route and must not specify a format.
+      if (($path === $route_path || $path === $route_path_outline) &&
+        (!$collection_route->getMethods() || in_array('GET', $collection_route->getMethods())) &&
+        !$collection_route->hasRequirement('_format')) {
+        // Return the base route name.
         return $name;
       }
     }
diff --git a/src/Routing/VariantRouteFilter.php b/src/Routing/VariantRouteFilter.php
index 6d2abcc..31e3eb3 100644
--- a/src/Routing/VariantRouteFilter.php
+++ b/src/Routing/VariantRouteFilter.php
@@ -10,9 +10,11 @@
 use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\ParamConverter\ParamNotConvertedException;
 use Drupal\Core\Path\CurrentPathStack;
 use Drupal\Core\Routing\RouteFilterInterface;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
 
@@ -42,16 +44,26 @@ class VariantRouteFilter implements RouteFilterInterface {
   protected $currentPath;
 
   /**
+   * The current request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
    * Constructs a new VariantRouteFilter.
    *
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   The entity type manager.
    * @param \Drupal\Core\Path\CurrentPathStack $current_path
    *   The current path stack.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The current request stack.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, CurrentPathStack $current_path) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, CurrentPathStack $current_path, RequestStack $request_stack) {
     $this->pageVariantStorage = $entity_type_manager->getStorage('page_variant');
     $this->currentPath = $current_path;
+    $this->requestStack = $request_stack;
   }
 
   /**
@@ -65,59 +77,28 @@ public function applies(Route $route) {
   /**
    * {@inheritdoc}
    *
-   * Invalid page manager routes will be removed. Routes not controlled by page
-   * manager will be moved to the end of the collection. Once a valid page
-   * manager route has been found, all other page manager routes will also be
-   * removed.
+   * Ensures only one page manager route remains in the collection.
    */
   public function filter(RouteCollection $collection, Request $request) {
-    // Only proceed if the collection is non-empty.
-    if (!$collection->count()) {
-      return $collection;
-    }
-
-    // Store the unaltered request attributes.
-    $original_attributes = $request->attributes->all();
-
-    // First get all routes and sort them by variant weight. Note that routes
-    // without a weight will have an undefined order, they are ignored here.
+    // Sort routes by variant weight.
     $routes = $collection->all();
     uasort($routes, [$this, 'routeWeightSort']);
 
-    // Find the first route that is accessible.
-    $accessible_route_name = NULL;
-    foreach ($routes as $name => $route) {
-      $attributes = $this->getRequestAttributes($route, $name, $request);
-      // Add the enhanced attributes to the request.
-      $request->attributes->add($attributes);
-      if ($page_variant_id = $route->getDefault('page_manager_page_variant')) {
-        if ($this->checkPageVariantAccess($page_variant_id)) {
-          // Access granted, use this route. Do not restore request attributes
-          // but keep those from this route by breaking out.
-          $accessible_route_name = $name;
-          break;
-        }
+    $variant_route_name = $this->getVariantRouteName($routes, $request);
+    foreach ($collection as $name => $route) {
+      if (!$route->hasDefault('page_manager_page_variant')) {
+        continue;
       }
 
-      // Restore the original request attributes, this must be done in the loop
-      // or the request attributes will not be calculated correctly for the
-      // next route.
-      $request->attributes->replace($original_attributes);
-    }
+      $base_route_name = $route->getDefault('base_route_name');
 
-    // Because the sort order of $routes is unreliable for a route without a
-    // variant weight, rely on the original order of $collection here.
-    foreach ($collection as $name => $route) {
-      if ($route->getDefault('page_manager_page_variant')) {
-        if ($accessible_route_name !== $name) {
-          // Remove all other page manager routes.
-          $collection->remove($name);
-        }
+      // If this page manager route isn't the one selected, remove it.
+      if ($variant_route_name !== $name) {
+        $collection->remove($name);
       }
-      else {
-        // This is not page manager route, move it to the end of the collection,
-        // those will only be used if there is no accessible variant route.
-        $collection->add($name, $route);
+      // If the selected route has a base route, move that to the end.
+      elseif ($base_route_name !== $name && ($base_route = $collection->get($base_route_name))) {
+        $collection->add($base_route_name, $base_route);
       }
     }
 
@@ -125,21 +106,49 @@ public function filter(RouteCollection $collection, Request $request) {
   }
 
   /**
+   * Gets the route name of the first valid variant.
+   *
+   * @param \Symfony\Component\Routing\Route[] $routes
+   *   An array of sorted routes.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   A current request.
+   *
+   * @return string|null
+   *   A route name, or NULL if none are found.
+   */
+  protected function getVariantRouteName(array $routes, Request $request) {
+    // Store the unaltered request attributes.
+    $original_attributes = $request->attributes->all();
+    foreach ($routes as $name => $route) {
+      if (!$page_variant_id = $route->getDefault('page_manager_page_variant')) {
+        continue;
+      }
+
+      if ($attributes = $this->getRequestAttributes($route, $name, $request)) {
+        // Add the enhanced attributes to the request.
+        $request->attributes->add($attributes);
+        $this->requestStack->push($request);
+
+        if ($this->checkPageVariantAccess($page_variant_id)) {
+          $this->requestStack->pop();
+          return $name;
+        }
+
+        // Restore the original request attributes, this must be done in the loop
+        // or the request attributes will not be calculated correctly for the
+        // next route.
+        $request->attributes->replace($original_attributes);
+        $this->requestStack->pop();
+      }
+    }
+  }
+
+  /**
    * Sort callback for routes based on the variant weight.
    */
   protected function routeWeightSort(Route $a, Route $b) {
     $a_weight = $a->getDefault('page_manager_page_variant_weight');
     $b_weight = $b->getDefault('page_manager_page_variant_weight');
-    if ($a_weight === $b_weight) {
-      return 0;
-    }
-    elseif ($a_weight === NULL) {
-      return 1;
-    }
-    elseif ($b_weight === NULL) {
-      return -1;
-    }
-
     return ($a_weight < $b_weight) ? -1 : 1;
   }
 
@@ -195,7 +204,12 @@ protected function getRequestAttributes(Route $route, $name, Request $request) {
     // Run the route enhancers on the raw attributes. This performs the same
     // functionality as \Symfony\Cmf\Component\Routing\DynamicRouter::match().
     foreach ($this->getRouteEnhancers() as $enhancer) {
-      $attributes = $enhancer->enhance($attributes, $request);
+      try {
+        $attributes = $enhancer->enhance($attributes, $request);
+      }
+      catch (ParamNotConvertedException $e) {
+        $attributes = [];
+      }
     }
 
     return $attributes;
diff --git a/src/Tests/PageManagerTranslationIntegrationTest.php b/src/Tests/PageManagerTranslationIntegrationTest.php
deleted file mode 100644
index 7ba06dc..0000000
--- a/src/Tests/PageManagerTranslationIntegrationTest.php
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\page_manager\Tests\PageManagerTranslationIntegrationTest.
- */
-
-namespace Drupal\page_manager\Tests;
-
-use Drupal\content_translation\Tests\ContentTranslationTestBase;
-use Drupal\page_manager\Entity\PageVariant;
-
-/**
- * Tests that overriding the entity page does not affect content translation.
- *
- * @group page_manager
- */
-class PageManagerTranslationIntegrationTest extends ContentTranslationTestBase {
-
-  use PageTestHelperTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = ['block', 'page_manager', 'node', 'content_translation'];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $entityTypeId = 'node';
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $bundle = 'article';
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setupBundle() {
-    parent::setupBundle();
-    $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function getTranslatorPermissions() {
-    return array_merge(parent::getTranslatorPermissions(), ['administer pages', 'administer pages']);
-  }
-
-  /**
-   * Tests that overriding the node page does not prevent translation.
-   */
-  public function testNode() {
-    $this->drupalPlaceBlock('local_tasks_block');
-    $this->drupalPlaceBlock('page_title_block');
-
-    $node = $this->drupalCreateNode(['type' => 'article']);
-    $this->drupalGet('node/' . $node->id());
-    $this->assertResponse(200);
-    $this->assertText($node->label());
-    $this->clickLink('Translate');
-    $this->assertResponse(200);
-
-    // Create a new variant.
-    $http_status_variant = PageVariant::create([
-      'variant' => 'http_status_code',
-      'label' => 'HTTP status code',
-      'id' => 'http_status_code',
-      'page' => 'node_view',
-    ]);
-    $http_status_variant->getVariantPlugin()->setConfiguration(['status_code' => 200]);
-    $http_status_variant->save();
-    $this->triggerRouterRebuild();
-
-    $this->drupalGet('node/' . $node->id());
-    $this->assertResponse(200);
-    $this->clickLink('Translate');
-    $this->assertResponse(200);
-  }
-
-}
diff --git a/tests/modules/page_manager_routing_test/page_manager_routing_test.info.yml b/tests/modules/page_manager_routing_test/page_manager_routing_test.info.yml
new file mode 100644
index 0000000..c0e20c7
--- /dev/null
+++ b/tests/modules/page_manager_routing_test/page_manager_routing_test.info.yml
@@ -0,0 +1,4 @@
+type: module
+name: Page Manager Routing Test
+description: 'Required for Page Manager tests.'
+core: 8.x
diff --git a/tests/modules/page_manager_routing_test/page_manager_routing_test.services.yml b/tests/modules/page_manager_routing_test/page_manager_routing_test.services.yml
new file mode 100644
index 0000000..e09db3b
--- /dev/null
+++ b/tests/modules/page_manager_routing_test/page_manager_routing_test.services.yml
@@ -0,0 +1,6 @@
+services:
+  page_manager_routing_test.subscriber:
+    class: Drupal\page_manager_routing_test\Routing\RouteSubscriber
+    arguments: ['@entity.manager']
+    tags:
+     - { name: event_subscriber }
diff --git a/tests/modules/page_manager_routing_test/src/Plugin/Condition/EntityTestCondition.php b/tests/modules/page_manager_routing_test/src/Plugin/Condition/EntityTestCondition.php
new file mode 100644
index 0000000..eb37701
--- /dev/null
+++ b/tests/modules/page_manager_routing_test/src/Plugin/Condition/EntityTestCondition.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\page_manager_routing_test\Plugin\Condition;
+
+use Drupal\Core\Condition\ConditionPluginBase;
+
+/**
+ * @todo.
+ *
+ * @Condition(
+ *   id = "page_manager_routing_test__entity_test",
+ *   label = @Translation("Entity Test"),
+ *   context = {
+ *     "entity_test" = @ContextDefinition("entity:entity_test")
+ *   }
+ * )
+ */
+class EntityTestCondition extends ConditionPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function evaluate() {
+    return (bool) $this->getContext('entity_test');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function summary() {
+    return '';
+  }
+
+}
diff --git a/tests/modules/page_manager_routing_test/src/Routing/RouteSubscriber.php b/tests/modules/page_manager_routing_test/src/Routing/RouteSubscriber.php
new file mode 100644
index 0000000..41ff7bc
--- /dev/null
+++ b/tests/modules/page_manager_routing_test/src/Routing/RouteSubscriber.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Drupal\page_manager_routing_test\Routing;
+
+use Drupal\Core\Routing\RouteBuildEvent;
+use Drupal\Core\Routing\RoutingEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Route subscriber for Page Manager Routing Test.
+ */
+class RouteSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Alters the existing route collection.
+   *
+   * @param \Drupal\Core\Routing\RouteBuildEvent $event
+   *   The route build event.
+   */
+  public function beforePageManagerRoutes(RouteBuildEvent $event) {
+    $collection = $event->getRouteCollection();
+    $route = new Route('/entity_test/{entity_test}', [], ['_access' => 'TRUE']);
+    $route->setRequirement('_format', 'xml');
+    $collection->add('entity.entity_test.canonical.xml', $route);
+  }
+
+  /**
+   * Alters the existing route collection.
+   *
+   * @param \Drupal\Core\Routing\RouteBuildEvent $event
+   *   The route build event.
+   */
+  public function afterPageManagerRoutes(RouteBuildEvent $event) {
+    $collection = $event->getRouteCollection();
+    if ($original_route = $collection->get('entity.entity_test.canonical')) {
+      $route = new Route($original_route->getPath(), $original_route->getDefaults(), $original_route->getRequirements(), $original_route->getOptions());
+      $route->setRequirement('_format', 'json');
+      $collection->add('entity.entity_test.canonical.json', $route);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    // Run before PageManagerRoutes.
+    $events[RoutingEvents::ALTER][] = ['beforePageManagerRoutes', -155];
+    // Run after PageManagerRoutes.
+    $events[RoutingEvents::ALTER][] = ['afterPageManagerRoutes', -165];
+    return $events;
+  }
+
+}
diff --git a/tests/src/Kernel/PageManagerRoutingTest.php b/tests/src/Kernel/PageManagerRoutingTest.php
new file mode 100644
index 0000000..ad10689
--- /dev/null
+++ b/tests/src/Kernel/PageManagerRoutingTest.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Drupal\Tests\page_manager\Kernel;
+
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
+use Drupal\page_manager\Entity\Page;
+use Drupal\page_manager\Entity\PageVariant;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Integration test for Page Manager routing.
+ *
+ * @group PageManager
+ */
+class PageManagerRoutingTest extends EntityKernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['page_manager', 'page_manager_routing_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->container->get('current_user')->setAccount($this->createUser([], ['view test entity']));
+    EntityTest::create()->save();
+
+    Page::create([
+      'id' => 'entity_test_view',
+      'path' => '/entity_test/{entity_test}',
+    ])->save();
+    PageVariant::create([
+      'id' => 'entity_test_view_variant',
+      'variant' => 'simple_page',
+      'page' => 'entity_test_view',
+    ])->save();
+
+    Page::create([
+      'id' => 'custom_entity_test_view',
+      'path' => '/custom/entity_test/{entity_test}',
+      'parameters' => [
+        'entity_test' => [
+          'type' => 'entity:entity_test',
+        ],
+      ],
+    ])->save();
+    $variant = PageVariant::create([
+      'id' => 'custom_entity_test_view_variant',
+      'variant' => 'simple_page',
+      'page' => 'custom_entity_test_view',
+    ]);
+    $variant->addSelectionCondition([
+      'id' => 'page_manager_routing_test__entity_test',
+    ]);
+    $variant->getPluginCollections();
+    $variant->save();
+
+    Page::create([
+      'id' => 'entity_test_edit',
+      'path' => '/entity_test/manage/{entity_test}/edit',
+    ])->save();
+    PageVariant::create([
+      'id' => 'entity_test_edit_variant',
+      'variant' => 'simple_page',
+      'page' => 'entity_test_edit',
+      // Add a selection condition that will never pass.
+      'selection_criteria' => [
+        'request_path' => [
+          'id' => 'request_path',
+          'pages' => 'invalid',
+        ],
+      ],
+    ])->save();
+
+    Page::create([
+      'id' => 'entity_test_delete',
+      'path' => '/entity_test/delete/entity_test/{entity_test}',
+      // Add an access condition that will never pass.
+      'access_conditions' => [
+        'request_path' => [
+          'id' => 'request_path',
+          'pages' => 'invalid',
+        ],
+      ],
+    ])->save();
+    PageVariant::create([
+      'id' => 'entity_test_delete_variant',
+      'variant' => 'simple_page',
+      'page' => 'entity_test_delete',
+    ])->save();
+  }
+
+  /**
+   * @covers \Drupal\page_manager\Routing\VariantRouteFilter
+   *
+   * @dataProvider providerTestRouteFilter
+   */
+  public function testRouteFilter($path, $expected) {
+    $request = Request::create($path);
+    try {
+      $parameters = $this->container->get('router')->matchRequest($request);
+    }
+    catch (\Exception $e) {
+      $parameters = [];
+    }
+
+    if ($expected) {
+      $this->assertArrayHasKey(RouteObjectInterface::ROUTE_NAME, $parameters);
+      $this->assertSame($expected, $parameters[RouteObjectInterface::ROUTE_NAME]);
+    }
+    else {
+      $this->assertEmpty($parameters);
+    }
+  }
+
+  public function providerTestRouteFilter() {
+    $data = [];
+    $data['custom'] = [
+      '/custom/entity_test/1',
+      'page_manager.page_view_custom_entity_test_view_custom_entity_test_view_variant',
+    ];
+    $data['no_format'] = [
+      '/entity_test/1',
+      'page_manager.page_view_entity_test_view_entity_test_view_variant',
+    ];
+    $data['format_added_after'] = [
+      '/entity_test/1?_format=json',
+      'entity.entity_test.canonical.json',
+    ];
+    $data['format_added_before'] = [
+      '/entity_test/1?_format=xml',
+      'entity.entity_test.canonical.xml',
+    ];
+    $data['same_pattern_no_match'] = [
+      '/entity_test/add',
+      'entity.entity_test.add_form',
+    ];
+    $data['failed_selection'] = [
+      '/entity_test/manage/1/edit',
+      'entity.entity_test.edit_form',
+    ];
+    $data['access_denied'] = [
+      '/entity_test/delete/entity_test/1',
+      NULL,
+    ];
+    return $data;
+  }
+
+}
diff --git a/tests/src/Unit/PageManagerRoutesTest.php b/tests/src/Unit/PageManagerRoutesTest.php
index fe6f005..4fb599c 100644
--- a/tests/src/Unit/PageManagerRoutesTest.php
+++ b/tests/src/Unit/PageManagerRoutesTest.php
@@ -126,8 +126,8 @@ public function testAlterRoutesWithStatus() {
     $this->routeSubscriber->onAlterRoutes($route_event);
 
     // Only the valid page should be in the collection.
-    $this->assertSame(1, $collection->count());
-    $route = $collection->get('page_manager.page_view_page1');
+    $this->assertSame(['page_manager.page_view_page1_variant1'], array_keys($collection->all()));
+    $route = $collection->get('page_manager.page_view_page1_variant1');
     $expected_defaults = [
       '_entity_view' => 'page_manager_page_variant',
       '_title' => 'Page label',
@@ -158,12 +158,12 @@ public function testAlterRoutesWithStatus() {
    * Tests overriding an existing route.
    *
    * @covers ::alterRoutes
-   * @covers ::findPageRouteName
+   * @covers ::findBaseRouteName
    *
    * @dataProvider providerTestAlterRoutesOverrideExisting
    */
   public function testAlterRoutesOverrideExisting($page_path, $existing_route_path, $requirements = []) {
-    $route_name = 'test_route';
+    $base_route_name = $route_name = 'test_route';
     // Set up a page with the same path as an existing route.
     /** @var \Drupal\page_manager\PageInterface|\Prophecy\Prophecy\ProphecyInterface $page */
     $page = $this->prophesize(PageInterface::class);
@@ -191,14 +191,20 @@ public function testAlterRoutesOverrideExisting($page_path, $existing_route_path
     $this->cacheTagsInvalidator->invalidateTags(["page_manager_route_name:$route_name"])->shouldBeCalledTimes(1);
 
     $collection = new RouteCollection();
+    $collection->add("$route_name.POST", new Route($existing_route_path, ['default_exists' => 'default_value'], $requirements, ['parameters' => ['foo' => ['type' => 'bar']]], '', [], ['POST']));
+    $collection->add("$route_name.POST_with_format", new Route($existing_route_path, ['default_exists' => 'default_value'], $requirements + ['_format' => 'json'], ['parameters' => ['foo' => ['type' => 'bar']]], '', [], ['GET', 'POST']));
     $collection->add($route_name, new Route($existing_route_path, ['default_exists' => 'default_value'], $requirements, ['parameters' => ['foo' => ['type' => 'bar']]]));
     $route_event = new RouteBuildEvent($collection);
     $this->routeSubscriber->onAlterRoutes($route_event);
 
-    // The normal route name is not used, the existing route name is instead.
-    $this->assertSame(1, $collection->count());
-    $this->assertNull($collection->get('page_manager.page_view_page1'));
-    $this->assertNull($collection->get('page_manager.page_view_page1_variant1'));
+    // The existing route name is not overridden.
+    $this->assertSame([
+      'test_route.POST',
+      'test_route.POST_with_format',
+      'test_route',
+      'page_manager.page_view_page1_variant1',
+      ], array_keys($collection->all()));
+    $route_name = 'page_manager.page_view_page1_variant1';
 
     $route = $collection->get($route_name);
     $expected_defaults = [
@@ -207,7 +213,7 @@ public function testAlterRoutesOverrideExisting($page_path, $existing_route_path
       'page_manager_page_variant' => 'variant1',
       'page_manager_page' => 'page1',
       'page_manager_page_variant_weight' => 0,
-      'base_route_name' => $route_name,
+      'base_route_name' => $base_route_name,
     ];
     $expected_requirements = $requirements + ['_page_access' => 'page_manager_page.view'];
     $expected_options = [
@@ -270,11 +276,26 @@ public function testAlterRoutesMultipleVariantsDifferentRequirements() {
     $route_event = new RouteBuildEvent($collection);
     $this->routeSubscriber->onAlterRoutes($route_event);
 
-    $this->assertSame(2, $collection->count());
+    $this->assertSame([
+      'test_route',
+      'page_manager.page_view_page1_variant1',
+      'page_manager.page_view_page2_variant2',
+      ], array_keys($collection->all()));
     $expected = [
       'test_route' => [
         'path' => '/test_route1',
         'defaults' => [
+        ],
+        'requirements' => [
+          '_access' => 'TRUE',
+        ],
+        'options' => [
+          'compiler_class' => 'Symfony\Component\Routing\RouteCompiler',
+        ],
+      ],
+      'page_manager.page_view_page1_variant1' => [
+        'path' => '/test_route1',
+        'defaults' => [
           '_entity_view' => 'page_manager_page_variant',
           '_title' => 'Page 1',
           'page_manager_page_variant' => 'variant1',
@@ -299,7 +320,7 @@ public function testAlterRoutesMultipleVariantsDifferentRequirements() {
           '_admin_route' => FALSE,
         ],
       ],
-      'page_manager.page_view_page2' => [
+      'page_manager.page_view_page2_variant2' => [
         'path' => '/test_route2',
         'defaults' => [
           '_entity_view' => 'page_manager_page_variant',
@@ -335,7 +356,7 @@ public function testAlterRoutesMultipleVariantsDifferentRequirements() {
    * Tests overriding an existing route with configured parameters.
    *
    * @covers ::alterRoutes
-   * @covers ::findPageRouteName
+   * @covers ::findBaseRouteName
    *
    * @dataProvider providerTestAlterRoutesOverrideExisting
    */
@@ -387,7 +408,11 @@ public function testAlterRoutesOverrideExistingWithConfiguredParameters($page_pa
       ],
       '_admin_route' => FALSE,
     ];
-    $this->assertMatchingRoute($collection->get($route_name), $existing_route_path, $expected_defaults, $expected_requirements, $expected_options);
+    $this->assertSame([
+      'test_route',
+      'page_manager.page_view_page1_variant1',
+    ], array_keys($collection->all()));
+    $this->assertMatchingRoute($collection->get('page_manager.page_view_page1_variant1'), $existing_route_path, $expected_defaults, $expected_requirements, $expected_options);
   }
 
   /**
diff --git a/tests/src/Unit/VariantRouteFilterTest.php b/tests/src/Unit/VariantRouteFilterTest.php
index dbcf0f3..387eb59 100644
--- a/tests/src/Unit/VariantRouteFilterTest.php
+++ b/tests/src/Unit/VariantRouteFilterTest.php
@@ -10,12 +10,14 @@
 use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Core\Config\Entity\ConfigEntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\ParamConverter\ParamNotConvertedException;
 use Drupal\Core\Path\CurrentPathStack;
 use Drupal\page_manager\PageVariantInterface;
 use Drupal\page_manager\Routing\VariantRouteFilter;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Cmf\Component\Routing\Enhancer\RouteEnhancerInterface;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
 
@@ -63,8 +65,9 @@ protected function setUp() {
     $this->entityTypeManager->getStorage('page_variant')
       ->willReturn($this->pageVariantStorage);
     $this->currentPath = $this->prophesize(CurrentPathStack::class);
+    $request_stack = new RequestStack();
 
-    $this->routeFilter = new VariantRouteFilter($this->entityTypeManager->reveal(), $this->currentPath->reveal());
+    $this->routeFilter = new VariantRouteFilter($this->entityTypeManager->reveal(), $this->currentPath->reveal(), $request_stack);
   }
 
   /**
@@ -102,6 +105,7 @@ public function testFilterEmptyCollection() {
 
   /**
    * @covers ::filter
+   * @covers ::getVariantRouteName
    * @covers ::checkPageVariantAccess
    */
   public function testFilterContextException() {
@@ -125,6 +129,7 @@ public function testFilterContextException() {
 
   /**
    * @covers ::filter
+   * @covers ::getVariantRouteName
    */
   public function testFilterNonMatchingRoute() {
     $route_collection = new RouteCollection();
@@ -143,6 +148,7 @@ public function testFilterNonMatchingRoute() {
 
   /**
    * @covers ::filter
+   * @covers ::getVariantRouteName
    * @covers ::checkPageVariantAccess
    */
   public function testFilterDeniedAccess() {
@@ -166,13 +172,18 @@ public function testFilterDeniedAccess() {
 
   /**
    * @covers ::filter
+   * @covers ::getVariantRouteName
    * @covers ::checkPageVariantAccess
    */
   public function testFilterAllowedAccess() {
     $route_collection = new RouteCollection();
     $request = new Request();
 
-    $route = new Route('/path/with/{slug}', ['page_manager_page_variant' => 'a_variant']);
+    $defaults = [
+      'page_manager_page_variant' => 'a_variant',
+      'base_route_name' => 'a_route',
+    ];
+    $route = new Route('/path/with/{slug}', $defaults);
     $route_collection->add('a_route', $route);
 
     $page_variant = $this->prophesize(PageVariantInterface::class);
@@ -183,9 +194,8 @@ public function testFilterAllowedAccess() {
 
     $result = $this->routeFilter->filter($route_collection, $request);
     $expected = ['a_route' => $route];
-    $this->assertSame($expected, $result->all());
-    $expected_attributes = [
-      'page_manager_page_variant' => 'a_variant',
+    $this->assertEquals($expected, $result->all());
+    $expected_attributes = $defaults + [
       '_route_object' => $route,
       '_route' => 'a_route',
     ];
@@ -194,14 +204,25 @@ public function testFilterAllowedAccess() {
 
   /**
    * @covers ::filter
+   * @covers ::getVariantRouteName
    */
   public function testFilterAllowedAccessTwoRoutes() {
     $route_collection = new RouteCollection();
     $request = new Request();
 
-    // Add route2 first to ensure that the routes are sorted by weight.
-    $route1 = new Route('/path/with/{slug}', ['page_manager_page_variant' => 'variant_1', 'page_manager_page_variant_weight' => 1]);
-    $route2 = new Route('/path/with/{slug}', ['page_manager_page_variant' => 'variant_2', 'page_manager_page_variant_weight' => 2]);
+    $defaults1 = [
+      'page_manager_page_variant' => 'variant_1',
+      'page_manager_page_variant_weight' => 1,
+      'base_route_name' => 'route_1',
+    ];
+    $defaults2 = [
+      'page_manager_page_variant' => 'variant_2',
+      'page_manager_page_variant_weight' => 2,
+      'base_route_name' => 'route_2',
+    ];
+    $route1 = new Route('/path/with/{slug}', $defaults1);
+    $route2 = new Route('/path/with/{slug}', $defaults2);
+    // Add route2 first to ensure that the routes get sorted by weight.
     $route_collection->add('route_2', $route2);
     $route_collection->add('route_1', $route1);
 
@@ -215,9 +236,7 @@ public function testFilterAllowedAccessTwoRoutes() {
     $result = $this->routeFilter->filter($route_collection, $request);
     $expected = ['route_1' => $route1];
     $this->assertSame($expected, $result->all());
-    $expected_attributes = [
-      'page_manager_page_variant' => 'variant_1',
-      'page_manager_page_variant_weight' => 1,
+    $expected_attributes = $defaults1 + [
       '_route_object' => $route1,
       '_route' => 'route_1',
     ];
@@ -226,14 +245,25 @@ public function testFilterAllowedAccessTwoRoutes() {
 
   /**
    * @covers ::filter
+   * @covers ::getVariantRouteName
    */
   public function testFilterAllowedAccessSecondRoute() {
     $route_collection = new RouteCollection();
     $request = new Request();
 
-    // Add route2 first to ensure that the routes are sorted by weight.
-    $route1 = new Route('/path/with/{slug}', ['page_manager_page_variant' => 'variant_1', 'page_manager_page_variant_weight' => 1]);
-    $route2 = new Route('/path/with/{slug}', ['page_manager_page_variant' => 'variant_2', 'page_manager_page_variant_weight' => 2]);
+    $defaults1 = [
+      'page_manager_page_variant' => 'variant_1',
+      'page_manager_page_variant_weight' => 1,
+      'base_route_name' => 'route_1',
+    ];
+    $defaults2 = [
+      'page_manager_page_variant' => 'variant_2',
+      'page_manager_page_variant_weight' => 2,
+      'base_route_name' => 'invalid',
+    ];
+    $route1 = new Route('/path/with/{slug}', $defaults1);
+    $route2 = new Route('/path/with/{slug}', $defaults2);
+    // Add route2 first to ensure that the routes get sorted by weight.
     $route_collection->add('route_2', $route2);
     $route_collection->add('route_1', $route1);
 
@@ -249,9 +279,7 @@ public function testFilterAllowedAccessSecondRoute() {
     $result = $this->routeFilter->filter($route_collection, $request);
     $expected = ['route_2' => $route2];
     $this->assertSame($expected, $result->all());
-    $expected_attributes = [
-      'page_manager_page_variant' => 'variant_2',
-      'page_manager_page_variant_weight' => 2,
+    $expected_attributes = $defaults2 + [
       '_route_object' => $route2,
       '_route' => 'route_2',
     ];
@@ -260,6 +288,7 @@ public function testFilterAllowedAccessSecondRoute() {
 
   /**
    * @covers ::filter
+   * @covers ::getVariantRouteName
    * @covers ::routeWeightSort
    *
    * Tests when the first page_manager route is allowed, but other
@@ -269,11 +298,22 @@ public function testFilterAllowedAccessFirstRoute() {
     $route_collection = new RouteCollection();
     $request = new Request();
 
-    // Add routes in different order to test sorting.
+    // The selected route specifies a different base route.
+    $defaults2 = [
+      'page_manager_page_variant' => 'variant1',
+      'page_manager_page_variant_weight' => 1,
+      'base_route_name' => 'route_1',
+    ];
+    $defaults3 = [
+      'page_manager_page_variant' => 'variant2',
+      'page_manager_page_variant_weight' => 2,
+      'base_route_name' => 'route_3',
+    ];
     $route1 = new Route('/path/with/{slug}');
-    $route2 = new Route('/path/with/{slug}', ['page_manager_page_variant' => 'variant1', 'page_manager_page_variant_weight' => 1]);
-    $route3 = new Route('/path/with/{slug}', ['page_manager_page_variant' => 'variant2', 'page_manager_page_variant_weight' => 2]);
+    $route2 = new Route('/path/with/{slug}', $defaults2);
+    $route3 = new Route('/path/with/{slug}', $defaults3);
     $route4 = new Route('/path/with/{slug}');
+    // Add routes in different order to test sorting.
     $route_collection->add('route_3', $route3);
     $route_collection->add('route_2', $route2);
     $route_collection->add('route_1', $route1);
@@ -288,11 +328,9 @@ public function testFilterAllowedAccessFirstRoute() {
     $this->pageVariantStorage->load('variant1')->willReturn($page_variant1->reveal())->shouldBeCalled();
 
     $result = $this->routeFilter->filter($route_collection, $request);
-    $expected = ['route_2' => $route2, 'route_1' => $route1, 'route_4' => $route4];
+    $expected = ['route_2' => $route2, 'route_4' => $route4, 'route_1' => $route1];
     $this->assertSame($expected, $result->all());
-    $expected_attributes = [
-      'page_manager_page_variant' => 'variant1',
-      'page_manager_page_variant_weight' => 1,
+    $expected_attributes = $defaults2 + [
       '_route_object' => $route2,
       '_route' => 'route_2',
     ];
@@ -301,12 +339,14 @@ public function testFilterAllowedAccessFirstRoute() {
 
   /**
    * @covers ::filter
+   * @covers ::getVariantRouteName
+   * @covers ::getRequestAttributes
    */
   public function testFilterRequestAttributes() {
     $route_collection = new RouteCollection();
     $request = new Request([], [], ['foo' => 'bar', 'slug' => 2]);
 
-    $route = new Route('/path/with/{slug}', ['page_manager_page_variant' => 'a_variant']);
+    $route = new Route('/path/with/{slug}', ['page_manager_page_variant' => 'a_variant', 'base_route_name' => 'a_route']);
     $route_collection->add('a_route', $route);
 
     $page_variant = $this->prophesize(PageVariantInterface::class);
@@ -321,6 +361,7 @@ public function testFilterRequestAttributes() {
       'foo' => 'bar',
       'slug' => '1',
       'page_manager_page_variant' => 'a_variant',
+      'base_route_name' => 'a_route',
       '_route_object' => $route,
       '_route' => 'a_route',
     ];
@@ -334,6 +375,7 @@ public function testFilterRequestAttributes() {
       'foo' => 'bar',
       'slug' => 'slug 1',
       'page_manager_page_variant' => 'a_variant',
+      'base_route_name' => 'a_route',
       '_route_object' => $route,
       '_route' => 'a_route',
     ];
@@ -341,6 +383,41 @@ public function testFilterRequestAttributes() {
   }
 
   /**
+   * @covers ::filter
+   * @covers ::getVariantRouteName
+   * @covers ::getRequestAttributes
+   */
+  public function testFilterRequestAttributesException() {
+    $route_collection = new RouteCollection();
+    $original_attributes = ['foo' => 'bar', 'slug' => 2];
+    $request = new Request([], [], $original_attributes);
+
+    $route = new Route('/path/with/{slug}', ['page_manager_page_variant' => 'a_variant']);
+    $route_collection->add('a_route', $route);
+
+    $page_variant = $this->prophesize(PageVariantInterface::class);
+    $page_variant->access('view')->willReturn(TRUE);
+
+    $this->currentPath->getPath($request)->willReturn('/path/with/1');
+    $this->pageVariantStorage->load('a_variant')->willReturn($page_variant->reveal());
+
+    $route_enhancer = $this->prophesize(RouteEnhancerInterface::class);
+    $this->routeFilter->addRouteEnhancer($route_enhancer->reveal());
+    $expected_enhance_attributes = [
+      'foo' => 'bar',
+      'slug' => '1',
+      'page_manager_page_variant' => 'a_variant',
+      '_route_object' => $route,
+      '_route' => 'a_route',
+    ];
+    $route_enhancer->enhance($expected_enhance_attributes, $request)->willThrow(new ParamNotConvertedException(sprintf('The "%s" parameter was not converted for the path "%s" (route name: "%s")', 'slug', $route->getPath(), 'a_route')));
+
+    $result = $this->routeFilter->filter($route_collection, $request);
+    $this->assertEmpty($result->all());
+    $this->assertSame($original_attributes, $request->attributes->all());
+  }
+
+  /**
    * @covers ::getRequestAttributes
    */
   public function testGetRequestAttributes() {
