vue.jsnuxt.jsheadgtag.js

Nuxt: Inside a plugin, how to add dynamic script tag to head?


I'm trying to build a Google Analytics plugin to Nuxt that will fetch tracking IDs from the CMS. I am really close I think.

I have a plugin file loading on client side only. The plugin is loaded from nuxt.config.js via the plugins:[{ src: '~/plugins/google-gtag.js', mode: 'client' }] array.

From there the main problem is that the gtag script needs the UA code in it's URL, so I can't just add that into the regular script object in nuxt.config.js. I need to get those UA codes from the store (which is hydrated form nuxtServerInit.

So I'm using head.script.push in the plugin to add the gtag script with the UA code in the URL. But that doesn't result in the script being added on first page load, but it does for all subsequent page transitions. So clearly I'm running head.script.push too late in the render of the page.

But I don't know how else to fetch tracking IDs, then add script's to the head.

// plugins/google.gtag.client.js with "mode": "client
export default ({ store, app: { head, router, context } }, inject) => {
    // Remove any empty tracking codes
    const codes = store.state.siteMeta.gaTrackingCodes.filter(Boolean)

    // Add script tag to head
    head.script.push({
        src: `https://www.googletagmanager.com/gtag/js?id=${codes[0]}`,
        async: true
    })
    console.log('added script')

    // Include Google gtag code and inject it (so this.$gtag works in pages/components)
    window.dataLayer = window.dataLayer || []
    function gtag() {
        dataLayer.push(arguments)
    }
    inject('gtag', gtag)
    gtag('js', new Date())

    // Add tracking codes from Vuex store
    codes.forEach(code => {
        gtag('config', code, {
            send_page_view: false // necessary to avoid duplicated page track on first page load
        })

        console.log('installed code', code)

        // After each router transition, log page event to Google for each code
        router.afterEach(to => {
            gtag('event', 'page_view', { page_path: to.fullPath })
            console.log('afterEach', code)
        })
    })
}

Solution

  • I ended up getting this to work and we use it in production here.

    Code as of this writing looks like this:

    export default ({ store, app: { router, context } }, inject) => {
        // Remove any empty tracking codes
        let codes = _get(store, "state.siteMeta.gaTrackingCodes", [])
        codes = codes.filter(Boolean)
    
        // Abort if no codes
        if (!codes.length) {
            if (context.isDev) console.log("No Google Anlaytics tracking codes set")
            inject("gtag", () => {})
            return
        }
    
        // Abort if in Dev mode, but inject dummy functions so $gtag events don't throw errors
        if (context.isDev) {
            console.log("No Google Anlaytics tracking becuase your are in Dev mode")
            inject("gtag", () => {})
            return
        }
    
        // Abort if we already added script to head
        let gtagScript = document.getElementById("gtag")
        if (gtagScript) {
            return
        }
    
        // Add script tag to head
        let script = document.createElement("script")
        script.async = true
        script.id = "gtag"
        script.src = "//www.googletagmanager.com/gtag/js"
        document.head.appendChild(script)
    
        // Include Google gtag code and inject it (so this.$gtag works in pages/components)
        window.dataLayer = window.dataLayer || []
        function gtag() {
            dataLayer.push(arguments)
        }
        inject("gtag", gtag)
        gtag("js", new Date())
    
        // Add tracking codes from Vuex store
        codes.forEach(code => {
            gtag("config", code, {
                send_page_view: false // Necessary to avoid duplicated page track on first page load
            })
    
            // After each router transition, log page event to Google for each code
            router.afterEach(to => {
                gtag("event", code, { page_path: to.fullPath })
            })
        })
    }
    

    If not in a plug-in, this was a good read on how to load 3rd party scripts: How to Load Third-Party Scripts in Nuxt.js