diff --git a/includes/class-algolia-search.php b/includes/class-algolia-search.php
index b1234186..c06dc03f 100644
--- a/includes/class-algolia-search.php
+++ b/includes/class-algolia-search.php
@@ -2,10 +2,15 @@
 
 class Algolia_Search {
 
+	/**
+	 * @var array
+	 */
+	private $current_page_hits = [];
+
 	/**
 	 * @var int
 	 */
-	private $nb_hits;
+	private $total_hits;
 
 	/**
 	 * @var Algolia_Index
@@ -18,7 +23,9 @@ class Algolia_Search {
 	public function __construct( Algolia_Index $index ) {
 		$this->index = $index;
 
+		add_action( 'loop_start', [ $this, 'begin_highlighting' ] );
 		add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
+		add_action( 'wp_head', [ $this, 'output_highlighting_bundled_styles' ] );
 	}
 
 	/**
@@ -56,6 +63,8 @@ public function pre_get_posts( WP_Query $query ) {
 				'attributesToRetrieve' => 'post_id',
 				'hitsPerPage'          => $posts_per_page,
 				'page'                 => $current_page - 1, // Algolia pages are zero indexed.
+				'highlightPreTag'      => '<em class="algolia-search-highlight">',
+				'highlightPostTag'     => '</em>',
 			)
 		);
 
@@ -73,9 +82,14 @@ public function pre_get_posts( WP_Query $query ) {
 		add_filter( 'found_posts', array( $this, 'found_posts' ), 10, 2 );
 		add_filter( 'posts_search', array( $this, 'posts_search' ), 10, 2 );
 
+		// Store the current page hits, so that we can use them for highlighting later on.
+		foreach ( $results['hits'] as $hit ) {
+			$this->current_page_hits[ $hit['post_id'] ] = $hit;
+		}
+
 		// Store the total number of its, so that we can hook into the `found_posts`.
 		// This is useful for pagination.
-		$this->nb_hits = $results['nbHits'];
+		$this->total_hits = $results['nbHits'];
 
 		$post_ids = array();
 		foreach ( $results['hits'] as $result ) {
@@ -117,7 +131,7 @@ public function pre_get_posts( WP_Query $query ) {
 	 * @return int
 	 */
 	public function found_posts( $found_posts, WP_Query $query ) {
-		return $this->should_filter_query( $query ) ? $this->nb_hits : $found_posts;
+		return $this->should_filter_query( $query ) ? $this->total_hits : $found_posts;
 	}
 
 	/**
@@ -134,4 +148,108 @@ public function found_posts( $found_posts, WP_Query $query ) {
 	public function posts_search( $search, WP_Query $query ) {
 		return $this->should_filter_query( $query ) ? '' : $search;
 	}
+
+	/**
+	 * Output the bundled styles for highlighting search result matches, if enabled.
+	 */
+	public function output_highlighting_bundled_styles() {
+		if ( ! $this->highlighting_enabled() ) {
+			return;
+		}
+
+		if ( ! apply_filters( 'algolia_search_highlighting_enable_bundled_styles', true ) ) {
+			return;
+		}
+
+		?>
+		<style>
+			.algolia-search-highlight {
+				background-color: #fffbcc;
+				border-radius: 2px;
+				font-style: normal;
+			}
+		</style>
+		<?php
+	}
+
+	/**
+	 * Begin highlighting search result matches, if enabled.
+	 *
+	 * This method is called on the loop_start action, where we want to begin highlighting search result matches.
+	 *
+	 * @param WP_Query $query
+	 */
+	public function begin_highlighting( $query ) {
+		if ( ! $this->should_filter_query( $query ) ) {
+			return;
+		}
+
+		if ( ! $this->highlighting_enabled() ) {
+			return;
+		}
+
+		add_filter( 'the_title', [ $this, 'highlight_the_title' ], 10, 2 );
+		add_filter( 'get_the_excerpt', [ $this, 'highlight_get_the_excerpt' ], 10, 2 );
+
+		add_action( 'loop_end', [ $this, 'end_highlighting' ] );
+	}
+
+	/**
+	 * Stop highlighting search result matches.
+	 *
+	 * This method is called on the loop_end action, where we want to stop highlighting search result matches.
+	 *
+	 * @param WP_Query $query
+	 */
+	public function end_highlighting( $query ) {
+		remove_filter( 'the_title', [ $this, 'highlight_the_title' ], 10 );
+		remove_filter( 'get_the_excerpt', [ $this, 'highlight_get_the_excerpt' ], 10 );
+
+		remove_action( 'loop_end', [ $this, 'end_highlighting' ] );
+	}
+
+	/**
+	 * Filter the_title, replacing it with the highlighted title from the Algolia index.
+	 *
+	 * @param string $title
+	 * @param int    $post_id
+	 *
+	 * @return string
+	 */
+	public function highlight_the_title( $title, $post_id ) {
+		$highlighted_title = $this->current_page_hits[ $post_id ]['_highlightResult']['post_title']['value'] ?? null;
+
+		if ( ! empty( $highlighted_title ) ) {
+			$title = $highlighted_title;
+		}
+
+		return $title;
+	}
+
+	/**
+	 * Filter get_the_excerpt, replacing it with the highlighted excerpt from the Algolia index.
+	 *
+	 * @param string  $excerpt
+	 * @param WP_Post $post
+	 *
+	 * @return string
+	 */
+	public function highlight_get_the_excerpt( $excerpt, $post ) {
+		$highlighted_excerpt = $this->current_page_hits[ $post->ID ]['_snippetResult']['content']['value'] ?? null;
+
+		if ( ! empty( $highlighted_excerpt ) ) {
+			$excerpt = $highlighted_excerpt;
+		}
+
+		return $excerpt;
+	}
+
+	/**
+	 * Determine whether highlighting is enabled.
+	 *
+	 * @return bool
+	 */
+	private function highlighting_enabled() : bool {
+		return apply_filters( 'algolia_search_highlighting_enabled', true );
+	}
 }
diff --git a/includes/indices/class-algolia-searchable-posts-index.php b/includes/indices/class-algolia-searchable-posts-index.php
index 1c664e4f..79ac85fe 100644
--- a/includes/indices/class-algolia-searchable-posts-index.php
+++ b/includes/indices/class-algolia-searchable-posts-index.php
@@ -201,7 +201,7 @@ protected function get_settings() {
 			),
 			'attributesToSnippet'   => array(
 				'post_title:30',
-				'content:30',
+				'content:' . intval( apply_filters( 'excerpt_length', 55 ) ),
 			),
 			'snippetEllipsisText'   => '…',
 		);