ドキュメント
ドキュメントを見る

Add Privatus Analytics to Shopify Hydrogen

Count real visitors on a Shopify Hydrogen storefront without cookies, track Add to Cart in the browser and record purchase revenue from Shopify checkout.

Markdownで表示

Many analytics tools set cookies. In many countries that means you must ask shoppers for permission first, and many say no. Those shoppers never show up in your numbers. Bots add fake visits on top.

Privatus Analytics counts visits without cookies and leaves bots out. This guide adds it to a Hydrogen storefront (including Weaverse themes such as Pilot), tracks Add to Cart and records purchase revenue from Shopify checkout. For an Online Store theme, see Shopify instead.

File names and route paths are examples, not a required project structure.

How it fits together#

text
Hydrogen storefront (browser)
    │ <PrivatusAnalytics /> loads pa.js
    │ page views on every route change
    │ Add to Cart from Hydrogen analytics events
    ▼
privatusstats.com
    ▲
    │ Purchase with revenue
Shopify checkout (custom pixel)

All events are sent from the shopper's browser. Nothing runs on the Hydrogen server, and no token or secret is needed.

Before you start#

  • A Privatus Analytics site for your storefront domain. The site ID looks like pa_AbC123.
  • A Hydrogen storefront.
  • Access to the Shopify admin, for the checkout pixel.

Add the site ID to your local and deployed environment variables:

bash
PUBLIC_PRIVATUS_SITE_ID=pa_YOURSITEID

The site ID is public. It appears in your page source, so it does not need to be kept secret.

Install the package#

bash
npm install @privatus-analytics/react

<PrivatusAnalytics /> adds the tracking script once, from an effect, so it only runs in the browser and is safe for server rendering. It renders nothing.

Load the script in the root layout#

Return the site ID from the root loader:

ts
// app/root.tsx
export async function loader({ context }: LoaderFunctionArgs) {
  // ...your existing loader data
  return {
    // ...
    privatusSiteId: context.env.PUBLIC_PRIVATUS_SITE_ID,
  };
}

Then render the component in the layout. Pass the Hydrogen nonce so the script passes your Content Security Policy:

tsx
// app/root.tsx
import { useNonce } from "@shopify/hydrogen";
import { PrivatusAnalytics } from "@privatus-analytics/react";
import { useRouteLoaderData } from "react-router";

export function Layout({ children }: { children?: React.ReactNode }) {
  const nonce = useNonce();
  const data = useRouteLoaderData<typeof loader>("root");

  return (
    <html lang="en">
      <head>{/* ...Meta, Links */}</head>
      <body>
        {children}
        {data?.privatusSiteId ? (
          <PrivatusAnalytics
            site={data.privatusSiteId}
            nonce={nonce}
            modules={["engage", "auto", "vitals", "clicks", "ask"]}
          />
        ) : null}
        {/* ...ScrollRestoration, Scripts */}
      </body>
    </html>
  );
}

Hydrogen changes routes with pushState. The script follows those changes by itself, so every page view is counted without extra code. See single-page apps.

The modules list turns on extra features: engage (time on page and scroll depth), auto (outbound links, file downloads and form sends), vitals (Core Web Vitals), clicks (click maps) and ask (short on-site surveys). Leave out any you do not want. See script attributes.

Prefer a plain script tag? Put this in the head instead of the component, with nonce={nonce} when your policy uses nonces:

html
<script defer src="https://privatusstats.com/js/pa.js" data-site="pa_YOURSITEID" data-modules="engage,auto,vitals,clicks,ask"></script>

Allow the script in your Content Security Policy#

Hydrogen builds a Content Security Policy that lists which hosts may load scripts and receive data. Add the Privatus Analytics host to both lists.

In Weaverse themes such as Pilot, these lists live in app/weaverse/csp.ts:

ts
// app/weaverse/csp.ts
const updatedCsp = {
  // ...defaultSrc, styleSrc
  connectSrc: [
    // ...existing hosts
    "https://privatusstats.com",
  ],
  scriptSrc: ["https://cdn.shopify.com", "https://privatusstats.com"],
};

In other Hydrogen projects, pass the same hosts to createContentSecurityPolicy in app/entry.server.tsx:

ts
// app/entry.server.tsx
const { nonce, header, NonceProvider } = createContentSecurityPolicy({
  shop: {
    checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
    storeDomain: context.env.PUBLIC_STORE_DOMAIN,
  },
  scriptSrc: ["'self'", "https://cdn.shopify.com", "https://privatusstats.com"],
  connectSrc: ["'self'", "https://privatusstats.com"],
});

Keep any hosts your theme already lists. If the browser console shows a blocked request for privatusstats.com, the host is missing from one of these lists.

Track Add to Cart#

Hydrogen publishes cart events through its analytics bus. Subscribe to product_added_to_cart and send a Privatus Analytics event:

tsx
// app/components/privatus-cart-events.tsx
import { useEffect } from "react";
import { AnalyticsEvent, useAnalytics } from "@shopify/hydrogen";
import { usePrivatus } from "@privatus-analytics/react";

export function PrivatusCartEvents() {
  const { subscribe } = useAnalytics();
  const privatus = usePrivatus();

  useEffect(() => {
    subscribe(AnalyticsEvent.PRODUCT_ADD_TO_CART, ({ currentLine, prevLine }) => {
      const merchandise = currentLine?.merchandise;
      if (!merchandise) return;

      const added = (currentLine?.quantity ?? 0) - (prevLine?.quantity ?? 0);
      privatus.track("Add to Cart", {
        product: merchandise.product?.title,
        vendor: merchandise.product?.vendor,
        quantity: added > 0 ? added : 1,
        price: Number(merchandise.price?.amount),
        currency: merchandise.price?.currencyCode,
      });
    });
  }, [subscribe, privatus]);

  return null;
}

Render <PrivatusCartEvents /> inside your <Analytics.Provider>, next to <PrivatusAnalytics />. Calls made before the script loads wait in a queue, so no event is lost.

The fields come from your cart query, so use the ones your theme fetches. Send only product details like these. Do not add names, emails or other customer data to event properties.

Note: This uses the useAnalytics hook from Hydrogen 2026.4 and earlier. Hydrogen 2026.10 replaces it with analytics destinations. On that version, register the same mapping with addDestination instead.

Record purchase revenue#

Shopify checkout and the thank-you page are hosted by Shopify, not by your Hydrogen app, so your storefront code never sees the finished order. A Shopify custom pixel does, and it runs in the shopper's browser.

  1. In the Shopify admin, open Settings, then Customer events, then Add custom pixel. Name it "Privatus Analytics".
  2. Paste the code from custom-pixel.js.
  3. At the top of the code, set SITE_ID to your pa_... site ID.
  4. Set STOREFRONT_PAGEVIEWS to false. Your Hydrogen storefront already counts its own page views, so the pixel then counts only checkout pages and nothing is counted twice.
  5. Click Save, then Connect.

The pixel sends a Purchase event with the order total and currency when checkout finishes. It sends no customer data, and order links are cut down to fixed paths so no order IDs leave the browser.

In Privatus Analytics, create a goal for the Purchase event to see revenue by source, page and campaign.

Track your own events#

Use the hook in any component:

tsx
import { usePrivatus } from "@privatus-analytics/react";

export function NewsletterButton() {
  const privatus = usePrivatus();
  return (
    <button onClick={() => privatus.track("Newsletter signup")}>
      Join the newsletter
    </button>
  );
}

To record money for any event, add revenue and currency:

ts
privatus.track("Gift card bought", { revenue: 25, currency: "USD" });

With Weaverse#

Load the script in the root layout, not inside a Weaverse section. Sections can be added, moved or removed in Studio, and the script must load on every page exactly once.

To track a click inside a section, call usePrivatus() in that section's component. Keep event names in code: letting merchants type event names in Studio leads to many spellings of the same event.

Test it#

  1. Run npm run dev and open the storefront.
  2. Pass debug to the component while testing, to log events in the console instead of sending them.
  3. Move between pages and check that a page view is logged for each route.
  4. Add a product to the cart and check for one Add to Cart event with the right quantity.
  5. Remove debug and deploy. Open Live in the dashboard and check that your visit appears.
  6. Place a test order and check for a Purchase event with the order total.
  7. Check the browser console for Content Security Policy errors.

Visits from localhost are not counted by default. Pass allowLocal to the component to test against your dashboard from your own machine.