diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php
index 499deeb785dc86116a75fcc772ed2f19253d2897..1a2df9f50d7491c19f78e83dbaea5316e87bf161 100644
--- a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php
+++ b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php
@@ -101,6 +101,32 @@ public function view(EntityInterface $_entity, $view_mode = 'full') {
     $page['#entity_type'] = $_entity->getEntityTypeId();
     $page['#' . $page['#entity_type']] = $_entity;
 
+    // Add canonical and shortlink links if the entity has a canonical
+    // link template and is not new.
+    if ($_entity->hasLinkTemplate('canonical') && !$_entity->isNew()) {
+
+      $url = $_entity->toUrl('canonical')->setAbsolute(TRUE);
+      $page['#attached']['html_head_link'][] = [
+        [
+          'rel' => 'canonical',
+          'href' => $url->toString(),
+        ],
+        TRUE,
+      ];
+
+      // Set the non-aliased canonical path as a default shortlink.
+      $page['#attached']['html_head_link'][] = [
+        [
+          'rel' => 'shortlink',
+          'href' => $url->setOption('alias', TRUE)->toString(),
+        ],
+        TRUE,
+      ];
+
+      // Since this generates absolute URLs, it can only be cached "per site".
+      $page['#cache']['contexts'][] = 'url.site';
+    }
+
     return $page;
   }
 
diff --git a/core/modules/comment/tests/src/Functional/CommentCacheTagsTest.php b/core/modules/comment/tests/src/Functional/CommentCacheTagsTest.php
index e5d308d682d062576971abc14b2d3400a6a0fa47..c48f32b4e0476bc48fc74b55d3be34d4a9a1186e 100644
--- a/core/modules/comment/tests/src/Functional/CommentCacheTagsTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentCacheTagsTest.php
@@ -7,6 +7,7 @@
 use Drupal\comment\Entity\Comment;
 use Drupal\comment\Tests\CommentTestTrait;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\entity_test\Entity\EntityTest;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\Tests\system\Functional\Entity\EntityWithUriCacheTagsTestBase;
@@ -158,4 +159,15 @@ protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
     ];
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultCacheContexts() {
+    return [
+      'languages:' . LanguageInterface::TYPE_INTERFACE,
+      'theme',
+      'user.permissions',
+    ];
+  }
+
 }
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTestTranslationUITest.php b/core/modules/content_translation/tests/src/Functional/ContentTestTranslationUITest.php
index 12b993c181d5b504af39158fc620c6dd710b0cba..3f644b9a8e5e8122f8c8f3c6cc7572165a41917a 100644
--- a/core/modules/content_translation/tests/src/Functional/ContentTestTranslationUITest.php
+++ b/core/modules/content_translation/tests/src/Functional/ContentTestTranslationUITest.php
@@ -14,6 +14,17 @@ class ContentTestTranslationUITest extends ContentTranslationUITestBase {
    */
   protected $testHTMLEscapeForAllLanguages = TRUE;
 
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultCacheContexts = [
+    'languages:language_interface',
+    'theme',
+    'url.query_args:_wrapper_format',
+    'user.permissions',
+    'url.site',
+  ];
+
   /**
    * Modules to enable.
    *
diff --git a/core/modules/node/src/Controller/NodeViewController.php b/core/modules/node/src/Controller/NodeViewController.php
index a0787d4abccce3c16a581bffa579119cd271c976..406d17b5377cd9a3f740208f3d1ced5b90f0719b 100644
--- a/core/modules/node/src/Controller/NodeViewController.php
+++ b/core/modules/node/src/Controller/NodeViewController.php
@@ -63,51 +63,7 @@ public static function create(ContainerInterface $container) {
    * {@inheritdoc}
    */
   public function view(EntityInterface $node, $view_mode = 'full', $langcode = NULL) {
-    $build = parent::view($node, $view_mode, $langcode);
-
-    foreach ($node->uriRelationships() as $rel) {
-      $url = $node->toUrl($rel)->setAbsolute(TRUE);
-      // Add link relationships if the user is authenticated or if the anonymous
-      // user has access. Access checking must be done for anonymous users to
-      // avoid traffic to inaccessible pages from web crawlers. For
-      // authenticated users, showing the links in HTML head does not impact
-      // user experience or security, since the routes are access checked when
-      // visited and only visible via view source. This prevents doing
-      // potentially expensive and hard to cache access checks on every request.
-      // This means that the page will vary by user.permissions. We also rely on
-      // the access checking fallback to ensure the correct cacheability
-      // metadata if we have to check access.
-      if ($this->currentUser->isAuthenticated() || $url->access($this->currentUser)) {
-        // Set the node path as the canonical URL to prevent duplicate content.
-        $build['#attached']['html_head_link'][] = [
-          [
-            'rel' => $rel,
-            'href' => $url->toString(),
-          ],
-          TRUE,
-        ];
-      }
-
-      if ($rel == 'canonical') {
-        // Set the non-aliased canonical path as a default shortlink.
-        $build['#attached']['html_head_link'][] = [
-          [
-            'rel' => 'shortlink',
-            'href' => $url->setOption('alias', TRUE)->toString(),
-          ],
-          TRUE,
-        ];
-      }
-    }
-
-    // Since this generates absolute URLs, it can only be cached "per site".
-    $build['#cache']['contexts'][] = 'url.site';
-
-    // Given this varies by $this->currentUser->isAuthenticated(), add a cache
-    // context based on the anonymous role.
-    $build['#cache']['contexts'][] = 'user.roles:anonymous';
-
-    return $build;
+    return parent::view($node, $view_mode);
   }
 
   /**
diff --git a/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php b/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php
index 4b40ae36d3e93e3b302272a78206157f3f5371a6..36f38aaa883915eb844f1d74d06fce6d93634ced 100644
--- a/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php
+++ b/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php
@@ -138,7 +138,7 @@ public function testRecentNodeBlock() {
     $this->assertSession()->pageTextContains($node3->label());
     $this->assertSession()->pageTextContains($node4->label());
 
-    $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user']);
+    $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.site', 'user']);
 
     // Enable the "Powered by Drupal" block only on article nodes.
     $edit = [
@@ -165,7 +165,7 @@ public function testRecentNodeBlock() {
     $label = $block->label();
     // Check that block is not displayed on the front page.
     $this->assertNoText($label);
-    $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user', 'route']);
+    $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.site', 'user', 'route']);
 
     // Ensure that a page that does not have a node context can still be cached,
     // the front page is the user page which is already cached from the login
diff --git a/core/modules/node/tests/src/Functional/NodeCacheTagsTest.php b/core/modules/node/tests/src/Functional/NodeCacheTagsTest.php
index 191955a83e06aa3dcaaab874e6a01302e3bcc65a..cadf25444871d7b3c9e0926d9c83471a2124dcca 100644
--- a/core/modules/node/tests/src/Functional/NodeCacheTagsTest.php
+++ b/core/modules/node/tests/src/Functional/NodeCacheTagsTest.php
@@ -43,17 +43,6 @@ protected function createEntity() {
     return $node;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function getDefaultCacheContexts() {
-    $defaults = parent::getDefaultCacheContexts();
-    // @see \Drupal\node\Controller\NodeViewController::view()
-    $defaults[] = 'url.site';
-    $defaults[] = 'user.roles:anonymous';
-    return $defaults;
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/node/tests/src/Functional/NodeViewTest.php b/core/modules/node/tests/src/Functional/NodeViewTest.php
index 5e64db52cd42e41a9072c064bf0a69b7e40eee47..c1cd40f33c34e6493f108c2f242938f7c2ddfa0a 100644
--- a/core/modules/node/tests/src/Functional/NodeViewTest.php
+++ b/core/modules/node/tests/src/Functional/NodeViewTest.php
@@ -25,50 +25,11 @@ public function testHtmlHeadLinks() {
 
     $this->drupalGet($node->toUrl());
 
-    $result = $this->xpath('//link[@rel = "canonical"]');
-    $this->assertEquals($node->toUrl()->setAbsolute()->toString(), $result[0]->getAttribute('href'));
+    $element = $this->assertSession()->elementExists('css', 'link[rel="canonical"]');
+    $this->assertEquals($node->toUrl()->setAbsolute()->toString(), $element->getAttribute('href'));
 
-    // Link relations are checked for access for anonymous users.
-    $result = $this->xpath('//link[@rel = "version-history"]');
-    $this->assertEmpty($result, 'Version history not present for anonymous users without access.');
-
-    $result = $this->xpath('//link[@rel = "edit-form"]');
-    $this->assertEmpty($result, 'Edit form not present for anonymous users without access.');
-
-    $this->drupalLogin($this->createUser(['access content']));
-    $this->drupalGet($node->toUrl());
-
-    $result = $this->xpath('//link[@rel = "canonical"]');
-    $this->assertEquals($node->toUrl()->setAbsolute()->toString(), $result[0]->getAttribute('href'));
-
-    // Link relations are present regardless of access for authenticated users.
-    $result = $this->xpath('//link[@rel = "version-history"]');
-    $this->assertEquals($node->toUrl('version-history')->setAbsolute()->toString(), $result[0]->getAttribute('href'));
-
-    $result = $this->xpath('//link[@rel = "edit-form"]');
-    $this->assertEquals($node->toUrl('edit-form')->setAbsolute()->toString(), $result[0]->getAttribute('href'));
-
-    // Give anonymous users access to edit the node. Do this through the UI to
-    // ensure caches are handled properly.
-    $this->drupalLogin($this->rootUser);
-    $edit = [
-      'anonymous[edit own ' . $node->bundle() . ' content]' => TRUE,
-    ];
-    $this->drupalGet('admin/people/permissions');
-    $this->submitForm($edit, 'Save permissions');
-    $this->drupalLogout();
-
-    // Anonymous user's should now see the edit-form link but not the
-    // version-history link.
-    $this->drupalGet($node->toUrl());
-    $result = $this->xpath('//link[@rel = "canonical"]');
-    $this->assertEquals($node->toUrl()->setAbsolute()->toString(), $result[0]->getAttribute('href'));
-
-    $result = $this->xpath('//link[@rel = "version-history"]');
-    $this->assertEmpty($result, 'Version history not present for anonymous users without access.');
-
-    $result = $this->xpath('//link[@rel = "edit-form"]');
-    $this->assertEquals($node->toUrl('edit-form')->setAbsolute()->toString(), $result[0]->getAttribute('href'));
+    $element = $this->assertSession()->elementExists('css', 'link[rel="shortlink"]');
+    $this->assertEquals($node->toUrl('canonical', ['alias' => TRUE])->setAbsolute()->toString(), $element->getAttribute('href'));
   }
 
   /**
@@ -79,8 +40,7 @@ public function testLinkHeader() {
 
     $expected = [
       '<' . Html::escape($node->toUrl('canonical')->setAbsolute()->toString()) . '>; rel="canonical"',
-      '<' . Html::escape($node->toUrl('canonical')->setAbsolute()->toString(), ['alias' => TRUE]) . '>; rel="shortlink"',
-      '<' . Html::escape($node->toUrl('revision')->setAbsolute()->toString()) . '>; rel="revision"',
+      '<' . Html::escape($node->toUrl('canonical', ['alias' => TRUE])->setAbsolute()->toString()) . '>; rel="shortlink"',
     ];
 
     $this->drupalGet($node->toUrl());
diff --git a/core/modules/page_cache/tests/src/Functional/PageCacheTagsIntegrationTest.php b/core/modules/page_cache/tests/src/Functional/PageCacheTagsIntegrationTest.php
index 3812010bee8860b7d99383976d7f9e8a7d9f1a6a..baad8c5309d907d726422d0611870729ec2d7d16 100644
--- a/core/modules/page_cache/tests/src/Functional/PageCacheTagsIntegrationTest.php
+++ b/core/modules/page_cache/tests/src/Functional/PageCacheTagsIntegrationTest.php
@@ -80,7 +80,6 @@ public function testPageCacheTags() {
       // These two cache contexts are added by BigPipe.
       'cookies:big_pipe_nojs',
       'session.exists',
-      'user.roles:anonymous',
       'user.roles:authenticated',
     ];
 
diff --git a/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php b/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php
index 53402fa890ec9868782a93179ce8a4c6d95f9e4f..7bdaa303b73b47c58ed44cb06ea357ad38fa439c 100644
--- a/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php
+++ b/core/modules/statistics/tests/src/Functional/StatisticsReportsTest.php
@@ -59,7 +59,7 @@ public function testPopularContentBlock() {
     $tags = Cache::mergeTags($tags, ['block_view', 'config:block_list', 'node_list', 'rendered', 'user_view']);
     $this->assertCacheTags($tags);
     $contexts = Cache::mergeContexts($node->getCacheContexts(), $block->getCacheContexts());
-    $contexts = Cache::mergeContexts($contexts, ['url.query_args:_wrapper_format']);
+    $contexts = Cache::mergeContexts($contexts, ['url.query_args:_wrapper_format', 'url.site']);
     $this->assertCacheContexts($contexts);
 
     // Check if the node link is displayed.
diff --git a/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php b/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php
index a9c2e419f4710c9f2d376fccbf07c816aa8c3d2a..af96555a7a175aff4b6f56a2a11ae39e2d429695 100644
--- a/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php
+++ b/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php
@@ -339,7 +339,7 @@ public function testReferencedEntity() {
 
     // The default cache contexts for rendered entities.
     $default_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
-    $entity_cache_contexts = $default_cache_contexts;
+    $entity_cache_contexts = Cache::mergeContexts($default_cache_contexts, ['url.site']);
     $page_cache_contexts = Cache::mergeContexts($default_cache_contexts, ['url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT]);
 
     // Cache tags present on every rendered page.
diff --git a/core/modules/system/tests/src/Functional/Entity/EntityWithUriCacheTagsTestBase.php b/core/modules/system/tests/src/Functional/Entity/EntityWithUriCacheTagsTestBase.php
index 83c3bf3faa17761786ec4ed0a426ac4d626a4274..7b497a491ea572047e01d988956873187147ef92 100644
--- a/core/modules/system/tests/src/Functional/Entity/EntityWithUriCacheTagsTestBase.php
+++ b/core/modules/system/tests/src/Functional/Entity/EntityWithUriCacheTagsTestBase.php
@@ -134,7 +134,9 @@ public function testEntityUri() {
    *   The default cache contexts for rendered entities.
    */
   protected function getDefaultCacheContexts() {
-    return ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
+    // For url.site, see
+    // \Drupal\Core\Entity\Controller\EntityViewController::view().
+    return ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions', 'url.site'];
   }
 
 }
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index 501874d5bd38fb16e714884d0827ae11f16f048a..067b41b4241a189aa76bc34184e2fb1d715d840a 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -12,7 +12,6 @@
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
 use Drupal\taxonomy\Entity\Term;
-use Drupal\taxonomy\TermInterface;
 
 /**
  * Implements hook_help().
@@ -57,37 +56,6 @@ function taxonomy_term_uri($term) {
   ]);
 }
 
-/**
- * Implements hook_page_attachments_alter().
- */
-function taxonomy_page_attachments_alter(array &$page) {
-  $route_match = \Drupal::routeMatch();
-  if ($route_match->getRouteName() == 'entity.taxonomy_term.canonical' && ($term = $route_match->getParameter('taxonomy_term')) && $term instanceof TermInterface) {
-    foreach ($term->uriRelationships() as $rel) {
-      // Set the URI relationships, like canonical.
-      $page['#attached']['html_head_link'][] = [
-        [
-          'rel' => $rel,
-          'href' => $term->toUrl($rel)->toString(),
-        ],
-        TRUE,
-      ];
-
-      // Set the term path as the canonical URL to prevent duplicate content.
-      if ($rel == 'canonical') {
-        // Set the non-aliased canonical path as a default shortlink.
-        $page['#attached']['html_head_link'][] = [
-          [
-            'rel' => 'shortlink',
-            'href' => $term->toUrl($rel, ['alias' => TRUE])->toString(),
-          ],
-          TRUE,
-        ];
-      }
-    }
-  }
-}
-
 /**
  * Implements hook_theme().
  */
diff --git a/core/modules/taxonomy/tests/src/Functional/TermTranslationUITest.php b/core/modules/taxonomy/tests/src/Functional/TermTranslationUITest.php
index 37c885fb2f439c66f792debae8f79ae4f25db14c..badf7be914119071bd580881c3f72befeb849cbf 100644
--- a/core/modules/taxonomy/tests/src/Functional/TermTranslationUITest.php
+++ b/core/modules/taxonomy/tests/src/Functional/TermTranslationUITest.php
@@ -20,6 +20,17 @@ class TermTranslationUITest extends ContentTranslationUITestBase {
    */
   protected $vocabulary;
 
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultCacheContexts = [
+    'languages:language_interface',
+    'theme',
+    'url.query_args:_wrapper_format',
+    'user.permissions',
+    'url.site',
+  ];
+
   /**
    * Modules to enable.
    *
diff --git a/core/modules/user/tests/src/Functional/UserTranslationUITest.php b/core/modules/user/tests/src/Functional/UserTranslationUITest.php
index 2c29e95c5553981c0df5b1046ad10bad9348f609..8e04b858ccde2fc85993049ebd5177cb9ed1dce5 100644
--- a/core/modules/user/tests/src/Functional/UserTranslationUITest.php
+++ b/core/modules/user/tests/src/Functional/UserTranslationUITest.php
@@ -18,6 +18,17 @@ class UserTranslationUITest extends ContentTranslationUITestBase {
    */
   protected $name;
 
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultCacheContexts = [
+    'languages:language_interface',
+    'theme',
+    'url.query_args:_wrapper_format',
+    'user.permissions',
+    'url.site',
+  ];
+
   /**
    * Modules to enable.
    *