Universal vs. Vertical Search| Hitchhikers Platform

Before diving into building an experience, it’s important to understand the difference between Vertical and Universal Search.

The Yext Search layout is very similar to Google. With Google you have your “main” search results page, but under the search bar there are other tabs like “images”, “maps”, and “news”. These tabs are what we call Verticals. These verticals are visible on the main (Universal) search page, but you can also click into them and see a separate search result page dedicated to those types of results.

For example:

The above shows the Universal search results page on Google (represented by the “All” tab). The user can browse the results on that page or optionally click into a different tab like “Images”:

Upon clicking into an alternate vertical tab like “Images”, the user only sees images in the results.

Vertical Search is a search page that only shows results from a single backend (vertical). For example, a Vertical Search page might show a list of locations or a list of events.

A Vertical Search page should use the <VerticalResults/> component for displaying results. Additionally, the search state must have the verticalKey property set.

When you setup your search experience in the Yext platform, you configure each vertical separately. This allows you to use different search algorithms and filter options for each vertical.

Here’s an example configuration for a Products vertical:

"verticals": {
	"products": {
	  "entityTypes": [
        "product"
    ],
	  "name": "Products",
    "facets": {
      "fields": [
        {
          "fieldId": "price.value",
          "ranges": {
            "algorithm": "DYNAMIC",
            "bucketCount": 4
          },
          "sortCriteria": "ASC"
        }
      ]
    },
    "searchableFields": {
      "builtin.entityType": {
        "nlpFilter": true
      },
      "keywords": {
        "phraseMatch": true
      },
      "name": {
        "semanticTextSearch": true
      },
      "price.value": {
        "facet": true
      },
      "richTextDescription": {
        "documentSearch": true
      },
      "size": {
        "nlpFilter": true
      }
    },
    "sortBys": [],
    "source": "KNOWLEDGE_MANAGER"
  }

Vertical Search is also paginated. The Vertical Search API will return 20 results by default but can be set to return a max of 50 results at a time. Adding the Pagination component makes it easy to toggle between different pages of results.


When a search is triggered by the SearchBar component, it will make Universal Search API call by default. To use Vertical Search instead set the verticalKey in the search state.

You can do this by either passing it as a prop when you initially declare your <SearchHeadlessProvider />:

return (
  <SearchHeadlessProvider
    apiKey="YOUR_API_KEY"
    experienceKey="YOUR_EXPERIENCE_KEY"
    locale="en"
    verticalKey={verticalKey}
  >
    <App />
  </SearchHeadlessProvider>
);

Or by manually setting it with the useSearchActions hook:

const searchActions = useSearchActions();

useEffect(() => {
  searchActions.setVertical("artists");
}, []);


Rendering Vertical Search Results

Once the verticalKey is set, it’s easy to render results with the VerticalResults and StandardCard components.

function App() {
  return (
    <div className="flex justify-center px-4 py-6">
      <div className="w-full max-w-5xl">
        <SearchBar />
        <VerticalResults CardComponent={StandardCard} />
        <Pagination />
      </div>
    </div>
  );
}

When results are returned, the Pagination component allows the user to advance to the next page of results.


Using a Custom Card

The CardComponent prop can be used to pass a component to Vertical Results for a more custom layout.

const ArtistCard = ({ result }: CardProps) => {
  const artistName = result.name;
  const imageUrl = result.rawData.primaryPhoto?.image.url;

  return (
    <div className="flex flex-col items-center p-4">
      <div className="rounded-full">
        <img src={imageUrl} className="object-contain w-32 rounded-full pb-4" />
      </div>
      <div className="font-semibold">{artistName}</div>
    </div>
  );
};

function App() {
  return (
    <div className="flex justify-center px-4 py-6">
      <div className="w-full max-w-5xl">
        <SearchBar />
        <VerticalResults
          customCssClasses={{
            results: "grid md:grid-cols-2 lg:grid-cols-4 gap-4 grid-cols-1",
          }}
          CardComponent={ArtistCard}
          allowPagination={false}
        />
      </div>
    </div>
  );
}


Universal Search is a search page that combines results from multiple different backends (verticals) together. For example, a Universal Search page might combine locations, events, people and FAQs into one search experience.

When using Universal Search, each vertical is still independent. This means that the query is searched across each vertical at once, applying the algorithms configured for each of those verticals, and then returning the verticals in order of relevance.

Furthermore, each vertical can style its results independently. A “products” vertical might display results as a grid, where an “FAQ” vertical might display results as collapsible sections. On top of styling, verticals can also use completely separate search algorithms and logic to return results.

A Universal Search page should use the <UniversalResults /> component for displaying results. Generally, universal search is labeled with the All tab in the navigation.

Facets/Static Filters are only applicable to a single vertical. Since Universal search combines multiple verticals together on one page, we do not support faceting/filtering on Universal pages.

Universal Search is also not paginated. It’s common to include a “View All” button at the top of each vertical that navigates to a specific vertical search page.


Rendering Universal Search Results

When a universal search runs, the results appear in the universal section of the search state separated into verticals.

The sections are ordered by their relevance to the search query. For example, imagine you have an artists and _musicevents vertical. A search query for “red hot chili peppers” would return the artists vertical first followed by the _musicevents vertical. There would be a lot of boilerplate code to parse out each vertical from the universal results in the search state, but thankfully, we can use the UniversalResults component.

Here, you can see the minimum configuration for the UniversalResults component where each vertical you want to see in the Universal Search is a key in the verticalConfigMap prop.

<UniversalResults
  verticalConfigMap={{
    artists: {},
    events: {},
  }}
/>

By default, the UniversalResult component will use the StandardSection and StandardCard components to layout each vertical.


verticalConfigMap

You can add custom sections and cards to the verticalConfigMap prop to change the appearance of each search vertical. Below, you can see an example of using the same custom card component as before and a new custom section to create a unique layout for the artists vertical.

const ArtistCard = ({ result }: CardProps) => {
  const artistName = result.name;
  const imageUrl = result.rawData.photoGallery?.[0].image.url;

  return (
    <div className="flex flex-col items-center p-4">
      <div className="rounded-full">
        <img src={imageUrl} className="object-contain w-32 rounded-full pb-4" />
      </div>
      <div className="font-semibold">{artistName}</div>
    </div>
  );
};

const GridSection = ({ results, CardComponent, header }: SectionProps) => {
  if (!CardComponent) {
    return <div>Missing Card Component</div>;
  }
  return (
    <div>
      <div>{header}</div>
      <div className="grid md:grid-cols-2 lg:grid-cols-4 gap-4 grid-cols-1">
        {results.map((r) => (
          <CardComponent result={r} />
        ))}
      </div>
    </div>
  );
};

function App() {
  return (
    <div className="flex justify-center px-4 py-6">
      <div className="w-full max-w-5xl">
        <SearchBar />
        <Pagination />
        <UniversalResults
          verticalConfigMap={{
            artists: {
              label: "Artists",
              CardComponent: ArtistCard,
              SectionComponent: GridSection,
            },
            music_events: {
              label: "Events",
            },
          }}
        />
      </div>
    </div>
  );
}