Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Added Shopify Analytics #1318

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

oybek-daniyarov
Copy link

Implementation of Shopify analytics with verce/commerce

Analytics have been enabled for the following events.

✅ Page view
✅ Add to cart (add to cart event, it will also enable tracking on checkout page and purchase event)

Todo

(I already have ready solution, but I'm afraid PR will get larger) I can do it in follow-up PR

  • Product page view
  • All Collection page view
  • Single collection page view
  • Product page view
  • Product page view with variants

For Shopify's ADD_TO_CART analytics to function correctly, it's important to note that they will not work with localhost
links directly due to cookie handling.

To enable these analytics features during local development, we can use ngrok

  • Install ngrok: First, visit ngrok's website to download and install ngrok. This tool will create a secure tunnel to
    your localhost, making it accessible over the internet.
  • Set Up a Custom Domain with ngrok: After installation, set up a custom domain through ngrok. This step is crucial as
    it ensures URL consistency, which helps maintain the cookies required for Shopify analytics.
  • Expose Your Local Development Server: Suppose your local development server runs on port 3000. You can then
    expose it to the internet with the following ngrok command:
ngrok http --domain=YOUR_NGROK_DOMAIN 3000

Screenshot from Shopify dashboard

image image
a4a16dba-bc68-44c3-852e-e8445b4bba7c.mp4

Copy link

vercel bot commented Apr 1, 2024

@oybek-daniyarov is attempting to deploy a commit to the Vercel Solutions Team on Vercel.

A member of the Team first needs to authorize it.

@oybek-daniyarov oybek-daniyarov marked this pull request as ready for review April 1, 2024 16:14
@oybek-daniyarov oybek-daniyarov marked this pull request as draft April 1, 2024 18:26
@oybek-daniyarov oybek-daniyarov marked this pull request as ready for review April 3, 2024 20:07
@lucaslosi
Copy link

just implemented this on my ecommerce, and worked as intended. thanks

@Itisfilipe
Copy link

Great solution. Do you happen to know how to implement the privacy consent banner? I think it can be done in a similar fashion...

@oybek-daniyarov
Copy link
Author

Great solution. Do you happen to know how to implement the privacy consent banner? I think it can be done in a similar fashion...

@Itisfilipe You need to do a few steps inside the GTM and on the component level.

Inside the GTM container, you need to enable consent, and for all the tags you should change them from page view to custom event "cookie_consent_update"

Here is the base script. I hope it will help. and inside your shopify analytics script you need to check the localConsent value

i'm working on the tutorial at the moment i will publish it in a few days

I'm using

  • next-client-cookies
  • shadcn/ui

Consent.tsx

'use client';

import { XIcon } from 'lucide-react';
import { usePathname } from 'next/navigation';
import { useCookies } from 'next-client-cookies';
import React, { useEffect, useState } from 'react';

import { Button } from '@/components/ui/button';

type Props = {
  message: string;
  labels: {
    accept: string;
    deny: string;
  };
};

function updateConsent(access: 'granted' | 'denied') {
  gtag('consent', 'update', {
    ad_storage: access,
    analytics_storage: access,
    ad_user_data: access,
    personalization_storage: access,
    ad_personalization: access,
    functionality_storage: access,
    security_storage: access,
  });
}

function Consent({ message, labels }: Props) {
  const pathname = usePathname();

  const [consent, setConsent] = useState(true);
  const cookies = useCookies();

  const handleAccept = () => {
    setConsent(true);
    cookies.set('localConsent', 'true');
  };
  const handleClose = () => {
    setConsent(true);
  };
  const handleDeny = () => {
    setConsent(true);
    cookies.set('localConsent', 'false');
  };

  // Check if user has given consent
  useEffect(() => {
    const consentStatus = cookies.get('localConsent') === 'true';
    setConsent(consentStatus);

    if (consentStatus) {
      updateConsent('granted');
    } else {
      updateConsent('denied');
    }
  }, [cookies]);

  // Update consent status on page change
  useEffect(() => {
    if (consent) {
      gtag('event', 'cookie_consent_update');
    }
  }, [pathname, consent]);

  return !consent ? (
    <div
      id='banner'
      tabIndex={-1}
      aria-hidden='false'
      className='fixed bottom-0 z-50 w-full overflow-y-auto overflow-x-hidden drop-shadow-2xl md:inset-x-0'
    >
      <div className='relative size-full md:h-auto'>
        <div className='relative bg-white shadow'>
          <div className='items-center justify-between p-5 lg:flex'>
            {message && (
              <div className='mb-4 text-dark-blue lg:mb-0'>
                {message}
              </div>
            )}
            <div className='flex shrink-0 items-center gap-1 sm:flex lg:pl-10'>
              <Button size='sm' type='button' onClick={handleDeny}>
                {labels.deny}
              </Button>
              <Button size='sm' type='button' onClick={handleAccept}>
                {labels.accept}
              </Button>
              <button
                onClick={handleClose}
                aria-label='Close banner'
                id='close-modal'
                type='button'
                className='ml-auto inline-flex items-center rounded-lg bg-transparent p-1.5 text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900 md:flex'
              >
                <XIcon className='size-5' />
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  ) : null;
}

export default Consent;

GoogleTagManager.tsx

import Script from 'next/script';
import type { FC } from 'react';

type Props = {
  id: string;
};

const GoogleTagManager: FC<Props> = ({ id }) => {
  return (
    <Script
      id='gtm-init'
      strategy='afterInteractive'
      dangerouslySetInnerHTML={{
        __html: `
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
       
           gtag('js', new Date());
           
          (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${id}');
          `,
      }}
    />
  );
};

export default GoogleTagManager;

@Itisfilipe
Copy link

Thank you! I was talking about the Shopify consent form but this helped me to use the one they provide and even configure GTM. Another question: you said you configured the other events

  • Product page view
  • All Collection page view
  • Single collection page view
  • Product page view with variants

Did you do that with the same hydrogen-react library? Those events aren't mentioned in the official docs 🤔

@oybek-daniyarov
Copy link
Author

Thank you! I was talking about the Shopify consent form but this helped me to use the one they provide and even configure GTM. Another question: you said you configured the other events

  • Product page view
  • All Collection page view
  • Single collection page view
  • Product page view with variants

Did you do that with the same hydrogen-react library? Those events aren't mentioned in the official docs 🤔

Their documentation sucks. They don't want us to use anything outside of the remix :), so I had to inspect their demo store using the developer tools to understand how it works. but I'm not 100% sure if that is accurate or not

@backbone-link
Copy link

This is pretty neat. Thanks for making the PR. They might give you a bit of a hard time with your code formatter (i.e. breaking the README lines into multiple shorter lines) but the feat is great.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants