Bento Next.js guide

Install Bento’s Next.js SDK, wire up analytics in either router, and extend tracking with server-side endpoints plus TypeScript safety.

Whether you ship on the App Router or the classic Pages Router, Bento’s SDK drops straight into your layout. Use it to track page views, identify users, fire custom events, and bridge into Node/Edge APIs for secure workflows.

Getting started

Step 1

Install packages

Install the client-side SDK for analytics plus the optional Node SDK when you want authenticated API routes and server actions.

Using npm

bash
npm install @bentonow/bento-nextjs-sdk --save

Using npm

bash
npm install @bentonow/bento-node-sdk --save
Step 2

Configure environment

Store your Site UUID, publishable key, and optional secret key in `.env`. Generate keys from Bento > Account > Team Settings > Private API Keys.

Environment Configuration

tsx
NEXT_PUBLIC_BENTO_SITE_ID=your_site_uuid
BENTO_PUBLISHABLE_KEY=your_publishable_key
BENTO_SECRET_KEY=your_secret_key
Step 3

Drop analytics into your layout

The SDK ships two components—one for the App Router and one for the Pages Router. Each watches route changes and emits `view` events automatically.

Next.js 13+ (App Router)

app/layout.tsx

tsx
import { BentoAnalytics } from '@bentonow/bento-nextjs-sdk/analytics'

export default function RootLayout({
children,
  }: {
    children: React.ReactNode
  }) {
  return (
    <html lang="en">
      <body>
        <BentoAnalytics
          siteUuid={process.env.NEXT_PUBLIC_BENTO_SITE_ID!}
          userEmail="" // Will be set dynamically for logged-in users
        />
        {children}
      </body>
    </html>
  )
}

Next.js 12 (Pages Router)

pages/_app.tsx

tsx
import { BentoLegacyAnalytics } from '@bentonow/bento-nextjs-sdk/analytics/legacy'
import type { AppProps } from 'next/app'

export default function App({ Component, pageProps }: AppProps) {
return (
<>
  <BentoLegacyAnalytics
    siteUuid={process.env.NEXT_PUBLIC_BENTO_SITE_ID!}
    userEmail=""
  />
  <Component {...pageProps} />
</>
)
}

Beginner guide

Page view tracking

The analytics component wires into `usePathname()` (App Router) or `router.events` (Pages Router). Use the hook snippets below when you need manual control.

Track a simple page view

tsx
'use client'
import { useBentoAnalytics } from '@bentonow/bento-nextjs-sdk/analytics'

export function PageTracker({ userEmail }: { userEmail?: string }) {
  useBentoAnalytics(userEmail)
  return null
}

Track a simple page view

tsx
import { useBentoLegacyAnalytics } from '@bentonow/bento-nextjs-sdk/analytics/legacy'

export function PageTracker({ userEmail }: { userEmail?: string }) {
  useBentoLegacyAnalytics(userEmail)
  return null
}

User identification

Pass the signed-in user’s email to connect devices and sessions. Use the dynamic pattern inside layouts or call `window.bento.identify()` manually when auth state changes.

Dynamic User Identification

tsx
import { useUser } from '@/hooks/useUser' // Your auth hook
import { BentoAnalytics } from '@bentonow/bento-nextjs-sdk/analytics'

export default function Layout({ children }) {
  const { user } = useUser()

  return (
    <html>
      <body>
        <BentoAnalytics
          siteUuid={process.env.NEXT_PUBLIC_BENTO_SITE_ID!}
          userEmail={user?.email || ''}
        />
        {children}
      </body>
    </html>
  )
}

Manual User Identification

tsx
// Identify user after login
useEffect(() => {
  if (user?.email && window.bento) {
    window.bento.identify(user.email)
  }
}, [user])

Event tracking

Track button clicks, onboarding milestones, or anything else with the client helper. The provided React hook ensures events are debounced and SSR-safe.

Basic Event Tracking

tsx
const handleSignup = () => {
  window.bento?.track('signup', {
    source: 'landing_page',
    plan: 'free'
  })
}

const handlePurchase = () => {
  window.bento?.track('purchase', {
    product_id: 'pro_plan',
    amount: 99,
    currency: 'USD'
  })
}

React Hook for Event Tracking

tsx
import { useCallback } from 'react'

export function useBentoTracking() {
  const track = useCallback((event: string, data?: Record<string, any>) => {
    if (typeof window !== 'undefined' && window.bento) {
      window.bento.track(event, data)
    }
  }, [])

  const tag = useCallback((tagName: string) => {
    if (typeof window !== 'undefined' && window.bento) {
      window.bento.tag(tagName)
    }
  }, [])

  return { track, tag }
}

Usage

tsx
import { useBentoTracking } from '@/hooks/useBentoTracking'

export function SignupForm() {
  const { track } = useBentoTracking()

  const handleSubmit = (formData) => {
    track('form_submission', {
      form_type: 'signup',
      utm_source: formData.utm_source
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
    </form>
  )
}

Intermediate guide

Server-side integration

Use the Node SDK inside API routes, server actions, or edge functions when you need to mutate subscribers or track secure events.

Server-Side Configuration

tsx
import { Analytics } from '@bentonow/bento-node-sdk'

export const bento = new Analytics({
  siteUuid: process.env.NEXT_PUBLIC_BENTO_SITE_ID!,
  publishableKey: process.env.BENTO_PUBLISHABLE_KEY!,
  secretKey: process.env.BENTO_SECRET_KEY!
})

API Route Examples App Router

tsx
import { bento } from '@/lib/bento'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  try {
    const { email, event, data } = await request.json()

    await bento.V1.track({
      email,
      type: event,
      fields: data
    })

    return NextResponse.json({ success: true })
  } catch (error) {
    return NextResponse.json({ error: 'Tracking failed' }, { status: 500 })
  }
}

API Route Examples Pages Router

tsx
import { bento } from '@/lib/bento'
import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' })
  }

  try {
    const { email, firstName, lastName } = req.body

    await bento.V1.importSubscribers({
      subscribers: [{
        email,
        firstName,
        lastName,
        tags: ['newsletter_signup']
      }]
    })

    res.status(200).json({ success: true })
  } catch (error) {
    res.status(500).json({ error: 'Subscription failed' })
  }
}

Advanced configuration

Control how and when the Bento script loads. Lazy-load it, target specific segments, or skip it entirely in dev and staging builds.

Custom Script Loading

tsx
'use client'
import Script from 'next/script'
import { useEffect, useState } from 'react'

interface BentoScriptProps {
  siteUuid: string
  userEmail?: string
  onLoad?: () => void
}

export function BentoScript({ siteUuid, userEmail, onLoad }: BentoScriptProps) {
  const [isLoaded, setIsLoaded] = useState(false)

  useEffect(() => {
    if (isLoaded && userEmail && window.bento) {
      window.bento.identify(userEmail)
    }
  }, [isLoaded, userEmail])

  return (
    <Script
      src={`https://fast.bentonow.com?site_uuid=${siteUuid}`}
      strategy="afterInteractive"
      onLoad={() => {
        setIsLoaded(true)
        onLoad?.()
      }}
    />
  )
}

Conditional Loading for Segments

tsx
export function shouldLoadBento(): boolean {
  // Don't load in development
  if (process.env.NODE_ENV !== 'production') return false

  // Don't load for admin users
  const isAdmin = /* your admin check */
  if (isAdmin) return false

  return true
}

Conditional Load when in Production

tsx
import { shouldLoadBento } from '@/lib/shouldLoadBento'

export default function Layout({ children }) {
  return (
    <html>
      <body>
        {shouldLoadBento() && (
          <BentoAnalytics siteUuid={process.env.NEXT_PUBLIC_BENTO_SITE_ID!} />
        )}
        {children}
      </body>
    </html>
  )
}

TypeScript support

Import the provided types to strongly type events and payloads. Typed helpers keep analytics predictable across large codebases.

Type Definitions

tsx
declare global {
  interface Window {
    bento?: {
      view: () => void
      identify: (email: string) => void
      track: (event: string, data?: Record<string, any>) => void
      tag: (tag: string) => void
      updateFields: (fields: Record<string, any>) => void
      showChat: () => void
      hideChat: () => void
      openChat: () => void
      closeChat: () => void
      getEmail: () => string | undefined
      spamCheck: (email: string) => Promise<boolean>
    }
  }
}

Usage

tsx
import { useCallback } from 'react'
import type { BentoEvent } from '@/types/events'

export function useTypedBentoTracking() {
  const track = useCallback((event: BentoEvent) => {
    if (typeof window !== 'undefined' && window.bento) {
      const { type, ...data } = event
      window.bento.track(type, data)
    }
  }, [])

  return { track }
}

Advanced guide

Custom events

Model ecommerce funnels, onboarding journeys, or feature adoption with bespoke events so automations can react instantly.

Get all existing tags

tsx
const trackPurchase = (orderData: {
  orderId: string
  items: Array<{
    productId: string
    name: string
    price: number
    quantity: number
  }>
  total: number
  email: string
}) => {
  window.bento?.track('purchase', {
    unique: orderData.orderId, // Prevent counting orders multiple times
    value: {
      amount: orderData.total,
      currency: 'USD'
    },
    details: {
      items: orderData.items,
      order_id: orderData.orderId
    }
  })
}

Track user progression

tsx
const trackOnboarding = (step: string, completed: boolean) => {
  window.bento?.track('onboarding_step', {
    step_name: step,
    completed,
    timestamp: new Date().toISOString()
  })
}

Track feature usage

tsx
const trackFeatureUsage = (feature: string, context?: Record<string, any>) => {
  window.bento?.track('feature_used', {
    feature_name: feature,
    ...context
  })
}

Best practices

  • Lazy-load analytics when possible to keep TTI fast.
  • Batch similar events when you expect high frequency interactions.
  • Debounce handlers so scroll or drag events don’t spam Bento.

Debounced Tracking

tsx
import { debounce } from 'lodash'

const debouncedTrack = debounce((event: string, data: any) => {
  window.bento?.track(event, data)
}, 300)

export { debouncedTrack }

Error Handling

tsx
export function safeTrack(event: string, data?: Record<string, any>) {
  try {
    if (typeof window !== 'undefined' && window.bento) {
      window.bento.track(event, data)
    }
  } catch (error) {
    console.warn('Bento tracking failed:', error)
    // Optional: Send to error monitoring service
  }
}

API reference

Method Description Example
window.bento.view() Track a page view manually. window.bento.view()
window.bento.identify(email) Identify a visitor. window.bento.identify("user@example.com")
window.bento.track(event, data) Track a custom event. window.bento.track("signup", { plan: "pro" })
window.bento.tag(tag) Apply a tag. window.bento.tag("premium_user")
Component Props Description
BentoAnalytics siteUuid, userEmail? App Router analytics component.
BentoLegacyAnalytics siteUuid, userEmail? Pages Router analytics component.
Hook Parameters Returns Description
useBentoAnalytics userEmail? void App Router helper.
useBentoLegacyAnalytics userEmail? void Pages Router helper.

Troubleshooting

Issue Solution
Script not loading Confirm `NEXT_PUBLIC_BENTO_SITE_ID` is set and available in the browser.
Events not tracking Guard on `window.bento` before calling methods and ensure analytics is mounted once.
TypeScript errors Import types from `@bentonow/bento-nextjs-sdk/types`.
Hydration mismatches Wrap analytics in a dynamic import with `ssr: false` where needed.

Debugging

tsx
if (typeof window !== 'undefined') {
  window.addEventListener('load', () => {
    console.log('Bento loaded:', !!window.bento)
    if (window.bento) {
      console.log('Current user:', window.bento.getEmail())
    }
  })
}

FAQ

Can I use the App Router and Pages Router simultaneously?

Pick the component that matches your routing system—`BentoAnalytics` for App Router, `BentoLegacyAnalytics` for Pages Router.

How should I handle logout?

Call `window.bento.identify('')` or pass an empty string to the component’s `userEmail` prop to clear context.

Is the SDK compatible with React Strict Mode?

Yes. Event handlers are debounced so interactions don’t double fire during dev-only renders.

Can I customize the script loading strategy?

Absolutely. Wrap Bento in the Next.js `Script` component and choose `lazyOnload`, `afterInteractive`, or even `beforeInteractive` when you need earliest tracking.

How do I track events from server components?

Server components can’t call client-side APIs directly. Send data to a client component or trigger the Node SDK from a server action.

Server Component

tsx
async function ServerComponent() {
const data = await fetchData()
return <ClientTracker data={data} />
}

Client Component

tsx
'use client'
function ClientTracker({ data }) {
  useEffect(() => {
    window.bento?.track('data_loaded', { count: data.length })
  }, [data])
return null
}

Need the original Markdown? Open raw file