If you're building a modern web application with Next.js and care about user privacy while still getting meaningful analytics, Plausible is an excellent choice. Unlike Google Analytics, Plausible is lightweight (< 1KB), privacy-friendly (no cookies, GDPR compliant), and provides all the essential metrics you need without the bloat.

In this guide, I'll walk you through integrating Plausible Analytics with a Next.js 14 application using the App Router, including support for internationalization (i18n), custom events, and environment-specific configurations. This is the exact setup I use for Nibi, my financial planning app for Canadian freelancers.

Why Plausible + Next.js?

Before diving into the implementation, here's why this combination works so well:

  • Performance: Plausible's script is under 1KB, compared to Google Analytics' 45KB+
  • Privacy-First: No cookie banners needed, fully GDPR/CCPA compliant
  • Developer Experience: The next-plausible package provides TypeScript support and React hooks
  • Real-time Data: See your visitors in real-time without any delay
  • Simple Interface: Clean, focused dashboard without overwhelming features

Prerequisites

Before starting this tutorial, make sure you have:

  1. A Next.js 14+ project using the App Router (not Pages Router)
  2. A Plausible account (free 30-day trial available at plausible.io)
  3. Your domain added to Plausible (you'll get a snippet after adding your site)
  4. Node.js 18+ and npm or yarn installed
  5. Basic knowledge of Next.js and React

Your Plausible snippet will look something like this:

html

<script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.js"></script>

Step 1: Install the next-plausible Package

First, we'll install the official community package recommended by Plausible for Next.js integration:

bash

npm install next-plausible
# or
yarn add next-plausible
# or
pnpm add next-plausible

This package provides:

  • Optimized script loading for Next.js
  • TypeScript definitions
  • React hooks for custom events
  • Automatic handling of client-side navigation

Step 2: Basic Integration in App Router

The App Router structure in Next.js 14 is different from the Pages Router. Here's how to integrate Plausible:

For a Simple App (without i18n)

If you have a standard Next.js app without internationalization, update your root layout:

app/layout.tsx

tsx

import './globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import PlausibleProvider from 'next-plausible'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Your App Name',
  description: 'Your app description',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <PlausibleProvider domain="yourdomain.com" />
      </head>
      <body className={inter.className}>{children}</body>
    </html>
  )
}

For a Multilingual App (with i18n)

If you're using dynamic routing with locales like I do with Nibi, your structure might look like app/[locale]/layout.tsx:

app/[locale]/layout.tsx

tsx

import '../globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import PlausibleProvider from 'next-plausible'

const inter = Inter({ subsets: ['latin'] })

export async function generateStaticParams() {
  return [{ locale: 'en' }, { locale: 'fr' }]
}

export const metadata: Metadata = {
  title: 'Your App Name',
  description: 'Your app description',
}

export default function RootLayout({
  children,
  params: { locale }
}: {
  children: React.ReactNode
  params: { locale: string }
}) {
  return (
    <html lang={locale}>
      <head>
        <PlausibleProvider 
          domain="yourdomain.com"
          trackOutboundLinks
          taggedEvents
        />
      </head>
      <body className={inter.className}>{children}</body>
    </html>
  )
}

Step 3: Configure Advanced Features

Plausible offers several optional features that you can enable based on your needs:

tsx

<PlausibleProvider 
  domain="yourdomain.com"
  customDomain="https://plausible.io" // Use this if self-hosting
  trackOutboundLinks={true}            // Track clicks on external links
  trackFileDownloads={true}            // Track file download links
  taggedEvents={true}                  // Enable tagged events
  revenue={true}                       // Track revenue in custom events
  pageviewProps={{                     // Add custom properties to every pageview
    locale: locale,
    version: 'v1.0.0'
  }}
  selfHosted={false}                   // Set to true if self-hosting Plausible
  enabled={true}                        // Conditionally enable/disable tracking
/>

Step 4: Environment-Specific Configuration

You don't want to track development visits in your analytics. Here's how to set up environment-specific tracking:

Create Environment Variables

.env.local

bash

NEXT_PUBLIC_PLAUSIBLE_DOMAIN=yourdomain.com
NEXT_PUBLIC_PLAUSIBLE_ENABLED=true

.env.development

bash

NEXT_PUBLIC_PLAUSIBLE_ENABLED=false

Update Your Layout

tsx

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const isDevelopment = process.env.NODE_ENV === 'development'
  
  return (
    <html lang="en">
      <head>
        <PlausibleProvider 
          domain={process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN || "yourdomain.com"}
          enabled={!isDevelopment} // Only track in production
          trackOutboundLinks
          taggedEvents
        />
      </head>
      <body>{children}</body>
    </html>
  )
}

Step 5: Track Custom Events

One of Plausible's strengths is its simple yet powerful custom events. Here's how to implement them:

Basic Event Tracking

tsx

'use client'
import { usePlausible } from 'next-plausible'

export default function SignupButton() {
  const plausible = usePlausible()

  const handleClick = () => {
    // Track the event
    plausible('signup_click')
    
    // Your actual logic
    console.log('Signup clicked!')
  }

  return (
    <button onClick={handleClick}>
      Sign Up Now
    </button>
  )
}

Events with Properties

You can add metadata to your events for more detailed analytics:

tsx

'use client'
import { usePlausible } from 'next-plausible'

export default function PricingCard({ plan }: { plan: string }) {
  const plausible = usePlausible()

  const handleUpgrade = () => {
    // Track with properties
    plausible('upgrade_click', {
      props: {
        plan: plan,
        location: 'pricing_page',
        currency: 'USD'
      }
    })
  }

  return (
    <button onClick={handleUpgrade}>
      Upgrade to {plan}
    </button>
  )
}

Revenue Tracking

If you enabled revenue tracking, you can track monetary values:

tsx

plausible('purchase', {
  revenue: {
    amount: 99.99,
    currency: 'USD'
  },
  props: {
    product: 'lifetime_access'
  }
})

Step 6: Common Implementation Patterns

Track Form Submissions

tsx

'use client'
import { usePlausible } from 'next-plausible'
import { useState } from 'react'

export default function NewsletterForm() {
  const plausible = usePlausible()
  const [email, setEmail] = useState('')

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    
    try {
      await subscribeToNewsletter(email)
      
      // Track successful submission
      plausible('newsletter_signup', {
        props: {
          location: 'footer',
          type: 'early_access'
        }
      })
    } catch (error) {
      // Track failed submission
      plausible('newsletter_signup_error', {
        props: {
          error: error.message
        }
      })
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="your@email.com"
      />
      <button type="submit">Subscribe</button>
    </form>
  )
}

Track 404 Pages

app/not-found.tsx

tsx

'use client'
import { useEffect } from 'react'
import { usePlausible } from 'next-plausible'

export default function NotFound() {
  const plausible = usePlausible()
  
  useEffect(() => {
    plausible('404', { 
      props: { 
        path: window.location.pathname 
      }
    })
  }, [plausible])

  return (
    <div>
      <h1>404 - Page Not Found</h1>
      <p>The page you're looking for doesn't exist.</p>
    </div>
  )
}

Track Scroll Depth

tsx

'use client'
import { useEffect } from 'react'
import { usePlausible } from 'next-plausible'

export default function LongArticle() {
  const plausible = usePlausible()

  useEffect(() => {
    let maxScroll = 0

    const trackScroll = () => {
      const scrollPercentage = Math.round(
        (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100
      )

      if (scrollPercentage > maxScroll) {
        maxScroll = scrollPercentage

        // Track milestones
        if (maxScroll === 25 || maxScroll === 50 || maxScroll === 75 || maxScroll === 100) {
          plausible('scroll_depth', {
            props: {
              depth: `${maxScroll}%`
            }
          })
        }
      }
    }

    window.addEventListener('scroll', trackScroll)
    return () => window.removeEventListener('scroll', trackScroll)
  }, [plausible])

  return <article>{/* Your content */}</article>
}

Step 7: Configure Goals in Plausible Dashboard

After implementing event tracking, configure goals in your Plausible dashboard:

  1. Log in to your Plausible account
  2. Navigate to your site settings
  3. Click on "Goals"
  4. Add your custom events as goals:
    • signup_click
    • newsletter_signup
    • purchase
    • 404

This enables conversion tracking and funnel analysis.

Step 8: Deploy to Production

For Vercel Deployment

Add your environment variables to Vercel:

bash

vercel env add NEXT_PUBLIC_PLAUSIBLE_DOMAIN
# Enter: yourdomain.com

vercel env add NEXT_PUBLIC_PLAUSIBLE_ENABLED  
# Enter: true

Or via the Vercel Dashboard:

  1. Go to Project Settings β†’ Environment Variables
  2. Add NEXT_PUBLIC_PLAUSIBLE_DOMAIN = yourdomain.com
  3. Add NEXT_PUBLIC_PLAUSIBLE_ENABLED = true

Deploy your application:

bash

git add .
git commit -m "Add Plausible Analytics integration"
git push

Step 9: Verify Your Integration

Check Script Loading

  1. Open your production site
  2. Open Developer Tools β†’ Network tab
  3. Filter by "plausible"
  4. You should see the script loading

Test Events in Console

javascript

// Test if Plausible is loaded
window.plausible('test_event')

Check Real-time Dashboard

  1. Visit your site
  2. Go to your Plausible dashboard
  3. You should see yourself as a current visitor

Advanced Configuration

Self-Hosted Plausible

If you're self-hosting Plausible:

tsx

<PlausibleProvider 
  domain="yourdomain.com"
  customDomain="https://analytics.yourdomain.com"
  selfHosted={true}
/>

Proxy to Avoid Ad Blockers

Some ad blockers block Plausible. You can proxy the script through your domain:

next.config.js

javascript

module.exports = {
  async rewrites() {
    return [
      {
        source: '/stats/js/script.js',
        destination: 'https://plausible.io/js/script.js'
      },
      {
        source: '/stats/api/event',
        destination: 'https://plausible.io/api/event'
      }
    ]
  }
}

Then update your provider:

tsx

<PlausibleProvider 
  domain="yourdomain.com"
  customDomain="https://yourdomain.com/stats"
/>

Troubleshooting Common Issues

Issue: Events Not Tracking

Solution: Ensure you're using the hook inside client components:

tsx

'use client' // Don't forget this!
import { usePlausible } from 'next-plausible'

Issue: Development Visits Being Tracked

Solution: Check your environment configuration:

tsx

enabled={process.env.NODE_ENV === 'production'}

Issue: Script Not Loading

Solution: Ensure PlausibleProvider is in the <head> tag:

tsx

<head>
  <PlausibleProvider domain="yourdomain.com" />
</head>

Issue: TypeScript Errors

Solution: Install types if needed:

bash

npm install --save-dev @types/react

Best Practices

  1. Keep Event Names Consistent: Use snake_case for all events (e.g., button_click, not buttonClick or button-click)
  2. Limit Event Properties: Plausible has limits on custom properties. Keep them simple and meaningful.
  3. Don't Track PII: Never include personally identifiable information in events or properties.
  4. Use Meaningful Event Names: Instead of click, use header_navigation_click for better insights.
  5. Group Related Events: Use consistent naming patterns like form_submit, form_error, form_abandon.

Performance Considerations

The next-plausible package is already optimized, but here are additional tips:

  • The script is loaded with defer by default
  • It's only 1KB, so it won't impact your Core Web Vitals
  • Events are batched and sent asynchronously
  • No impact on Time to Interactive (TTI)

Conclusion

Integrating Plausible Analytics with Next.js 14's App Router is straightforward and provides a privacy-friendly alternative to traditional analytics platforms. With this setup, you get:

  • βœ… Lightweight analytics (< 1KB)
  • βœ… GDPR compliance without cookie banners
  • βœ… Real-time visitor data
  • βœ… Custom event tracking
  • βœ… TypeScript support
  • βœ… Great developer experience

The combination of Next.js and Plausible offers the perfect balance of performance, privacy, and insights. Your users will appreciate the privacy-first approach, and you'll still get all the metrics you need to improve your application.

Resources

Share this post