Smart Collection Usage
This page outlines how to implement and customize the usage of Smart Collections within Shopify Hydrogen using Fast Simon's capabilities.
Implementation on file \app\routes\collections.$handle.tsx
The Smart Collection integration can be configured and customized in this file to fetch, display, and manipulate collections dynamically.
Configuration
getSmartCollection
function
To customize the behavior of the getSmartCollection
function, you can pass the specs
parameter to control various aspects of the collection retrieval process.
Parameters
categoryURL
- The URL of the category you want to fetch (String)page
- The page number for pagination (Number)narrow
- Filtered data (Narrow)facetsRequired
- Whether facets are required in the response (Boolean)productsPerPage
- The number of products per page (Number)categoryID
- The category ID (Boolean)sortBy
- Sorting option (Sorting)searchWithinResultsQuery
- Internal search term within the category products (String)
Return Object
The function returns a promise of an object type: SmartCollectionResponse.
Usage Example
Imports
import {CacheLong} from '@shopify/hydrogen';
import {transformToShopifyStructure,PaginationBar} from '@fast-simon/storefront-kit';
import {Narrow} from '@fast-simon/utilities';
... // additional imports
Loader Function
export async function loader(args: LoaderFunctionArgs) {
// Start fetching non-critical data without blocking time to first byte
const deferredData = loadDeferredData(args);
// Await the critical data required to render initial state of the page
const criticalData = await loadCriticalData(args);
const facets = {
facets: criticalData.collection.getFacetsOnly
? criticalData.collection.getFacetsOnly()
: criticalData.collection.facets,
};
const dashboardConfig = {
dashboardConfig: args.context.fastSimon.getDashboardConfig({
cacheStrategy: CacheLong(),
}),
};
return defer({...criticalData, ...facets, ...dashboardConfig});
}
Critical Data Loading
async function loadCriticalData({
context,
params,
request,
}: LoaderFunctionArgs) {
const {handle} = params;
const {fastSimon} = context;
if (!handle) {
throw redirect('/collections');
}
const url = new URL(request.url);
const page = Number(url.searchParams.get('page')) || 1;
const narrowString = url.searchParams.get('filters');
const sortBy = url.searchParams.get('sort');
const narrow = narrowString
? Narrow.toServerNarrow(Narrow.parseNarrow(narrowString || ''))
: [];
const collection = await fastSimon.getSmartCollection({
props: {
categoryURL: '/collections/' + handle,
page,
narrow,
facetsRequired: true,
productsPerPage: 20,
categoryID: undefined,
sortBy,
},
});
if (!collection) {
throw new Response(`Collection ${handle} not found`, {
status: 404,
});
}
const transformed = transformToShopifyStructure(collection.items);
collection.products = transformed.products;
collection.handle = collection.category_url.split('/')[1];
collection.title = collection.category_name;
collection.description = collection.category_description;
return {
collection,
};
}
... // more functions and logic
Display Collection
export default function Collection() {
const {collection, facets, dashboardConfig} = useLoaderData<typeof loader>();
return (
<div className="collection">
<h1>{collection.title}</h1>
<p className="collection-description">{collection.description}</p>
<div className={'results-filters-container'}>
<div className={'fs-products-summary'}>
<ProductsGrid products={collection.products.nodes} />
</div>
</div>
<br />
<PaginationBar total={collection.total_p} />
</div>
);
}
function ProductsGrid({products}) {
return (
<div className="products-grid">
{products.map((product, index) => {
return (
<ProductItem
key={product.id}
product={product}
loading={index < 8 ? 'eager' : undefined}
/>
);
})}
</div>
);
}
function ProductItem({
product,
loading,
}: {
product: ProductItemFragment;
loading?: 'eager' | 'lazy';
}) {
const variant = product.variants.nodes[0];
const variantUrl = useVariantUrl(
product.handle,
variant.selectedOptions,
).replace('?=', '');
return (
<Link
className="product-item"
key={product.id}
prefetch="intent"
to={variantUrl}
>
{product.featuredImage && (
<Image
alt={product.featuredImage.altText || product.title}
aspectRatio="0.714"
data={product.featuredImage}
loading={loading}
sizes="(min-width: 45em) 400px, calc(50vw - 2rem)"
/>
)}
<h4>{product.title}</h4>
<small>
<Money data={product.priceRange.minVariantPrice} />
</small>
</Link>
);
}