Sitecore Search
Here, we will build a sample of Sitecore Search from the initial setup of Next.js. Since XM Cloud’s Headless SXA is currently provided based on the Next.js Pages Router, we will also create the sample here using the Pages router.
This time, we will create a search site for both Japanese and English content.
Create a Next.js Project
This time, we will select version 14.2.18, which is as close as possible to the version of Next.js used by XM Cloud at the time of writing this document.
npx create-next-app@14.2.18
Add Packages
Install the packages provided by the Sitecore Search SDK. The procedure for this is provided on the following pages.
Refer to the documentation and proceed as follows.
-
Install the React package
Terminal window npm install --save @sitecore-search/react -
Install the UI components package
Terminal window npm install --save @sitecore-search/ui -
Add the package to create Widget components
Terminal window npm install --D @sitecore-search/cli -
Create the .sc-search-settings.json file to define the folder where the Widget will be created
{"widgets-path": "src/widgets"} -
Add the following command to the scripts in the package.json file.
"scripts": {"dev": "next dev","build": "next build","start": "next start","lint": "next lint","create-widget": "sc-search new-widget"},
This completes the package settings required for Sitecore Search.
Implement Language Switching
Create a Language Switching Component
This sample will create a search service for English and Japanese content.
First, define the language definitions to be used on the site in the src/data/locales.ts
file.
export type Language = 'en' | 'ja';
export interface LanguageInfo { country: string; language: Language; label: string;}
const languages: Record<Language, LanguageInfo> = { en: { country: 'us', language: 'en', label: 'English' }, ja: { country: 'jp', language: 'ja', label: '日本語' },};
export default languages;
Next, create the src/contexts/languageContext.ts
file to share the context so that language settings can be shared across the application.
import { createContext } from 'react';import type { Language } from '@/data/locales';
export const LanguageContext = createContext({ language: '', setLanguage: (l: Language) => { console.log(`Language set to: ${l}`); },});
export interface ILanguageContext { language: string; setLanguage: (t: Language) => void;}
Also, create the src/lib/useLocalStorage.ts
file to store language information using the browser’s local storage.
import { useState } from 'react';
export default function useLocalStorage<T>( key: string, initialValue: T | (() => T)): [T, (value: T | ((val: T) => T)) => void] { const [storedValue, setStoredValue] = useState<T>(() => { try { const item = typeof window !== 'undefined' && window.localStorage.getItem(key); return item ? JSON.parse(item) : typeof initialValue === 'function' ? (initialValue as () => T)() : initialValue; } catch { return typeof initialValue === 'function' ? (initialValue as () => T)() : initialValue; } });
const setValue = (value: T | ((val: T) => T)) => { try { const valueToStore = value instanceof Function ? (value as (val: T) => T)(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); } catch (error) { console.error(error); } }; return [storedValue, setValue];}
Finally, create the src/components/LocaleSelector.tsx
file as a language
import { useEffect, useContext } from 'react';import languages, { Language } from '@/data/locales';import { LanguageContext, ILanguageContext } from '@/contexts/languageContext';
export default function LocaleSelector() { const { language, setLanguage } = useContext<ILanguageContext>(LanguageContext);
useEffect(() => { const savedLanguage = window.localStorage.getItem('lang') as Language | null; if (savedLanguage && languages[savedLanguage]) { setLanguage(savedLanguage); } }, [setLanguage]);
const handleLanguageChange = (event: React.ChangeEvent<HTMLSelectElement>) => { const newLanguage = event.target.value as Language; setLanguage(newLanguage);
window.localStorage.setItem('lang', newLanguage); };
return ( <div> <select onChange={handleLanguageChange} value={language || ''}> <option value="">Select a language</option> {Object.keys(languages).map((key) => ( <option key={key} value={key}> {languages[key as Language].label} </option> ))} </select> <p></p> </div> );}
With this, the component setup is complete.
Implementing the Component
Next, we will implement the created language switcher component to make it functional. First, we will implement the created components in src/pages/_app.tsx
. Below is the modified code.
import '@/styles/globals.css';import { useEffect, useState } from 'react';import type { AppProps } from 'next/app';import { LanguageContext } from '@/contexts/languageContext';import useStorage from '@/lib/useLocalStorage';import { Language } from '@/data/locales';import LocaleSelector from '@/components/LocaleSelector';
export default function App({ Component, pageProps }: AppProps) { const [storageLanguage, setStorageLanguage] = useStorage('lang', 'en' as Language); const [language, setLanguage] = useState<Language>(storageLanguage);
useEffect(() => { setStorageLanguage(language); }, [language, setStorageLanguage]); return ( <LanguageContext.Provider value={{ language, setLanguage }}> <LocaleSelector /> <Component {...pageProps} /> </LanguageContext.Provider> );}
To display it on the actual page, change the content of the top page to the following code.
import Head from 'next/head';
export default function Home() { return ( <> <Head> <title>Create Next App</title> <meta name="description" content="Generated by create next app" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/favicon.ico" /> </Head> <main> <h1>Hello Sitecore Search</h1> </main> </> );}
The execution result will be as follows, and when you switch the language, the selected language information will be saved in the local storage under lang
.
Adding the First Widgets
Now that the Next.js side is ready, we will create a search using the Widget.
Adding PreviewSearch
First, we will implement the PreviewSearch
Widget on the top page.
-
Run the following command to create the Widget.
Terminal window npm run create-widgetAnswer as follows:
- What language do you want to support?: Typescript
- What entity do you want to implement?: Content
- What kind of template do you want to create?: PreviewSearchBasic
- What version do you want?: Tailwind
- Save to the folder specified in .sc-search-settings.json?: Yes
- Please enter the name of the component to create: Press Enter without entering anything
-
The following files will be created
Directorysrc
Directorywidgets
Directorycomponents
DirectorySpinner
- index.tsx
DirectoryPreviewSearchBasic
- index.tsx
-
To enable Tailwind CSS for the added widget, add the following line to
tailwind.config.ts
.import type { Config } from "tailwindcss";const config: Config = {content: ["./src/pages/**/*.{js,ts,jsx,tsx,mdx}","./src/components/**/*.{js,ts,jsx,tsx,mdx}","./src/app/**/*.{js,ts,jsx,tsx,mdx}","./src/widgets/**/*.{js,ts,jsx,tsx,mdx}",],
With this, the PreviewSearch Widget is ready.
Adding Widget Settings
To use the created Widget in Next.js, proceed with the following code changes.
Create a .env.local
file and set the values as environment variables to connect to Sitecore Search.
NEXT_PUBLIC_SEARCH_ENV=NEXT_PUBLIC_SEARCH_CUSTOMER_KEY=NEXT_PUBLIC_SEARCH_API_KEY=NEXT_PUBLIC_SEARCH_PATH=/
Once the file is ready, add the necessary code to src/pages/_app.tsx
.
- Import
WidgetsProvider
,PageController
, andEnvironment
required by the Widget - Add
Locales
for the language used byPageController
- Import environment variables used by
WidgetsProvider
from@/data/search
- Add processing for
PageController
- Add
WidgetsProvider
to be used withLanguageContext
The result is as follows.
import '@/styles/globals.css';import { useEffect, useState } from 'react';import type { AppProps } from 'next/app';import { LanguageContext } from '@/contexts/languageContext';import useStorage from '@/lib/useLocalStorage';import locales, { Language } from '@/data/locales';import LocaleSelector from '@/components/LocaleSelector';import { PageController, WidgetsProvider } from '@sitecore-search/react';import { SEARCH_ENV, SEARCH_CUSTOMER_KEY, SEARCH_API_KEY } from '@/data/search';import type { Environment } from '@sitecore-search/data';
export default function App({ Component, pageProps }: AppProps) { const [storageLanguage, setStorageLanguage] = useStorage('lang', 'en' as Language); const [language, setLanguage] = useState<Language>(storageLanguage);
PageController.getContext().setLocaleLanguage(language); PageController.getContext().setLocaleCountry(locales[language].country);
useEffect(() => { PageController.getContext().setLocaleLanguage(language); PageController.getContext().setLocaleCountry(locales[language].country); setStorageLanguage(language); }, [language, setStorageLanguage]); return ( <LanguageContext.Provider value={{ language, setLanguage }}> <WidgetsProvider env={SEARCH_ENV as Environment} customerKey={SEARCH_CUSTOMER_KEY} apiKey={SEARCH_API_KEY} > <LocaleSelector /> <Component {...pageProps} /> </WidgetsProvider> </LanguageContext.Provider> );}
With this, the widget is ready to be placed. First, place the widget in src/pages/index.tsx
.
import Head from "next/head";import PreviewSearch from "@/widgets/PreviewSearchBasic";
export default function Home() { return ( <> <Head> <title>Create Next App</title> <meta name="description" content="Generated by create next app" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/favicon.ico" /> </Head> <main> <h1>Hello Sitecore Search</h1> <div>{<PreviewSearch rfkId="rfkid_6"></PreviewSearch>}</div> </main> </> );}
The execution result is as follows. Since no stylesheets have been adjusted, the search results are displayed vertically, but they are working correctly.
Creating the Header
To create the header for this sample site, we will place a logo on the top left, a search box in the center, and a language switcher on the right.
Adding the Header Component
First, we will place the following component for the header.
import LocaleSelector from '@/components/LocaleSelector';import PreviewSearch from '@/widgets/PreviewSearchBasic';import Image from 'next/image';
export default function Header() { return ( <> <header className="m-6 flex flex-wrap"> <div className="w-6/12 md:w-3/12"> <Image src="/search-horizontal-color-black-txt.svg" alt="Search Logo" width="120" height="40" /> </div> <div className="hidden md:flex md:w-6/12 md:justify-center"> <div className="w-full"> <PreviewSearch rfkId="rfkid_6"></PreviewSearch> </div> </div> <div className="flex w-6/12 justify-end md:w-3/12"> <LocaleSelector /> </div> </header> </> );}
Next, we will modify the Next.js file to load the Header
in src/pages/_app.tsx
.
import '@/styles/globals.css';import { useEffect, useState } from 'react';import type { AppProps } from 'next/app';import { LanguageContext } from '@/contexts/languageContext';import useStorage from '@/lib/useLocalStorage';import locales, { Language } from '@/data/locales';import LocaleSelector from '@/components/LocaleSelector';import { PageController, WidgetsProvider } from '@sitecore-search/react';import { SEARCH_ENV, SEARCH_CUSTOMER_KEY, SEARCH_API_KEY } from '@/data/search';import type { Environment } from '@sitecore-search/data';import Header from "@/components/Header";
export default function App({ Component, pageProps }: AppProps) { const [storageLanguage, setStorageLanguage] = useStorage('lang', 'en' as Language); const [language, setLanguage] = useState<Language>(storageLanguage);
PageController.getContext().setLocaleLanguage(language); PageController.getContext().setLocaleCountry(locales[language].country);
useEffect(() => { PageController.getContext().setLocaleLanguage(language); PageController.getContext().setLocaleCountry(locales[language].country); setStorageLanguage(language); }, [language, setStorageLanguage]); return ( <LanguageContext.Provider value={{ language, setLanguage }}> <WidgetsProvider env={SEARCH_ENV as Environment} customerKey={SEARCH_CUSTOMER_KEY} apiKey={SEARCH_API_KEY} > <LocaleSelector /> <Header /> <Component {...pageProps} /> </WidgetsProvider> </LanguageContext.Provider> );}
Next, we will remove the PreviewSearch
added to the src/pages/index.tsx
page.
import Head from "next/head";import PreviewSearch from "@/widgets/PreviewSearchBasic";
export default function Home() { return ( <> <Head> <title>Create Next App</title> <meta name="description" content="Generated by create next app" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/favicon.ico" /> </Head> <main> <h1>Hello Sitecore Search</h1> <div>{<PreviewSearch rfkId="rfkid_6"></PreviewSearch>}</div> </main> </> );}
Finally, we will change the width of the PreviewSearch
from a fixed 800 to a flexible size.
return ( <PreviewSearch.Root> <PreviewSearch.Input className="w-full box-border py-2 px-2 focus:outline-solid focus:outline-1 focus:outline-gray-500 border-2 bg-gray-100 dark:bg-gray-700 dark:text-gray-100"
Adding Dark Mode
The Sitecore Search Widget supports dark mode. Therefore, we will also enable dark mode for this Next.js sample.
-
Install the next-themes package.
Terminal window npm install next-themes -
Add one line to the Tailwind CSS configuration file
tailwind.config.ts
to enable dark mode.tailwind.config.ts import type { Config } from "tailwindcss";const config: Config = {content: ["./src/pages/**/*.{js,ts,jsx,tsx,mdx}","./src/components/**/*.{js,ts,jsx,tsx,mdx}","./src/app/**/*.{js,ts,jsx,tsx,mdx}","./src/widgets/**/*.{js,ts,jsx,tsx,mdx}",],darkMode: 'class',plugins: [],};export default config; -
Modify
src/pages/_app.tsx
to enable dark mode switching throughout the Next.js application. The added lines are highlighted, and some parts of the code are omitted for clarity.src/pages/_app.tsx import type { Environment } from "@sitecore-search/data";import Header from "@/components/Header";import { ThemeProvider } from "next-themes";export default function App({ Component, pageProps }: AppProps) {return (<ThemeProvider attribute="class" defaultTheme="system" enableSystem><LanguageContext.Provider value={{ language, setLanguage }}><WidgetsProviderenv={SEARCH_ENV as Environment}customerKey={SEARCH_CUSTOMER_KEY}apiKey={SEARCH_API_KEY}><Header /><Component {...pageProps} /></WidgetsProvider></LanguageContext.Provider></ThemeProvider>);} -
Create a component for mode switching in
src/components/mode.tsx
.src/components/mode.tsx import { useState, useEffect } from 'react';export default function DarkModeToggle() {const [isDarkMode, setIsDarkMode] = useState(false);useEffect(() => {const darkModeClass = 'dark';const element = document.documentElement;if (isDarkMode) {element.classList.add(darkModeClass);element.style.colorScheme = 'dark';} else {element.classList.remove(darkModeClass);element.style.colorScheme = 'light';}}, [isDarkMode]);const toggleDarkMode = () => {setIsDarkMode(!isDarkMode);};return (<buttononClick={toggleDarkMode}className="mr-4 bg-gray-200 p-2 text-black dark:bg-gray-800 dark:text-white">Mode</button>);} -
Add the above component to the header before the language switcher component. Also, adjust the height to align them.
src/components/Header.tsx import Image from "next/image";import DarkModeToggle from "./mode";export default function Header() {return (<><header className="flex flex-wrap m-6"><div className="w-6/12 md:w-3/12 flex justify-end items-center"><DarkModeToggle /><LocaleSelector /></div></header></>);}
The button now toggles the mode as shown below.
Creating the Search Results Page
We will add a page to display the search results instead of using the PreviewSearch search box. We will copy the src/pages/index.tsx
file and create src/pages/search/index.tsx
.
Modifying PreviewSearch
The PreviewSearch widget we implemented so far displays search results using the keywords entered in the input box. We will change this to navigate to the next page using a form tag.
First, add the form tag.
return ( <PreviewSearch.Root> <form onSubmit={handleSubmit}> <PreviewSearch.Input className="w-full box-border py-2 px-2 focus:outline-solid focus:outline-1 focus:outline-gray-500 border-2 bg-gray-100 dark:bg-gray-700 dark:text-gray-100" onChange={keyphraseHandler} autoComplete="off" placeholder="Type to search..." /> </form> <PreviewSearch.Content
Next, implement the handleSubmit function to handle form submission as follows. Note that some imports are added, so the code is shown with those parts as well.
import type { ChangeEvent, SyntheticEvent } from "react";import { useRouter } from "next/router";
//
export const PreviewSearchBasicComponent = ({ defaultItemsPerPage = 6 }) => { const router = useRouter();
//
const handleSubmit = (e: SyntheticEvent): void => { e.preventDefault(); const target = (e.target as HTMLFormElement).querySelector( "input" ) as HTMLInputElement; router.push(`/search?q=${target.value}`); target.value = ""; };
return (
With the above changes, we can now pass the keyword to the search results page. To confirm the search keyword, modify the copied file src/pages/search/index.tsx
.
import Head from 'next/head';import { useRouter } from 'next/router';import { useEffect, useState } from 'react';
export default function Home() { const router = useRouter(); const [keyword, setKeyword] = useState('');
useEffect(() => { if (router.isReady) { const query = router.query.q as string; setKeyword(query || ''); } }, [router.isReady, router.query.q]);
return ( <> <Head> <title>Sitecore Search: Results</title> <meta name="description" content="Generated by create next app" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/favicon.ico" /> </Head> <main> <h1 className="m-4 text-3xl">Search Results</h1> <p className="m-4 text-xl">Keyword: {keyword}</p> </main> </> );}
We can now retrieve the keyword on the /search page.
Adding SearchResult
In this sample page, we will add the Basic Search Results
widget provided by the SDK.
To add the widget, run the following command.
npm run create-widget
Select the SearchResults
widget.
The widget and the necessary files for the widget will be added.
Directorysrc
Directorywidgets
Directorycomponents
DirectoryArticleHorizontalCard
- index.tsx
DirectoryFilter
- index.tsx
DirectoryQueryResultsSummary
- index.tsx
DirectoryResultsPerPage
- index.tsx
DirectorySearchFacets
- index.tsx
DirectorySearchPagination
- index.tsx
DirectorySortOrder
- index.tsx
DirectorySearchResults
- index.tsx
To use the added widget on the Search page, modify the code as follows:
import Head from 'next/head';import { useRouter } from 'next/router';import { useEffect, useState } from 'react';import SearchResults from '@/widgets/SearchResults';
export default function Home() { const router = useRouter(); const [keyword, setKeyword] = useState('');
useEffect(() => { if (router.isReady) { const query = router.query.q as string; setKeyword(query || ''); } }, [router.isReady, router.query.q]);
return ( <> <Head> <title>Sitecore Search: Results</title> <meta name="description" content="Generated by create next app" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/favicon.ico" /> </Head> <main> <h1 className="m-4 text-3xl">Search Results</h1> <p className="m-4 text-xl">Keyword: {keyword}</p> <SearchResults key={`${keyword}-search`} rfkId="rfkid_7" defaultKeyphrase={keyword} /> </main> </> );}
Actually, enter the keyword and execute it. At this stage, the result is 0. The error screen shows the message sorting option 'featured_desc' not configured
.
The reason is that the Sitecore Search display order does not include this featured order
setting, so the results cannot be retrieved.
Since the purpose is to make it work before creating a new one, we will change the order to title_ascending
.
export const SearchResultsComponent = ({ defaultSortType = "title_ascending", defaultPage = 1, defaultKeyphrase = "", defaultItemsPerPage = 24,}: ArticleSearchResultsProps) => { const {
After making the change, the search results were displayed.
Fixing Build Errors
The search is now working as above, but when you run the build, an error is displayed.
This is because the widget interface provided by default uses any
. We will fix the following files.
type ArticleCardItemCardProps = { className?: string; displayText?: boolean; article: any; article: { id: string; image_url?: string; url: string; name?: string; title?: string; author?: string; description?: string; source_id: string; }; onItemClick: ActionProp<ItemClickedAction>; index: number;};
const buildRangeLabel = (min: number | undefined, max: number | undefined): string => { return typeof min === 'undefined' ? `< $${max}` : typeof max === 'undefined' ? ` > $${min}` : `$${min} - $${max}`;};
interface SelectedFacet { facetId: string; facetLabel?: string; valueLabel?: string | undefined; min?: number; max?: number;}
const buildFacetLabel = (selectedFacet: any SelectedFacet) => { if ('min' in selectedFacet || 'max' in selectedFacet) { return `${buildRangeLabel(selectedFacet.min, selectedFacet.max)}`; } return `${selectedFacet.valueLabel}`;};
<SortSelect.Content className="bg-gray-100 dark:bg-gray-700 shadow-[2px_2px_4px_#CFCFCF] z-[100] absolute top-8 focus-within:border-gray-700 min-w-[150px] rounded-md"> {options.map((option: any) => ( {options.map((option: SearchResponseSortChoice) => ( <SortSelect.Option
After making the above changes, when you run the build again, this time an error occurs in the SearchResults widget. This can be resolved by making url
and source_id
required values.
type ArticleModel = { id: string; type?: string; title?: string; name?: string; subtitle?: string; url?: string; url: string; description?: string; content_text?: string; image_url?: string; source_id?: string; source_id: string;};
This resolves the error and completes the build successfully.
Creating Content Pages
In the search site, we will configure it to display the target page when clicking on the search results. For this sample site, we will prepare a page to check the information held by Sitecore Search for the target page.
Preparing the Search Results Page
To configure the page to display using the ID, we first created the following page.
import { useRouter } from 'next/router';
export default function Detail() { const router = useRouter(); const { id } = router.query;
return ( <div> <h1>Detail Page</h1> <p>ID: {id}</p> </div> );}
When accessing the URL http://localhost:3000/detail/test
, the following result is displayed.
Retrieving Search Results
This time, we will create the detail page by referring to the code of the Sitecore Search SDK. The sample code for the widget is as follows.
We have rewritten the page as follows.
import { useRouter } from 'next/router';import Image from 'next/image';import type { ArticleModel } from '@/widgets/SearchResults';import type { SearchResultsInitialState } from '@sitecore-search/react';import { FilterEqual, WidgetDataType, useSearchResults, widget } from '@sitecore-search/react';import Link from 'next/link';
type ArticleDetailProps = { id: string;};
type InitialState = SearchResultsInitialState<'itemsPerPage'>;
const ArticleDetailComponent = ({ id }: ArticleDetailProps): JSX.Element => { const { queryResult: { data: { content: articles = [] } = {} }, } = useSearchResults<ArticleModel, InitialState>({ query: (query) => { const equalFilter = new FilterEqual('id', id); query.getRequest().setSearchFilter(equalFilter); }, state: { itemsPerPage: 1, }, });
useEffect(() => { refetch(); }, [id, refetch]);
let mainArticle: ArticleModel = { id: '', title: '', url: '', source_id: '', }; if (articles.length > 0) { mainArticle = articles[0]; }
return ( <div className="m-7 flex"> <div className="flex-shrink-0"> <Image className="max-w-[300px]" src={mainArticle.image_url || '/search-horizontal-color-black-txt.svg'} alt={mainArticle.title || 'Article Image'} width={500} height={300} /> </div> <div className="ml-4"> <h1 className="mb-5 w-full text-xl font-bold text-gray-700 dark:text-gray-100"> Title: {mainArticle.name} </h1> <div className="mb-5 text-left text-gray-700 dark:text-gray-100"> Description: {mainArticle?.description} </div> <div> <Link href={mainArticle?.url || '/'} target="_blank"> <button className="float-right rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-700"> More detail </button> </Link> </div> </div> </div> );};
const ArticleDetailWidget = widget( ArticleDetailComponent, WidgetDataType.SEARCH_RESULTS, 'content');
export default function Detail() { const router = useRouter(); const { id } = router.query;
if (!id) { return <div>Loading...</div>; }
return ( <div> <h1 className="m-4 text-3xl font-bold">Detail Page</h1> <p className="m-4">ID: {id}</p> <ArticleDetailWidget id={id as string} rfkId="rfkid_7" /> </div> );}
Additionally, we added an export to ArticleModel
to use it as an interface for search results on this page.
export type ArticleModel = { id: string; type?: string; title?: string; name?: string; subtitle?: string; url: string; description?: string; content_text?: string; image_url?: string; source_id: string;};
To use the search result images in Next.js, we added the following code to the next.config.mjs
file.
// next.config.mjsconst nextConfig = { reactStrictMode: true, images: { remotePatterns: [ { protocol: "https", hostname: "doc.haramizu.com", port: "", pathname: "/**", }, ], },};
As a result of the above work, it is now possible to display the results using the ID.
Adjusting Links
To access the result page, we will add the page transition when clicking on the items displayed in PreviewSearch
and the same behavior to ArticleHorizontalCard
displayed in SearchResults
.
<PreviewSearch.Item key={article.id} asChild> <PreviewSearch.ItemLink href={article.url} onClick={() => { onItemClick({ id: article.id, index, sourceId: article.source_id, window.location.href = `/detail/${article.id}`; }} className="flex box-border no-underline w-full text-black focus:shadow-md" >
import { useRouter } from "next/router";
//
}: ArticleCardItemCardProps) => { const router = useRouter();
return (
//
<a className="focus:outline-indigo-500" href={article.url} onClick={(event) => { event.preventDefault(); onItemClick({ id: article.id, index, sourceId: article.source_id, router.push(`/detail/${article.id}`); }} >
With this, we were able to add links to the pages displaying the search results.
Summary
This time, we confirmed the steps to display the search results of Sitecore Search in a vanilla Next.js + Tailwind CSS environment. We proceeded with the steps to add widgets and display them in Next.js.
Regarding the code introduced here, there are parts where a footer is added, etc., but all the code is shared on the following site.
Also, the code used this time is running on the following site for you to experience.