Skip to content

Commit

Permalink
docs: Update docs to suggest using Suspense around PageView (#10102)
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaeelaudibert authored Dec 11, 2024
1 parent cc569ff commit 05e5c12
Showing 1 changed file with 150 additions and 146 deletions.
296 changes: 150 additions & 146 deletions contents/docs/integrate/_snippets/nextjs/app-router-setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,236 +2,240 @@ If your Next.js app to uses the [app router](https://nextjs.org/docs/app), you c

<MultiLanguage>

```js
// app/providers.js
```jsx
// app/providers.jsx
'use client'

import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
import { useEffect } from 'react'

export function PHProvider({ children }) {
export function PostHogProvider({ children }) {

This comment has been minimized.

Copy link
@fatihcelikbas

fatihcelikbas Dec 11, 2024

You are importing PostHogProvider and export a new function with the same name: PostHogProvider

This broke the whole docs for Next.JS

useEffect(() => {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
person_profiles: 'identified_only',
capture_pageview: false // Disable automatic pageview capture, as we capture manually
})
}, []);
}, [])

return <PostHogProvider client={posthog}>{children}</PostHogProvider>
return (
<PostHogProvider client={posthog}>
{children}
</PostHogProvider>
)
}
```

```tsx
// app/providers.tsx
'use client'

import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
import { useEffect } from 'react'

export function PHProvider({
children,
}: {
children: React.ReactNode
}) {
export function PostHogProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
person_profiles: 'identified_only',
capture_pageview: false // Disable automatic pageview capture, as we capture manually
})
}, []);
}, [])

return (
<PostHogProvider client={posthog}>
{children}
</PostHogProvider>
)
}
```

</MultiLanguage>

Then, import the `PostHogProvider` component into your `app/layout` file and wrap your app with it.

<MultiLanguage>

```jsx
// app/layout.jsx

import './globals.css'
import { PostHogProvider } from './providers'

export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<PostHogProvider>
{children}
</PostHogProvider>
</body>
</html>
)
}
```

```tsx
// app/layout.tsx

return <PostHogProvider client={posthog}>{children}</PostHogProvider>
import './globals.css'
import { PostHogProvider } from './providers'

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<PostHogProvider>
{children}
</PostHogProvider>
</body>
</html>
)
}
```

</MultiLanguage>

PostHog is now set up and ready to go. Files and components accessing PostHog on the client-side need the `'use client'` directive.

#### Pageview

PostHog's `$pageview` autocapture relies on page load events. Since Next.js acts as a single-page app, this event doesn't trigger on navigation and we need to capture `$pageview` events manually.

To do this, we set up a `PostHogPageView` component to listen to URL path changes:

<MultiLanguage>

```js
```jsx
// app/PostHogPageView.jsx
'use client'

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

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

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

posthog.capture('$pageview', { '$current_url': url })
}
}, [pathname, searchParams, posthog])

return null
}

// Wrap this in Suspense to avoid the `useSearchParams` usage above
// from deopting the whole app into client-side rendering
// See https://nextjs.org/docs/messages/deopted-into-client-rendering
export default SuspendedPostHogPageView() {
return <Suspense fallback={null}>
<PostHogPageView />
</Suspense>
}
```

```ts
```tsx
// app/PostHogPageView.tsx
'use client'

import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";
import { usePostHog } from 'posthog-js/react';
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();
function PostHogPageView() : null {
const pathname = usePathname()
const searchParams = useSearchParams()
const posthog = usePostHog()

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

posthog.capture('$pageview', { '$current_url': url })
}
}, [pathname, searchParams, posthog])

return null
}
```

// Wrap this in Suspense to avoid the `useSearchParams` usage above
// from deopting the whole app into client-side rendering
// See https://nextjs.org/docs/messages/deopted-into-client-rendering
export default SuspendedPostHogPageView() {
return <Suspense fallback={null}>
<PostHogPageView />
</Suspense>
}
```
</MultiLanguage>

Then, import the `PHProvider` component into your `app/layout` file and wrap your app with it. We also dynamically import the `PostHogPageView` component and include it as a child of `PHProvider`.

> **Why is `PostHogPageView` dynamically imported?** It contains the [`useSearchParams`](https://nextjs.org/docs/app/api-reference/functions/use-search-params) hook, which [deopts](https://nextjs.org/docs/messages/deopted-into-client-rendering) the entire app into client-side rendering if it is not dynamically imported.
<MultiLanguage>
We can then update our `PostHogProvider` to include this component in all of our pages.

```js
// app/layout.js
```diff
// app/providers.js
'use client'

import './globals.css'
import { PHProvider } from './providers'
import dynamic from 'next/dynamic'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
import { useEffect } from 'react'
+ import PostHogPageView from "./PostHogPageView"

const PostHogPageView = dynamic(() => import('./PostHogPageView'), {
ssr: false,
})

export default function RootLayout({ children }) {
return (
<html lang="en">
<PHProvider>
<body>
<PostHogPageView />
{children}
</body>
</PHProvider>
</html>
)
}
```

```tsx
// app/layout.tsx

import './globals.css'
import { PHProvider } from './providers'
import dynamic from 'next/dynamic'

const PostHogPageView = dynamic(() => import('./PostHogPageView'), {
ssr: false,
})

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<PHProvider>
<body>
<PostHogPageView />
{children}
</body>
</PHProvider>
</html>
)
}
export function PostHogProvider({ children }) {
useEffect(() => {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
person_profiles: 'identified_only',
capture_pageview: false // Disable automatic pageview capture, as we capture manually
})
}, [])

return (
<PostHogProvider client={posthog}>
+ <PostHogPageView />
{children}
</PostHogProvider>
)
}
```

</MultiLanguage>

PostHog is now set up and ready to go. Files and components accessing PostHog on the client-side need the `'use client'` directive.

#### Pageleave events (optional)

To capture pageleave events, we need to set `capture_pageleave: true` in the initialization because setting `capture_pageview: false` disables it.

<MultiLanguage>
```diff
// app/providers.js
'use client'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
import { useEffect } from 'react'

```js
// app/providers.js
'use client'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
import { useEffect } from 'react'

export function PHProvider({ children }) {
useEffect(() => {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
person_profiles: 'identified_only',
capture_pageview: false,
capture_pageleave: true // Enable pageleave capture
})
}, []);

return <PostHogProvider client={posthog}>{children}</PostHogProvider>
}
```

```tsx
// app/providers.tsx
'use client'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
import { useEffect } from 'react'
export function PHProvider({
children,
}: {
children: React.ReactNode
}) {
useEffect(() => {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
person_profiles: 'identified_only',
capture_pageview: false,
capture_pageleave: true // Enable pageleave capture
})
}, []);
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
}
```
export function PostHogProvider({ children }) {
useEffect(() => {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
person_profiles: 'identified_only',
capture_pageview: false,
+ capture_pageleave: true // Enable pageleave capture
})
}, [])

</MultiLanguage>
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
}
```

0 comments on commit 05e5c12

Please sign in to comment.