{"id":31750,"date":"2024-12-12T10:22:41","date_gmt":"2024-12-12T16:22:41","guid":{"rendered":"https:\/\/wpengine.com\/builders\/?p=31750"},"modified":"2024-12-12T11:22:27","modified_gmt":"2024-12-12T17:22:27","slug":"wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx","status":"publish","type":"post","link":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/","title":{"rendered":"WP Engine Smart Search for Headless WordPress with Next.js and MDX"},"content":{"rendered":"\n<p>If you\u2019re using Headless WordPress and aiming to build a documentation site alongside a blog, you might consider integrating MDX or Markdown files. These formats make it simple for developers to create and edit content, which is especially handy if you want to open up your docs to contributions from the open-source community.<\/p>\n\n\n\n<p>While WordPress offers powerful tools for indexing and retrieving blog content, challenges arise when you want to add search functionality that also includes MDX or Markdown-based documentation files. Without a unified search solution, users could be left searching in silos\u2014missing out on relevant information across your content types.<\/p>\n\n\n\n<p>In this guide, we\u2019ll walk you through using the WP Engine Smart Search plugin to create a seamless search experience that indexes both your WordPress content and MDX\/Markdown files. With this setup, you\u2019ll be able to serve a Next.js application where users can search blog posts, documentation, and any additional content in a unified experience.<\/p>\n\n\n\n<div class=\"wp-block-group has-polar-background-color has-background is-layout-flow wp-container-core-group-is-layout-7a03825d wp-block-group-is-layout-flow\" style=\"padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--40)\">\n<p class=\"has-large-font-size\"><strong>Table of Contents<\/strong><\/p>\n\n\n\n<ul id=\"Prerequisites\" class=\"wp-block-list\">\n<li><a href=\"#prerequisites\">Prerequisites<\/a><\/li>\n\n\n\n<li><a href=\"#wpengine-smartsearch\">WP Engine Smart Search<\/a><\/li>\n\n\n\n<li><a href=\"#steps\">Steps<\/a><\/li>\n\n\n\n<li><a href=\"#install-and-activate\">Install and Activate Smart Search<\/a><\/li>\n\n\n\n<li><a href=\"#configure-smartsearch\">Configure Smart Search<\/a><\/li>\n\n\n\n<li><a href=\"#smart-search-env-variables\">Smart Search Environment Variables<\/a><\/li>\n\n\n\n<li><a href=\"#configure-nextjs-app-router\">Configure Next.js and App Router<\/a><\/li>\n\n\n\n<li><a href=\"#installing-dependencies\">Installing Dependencies<\/a><\/li>\n\n\n\n<li><a href=\"#env-var\">Environment Variables<\/a><\/li>\n\n\n\n<li><a href=\"#next-config\">Next Config File<\/a><\/li>\n\n\n\n<li><a href=\"#mdx-files\">Create MDX Files And Paths <\/a><\/li>\n\n\n\n<li><a href=\"#global-mdx-components\">Global MDX Components<\/a><\/li>\n\n\n\n<li><a href=\"#smart-search-plugin\">Smart Search Plugin<\/a><\/li>\n\n\n\n<li><a href=\"#components-directory\">Components Directory<\/a><\/li>\n\n\n\n<li><a href=\"#search-bar-jsx-file\">Search Bar Component<\/a><\/li>\n\n\n\n<li><a href=\"#layout-heading-navbar\">Layout, Heading, and NavBar<\/a><\/li>\n\n\n\n<li><a href=\"#routejs-file\">The route.js File<\/a><\/li>\n\n\n\n<li><a href=\"#test-search-functionality\">Test The Search Functionality<\/a><\/li>\n\n\n\n<li><a href=\"#conclusion\">Conclusion<\/a><\/li>\n<\/ul>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequisites\"><strong>Prerequisites<\/strong><\/h2>\n\n\n\n<p>Before reading this article, you should have the following prerequisites checked off:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Basic knowledge of <a href=\"https:\/\/nextjs.org\/docs\">Next.js 15 App Router<\/a><\/li>\n\n\n\n<li>Next.js boilerplate project that uses the App Router<\/li>\n\n\n\n<li>A WordPress install on <a href=\"https:\/\/wpengine.com\/\">WP Engine <\/a><a href=\"https:\/\/wpengine.com\/smart-search\/\">with Smart Search activated<\/a><\/li>\n\n\n\n<li>A basic knowledge of <a href=\"https:\/\/github.com\/wp-graphql\/wp-graphql\">WPGraphQL<\/a> and headless WordPress<\/li>\n\n\n\n<li>A basic knowledge of <a href=\"https:\/\/mdxjs.com\/\">MDX<\/a> and <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/MDN\/Writing_guidelines\/Howto\/Markdown_in_MDN\">Markdown<\/a>&nbsp;<\/li>\n<\/ul>\n\n\n\n<p>In order to follow along step by step, you need the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A Next.js project that is connected with your WordPress backend with a basic home page and layout.<\/li>\n\n\n\n<li>A dynamic route that grabs a single post by its URI to display a single post detail page in your Next.js frontend<\/li>\n<\/ul>\n\n\n\n<p>If you do not have that yet and do want to follow along step by step, you can clone down my demo here:&nbsp;<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/Fran-A-Dev\/smart-search-with-app-router\">https:\/\/github.com\/Fran-A-Dev\/smart-search-with-app-router<\/a><\/p>\n\n\n\n<p>If you need a WordPress install, you can use a free sandbox account on WP Engine\u2019s Headless Platform:<\/p>\n\n\n\n<div class=\"wp-block-group alignfull wpe-footer-cta has-base-color has-custom-cta-gradient-background has-text-color has-background has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\" style=\"padding-top:100px;padding-bottom:100px\">\n<h2 class=\"wp-block-heading has-text-align-center has-max-48-font-size\" style=\"line-height:1.3\">WP Engine Headless Platform<\/h2>\n\n\n\n<p class=\"has-text-align-center has-large-font-size\" style=\"line-height:1.3\">Build, deploy, and manage headless websites with the power of WordPress and the best of modern web development.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-content-justification-center is-layout-flex wp-container-core-buttons-is-layout-48bc80de wp-block-buttons-is-layout-flex\" style=\"margin-top:40px\">\n<div class=\"wp-block-button is-style-fill-base\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/wpengine.com\/headless-wordpress\/\">Try Headless for Free<\/a><\/div>\n<\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"wpengine-smartsearch\">WP Engine Smart Search<\/h2>\n\n\n\n<p>WP Engine Smart Search is an Add-on for WP Engine customers that improves search for headless and traditional WordPress applications. It is designed to improve search result relevancy, support advanced search query operators, and add support for advanced WordPress data types.<\/p>\n\n\n\n<p>We will use its public API for this article.<\/p>\n\n\n\n<p>Smart Search search is included in paid plans with WP Engine accounts.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"steps\">Steps<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"install-and-activate\"><strong>Install and activate Smart Search<\/strong><\/h2>\n\n\n\n<p>Steps to install and activate the Smart Search plugin:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Go to your WP Engine User Portal and <a href=\"https:\/\/wpengine.com\/support\/wp-engine-smart-search#Enable\">follow the steps to enable it here<\/a>.<\/li>\n\n\n\n<li>Navigate to your WP Admin<\/li>\n<\/ul>\n\n\n\n<p>You should have this:<\/p>\n\n\n\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXcu4syltRzSEEPg-g1kwmn3APG5C-aHaMe0kk-iiQcw2FdEbw4rGehbXe7mhcc3VtoK2KgPJbtE9dfKfA18bAdf8ox1ybMPRPB2I1aCCAheYLtpqP2leeiNCWq18gfCf0IPFjc1jA?key=3m-9LI1ZF8LoJcX0sYilKkoD\" width=\"187\" height=\"345\"><\/p>\n\n\n\n<p><br>Now that you have Smart Search activated in your WordPress backend, navigate to <code>WP Engine Smart Search &gt; Index data<\/code> and you should see this page:<\/p>\n\n\n\n<p><img loading=\"lazy\" decoding=\"async\" width=\"709\" height=\"378\" src=\"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXdO-xZlYH--M8O4B7tDUR8Z9VvKoiyZ6lGieoVTd7DOMCTRk072y1yWahHe1r9zV7bHnXi5l4ozVr6C4haxEVwlJWHFST1ECI5KnV3AXIC0J9xz3DpDJkzJbyYmZjTirU--5kObBw?key=3m-9LI1ZF8LoJcX0sYilKkoD\"><\/p>\n\n\n\n<p>A content sync sends all pre-existing content on the WordPress site to WP Engine Smart Search for indexing. This ensures the plugin knows exactly what content exists so it can serve the best results to site users.&nbsp; Click on the <em>\u201cIndex Now\u201d <\/em>button and this will Index your content. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"configure-smartsearch\">Configure Smart Search<\/h3>\n\n\n\n<p>Now that we have Smart Search installed, let\u2019s go ahead and configure it.&nbsp; For this article, we will use the default settings.&nbsp; In the WP admin sidebar, click on <code>WP Engine Smart Search &gt; Configuration<\/code>. You will see this page:<\/p>\n\n\n\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXe-P1h5NecUmnb57TR1gv0-xfdnZcp7s0W9mPOwXquq2U5GeItUYW4xaL3sevVHHSaaVVDezcQUlVAou3eKraLVsYC1laPwkx0QIkrgFmO73b90uzAi3CG2atiIuml2eOicO0yOwQ?key=3m-9LI1ZF8LoJcX0sYilKkoD\" width=\"711\" height=\"399\"><\/p>\n\n\n\n<p>Smart Search will default to <em>\u201cFull text\u201d <\/em>and <em>\u201cStemming\u201d<\/em> for the search config.&nbsp; We will stick to these for this guide, but feel free to try the other options on your own.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"smart-search-env-variables\">Smart Search Environment Variables<\/h3>\n\n\n\n<p>Everything is installed and configured.&nbsp; We need to grab the environment variables we are going to use to communicate with Smart Search on our frontend.&nbsp; Navigate to <em>Settings<\/em> in the Smart Search menu option and it will take you to this page:<\/p>\n\n\n\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXeTCB4pRXmzzW8A-kS7YqBzTo-DKHsU6qPftw57GzZeKkQr4OBsr6DR_G4ajsUnz4h5Jn_fIlqWJGMb0rcMjezF5JXuRJOfqkglqO1OxXKkPQmDpFy9IrAeuOtrJRDhwYwUnxm82A?key=3m-9LI1ZF8LoJcX0sYilKkoD\" width=\"709\" height=\"425\"><\/p>\n\n\n\n<p>The <strong>Access Token<\/strong> and <strong>URL<\/strong> in WP Engine\u2019s<strong> Smart Search<\/strong> plugin are essential credentials that enable communication between your WordPress site and the Smart Search service.&nbsp;<\/p>\n\n\n\n<p class=\"has-inter-font-family\">The <strong>URL<\/strong> points to the Smart Search API endpoint, which ships with a GraphQL interface. This interface allows you to query and manage indexed data with GraphQL.&nbsp;<\/p>\n\n\n\n<p>The <strong>Access Token<\/strong> is used for secure authentication to ensure that only authorized requests interact with the search backend.<\/p>\n\n\n\n<p>Save these two variables since we will need them in the next section.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"configure-nextjs-app-router\"><strong>Configure Next.js and App Router<\/strong><\/h2>\n\n\n\n<p>If you followed along in the prerequisites section of this article, you should already have a Next.js frontend spun up with a dynamic route that displays a single post detail page from WordPress via its URI.&nbsp;&nbsp;<br><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"installing-dependencies\">Installing Dependencies<\/h3>\n\n\n\n<p>The first thing we will need to do is import the necessary package dependencies.&nbsp; In your terminal, run the following command:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-selector-tag\">npm<\/span> <span class=\"hljs-selector-tag\">i<\/span> <span class=\"hljs-selector-tag\">downshift<\/span> <span class=\"hljs-selector-tag\">lodash<\/span><span class=\"hljs-selector-class\">.debounce<\/span> <span class=\"hljs-selector-tag\">json5<\/span> <span class=\"hljs-keyword\">@next<\/span>\/mdx shikiji-transformers next-secure-headers rehype-mdx-import-media rehype-pretty-code rehype-slug http-status-codes\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The packages we installed do the following:<\/p>\n\n\n\n<p><strong>Packages<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>downshift<\/code>: Provides functionality for building autocomplete or dropdown experiences.<\/li>\n\n\n\n<li><code>lodash.debounce<\/code>: A utility for debouncing functions.<\/li>\n\n\n\n<li><code>@next\/mdx<\/code>: Allows you to use .mdx files in a Next.js project.<\/li>\n\n\n\n<li><code>shikiji-transformers<\/code>: A collection of common transformers for <a href=\"https:\/\/github.com\/antfu\/shikiji\">shikiji<\/a>, which we will use for adding syntax highlighting to the code blocks on our documentation pages.<\/li>\n\n\n\n<li><code>next-secure-headers<\/code>: Helps secure Next.js apps by adding headers.<\/li>\n\n\n\n<li><code>rehype-pretty-code<\/code>: Formats and highlights code blocks in markdown or MDX.<\/li>\n\n\n\n<li><code>rehype-slug<\/code>: Adds IDs to headings in markdown or MDX for easier linking.<\/li>\n\n\n\n<li><code>http-status-codes<\/code>: \u00a0Constants enumerating the HTTP status codes. Based on the\u00a0<a href=\"http:\/\/hc.apache.org\/httpclient-3.x\/apidocs\/org\/apache\/commons\/httpclient\/HttpStatus.html\">Java Apache HttpStatus API<\/a><\/li>\n<\/ul>\n\n\n\n<p>These packages will allow us to have our MDX files with nice, clean syntax highlighting and code blocks. It will also optimize our search UI experience.&nbsp;&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"env-var\">Environment Variables<\/h3>\n\n\n\n<p>The following step is to create a <code>.env.local<\/code> folder at the root of the project so that we can add the environment variables necessary to our project.<\/p>\n\n\n\n<p>Once that is created, add these keys and values to it:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>NEXT_PUBLIC_GRAPHQL_ENDPOINT=https:<span class=\"hljs-comment\">\/\/your-wordpress-site.conm g<\/span>\n<\/span><\/span><span class='shcb-loc'><span> \/graphql\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>NEXT_PUBLIC_WORDPRESS_HOSTNAME=https:<span class=\"hljs-comment\">\/\/your-wordpress-site.com<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>NEXT_PUBLIC_SEARCH_ENDPOINT=https:<span class=\"hljs-comment\">\/\/your-smartsearch-endpoint.uc.a.run.app\/graphql<\/span>\n<\/span><\/span><span class='shcb-loc'><span>NEXT_SEARCH_ACCESS_TOKEN=your-access-token\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The access token and endpoints will allow our app to access our smart search and WordPress API&#8217;s.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"next-config\">The <code>next.config.mjs<\/code> file<\/h3>\n\n\n\n<p>We have our packages dependencies installed. Now, let\u2019s setup our Next.js project to support MDX and Webpack for our search plugin.&nbsp;&nbsp;<\/p>\n\n\n\n<p>In the root of the project, you will find the <code>next.config.mjs<\/code> file and it should look like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ next.config.mjs<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { env } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"node:process\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> createMDX <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@next\/mdx\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { transformerNotationDiff } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@shikijs\/transformers\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { createSecureHeaders } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next-secure-headers\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> rehypeMdxImportMedia <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"rehype-mdx-import-media\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { rehypePrettyCode } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"rehype-pretty-code\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> rehypeSlug <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"rehype-slug\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> smartSearchPlugin <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/lib\/smart-search-plugin.mjs\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/**<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\"> * @type {import('next').NextConfig}<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\"> *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> nextConfig = {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">trailingSlash<\/span>: <span class=\"hljs-literal\">true<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">reactStrictMode<\/span>: <span class=\"hljs-literal\">true<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">pageExtensions<\/span>: &#91;<span class=\"hljs-string\">\"js\"<\/span>, <span class=\"hljs-string\">\"jsx\"<\/span>, <span class=\"hljs-string\">\"mdx\"<\/span>, <span class=\"hljs-string\">\"ts\"<\/span>, <span class=\"hljs-string\">\"tsx\"<\/span>],\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">sassOptions<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">includePaths<\/span>: &#91;<span class=\"hljs-string\">\"node_modules\"<\/span>],\n<\/span><\/span><span class='shcb-loc'><span>  },\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">eslint<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">ignoreDuringBuilds<\/span>: <span class=\"hljs-literal\">true<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>  },\n<\/span><\/span><span class='shcb-loc'><span>  redirects() {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> &#91;\n<\/span><\/span><span class='shcb-loc'><span>      {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">source<\/span>: <span class=\"hljs-string\">\"\/discord\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">destination<\/span>: <span class=\"hljs-string\">\"https:\/\/discord.gg\/headless-wordpress-836253505944813629\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">permanent<\/span>: <span class=\"hljs-literal\">false<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      },\n<\/span><\/span><span class='shcb-loc'><span>    ];\n<\/span><\/span><span class='shcb-loc'><span>  },\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">images<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">remotePatterns<\/span>: &#91;\n<\/span><\/span><span class='shcb-loc'><span>      {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">protocol<\/span>: <span class=\"hljs-string\">\"https\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">hostname<\/span>: process.env.NEXT_PUBLIC_WORDPRESS_HOSTNAME, <span class=\"hljs-comment\">\/\/ Updated hostname<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">pathname<\/span>: <span class=\"hljs-string\">\"\/**\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      },\n<\/span><\/span><span class='shcb-loc'><span>    ],\n<\/span><\/span><span class='shcb-loc'><span>  },\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">i18n<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">locales<\/span>: &#91;<span class=\"hljs-string\">\"en\"<\/span>],\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">defaultLocale<\/span>: <span class=\"hljs-string\">\"en\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>  },\n<\/span><\/span><span class='shcb-loc'><span> \n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">webpack<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">config, { isServer }<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (isServer) {\n<\/span><\/span><span class='shcb-loc'><span>      config.plugins.push(\n<\/span><\/span><span class='shcb-loc'><span>        smartSearchPlugin({\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">endpoint<\/span>: env.NEXT_PUBLIC_SEARCH_ENDPOINT,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">accessToken<\/span>: env.NEXT_SEARCH_ACCESS_TOKEN,\n<\/span><\/span><span class='shcb-loc'><span>        })\n<\/span><\/span><span class='shcb-loc'><span>      );\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> config;\n<\/span><\/span><span class='shcb-loc'><span>  },\n<\/span><\/span><span class='shcb-loc'><span>};\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> withMDX = createMDX({\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">options<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ Add any remark plugins if needed<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">rehypePlugins<\/span>: &#91;\n<\/span><\/span><span class='shcb-loc'><span>    rehypeSlug,\n<\/span><\/span><span class='shcb-loc'><span>      &#91;\n<\/span><\/span><span class='shcb-loc'><span>        rehypePrettyCode,\n<\/span><\/span><span class='shcb-loc'><span>        {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">transformers<\/span>: &#91;transformerNotationDiff()],\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">theme<\/span>: <span class=\"hljs-string\">\"github-dark-dimmed\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">defaultLang<\/span>: <span class=\"hljs-string\">\"plaintext\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">bypassInlineCode<\/span>: <span class=\"hljs-literal\">false<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        },\n<\/span><\/span><span class='shcb-loc'><span>      ],\n<\/span><\/span><span class='shcb-loc'><span>    ],\n<\/span><\/span><span class='shcb-loc'><span>  },\n<\/span><\/span><span class='shcb-loc'><span>});\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> withMDX(nextConfig);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In this config, we have a few things set up to support <code>.mdx<\/code> files acting as pages, routes, or imports.<\/p>\n\n\n\n<p>We import all the necessary dependencies at the top to make our Next.js app run and support what we need.<\/p>\n\n\n\n<p>Toward the bottom of the file, we add a custom Webpack plugin to run during the server build process only.&nbsp; The plugin is configured using environment variables for the search endpoint and access token:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>webpack: <span class=\"hljs-function\">(<span class=\"hljs-params\">config, { isServer }<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (isServer) {\n<\/span><\/span><span class='shcb-loc'><span>      config.plugins.push(\n<\/span><\/span><span class='shcb-loc'><span>        smartSearchPlugin({\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">endpoint<\/span>: env.NEXT_PUBLIC_SEARCH_ENDPOINT,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">accessToken<\/span>: env.NEXT_SEARCH_ACCESS_TOKEN,\n<\/span><\/span><span class='shcb-loc'><span>        })\n<\/span><\/span><span class='shcb-loc'><span>      );\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> config;\n<\/span><\/span><span class='shcb-loc'><span>  },\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" id=\"mdx-files\">Create MDX Files And Paths<\/h3>\n\n\n\n<p>The next thing we need to do is create our MDX files which will represent our docs pages and routes.&nbsp; In the app folder, create another folder with subfolders that look like this:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>app\/docs\/hello-world\n<\/span><\/span><span class='shcb-loc'><span>app\/docs\/example\n<\/span><\/span><\/code><\/span><\/pre>\n\n\n<p>Then at the last level of the final subfolder, you will add a <code>page.mdx<\/code> file which is what will be rendered on the browser.\u00a0 So the tree should look like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"525\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/terminal_directory_tree-1024x525.png\" alt=\"\" class=\"wp-image-31751\" style=\"width:640px;height:auto\" srcset=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/terminal_directory_tree-1024x525.png 1024w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/terminal_directory_tree-300x154.png 300w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/terminal_directory_tree-768x394.png 768w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/terminal_directory_tree-1536x788.png 1536w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/terminal_directory_tree.png 1919w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>You can name these paths anything you like.\u00a0 <br>Now, in your <code>page.mdx<\/code> file is where you can add your MDX content:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>export <span class=\"hljs-keyword\">const<\/span> metadata = {\n<\/span><\/span><span class='shcb-loc'><span>  title: <span class=\"hljs-string\">\"Test Page\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>  date: <span class=\"hljs-string\">\"2023-10-30\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>};\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\"># Test Page<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>This is a test MDX file <span class=\"hljs-keyword\">for<\/span> verifying the search indexing process.\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>- Point one\n<\/span><\/span><span class='shcb-loc'><span>- Point two\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>Here is some code:\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>```js\n<\/span><\/span><span class='shcb-loc'><span>console.log(<span class=\"hljs-string\">\"Hello, world!\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>```\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">## Test Point 2<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">### Test Point 3<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">#### Test Point 4<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Most of the content in this file is regular Markdown, but because we are using MDX, you will notice at the top of the file that we have an export syntax to define our metadata directly in the file instead of in the front matter. This allows MDX to be processed as JS, which adds flexibility to using variables or functions in metadata.<\/p>\n\n\n\n<p>In a framework like Next.js, MDX is treated as a module and metadata can be consumed dynamically within React components or by the framework\u2019s data fetching mechanisms.&nbsp;<\/p>\n\n\n\n<p>Feel free to add as many MDX files as you like. I only added two in this article.\u00a0<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"global-mdx-components\">Global MDX Components<\/h2>\n\n\n\n<p>In order for the <code>@next\/mdx<\/code> package to work and have mdx rendered properly with our configuration in Next.js, it is necessary to have a <code>mdx-components.jsx(or .tsx).<\/code><\/p>\n\n\n\n<p>This file will help us customize our component mapping for MDX files, replacing standard HTML tags with React components.\u00a0 In the root of our Next.js app, create a <code>mdx-components.js<\/code> file and copy and paste this code block in:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> DocsLayout <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/components\/docs-layout\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> Heading <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@\/components\/heading\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> Link <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/link\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">useMDXComponents<\/span>(<span class=\"hljs-params\">components<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> {\n<\/span><\/span><span class='shcb-loc'><span> \n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">a<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">props<\/span>) =&gt;<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> {<span class=\"hljs-attr\">...props<\/span>} \/&gt;<\/span><\/span>,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">wrapper<\/span>: DocsLayout,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">h1<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">props<\/span>) =&gt;<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Heading<\/span> <span class=\"hljs-attr\">level<\/span>=<span class=\"hljs-string\">{1}<\/span> {<span class=\"hljs-attr\">...props<\/span>} \/&gt;<\/span><\/span>,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">h2<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">props<\/span>) =&gt;<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Heading<\/span> <span class=\"hljs-attr\">level<\/span>=<span class=\"hljs-string\">{2}<\/span> {<span class=\"hljs-attr\">...props<\/span>} \/&gt;<\/span><\/span>,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">h3<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">props<\/span>) =&gt;<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Heading<\/span> <span class=\"hljs-attr\">level<\/span>=<span class=\"hljs-string\">{3}<\/span> {<span class=\"hljs-attr\">...props<\/span>} \/&gt;<\/span><\/span>,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">h4<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">props<\/span>) =&gt;<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Heading<\/span> <span class=\"hljs-attr\">level<\/span>=<span class=\"hljs-string\">{4}<\/span> {<span class=\"hljs-attr\">...props<\/span>} \/&gt;<\/span><\/span>,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">h5<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">props<\/span>) =&gt;<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Heading<\/span> <span class=\"hljs-attr\">level<\/span>=<span class=\"hljs-string\">{5}<\/span> {<span class=\"hljs-attr\">...props<\/span>} \/&gt;<\/span><\/span>,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">h6<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">props<\/span>) =&gt;<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Heading<\/span> <span class=\"hljs-attr\">level<\/span>=<span class=\"hljs-string\">{6}<\/span> {<span class=\"hljs-attr\">...props<\/span>} \/&gt;<\/span><\/span>,\n<\/span><\/span><span class='shcb-loc'><span>    ...components,\n<\/span><\/span><span class='shcb-loc'><span>  };\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Let&#8217;s move on to the search part now.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"smart-search-plugin\">Smart Search Plugin<\/h2>\n\n\n\n<p>To integrate smart search into our Next.js frontend, we need to create a plugin. This plugin will allow us to automate the process of collecting, cleaning, and indexing MDX files from the app\/docs directory into our search engine.\u00a0\u00a0In the root of the project, create a folder called <code>lib<\/code>.\u00a0 In this folder, create a file called <code>smart-search-plugin.mjs<\/code>.\u00a0 In this file, copy and paste this entire code block:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { hash } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"node:crypto\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> fs <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"node:fs\/promises\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> path <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"node:path\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { cwd } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"node:process\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { htmlToText } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"html-to-text\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> queryDocuments = <span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">query FindIndexedMdxDocs($query: String!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  find(query: $query) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    documents {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      id<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">}<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> deleteMutation = <span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">mutation DeleteDocument($id: ID!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  delete(id: $id) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    code<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    message<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    success<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">}<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> bulkIndexQuery = <span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">mutation BulkIndex($input: BulkIndexInput!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  bulkIndex(input: $input) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    code<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    documents {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      id<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">}<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">`<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">let<\/span> isPluginExecuted = <span class=\"hljs-literal\">false<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">smartSearchPlugin<\/span>(<span class=\"hljs-params\">{ endpoint, accessToken }<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">apply<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">compiler<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      compiler.hooks.done.tapPromise(<span class=\"hljs-string\">\"SmartSearchPlugin\"<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">if<\/span> (isPluginExecuted) <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>        isPluginExecuted = <span class=\"hljs-literal\">true<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">if<\/span> (compiler.options.mode !== <span class=\"hljs-string\">\"production\"<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Skipping indexing in non-production mode.\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-keyword\">const<\/span> pages = <span class=\"hljs-keyword\">await<\/span> collectPages(path.join(cwd(), <span class=\"hljs-string\">\"app\/docs\"<\/span>));\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Docs Pages collected for indexing:\"<\/span>, pages.length);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-keyword\">await<\/span> deleteOldDocs({ endpoint, accessToken }, pages);\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-keyword\">await<\/span> sendPagesToEndpoint({ endpoint, accessToken }, pages);\n<\/span><\/span><span class='shcb-loc'><span>        } <span class=\"hljs-keyword\">catch<\/span> (error) {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error in smartSearchPlugin:\"<\/span>, error);\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>      });\n<\/span><\/span><span class='shcb-loc'><span>    },\n<\/span><\/span><span class='shcb-loc'><span>  };\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">collectPages<\/span>(<span class=\"hljs-params\">directory<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> pages = &#91;];\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> entries = <span class=\"hljs-keyword\">await<\/span> fs.readdir(directory, { <span class=\"hljs-attr\">withFileTypes<\/span>: <span class=\"hljs-literal\">true<\/span> });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> entry <span class=\"hljs-keyword\">of<\/span> entries) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> entryPath = path.join(directory, entry.name);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (entry.isDirectory()) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> subPages = <span class=\"hljs-keyword\">await<\/span> collectPages(entryPath);\n<\/span><\/span><span class='shcb-loc'><span>      pages.push(...subPages);\n<\/span><\/span><span class='shcb-loc'><span>    } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (entry.isFile() &amp;&amp; entry.name.endsWith(<span class=\"hljs-string\">\".mdx\"<\/span>)) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> content = <span class=\"hljs-keyword\">await<\/span> fs.readFile(entryPath, <span class=\"hljs-string\">\"utf8\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> metadataMatch = content.match(\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-regexp\">\/export\\s+const\\s+metadata\\s*=\\s*(?&lt;metadata&gt;{&#91;\\S\\s]*?});\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      );\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">if<\/span> (!metadataMatch?.groups?.metadata) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">`No metadata found in <span class=\"hljs-subst\">${entryPath}<\/span>. Skipping.`<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">continue<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">let<\/span> metadata = {};\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/\/ eslint-disable-next-line no-eval<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        metadata = <span class=\"hljs-built_in\">eval<\/span>(<span class=\"hljs-string\">`(<span class=\"hljs-subst\">${metadataMatch.groups.metadata}<\/span>)`<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>      } <span class=\"hljs-keyword\">catch<\/span> (error) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error parsing metadata:\"<\/span>, error);\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">continue<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">if<\/span> (!metadata.title) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">`No title found in metadata of <span class=\"hljs-subst\">${entryPath}<\/span>. Skipping.`<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">continue<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> textContent = htmlToText(content);\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> cleanedPath = cleanPath(entryPath);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> id = hash(<span class=\"hljs-string\">\"sha-1\"<\/span>, <span class=\"hljs-string\">`mdx:<span class=\"hljs-subst\">${cleanedPath}<\/span>`<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      pages.push({\n<\/span><\/span><span class='shcb-loc'><span>        id,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">data<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">title<\/span>: metadata.title,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">content<\/span>: textContent,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">path<\/span>: cleanedPath,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">content_type<\/span>: <span class=\"hljs-string\">\"mdx_doc\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        },\n<\/span><\/span><span class='shcb-loc'><span>      });\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> pages;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">cleanPath<\/span>(<span class=\"hljs-params\">filePath<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> relativePath = path.relative(cwd(), filePath);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-string\">\"\/\"<\/span> +\n<\/span><\/span><span class='shcb-loc'><span>    relativePath\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/^src\\\/pages\\\/\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/^pages\\\/\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/^app\\\/\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/\\\/index\\.mdx$\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/\\.mdx$\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-comment\">\/\/ Remove trailing \"\/page\" segment if it appears<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/\\\/page$\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>  );\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">deleteOldDocs<\/span>(<span class=\"hljs-params\">{ endpoint, accessToken }, pages<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> currentMdxDocuments = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Set<\/span>(pages.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">page<\/span>) =&gt;<\/span> page.id));\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> variablesForQuery = { <span class=\"hljs-attr\">query<\/span>: <span class=\"hljs-string\">'content_type:\"mdx_doc\"'<\/span> };\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(endpoint, {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">headers<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">Authorization<\/span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${accessToken}<\/span>`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      },\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">query<\/span>: queryDocuments,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">variables<\/span>: variablesForQuery,\n<\/span><\/span><span class='shcb-loc'><span>      }),\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> response.json();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (result.errors) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error fetching existing documents:\"<\/span>, result.errors);\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> existingIndexedDocuments = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Set<\/span>(\n<\/span><\/span><span class='shcb-loc'><span>      result.data.find.documents.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">doc<\/span>) =&gt;<\/span> doc.id)\n<\/span><\/span><span class='shcb-loc'><span>    );\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> documentsToDelete = &#91;...existingIndexedDocuments].filter(\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-function\">(<span class=\"hljs-params\">id<\/span>) =&gt;<\/span> !currentMdxDocuments.has(id)\n<\/span><\/span><span class='shcb-loc'><span>    );\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (documentsToDelete.length === <span class=\"hljs-number\">0<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"No documents to delete.\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> docId <span class=\"hljs-keyword\">of<\/span> documentsToDelete) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> variablesForDelete = { <span class=\"hljs-attr\">id<\/span>: docId };\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">const<\/span> deleteResponse = <span class=\"hljs-keyword\">await<\/span> fetch(endpoint, {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">headers<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-attr\">Authorization<\/span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${accessToken}<\/span>`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>          },\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-attr\">query<\/span>: deleteMutation,\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-attr\">variables<\/span>: variablesForDelete,\n<\/span><\/span><span class='shcb-loc'><span>          }),\n<\/span><\/span><span class='shcb-loc'><span>        });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">const<\/span> deleteResult = <span class=\"hljs-keyword\">await<\/span> deleteResponse.json();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">if<\/span> (deleteResult.errors) {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">`Error deleting document ID <span class=\"hljs-subst\">${docId}<\/span>:`<\/span>, deleteResult.errors);\n<\/span><\/span><span class='shcb-loc'><span>        } <span class=\"hljs-keyword\">else<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`Deleted document ID <span class=\"hljs-subst\">${docId}<\/span>:`<\/span>, deleteResult.data.delete);\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>      } <span class=\"hljs-keyword\">catch<\/span> (error) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">`Network error deleting document ID <span class=\"hljs-subst\">${docId}<\/span>:`<\/span>, error);\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  } <span class=\"hljs-keyword\">catch<\/span> (error) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error during deletion process:\"<\/span>, error);\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">sendPagesToEndpoint<\/span>(<span class=\"hljs-params\">{ endpoint, accessToken }, pages<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (pages.length === <span class=\"hljs-number\">0<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">\"No documents found for indexing.\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> documents = pages.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">page<\/span>) =&gt;<\/span> ({\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">id<\/span>: page.id,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">data<\/span>: page.data,\n<\/span><\/span><span class='shcb-loc'><span>  }));\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> variables = { <span class=\"hljs-attr\">input<\/span>: { documents } };\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(endpoint, {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">headers<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">Authorization<\/span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${accessToken}<\/span>`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      },\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ <span class=\"hljs-attr\">query<\/span>: bulkIndexQuery, variables }),\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (!response.ok) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.error(\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">`Error during bulk indexing: <span class=\"hljs-subst\">${response.status}<\/span> <span class=\"hljs-subst\">${response.statusText}<\/span>`<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      );\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> response.json();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (result.errors) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"GraphQL bulk indexing error:\"<\/span>, result.errors);\n<\/span><\/span><span class='shcb-loc'><span>    } <span class=\"hljs-keyword\">else<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`Indexed <span class=\"hljs-subst\">${documents.length}<\/span> documents successfully.`<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  } <span class=\"hljs-keyword\">catch<\/span> (error) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error during bulk indexing:\"<\/span>, error);\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> smartSearchPlugin;\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This is a lot of code so let\u2019s break this down into small chunks.<\/p>\n\n\n\n<p>At the top of the file, we have our imports:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { hash } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"node:crypto\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> fs <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"node:fs\/promises\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> path <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"node:path\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { cwd } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"node:process\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { htmlToText } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"html-to-text\"<\/span>;\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>These imports are responsible for the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>hash<\/code> This is Node\u2019s built-in <code>crypto<\/code> module. We\u2019ll use this to generate unique identifiers for our documents, ensuring that each piece of content can be tracked as it\u2019s indexed.<\/li>\n\n\n\n<li><code>fs\/promises<\/code> This module lets us work with the file system using async\/await. We\u2019ll read directories and MDX files and collect their content asynchronously.<\/li>\n\n\n\n<li><code>path<\/code> \u00a0Used for handling and transforming file paths<\/li>\n\n\n\n<li><code>cwd<\/code> Retrieves the current working directory of the Node.js process<\/li>\n\n\n\n<li><code>html-to-text<\/code> This allows us to convert raw HTML (or MDX content that often renders to HTML) into plain text. <\/li>\n<\/ul>\n\n\n\n<p>Then, We define a GraphQL query to retrieve already indexed documents that match a specific query\u2014in this case, all documents tagged as <code>mdx_doc<\/code>. This is part of the cleanup process where we compare what\u2019s currently indexed with what we are about to index:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> queryDocuments = `\n<\/span><\/span><span class='shcb-loc'><span>query FindIndexedMdxDocs($query: String!) {\n<\/span><\/span><span class='shcb-loc'><span>  find(query: $query) {\n<\/span><\/span><span class='shcb-loc'><span>    documents {\n<\/span><\/span><span class='shcb-loc'><span>      id\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>`;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Following that, we have a GraphQL mutation for deleting a document by its <code>id<\/code>. We\u2019ll use this to remove outdated content from the index, ensuring the search results stay accurate:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> deleteMutation = `\n<\/span><\/span><span class='shcb-loc'><span>mutation DeleteDocument($id: ID!) {\n<\/span><\/span><span class='shcb-loc'><span>  delete(id: $id) {\n<\/span><\/span><span class='shcb-loc'><span>    code\n<\/span><\/span><span class='shcb-loc'><span>    message\n<\/span><\/span><span class='shcb-loc'><span>    success\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>`;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Next, we define a GraphQL mutation for bulk indexing.  After collecting all MDX pages, we send them to this mutation in a single request, making the indexing process efficient and seamless.<\/p>\n\n\n\n<p>This line here is a flag that ensures that our indexing logic runs only once per build.  Without it, we might accidentally trigger multiple indexing operations, leading to redundant work and inconsistent states.<\/p>\n\n\n\n<p><code>let isPluginExecuted = false<\/code><\/p>\n\n\n\n<p>After that, we have our main plugin function:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">smartSearchPlugin<\/span>(<span class=\"hljs-params\">{ endpoint, accessToken }<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">apply<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">compiler<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      compiler.hooks.done.tapPromise(<span class=\"hljs-string\">\"SmartSearchPlugin\"<\/span>, <span class=\"hljs-keyword\">async<\/span> () =&gt; {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">if<\/span> (isPluginExecuted) <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>        isPluginExecuted = <span class=\"hljs-literal\">true<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">if<\/span> (compiler.options.mode !== <span class=\"hljs-string\">\"production\"<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Skipping indexing in non-production mode.\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-keyword\">const<\/span> pages = <span class=\"hljs-keyword\">await<\/span> collectPages(path.join(cwd(), <span class=\"hljs-string\">\"app\/docs\"<\/span>));\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Docs Pages collected for indexing:\"<\/span>, pages.length);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-keyword\">await<\/span> deleteOldDocs({ endpoint, accessToken }, pages);\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-keyword\">await<\/span> sendPagesToEndpoint({ endpoint, accessToken }, pages);\n<\/span><\/span><span class='shcb-loc'><span>        } <span class=\"hljs-keyword\">catch<\/span> (error) {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error in smartSearchPlugin:\"<\/span>, error);\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>      });\n<\/span><\/span><span class='shcb-loc'><span>    },\n<\/span><\/span><span class='shcb-loc'><span>  };\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>It receives an endpoint from the search API and an access token for authentication.  The plugin hooks into the build process via the <code>apply<\/code> method. We use <code>compiler.hooks.done.tapPromise<\/code> to run our indexing logic once the build time finishes. This ensures our search index updates after the latest version of the site is compiled.<\/p>\n\n\n\n<p>If we&#8217;ve already run this plugin once, we skip running it again.  Setting <code>isPluginExecuted<\/code> to <code>true<\/code> ensures that even if the build pipeline triggers multiple times, the indexing won&#8217;t repeat.  Then we make sure we only index in production mode.  In dev mode, we don&#8217;t want to flood our search index with unstable or temporary content, so we bail out if we are not in production.<\/p>\n\n\n\n<p>We then call <code>collectPages()<\/code> to gather all MDX documents from <code>app\/docs<\/code>. After this call, <code>pages<\/code> is an array of all the files we want to index.  We log how many we find for transparency.  <\/p>\n\n\n\n<p>Then we remove any documents that are no longer relevant by calling <code>deleteOldDocs()<\/code>.  That is followed by adding our current pages to the search index with <code>sendPagesToEndpoint()<\/code>.<\/p>\n\n\n\n<p>If anything goes wrong in our try block, we catch the error, log it out, and do not crash the build.  This error handling gives us insight into indexing issues without breaking the build entirely.  <\/p>\n\n\n\n<p>The next thing we have is an async function that walks through a given directory, finds all MDX files, extracts their metadata, and preps them for indexing:<br><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">collectPages<\/span>(<span class=\"hljs-params\">directory<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> pages = &#91;];\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> entries = <span class=\"hljs-keyword\">await<\/span> fs.readdir(directory, { <span class=\"hljs-attr\">withFileTypes<\/span>: <span class=\"hljs-literal\">true<\/span> });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> entry <span class=\"hljs-keyword\">of<\/span> entries) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> entryPath = path.join(directory, entry.name);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (entry.isDirectory()) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> subPages = <span class=\"hljs-keyword\">await<\/span> collectPages(entryPath);\n<\/span><\/span><span class='shcb-loc'><span>      pages.push(...subPages);\n<\/span><\/span><span class='shcb-loc'><span>    } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (entry.isFile() &amp;&amp; entry.name.endsWith(<span class=\"hljs-string\">\".mdx\"<\/span>)) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> content = <span class=\"hljs-keyword\">await<\/span> fs.readFile(entryPath, <span class=\"hljs-string\">\"utf8\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> metadataMatch = content.match(\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-regexp\">\/export\\s+const\\s+metadata\\s*=\\s*(?&lt;metadata&gt;{&#91;\\S\\s]*?});\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      );\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">if<\/span> (!metadataMatch?.groups?.metadata) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">`No metadata found in <span class=\"hljs-subst\">${entryPath}<\/span>. Skipping.`<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">continue<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If we don&#8217;t find any metadata in the file, we log a warning and skip this file.  Our indexing workflow requires metadata &#8211; specifically a title &#8211; to proper,y index the content.<\/p>\n\n\n\n<p>Here, we parse the metadata using <code>eval<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">let<\/span> metadata = {};\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>        metadata = <span class=\"hljs-built_in\">eval<\/span>(<span class=\"hljs-string\">`(<span class=\"hljs-subst\">${metadataMatch.groups.metadata}<\/span>)`<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>      } <span class=\"hljs-keyword\">catch<\/span> (error) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error parsing metadata:\"<\/span>, error);\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">continue<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>      \n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">if<\/span> (!metadata.title) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">`No title found in metadata of <span class=\"hljs-subst\">${entryPath}<\/span>. Skipping.`<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">continue<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If the metadata is invalid or can&#8217;t be evaluated, we log an error and skip the file.  Then we ensure that the metadata includes a title. If it&#8217;s not a valid document for indexing, we skip it.<\/p>\n\n\n\n<p>Next, we convert the MDX content to plain text for indexing.  This gives our search engine clean text without HTML tags.  Then we run <code>cleanPath()<\/code> to transform the file&#8217;s absolute path into a user-facing URL path.  It ensures that the <code>app\/docs\/my-page\/page.mdx<\/code> becomes a neat URL, like <code>\/docs\/my-page<\/code>.  After that, a unique ID for this document is generated based on the cleaned path.  Using a hash ensures that the same path always yields the same ID, which is needed for identifying when documents change or need deletion:<br><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> textContent = htmlToText(content);\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> cleanedPath = cleanPath(entryPath);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> id = hash(<span class=\"hljs-string\">\"sha-1\"<\/span>, <span class=\"hljs-string\">`mdx:<span class=\"hljs-subst\">${cleanedPath}<\/span>`<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The next lines add a new document to the <code>pages<\/code> array.  It includes the ID, metadata, raw text, and other helpful attributes that the search API needs:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>pages.push({\n<\/span><\/span><span class='shcb-loc'><span>        id,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">data<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">title<\/span>: metadata.title,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">content<\/span>: textContent,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">path<\/span>: cleanedPath,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">content_type<\/span>: <span class=\"hljs-string\">\"mdx_doc\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        },\n<\/span><\/span><span class='shcb-loc'><span>      });\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> pages;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The following function <code>cleanPath<\/code> takes a file&#8217;s absolute path, converts it to a project-relative path, and then strips out unwanted prefixes.  It also removes trailing extensions and ensures any trailing <code>\/page<\/code> segments are gone.  <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">cleanPath<\/span>(<span class=\"hljs-params\">filePath<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> relativePath = path.relative(cwd(), filePath);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-string\">\"\/\"<\/span> +\n<\/span><\/span><span class='shcb-loc'><span>    relativePath\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/^src\\\/pages\\\/\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/^pages\\\/\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/^app\\\/\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/\\\/index\\.mdx$\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/\\.mdx$\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-comment\">\/\/ Remove trailing \"\/page\" segment if it appears<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/\\\/page$\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>  );\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In this following async function, we take a snapshot of all the current document IDs we are about to index.  We then prepare a GraphQL query to find all existing <code>mdx_doc<\/code> documents that might no longer be relevant.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">deleteOldDocs<\/span>(<span class=\"hljs-params\">{ endpoint, accessToken }, pages<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> currentMdxDocuments = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Set<\/span>(pages.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">page<\/span>) =&gt;<\/span> page.id));\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> variablesForQuery = { <span class=\"hljs-attr\">query<\/span>: <span class=\"hljs-string\">'content_type:\"mdx_doc\"'<\/span> };\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Following that, we perform a network request to the search GraphQL endpoint to fetch a list of already indexed documents. After sending the request, we <code>await response.json()<\/code> for the server&#8217;s reply.  We get the data returned in JSON format which we parse into a JS object.<\/p>\n\n\n\n<p>Then we check for errors and if something is wrong, we log it.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(endpoint, {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">headers<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">Authorization<\/span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${accessToken}<\/span>`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      },\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">query<\/span>: queryDocuments,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">variables<\/span>: variablesForQuery,\n<\/span><\/span><span class='shcb-loc'><span>      }),\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> response.json();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (result.errors) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error fetching existing documents:\"<\/span>, result.errors);\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here, we take the documents returned from our earlier GraphQL query <code>(result.data.find.documents)<\/code> and map them down to their IDs. We then wrap these IDs in a JavaScript Set. A Set gives us fast lookups, helping us easily check whether a given document ID is present.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span> <span class=\"hljs-keyword\">const<\/span> existingIndexedDocuments = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Set<\/span>(\n<\/span><\/span><span class='shcb-loc'><span>      result.data.find.documents.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">doc<\/span>) =&gt;<\/span> doc.id)\n<\/span><\/span><span class='shcb-loc'><span>    );\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then we identify documents to delete here.  This filters out any <code>id<\/code> that is not in the current MDX documents and all IDs that are currently indexed but should not be anymore:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> documentsToDelete = &#91;...existingIndexedDocuments].filter(\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-function\">(<span class=\"hljs-params\">id<\/span>) =&gt;<\/span> !currentMdxDocuments.has(id)\n<\/span><\/span><span class='shcb-loc'><span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If <code>documentsToDelete<\/code> is empty, it means our search index is perfectly aligned with our current documents\u2014nothing to remove. In that case, we log a message and return early.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">if<\/span> (documentsToDelete.length === <span class=\"hljs-number\">0<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"No documents to delete.\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now we iterate over each document ID in documentsToDelete. For each, we create a <code>variablesForDelete<\/code> object that will be passed into a GraphQL mutation. This mutation will delete the document with that specific <code>id<\/code> from the search index.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span> <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> docId <span class=\"hljs-keyword\">of<\/span> documentsToDelete) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> variablesForDelete = { <span class=\"hljs-attr\">id<\/span>: docId };\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The next lines send a GraphQL mutation request to the search endpoint to remove a specific document from the index. It posts a <code>deleteMutation<\/code> query along with the necessary variables (including the document ID to delete). After the network call completes, it processes the JSON response to confirm whether the deletion was successful.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">const<\/span> deleteResponse = <span class=\"hljs-keyword\">await<\/span> fetch(endpoint, {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">headers<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-attr\">Authorization<\/span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${accessToken}<\/span>`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>          },\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-attr\">query<\/span>: deleteMutation,\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-attr\">variables<\/span>: variablesForDelete,\n<\/span><\/span><span class='shcb-loc'><span>          }),\n<\/span><\/span><span class='shcb-loc'><span>        });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">const<\/span> deleteResult = <span class=\"hljs-keyword\">await<\/span> deleteResponse.json();\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This section checks whether the deletion request was successful or not. If the GraphQL response includes any errors, it logs those errors along with the document ID that failed to delete. Otherwise, it confirms that the document was successfully removed by logging a success message. Should any network issues occur during the process, those are also caught and logged. After processing all documents set for deletion, if a broader error occurs, it\u2019s logged to help diagnose issues in the overall deletion process:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span> <span class=\"hljs-keyword\">if<\/span> (deleteResult.errors) {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-built_in\">console<\/span>.error(\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-string\">`Error deleting document ID <span class=\"hljs-subst\">${docId}<\/span>:`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>            deleteResult.errors\n<\/span><\/span><span class='shcb-loc'><span>          );\n<\/span><\/span><span class='shcb-loc'><span>        } <span class=\"hljs-keyword\">else<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-built_in\">console<\/span>.log(\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-string\">`Deleted document ID <span class=\"hljs-subst\">${docId}<\/span>:`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>            deleteResult.data.delete\n<\/span><\/span><span class='shcb-loc'><span>          );\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>      } <span class=\"hljs-keyword\">catch<\/span> (error) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">`Network error deleting document ID <span class=\"hljs-subst\">${docId}<\/span>:`<\/span>, error);\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  } <span class=\"hljs-keyword\">catch<\/span> (error) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error during deletion process:\"<\/span>, error);\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Following that, we set up the data needed to index newly collected pages. If there are no pages to index, it simply logs a warning and exits. Otherwise, it transforms the array of page objects into a format required by the GraphQL <code>bulkIndex<\/code> mutation, packaging each page\u2019s ID and associated metadata into a <code>documents<\/code> array. It then wraps that array into a <code>variables<\/code> object, preparing the payload that will be sent to the search endpoint.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">sendPagesToEndpoint<\/span>(<span class=\"hljs-params\">{ endpoint, accessToken }, pages<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (pages.length === <span class=\"hljs-number\">0<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">\"No documents found for indexing.\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> documents = pages.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">page<\/span>) =&gt;<\/span> ({\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">id<\/span>: page.id,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">data<\/span>: page.data,\n<\/span><\/span><span class='shcb-loc'><span>  }));\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> variables = { <span class=\"hljs-attr\">input<\/span>: { documents } };\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>A the end of this code block, we send the prepared list of documents to the search service using a <code>POST<\/code> request. It includes the <code>bulkIndexQuery<\/code> and the <code>variables<\/code> containing all documents that need indexing. If the server responds with a non-OK status, it logs an error and stops. Once a successful response comes in, it checks for any GraphQL-level errors and logs them. If there are no errors, it confirms that the documents have been successfully indexed. If a network or unexpected error occurs at any point, it\u2019s caught and reported, ensuring any issues are visible and can be addressed.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(endpoint, {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">headers<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">Authorization<\/span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${accessToken}<\/span>`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      },\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ <span class=\"hljs-attr\">query<\/span>: bulkIndexQuery, variables }),\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (!response.ok) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.error(\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">`Error during bulk indexing: <span class=\"hljs-subst\">${response.status}<\/span> <span class=\"hljs-subst\">${response.statusText}<\/span>`<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      );\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> response.json();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (result.errors) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"GraphQL bulk indexing error:\"<\/span>, result.errors);\n<\/span><\/span><span class='shcb-loc'><span>    } <span class=\"hljs-keyword\">else<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`Indexed <span class=\"hljs-subst\">${documents.length}<\/span> documents successfully.`<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  } <span class=\"hljs-keyword\">catch<\/span> (error) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error during bulk indexing:\"<\/span>, error);\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> smartSearchPlugin;\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\" id=\"components-directory\">Components Directory<\/h2>\n\n\n\n<p>The next thing we have to do is go over the components needed in this project.\u00a0 The components directory will be at the root of our Next.js 15 project.\u00a0<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"search-bar-jsx-file\">The <code>search-bar.jsx<\/code> file<\/h3>\n\n\n\n<p><br>The search bar in our project is a client-side search component with a keyboard-accessible modal. Since it is a large block of code, let\u2019s break down the code line by line.<\/p>\n\n\n\n<p>At the start of the file, we declare this as a client-side component with<code> \u201cuse client.\u201d\u00a0 <\/code>This tells Next.js to run this component on the client allowing the use of hooks like useState and useEffect.<\/p>\n\n\n\n<p>All our necessary imports for this component follow that.<\/p>\n\n\n\n<p>Next, we define and export our <code>SearchBar<\/code> component, which is our main component. It renders the search bar and handles all the logic.<\/p>\n\n\n\n<p>Then we have the state and variable references:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">SearchBar<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;items, setItems] = useState(&#91;]);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;inputValue, setInputValue] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> &#91;isModalOpen, setIsModalOpen] = useState(<span class=\"hljs-literal\">false<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> dialogReference = useRef(<span class=\"hljs-literal\">null<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here is what the state and variable references do:<\/p>\n\n\n\n<p><code>const [items, setItems] = useState([]);\u00a0<\/code><\/p>\n\n\n\n<p>This initializes items state as an empty array. It stores the search results fetched from the API.<\/p>\n\n\n\n<p><code>const [inputValue, setInputValue] = useState(\"\");\u00a0<\/code><\/p>\n\n\n\n<p>This initializes the input value state as an empty string and tracks the current value of the search input field.<\/p>\n\n\n\n<p><code>const [isModalOpen, setIsModalOpen] = useState(false);\u00a0<\/code><\/p>\n\n\n\n<p>This sets up the <code>isModalOpen<\/code> state as false and controls the visibility of the search modal.<\/p>\n\n\n\n<p><code>const dialogReference = useRef(null);<\/code><\/p>\n\n\n\n<p>This creates a reference to <code>dialogReference <\/code>which is set to null.\u00a0 It references the modal dialog DOM element to detect clicks outside the modal.<\/p>\n\n\n\n<p><code>const router = useRouter();<\/code><\/p>\n\n\n\n<p>This sets the Next.js router instance.&nbsp; It allows navigation to different routes programmatically.<\/p>\n\n\n\n<p>After the state and references, we have our modal control functions:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> openModal = useCallback(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    setIsModalOpen(<span class=\"hljs-literal\">true<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    setInputValue(<span class=\"hljs-string\">\"\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    setItems(&#91;]);\n<\/span><\/span><span class='shcb-loc'><span>  }, &#91;]);\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> closeModal = useCallback(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> setIsModalOpen(<span class=\"hljs-literal\">false<\/span>), &#91;]);\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This defines the <code>openModal<\/code> function calling <code>useCallback<\/code>.\u00a0 It opens the search modal and resets the input and items.\u00a0 The dependency array is empty so the function is memoized once and doesn\u2019t change across renders.<\/p>\n\n\n\n<p>Then we define the <code>closeModal<\/code> function using <code>useCallback<\/code>.\u00a0 This closes the search modal by setting<code> isModalOpen<\/code> to false.\u00a0 The array is empty here to ensure consistent reference.<\/p>\n\n\n\n<p>The next block of code is event handlers:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> handleOutsideClick = useCallback(\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-function\">(<span class=\"hljs-params\">event<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> isClickOutsideOfModal = event.target === dialogReference.current;\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (isClickOutsideOfModal) {\n<\/span><\/span><span class='shcb-loc'><span>      closeModal();\n<\/span><\/span><span class='shcb-loc'><span>      setInputValue(<span class=\"hljs-string\">\"\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>      setItems(&#91;]);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  },\n<\/span><\/span><span class='shcb-loc'><span>  &#91;closeModal]\n<\/span><\/span><span class='shcb-loc'><span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> handleKeyDown = useCallback(\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-function\">(<span class=\"hljs-params\">event<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (event.metaKey &amp;&amp; event.key === <span class=\"hljs-string\">\"k\"<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>      event.preventDefault();\n<\/span><\/span><span class='shcb-loc'><span>      openModal();\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (event.key === <span class=\"hljs-string\">\"Escape\"<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>      closeModal();\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>  },\n<\/span><\/span><span class='shcb-loc'><span>  &#91;openModal, closeModal]\n<\/span><\/span><span class='shcb-loc'><span>);\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The first event handler closes the modal when the user clicks outside of it.<\/p>\n\n\n\n<p>The next event handles keyboard shortcuts on a Mac. If command k is pressed, it prevents default behavior and opens the modal. If the escape key is pressed, it closes it.<\/p>\n\n\n\n<p>The following is an event listener:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-built_in\">document<\/span>.addEventListener(<span class=\"hljs-string\">\"keydown\"<\/span>, handleKeyDown);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> <span class=\"hljs-built_in\">document<\/span>.removeEventListener(<span class=\"hljs-string\">\"keydown\"<\/span>, handleKeyDown);\n<\/span><\/span><span class='shcb-loc'><span>}, &#91;handleKeyDown]);\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This attaches the key-down event listener to the document when the component mounts and cleans up when it unmounts.<\/p>\n\n\n\n<p>Next, we have our debounced fetch function:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span> <span class=\"hljs-keyword\">const<\/span> debouncedFetchItems = useRef(\n<\/span><\/span><span class='shcb-loc'><span>    debounce(<span class=\"hljs-keyword\">async<\/span> (value) =&gt; {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">if<\/span> (!value) {\n<\/span><\/span><span class='shcb-loc'><span>        setItems(&#91;]);\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-string\">`\/api\/search?query=<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">encodeURIComponent<\/span>(value)}<\/span>`<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        );\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">if<\/span> (!response.ok) {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-built_in\">console<\/span>.error(\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-string\">`Search API error: <span class=\"hljs-subst\">${response.status}<\/span> <span class=\"hljs-subst\">${response.statusText}<\/span>`<\/span>\n<\/span><\/span><span class='shcb-loc'><span>          );\n<\/span><\/span><span class='shcb-loc'><span>          setItems(&#91;]);\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-keyword\">return<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> response.json();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-built_in\">Array<\/span>.isArray(data)) {\n<\/span><\/span><span class='shcb-loc'><span>          setItems(data);\n<\/span><\/span><span class='shcb-loc'><span>        } <span class=\"hljs-keyword\">else<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Search API returned unexpected data:\"<\/span>, data);\n<\/span><\/span><span class='shcb-loc'><span>          setItems(&#91;]);\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>      } <span class=\"hljs-keyword\">catch<\/span> (error) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error fetching search results:\"<\/span>, error);\n<\/span><\/span><span class='shcb-loc'><span>        setItems(&#91;]);\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>    }, <span class=\"hljs-number\">500<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>  ).current;\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This creates a debounced function that fetches search results, limiting API calls to one every 500 milliseconds.\u00a0 <code>useRef<\/code> stores the debounced function so it is not recreated on every render.\u00a0 <code>debounce<\/code> wraps the async function to delay execution.\u00a0\u00a0<\/p>\n\n\n\n<p>In the body of the function, we have fetched search results from the API based on the user\u2019s input value.&nbsp; It checks if the value is empty or falsy, it will clear items and it exits.<\/p>\n\n\n\n<p>The API call fetches data from <code>api\/search<\/code>, encoding the value to handle special characters.\u00a0 If the response is not OK, it logs an error and clears items.\u00a0 The response JSON is parsed and then checks if <code>data<\/code> is an array.\u00a0 If it is, it updates the items.\u00a0 Otherwise, it logs an error and clears.<\/p>\n\n\n\n<p>The error handling then catches any network or parsing errors, logs them and clears items.<\/p>\n\n\n\n<p>Following that we have our cleanup effect:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    debouncedFetchItems.cancel();\n<\/span><\/span><span class='shcb-loc'><span>  };\n<\/span><\/span><span class='shcb-loc'><span>}, &#91;debouncedFetchItems]);\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This <code>useEffect <\/code>cleanup hook cancels any pending debounced function calls when the component unmounts. The next block of code is our downshift combobox configuration:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-33\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    isOpen,\n<\/span><\/span><span class='shcb-loc'><span>    getMenuProps,\n<\/span><\/span><span class='shcb-loc'><span>    getInputProps,\n<\/span><\/span><span class='shcb-loc'><span>    getItemProps,\n<\/span><\/span><span class='shcb-loc'><span>    highlightedIndex,\n<\/span><\/span><span class='shcb-loc'><span>    openMenu,\n<\/span><\/span><span class='shcb-loc'><span>    closeMenu,\n<\/span><\/span><span class='shcb-loc'><span>  } = useCombobox({\n<\/span><\/span><span class='shcb-loc'><span>    items,\n<\/span><\/span><span class='shcb-loc'><span>    inputValue,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">defaultHighlightedIndex<\/span>: <span class=\"hljs-number\">0<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">onInputValueChange<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">{ inputValue: newValue }<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      setInputValue(newValue);\n<\/span><\/span><span class='shcb-loc'><span>      debouncedFetchItems(newValue);\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">if<\/span> (newValue.trim() === <span class=\"hljs-string\">\"\"<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>        closeMenu();\n<\/span><\/span><span class='shcb-loc'><span>      } <span class=\"hljs-keyword\">else<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>        openMenu();\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>    },\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">onSelectedItemChange<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">{ selectedItem }<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">if<\/span> (selectedItem) {\n<\/span><\/span><span class='shcb-loc'><span>        closeModal();\n<\/span><\/span><span class='shcb-loc'><span>        router.push(selectedItem.path);\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>    },\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attr\">itemToString<\/span>: <span class=\"hljs-function\">(<span class=\"hljs-params\">item<\/span>) =&gt;<\/span> (item ? item.title : <span class=\"hljs-string\">\"\"<\/span>),\n<\/span><\/span><span class='shcb-loc'><span>  });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This configures the <code>combobox<\/code> behavior using the <code>useCombobox<\/code> hook from <a href=\"https:\/\/www.downshift-js.com\/\">Downshift<\/a>.<\/p>\n\n\n\n<p>At the start of the const, we have our destructured values:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>isOpen<\/code>: Boolean indicating if the menu is open.<\/li>\n\n\n\n<li><code>getMenuProps<\/code>: Props to spread onto the menu (&lt;ul>) element.<\/li>\n\n\n\n<li><code>getInputProps<\/code>: Props to spread onto the input element.<\/li>\n\n\n\n<li><code>getItemProps<\/code>: Props to spread onto each item (&lt;li>) element.<\/li>\n\n\n\n<li><code>highlightedIndex<\/code>: The index of the currently highlighted item.<\/li>\n\n\n\n<li><code>openMenu<\/code>: Function to open the menu.<\/li>\n\n\n\n<li><code>closeMenu<\/code>: Function to close the menu.<\/li>\n<\/ul>\n\n\n\n<p>After that, we have our config options:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>items<\/code>: The array of items to display (search results).<\/li>\n\n\n\n<li><code>inputValue<\/code>: The current value of the input field.<\/li>\n\n\n\n<li><code>defaultHighlightedIndex<\/code>: Sets the default highlighted item (index 0).<\/li>\n\n\n\n<li><code>onInputValueChange<\/code>: Called when the input value changes.<\/li>\n\n\n\n<li><code>onSelectedItemChange<\/code>: Called when an item is selected.<\/li>\n\n\n\n<li><code>itemToString<\/code>: Converts an item to a string representation for display.<\/li>\n<\/ul>\n\n\n\n<p>Then, the <code>onInputValueChange<\/code> handler updates state and manages menu visibility when the input value changes.\u00a0 If the menu is empty, it closes. Otherwise, it is left open.<\/p>\n\n\n\n<p>The <code>onSelectedItemChange<\/code> handler handles actions when an item is selected from the search results.\u00a0 If a selected item exists, it closes the modal and navigates to the selected item\u2019s path with <code>router.push<\/code>.<\/p>\n\n\n\n<p>The last thing in this code block is the <code>itemToString<\/code> function. This converts an item to a string for display purposes.\u00a0 If there is an item, it will return its title and if not, it returns an empty string.<\/p>\n\n\n\n<p>Lastly, we have our JSX return statement:<br><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-34\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\">    {\/* Search Button *\/}<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\">    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"inline-flex items-center rounded-md bg-gray-800 px-2 py-1.5 text-sm font-medium text-gray-400 hover:bg-gray-700\"<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{openModal}<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">      <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"button\"<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">    &gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\">      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"hidden md:inline\"<\/span>&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"pl-3\"<\/span>&gt;<\/span>Search docs or posts...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">kbd<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"ml-8 rounded bg-gray-700 px-2 py-1 text-gray-400\"<\/span>&gt;<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          \u2318K<\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">kbd<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    {\/* Modal *\/}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    {isModalOpen &amp;&amp; (<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-black fixed inset-0 z-50 flex items-start justify-center bg-opacity-50 backdrop-blur-sm\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{handleOutsideClick}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-attr\">onKeyDown<\/span>=<span class=\"hljs-string\">{(event)<\/span> =&gt;<\/span> {<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          if (event.key === \"Enter\" || event.key === \" \") {<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            handleOutsideClick(event);<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          }<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        }}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        role=\"button\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        tabIndex=\"0\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        ref={dialogReference}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      &gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        {\/* Modal Content *\/}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"relative mt-10 w-full max-w-4xl rounded-lg bg-gray-800 p-6 shadow-lg\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          <span class=\"hljs-attr\">role<\/span>=<span class=\"hljs-string\">\"dialog\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          <span class=\"hljs-attr\">tabIndex<\/span>=<span class=\"hljs-string\">\"-1\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        &gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          {\/* Combobox *\/}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-attr\">role<\/span>=<span class=\"hljs-string\">\"combobox\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-attr\">aria-expanded<\/span>=<span class=\"hljs-string\">{isOpen}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-attr\">aria-haspopup<\/span>=<span class=\"hljs-string\">\"listbox\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-attr\">aria-controls<\/span>=<span class=\"hljs-string\">\"search-results\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          &gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            {\/* Input Field *\/}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"relative\"<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                <span class=\"hljs-attr\">autoFocus<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                {<span class=\"hljs-attr\">...getInputProps<\/span>({<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                  <span class=\"hljs-attr\">placeholder:<\/span> \"<span class=\"hljs-attr\">What<\/span> <span class=\"hljs-attr\">are<\/span> <span class=\"hljs-attr\">you<\/span> <span class=\"hljs-attr\">searching<\/span> <span class=\"hljs-attr\">for<\/span>?\",<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                  \"<span class=\"hljs-attr\">aria-label<\/span>\"<span class=\"hljs-attr\">:<\/span> \"<span class=\"hljs-attr\">Search<\/span> <span class=\"hljs-attr\">input<\/span>\",<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                  <span class=\"hljs-attr\">className:<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                    \"<span class=\"hljs-attr\">w-full<\/span> <span class=\"hljs-attr\">pr-10<\/span> <span class=\"hljs-attr\">p-2<\/span> <span class=\"hljs-attr\">bg-gray-700<\/span> <span class=\"hljs-attr\">text-white<\/span> <span class=\"hljs-attr\">placeholder-gray-400<\/span> <span class=\"hljs-attr\">border<\/span> <span class=\"hljs-attr\">border-gray-700<\/span> <span class=\"hljs-attr\">rounded<\/span> <span class=\"hljs-attr\">focus:outline-none<\/span> <span class=\"hljs-attr\">focus:ring-2<\/span> <span class=\"hljs-attr\">focus:ring-blue-500<\/span>\",<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                })}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">              \/&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">              {\/* Close Button *\/}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"button\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"absolute right-2 top-1\/2 -translate-y-1\/2 transform text-xs text-gray-400 hover:text-white\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{closeModal}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">              &gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                Esc<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            {\/* Results List *\/}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">              {<span class=\"hljs-attr\">...getMenuProps<\/span>({<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                <span class=\"hljs-attr\">id:<\/span> \"<span class=\"hljs-attr\">search-results<\/span>\",<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">              })}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mt-2 max-h-60 overflow-y-auto\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            &gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">              {isOpen &amp;&amp;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                items &amp;&amp;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                items.length &gt; 0 &amp;&amp;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                items.map((item, index) =&gt; (<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                    <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{item.id}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                    {<span class=\"hljs-attr\">...getItemProps<\/span>({<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                      <span class=\"hljs-attr\">item<\/span>,<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                      <span class=\"hljs-attr\">index<\/span>,<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                      <span class=\"hljs-attr\">onClick:<\/span> () =&gt;<\/span> {<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                        closeModal();<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                        router.push(item.path);<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                      },<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                      onKeyDown: (event) =&gt; {<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                        if (event.key === \"Enter\") {<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                          closeModal();<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                          router.push(item.path);<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                        }<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                      },<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                    })}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                    role=\"option\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                    aria-selected={highlightedIndex === index}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                    tabIndex={0}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                    className={`flex w-full cursor-pointer items-center justify-between px-4 py-4 ${<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                      highlightedIndex === index<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                        ? \"bg-blue-600 text-white\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                        : \"bg-gray-800 text-white\"<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                    }`}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                  &gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-left\"<\/span>&gt;<\/span>{item.title}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-right text-sm text-gray-400\"<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                      {item.type === \"mdx_doc\" ? \"Doc\" : \"Blog\"}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">                ))}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            {\/* No Results Message *\/}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            {isOpen &amp;&amp; items.length === 0 &amp;&amp; (<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mt-2 text-gray-500\"<\/span>&gt;<\/span>No results found.<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">            )}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">    )}<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">  <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\"><span class=\"hljs-tag\">);<\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-34\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This JSX return statement renders a dynamic and interactive search feature. It starts with a button styled to initiate the search modal and displays a keyboard shortcut (\u2318K) for convenience. When clicked, the button opens a full-screen modal overlay with a slight blur effect, visually focusing the user on the search functionality while the rest of the page dims.\u00a0<\/p>\n\n\n\n<p>The modal includes a responsive, styled search input field that is immediately focused and supports live autocomplete through the <code>useCombobox<\/code> hook. Below the input field, a scrollable list dynamically displays search results, with highlighted and selectable options that navigate the user to their respective pages when clicked or when the Enter key is pressed. If no results are found, a subtle message is shown.\u00a0<\/p>\n\n\n\n<p>Additionally, the modal is fully accessible, with ARIA roles and attributes for the <code>combobox<\/code> and options, and it can be closed by clicking outside, pressing the Escape key, or using the close button within the modal. In this example code, we\u2019re using\u00a0 Tailwind CSS, but you can use whatever styling solution works best for your project.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"layout-heading-navbar\">Layout, Heading, and NavBar<\/h3>\n\n\n\n<p>There are 3 more components that we will go over in our components folder.<\/p>\n\n\n\n<p><br>The <code>docs-layout<\/code> component is a wrapper that applies consistent styling to docs pages.\u00a0 This ensures proper spacing and a clean readable layout for its child content.<\/p>\n\n\n\n<p>The <code>heading<\/code> component creates custom-styled headings with clickable anchor links, making it easy for users to share or navigate to specific sections. It dynamically adjusts styles based on the heading level.<\/p>\n\n\n\n<p>Lastly, the Navbar component provides a global navigation bar with a link to the home page on the left and a <code>SearchBar<\/code> component on the right for usability.<\/p>\n\n\n\n<p>There is not much to the code in these components.&nbsp; You can reference them in the repo here:&nbsp;<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/Fran-A-Dev\/smart-search-with-app-router\/blob\/main\/components\/nav-bar.jsx\">https:\/\/github.com\/Fran-A-Dev\/smart-search-with-app-router\/blob\/main\/components\/nav-bar.jsx<\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/Fran-A-Dev\/smart-search-with-app-router\/blob\/main\/components\/heading.jsx\">https:\/\/github.com\/Fran-A-Dev\/smart-search-with-app-router\/blob\/main\/components\/heading.jsx<\/a><\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/Fran-A-Dev\/smart-search-with-app-router\/blob\/main\/components\/docs-layout.jsx\">https:\/\/github.com\/Fran-A-Dev\/smart-search-with-app-router\/blob\/main\/components\/docs-layout.jsx<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"routejs-file\">The <code>route.js<\/code> File<\/h2>\n\n\n\n<p>In Next.js 15 App Router, we need to set up a <a href=\"https:\/\/nextjs.org\/docs\/canary\/app\/api-reference\/file-conventions\/route\">route handler<\/a> that allows us to create a custom request handler for our search API route.&nbsp; This API route handles GET requests for search functionality by querying a GraphQL endpoint for documents matching a user-provided query string.<\/p>\n\n\n\n<p>In the <code>app<\/code> folder, create a subfolder called <code>api<\/code>. In that subfolder, create another subfolder called <code>search<\/code>.\u00a0 Within that <code>search<\/code> folder, create a file called <code>route.js<\/code>.\u00a0 Copy and paste this code block:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-35\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { NextResponse } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/server\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">import<\/span> { ReasonPhrases, StatusCodes } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"http-status-codes\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">cleanPath<\/span>(<span class=\"hljs-params\">filePath<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">return<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>    filePath\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/^\\\/?src\\\/pages\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/^\\\/?pages\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/\\\/index\\.mdx$\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>      .replace(<span class=\"hljs-regexp\">\/\\.mdx$\/<\/span>, <span class=\"hljs-string\">\"\"<\/span>) || <span class=\"hljs-string\">\"\/\"<\/span>\n<\/span><\/span><span class='shcb-loc'><span>  );\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">GET<\/span>(<span class=\"hljs-params\">request<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> endpoint = process.env.NEXT_PUBLIC_SEARCH_ENDPOINT;\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> accessToken = process.env.NEXT_SEARCH_ACCESS_TOKEN;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> { searchParams } = <span class=\"hljs-keyword\">new<\/span> URL(request.url);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> query = searchParams.get(<span class=\"hljs-string\">\"query\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (!query) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> NextResponse.json(\n<\/span><\/span><span class='shcb-loc'><span>      { <span class=\"hljs-attr\">error<\/span>: <span class=\"hljs-string\">\"Search query is required.\"<\/span> },\n<\/span><\/span><span class='shcb-loc'><span>      { <span class=\"hljs-attr\">status<\/span>: StatusCodes.BAD_REQUEST }\n<\/span><\/span><span class='shcb-loc'><span>    );\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> graphqlQuery = <span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    query FindDocuments($query: String!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      find(query: $query) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        total<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        documents {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">          id<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">          data<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  `<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(endpoint, {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">headers<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">Authorization<\/span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${accessToken}<\/span>`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      },\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">query<\/span>: graphqlQuery,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">variables<\/span>: { query },\n<\/span><\/span><span class='shcb-loc'><span>      }),\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (!response.ok) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span> NextResponse.json(\n<\/span><\/span><span class='shcb-loc'><span>        { <span class=\"hljs-attr\">error<\/span>: ReasonPhrases.SERVICE_UNAVAILABLE },\n<\/span><\/span><span class='shcb-loc'><span>        { <span class=\"hljs-attr\">status<\/span>: StatusCodes.SERVICE_UNAVAILABLE }\n<\/span><\/span><span class='shcb-loc'><span>      );\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> response.json();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (result.errors) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span> NextResponse.json(\n<\/span><\/span><span class='shcb-loc'><span>        { <span class=\"hljs-attr\">errors<\/span>: result.errors },\n<\/span><\/span><span class='shcb-loc'><span>        { <span class=\"hljs-attr\">status<\/span>: StatusCodes.INTERNAL_SERVER_ERROR }\n<\/span><\/span><span class='shcb-loc'><span>      );\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> seenIds = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Set<\/span>();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> formattedResults = &#91;];\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> content <span class=\"hljs-keyword\">of<\/span> result.data.find.documents) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> contentType =\n<\/span><\/span><span class='shcb-loc'><span>        content.data.content_type || content.data.post_type || <span class=\"hljs-string\">\"mdx_doc\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">let<\/span> item = <span class=\"hljs-literal\">null<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">if<\/span> (contentType === <span class=\"hljs-string\">\"mdx_doc\"<\/span> &amp;&amp; content.data.title) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">const<\/span> path = content.data.path ? cleanPath(content.data.path) : <span class=\"hljs-string\">\"\/\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>        item = {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">id<\/span>: content.id,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">title<\/span>: content.data.title,\n<\/span><\/span><span class='shcb-loc'><span>          path,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-string\">\"mdx_doc\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        };\n<\/span><\/span><span class='shcb-loc'><span>      } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>        (contentType === <span class=\"hljs-string\">\"wp_post\"<\/span> || contentType === <span class=\"hljs-string\">\"post\"<\/span>) &amp;&amp;\n<\/span><\/span><span class='shcb-loc'><span>        content.data.post_title &amp;&amp;\n<\/span><\/span><span class='shcb-loc'><span>        content.data.post_name\n<\/span><\/span><span class='shcb-loc'><span>      ) {\n<\/span><\/span><span class='shcb-loc'><span>        item = {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">id<\/span>: content.id,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">title<\/span>: content.data.post_title,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">path<\/span>: <span class=\"hljs-string\">`\/blog\/<span class=\"hljs-subst\">${content.data.post_name}<\/span>`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-string\">\"post\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        };\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">if<\/span> (item &amp;&amp; !seenIds.has(item.id)) {\n<\/span><\/span><span class='shcb-loc'><span>        seenIds.add(item.id);\n<\/span><\/span><span class='shcb-loc'><span>        formattedResults.push(item);\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> NextResponse.json(formattedResults, { <span class=\"hljs-attr\">status<\/span>: StatusCodes.OK });\n<\/span><\/span><span class='shcb-loc'><span>  } <span class=\"hljs-keyword\">catch<\/span> (error) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Error fetching search data:\"<\/span>, error);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> NextResponse.json(\n<\/span><\/span><span class='shcb-loc'><span>      { <span class=\"hljs-attr\">error<\/span>: ReasonPhrases.INTERNAL_SERVER_ERROR },\n<\/span><\/span><span class='shcb-loc'><span>      { <span class=\"hljs-attr\">status<\/span>: StatusCodes.INTERNAL_SERVER_ERROR }\n<\/span><\/span><span class='shcb-loc'><span>    );\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-35\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>First, we import <code>NextResponse<\/code> from Next.js, to return responses in App Router-based endpoints. We also bring in <code>ReasonPhrases<\/code> and <code>StatusCodes<\/code> from <code>http-status-codes<\/code> to ensure our responses are consistent and human-readable, making it easier to understand which HTTP statuses we\u2019re returning and why.<\/p>\n\n\n\n<p>The <code>cleanPath<\/code> function then takes an internal file path\u2014like one found in a Next.js <code>src\/pages<\/code> or <code>app<\/code> directory\u2014and transforms it into a clean, user-friendly URL. It strips away any directory prefixes (<code>\/src\/pages<\/code>, <code>\/pages<\/code>), and removes filename extensions or index placeholders (such as <code>index.mdx<\/code>). <\/p>\n\n\n\n<p>If all content is removed, it defaults to <code>\/<\/code>, ensuring every piece of content resolves to a neat, presentable URL. <\/p>\n\n\n\n<p>In the next few lines, we handle incoming search requests. The <code>GET<\/code> function is the main entry point for the endpoint. It begins by reading environment variables <code>NEXT_PUBLIC_SEARCH_ENDPOINT<\/code> and <code>NEXT_SEARCH_ACCESS_TOKEN<\/code>\u2014these determine where we\u2019ll send our search queries and how we authenticate with the search service.<\/p>\n\n\n\n<p>Next, it extracts the <code>query<\/code> parameter from the request\u2019s URL. If no <code>query<\/code> is provided, the function quickly returns a <code>400 Bad Request<\/code> response, ensuring that the caller knows a query string is mandatory.<\/p>\n\n\n\n<p>Then we have a GraphQL query that finds all documents matching the user\u2019s <code>query<\/code> string:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-36\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">GET<\/span>(<span class=\"hljs-params\">request<\/span>) <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> endpoint = process.env.NEXT_PUBLIC_SEARCH_ENDPOINT;\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> accessToken = process.env.NEXT_SEARCH_ACCESS_TOKEN;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> { searchParams } = <span class=\"hljs-keyword\">new<\/span> URL(request.url);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> query = searchParams.get(<span class=\"hljs-string\">\"query\"<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (!query) {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">return<\/span> NextResponse.json(\n<\/span><\/span><span class='shcb-loc'><span>      { <span class=\"hljs-attr\">error<\/span>: <span class=\"hljs-string\">\"Search query is required.\"<\/span> },\n<\/span><\/span><span class='shcb-loc'><span>      { <span class=\"hljs-attr\">status<\/span>: StatusCodes.BAD_REQUEST }\n<\/span><\/span><span class='shcb-loc'><span>    );\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">const<\/span> graphqlQuery = <span class=\"hljs-string\">`<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    query FindDocuments($query: String!) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      find(query: $query) {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        total<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        documents {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">          id<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">          data<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">        }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">      }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">    }<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-string\">  `<\/span>;\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-36\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the next few lines, we send a POST request to the search endpoint, including the GraphQL query and the search term. This request includes authentication via a bearer token to ensure only authorized calls go through.<\/p>\n\n\n\n<p>If the response indicates an issue\u2014like the server being unreachable\u2014it immediately returns a <code>503 Service Unavailable<\/code> message to let the caller know the search service isn\u2019t currently accessible.<\/p>\n\n\n\n<p>After verifying the response is good, it converts the response to JSON. If the server replies with GraphQL errors, the code returns a <code>500 Internal Server Error<\/code> response, signaling that something went wrong on the server side. <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-37\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span> <span class=\"hljs-keyword\">try<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> response = <span class=\"hljs-keyword\">await<\/span> fetch(endpoint, {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">headers<\/span>: {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">Authorization<\/span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${accessToken}<\/span>`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>      },\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">query<\/span>: graphqlQuery,\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-attr\">variables<\/span>: { query },\n<\/span><\/span><span class='shcb-loc'><span>      }),\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (!response.ok) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span> NextResponse.json(\n<\/span><\/span><span class='shcb-loc'><span>        { <span class=\"hljs-attr\">error<\/span>: ReasonPhrases.SERVICE_UNAVAILABLE },\n<\/span><\/span><span class='shcb-loc'><span>        { <span class=\"hljs-attr\">status<\/span>: StatusCodes.SERVICE_UNAVAILABLE }\n<\/span><\/span><span class='shcb-loc'><span>      );\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> response.json();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">if<\/span> (result.errors) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">return<\/span> NextResponse.json(\n<\/span><\/span><span class='shcb-loc'><span>        { <span class=\"hljs-attr\">errors<\/span>: result.errors },\n<\/span><\/span><span class='shcb-loc'><span>        { <span class=\"hljs-attr\">status<\/span>: StatusCodes.INTERNAL_SERVER_ERROR }\n<\/span><\/span><span class='shcb-loc'><span>      );\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-37\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Following that, the documents are returned by the search service, turning them into a more usable, standardized format. It uses a <code>Set<\/code> called <code>seenIds<\/code> to keep track of which document IDs have already been processed, ensuring that no duplicates end up in the final results.<\/p>\n\n\n\n<p>For each <code>content<\/code> object, the code identifies the document\u2019s type\u2014whether it\u2019s an <code>mdx_doc<\/code> or a WordPress post\u2014so that it knows how to handle its title and path. <\/p>\n\n\n\n<p>For <code>mdx_doc<\/code> types, it cleans the path using <code>cleanPath()<\/code> to create a user-friendly URL. For WordPress posts, it constructs a blog URL based on the post\u2019s name. This ensures that each document is transformed into a consistent, meaningful shape: it has an <code>id<\/code>, a <code>title<\/code>, and a <code>path<\/code> that can be used directly in the frontend.<\/p>\n\n\n\n<p>Finally, each document is only added to the results if it hasn\u2019t been seen before. Once processed, <code>formattedResults<\/code> ends up with a clean, non-redundant list of items.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-38\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> seenIds = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Set<\/span>();\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">const<\/span> formattedResults = &#91;];\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> content <span class=\"hljs-keyword\">of<\/span> result.data.find.documents) {\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">const<\/span> contentType =\n<\/span><\/span><span class='shcb-loc'><span>        content.data.content_type || content.data.post_type || <span class=\"hljs-string\">\"mdx_doc\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">let<\/span> item = <span class=\"hljs-literal\">null<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">if<\/span> (contentType === <span class=\"hljs-string\">\"mdx_doc\"<\/span> &amp;&amp; content.data.title) {\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">const<\/span> path = content.data.path ? cleanPath(content.data.path) : <span class=\"hljs-string\">\"\/\"<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>        item = {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">id<\/span>: content.id,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">title<\/span>: content.data.title,\n<\/span><\/span><span class='shcb-loc'><span>          path,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-string\">\"mdx_doc\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        };\n<\/span><\/span><span class='shcb-loc'><span>      } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (\n<\/span><\/span><span class='shcb-loc'><span>        (contentType === <span class=\"hljs-string\">\"wp_post\"<\/span> || contentType === <span class=\"hljs-string\">\"post\"<\/span>) &amp;&amp;\n<\/span><\/span><span class='shcb-loc'><span>        content.data.post_title &amp;&amp;\n<\/span><\/span><span class='shcb-loc'><span>        content.data.post_name\n<\/span><\/span><span class='shcb-loc'><span>      ) {\n<\/span><\/span><span class='shcb-loc'><span>        item = {\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">id<\/span>: content.id,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">title<\/span>: content.data.post_title,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">path<\/span>: <span class=\"hljs-string\">`\/blog\/<span class=\"hljs-subst\">${content.data.post_name}<\/span>`<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>          <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-string\">\"post\"<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        };\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>      <span class=\"hljs-keyword\">if<\/span> (item &amp;&amp; !seenIds.has(item.id)) {\n<\/span><\/span><span class='shcb-loc'><span>        seenIds.add(item.id);\n<\/span><\/span><span class='shcb-loc'><span>        formattedResults.push(item);\n<\/span><\/span><span class='shcb-loc'><span>      }\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-38\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Lastly, the code returns a <code>200 OK<\/code> response containing the <code>formattedResults<\/code> array if all is good. This gives the user a clear set of neatly formatted search results.<\/p>\n\n\n\n<p>If an exception occurs, the catch block logs the issue for diagnostic purposes and responds with a <code>500 Internal Server Error<\/code>. This ensures that the client knows there is an error and logs a record of the error to troubleshoot.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"test-search-functionality\">Test the Search Functionality<\/h2>\n\n\n\n<p>That is all the code we need to make this work. Stoked!!!<\/p>\n\n\n\n<p>Now, let\u2019s test in production mode.&nbsp;&nbsp;<\/p>\n\n\n\n<p>Run this command in your terminal to create a production build in your Next.js project:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>npm run build\n<\/span><\/span><\/code><\/span><\/pre>\n\n\n<p>When you run a build, you will notice this output in your terminal:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"524\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-11-at-4.25.55\u202fPM-1024x524.png\" alt=\"\" class=\"wp-image-31752\" style=\"width:535px;height:auto\" srcset=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-11-at-4.25.55\u202fPM-1024x524.png 1024w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-11-at-4.25.55\u202fPM-300x154.png 300w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-11-at-4.25.55\u202fPM-768x393.png 768w, https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/Screenshot-2024-12-11-at-4.25.55\u202fPM.png 1352w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>You should be even more stoked because the output shows the plugin working with the indexing process being output!&nbsp;&nbsp;<\/p>\n\n\n\n<p>Now, start the server to test the application in production:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>npm run start\n<\/span><\/span><\/code><\/span><\/pre>\n\n\n<p>On the page, click on the search bar and type whatever title or associated content you have with a WordPress blog post in your WP backend.&nbsp; You should see this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1920\" height=\"1080\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/WP-search.gif\" alt=\"\" class=\"wp-image-31753\"\/><\/figure>\n\n\n\n<p>Then, try typing a title or content associated with your MDX docs and you should see this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1920\" height=\"1080\" src=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/docs-search.gif\" alt=\"\" class=\"wp-image-31754\"\/><\/figure>\n\n\n\n<p><br>Notice that the search results will return both documents and blog posts if they contain similar data!\u00a0 When you click that search result, it will take you to the correct path of the MDX page or the WordPress blog post. (Remember, the WP posts are coming from the dynamic route file you already have setup )<\/p>\n\n\n\n<p>The good thing is, that we have labeled them accordingly so our users know which one they are.\u00a0\u00a0<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"conclusion\">Conclusion\u00a0<\/h2>\n\n\n\n<p>The WP Engine Smart Search plugin is an enhanced search solution for regular WordPress and headless WordPress. We hope this tutorial helps you better understand how to set it up and use it in a headless app.&nbsp;&nbsp;<\/p>\n\n\n\n<p>As always, we\u2019re super stoked to hear your feedback and learn about the headless projects you\u2019re working on, so hit us up in our <a href=\"https:\/\/discord.com\/invite\/headless-wordpress-836253505944813629\">Discord<\/a>!<\/p>\n\n\n\n<p><br><br><\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you\u2019re using Headless WordPress and aiming to build a documentation site alongside a blog, you might consider integrating MDX or Markdown files. These formats make it simple for developers [&hellip;]<\/p>\n","protected":false},"author":20,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_EventAllDay":false,"_EventTimezone":"","_EventStartDate":"","_EventEndDate":"","_EventStartDateUTC":"","_EventEndDateUTC":"","_EventShowMap":false,"_EventShowMapLink":false,"_EventURL":"","_EventCost":"","_EventCostDescription":"","_EventCurrencySymbol":"","_EventCurrencyCode":"","_EventCurrencyPosition":"","_EventDateTimeSeparator":"","_EventTimeRangeSeparator":"","_EventOrganizerID":[],"_EventVenueID":[],"_OrganizerEmail":"","_OrganizerPhone":"","_OrganizerWebsite":"","_VenueAddress":"","_VenueCity":"","_VenueCountry":"","_VenueProvince":"","_VenueState":"","_VenueZip":"","_VenuePhone":"","_VenueURL":"","_VenueStateProvince":"","_VenueLat":"","_VenueLng":"","_VenueShowMap":false,"_VenueShowMapLink":false,"footnotes":""},"categories":[23],"tags":[],"class_list":["post-31750","post","type-post","status-publish","format-standard","hentry","category-headless"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.5 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>WP Engine Smart Search for Headless WordPress with Next.js and MDX - Builders<\/title>\n<meta name=\"description\" content=\"Learn how to implement WP Engine&#039;s Smart Search in Next.js App Router for your headless WordPress setup along with MDX. This guide covers indexing, error handling, and configuration for a seamless search experience.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"WP Engine Smart Search for headless WordPress and MDX with Next.js\" \/>\n<meta property=\"og:description\" content=\"Learn how to implement WP Engine&#039;s Smart Search in Next.js App Router for your headless WordPress setup along with MDX. This guide covers indexing, error handling, and configuration for a seamless search experience.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/\" \/>\n<meta property=\"og:site_name\" content=\"Builders\" \/>\n<meta property=\"article:published_time\" content=\"2024-12-12T16:22:41+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-12-12T17:22:27+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/WPE-Builders-YouTube-ScreenshotOrange-1920x1080-1-1.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"1080\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Francis Agulto\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@wpebuilders\" \/>\n<meta name=\"twitter:site\" content=\"@wpebuilders\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Francis Agulto\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"44 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/\"},\"author\":{\"name\":\"Francis Agulto\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/person\\\/bcdcb4ac0b215c34b6b30e440a24dc54\"},\"headline\":\"WP Engine Smart Search for Headless WordPress with Next.js and MDX\",\"datePublished\":\"2024-12-12T16:22:41+00:00\",\"dateModified\":\"2024-12-12T17:22:27+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/\"},\"wordCount\":4711,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/lh7-rt.googleusercontent.com\\\/docsz\\\/AD_4nXcu4syltRzSEEPg-g1kwmn3APG5C-aHaMe0kk-iiQcw2FdEbw4rGehbXe7mhcc3VtoK2KgPJbtE9dfKfA18bAdf8ox1ybMPRPB2I1aCCAheYLtpqP2leeiNCWq18gfCf0IPFjc1jA?key=3m-9LI1ZF8LoJcX0sYilKkoD\",\"articleSection\":[\"Headless\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/\",\"name\":\"WP Engine Smart Search for Headless WordPress with Next.js and MDX - Builders\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/lh7-rt.googleusercontent.com\\\/docsz\\\/AD_4nXcu4syltRzSEEPg-g1kwmn3APG5C-aHaMe0kk-iiQcw2FdEbw4rGehbXe7mhcc3VtoK2KgPJbtE9dfKfA18bAdf8ox1ybMPRPB2I1aCCAheYLtpqP2leeiNCWq18gfCf0IPFjc1jA?key=3m-9LI1ZF8LoJcX0sYilKkoD\",\"datePublished\":\"2024-12-12T16:22:41+00:00\",\"dateModified\":\"2024-12-12T17:22:27+00:00\",\"description\":\"Learn how to implement WP Engine's Smart Search in Next.js App Router for your headless WordPress setup along with MDX. This guide covers indexing, error handling, and configuration for a seamless search experience.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/#primaryimage\",\"url\":\"https:\\\/\\\/lh7-rt.googleusercontent.com\\\/docsz\\\/AD_4nXcu4syltRzSEEPg-g1kwmn3APG5C-aHaMe0kk-iiQcw2FdEbw4rGehbXe7mhcc3VtoK2KgPJbtE9dfKfA18bAdf8ox1ybMPRPB2I1aCCAheYLtpqP2leeiNCWq18gfCf0IPFjc1jA?key=3m-9LI1ZF8LoJcX0sYilKkoD\",\"contentUrl\":\"https:\\\/\\\/lh7-rt.googleusercontent.com\\\/docsz\\\/AD_4nXcu4syltRzSEEPg-g1kwmn3APG5C-aHaMe0kk-iiQcw2FdEbw4rGehbXe7mhcc3VtoK2KgPJbtE9dfKfA18bAdf8ox1ybMPRPB2I1aCCAheYLtpqP2leeiNCWq18gfCf0IPFjc1jA?key=3m-9LI1ZF8LoJcX0sYilKkoD\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"WP Engine Smart Search for Headless WordPress with Next.js and MDX\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#website\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\",\"name\":\"Builders\",\"description\":\"Reimagining the way we build with WordPress.\",\"publisher\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#organization\",\"name\":\"WP Engine\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/WP-Engine-Horizontal@2x.png\",\"contentUrl\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/wp-content\\\/uploads\\\/2024\\\/05\\\/WP-Engine-Horizontal@2x.png\",\"width\":348,\"height\":68,\"caption\":\"WP Engine\"},\"image\":{\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/x.com\\\/wpebuilders\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UCh1WuL54XFb9ZI6m6goFv1g\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/#\\\/schema\\\/person\\\/bcdcb4ac0b215c34b6b30e440a24dc54\",\"name\":\"Francis Agulto\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/0c8a05c76944fc987d57296c96dc368055844527088c0aa44297edbfa8b82546?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/0c8a05c76944fc987d57296c96dc368055844527088c0aa44297edbfa8b82546?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/0c8a05c76944fc987d57296c96dc368055844527088c0aa44297edbfa8b82546?s=96&d=mm&r=g\",\"caption\":\"Francis Agulto\"},\"description\":\"Fran Agulto is a Developer Advocate at WP Engine. He is a lover of all things headless WordPress, Rock Climbing, and overall being stoked for people that love what they do and share that stoke with others! Follow me on Twitter for cool stoked headless WP!\",\"url\":\"https:\\\/\\\/wpengine.com\\\/builders\\\/author\\\/francis-agultowpengine-com-2-2-2-2-2-2-2-2-2-2-2-3\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"WP Engine Smart Search for Headless WordPress with Next.js and MDX - Builders","description":"Learn how to implement WP Engine's Smart Search in Next.js App Router for your headless WordPress setup along with MDX. This guide covers indexing, error handling, and configuration for a seamless search experience.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/","og_locale":"en_US","og_type":"article","og_title":"WP Engine Smart Search for headless WordPress and MDX with Next.js","og_description":"Learn how to implement WP Engine's Smart Search in Next.js App Router for your headless WordPress setup along with MDX. This guide covers indexing, error handling, and configuration for a seamless search experience.","og_url":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/","og_site_name":"Builders","article_published_time":"2024-12-12T16:22:41+00:00","article_modified_time":"2024-12-12T17:22:27+00:00","og_image":[{"width":1920,"height":1080,"url":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/12\/WPE-Builders-YouTube-ScreenshotOrange-1920x1080-1-1.png","type":"image\/png"}],"author":"Francis Agulto","twitter_card":"summary_large_image","twitter_creator":"@wpebuilders","twitter_site":"@wpebuilders","twitter_misc":{"Written by":"Francis Agulto","Est. reading time":"44 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/#article","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/"},"author":{"name":"Francis Agulto","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/person\/bcdcb4ac0b215c34b6b30e440a24dc54"},"headline":"WP Engine Smart Search for Headless WordPress with Next.js and MDX","datePublished":"2024-12-12T16:22:41+00:00","dateModified":"2024-12-12T17:22:27+00:00","mainEntityOfPage":{"@id":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/"},"wordCount":4711,"commentCount":0,"publisher":{"@id":"https:\/\/wpengine.com\/builders\/#organization"},"image":{"@id":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/#primaryimage"},"thumbnailUrl":"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXcu4syltRzSEEPg-g1kwmn3APG5C-aHaMe0kk-iiQcw2FdEbw4rGehbXe7mhcc3VtoK2KgPJbtE9dfKfA18bAdf8ox1ybMPRPB2I1aCCAheYLtpqP2leeiNCWq18gfCf0IPFjc1jA?key=3m-9LI1ZF8LoJcX0sYilKkoD","articleSection":["Headless"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/","url":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/","name":"WP Engine Smart Search for Headless WordPress with Next.js and MDX - Builders","isPartOf":{"@id":"https:\/\/wpengine.com\/builders\/#website"},"primaryImageOfPage":{"@id":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/#primaryimage"},"image":{"@id":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/#primaryimage"},"thumbnailUrl":"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXcu4syltRzSEEPg-g1kwmn3APG5C-aHaMe0kk-iiQcw2FdEbw4rGehbXe7mhcc3VtoK2KgPJbtE9dfKfA18bAdf8ox1ybMPRPB2I1aCCAheYLtpqP2leeiNCWq18gfCf0IPFjc1jA?key=3m-9LI1ZF8LoJcX0sYilKkoD","datePublished":"2024-12-12T16:22:41+00:00","dateModified":"2024-12-12T17:22:27+00:00","description":"Learn how to implement WP Engine's Smart Search in Next.js App Router for your headless WordPress setup along with MDX. This guide covers indexing, error handling, and configuration for a seamless search experience.","breadcrumb":{"@id":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/#primaryimage","url":"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXcu4syltRzSEEPg-g1kwmn3APG5C-aHaMe0kk-iiQcw2FdEbw4rGehbXe7mhcc3VtoK2KgPJbtE9dfKfA18bAdf8ox1ybMPRPB2I1aCCAheYLtpqP2leeiNCWq18gfCf0IPFjc1jA?key=3m-9LI1ZF8LoJcX0sYilKkoD","contentUrl":"https:\/\/lh7-rt.googleusercontent.com\/docsz\/AD_4nXcu4syltRzSEEPg-g1kwmn3APG5C-aHaMe0kk-iiQcw2FdEbw4rGehbXe7mhcc3VtoK2KgPJbtE9dfKfA18bAdf8ox1ybMPRPB2I1aCCAheYLtpqP2leeiNCWq18gfCf0IPFjc1jA?key=3m-9LI1ZF8LoJcX0sYilKkoD"},{"@type":"BreadcrumbList","@id":"https:\/\/wpengine.com\/builders\/wp-engine-smart-search-for-headless-wordpress-with-next-js-and-mdx\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/wpengine.com\/builders\/"},{"@type":"ListItem","position":2,"name":"WP Engine Smart Search for Headless WordPress with Next.js and MDX"}]},{"@type":"WebSite","@id":"https:\/\/wpengine.com\/builders\/#website","url":"https:\/\/wpengine.com\/builders\/","name":"Builders","description":"Reimagining the way we build with WordPress.","publisher":{"@id":"https:\/\/wpengine.com\/builders\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/wpengine.com\/builders\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/wpengine.com\/builders\/#organization","name":"WP Engine","url":"https:\/\/wpengine.com\/builders\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/logo\/image\/","url":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/05\/WP-Engine-Horizontal@2x.png","contentUrl":"https:\/\/wpengine.com\/builders\/wp-content\/uploads\/2024\/05\/WP-Engine-Horizontal@2x.png","width":348,"height":68,"caption":"WP Engine"},"image":{"@id":"https:\/\/wpengine.com\/builders\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/wpebuilders","https:\/\/www.youtube.com\/channel\/UCh1WuL54XFb9ZI6m6goFv1g"]},{"@type":"Person","@id":"https:\/\/wpengine.com\/builders\/#\/schema\/person\/bcdcb4ac0b215c34b6b30e440a24dc54","name":"Francis Agulto","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/0c8a05c76944fc987d57296c96dc368055844527088c0aa44297edbfa8b82546?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/0c8a05c76944fc987d57296c96dc368055844527088c0aa44297edbfa8b82546?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/0c8a05c76944fc987d57296c96dc368055844527088c0aa44297edbfa8b82546?s=96&d=mm&r=g","caption":"Francis Agulto"},"description":"Fran Agulto is a Developer Advocate at WP Engine. He is a lover of all things headless WordPress, Rock Climbing, and overall being stoked for people that love what they do and share that stoke with others! Follow me on Twitter for cool stoked headless WP!","url":"https:\/\/wpengine.com\/builders\/author\/francis-agultowpengine-com-2-2-2-2-2-2-2-2-2-2-2-3\/"}]}},"_links":{"self":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/31750","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/users\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/comments?post=31750"}],"version-history":[{"count":0,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/posts\/31750\/revisions"}],"wp:attachment":[{"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/media?parent=31750"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/categories?post=31750"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wpengine.com\/builders\/wp-json\/wp\/v2\/tags?post=31750"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}