diff --git a/core/lib/Drupal/Core/Entity/ContentEntityViewBuilder.php b/core/lib/Drupal/Core/Entity/ContentEntityViewBuilder.php
new file mode 100644
index 0000000000..c3ec810806
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/ContentEntityViewBuilder.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+
+/**
+ * Provides a view builder for content entities.
+ */
+class ContentEntityViewBuilder extends EntityViewBuilder {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
+    if ($entity->isNew() || !$entity instanceof ContentEntityInterface) {
+      return;
+    }
+    $key = $entity->getEntityTypeId();
+    $rel = 'canonical';
+    if (!$entity->isDefaultRevision()) {
+      $rel = 'revision';
+      $key .= '_revision';
+    }
+    if ($entity->hasLinkTemplate($rel)) {
+      $build['#contextual_links'][$key] = [
+        'route_parameters' => $entity->toUrl($rel)->getRouteParameters(),
+      ];
+      if ($entity instanceof EntityChangedInterface) {
+        $build['#contextual_links'][$key]['metadata'] = [
+          'changed' => $entity->getChangedTime(),
+        ];
+      }
+    }
+  }
+
+}
diff --git a/core/modules/block_content/src/BlockContentViewBuilder.php b/core/modules/block_content/src/BlockContentViewBuilder.php
index 29c668cd63..26079219af 100644
--- a/core/modules/block_content/src/BlockContentViewBuilder.php
+++ b/core/modules/block_content/src/BlockContentViewBuilder.php
@@ -2,14 +2,13 @@
 
 namespace Drupal\block_content;
 
-use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\ContentEntityViewBuilder;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityViewBuilder;
 
 /**
  * View builder handler for custom blocks.
  */
-class BlockContentViewBuilder extends EntityViewBuilder {
+class BlockContentViewBuilder extends ContentEntityViewBuilder {
 
   /**
    * {@inheritdoc}
@@ -41,18 +40,4 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode) {
     return $build;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
-    parent::alterBuild($build, $entity, $display, $view_mode);
-    // Add contextual links for this custom block.
-    if (!$entity->isNew()) {
-      $build['#contextual_links']['block_content'] = [
-        'route_parameters' => ['block_content' => $entity->id()],
-        'metadata' => ['changed' => $entity->getChangedTime()],
-      ];
-    }
-  }
-
 }
diff --git a/core/modules/block_content/tests/src/Functional/BlockContentContextualLinksTest.php b/core/modules/block_content/tests/src/Functional/BlockContentContextualLinksTest.php
new file mode 100644
index 0000000000..855bfd49e1
--- /dev/null
+++ b/core/modules/block_content/tests/src/Functional/BlockContentContextualLinksTest.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Drupal\Tests\block_content\Functional;
+
+/**
+ * Tests views contextual links on block content.
+ *
+ * @group block_content
+ */
+class BlockContentContextualLinksTest extends BlockContentTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'contextual',
+  ];
+
+  /**
+   * Tests contextual links.
+   */
+  public function testNodeContextualLinks() {
+    $block_content = $this->createBlockContent();
+
+    $block = $this->placeBlock('block_content:' . $block_content->uuid());
+
+    $user = $this->drupalCreateUser([
+      'administer blocks',
+      'access contextual links',
+    ]);
+    $this->drupalLogin($user);
+
+    $this->drupalGet('<front>');
+    $this->assertSession()->responseContains('data-contextual-id="block:block=' . $block->id() . ':langcode=en|block_content:block_content=' . $block_content->id() . ':');
+  }
+
+}
diff --git a/core/modules/node/src/NodeViewBuilder.php b/core/modules/node/src/NodeViewBuilder.php
index f0971fb3bb..f1f769269e 100644
--- a/core/modules/node/src/NodeViewBuilder.php
+++ b/core/modules/node/src/NodeViewBuilder.php
@@ -2,15 +2,14 @@
 
 namespace Drupal\node;
 
-use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\ContentEntityViewBuilder;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityViewBuilder;
 use Drupal\node\Entity\Node;
 
 /**
  * View builder handler for nodes.
  */
-class NodeViewBuilder extends EntityViewBuilder {
+class NodeViewBuilder extends ContentEntityViewBuilder {
 
   /**
    * {@inheritdoc}
@@ -141,29 +140,4 @@ protected static function buildLinks(NodeInterface $entity, $view_mode) {
     ];
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
-    /** @var \Drupal\node\NodeInterface $entity */
-    parent::alterBuild($build, $entity, $display, $view_mode);
-    if ($entity->id()) {
-      if ($entity->isDefaultRevision()) {
-        $build['#contextual_links']['node'] = [
-          'route_parameters' => ['node' => $entity->id()],
-          'metadata' => ['changed' => $entity->getChangedTime()],
-        ];
-      }
-      else {
-        $build['#contextual_links']['node_revision'] = [
-          'route_parameters' => [
-            'node' => $entity->id(),
-            'node_revision' => $entity->getRevisionId(),
-          ],
-          'metadata' => ['changed' => $entity->getChangedTime()],
-        ];
-      }
-    }
-  }
-
 }
diff --git a/core/modules/node/tests/src/Functional/NodeContextualLinksTest.php b/core/modules/node/tests/src/Functional/NodeContextualLinksTest.php
new file mode 100644
index 0000000000..74992c95d5
--- /dev/null
+++ b/core/modules/node/tests/src/Functional/NodeContextualLinksTest.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\Tests\node\Functional;
+
+use Drupal\node\Entity\Node;
+
+/**
+ * Tests views contextual links on nodes.
+ *
+ * @group node
+ */
+class NodeContextualLinksTest extends NodeTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'contextual',
+  ];
+
+  /**
+   * Tests contextual links.
+   */
+  public function testNodeContextualLinks() {
+    // Create a node item.
+    $node = Node::create([
+      'type' => 'article',
+      'title' => 'Unnamed',
+    ]);
+    $node->save();
+
+    $user = $this->drupalCreateUser([
+      'administer nodes',
+      'access contextual links',
+    ]);
+    $this->drupalLogin($user);
+
+    $this->drupalGet('node/' . $node->id());
+    $this->assertSession()->responseContains('data-contextual-id="node:node=' . $node->id() . ':');
+  }
+
+}
diff --git a/core/modules/taxonomy/src/TermViewBuilder.php b/core/modules/taxonomy/src/TermViewBuilder.php
index 6748a4b03c..3d6e488f1f 100644
--- a/core/modules/taxonomy/src/TermViewBuilder.php
+++ b/core/modules/taxonomy/src/TermViewBuilder.php
@@ -2,24 +2,9 @@
 
 namespace Drupal\taxonomy;
 
-use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityViewBuilder;
+use Drupal\Core\Entity\ContentEntityViewBuilder;
 
 /**
  * View builder handler for taxonomy terms.
  */
-class TermViewBuilder extends EntityViewBuilder {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function alterBuild(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
-    parent::alterBuild($build, $entity, $display, $view_mode);
-    $build['#contextual_links']['taxonomy_term'] = [
-      'route_parameters' => ['taxonomy_term' => $entity->id()],
-      'metadata' => ['changed' => $entity->getChangedTime()],
-    ];
-  }
-
-}
+class TermViewBuilder extends ContentEntityViewBuilder {}
diff --git a/core/modules/taxonomy/tests/src/Functional/TermContextualLinksTest.php b/core/modules/taxonomy/tests/src/Functional/TermContextualLinksTest.php
new file mode 100644
index 0000000000..836d1a46fc
--- /dev/null
+++ b/core/modules/taxonomy/tests/src/Functional/TermContextualLinksTest.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\Tests\taxonomy\Functional;
+
+/**
+ * Tests views contextual links on terms.
+ *
+ * @group taxonomy
+ */
+class TermContextualLinksTest extends TaxonomyTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'contextual',
+  ];
+
+  /**
+   * Tests contextual links.
+   */
+  public function testNodeContextualLinks() {
+    $vocabulary = $this->createVocabulary();
+    $term = $this->createTerm($vocabulary);
+
+    $user = $this->drupalCreateUser([
+      'administer taxonomy',
+      'access contextual links',
+    ]);
+    $this->drupalLogin($user);
+
+    $this->drupalGet('taxonomy/term/' . $term->id());
+    $this->assertSession()->responseContains('data-contextual-id="taxonomy_term:taxonomy_term=' . $term->id() . ':');
+  }
+
+}
