Back to blog

Seamless Posthog Product Analytics in Your Vue 3 App

At Juuno, we're obsessed with user experience. Our digital signage solution isn't just powerful; it's the easiest to use in the market. You’ll have your screen setup and attracting traffic into your business within minutes.

How? Decades of experience in our team have taught us that great UX starts with instinct, inspiration, and user empathy. Analytics are powerful, but come later - to validate and refine an idea. It's heart first, then brain.

In this post, we focus on the brain: integrating Posthog, our product analytics tool of choice, into our Vue 3 app. We'll cover:

  1. Setting up Posthog in Vue 3
  2. Tracking page views with Vue Router
  3. Handling user identification and logout

In a few minutes you'll have access to detailed product analytics, funnels, and more while maintaining Vue 3 best practices and an idiomatic code base.

Vue 3

Juuno is powered by Vue.js, a popular UI framework. More specifically, our setup is:

  • Vue 3
  • Composition API
  • Single-File Components (SFC)
  • <script setup />
  • Typescript

// ComponentA.vue
<script setup>
import ComponentB from './ComponentB.vue'
const count = ref<boolean>(0)
</script>

<template>
  <ComponentB count={count} />
</template>

We find this setup provides a great developer experience for quickly building scalable and performant apps, and this is also the setup you’ll get out of the box with pnpm create vue@latest.

Next, let’s dive into how we integrate Posthog with our application.

Posthog

PostHog has a guide for Vue integration; however, it falls short of showing how you’d integrate into a real-world Vue app.

Let’s build an “idiomatic Vue” solution on top of their Method 2: Use provide / inject:

// main.ts
import { createApp } from 'vue';
import { createPosthog } from './posthog';

// Create your Vue app as you normally would ...
const app = createApp()
createPosthog(app)

Now to implement createPosthog():

// posthog.ts
import posthog, { type PostHog } from 'posthog-js';
import { inject, type App, type InjectionKey } from 'vue';

export function createPosthog(app: App) {
  // Read public Posthog token from Vite env vars.
  // See: https://vitejs.dev/guide/env-and-mode
  const posthogToken = import.meta.env.VUE_APP_POSTHOG_TOKEN;
  
  posthog.init(posthogToken, {
    api_host: 'https://us.i.posthog.com',
    // Only create a Posthog user profile when we identify the user (log in).
    person_profiles: 'identified_only',
    // Posthog can't automatically capture SPA pageviews so don't try. We'll configure Vue Router later.
    // See: https://posthog.com/docs/libraries/vue-js#capturing-pageviews
    capture_pageview: false,
    loaded: function (ph) {
      if (import.meta.env.DEV) {
        // Log debugging information to the console.
        posthog.debug(true);
      }

      // Only capture Posthog data in production.
      // See: https://posthog.com/tutorials/multiple-environments#opt-out-of-capturing-on-initialization
      if (!import.meta.env.PROD) {
        ph.opt_out_capturing(); // opts a user out of event capture
        ph.set_config({ disable_session_recording: true });
      }
    },
  });

  // Provide the initialised posthog object to the entire app.
  // See: https://vuejs.org/guide/components/provide-inject#app-level-provide
  app.provide(posthogContextKey, posthog);
}

// Symbol key for injection to ensure uniqueness.
// See: https://vuejs.org/guide/components/provide-inject#working-with-symbol-keys
export const posthogContextKey: InjectionKey<PostHog> =
  Symbol('PosthogContext');

// Composable to access the posthog object from other parts of the app.
export function usePosthog() {
  const posthog = inject(posthogContextKey);

  if (!posthog) {
    throw new Error('No Posthog found.');
  }

  return posthog;
}

Here’s how you’d use the composable to access the `posthog` object in a component and capture an event:

// AppSignup.vue
import { usePosthog } from '../posthog'
const posthog = usePosthog();

const onSubmit = async () => {
  // ... register the user
  posthog.capture('user signed up');
}

<template>
  // ... your component's template
</template>

This approach leans into Vue 3 composition patterns and explicitly injects posthog, making the source of data clear and expected.

Here’s how you’d use this approach in conjunction with Vue Router to capture page views using an afterEach() guard:

// router.ts
import { isNavigationFailure } from 'vue-router';

const router = createRouter({ 
  routes: [
    // ... my routes
  ] 
}

router.afterEach((to, _from, failure) => {
  const posthog = usePosthog();

  if (!isNavigationFailure(failure)) {
    // nextTick() to fire after the page is mounted.
    void nextTick(() => {
      posthog.capture('$pageview', {
        path: to.fullPath,
      });
    });
  }
});

And in our auth layer to identify the user in Posthog when they login:

// auth.ts
// ... example auth code, yours will be specific to your app.
const auth = new DummyAuthenticator();

auth.onLoggedIn(() => {
  const posthog = usePosthog();
  posthog.identify(auth.profile.idx, {
    email: auth.profile.email,
  });
});

auth.onLogOut(() => {
  const posthog = usePosthog();
  posthog.reset();
});

Wrapping up

By following this approach, you can easily gain valuable insights into your users' behaviour while maintaining a clean, idiomatic Vue codebase.

Please share any questions or comments in the Juuno Facebook group.

Lastly, why not give Juuno, our digital signage solution a try? It's a perfect example of how powerful analytics can drive an intuitive user experience.

Sign up now.

Back to blog