Inhaltsverzeichnis
- Verwendeter Stack
- RefinementList Widget
- NPM Pakete
- Hauptkomponente für Algoliasuche
- Anpassung Suchekomponente im Headerbereich
- Kompletter Code nach der Anpassung
- Integration von HighLight und Snippet in die CardAlgolia-Komponente
- Kompletter Code
- Algolia Configuration
- Highlight Attributes
- Snippet Attributes
- Cloudapp-dev und bevor Sie uns verlassen
In der letzten Geschichte haben wir die nativen Algolia Widgets für React (Hits, SearchBox, RefinementList und DynamicWidgets) verwendet. Jetzt werden wir uns die letzten beiden ("HighLight" und "Snippet") genauer ansehen, die wir im ersten Schritt nicht verwendet haben.
Hier ist das GitHub Repo mit dem gesamten Code und darunter der Link zur Beispielwebsite.
Verwendeter Stack
Ich werde mit meinem Standard-Stack beginnen:
Next.js 14 als Web-Framework, und ich werde die mitgelieferte Middleware Edge-Funktion verwenden
TailwindCss for Styling
Contentful CMS (Kostenloses Abo)
Algolia als Such-Engine
Vercel für das Hosting
RefinementList Widget
Im letzten Post habe ich ein Detail vergessen.Der Parameter attribute={attribute} definiert das verwendete Attribut. In unserem Fall haben wir "tags" verwendet. limit definiert die maximale Anzahl der visualisierten Verfeinerungseinträge (tags). showmore definiert, ob wir den "ShowMore" Button anzeigen wollen oder nicht, wenn es mehr Einträge gibt, als wir in limit angegeben haben und showMoreLimit definiert die maximale Anzahl von Verfeinerungseinträgen, die visualisiert werden.
//src/components/search/algoliasearch.component.tsx
function FallbackComponent({ attribute }: { attribute: string }) {
return (
<Panel header={attribute}>
<RefinementList
classNames={{
root: "MyCustomRefinementList",
}}
attribute={attribute}
limit={8}
showMore={true}
showMoreLimit={20}
/>
</Panel>
);
}NPM Pakete
Wir brauchen kein neues Paket, da wir bereits das Npm-Paket "react-instantsearch" installiert haben, das alles enthält, was wir brauchen. Jetzt brauchen wir das Highlight- und Snippet-Widget aus diesem Paket.
Hauptkomponente für Algoliasuche
Wir können beide Importe aus der Hauptkomponente von Algolia entfernen, so dass nur Hits, SearchBox, RefinementList und DynamicWidgets übrig bleiben. Der Rest des Codes ist in Ordnung.
//src/components/serach/algoliasearch.component.tsx
"use client";
import algoliasearch from "algoliasearch/lite";
import { Hit as AlgoliaHit } from "instantsearch.js";
import {
Hits,
SearchBox,
RefinementList,
DynamicWidgets,
} from "react-instantsearch";
import { PageBlogPostFieldsFragment } from "@/lib/__generated/sdk";
import { InstantSearchNext } from "react-instantsearch-nextjs";
import { Panel } from "@/components/search/panel.component";
import { useParams, useSearchParams } from "next/navigation";
import type { LocaleTypes } from "@/app/i18n/settings";
import CardAlgolia from "./cardalgolia.component";
interface ArticleAuthorProps {
article: PageBlogPostFieldsFragment;
}
const client = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || "",
process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || ""
);
type CardProps = {
hit: AlgoliaHit<{
intName: string;
image: string;
pubdate: Date;
slug: string;
width: number;
height: number;
tags: string[];
lang: {
"de-DE": { content: string; shortDescription: string; title: string };
"en-US": { content: string; shortDescription: string; title: string };
};
}>;
};
let locale: LocaleTypes;
function Hit({ hit }: CardProps) {
const blurURL = new URL(hit.image);
blurURL.searchParams.set("w", "10");
return (
<div>
<CardAlgolia key={hit.objectID} result={hit} />
</div>
);
}
export default function Search() {
const urlSearchParams = useSearchParams();
const params = Object.fromEntries(urlSearchParams.entries());
locale = useParams()?.locale as LocaleTypes;
return (
<InstantSearchNext
searchClient={client}
indexName={process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME}
routing={{
router: {
cleanUrlOnDispose: false,
windowTitle(routeState) {
const indexState = routeState.indexName || {};
return indexState.query
? `MyWebsite - Results for: ${indexState.query}`
: "MyWebsite - Results page";
},
},
}}
future={{
preserveSharedStateOnUnmount: true,
}}
>
<div className="Container mx-4">
<div>
<DynamicWidgets fallbackComponent={FallbackComponent} />
</div>
<div>
<SearchBox className="p-3 shadow-sm" />
<Hits
hitComponent={Hit}
classNames={{
root: "MyCustomHits",
}}
/>
</div>
</div>
</InstantSearchNext>
);
}
function FallbackComponent({ attribute }: { attribute: string }) {
return (
<Panel header={attribute}>
<RefinementList
classNames={{
root: "MyCustomRefinementList",
}}
attribute={attribute}
limit={8}
showMore={true}
showMoreLimit={20}
/>
</Panel>
);
}Anpassung Suchekomponente im Headerbereich
Hier ändern wir nur eine Zeile, damit wir eine Umgebungsvariable für den Indexnamen verwenden können.
const searchParams = `${process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME}[query]`;Kompletter Code nach der Anpassung
//src/components/header/search.component.tsx
"use client";
import { useState, useEffect } from "react";
import { useRouter, usePathname, useParams } from "next/navigation";
import type { LocaleTypes } from "@/app/i18n/settings";
import { fallbackLng } from "@/app/i18n/settings";
import { useTranslation } from "@/app/i18n/client";
export default function SearchBar({
searchCta,
searchPlaceholder,
}: {
searchCta: string;
searchPlaceholder: string;
}) {
const [search, setSearch] = useState("");
const router = useRouter();
const path = usePathname();
const locale = useParams()?.locale as LocaleTypes;
const { t } = useTranslation(locale, "common");
const pathWithoutQuery = path.split("?")[0];
let pathArray = pathWithoutQuery.split("/");
pathArray.shift();
pathArray = pathArray.filter((path) => path !== "");
// if the path is searchalgolia, don't show the search bar
if (pathArray[0] === "searchalgolia") {
return null;
}
function handleSubmit(e: any) {
e.preventDefault(); // prevent page refresh
if (!search) return; // if there is no search, return
const searchParams = `${process.env.NEXT_PUBLIC_ALGOLIA_INDEX_NAME}[query]`;
if (locale === fallbackLng) {
router.push(
`/searchalgolia?${encodeURIComponent(searchParams)}=${search}`
);
} else {
router.push(
`/${locale}/searchalgolia?${encodeURIComponent(searchParams)}=${search}`
);
}
}
return (
<div className="mb-1 bg-gray-100 rounded-sm shadow-md dark:bg-gray-800">
<form onSubmit={handleSubmit}>
<label
htmlFor="default-search"
className="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white"
>
{t("search.button")}
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<svg
className="w-4 h-4 text-gray-500 dark:text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 20"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"
/>
</svg>
</div>
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
// type="text"
type="search"
id="default-search"
className="block w-full p-4 pl-10 text-base text-gray-900 border border-gray-300 bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder={t("search.searchPlaceholder")}
// {searchPlaceholder ? searchPlaceholder : "Search keywords..."}
required
/>
<button
disabled={!search} // disable the button if there is no search
type="submit"
className="text-white absolute right-2.5 bottom-2.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-base px-4 py-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
{t("search.button")}
{/* {searchCta ? searchCta : Search} */}
</button>
</div>
</form>
</div>
);
}Integration von HighLight und Snippet in die CardAlgolia-Komponente
Als Erstes importieren wir die Widgets
import { Hit as AlgoliaHit } from "instantsearch.js";
import { Highlight, Snippet } from "react-instantsearch";dann ersetzen wir die alte Interface-Deklaration durch eine neue Type-Deklaration, in der wir "AlgoliaHit" verwenden, das ebenfalls importiert wurde.
//old
interface CardProps {
result: {
intName: string;
image: string;
pubdate: Date;
slug: string;
width: number;
height: number;
tags: string[];
lang: {
"de-DE": { content: string; shortDescription: string; title: string };
"en-US": { content: string; shortDescription: string; title: string };
};
};
}
//new
type CardProps = {
result: AlgoliaHit<{
intName: string;
image: string;
pubdate: Date;
slug: string;
width: number;
height: number;
tags: string[];
lang: {
"de-DE": { content: string; shortDescription: string; title: string };
"en-US": { content: string; shortDescription: string; title: string };
};
}>;
};Nun können wir die Widgets verwenden
//old
{langresult.title}
//new
<Highlight attribute="intName" hit={result} />
//old
{langresult.shortDescription}
//new
<Snippet attribute="intName" hit={result} />Kompletter Code
//src/components/search/cardalgolia.component.tsx
"use client";
import Image from "next/image";
import Link from "next/link";
import { FormatDate } from "@/components/contentful/format-date/FormatDate";
import { useParams } from "next/navigation";
import type { LocaleTypes } from "@/app/i18n/settings";
import { twMerge } from "tailwind-merge";
import { ArticleLabel } from "@/components/contentful/ArticleLabel";
import { Hit as AlgoliaHit } from "instantsearch.js";
import { Highlight, Snippet } from "react-instantsearch";
type CardProps = {
result: AlgoliaHit<{
intName: string;
image: string;
pubdate: Date;
slug: string;
width: number;
height: number;
tags: string[];
lang: {
"de-DE": { content: string; shortDescription: string; title: string };
"en-US": { content: string; shortDescription: string; title: string };
};
}>;
};
interface LangProps {
title: string;
content: string;
shortDescription: string;
}
export default function CardAlgolia({ result }: CardProps) {
const locale = useParams()?.locale as LocaleTypes;
const langNr = locale === "de-DE" ? 0 : 1;
const langresult = JSON.parse(
JSON.stringify(Object.entries(result.lang)[langNr][1])
) as LangProps;
const className = "md:grid-cols-2 lg:grid-cols-3";
const classNameImage = "object-cover aspect-[16/10] w-full";
const blurURL = new URL(result.image);
blurURL.searchParams.set("w", "10");
return (
// {/* group - wird benötigt damit man unten im Classname darauf verweisen kann mit group-hover:.... */}
<div className="flex flex-col">
<div
className={twMerge(
"flex flex-1 flex-col overflow-hidden dark:shadow-white shadow-lg dark:shadow-sm-light",
className
)}
>
<Link href={`/${locale}/${result.slug}`}>
<Image
src={result.image}
width={result.width || 722}
height={result.height || 590}
sizes="(max-width: 1200px) 100vw, 50vw"
placeholder="blur"
blurDataURL={blurURL.toString()}
alt={langresult.title || ""}
className={twMerge(classNameImage, "transition-all")}
></Image>
</Link>
<div className="flex flex-col flex-1 px-4 py-3 dark:bg-gray-800 md:px-5 md:py-4 lg:px-7 lg:py-5">
{langresult.title && (
<Link href={`/${locale}/${result.slug}`}>
<p className="mb-2 h3 line-clamp-2 text-gray-800 dark:text-[#AEC1CC] md:mb-3">
{/* {langresult.title} */}
<Highlight attribute="intName" hit={result} />
</p>
</Link>
)}
{langresult.shortDescription && (
<p className="mt-2 text-base line-clamp-2">
{/* {langresult.shortDescription} */}
<Snippet attribute="intName" hit={result} />
</p>
)}
<div className="flex flex-wrap max-w-2xl gap-2 mr-auto">
{result.tags.map((tag: string, index) => (
<Link href={`/${locale}/search/${tag}`} key={index}>
<ArticleLabel className="flex items-center ml-1">
{tag}
</ArticleLabel>
</Link>
))}
<div className={twMerge("ml-auto pl-2 text-xs text-gray-600")}>
<FormatDate date={result.pubdate} />
</div>
</div>
</div>
</div>
</div>
);
}Algolia Configuration
We have to adapt the Algolia Index Configuration so that the widgets show the data.
Highlight Attributes
Hier können wir die Standardeinstellung verwenden, so dass alle als durchsuchbar markierten Attribute hervorgehoben werden.

Snippet Attributes
Wählen Sie das Attribut für das Snippet. ShortDescription oder Description sind Standardwerte, die Sie verwenden können oder sollten.

Cloudapp-dev und bevor Sie uns verlassen
Danke, dass Sie bis zum Ende gelesen haben. Noch eine Bitte bevor Sie gehen:



