npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dtcnative/relay-hydrogen

v0.7.3

Published

Meta Pixel + Conversions API for Shopify Hydrogen storefronts. Browser pixel + server-side dedup. Powered by Relay (DTC Native).

Readme

@dtcnative/relay-hydrogen

Meta Pixel + Conversions API for Shopify Hydrogen storefronts. Browser pixel + server-side dedup. Powered by Relay, the CAPI proxy built for DTC Native members.

npm i @dtcnative/relay-hydrogen

Why

iOS 14, ad blockers, and Safari ITP eat 30–50% of browser-only Meta Pixel events. Relay sends every event twice — once from the browser via fbq(), once from your server via the Conversions API — sharing an event_id so Meta deduplicates. The server signal recovers most of the lost attribution; this package wires both sides for Hydrogen storefronts.

Quick start

1. Wrap your app

// app/root.tsx
import { RelayProvider, useTrackPageView } from "@dtcnative/relay-hydrogen/client";
import { Outlet, useLocation } from "@remix-run/react";

function PageViewTracker() {
  useTrackPageView(useLocation().pathname);
  return null;
}

export default function App() {
  return (
    <RelayProvider
      config={{ apiKey: process.env.RELAY_API_KEY! }}   // rly_…
    >
      <PageViewTracker />
      <Outlet />
    </RelayProvider>
  );
}

2. Track ecommerce events

// app/components/AddToCartButton.tsx
import { useRelay } from "@dtcnative/relay-hydrogen/client";

export function AddToCartButton({ product, variant }) {
  const { track } = useRelay();
  return (
    <button onClick={() => {
      track("AddToCart", {
        value: variant.price.amount,
        currency: variant.price.currencyCode,
        content_ids: [variant.id],
        content_type: "product",
        num_items: 1,
      });
      // ...your cart-add action
    }}>
      Add to cart
    </button>
  );
}

3. Server-side Purchase (deduped)

The browser pixel often dies during the Shopify checkout redirect. Belt-and-suspenders: fire the same Purchase from your order webhook with the same event_id. Meta dedupes.

// app/routes/api.shopify-order-webhook.ts
import { relayCapture, eventIdForOrder } from "@dtcnative/relay-hydrogen/server";

export async function action({ request, context }) {
  const order = await request.json();
  const eventId = await eventIdForOrder(order.id);
  await relayCapture({
    apiKey: context.env.RELAY_API_KEY,
    pixelId: context.env.META_PIXEL_ID,
    eventName: "Purchase",
    eventId,
    eventSourceUrl: `https://${context.env.SHOP_DOMAIN}/orders/${order.id}`,
    userData: { em: order.email, ph: order.phone },
    customData: {
      value: Number(order.total_price),
      currency: order.currency,
      content_ids: order.line_items.map((li) => String(li.product_id)),
      order_id: String(order.id),
    },
    request,
  });
  return new Response("ok");
}

Features

  • Type-safe events — discriminated union so track('Purchase', { value }) is a TS error if currency is missing.
  • React API<RelayProvider> + useRelay() + useTrackPageView(pathname).
  • Server helperrelayCapture() + eventIdForOrder() for browser-server dedup on Purchase events.
  • Browser + server dedup — same event_id shared between fbq and CAPI so Meta sees one event, never two.
  • Outbound click attribution — preserves fbclid / gclid / ttclid / utm_* across navigation.
  • Resilient delivery — batched, retried, and queued for tab-close so events arrive even on flaky networks.
  • Stable visitor identity — recognizes returning shoppers across cookie clears, devices, and Safari ITP wipes. Browser pixel and server-side CAPI agree on the same visitor automatically.
  • First-party auto-routing — when your store has a custom Relay hostname configured (e.g. data.brand.com), the SDK uses it without any code change.
  • Consent-aware — call tracker.setConsent({ marketing: false }) and events ship with opt_out: true so Meta won't use them for attribution or audiences.

API reference

Client — @dtcnative/relay-hydrogen/client

| Export | Notes | |---|---| | RelayProvider | React context provider. Place near the root of your app. | | useRelay() | { track, identify, setConsent, flush } — call from any component. | | useTrackPageView(pathname) | Auto-fires PageView whenever pathname changes. | | useRelayAutoTrack(useMatches()) | Browser-side dual-fire for events tagged on loader results by createRelayLoader. | | useRelayAutoTrackFetchers(useFetchers()) | Browser-side dual-fire for events tagged on action results by createRelayAction and submitted via useFetcher(). | | Tracker | Low-level class for advanced (non-React) integrations. |

Server — @dtcnative/relay-hydrogen/server

| Export | Notes | |---|---| | createRelayLoader(handler, eventFactory?) | Wrap a Remix loader. Server-fires the event via executionContext.waitUntil (non-blocking — does not delay your loader's response). Injects a __relay marker on the data so useRelayAutoTrack can dual-fire client-side. | | createRelayAction(handler, eventFactory?) | Same as createRelayLoader but for actions. Pair with useRelayAutoTrackFetchers on the client to dual-fire after a fetcher submit. | | relayCapture(opts) | Low-level POST of a single event. Use directly only from webhook handlers where you need delivery confirmation. Do NOT await relayCapture(...) inside a loader/action — it blocks the render path. Use createRelayLoader / createRelayAction instead. | | capturePageViewIfNeeded(opts) | Convenience server-side PageView for routes with attribution params (fbclid, gclid, ttclid, _rly). Pass opts.executionContext (or run inside createRelayLoader) for non-blocking dispatch. | | eventIdForOrder(orderId) | sha-256 hash of relay:purchase:<orderId>. Use the same on browser side to dedup. |

Configuration (RelayConfig)

type RelayConfig = {
  /** rly_… API token from your Relay dashboard. */
  apiKey: string;
  /**
   * Optional Meta pixel id(s). When omitted (recommended), the SDK fetches
   * the merchant's configured pixels from the Relay worker at boot, fans
   * `fbq` out to each, and buffers any track() calls until init resolves.
   * Cached in localStorage with revalidate-in-background.
   */
  pixelId?: string | string[];
  /**
   * Worker URL. Defaults to https://worker.trackrelay.app. The SDK
   * automatically switches to your store's configured first-party
   * hostname when one is set. Override only for self-hosted setups.
   */
  endpoint?: string;
  /**
   * Dual-fire control. When `true` (default), every event fires BOTH
   * browser-side via fbq/ttq AND server-side via CAPI through the
   * worker, sharing the same `event_id` so Meta/TikTok dedupe.
   * When `false`, only the CAPI path fires — useful for storefronts
   * that already inject Meta's pixel via another integration and want
   * to avoid double-loading fbevents.js.
   */
  pixelEnabled?: boolean;
  /** Enable the link decorator. Default: true. */
  linkDecorator?: boolean;
  /** Override the store's test_event_code per browser session. */
  testEventCode?: string;
  /** Console-log every track() call. Default: false. */
  debug?: boolean;
};

Performance & blocking behaviour

The SDK is designed so tracking never blocks page rendering.

  • track(), identify(), setConsent(), buildCartAttribute() are all synchronous from the caller's perspective. They return void / a value immediately; the network round-trip happens in the background.
  • createRelayLoader / createRelayAction dispatch the server-side CAPI POST via Cloudflare's executionContext.waitUntil() so the request runs as a background promise that DOES NOT block the loader/action's response.
  • capturePageViewIfNeeded resolves in a microtask — the actual worker round-trip runs via waitUntil (pass opts.executionContext in non-Hydrogen runtimes).

Don't await relayCapture(...) inside a loader/action

relayCapture() is a low-level helper that performs a synchronous HTTP POST. If you await it inside a Remix loader or action, every page navigation will block on the round-trip to the Relay worker (50–200ms+) before your page can render. On product pages with multiple loaders the cost compounds (up to 8× perf regression measured against a client storefront before 0.7.1).

Use the wrappers (createRelayLoader / createRelayAction) for loaders and actions. They handle non-blocking dispatch via waitUntil automatically. Reserve relayCapture() for webhook handlers where you actually need delivery confirmation before responding with 200.

Browser-side batching

Events are coalesced in-memory and POSTed as a single request per batch:

  • Flushes at 10 events in the buffer, OR
  • 2000 ms after the first unsent event, OR
  • On pagehide / visibilitychange:hidden (via sendBeacon to survive unload).

Single events POST to /v1/events with a flat payload; batches POST to the same endpoint with { batch: [...] }. The worker handles both shapes transparently.

CORS preflight avoidance

Since 0.7.2 the SDK sends events as CORS simple requests (Content-Type: text/plain;charset=UTF-8 + API key in ?k= query param) so the browser skips the OPTIONS preflight on every event. Saves ~150 ms on the first event of every visitor session. The Relay API key is a publishable token (already inlined in your storefront JS) so URL-positioning it adds no security risk.

Get an API key

DTC Native members get unlimited Relay tenants — sign in at dtcnative.com, open the Relay tool, generate an API key.

License

Business Source License 1.1 — see LICENSE and NOTICE.

You're free to use this in your own ecommerce business. The license converts to Apache-2.0 on the Change Date stated in LICENSE.