All Posts
EngineeringMay 27, 2026

Typesense on Shopify Hydrogen: Headless Search Architecture (2026)

Shopify's built in Storefront API search works fine for catalogs under roughly 5,000 SKUs and shoppers who arrive with a clear query in mind. Once you cross 10,000 SKUs, add faceted filtering on more than three attributes, or need ranking customization (boost in stock items, demote slow movers, surface new arrivals on certain queries), the native search path stops being sufficient. The enterprise Hydrogen storefronts we work on at Contra Collective almost always reach for a dedicated search index by the time the catalog gets serious.

Shopify's built in Storefront API search works fine for catalogs under roughly 5,000 SKUs and shoppers who arrive with a clear query in mind. Once you cross 10,000 SKUs, add faceted filtering on more than three attributes, or need ranking customization (boost in stock items, demote slow movers, surface new arrivals on certain queries), the native search path stops being sufficient. The enterprise Hydrogen storefronts we work on at Contra Collective almost always reach for a dedicated search index by the time the catalog gets serious.

Typesense is the open source choice that wins most often in our evaluations. The reasons are operational: a single binary, no JVM, sub 50 ms p95 latency on million document catalogs, and a typed schema model that maps cleanly onto Shopify product data. Algolia is faster to set up but costs more at scale; Elasticsearch is more flexible but operationally heavier (see our comparison of Typesense, Elasticsearch, and OpenSearch for commerce for the full breakdown).

This is the architecture, the sync pipeline, the Hydrogen integration pattern, and the tradeoffs that matter for production deployments.

The Architecture

The integration has three moving parts: the Typesense cluster (or single node), the sync layer that keeps Typesense in step with Shopify's product catalog, and the Hydrogen storefront that queries Typesense at request time.

The Typesense cluster sits as the source of truth for search relevant product data. Shopify remains the source of truth for product creation, inventory, pricing, and order processing. The sync layer is responsible for translating Shopify product events into Typesense documents and pushing updates as they happen.

The Hydrogen storefront makes search requests directly from the React Router loader (or Remix loader in older Hydrogen versions) to the Typesense API, not through Shopify. This is the key architectural choice: search queries bypass Shopify entirely, which removes the Storefront API rate limit ceiling from the search hot path and cuts p95 latency by 200 to 400 ms.

[Shopify Admin]
      |
      | webhooks (products/create, products/update, products/delete,
      |          inventory_levels/update, collections/update)
      v
[Sync Worker (Cloudflare Workers / Cloud Run)]
      |
      | upsert / delete documents
      v
[Typesense Cluster]
      ^
      | search queries via JS client
      |
[Hydrogen Storefront (server side loaders + client side instantsearch)]

The Sync Pipeline

The sync pipeline is the part that breaks most often in production, so it deserves more design attention than the search query side.

Three trigger paths feed Typesense from Shopify:

Initial backfill. Use the Shopify Admin GraphQL API with bulk operations to pull the full product catalog at setup. A 50,000 SKU catalog with full metafield expansion takes roughly 8 to 15 minutes through the bulk operations API. Write the result to S3 or GCS, then stream it into Typesense via the import endpoint (Typesense handles 50,000 documents in batches of 10,000 in under 60 seconds on a 2 vCPU node).

Webhook driven incremental updates. Subscribe to products/create, products/update, products/delete, inventory_levels/update, and collections/update webhooks. Route them to a sync worker (we run these on Cloudflare Workers with a Durable Object for deduplication, or Cloud Run with Cloud Tasks for retry semantics). Each webhook gets normalized into a Typesense upsert or delete.

Periodic reconciliation. Webhooks fail. Shopify has documented webhook delivery SLA of best effort, and we see roughly 0.3 percent webhook loss in production over a 30 day window. Run a nightly reconciliation job that walks the full catalog through the Admin API, computes a checksum per product, and reissues upserts for any drift. This catches the long tail of missed webhooks before they show up as stale data in search results.

The sync worker normalization is where business logic lives. A Shopify product with multiple variants, multiple images, metafields, and collection memberships becomes a single Typesense document with denormalized fields:

type TypesenseProduct = {
  id: string;
  handle: string;
  title: string;
  description: string;
  vendor: string;
  product_type: string;
  tags: string[];
  collection_handles: string[];
  variant_count: number;
  price_min: number;
  price_max: number;
  available: boolean;
  inventory_total: number;
  featured_image: string;
  created_at: number;
  updated_at: number;
  sales_rank: number; // computed, updated nightly from order data
  search_keywords: string[]; // editorial, from a separate admin tool
};

Denormalization is the right default. Joins do not exist in Typesense (or in any search engine that you would deploy for commerce). Anything you want to filter, facet, or sort by needs to be a field on the document.

Typesense Schema for Shopify Products

The Typesense collection schema declares field types, faceting, sorting, and search weighting up front. Get this wrong and you pay for it later through reindex cycles.

{
  "name": "products",
  "fields": [
    {"name": "id", "type": "string"},
    {"name": "handle", "type": "string"},
    {"name": "title", "type": "string"},
    {"name": "description", "type": "string"},
    {"name": "vendor", "type": "string", "facet": true},
    {"name": "product_type", "type": "string", "facet": true},
    {"name": "tags", "type": "string[]", "facet": true},
    {"name": "collection_handles", "type": "string[]", "facet": true},
    {"name": "price_min", "type": "float", "facet": true, "sort": true},
    {"name": "price_max", "type": "float"},
    {"name": "available", "type": "bool", "facet": true},
    {"name": "inventory_total", "type": "int32"},
    {"name": "featured_image", "type": "string", "index": false},
    {"name": "created_at", "type": "int64", "sort": true},
    {"name": "updated_at", "type": "int64", "sort": true},
    {"name": "sales_rank", "type": "int32", "sort": true},
    {"name": "search_keywords", "type": "string[]"}
  ],
  "default_sorting_field": "sales_rank",
  "token_separators": ["-", "_", "/"],
  "symbols_to_index": ["+", "&"]
}

A few choices worth calling out:

token_separators controls how Typesense splits product titles like iPhone 15 Pro Max 256GB into searchable tokens. Without proper separators, the query 15 pro may not match. The defaults are conservative; tune them for your catalog naming conventions.

symbols_to_index lets you make & and + searchable, which matters for brand names like Dolce & Gabbana or product names like C++. Most commerce catalogs ignore this until the first support ticket arrives.

The search_keywords field is editorial. Merchandisers add synonyms or alternative terms through an admin interface; the sync layer pushes them into Typesense. This is how you handle the sneakers vs trainers problem without retraining the search engine.

Hydrogen Integration

The query side runs from Hydrogen loaders. Use the typesense-instantsearch-adapter for the client side search experience (InstantSearch React components) and the raw Typesense JS client for server side data fetches in loaders.

Server side product list with facets:

// app/routes/search.tsx
import {Typesense} from 'typesense';
import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';

const client = new Typesense.Client({
  nodes: [{host: env.TYPESENSE_HOST, port: 443, protocol: 'https'}],
  apiKey: env.TYPESENSE_SEARCH_KEY, // search-only API key
  connectionTimeoutSeconds: 2,
});

export async function loader({request}: LoaderFunctionArgs) {
  const url = new URL(request.url);
  const query = url.searchParams.get('q') ?? '*';
  const collection = url.searchParams.get('collection');
  const minPrice = url.searchParams.get('min_price');
  const maxPrice = url.searchParams.get('max_price');

  const filterParts = ['available:true'];
  if (collection) filterParts.push(`collection_handles:=${collection}`);
  if (minPrice || maxPrice) {
    filterParts.push(
      `price_min:[${minPrice ?? '0'}..${maxPrice ?? '999999'}]`,
    );
  }

  const results = await client.collections('products').documents().search({
    q: query,
    query_by: 'title,vendor,product_type,tags,search_keywords,description',
    query_by_weights: '8,4,3,3,5,1',
    filter_by: filterParts.join(' && '),
    facet_by: 'vendor,product_type,tags,price_min,available',
    sort_by: '_text_match:desc,sales_rank:desc',
    per_page: 24,
    page: parseInt(url.searchParams.get('page') ?? '1'),
  });

  return json({results});
}

The query_by_weights is the ranking lever that drives perceived search quality. Title matches outweigh tag matches outweigh description matches. search_keywords sits at weight 5 because merchandiser added synonyms should rank highly when they match. Tune these against your real query logs.

Use a search only API key for the public storefront (TYPESENSE_SEARCH_KEY). The admin key with write access never leaves the sync worker.

The instantsearch experience on the client uses the same query path, just routed through the React adapter:

import {InstantSearch, SearchBox, Hits, RefinementList} from 'react-instantsearch';
import TypesenseInstantSearchAdapter from 'typesense-instantsearch-adapter';

const adapter = new TypesenseInstantSearchAdapter({
  server: {
    apiKey: env.TYPESENSE_SEARCH_KEY,
    nodes: [{host: env.TYPESENSE_HOST, port: 443, protocol: 'https'}],
  },
  additionalSearchParameters: {
    query_by: 'title,vendor,product_type,tags,search_keywords,description',
    query_by_weights: '8,4,3,3,5,1',
  },
});

Operational Tradeoffs

Self host or managed. Typesense Cloud is the managed option (roughly $40 per month for a 1 vCPU, 2GB cluster, scaling up to hundreds per month for HA multi region). Self hosting on a single VM is cheaper and works fine for catalogs under 500,000 documents, but you eat the operational overhead (backups, version upgrades, monitoring, replica failover). For enterprise commerce, Typesense Cloud is usually worth the line item.

Indexing latency. Webhook driven updates land in Typesense within roughly 200 ms of the Shopify event, which is fast enough that inventory changes show in search results within a single user session. Bulk operations (full reindex) take longer; budget a 60 second window for a 100,000 document collection.

Sync worker reliability. The sync worker is single point of failure for index freshness. Run it on a platform with built in retry semantics (Cloudflare Workers with Durable Objects for dedup, or Cloud Run with Cloud Tasks for retry queues). Log every webhook receipt and every Typesense write so you can audit drift.

Search analytics. Typesense logs queries to the analytics endpoint if you enable it. Pipe these to your analytics warehouse (BigQuery, Snowflake) for query log analysis. Knowing which searches return zero results is the highest leverage merchandising input you can give a category team.

For storefronts already running on the Storefront API search, the migration is staged:

Phase 1: Stand up Typesense alongside the existing search. Mirror the index. Compare results offline.

Phase 2: Roll Typesense to a percentage of traffic via feature flag. Compare conversion rate, search refinement rate, and zero result rate.

Phase 3: Cut over fully. Keep the Shopify search path as a fallback for the first 30 days.

Most of our Shopify Plus migration playbook applies here for the broader infrastructure work, but search is one of the cleaner subsystems to migrate because it has a sharp boundary (one API call per page load) and clear success metrics (conversion, refinement rate, zero results).

When This Applies to Your Stack

Reach for Typesense on Hydrogen when:

Your catalog is over 10,000 SKUs and faceted search is a primary navigation path.

You need typo tolerance, synonym handling, or editorial ranking that Shopify Storefront API search does not support.

You want sub 50 ms p95 search latency, which is hard to hit through the Shopify Storefront API at scale.

You can stand up the operational overhead of a sync pipeline (or are willing to use Typesense Cloud to outsource the cluster ops).

Skip it when your catalog is small, your search use case is simple, or you do not have the engineering bandwidth to maintain a sync pipeline. Shopify Storefront API search is sufficient for many storefronts and saves you operational work.

Contra Collective designs and ships Typesense on Hydrogen integrations for enterprise commerce teams, including the sync pipeline, schema design, and merchandising workflow. If you are evaluating a search migration or scaling an existing one, we can shorten the cycle. Reach out through our services page.

FAQ

Why Typesense over Algolia for Shopify Hydrogen?

Cost at scale and open source flexibility. Algolia is faster to set up but pricing climbs into thousands per month past medium catalog sizes. Typesense self hosted runs at roughly 10 percent of Algolia's cost for equivalent throughput. Algolia wins on out of the box features (analytics, AI ranking, A/B testing) and is the right pick if engineering capacity is the constraint.

How do I keep Typesense in sync with Shopify inventory?

Subscribe to inventory_levels/update webhooks, push the updated inventory_total and available flags to Typesense in the sync worker. Run a nightly reconciliation job to catch missed webhooks.

Does Typesense support vector search for semantic queries on Shopify products?

Yes. Typesense supports hybrid search combining keyword and vector queries since version 0.25. Index a product embedding (OpenAI text-embedding-3-small or a self hosted alternative) alongside the lexical fields, then query both. For commerce, we usually find that lexical search with good editorial overrides covers 90 percent of queries; vector search closes the long tail.

What is the latency cost of querying Typesense from a Hydrogen loader?

Roughly 15 to 40 ms p95 from a Cloudflare Workers loader to a Typesense Cloud node in the same region. Compared to 200 to 500 ms for the equivalent Shopify Storefront API search call, Typesense is meaningfully faster.

[ 02 ] — Keep Reading

More from the lab.

Jun 14, 2026Engineering

Stytch vs Magic.link: Passwordless Authentication for Modern Web Apps

Passwords are a UX tax. Every password a user creates is a support ticket waiting to happen, a security incident in the making, and a checkout abandonment rate line item your e-commerce analytics will eventually surface. The industry has known this for years. Passwordless authentication, once a niche experiment, is now table stakes for consumer-facing applications that care about conversion.

Jun 12, 2026Engineering

Cognito vs Clerk: AWS Native Auth vs Developer-First Identity in 2026

AWS Cognito is one of the most widely used authentication services in the world, and one of the most frequently replaced. Its usage stats reflect the gravitational pull of the AWS ecosystem. Its replacement frequency reflects something more honest about its developer experience. Clerk built its entire company on the premise that authentication should feel like a first-party framework feature, not a cloud service you configure through a JSON policy document.

Jun 12, 2026Engineering

Shopify Plus vs Salesforce Commerce Cloud: 2026 Enterprise Platform Decision

The enterprise commerce platform market has bifurcated sharply. On one side: Salesforce Commerce Cloud, a legacy powerhouse built for complexity and customization at a price that reflects it. On the other: Shopify Plus, a platform that has spent the last four years systematically closing the enterprise feature gap while keeping total cost of ownership radically lower. The question for most brands in 2026 is no longer whether Shopify Plus is enterprise-ready. It is whether SFCC's remaining advantages justify its cost.

Ready when you are

Want to discuss this topic?

Start a Conversation