How to set up analytics in Nuxt
Jan 19, 2024
Product analytics enable you to gather and analyze data about how users interact with your Nuxt.js app. To show you how to set up analytics, in this tutorial we create a basic Nuxt app, add PostHog on both the client and server, and use it to capture pageviews and custom events.
Creating a Nuxt app
To demonstrate the basics of PostHog analytics, we create a simple Nuxt 3 app with two pages and a link to navigate between them.
First, ensure Node.js is installed (version 18.0.0 or newer). Then run the following command:
npx nuxi@latest init <project-name>
Name it whatever you like (we call ours nuxt-analytics
), select npm
as the package manager, and use the defaults for the remaining options.
Adding pages
Next, create a new directory pages
and two new files index.vue
and about.vue
in it:
cd ./nuxt-analytics # replace "nuxt-analytics" with your project namemkdir pagescd ./pagestouch index.vuetouch about.vue
In index.vue
, add the following the code:
<template><div><h1>Home</h1><NuxtLink to="/about">Go to About</NuxtLink></div></template>
In about.vue
, add the following the code:
<template><div><h1>About</h1><NuxtLink to="/">Back Home</NuxtLink></div></template>
Lastly, replace the code in app.vue
with:
<template><div><NuxtLayout><NuxtPage/></NuxtLayout></div></template>
The basic setup is now complete. Run npm run dev
to see your app.
Adding PostHog on the client side
This tutorial shows how to integrate PostHog with
Nuxt 3
. If you're usingNuxt 2
, see our Nuxt docs for how to integrate.
With our app set up, it’s time to install and set up PostHog. If you don't have a PostHog instance, you can sign up for free.
First install posthog-js
:
npm install posthog-js
Then, add your PostHog API key and host to your nuxt.config.ts
file. You can find your project API key in your PostHog project settings
export default defineNuxtConfig({devtools: { enabled: true },runtimeConfig: {public: {posthogPublicKey: '<ph_project_api_key>',posthogHost: 'https://us.i.posthog.com'}}})
Create a new plugin by creating a new folder called plugins
in your base directory and then a new file posthog.client.js
:
mkdir pluginscd pluginstouch posthog.client.js
Add the following code to your posthog.client.js
file:
import { defineNuxtPlugin } from '#app'import posthog from 'posthog-js'export default defineNuxtPlugin(nuxtApp => {const runtimeConfig = useRuntimeConfig();const posthogClient = posthog.init(runtimeConfig.public.posthogPublicKey, {api_host: runtimeConfig.public.posthogHost,person_profiles: 'identified_only',})return {provide: {posthog: () => posthogClient}}})
Once you’ve done this, reload your app. You should begin seeing events in the PostHog events explorer.
Capturing pageviews
You might notice that moving between pages only captures a single pageview event. This is because PostHog only captures pageview events when a page load is fired. Since Nuxt creates a single-page app, this only happens once, and the Nuxt router handles subsequent page changes.
If we want to capture every route change, we must write code to capture pageviews that integrates with the router.
In posthog.client.js
, set up PostHog to capture pageviews in the router.afterEach
function. Additionally, you can use nextTick
so that you capture the event after the page is mounted.
import { defineNuxtPlugin } from '#app'import posthog from 'posthog-js'export default defineNuxtPlugin(nuxtApp => {const runtimeConfig = useRuntimeConfig();const posthogClient = posthog.init(runtimeConfig.public.posthogPublicKey, {api_host: runtimeConfig.public.posthogHost,person_profiles: 'identified_only'capture_pageview: false // set this to false since we manually capture pageviews in router.afterEach})const router = useRouter();router.afterEach((to) => {nextTick(() => {posthog.capture('$pageview', {current_url: to.fullPath});});});// rest of your code})
Make sure to set capture_pageview
in the PostHog initialization config to false
. This turns off autocaptured pageviews and ensures you won’t double-capture pageviews on the first load.
Capturing custom events
Beyond pageviews, there might be more events you want to capture. You can do this by capturing custom events with PostHog.
To showcase this, we add a button to the home page and capture a custom event whenever it is clicked. Update the code in index.vue
:
<template><div><h1>Home</h1><NuxtLink to="/about">Go to About</NuxtLink><button @click="captureCustomEvent">Click Me</button></div></template><script setup>const { $posthog } = useNuxtApp()const captureCustomEvent = () => {$posthog().capture('home_button_clicked', {'user_name': 'Max the Hedgehog'});};</script>
When you click the button now, PostHog captures a custom home_button_clicked
event. Notice that we also added a property user_name
to the event. This is helpful for filtering events in the PostHog dashboard.
Adding PostHog on the server side
Nuxt is a full stack framework, parts of it run on both the client and server side. So far, we’ve only used PostHog on the client side and the posthog-js
library we installed won’t work on the server side. Instead, we must use the posthog-node
SDK. We can start by installing it:
npm install posthog-node
Next, initialize posthog-node
wherever you'd like to capture events server-side, such as in your server routes or in the middleware. For this tutorial, we'll show you how to capture an event in the middleware:
Create a folder middleware
in server
directory and then a new file analytics.js
in it:
cd ./servermkdir middlewarecd ./middlewaretouch analytics.js
Inside analytics.js
, create a middleware function. Initialize PostHog in it and capture an event:
import { PostHog } from 'posthog-node';export default defineEventHandler(async (event) => {const runtimeConfig = useRuntimeConfig();const posthog = new PostHog(runtimeConfig.public.posthogPublicKey,{ host: runtimeConfig.public.posthogHost });posthog.capture({distinctId: 'placeholder_distinct_id_of_the_user',event: 'in_the_middleware',});await posthog.shutdown()});
Note: Make sure to always call
await posthog.shutdown()
after capturing events from the server-side. PostHog queues events into larger batches, and this call forces all batched events to be flushed immediately.
If you run your app again, you should begin to see in_the_middleware
events in PostHog.
Handling distinctId
on the server
You may have noticed that we set distinctId: placeholder_distinct_id_of_the_user
in our event above. To attribute events to the correct user, distinctId
should be set to your user's unique ID. For logged-in users, we typically use their email as their distinctId
. However, for logged-out users, you can use the distinct_id
property from their PostHog cookie:
import { PostHog } from 'posthog-node';export default defineEventHandler(async (event) => {const runtimeConfig = useRuntimeConfig();const cookieString = event.node.req.headers.cookie || '';const cookieName =`ph_${runtimeConfig.public.posthogPublicKey}_posthog`;const cookieMatch = cookieString.match(new RegExp(cookieName + '=([^;]+)'));let distinctId;if (cookieMatch) {const parsedValue = JSON.parse(decodeURIComponent(cookieMatch[1]));if (parsedValue && parsedValue.distinct_id) {distinctId = parsedValue.distinct_id;const posthog = new PostHog(runtimeConfig.public.posthogPublicKey,{ host: runtimeConfig.public.posthogHost });posthog.capture({distinctId: distinctId,event: 'in_the_middleware',});await posthog.shutdown()}}});
Further reading
Subscribe to our newsletter
Product for Engineers
Join 25k+ subscribers learning how to build successful products and become better engineers.
We'll share your email with Substack