import Head from 'next/head' import Image from 'next/image' import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter"; import { InstantSearch, SearchBox, Hits, HierarchicalMenu, RefinementList, ToggleRefinement, RatingMenu, ClearRefinements, Stats, HitsPerPage, SortBy, Pagination, } from 'react-instantsearch-dom' import {RangeSlider} from "../components/RangeSlider"; import {Hit} from "../components/Hit"; import {assembleTypesenseServerConfig} from '../lib/utils' import {findResultsState} from 'react-instantsearch-dom/server'; // Initialize the Typesense Instantsearch adapter: https://github.com/typesense/typesense-instantsearch-adapter const TYPESENSE_SERVER_CONFIG = assembleTypesenseServerConfig() const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({ server: TYPESENSE_SERVER_CONFIG, additionalSearchParameters: { // The following parameters are directly passed to Typesense's search API endpoint. // So you can pass any parameters supported by the search endpoint below. // queryBy is required. queryBy: 'name,categories,description', queryByWeights: '4,2,1', numTypos: 1, typoTokensThreshold: 1, // groupBy: "categories", // groupLimit: 1 // pinnedHits: "23:2" }, }); export default function Home({ searchClient = typesenseInstantsearchAdapter.searchClient, searchState, resultsState, onSearchParameters, widgetsCollector, ...props }) { return ( <div> <Head> <title>Ecommerce Store with Typesense + Next.js + Vercel</title> <link rel="icon" href="/favicon.png"/> </Head> <main> <InstantSearch indexName="products" {...props} searchClient={searchClient} searchState={searchState} resultsState={resultsState} onSearchParameters={onSearchParameters} widgetsCollector={widgetsCollector}> <div className="container-fluid px-md-5 pt-4"> <div className="row d-flex align-items-center"> <div className="col-md"> <h1 className="display-6"> Ecommerce Store with Typesense + Next.js + Vercel </h1> </div> <div className="col-md-2 d-none d-md-block"> <SearchBox/> </div> </div> <div className="lead mt-2"> Besides search experiences, Typesense can also be used to build <strong className="marker-highlight">blazing fast</strong>, <strong className="marker-highlight">browsing experiences</strong> like product listing pages in an ecommerce store. </div> <ul className="lead mt-1"> <li>Product data to render the grid is fetched by the front-end from a <strong>Geo-Distributed Typesense Cloud cluster</strong> with nodes in Oregon, Frankfurt and Mumbai. </li> {/* eslint-disable-next-line react/no-unescaped-entities */} <li>Product API Requests are routed to the node that is closest to the user's location, like a CDN. Since data is geographically distributed, this reduces latency even more for your users, as they browse products. </li> <li>The front-end uses Next.js, is statically generated and is hosted on Vercel.</li> <li>See <a href="https://github.com/typesense/showcase-nextjs-typesense-ecommerce-store" target="_blank" rel="noreferrer">Source Code</a>. </li> </ul> <div className="row mt-4"> <div className="col-md-3 pr-md-5"> <h5>Browse by Categories</h5> <HierarchicalMenu className="mt-3" attributes={[ 'categories.lvl0', 'categories.lvl1', 'categories.lvl2', 'categories.lvl3', ]} showParentLevel={true} rootPath={"Cell Phones"} limit={50} /> <h5 className="mt-5">Filter by Brands</h5> <RefinementList className="mt-3" attribute="brand" limit={10} showMore={true} showMoreLimit={50} searchable={true} transformItems={items => items.sort((a, b) => a.label > b.label ? 1 : -1) } /> <div className="mt-2"> </div> <ToggleRefinement className="mt-5" attribute="free_shipping" label="Free Shipping" value={true} /> <div className="mt-1"> </div> <h5 className="mt-5">Filter by Price</h5> <RangeSlider attribute="price"/> <div className="mt-1"> </div> <h5 className="mt-5">Filter by Rating</h5> <RatingMenu className="mt-3" attribute="rating" /> <div className="mt-1"> </div> <ClearRefinements className="mt-5"/> </div> <div className="col-md"> <div className="row mt-5 mt-md-0"> <div className="col-md"> <div className="row"> <div className="col-md-4"> </div> <div className="col-md-8 d-flex justify-content-end align-items-center"> <Stats translations={{ stats(nbHits, processingTimeMS) { let hitCountPhrase if (nbHits === 0) { hitCountPhrase = 'No products' } else if (nbHits === 1) { hitCountPhrase = '1 product' } else { hitCountPhrase = `${nbHits.toLocaleString()} products` } return `${hitCountPhrase} found in ${processingTimeMS.toLocaleString()}ms`; }, }}/> <HitsPerPage className="ms-4" items={[ {label: '9 per page', value: 9}, {label: '18 per page', value: 18} ]} defaultRefinement={9} /> <SortBy items={[ {label: 'Relevancy', value: 'products'}, {label: 'Price (asc)', value: 'products/sort/price:asc'}, {label: 'Price (desc)', value: 'products/sort/price:desc'}, ]} defaultRefinement="products" /> </div> </div> </div> </div> <div className="row mt-1"> <div className="col-sm"> <Hits hitComponent={Hit}/> </div> </div> <div className="row"> <div className="col-sm"> <Pagination/> </div> </div> </div> </div> </div> </InstantSearch> </main> </div> ) } export async function getServerSideProps({res}) { res.setHeader("Cache-Control", `s-maxage=${1 * 60 * 60}, stale-while-revalidate=${24 * 60 * 60}`); const resultsState = await findResultsState(Home, { indexName: "products", searchClient: typesenseInstantsearchAdapter.searchClient }); return { props: { resultsState: JSON.parse(JSON.stringify(resultsState)), } } }