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 e07e214d97..dbf7547c1d 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.
 	 *
@@ -450,12 +458,14 @@ class WP_REST_Server {
 			$data['_links'] = $links;
 		}
 		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;
@@ -575,24 +585,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 74d957cbe5..4e1b4baae1 100644
--- a/tests/phpunit/tests/rest-api/rest-server.php
+++ b/tests/phpunit/tests/rest-api/rest-server.php
@@ -677,6 +677,30 @@ 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'] );
+	}
+
 	/**
 	 * Ensure embedding is a no-op without links in the data.
 	 */
