diff --git a/core/modules/jsonapi/src/Normalizer/LinkCollectionNormalizer.php b/core/modules/jsonapi/src/Normalizer/LinkCollectionNormalizer.php
index 2c3f5b71de4665fc548e21e40939704d3cc334ad..a2440a9aff2a8a432af6032c76ec2d03896325da 100644
--- a/core/modules/jsonapi/src/Normalizer/LinkCollectionNormalizer.php
+++ b/core/modules/jsonapi/src/Normalizer/LinkCollectionNormalizer.php
@@ -17,7 +17,8 @@
  *
  * When normalizing more than one link in a LinkCollection with the same key, a
  * unique and random string is appended to the link's key after a double dash
- * (--) to differentiate the links.
+ * (--) to differentiate the links. See this class's hashByHref() method for
+ * details.
  *
  * This may change with a later version of the JSON:API specification.
  *
@@ -82,7 +83,15 @@ public function normalize($object, $format = NULL, array $context = []) {
   }
 
   /**
-   * Hashes a link by its href.
+   * Hashes a link using its href and its target attributes, if any.
+   *
+   * This method generates an unpredictable, but deterministic, 7 character
+   * alphanumeric hash for a given link.
+   *
+   * The hash is unpredictable because a random hash salt will be used for every
+   * request. The hash is deterministic because, within a single request, links
+   * with the same href and target attributes (i.o.w. duplicates) will generate
+   * equivalent hash values.
    *
    * @param \Drupal\jsonapi\JsonApiResource\Link $link
    *   A link to be hashed.
@@ -91,10 +100,23 @@ public function normalize($object, $format = NULL, array $context = []) {
    *   A 7 character alphanumeric hash.
    */
   protected function hashByHref(Link $link) {
+    // Generate a salt unique to each instance of this class.
     if (!$this->hashSalt) {
       $this->hashSalt = Crypt::randomBytesBase64();
     }
-    return substr(str_replace(['-', '_'], '', Crypt::hashBase64($this->hashSalt . $link->getHref())), 0, 7);
+    // Create a dictionary of link parameters.
+    $link_parameters = [
+      'href' => $link->getHref(),
+    ] + $link->getTargetAttributes();
+    // Serialize the dictionary into a string.
+    foreach ($link_parameters as $name => $value) {
+      $serialized_parameters[] = sprintf('%s="%s"', $name, implode(' ', (array) $value));
+    }
+    // Hash the string.
+    $b64_hash = Crypt::hashBase64($this->hashSalt . implode('; ', $serialized_parameters));
+    // Remove any dashes and underscores from the base64 hash and then return
+    // the first 7 characters.
+    return substr(str_replace(['-', '_'], '', $b64_hash), 0, 7);
   }
 
 }
diff --git a/core/modules/jsonapi/tests/src/Kernel/Normalizer/LinkCollectionNormalizerTest.php b/core/modules/jsonapi/tests/src/Kernel/Normalizer/LinkCollectionNormalizerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9385b09749f309804c7e67c60d37980715bf5060
--- /dev/null
+++ b/core/modules/jsonapi/tests/src/Kernel/Normalizer/LinkCollectionNormalizerTest.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Drupal\Tests\jsonapi\Kernel\Normalizer;
+
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Url;
+use Drupal\jsonapi\JsonApiResource\Link;
+use Drupal\jsonapi\JsonApiResource\LinkCollection;
+use Drupal\jsonapi\JsonApiResource\ResourceObject;
+use Drupal\jsonapi\Normalizer\LinkCollectionNormalizer;
+use Drupal\jsonapi\ResourceType\ResourceType;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * @coversDefaultClass \Drupal\jsonapi\Normalizer\LinkCollectionNormalizer
+ * @group jsonapi
+ *
+ * @internal
+ */
+class LinkCollectionNormalizerTest extends KernelTestBase {
+
+  /**
+   * The subject under test.
+   *
+   * @var \Symfony\Component\Serializer\Normalizer\NormalizerInterface
+   */
+  protected $normalizer;
+
+  /**
+   * {@inheritDoc}
+   */
+  protected static $modules = [
+    'jsonapi',
+    'serialization',
+  ];
+
+  /**
+   * {@inheritDoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->normalizer = new LinkCollectionNormalizer();
+    $this->normalizer->setSerializer($this->container->get('jsonapi.serializer'));
+  }
+
+  /**
+   * Tests the link collection normalizer.
+   */
+  public function testNormalize() {
+    $link_context = new ResourceObject(new CacheableMetadata(), new ResourceType('n/a', 'n/a', 'n/a'), 'n/a', NULL, [], new LinkCollection([]));
+    $link_collection = (new LinkCollection([]))
+      ->withLink('related', new Link(new CacheableMetadata(), Url::fromUri('http://example.com/post/42'), 'related', ['title' => 'Most viewed']))
+      ->withLink('related', new Link(new CacheableMetadata(), Url::fromUri('http://example.com/post/42'), 'related', ['title' => 'Top rated']))
+      ->withContext($link_context);
+    $normalized = $this->normalizer->normalize($link_collection)->getNormalization();
+    $this->assertIsArray($normalized);
+    foreach (array_keys($normalized) as $key) {
+      $this->assertStringStartsWith('related', $key);
+    }
+    $this->assertSame([
+      [
+        'href' => 'http://example.com/post/42',
+        'meta' => [
+          'title' => 'Most viewed',
+        ],
+      ],
+      [
+        'href' => 'http://example.com/post/42',
+        'meta' => [
+          'title' => 'Top rated',
+        ],
+      ],
+    ], array_values($normalized));
+  }
+
+}