Skip to content

Latest commit

 

History

History
118 lines (92 loc) · 7.96 KB

File metadata and controls

118 lines (92 loc) · 7.96 KB

sitecore-search-nextjs-search-experience

A reusable Next.js App Router search experience built on the Sitecore Search Cloud SDK — search, keyphrase suggestions, facets, sorting, pagination, empty/error/loading states, and Search event tracking, with a clean separation between Sitecore Search configuration and UI.

This is a reference implementation, not just a demo: the goal is to show how to structure a maintainable Sitecore Search integration in a Next.js app — where SDK calls live, how to keep components testable without the SDK, and how to avoid committing credentials.

Why this exists

Sitecore's current docs for connecting Search to a Next.js app (XM Cloud / SitecoreAI) point to the Cloud SDK (@sitecore-cloudsdk/search, @sitecore-cloudsdk/events, @sitecore-cloudsdk/core) — SearchWidgetItem, getWidgetData, Context, facets, sorting, and click/view events. But most of the docs' code samples are single-file, "paste this into page.tsx" snippets. There isn't a reference showing how those pieces fit together in a real app: where the SDK calls live, how facets/sort/pagination/suggestions compose, how to keep components testable without a live Sitecore Search tenant, and how to avoid ever committing credentials.

Structure

sitecore-search-nextjs-search-experience/
│
├── app/
│   ├── layout.tsx              # mounts CloudSDKProvider once for the whole app
│   ├── page.tsx
│   └── search/
│       └── page.tsx            # server component shell, renders <SearchExperience />
│
├── components/
│   ├── CloudSDKProvider.tsx    # initializes the Cloud SDK once, client-side
│   └── search/
│       ├── SearchBox.tsx       # input + debounced suggestions dropdown
│       ├── SearchFacets.tsx    # facet checkboxes
│       ├── SearchSort.tsx      # sort dropdown
│       ├── SearchPagination.tsx
│       ├── SearchResults.tsx   # loading / error / empty / results states
│       └── SearchExperience.tsx # owns state, wires data + tracking to the above
│
├── lib/
│   └── sitecore-search/
│       ├── client.ts   # reads & validates env config — the ONLY place that reads process.env
│       ├── search.ts   # the ONLY module that imports @sitecore-cloudsdk/search
│       ├── events.ts   # the ONLY module that imports @sitecore-cloudsdk/events
│       ├── mock-data.ts # local demo data, used only when Search isn't configured
│       └── types.ts    # app-owned TypeScript models, decoupled from SDK response shapes
│
├── tests/
│   ├── lib/            # config validation, mock data, and SDK-mapping tests (SDK is mocked)
│   └── components/     # presentational component tests
│
├── .env.example
├── README.md
├── package.json
├── tsconfig.json
└── LICENSE

The separation this repo demonstrates

  • Every component under components/search/ is presentational. They take data and callbacks as props and know nothing about Sitecore Search, fetch, or the SDK. That's what makes SearchBox.test.tsx, SearchFacets.test.tsx, and SearchPagination.test.tsx possible without any SDK setup.
  • components/search/SearchExperience.tsx is the only component that touches lib/sitecore-search. It owns all search state (keyphrase, page, selected facets, sort) and coordinates data fetching + event tracking.
  • lib/sitecore-search/search.ts is the only file that imports @sitecore-cloudsdk/search. It builds the SearchWidgetItem request and maps the raw SearchEndpointResponse into this repo's own SearchExperienceResult type. If Sitecore changes the SDK's response shape, or you swap to a different search backend entirely, this is the one file that changes.
  • lib/sitecore-search/events.ts is the only file that imports @sitecore-cloudsdk/events and the SDK's search event functions (widgetItemClick, widgetFacetClick, widgetSuggestionClick, entityView, pageView).
  • lib/sitecore-search/client.ts is the only file that reads process.env for Sitecore Search settings, and validates it, throwing a clear error naming exactly which variable is missing.

Getting started

npm install
npm run dev

Without any configuration, /search runs on local demo data (lib/sitecore-search/mock-data.ts) so you can explore the UI immediately — no Sitecore Search tenant required.

Connecting a real Sitecore Search tenant

  1. Copy .env.example to .env.local.
  2. Fill in the values from Sitecore Cloud Portal → your project → Admin → API Access (Edge context ID, site name) and the Search Customer Engagement Console (CEC) → Developer Resources (widget ID, entity, suggestion block name).
  3. Restart npm run dev. lib/sitecore-search/client.ts detects the configuration and SearchExperience automatically switches from mock data to live Sitecore Search queries.

No API credentials are committed to this repo — .env.local is git-ignored, and .env.example contains only placeholders.

What each piece covers

Requirement Where
Search SearchBox (input) → SearchExperiencelib/sitecore-search/search.ts (SearchWidgetItem.query)
Suggestions SearchBox suggestions dropdown, backed by the SDK's suggestion option and a configured Suggestion Block (NEXT_PUBLIC_SITECORE_SEARCH_SUGGESTION_BLOCK)
Facets SearchFacets, backed by SearchWidgetItem.facet
Sorting SearchSort, backed by SearchWidgetItem.sort
Pagination SearchPagination, backed by SearchWidgetItem.offset / .limit
Loading / empty / error states SearchResults
Event tracking lib/sitecore-search/events.ts — page view, result click, facet click, suggestion click, entity view
TypeScript models lib/sitecore-search/types.ts
Config/UI separation see above
No committed credentials .env.example + .gitignore

A note on the Cloud SDK's real shapes

A few things the SDK does differently than you might expect from a quick skim of the docs — worth knowing if you extend this:

  • Sort responses don't include direction. getWidgetData returns sort choices as { name, label } only; ascending/descending is something you request. This repo expands each response choice into a labeled "(A–Z)" / "(Z–A)" pair — see mapSortChoices in lib/sitecore-search/search.ts.
  • Facet values use text, not label, in the raw response (FacetValueResponse.text) — mapped to this repo's label field.
  • Suggestions come back keyed by Suggestion Block name: { [blockName]: Array<{ text, freq }> }, not a flat array. You request one block via suggestion: [{ name, max }] on the same SearchWidgetItem — there's no separate suggestions widget.
  • content items in the response are typed unknown[] by the SDK, since their shape depends entirely on your Sitecore Search entity configuration. mapContentItem reads a handful of conventional fields defensively and keeps the rest in attributes.
  • Event functions take one structured params object, not positional IDs — e.g. widgetItemClick({ pathname, widgetId, entity, itemPosition, request }), where entity and request are themselves structured objects. See lib/sitecore-search/events.ts.

Testing

npm test        # run once
npm run test:watch
npm run typecheck
npm run build    # full Next.js production build

Every SDK-touching module is tested with the SDK mocked via vi.mock('@sitecore-cloudsdk/search/browser', ...), so the suite runs with no network access and no real Sitecore Search credentials. Presentational components are tested with Testing Library, independent of the SDK entirely.

Contributing

Issues and PRs welcome. Please add or update tests for any behavior change, and run npm test, npm run typecheck, and npm run build before submitting.

License

MIT