Skip to main content

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>
);
}