Until now, the blog has used the Sitecore Search SDK to display search results. In this article, we will show you how it actually works by adding a widget to the plain Next.js environment.
Create a Next.js project
This time, we want to create a plain Next.js project, which we will call Pages Router, assuming that it can be used with Headless SXA provided by XM Cloud, and turn off Tailwind.css by default (we will turn it on later).
npx create-next-app
![searchsample01.png](/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fzi1ovlormku8%2F7prP6a79GeDGZto1WbPP9A%2F416531367853cbadc3059c35c8925b8e%2Fsearchsample01.png&w=3840&q=75)
The sample code is now ready.
Adding packages to a project
If you refer to the Sitecore Search SDK documentation, you will find instructions for installing the package on the Getting Started page.
Install @sitecore-search/react and @sitecore-search/ui. The executed code is as follows.
npm install --save @sitecore-search/react
npm install --save @sitecore-search/ui
![searchsample02.png](/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fzi1ovlormku8%2F5oe1DuJmRpjiOS3L85FcSR%2Fc74f4d1ad2299ca32e5a0d73ed37e8ca%2Fsearchsample02.png&w=3840&q=75)
Next, add values to .env to connect to Sitecore Search. In this case, we added the following to .env
NEXT_PUBLIC_SEARCH_ENV=
NEXT_PUBLIC_SEARCH_CUSTOMER_KEY=
NEXT_PUBLIC_SEARCH_API_KEY=
NEXT_PUBLIC_SEARCH_PATH=/
The above values can be found on the Devleoper Resource screen in Search. The values can be prod, staging, prodEu or apse2. For this, use the name given in the URL if it is not a subdomain.
To make this value easier to use, a file called constants/search.ts was created and the following code was written
export const SEARCH_ENV = process.env.NEXT_PUBLIC_SEARCH_ENV;
export const SEARCH_CUSTOMER_KEY = process.env.NEXT_PUBLIC_SEARCH_CUSTOMER_KEY;
export const SEARCH_API_KEY = process.env.NEXT_PUBLIC_SEARCH_API_KEY;
Implement language switching
For a single language, it is possible to omit creating the switching part, but this time, we will create it in a non-abbreviated form. First, prepare a language definition file as src/data/locales.ts as follows
export type Language = "en" | "es" | "de" | "it" | "fr" | "zh" | "da" | "ja";
export interface LanguageInfo {
country: string;
language: Language;
label: string;
}
const languages: Record<Language, LanguageInfo> = {
en: { country: "us", language: "en", label: "English" },
es: { country: "es", language: "es", label: "Español" },
de: { country: "de", language: "de", label: "Deutsch" },
it: { country: "it", language: "it", label: "Italiano" },
fr: { country: "fr", language: "fr", label: "Français" },
zh: { country: "cn", language: "zh", label: "中文" },
da: { country: "dk", language: "da", label: "Dansk" },
ja: { country: "jp", language: "ja", label: "日本語" },
};
export default languages;
Then provide a drop-down menu to switch languages. We will use React's Context mechanism to provide the language and language change so that another component can see that the language has been switched.
Create a file src/contexts/languageContext.ts defining the context. This file defines the LangageContext as follows
import { createContext } from "react";
import type { Language } from "@/data/locales";
export const LanguageContext = createContext({
language: "",
setLanguage: (l: Language) => {},
});
export interface ILanguageContext {
language: string;
setLanguage: (t: Language) => void;
}
Prepare as src/components/LocaleSelector/index.tsx using the above definition.
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);
}
}, []);
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>
);
}
Finally, create the src/hooks/useLocalStorage.ts file as the component that makes the browser remember the locale, and code it as follows
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 (error) {
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];
}
The drop-down box is now ready.
Implementation of drop-down menus
First, for a simple implementation, change src/pages/index.tsx on the top page as follows
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>
</>
);
Next, since we want to implement this menu this time so that it is available on all pages Update the file src/pages/_app.tsx as follows, using the files we have prepared so far.
import { useEffect, useState } from "react";
import type { AppProps } from "next/app";
import { LanguageContext } from "@/contexts/languageContext";
import useStorage from "@/hooks/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>
);
}
I ran the command npm run dev to get it running and now the drop down box works.
![searchsample03.gif](/_next/image?url=https%3A%2F%2Fimages.ctfassets.net%2Fzi1ovlormku8%2F6jN0tSFNJbIdpnUriJJUfR%2F5594a2853000b4a57a1caeaafcb60820%2Fsearchsample03.gif&w=3840&q=75)
Summary
This time, we have proceeded to the point of preparing an environment in which the widget can be used. In the next issue, we would like to add the widget and display the search results.