Files
zeekay 10e6fec4c0 debrand: the brand token is INSIGHTS_, and a wrong one stops the container
The rule table mapped HOG_ -> FN_ ahead of any POSTHOG rule, so POSTHOG_FOO
split into POST + HOG_ and came out POSTFN_FOO. Nothing sets that. The live
pods set INSIGHTS_SKIP_MIGRATION_CHECKS, which bin/migrate-check reads on the
container entrypoint, so the misspelling does not degrade a feature -- it runs
migrate --check against a database whose history this tree does not share and
the container never starts.

Both replacements are measured against the shipped tree rather than chosen:
every HOG_ name with a counterpart there is spelled INSIGHTS_ (HOG_QL ->
INSIGHTS_QL, HOG_FUNCTION_TEMPLATES -> INSIGHTS_FUNCTION_TEMPLATES, 12 of 12
determinate cases), and FN_ appears in it nowhere. Upstream carries 95 HOG_
tokens and zero FN_ ones, so repairing FN_ back out is exactly the inverse of
the bad rule and cannot collide with an upstream name.

The rule is fixed for the next convergence, and the tree it already produced is
repaired in the same commit -- the codemod is idempotent against its own output,
so it cannot undo this itself.
2026-08-01 17:42:28 -07:00

465 lines
23 KiB
TypeScript

import { OnboardingComponentsContext, createInstallation } from 'scenes/onboarding/shared/OnboardingDocsContentWrapper'
import { StepDefinition } from '../steps'
import { SDK_DEFAULTS_DATE } from './_snippets/sdkDefaults'
export const getNextJSClientSteps = (ctx: OnboardingComponentsContext): StepDefinition[] => {
const { CodeBlock, Markdown, CalloutBox, Tab, dedent } = ctx
return [
{
title: 'Install the package',
badge: 'required',
content: (
<>
<Markdown>Install the Insights JavaScript library using your package manager:</Markdown>
<CodeBlock
blocks={[
{
language: 'bash',
file: 'npm',
code: dedent`
npm install insights-js
`,
},
{
language: 'bash',
file: 'yarn',
code: dedent`
yarn add insights-js
`,
},
{
language: 'bash',
file: 'pnpm',
code: dedent`
pnpm add insights-js
`,
},
]}
/>
</>
),
},
{
title: 'Add environment variables',
badge: 'required',
content: (
<>
<Markdown>
Add your Insights project token and host to your `.env.local` file and to your hosting provider
(e.g. Vercel, Netlify). These values need to start with `NEXT_PUBLIC_` to be accessible on the
client-side.
</Markdown>
<CodeBlock
blocks={[
{
language: 'bash',
file: '.env.local',
code: dedent`
NEXT_PUBLIC_INSIGHTS_PROJECT_TOKEN=<ph_project_token>
NEXT_PUBLIC_INSIGHTS_HOST=<ph_client_api_host>
`,
},
]}
/>
</>
),
},
{
title: 'Initialize Insights',
badge: 'required',
content: (
<>
<Markdown>Choose the integration method based on your Next.js version and router type.</Markdown>
<Tab.Group tabs={['Next.js 15.3+', 'App router', 'Pages router']}>
<Tab.List>
<Tab>Next.js 15.3+</Tab>
<Tab>App router</Tab>
<Tab>Pages router</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<Markdown>
If you're using Next.js 15.3+, you can use `instrumentation-client.ts` for a
lightweight, fast integration:
</Markdown>
<CodeBlock
blocks={[
{
language: 'typescript',
file: 'instrumentation-client.ts',
code: dedent`
import insights from 'insights-js'
insights.init(process.env.NEXT_PUBLIC_INSIGHTS_PROJECT_TOKEN!, {
api_host: process.env.NEXT_PUBLIC_INSIGHTS_HOST,
defaults: '${SDK_DEFAULTS_DATE}'
})
`,
},
]}
/>
</Tab.Panel>
<Tab.Panel>
<Markdown>
For the App router, create a `providers.tsx` file in your `app` folder. The
`insights-js` library needs to be initialized on the client-side using the `'use
client'` directive:
</Markdown>
<CodeBlock
blocks={[
{
language: 'typescript',
file: 'app/providers.tsx',
code: dedent`
'use client'
import { usePathname, useSearchParams } from "next/navigation"
import { useEffect } from "react"
import insights from 'insights-js'
import { InsightsProvider as PHProvider } from '@hanzo/react'
export function InsightsProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
insights.init(process.env.NEXT_PUBLIC_INSIGHTS_PROJECT_TOKEN as string, {
api_host: process.env.NEXT_PUBLIC_INSIGHTS_HOST,
defaults: '${SDK_DEFAULTS_DATE}'
})
}, [])
return (
<PHProvider client={insights}>
{children}
</PHProvider>
)
}
`,
},
]}
/>
<Markdown>
Then import the `InsightsProvider` component in your `app/layout.tsx` and wrap your
app with it:
</Markdown>
<CodeBlock
blocks={[
{
language: 'typescript',
file: 'app/layout.tsx',
code: dedent`
import './globals.css'
import { InsightsProvider } from './providers'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<InsightsProvider>
{children}
</InsightsProvider>
</body>
</html>
)
}
`,
},
]}
/>
</Tab.Panel>
<Tab.Panel>
<Markdown>
For the Pages router, integrate Insights at the root of your app in `pages/_app.tsx`:
</Markdown>
<CodeBlock
blocks={[
{
language: 'typescript',
file: 'pages/_app.tsx',
code: dedent`
import { useEffect } from 'react'
import { Router } from 'next/router'
import insights from 'insights-js'
import { InsightsProvider } from '@hanzo/react'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
useEffect(() => {
insights.init(process.env.NEXT_PUBLIC_INSIGHTS_PROJECT_TOKEN as string, {
api_host: process.env.NEXT_PUBLIC_INSIGHTS_HOST,
defaults: '${SDK_DEFAULTS_DATE}',
loaded: (insights) => {
if (process.env.NODE_ENV === 'development') insights.debug()
}
})
}, [])
return (
<InsightsProvider client={insights}>
<Component {...pageProps} />
</InsightsProvider>
)
}
`,
},
]}
/>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
<CalloutBox type="fyi" title="Defaults option">
<Markdown>
The `defaults` option automatically configures Insights with recommended settings for new
projects. See [SDK defaults](https://hanzo.ai/docs/libraries/js#sdk-defaults) for
details.
</Markdown>
</CalloutBox>
</>
),
},
{
title: 'Accessing Insights on the client',
badge: 'recommended',
content: (
<>
<Tab.Group tabs={['Next.js 15.3+', 'App/Pages router']}>
<Tab.List>
<Tab>Next.js 15.3+</Tab>
<Tab>App/Pages router</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<Markdown>
Once initialized in `instrumentation-client.ts`, import `insights` from `insights-js`
anywhere and call the methods you need:
</Markdown>
<CodeBlock
blocks={[
{
language: 'typescript',
file: 'app/checkout/page.tsx',
code: dedent`
'use client'
import insights from 'insights-js'
export default function CheckoutPage() {
function handlePurchase() {
insights.capture('purchase_completed', { amount: 99 })
}
return <button onClick={handlePurchase}>Complete purchase</button>
}
`,
},
]}
/>
</Tab.Panel>
<Tab.Panel>
<Markdown>Use the `useInsights` hook to access Insights in client components:</Markdown>
<CodeBlock
blocks={[
{
language: 'typescript',
file: 'app/checkout/page.tsx',
code: dedent`
'use client'
import { useInsights } from '@hanzo/react'
export default function CheckoutPage() {
const insights = useInsights()
function handlePurchase() {
insights.capture('purchase_completed', { amount: 99 })
}
return <button onClick={handlePurchase}>Complete purchase</button>
}
`,
},
]}
/>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
</>
),
},
]
}
export const getNextJSServerSteps = (ctx: OnboardingComponentsContext): StepDefinition[] => {
const { CodeBlock, Markdown, CalloutBox, Tab, dedent } = ctx
return [
{
title: 'Server-side setup',
badge: 'optional',
content: (
<>
<Markdown>To capture events from API routes or server actions, install `insights-node`:</Markdown>
<CodeBlock
blocks={[
{
language: 'bash',
file: 'npm',
code: dedent`
npm install insights-node
`,
},
{
language: 'bash',
file: 'yarn',
code: dedent`
yarn add insights-node
`,
},
{
language: 'bash',
file: 'pnpm',
code: dedent`
pnpm add insights-node
`,
},
]}
/>
<Markdown>
Then, initialize Insights in your API route or server action. Choose the method based on your
router type:
</Markdown>
<Tab.Group tabs={['App router', 'Pages router']}>
<Tab.List>
<Tab>App router</Tab>
<Tab>Pages router</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<Markdown>
For the App router, you can use Insights in API routes or server actions. Create a
new Insights client instance for each request, or reuse a singleton instance across
requests:
</Markdown>
<CodeBlock
blocks={[
{
language: 'typescript',
file: 'app/api/example/route.ts',
code: dedent`
import { Insights } from 'insights-node'
export async function POST(request: Request) {
const insights = new Insights(process.env.NEXT_PUBLIC_INSIGHTS_PROJECT_TOKEN!, {
host: process.env.NEXT_PUBLIC_INSIGHTS_HOST
})
insights.capture({
distinctId: 'distinct_id_of_the_user',
event: 'event_name'
})
await insights.shutdown()
}
`,
},
]}
/>
<Markdown>You can also use Insights in server actions:</Markdown>
<CodeBlock
blocks={[
{
language: 'typescript',
file: 'app/actions.ts',
code: dedent`
'use server'
import { Insights } from 'insights-node'
export async function myServerAction() {
const insights = new Insights(process.env.NEXT_PUBLIC_INSIGHTS_PROJECT_TOKEN!, {
host: process.env.NEXT_PUBLIC_INSIGHTS_HOST
})
insights.capture({
distinctId: 'distinct_id_of_the_user',
event: 'server_action_completed'
})
await insights.shutdown()
}
`,
},
]}
/>
</Tab.Panel>
<Tab.Panel>
<Markdown>For the Pages router, use Insights in your API routes:</Markdown>
<CodeBlock
blocks={[
{
language: 'typescript',
file: 'pages/api/example.ts',
code: dedent`
import { Insights } from 'insights-node'
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const insights = new Insights(process.env.NEXT_PUBLIC_INSIGHTS_PROJECT_TOKEN!, {
host: process.env.NEXT_PUBLIC_INSIGHTS_HOST
})
insights.capture({
distinctId: 'distinct_id_of_the_user',
event: 'event_name'
})
await insights.shutdown()
res.status(200).json({ success: true })
}
`,
},
]}
/>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
<CalloutBox type="fyi" title="Important">
<Markdown>
Always call `await insights.shutdown()` when you're done with the client to ensure all events
are flushed before the request completes. For better performance, consider creating a
singleton Insights instance that you reuse across requests.
</Markdown>
</CalloutBox>
</>
),
},
]
}
export const getNextJSSteps = (ctx: OnboardingComponentsContext): StepDefinition[] => {
const { snippets } = ctx
const JSEventCapture = snippets?.JSEventCapture
return [
...getNextJSClientSteps(ctx),
...getNextJSServerSteps(ctx),
{
title: 'Send events',
badge: 'recommended',
content: <>{JSEventCapture && <JSEventCapture />}</>,
},
]
}
export const NextJSInstallation = createInstallation(getNextJSSteps)