MatteoGabriele / vue-gtag

Global Site Tag plugin for Vue (gtag.js)
https://matteo-gabriele.gitbook.io/vue-gtag/
MIT License
870 stars 67 forks source link

NUXT Module Available? #87

Closed jssouders closed 4 years ago

jssouders commented 4 years ago

I am starting a new NUXT application and have a requirement to add google analytics. There is currently an analytics-module package in the nuxt-community that utilizes your vue-analytics package, but I have not been able to find one for your new vue-tag package. Do you know if one exists?

MatteoGabriele commented 4 years ago

I know they have a nuxt plugin for gtag, but it's not a wrapper of vue-gtag, it's a standalone package called @nuxtjs/google-gtag

I'm not sure if I'm going to make a nuxt package wrapper of vue-gtag I'll let u know

thanks for the feedback

cesasol commented 4 years ago

I have a simple solution for this. I load the script through a plugin with the following code

In plugins/vue-gtag.client.js

import Vue from 'vue'
import VueGtag from 'vue-gtag'

/**
 * @type {import('@nuxt/types').Plugin}
 */
const vueGtag = ({ app, env }) => {
  Vue.use(
    VueGtag,
    {
      config: { id: env.gaId },
      disabled: true, // Here you could ignore user privacy and set to true
    },
    app.router
  )
}

export default vueGtag

And in plugins/vue-gtag.server.js

/**
 * @type {import('@nuxt/types').Plugin}
 */
const vueGtag = ({ app, env }) => {
  const preloadLinks = [
    {
      rel: 'preload',
      as: 'script',
      href: `https://www.googletagmanager.com/gtag/js?id=${env.gaId}`,
    },
    {
      rel: 'preconnect',
      href: 'https://www.google-analytics.com/',
    },
  ]
  if (process.env.NODE_ENV === 'production') {
    if (typeof app.head === 'function') {
      console.error("We can't add the preload links if head is a function")
    } else if (app.head) {
      app.head.link = [...(app.head.link || []), ...preloadLinks]
    } else {
      app.head = {
        link: preloadLinks,
      }
    }
  }
}

export default vueGtag

On nuxt.config.js

export default {
  env: {
    /** Configuración de google-analytics */
    gaId: 'UA-XXXXX-X',
  },

  plugins: [
    // ... other plugins
    '~/plugins/vue-gtag.client.js',
    '~/plugins/vue-gtag.server.js',
  ]
}

And here is a component for controlling the activation of said plugin. This part is optional and depends on your moral and legal concerns.

<template>
  <transition name="fade">
    <article
      v-if="showComponent"
      class="alert-cookie bg-gray-800 shadow max-w-xs text-white p-3 rounded"
      role="alert"
    >
      <h1 class="h6">
        Uso de cookies
      </h1>
      <p class="mb-2 text-small leading-point">
        Este sitio web utiliza cookies para que tengas la mejor experiencia de
        usuario. Si continúas navegando nos das tu consentimiento y aceptas
        nuestra
        <n-link class="underline text-blue-200" no-prefetch to="/cookies">
          Política de cookies
        </n-link>
      </p>
      <p>
        <button class="btn" @click="setCookie(true)">
          Ok gracias
        </button>
      </p>
    </article>
  </transition>
</template>

<script>
export const cookieName = 'DinamoGalleta'
export const cookieMaxAge = 60 * 60 * 24 * 365

export const hasCookie = (name = '') => {
  const cookies = document.cookie.split(';')
  const [dinamoCookie = ''] = cookies.filter(item => item.includes(`${name}=`))
  return {
    cookie: dinamoCookie,
    isEnabled: dinamoCookie.indexOf('active') !== -1,
    isDisabled: dinamoCookie.indexOf('nope') !== -1,
  }
}

export const setCookie = (active = false) => {
  const cookieValue = active ? 'active' : 'nope'
  document.cookie = `${cookieName}=${cookieValue};path=/;max-age=${cookieMaxAge};SameSite=Strict`
  return cookieValue
}

export default {
  // TODO: En vue3 vamos a reemplazar esto por composition API
  data() {
    return {
      showComponent: false,
      cookie: {
        cookie: '',
        isEnabled: false,
        isDisabled: false,
      },
    }
  },
  mounted() {
    this.getCookieFromCtx()
    if (!this.cookie.cookie) {
      this.$root.$once('routeChanged', () => this.setCookie(true))
      this.enableTracking()
    } else if (this.cookie.isEnabled) {
      this.enableTracking()
    }
  },
  methods: {
    getCookieFromCtx() {
      this.cookie = hasCookie(cookieName)
    },
    setCookie(active = false) {
      const cookieValue = setCookie(active)
      this.getCookieFromCtx()
      this.showComponent = false
      if (this.cookie.isEnabled) {
        this.enableTracking()
      } else {
        this.disableTracking()
      }
      this.$emit('cookieSet', cookieValue)
    },
    enableTracking() {
      try {
        // NOTE: Si quieres más trackers, aquí van.
        this.$gtag.optIn()
        this.$fbpixel.enable()
        // eslint-disable-next-line no-empty
      } catch (e) {}
    },
    disableTracking() {
      try {
        // NOTE: Si quieres más trackers, aquí van.
        this.$gtag.optOut()
        this.$fbpixel.disable()
        // eslint-disable-next-line no-empty
      } catch (e) {}
    },
  },
}
</script>

And a page for disabling cookies.vue

<template>
  <main class="page--cookies section">
    <div class="container cookies">
      <h1 class="h1">Política de cookies</h1>
      <p>
        Las cookies de este sitio web se utilizan para ofrecer contenido y
        anuncios personalizados, además de brindar funciones de redes sociales y
        analizar el tráfico. Es importante que sepas que compartimos información
        sobre el uso que hagas del sitio web con nuestros partners de redes
        sociales, publicidad y análisis web, quienes pueden combinar esta
        información con otra que les hayas proporcionado o que hayan recopilado
        a partir del uso que haya hecho de sus servicios. Aceptas nuestras
        cookies si continúas navegando nuestro sitio web.
      </p>

      <ul class="flex flex-col lg:flex-row justify-between mt-8 mb-6">
        <li
          v-for="(definition, i) in cookieDefinition"
          :key="i"
          class="cookies__list-col"
        >
          <h2 class="h3 title">{{ definition.label }}</h2>
          <div class="content">
            <p v-for="(d, o) in definition.desc" :key="o" v-text="d"></p>
          </div>
        </li>
      </ul>

      <div class="flex flex-col justify-center items-center">
        <p>¿Aceptas las cookies de terceros?</p>
        <input
          id="accept-cookie"
          v-model="cookie.isEnabled"
          :value="true"
          type="radio"
          name="accept-cookie"
          class="hidden"
        />
        <input
          id="deny-cookie"
          v-model="cookie.isDisabled"
          :value="true"
          type="radio"
          name="accept-cookie"
          class="hidden"
        />
        <div
          class="switch"
          :class="{
            'switch--yes': cookie.isEnabled,
            'switch--no': cookie.isDisabled,
          }"
        >
          <label class="switch__label switch__label--yes" for="accept-cookie"
            >Si</label
          >
          <span class="switch__icon"></span>
          <label class="switch__label switch__label--no" for="deny-cookie"
            >No</label
          >
        </div>
      </div>
    </div>
  </main>
</template>

<script>
import { data as cookieDefinition } from '~/assets/data/cookie-definitions.json'
import { hasCookie, cookieName, setCookie } from '~/components/AlertCookie.vue'

export default {
  // TODO: En vue3 vamos a reemplazar esto por composition API
  name: 'PageCookies',
  data() {
    return {
      cookieDefinition,
      cookie: {
        cookie: '',
        isEnabled: false,
        isDisabled: false,
      },
    }
  },
  watch: {
    'cookie.isDisabled': function cookieIsDisabled(newValue) {
      this.cookie.isEnabled = !newValue
      if (newValue) {
        this.denyCookie()
      }
    },
    'cookie.isEnabled': function cookieIsEnabled(newValue) {
      this.cookie.isDisabled = !newValue
      if (newValue) {
        this.acceptCookie()
      }
    },
  },
  mounted() {
    this.getCookieFromCtx()
  },
  methods: {
    getCookieFromCtx() {
      this.cookie = hasCookie(cookieName)
    },
    acceptCookie() {
      this.setCookie(true)
    },
    denyCookie() {
      this.setCookie(false)
    },
    setCookie(active = false) {
      const cookieValue = setCookie(active)
      this.getCookieFromCtx()
      if (this.cookie.isEnabled) {
        this.enableTracking()
      } else {
        this.disableTracking()
      }
      this.$emit('cookieSet', cookieValue)
    },
    enableTracking() {
      try {
        // NOTE: Si quieres más trackers, aquí van.
        this.$gtag.optIn()
        this.$fbpixel.enable()
        // eslint-disable-next-line no-empty
      } catch (e) {}
    },
    disableTracking() {
      try {
        // NOTE: Si quieres más trackers, aquí van.
        this.$gtag.optOut()
        this.$fbpixel.disable()
        // eslint-disable-next-line no-empty
      } catch (e) {}
    },
  },
}
</script>

<style lang="scss" scoped>
$radious: 25px;

.switch {
  position: relative;
  display: grid;
  max-width: theme('maxWidth.sm');
  line-height: theme('lineHeight.small');
  text-align: center;
  border-radius: $radious;
  transition: 0.3s ease;
  transition-property: color, background-color;
  align-items: stretch;
  grid-template-columns: 1fr 40px 1fr;

  &__icon {
    display: block;
    width: 20px;
    height: 4px;
    margin: 4px 4px 4px 12px;
    background: #fff;
    border-radius: 2px;
    transition: 0.2s ease;
    transform: rotate(-45deg);
    align-self: center;

    &::after {
      position: absolute;
      display: block;
      width: 4px;
      height: 12px;
      margin-top: -8px;
      content: '';
      background: #fff;
      border-radius: 2px;
      transition: 0.2s ease;
      transition-property: margin, height, width, background-color;
    }
  }

  &__label {
    display: flex;
    padding: theme('spacings.1');
    align-items: center;
    font-weight: normal;
    color: rgba(0, 0, 0, 0.2);
    cursor: pointer;
    background-color: transparent;
    transition: color 0.2s ease, background-color 0.6s ease-out;

    &:hover,
    &:focus {
      background-color: rgba($black, $alpha: 0.1);
    }
  }

  &__label--yes {
    padding-left: theme('spacings.2');
    text-align: left;
    border-top-left-radius: $radious;
    border-bottom-left-radius: $radious;
  }

  &__label--no {
    padding-right: theme('spacings.2');
    text-align: right;
    border-top-right-radius: $radious;
    border-bottom-right-radius: $radious;
  }

  &--no {
    background-color: #ff3b30;
  }

  &--no & {
    &__label--no {
      font-weight: bold;
    }

    &__icon {
      background-color: #fff;

      &::after {
        height: 20px;
        margin-top: -8px;
        margin-left: 8px;
        background-color: #fff;
      }
    }

    &__label--yes,
    &__label--no {
      color: #fff;
    }
  }

  &--yes {
    font-weight: bold;
    background: $green-500;
  }

  &--yes & {
    &__label--yes {
      font-weight: bold;
    }

    &__label--yes,
    &__label--no {
      color: #fff;
    }
  }
}
</style>
AlejandroMut commented 4 years ago

@cesasol on vue-gtag.server.js you might want to push those objects into the app.head.link array. Otherwise you will overwrite all other head settings.

aiman1717a commented 4 years ago

@AlejandroMut Thank you so much for stating this. I had this problem and i had to replace the heads again from each page dynamically to solve it. Now i know why the heads were missing in production in the first place.

ReindDooyeweerd commented 4 years ago

I tried the code from @cesasol and it works (after a small change to push data to the app.head instead of overwriting it all) however i have one issue i couldn't figure out.

Everything seems to work fine however query parameters in the URL aren't displayed in Analytics, so i see this one appearing in "live metrics" https://domain.xx/gaming-entertainment but i don't see https://domain.xx/gaming-entertainment?page=2.

So it looks like the vue-gtag.client.js isn't working like it should, any suggestions on how to fix that @cesasol because i think vue-gtag is a wiser thing to use then the current community analytics module for nuxtjs that is based on the older vue-analytics code.

cesasol commented 4 years ago

@Vout Sorry for the late response. But your issue comes from the fact that vue-gtag uses vue-router to detect route changes but if you change the query string without using vue-router (like assigning a location) then vue-gtag has no way of detecting the route change

Tobeyforce commented 3 years ago

Using @cesasol solution, it starts tracking even though I don't run opt in (sets the ga-cookies right away) on the first load before opt out has explicitly been set.. Any ideas? I thought using enabled: false would prevent that? Perhaps because it's loading the SSR plugin?