diff --git a/src/wp-includes/rest-api/class-wp-rest-server.php b/src/wp-includes/rest-api/class-wp-rest-server.php
index c869bdbc05..a14de5c225 100644
--- a/src/wp-includes/rest-api/class-wp-rest-server.php
+++ b/src/wp-includes/rest-api/class-wp-rest-server.php
@@ -78,6 +78,14 @@ class WP_REST_Server {
 	 */
 	protected $route_options = array();
 
+	/**
+	 * Caches embedded requests.
+	 *
+	 * @since 5.4.0
+	 * @var array
+	 */
+	protected $embed_cache = array();
+
 	/**
 	 * Instantiates the REST server.
 	 *
@@ -462,12 +470,14 @@ class WP_REST_Server {
 		}
 
 		if ( $embed ) {
+			$this->embed_cache = array();
 			// Determine if this is a numeric array.
 			if ( wp_is_numeric_array( $data ) ) {
 				$data = array_map( array( $this, 'embed_links' ), $data );
 			} else {
 				$data = $this->embed_links( $data );
 			}
+			$this->embed_cache = array();
 		}
 
 		return $data;
@@ -588,24 +598,28 @@ class WP_REST_Server {
 					continue;
 				}
 
-				// Run through our internal routing and serve.
-				$request = WP_REST_Request::from_url( $item['href'] );
-				if ( ! $request ) {
-					$embeds[] = array();
-					continue;
-				}
+				if ( ! array_key_exists( $item['href'], $this->embed_cache ) ) {
+					// Run through our internal routing and serve.
+					$request = WP_REST_Request::from_url( $item['href'] );
+					if ( ! $request ) {
+						$embeds[] = array();
+						continue;
+					}
 
-				// Embedded resources get passed context=embed.
-				if ( empty( $request['context'] ) ) {
-					$request['context'] = 'embed';
-				}
+					// Embedded resources get passed context=embed.
+					if ( empty( $request['context'] ) ) {
+						$request['context'] = 'embed';
+					}
+
+					$response = $this->dispatch( $request );
 
-				$response = $this->dispatch( $request );
+					/** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
+					$response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request );
 
-				/** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
-				$response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request );
+					$this->embed_cache[ $item['href'] ] = $this->response_to_data( $response, false );
+				}
 
-				$embeds[] = $this->response_to_data( $response, false );
+				$embeds[] = $this->embed_cache[ $item['href'] ];
 			}
 
 			// Determine if any real links were found.
diff --git a/tests/phpunit/tests/rest-api/rest-server.php b/tests/phpunit/tests/rest-api/rest-server.php
index ec9870f033..eeab201f45 100644
--- a/tests/phpunit/tests/rest-api/rest-server.php
+++ b/tests/phpunit/tests/rest-api/rest-server.php
@@ -677,6 +677,108 @@ class Tests_REST_Server extends WP_Test_REST_TestCase {
 		$this->assertEquals( 403, $up_data['data']['status'] );
 	}
 
+	/**
+	 * @ticket 48838
+	 */
+	public function test_link_embedding_clears_cache() {
+		$post_id = self::factory()->post->create();
+
+		$response = new WP_REST_Response();
+		$response->add_link( 'post', rest_url( 'wp/v2/posts/' . $post_id ), array( 'embeddable' => true ) );
+
+		$data = rest_get_server()->response_to_data( $response, true );
+		$this->assertArrayHasKey( 'post', $data['_embedded'] );
+		$this->assertCount( 1, $data['_embedded']['post'] );
+
+		wp_update_post(
+			array(
+				'ID'         => $post_id,
+				'post_title' => 'My Awesome Title',
+			)
+		);
+
+		$data = rest_get_server()->response_to_data( $response, true );
+		$this->assertArrayHasKey( 'post', $data['_embedded'] );
+		$this->assertCount( 1, $data['_embedded']['post'] );
+		$this->assertEquals( 'My Awesome Title', $data['_embedded']['post'][0]['title']['rendered'] );
+	}
+
+	/**
+	 * @ticket 48838
+	 */
+	public function test_link_embedding_cache() {
+		$response = new WP_REST_Response(
+			array(
+				'id' => 1,
+			)
+		);
+		$response->add_link(
+			'author',
+			rest_url( 'wp/v2/users/1' ),
+			array( 'embeddable' => true )
+		);
+		$response->add_link(
+			'author',
+			rest_url( 'wp/v2/users/1' ),
+			array( 'embeddable' => true )
+		);
+
+		$mock = new MockAction();
+		add_filter( 'rest_post_dispatch', array( $mock, 'filter' ) );
+
+		$data = rest_get_server()->response_to_data( $response, true );
+
+		$this->assertArrayHasKey( '_embedded', $data );
+		$this->assertArrayHasKey( 'author', $data['_embedded'] );
+		$this->assertCount( 2, $data['_embedded']['author'] );
+
+		$this->assertCount( 1, $mock->get_events() );
+	}
+
+	/**
+	 * @ticket 48838
+	 */
+	public function test_link_embedding_cache_collection() {
+		$response = new WP_REST_Response(
+			array(
+				array(
+					'id' => 1,
+					'_links' => array(
+						'author' => array(
+							array(
+								'href' => rest_url( 'wp/v2/users/1' ),
+								'embeddable' => true,
+							),
+						),
+					),
+				),
+				array(
+					'id' => 2,
+					'_links' => array(
+						'author' => array(
+							array(
+								'href' => rest_url( 'wp/v2/users/1' ),
+								'embeddable' => true,
+							),
+						),
+					),
+				),
+			)
+		);
+
+		$mock = new MockAction();
+		add_filter( 'rest_post_dispatch', array( $mock, 'filter' ) );
+
+		$data = rest_get_server()->response_to_data( $response, true );
+
+		$embeds = wp_list_pluck( $data, '_embedded' );
+		$this->assertCount( 2, $embeds );
+		$this->assertArrayHasKey( 'author', $embeds[0] );
+		$this->assertArrayHasKey( 'author', $embeds[1] );
+
+		$this->assertCount( 1, $mock->get_events() );
+	}
+
 	/**
 	 * Ensure embedding is a no-op without links in the data.
 	 */
