diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/jsonapi_test_entity_revisions_normalization_cache.info.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/jsonapi_test_entity_revisions_normalization_cache.info.yml
new file mode 100644
index 0000000000..d2b3b3c5ea
--- /dev/null
+++ b/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/jsonapi_test_entity_revisions_normalization_cache.info.yml
@@ -0,0 +1,8 @@
+name: jsonapi_test_entity_revisions_normalization_cache
+type: module
+description: 'Provides extended `jsonapi.entity_resource` for programmatic use.'
+package: Testing
+
+dependencies:
+  - drupal:jsonapi
+  - drupal:node
diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/jsonapi_test_entity_revisions_normalization_cache.routing.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/jsonapi_test_entity_revisions_normalization_cache.routing.yml
new file mode 100644
index 0000000000..3c2a81873b
--- /dev/null
+++ b/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/jsonapi_test_entity_revisions_normalization_cache.routing.yml
@@ -0,0 +1,8 @@
+jsonapi_test_entity_revisions_normalization_cache:
+  path: '/jsonapi_test_entity_revisions_normalization_cache/{entity_type_id}/{entity_ids}/{type}'
+  defaults:
+    _controller: 'Drupal\jsonapi_test_entity_revisions_normalization_cache\Controller\JsonApiEntityController::test'
+  requirements:
+    _access: 'TRUE'
+  methods:
+    - GET
diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/jsonapi_test_entity_revisions_normalization_cache.services.yml b/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/jsonapi_test_entity_revisions_normalization_cache.services.yml
new file mode 100644
index 0000000000..a7632cb7f7
--- /dev/null
+++ b/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/jsonapi_test_entity_revisions_normalization_cache.services.yml
@@ -0,0 +1,4 @@
+services:
+  jsonapi_test_entity_revisions_normalization_cache.entity_resource:
+    class: Drupal\jsonapi_test_entity_revisions_normalization_cache\Controller\EntityResource
+    parent: jsonapi.entity_resource
diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/src/Controller/EntityResource.php b/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/src/Controller/EntityResource.php
new file mode 100644
index 0000000000..8060f993e3
--- /dev/null
+++ b/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/src/Controller/EntityResource.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Drupal\jsonapi_test_entity_revisions_normalization_cache\Controller;
+
+use Drupal\jsonapi\Controller\EntityResource as EntityResourceBase;
+use Drupal\jsonapi\JsonApiResource\NullIncludedData;
+use Drupal\jsonapi\JsonApiResource\ResourceObjectData;
+use Drupal\jsonapi\Query\OffsetPage;
+use Drupal\jsonapi\ResourceResponse;
+use Drupal\jsonapi\Routing\Routes;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Test entity resource.
+ */
+class EntityResource extends EntityResourceBase {
+
+  /**
+   * Returns the JSON:API formatted response for entities collection.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The inbound HTTP request.
+   * @param \Drupal\Core\Entity\EntityInterface[] $entities
+   *   The list of entities to build a response for. Note that
+   *   the ordering matters and will be preserved.
+   * @param int $total
+   *   The total number of entities. This value is used to build
+   *   pagination links.
+   * @param int $page
+   *   The number of a page the list of entities is displayed at.
+   * @param int $per_page
+   *   The number of entities per page. Note that the count of
+   *   entities must not be greater than this value.
+   *
+   * @return \Drupal\jsonapi\ResourceResponse
+   *   The JSON:API formatted response for entities collection.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   */
+  public function getCollectionResponse(Request $request, array $entities, int $total, int $page, int $per_page): ResourceResponse {
+    // Clone current request for further modifications.
+    $request = clone $request;
+    // Mark the request in order to allow JSON:API to recognize requests.
+    /* @see \Drupal\jsonapi\Routing\Routes::getResourceTypeNameFromParameters() */
+    $request->attributes->set(Routes::JSON_API_ROUTE_FLAG_KEY, TRUE);
+
+    if (empty($entities)) {
+      $response = $this->buildWrappedResponse(new ResourceObjectData([]), $request, new NullIncludedData());
+    }
+    else {
+      $resources = [];
+
+      foreach ($entities as $entity) {
+        $resources[] = $this->entityAccessChecker->getAccessCheckedResourceObject($entity);
+      }
+
+      \assert(isset($entity));
+      // Use the last entity that is definitely defined because the
+      // entities list isn't empty.
+      $resource = $this->resourceTypeRepository->get($entity->getEntityTypeId(), $entity->bundle());
+      $offset = $page * $per_page;
+      $data = new ResourceObjectData($resources);
+
+      $data->setTotalCount($total);
+      $data->setHasNextPage($offset < $total);
+
+      $request->attributes->set(Routes::RESOURCE_TYPE_KEY, $resource);
+
+      $response = $this->respondWithCollection($data, $this->getIncludes($request, $data), $request, $resource, new OffsetPage($offset, $per_page));
+    }
+
+    return $response;
+  }
+
+}
diff --git a/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/src/Controller/JsonApiEntityController.php b/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/src/Controller/JsonApiEntityController.php
new file mode 100644
index 0000000000..f01b1b8028
--- /dev/null
+++ b/core/modules/jsonapi/tests/modules/jsonapi_test_entity_revisions_normalization_cache/src/Controller/JsonApiEntityController.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\jsonapi_test_entity_revisions_normalization_cache\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\RevisionableStorageInterface;
+use Drupal\jsonapi\ResourceResponse;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Test controller.
+ */
+class JsonApiEntityController extends ControllerBase {
+
+  /**
+   * The "jsonapi_test_entity_revisions_normalization_cache.entity_resource".
+   *
+   * @var \Drupal\jsonapi_test_entity_revisions_normalization_cache\Controller\EntityResource
+   */
+  protected $entityResource;
+
+  /**
+   * JsonApiEntityController constructor.
+   *
+   * @param \Drupal\jsonapi_test_entity_revisions_normalization_cache\Controller\EntityResource $entity_resource
+   *   The "jsonapi_test_entity_revisions_normalization_cache.entity_resource".
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The "entity_type.manager".
+   */
+  public function __construct(
+    EntityResource $entity_resource,
+    EntityTypeManagerInterface $entity_type_manager
+  ) {
+    $this->entityResource = $entity_resource;
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container): self {
+    return new static(
+      $container->get('jsonapi_test_entity_revisions_normalization_cache.entity_resource'),
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * Returns the JSON:API formatted response for entities collection.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The inbound HTTP request.
+   * @param string $entity_type_id
+   *   The entity type ID.
+   * @param string $entity_ids
+   *   The list of entity/revision IDs glued by "-".
+   * @param string $type
+   *   The request type, either "revisions" or "default".
+   *
+   * @return \Drupal\jsonapi\ResourceResponse
+   *   The JSON:API formatted response for entities collection.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   */
+  public function test(Request $request, string $entity_type_id, string $entity_ids, string $type): ResourceResponse {
+    $storage = $this->entityTypeManager->getStorage($entity_type_id);
+    $entity_ids = explode('-', $entity_ids);
+
+    switch ($type) {
+      case 'revisions':
+        \assert($storage instanceof RevisionableStorageInterface);
+        $entities = $storage->loadMultipleRevisions($entity_ids);
+        break;
+
+      case 'default':
+        $entities = $storage->loadMultiple($entity_ids);
+        break;
+
+      default:
+        throw new \InvalidArgumentException(\sprintf('The "%s" is not supported.', $type));
+    }
+
+    return $this->entityResource->getCollectionResponse($request, $entities, count($entities), 1, 50);
+  }
+
+}
diff --git a/core/modules/jsonapi/tests/src/Functional/NodeTest.php b/core/modules/jsonapi/tests/src/Functional/NodeTest.php
index 363c69ec45..9d213be08b 100644
--- a/core/modules/jsonapi/tests/src/Functional/NodeTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/NodeTest.php
@@ -511,4 +511,62 @@ public function testCollectionFilterAccess() {
     $this->assertContains('user.node_grants:view', explode(' ', $response->getHeader('X-Drupal-Cache-Contexts')[0]));
   }
 
+  /**
+   * Tests normalizations' cache of multiple revisions of the same entity.
+   *
+   * @link https://www.drupal.org/project/drupal/issues/3149854
+   */
+  public function testSameEntityRevisionsNormalizationCache(): void {
+    static::assertTrue(
+      $this->container
+        ->get('module_installer')
+        ->install(['jsonapi_test_entity_revisions_normalization_cache'])
+    );
+
+    $original_title = $this->entity->getTitle();
+    $revision_ids = [];
+    $titles = [];
+
+    // Produce revisions.
+    foreach (range(1, 3) as $i) {
+      $title = sprintf('Clone #%d: "%s"', $i, $original_title);
+      $clone = clone $this->entity;
+      $clone->isDefaultRevision(FALSE);
+      $clone->setNewRevision(TRUE);
+      $clone->setPublished();
+      $clone->setTitle($title);
+      $clone->save();
+      $revision_ids[] = $clone->getRevisionId();
+      $titles[] = $title;
+    }
+
+    $url_jsonapi = Url::fromRoute(\sprintf('jsonapi.%s.collection', static::$resourceTypeName));
+    $url_custom = Url::fromRoute('jsonapi_test_entity_revisions_normalization_cache', [
+      'entity_type_id' => $this->entity->getEntityTypeId(),
+      'entity_ids' => \implode('-', $revision_ids),
+      'type' => 'revisions',
+    ]);
+
+    $this->grantPermissionsToTestedRole([
+      'access content',
+      \sprintf('view %s revisions', $this->entity->bundle()),
+    ]);
+
+    $this->drupalLogin($this->account);
+
+    $assert_titles = function (Url $url, array $expected): void {
+      $response = $this->request('GET', $url, []);
+      $normalization = Json::decode((string) $response->getBody());
+
+      // This also assert ordering.
+      static::assertSame($expected, array_map(static function (array $item) {
+        return $item['attributes']['title'];
+      }, $normalization['data']));
+    };
+
+    // Build the cache by visiting the default collection.
+    $assert_titles($url_jsonapi, [$original_title]);
+    $assert_titles($url_custom, $titles);
+  }
+
 }
