posthog-js icon indicating copy to clipboard operation
posthog-js copied to clipboard

Next.js + Sentry + PostHog integration hydration error

Open freeatnet opened this issue 1 year ago • 4 comments

Steps to reproduce:

  1. Configure a Next.js app with [email protected] as outlined in the guide (including placing posthog.init in a useEffect).
  2. Add Sentry 8.x and set up PostHog Sentry integration as outlined in the guide..

Expected: Things work :)

Observed: Next.js starts throwing a hydration error. Specifically, [email protected] will log a warning stating "Prop dangerouslySetInnerHTML did not match", while [email protected] will log "A tree hydrated but some attributes of the server-rendered HTML didn't match the client properties." More detailed errors below.

Triage

This seems to happen because calling posthog.init loads a remote config JS file by inserting a script tag.

The error disappears if I disable remote script loading with disable_external_dependency_loading: true; however, that also disables surveys, so it's not ideal.

I can see two possible solutions:

  1. Expose a flag that would switch enable _loadRemoteConfigJSON by default. This should allow Nextjs to hydrate without error while still getting the settings.
  2. Let me, as the user of the library, optionally load config.js. E.g., if the library could skip inserting the script tag based on id, I could manually add <Script id="deterministic-posthog-config-key" strategy="beforeInteractive" src="https://my.app/ingest/array/phc_Ds…/config.js" /> in layout.tsx and therefore load the config without a warning.

Sample errors

Next 14.22

Given a <Script strategy="beforeInteractive" src="https://js.stripe.com/v3/" /> in the layout:

Prop `dangerouslySetInnerHTML` did not match. Server: "" Client: "(self.__next_s=self.__next_s||[]).push([\"https://js.stripe.com/v3/\",{}])"
script
Script
body
html
RootLayout (Server)
Component@
RedirectBoundary
Component@
NotFoundBoundary
DevRootNotFoundBoundary
PureComponent@
HotReload
Router
Component@
ErrorBoundary
AppRouter
ServerRoot
Root
Details

A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:

  • A server/client branch if (typeof window !== 'undefined').
  • Variable input such as Date.now() or Math.random() which changes each time it's called.
  • Date formatting in a user's locale which doesn't match the server.
  • External changing data without sending a snapshot of it along with the HTML.
  • Invalid HTML tag nesting.

It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.

https://react.dev/link/hydration-mismatch

... <Router actionQueue={{state:{...}, ...}} assetPrefix=""> <HistoryUpdater> <RuntimeStyles> <HotReload assetPrefix=""> <ReactDevOverlay state={{nextId:1, ...}} dispatcher={{...}}> <DevRootHTTPAccessFallbackBoundary> <HTTPAccessFallbackBoundary notFound={<NotAllowedRootHTTPFallbackError>}> <HTTPAccessFallbackErrorBoundary pathname="/home" notFound={<NotAllowedRootHTTPFallbackError>} ...> <RedirectBoundary> <RedirectErrorBoundary router={{...}}> <Head> <RootLayout> <html lang="en" className="__variable_3a0388" >

<CSPostHogProvider> <Toaster> <Script id="log-locati..." strategy="beforeInte..."> <script nonce={undefined} dangerouslySetInnerHTML={{
  •                           __html: "(self.__next_s=self.__next_s||[]).push([0,{\"children\":\"\\n            cons..."
    
  •                           __html: ""
                            }}
    
  •                         type="text/javascript"
    
  •                         crossorigin="anonymous"
    
  •                         src="https://my.app/ingest/array/phc_Ds..."
                          >
                        <Script strategy="beforeInte..." src="https://js.stripe.com...">
                          <script
                            nonce={undefined}
                            dangerouslySetInnerHTML={{
    
  •                           __html: "(self.__next_s=self.__next_s||[]).push([\"https://js.stripe.com/v3..."
    
  •                           __html: "(self.__next_s=self.__next_s||[]).push([0,{\"children\":\"\\n            cons..."
                            }}
                          >
    

</p>
</details> 

freeatnet avatar Jan 08 '25 20:01 freeatnet

This is possibly related to #774.

freeatnet avatar Jan 08 '25 20:01 freeatnet

I confirm that disable_external_dependency_loading: true works. This bug is not related to Sentry, I don't have it on my project.

apflieger avatar Nov 12 '25 11:11 apflieger

The same thing with Tanstack Start. I had to set disable_external_dependency_loading. Tried creating the instance in useEffect - didn't help.

martinliptak avatar Dec 08 '25 12:12 martinliptak

I found a workaround to still use dependencies with disable_external_dependency_loading: true. You just have to import the needed dependency in your code, in addition to the code library :

import posthog from "posthog-js"; // Core library
import "posthog-js/dist/recorder"
import "posthog-js/dist/surveys"
import "posthog-js/dist/exception-autocapture"
import "posthog-js/dist/tracing-headers"
import "posthog-js/dist/web-vitals"

posthog.init("xxxx", {
  api_host: "xxx",
  defaults: "2025-11-30",
  disable_external_dependency_loading: true,
});

As mentioned in the doc here https://posthog.com/docs/surveys/installation/web

apflieger avatar Dec 16 '25 18:12 apflieger