Your storefront
Run a shop on the home store in a few minutes, with the search box, listing and product pages of the React components.
From an empty folder to a shop with search, filters and product pages, in six files. You need the stack with the home store from the Quickstart, and every command runs from the repository root.
The two packages, @orbsearch/client and @orbsearch/react, are not on npm yet. So the shop lives inside the repository's pnpm workspace, as the demo storefront in apps/demo does, and uses React Router.
Create the app
Make the folder apps/shop with its manifest and its Vite config:
{
"name": "shop",
"private": true,
"type": "module",
"dependencies": {
"@orbsearch/client": "workspace:*",
"@orbsearch/react": "workspace:*",
"@react-router/node": "catalog:",
"isbot": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"react-router": "catalog:"
},
"devDependencies": {
"@react-router/dev": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"vite-plus": "catalog:"
}
}import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite-plus";
export default defineConfig({ plugins: [reactRouter()] });catalog: takes the versions the whole workspace uses.
Make a client per page load
Every search of one page load names the same page view, which Insights counts queries by. So the shop makes a client per request on the server, and one in the browser:
import { } from "@orbsearch/client";
const = "http://127.0.0.1:7800";
// One client per page load: the server's for each request, the browser's with the page view the server named.
export function (: string, ?: string | null) {
return ({ , , ...( && { }) });
}The browser asks the same address for suggestions and "Load more", so it must reach the Query API too. It needs no key.
Answer every path
One route answers every path. It asks resolve what the path is, a listing, a product, a redirect or nothing, and renders the page for it:
import { route, type RouteConfig } from "@react-router/dev/routes";
export default [route("*", "routes/shop.tsx")] satisfies RouteConfig;import { isListing, locationOf } from "@orbsearch/client";
import { ListingPage, ProductPage, listingView } from "@orbsearch/react";
import { data, redirect, useLoaderData, type LoaderFunctionArgs } from "react-router";
import { searchFor } from "../search.ts";
export async function loader({ request, url }: LoaderFunctionArgs) {
const pageView = crypto.randomUUID();
const search = searchFor(pageView, request.headers.get("user-agent"));
const page = await search.resolve(url.pathname + url.search, { locale: "de" }, { signal: request.signal });
const status = { status: page.status };
if (isListing(page)) {
const listing = listingView(page, { path: url.pathname, locale: "de" });
return data({ kind: "listing" as const, listing, pageView }, status);
}
if (page.product) {
return data({ kind: "product" as const, product: page.product, path: url.pathname, pageView }, status);
}
const location = locationOf(page);
if (location !== null) throw redirect(location, page.status);
throw data(null, status);
}
export default function Shop() {
const shown = useLoaderData<typeof loader>();
if (shown.kind === "product") {
return <ProductPage product={shown.product} trail={[{ title: shown.product.title, href: shown.path }]} />;
}
return <ListingPage listing={shown.listing} />;
}The loader keeps the status resolve gives: a moved URL answers 301, a URL removed on purpose 410, an unknown one 404. A shop's own pages, such as its cart, come before the catch-all in routes.ts.
Lay out the root
The root holds the provider and the search box on every page, and loads the components' styles:
import { SearchBox, StorefrontProvider } from "@orbsearch/react";
import "@orbsearch/react/orbsearch.css";
import { useMemo, type ReactNode } from "react";
import { Links, Meta, Outlet, Scripts, useLocation, useNavigate, useRouteLoaderData } from "react-router";
import type { loader } from "./routes/shop.tsx";
import { searchFor } from "./search.ts";
export function Layout({ children }: { children: ReactNode }) {
const navigate = useNavigate();
const location = useLocation();
const shown = useRouteLoaderData<typeof loader>("routes/shop");
const pageView = shown?.pageView;
// The browser's searches count as the page load the server answered.
const search = useMemo(() => searchFor(pageView ?? crypto.randomUUID()), [pageView]);
const listing = shown?.kind === "listing" ? shown.listing : undefined;
return (
<html lang="de">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<StorefrontProvider
search={search}
locale="de"
navigate={(href, { replace, keepScroll } = {}) =>
void navigate(href, {
...(replace && { replace }),
...(keepScroll && { preventScrollReset: true }),
})
}
>
<header>
<SearchBox action="/suche" query={listing?.from ?? listing?.query ?? ""} at={location.key} />
</header>
<main>{children}</main>
</StorefrontProvider>
<Scripts />
</body>
</html>
);
}
export default function App() {
return <Outlet />;
}/suche is the home store's search page (suche: search). navigate hands every filter, chip and sort to React Router, so a choice is a navigation to the next URL.
Start the shop
Install the workspace's new member and start it:
vp install
vp -C apps/shop devOpen localhost:5173/suche?q=graues sofa. The search for "graues Sofa" (grey sofa) lands on the sofa page /wohnen/sofas with grey chosen, two sofas, the banner the shop's rule places and a filter bar. The home store's photos point at a placeholder host, so their frames stay empty.
What you have
- Every page renders on the server, so it arrives with its results and works before the script loads. Search, filters, sort and paging are links and GET forms underneath.
- Every URL is the merchant's. A category, a product and the search page sit at the paths the catalog and the release give them (URLs and redirects).
- Clicks and views are reported for Insights by the components themselves (Clicks and views).
listingView reads resolve's answer into the view ListingPage draws. What only your shop knows, such as your home in the trail and your category navigation, is empty in it and yours to set (React components).
Next
- Customize: give the shop your look and your own parts.
- React components: every part and what it takes.
- Recipes: one facet in a layout of your own, filters in a sheet on a phone.