DEV Community

Cover image for A look at implementing PostHog analytics in a Next.js AI tool - starting the analytics journey
Emanuel Reis
Emanuel Reis

Posted on

A look at implementing PostHog analytics in a Next.js AI tool - starting the analytics journey

As I continue building my AI Product Description Generator, I realized I needed to understand how people would actually use the tool. After researching different options, I decided to add PostHog analytics to track user behavior and make data-driven improvements.

Why PostHog?

When choosing an analytics solution, several factors made PostHog stand out:

  1. Open Source: The entire platform is open source, which aligns with my development values
  2. Developer-First: Built specifically with developers in mind
  3. Next.js Integration: Clean documentation and easy setup with my tech stack
  4. Session Recordings: The ability to see how users interact with the UI
  5. Self-hostable: Option to self-host in the future if needed

Setting Up PostHog in Next.js

The setup process was straightforward. Here's how I added PostHog to my project:

  1. First, install the PostHog package:
npm install --save posthog-js
# or
yarn add posthog-js
# or
pnpm add posthog-js
Enter fullscreen mode Exit fullscreen mode
  1. Add your environment variables to .env.local:
NEXT_PUBLIC_POSTHOG_KEY=your-project-key
NEXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com
Enter fullscreen mode Exit fullscreen mode

These variables need to start with NEXT_PUBLIC_ to be accessible on the client side.

  1. For my Next.js app using the App Router, I created two key files:

First, a providers file for PostHog initialization:

// app/providers.tsx
'use client'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
import { useEffect } from 'react'
const PostHogPageView = dynamic(() => import('./PostHogPageView'), {
  ssr: false,
})

export function PHProvider({
  children,
}: {
  children: React.ReactNode
}) {
  useEffect(() => {
      posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
        api_host: "/ingest",
        ui_host: "https://eu.posthog.com",
        person_profiles: 'identified_only',
        capture_pageview: false,
        capture_pageleave: true
      })
  }, []);

  return (
    <PostHogProvider client={posthog}>
      <PostHogPageView/>
      {children}
    </PostHogProvider>
  )
}
Enter fullscreen mode Exit fullscreen mode

Note: We use dynamic import for PostHogPageView because it contains the useSearchParams hook, which would otherwise force the entire app into client-side rendering.

Then, a component to handle page view tracking (since Next.js is a single-page app):

// app/PostHogPageView.tsx
'use client'

import { usePathname, useSearchParams } from "next/navigation"
import { useEffect } from "react"
import { usePostHog } from 'posthog-js/react'

export default function PostHogPageView(): null {
  const pathname = usePathname()
  const searchParams = useSearchParams()
  const posthog = usePostHog()

  useEffect(() => {
    if (pathname && posthog) {
      let url = window.origin + pathname
      if (searchParams.toString()) {
        url = url + `?${searchParams.toString()}`
      }
      posthog.capture(
        '$pageview',
        {
          '$current_url': url,
        }
      )
    }
  }, [pathname, searchParams, posthog])

  return null
}
Enter fullscreen mode Exit fullscreen mode

Finally, I integrated these components in my root layout:

// app/layout.tsx
import './globals.css'
import { PHProvider } from './providers'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <PHProvider>
        <body>
          <PostHogPageView />
          {children}
        </body>
      </PHProvider>
    </html>
  )
}
Enter fullscreen mode Exit fullscreen mode

Setting Up a Reverse Proxy

To improve privacy and avoid ad-blockers, I also set up a reverse proxy using Next.js rewrites. Here's how:

  1. First, I added the proxy configuration to next.config.js:
// next.config.js
const nextConfig = {
  async rewrites() {
    return [
      {
        source: "/ingest/static/:path*",
        destination: "https://us-assets.i.posthog.com/static/:path*",
      },
      {
        source: "/ingest/:path*",
        destination: "https://us.i.posthog.com/:path*",
      },
      {
        source: "/ingest/decide",
        destination: "https://us.i.posthog.com/decide",
      },
    ];
  },
  // Required to support PostHog trailing slash API requests
  skipTrailingSlashRedirect: true,
}

module.exports = nextConfig
Enter fullscreen mode Exit fullscreen mode
  1. Then, I updated the PostHog initialization to use the proxy:
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
  api_host: "/ingest",
  ui_host: 'https://eu.posthog.com'  // Adjust if you're using EU cloud
  // ... other options
})
Enter fullscreen mode Exit fullscreen mode

Note: If you're using PostHog's EU cloud (like I am), replace us with eu in all domains in the next.config.js file.

What I'm Planning to Track

I'm starting with basic events to understand user behavior:

  1. Core User Actions:
// Track when users attempt to generate descriptions
function handleGenerate() {
  posthog.capture('generate_description', {
    productType: product.type,
    inputLength: product.input.length
  })
}

// Track successful generations
function onGenerationSuccess(result) {
  posthog.capture('generation_success', {
    responseLength: result.length,
    timeToGenerate: performance.now() - startTime
  })
}

// Track errors
function onGenerationError(error) {
  posthog.capture('generation_error', {
    errorType: error.type,
    errorMessage: error.message
  })
}
Enter fullscreen mode Exit fullscreen mode
  1. User Flow Events:
// Track form interactions
function trackFormInteraction(fieldName: string, action: string) {
  posthog.capture('form_interaction', {
    field: fieldName,
    action: action // focus, blur, change, etc.
  })
}
Enter fullscreen mode Exit fullscreen mode

Questions I Want to Answer

By implementing analytics, I'm hoping to understand:

  1. User Behavior
  2. How many descriptions do users typically generate?
  3. What types of products are they describing?
  4. Where do users get stuck in the process?

  5. Performance Metrics

  6. How long do generations typically take?

  7. Are there common error patterns?

  8. What's the success rate of generations?

  9. Usage Patterns

  10. What times are most active?

  11. Which features are used most?

  12. Do users return for multiple sessions?

Next Steps

Now that the basic setup is complete, my next steps are:

  1. Create Funnels
  2. Track the complete user journey
  3. Identify drop-off points
  4. Measure conversion rates

  5. Set Up A/B Testing

  6. Test different UI layouts

  7. Experiment with form fields

  8. Try various generation prompts

  9. Monitor Performance

  10. Track load times

  11. Measure API response times

  12. Identify bottlenecks

Learning in Public

This is just the beginning of my analytics journey. I'll be sharing what I learn as I gather real user data and make improvements based on these insights.

Some questions I'm curious about:

  • What metrics do you track in your projects?
  • How do you balance privacy with data collection?
  • What analytics insights have surprised you the most?

I'd love to hear about your experiences with analytics and any suggestions for what I should be tracking. Drop your thoughts in the comments!


Keep following my journey:

Let's learn and build together! ๐Ÿš€

Top comments (0)