diff --git a/src/Normalizer/FieldNormalizer.php b/src/Normalizer/FieldNormalizer.php
index 98d8ed8..76bd532 100644
--- a/src/Normalizer/FieldNormalizer.php
+++ b/src/Normalizer/FieldNormalizer.php
@@ -3,6 +3,8 @@
 namespace Drupal\jsonapi\Normalizer;
 
 use Drupal\Component\Assertion\Inspector;
+use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Field\EntityReferenceFieldItemList;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
@@ -50,7 +52,8 @@ class FieldNormalizer extends NormalizerBase implements DenormalizerInterface {
       $cardinality = $field->getFieldDefinition()
         ->getFieldStorageDefinition()
         ->getCardinality();
-      return new FieldNormalizerValue($access, $normalized_field_items, $cardinality, $property_type);
+      $cacheability = CacheableMetadata::createFromObject($access);
+      return new FieldNormalizerValue($cacheability, $normalized_field_items, $cardinality, $property_type);
     }
     else {
       return new NullFieldNormalizerValue($access, $property_type);
diff --git a/src/Normalizer/RelationshipNormalizer.php b/src/Normalizer/RelationshipNormalizer.php
index 3c32d6c..0bf90cf 100644
--- a/src/Normalizer/RelationshipNormalizer.php
+++ b/src/Normalizer/RelationshipNormalizer.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\jsonapi\Normalizer;
 
-use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityRepositoryInterface;
@@ -200,14 +200,8 @@ class RelationshipNormalizer extends NormalizerBase implements DenormalizerInter
       'link_manager' => $this->linkManager,
       'resource_type' => $resource_type,
     ];
-    // If this is called, access to the Relationship field is allowed. The
-    // cacheability of the access result is carried by the Relationship value
-    // object. Therefore, we can safely construct an access result object here.
-    // Access to the targeted related resources will be checked separately.
-    // @see \Drupal\jsonapi\Normalizer\EntityReferenceFieldNormalizer::normalize()
-    // @see \Drupal\jsonapi\Normalizer\RelationshipItemNormalizer::normalize()
-    $relationship_access = AccessResult::allowed()->addCacheableDependency($relationship);
-    return new RelationshipNormalizerValue($relationship_access, $normalizer_items, $cardinality, $link_context);
+    $cacheability = CacheableMetadata::createFromObject($relationship);
+    return new RelationshipNormalizerValue($cacheability, $normalizer_items, $cardinality, $link_context);
   }
 
   /**
diff --git a/src/Normalizer/Value/FieldNormalizerValue.php b/src/Normalizer/Value/FieldNormalizerValue.php
index 295cb44..c51074c 100644
--- a/src/Normalizer/Value/FieldNormalizerValue.php
+++ b/src/Normalizer/Value/FieldNormalizerValue.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\jsonapi\Normalizer\Value;
 
-use Drupal\Core\Access\AccessResultInterface;
+use Drupal\Core\Cache\CacheableDependencyInterface;
 use Drupal\Core\Cache\CacheableDependencyTrait;
 
 /**
@@ -39,8 +39,12 @@ class FieldNormalizerValue implements FieldNormalizerValueInterface {
   /**
    * Instantiate a FieldNormalizerValue object.
    *
-   * @param \Drupal\Core\Access\AccessResultInterface $field_access_result
-   *   The field access result.
+   * @param \Drupal\Core\Cache\CacheableDependencyInterface $field_cacheability
+   *   The cacheability of the normalized field. This cacheability is not part
+   *   of $values because field item list is normalized by Drupal core's
+   *   serialization system, which was never designed with cacheability in mind.
+   *   FieldNormalizer::normalize() must catch the out-of-band bubbled
+   *   cacheability and then passes it to this value object.
    * @param \Drupal\jsonapi\Normalizer\Value\FieldItemNormalizerValue[] $values
    *   The normalized result.
    * @param int $cardinality
@@ -48,9 +52,9 @@ class FieldNormalizerValue implements FieldNormalizerValueInterface {
    * @param string $property_type
    *   The property type of the field: 'attributes' or 'relationships'.
    */
-  public function __construct(AccessResultInterface $field_access_result, array $values, $cardinality, $property_type) {
+  public function __construct(CacheableDependencyInterface $field_cacheability, array $values, $cardinality, $property_type) {
     assert($property_type === 'attributes' || $property_type === 'relationships');
-    $this->setCacheability(static::mergeCacheableDependencies(array_merge([$field_access_result], $values)));
+    $this->setCacheability(static::mergeCacheableDependencies(array_merge([$field_cacheability], $values)));
 
     $this->values = $values;
     $this->cardinality = $cardinality;
diff --git a/src/Normalizer/Value/RelationshipNormalizerValue.php b/src/Normalizer/Value/RelationshipNormalizerValue.php
index 0b81a0a..637e827 100644
--- a/src/Normalizer/Value/RelationshipNormalizerValue.php
+++ b/src/Normalizer/Value/RelationshipNormalizerValue.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\jsonapi\Normalizer\Value;
 
-use Drupal\Core\Access\AccessResultInterface;
+use Drupal\Core\Cache\CacheableDependencyInterface;
 
 /**
  * Helps normalize relationships in compliance with the JSON API spec.
@@ -42,8 +42,8 @@ class RelationshipNormalizerValue extends FieldNormalizerValue {
   /**
    * Instantiate a EntityReferenceNormalizerValue object.
    *
-   * @param \Drupal\Core\Access\AccessResultInterface $relationship_access_result
-   *   The relationship access result.
+   * @param \Drupal\Core\Cache\CacheableDependencyInterface $relationship_cacheability
+   *   The cacheability of the relationship.
    * @param RelationshipItemNormalizerValue[] $values
    *   The normalized result.
    * @param int $cardinality
@@ -52,7 +52,7 @@ class RelationshipNormalizerValue extends FieldNormalizerValue {
    *   All the objects and variables needed to generate the links for this
    *   relationship.
    */
-  public function __construct(AccessResultInterface $relationship_access_result, array $values, $cardinality, array $link_context) {
+  public function __construct(CacheableDependencyInterface $relationship_cacheability, array $values, $cardinality, array $link_context) {
     $this->hostEntityId = $link_context['host_entity_id'];
     $this->fieldName = $link_context['field_name'];
     $this->linkManager = $link_context['link_manager'];
@@ -62,7 +62,7 @@ class RelationshipNormalizerValue extends FieldNormalizerValue {
         throw new \RuntimeException(sprintf('Unexpected normalizer item value for this %s.', get_called_class()));
       }
     });
-    parent::__construct($relationship_access_result, $values, $cardinality, 'relationships');
+    parent::__construct($relationship_cacheability, $values, $cardinality, 'relationships');
   }
 
   /**
diff --git a/tests/modules/jsonapi_test_computed_field/jsonapi_test_computed_field.info.yml b/tests/modules/jsonapi_test_computed_field/jsonapi_test_computed_field.info.yml
new file mode 100644
index 0000000..5c61ed6
--- /dev/null
+++ b/tests/modules/jsonapi_test_computed_field/jsonapi_test_computed_field.info.yml
@@ -0,0 +1,9 @@
+name: 'JSON API computed field test'
+description: 'JSON API test for computed field normalization'
+type: module
+package: Testing
+core: 8.x
+dependencies:
+  - drupal:entity_test
+  - jsonapi:jsonapi
+  - drupal:field
diff --git a/tests/modules/jsonapi_test_computed_field/jsonapi_test_computed_field.module b/tests/modules/jsonapi_test_computed_field/jsonapi_test_computed_field.module
new file mode 100644
index 0000000..2cbca3d
--- /dev/null
+++ b/tests/modules/jsonapi_test_computed_field/jsonapi_test_computed_field.module
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Module file for JSON API computed field test.
+ */
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\jsonapi_test_computed_field\JsonApiComputedFieldItemList;
+
+/**
+ * Implements hook_entity_base_field_info().
+ */
+function jsonapi_test_computed_field_entity_base_field_info(EntityTypeInterface $entity_type) {
+  $fields = [];
+  if ($entity_type->id() === 'entity_test') {
+    $fields['jsonapi_test_computed_field'] = BaseFieldDefinition::create('cacheable_string')
+      ->setLabel(t('JSON API test computed field'))
+      ->setDescription(t('JSON API test for computed field normalization.'))
+      ->setComputed(TRUE)
+      ->setClass(JsonApiComputedFieldItemList::class)
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayConfigurable('view', FALSE)
+      ->setReadOnly(FALSE)
+      ->setInternal(FALSE)
+      ->setDisplayOptions('view', [
+        'label' => 'hidden',
+        'region' => 'hidden',
+        'weight' => -5,
+      ])
+      ->setDisplayOptions('form', [
+        'label' => 'hidden',
+        'region' => 'hidden',
+        'weight' => -5,
+      ])
+      ->setTargetEntityTypeId('entity_test')
+      ->setTargetBundle('entity_test')
+      ->setName('jsonapi_test_computed_field');
+  }
+
+  return $fields;
+}
diff --git a/tests/modules/jsonapi_test_computed_field/src/JsonApiComputedFieldItemList.php b/tests/modules/jsonapi_test_computed_field/src/JsonApiComputedFieldItemList.php
new file mode 100644
index 0000000..189817b
--- /dev/null
+++ b/tests/modules/jsonapi_test_computed_field/src/JsonApiComputedFieldItemList.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\jsonapi_test_computed_field;
+
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Field\FieldItemList;
+use Drupal\Core\TypedData\ComputedItemListTrait;
+
+/**
+ * Item list class for jsonapi_test_computed_field.
+ */
+class JsonApiComputedFieldItemList extends FieldItemList {
+
+  use ComputedItemListTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function computeValue() {
+    /** @var \Drupal\jsonapi_test_computed_field\Plugin\Field\FieldType\CacheableStringItem $item */
+    $item = $this->createItem(0, 'jsonapi_test_computed_field');
+    /** @var \Drupal\jsonapi_test_computed_field\Plugin\DataType\CacheableStringData $value */
+    $value = $item->get('value');
+    $cacheability = (new CacheableMetadata())
+      ->setCacheContexts(['user'])
+      ->setCacheTags(['field:jsonapi_test_computed_field'])
+      ->setCacheMaxAge(800);
+    $value->setCacheability($cacheability);
+    $item->set('value', $value, FALSE);
+    $this->list[0] = $item;
+  }
+
+}
diff --git a/tests/modules/jsonapi_test_computed_field/src/Plugin/DataType/CacheableStringData.php b/tests/modules/jsonapi_test_computed_field/src/Plugin/DataType/CacheableStringData.php
new file mode 100644
index 0000000..4cce0a8
--- /dev/null
+++ b/tests/modules/jsonapi_test_computed_field/src/Plugin/DataType/CacheableStringData.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\jsonapi_test_computed_field\Plugin\DataType;
+
+use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Cache\CacheableDependencyTrait;
+use Drupal\Core\TypedData\Plugin\DataType\StringData;
+
+/**
+ * The string data type with cacheablity metadata.
+ *
+ * The plain value of a string is a regular PHP string. For setting the value
+ * any PHP variable that casts to a string may be passed.
+ *
+ * @DataType(
+ *   id = "cacheable_string",
+ *   label = @Translation("Cacheable String")
+ * )
+ */
+class CacheableStringData extends  StringData implements CacheableDependencyInterface {
+
+  use CacheableDependencyTrait;
+
+}
diff --git a/tests/modules/jsonapi_test_computed_field/src/Plugin/Field/FieldType/CacheableStringItem.php b/tests/modules/jsonapi_test_computed_field/src/Plugin/Field/FieldType/CacheableStringItem.php
new file mode 100644
index 0000000..51628d1
--- /dev/null
+++ b/tests/modules/jsonapi_test_computed_field/src/Plugin/Field/FieldType/CacheableStringItem.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\jsonapi_test_computed_field\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\TypedData\DataDefinition;
+
+/**
+ * Defines the 'string' entity field type with cacheablity metadata.
+ *
+ * @FieldType(
+ *   id = "cacheable_string",
+ *   label = @Translation("Text (plain with cacheablity)"),
+ *   description = @Translation("A field containing a plain string value and cacheablity metadata."),
+ *   category = @Translation("Text"),
+ *   no_ui = TRUE,
+ *   default_widget = "string_textfield",
+ *   default_formatter = "string"
+ * )
+ */
+class CacheableStringItem extends StringItem {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
+    $properties['value'] = DataDefinition::create('cacheable_string')
+      ->setLabel(new TranslatableMarkup('Text value'))
+      ->setSetting('case_sensitive', $field_definition->getSetting('case_sensitive'))
+      ->setRequired(TRUE);
+
+    return $properties;
+  }
+
+}
diff --git a/tests/src/Kernel/Serializer/ComputedFieldSerializerTest.php b/tests/src/Kernel/Serializer/ComputedFieldSerializerTest.php
new file mode 100644
index 0000000..161cc00
--- /dev/null
+++ b/tests/src/Kernel/Serializer/ComputedFieldSerializerTest.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\Tests\jsonapi\Kernel\Serializer;
+
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\jsonapi\Normalizer\Value\EntityNormalizerValue;
+use Drupal\Tests\jsonapi\Kernel\JsonapiKernelTestBase;
+
+/**
+ * Tests the JSON API serializer.
+ *
+ * @coversClass \Drupal\jsonapi\Serializer\Serializer
+ * @group jsonapi
+ *
+ * @internal
+ */
+class ComputedFieldSerializerTest extends JsonapiKernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'jsonapi_test_computed_field',
+  ];
+
+  /**
+   * An entity for testing.
+   *
+   * @var \Drupal\entity_test\Entity\EntityTest
+   */
+  protected $entity_test;
+
+  /**
+   * The subject under test.
+   *
+   * @var \Drupal\jsonapi\Serializer\Serializer
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    // Add the entity schemas.
+    $this->installEntitySchema('entity_test');
+    // Add the additional table schemas.
+    $this->installSchema('system', ['sequences']);
+    $this->container->setAlias('sut', 'jsonapi.serializer_do_not_use_removal_imminent');
+    $this->sut = $this->container->get('sut');
+    $this->entity_test = EntityTest::create([
+      'name' => 'Test Entity for Computed Field',
+      'type' => 'entity_test',
+    ]);
+    $this->entity_test->save();
+  }
+
+  /**
+   * @covers \Drupal\jsonapi\Serializer\Serializer::normalize
+   */
+  public function testFallbackNormalizer() {
+    $value = $this->sut->normalize($this->entity_test, 'api_json');
+    $this->assertInstanceOf(EntityNormalizerValue::class, $value);
+    $this->assertContains('user', $value->getCacheContexts());
+    $this->assertContains('field:jsonapi_test_computed_field', $value->getCacheTags());
+    $this->assertSame(800, $value->getCacheMaxAge());
+  }
+
+}
diff --git a/tests/src/Unit/Normalizer/Value/FieldNormalizerValueTest.php b/tests/src/Unit/Normalizer/Value/FieldNormalizerValueTest.php
index 5014c86..8094e7b 100644
--- a/tests/src/Unit/Normalizer/Value/FieldNormalizerValueTest.php
+++ b/tests/src/Unit/Normalizer/Value/FieldNormalizerValueTest.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Tests\jsonapi\Unit\Normalizer\Value;
 
-use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\jsonapi\Normalizer\Value\FieldItemNormalizerValue;
 use Drupal\jsonapi\Normalizer\Value\FieldNormalizerValue;
@@ -45,7 +45,11 @@ class FieldNormalizerValueTest extends UnitTestCase {
    * @dataProvider rasterizeValueProvider
    */
   public function testRasterizeValue($values, $cardinality, $expected) {
-    $object = new FieldNormalizerValue(AccessResult::allowed()->cachePerUser()->addCacheTags(['field:foo']), $values, $cardinality, 'attributes');
+    $cacheability = new CacheableMetadata();
+    $cacheability->addCacheContexts(['user']);
+    $cacheability->addCacheTags(['field:foo']);
+    $cacheability->setCacheMaxAge(8000);
+    $object = new FieldNormalizerValue($cacheability, $values, $cardinality, 'attributes');
     $this->assertEquals($expected, $object->rasterizeValue());
     $this->assertSame(['ccfoo', 'user'], $object->getCacheContexts());
     $this->assertSame(['ctfoo', 'field:foo'], $object->getCacheTags());
