Next.js + Sentry + PostHog integration hydration error
Steps to reproduce:
- Configure a Next.js app with
[email protected]as outlined in the guide (including placingposthog.initin auseEffect). - Add Sentry
8.xand 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:
- Expose a flag that would switch enable
_loadRemoteConfigJSONby default. This should allow Nextjs to hydrate without error while still getting the settings. - Let me, as the user of the library, optionally load
config.js. E.g., if the library could skip inserting the script tag based onid, I could manually add<Script id="deterministic-posthog-config-key" strategy="beforeInteractive" src="https://my.app/ingest/array/phc_Ds…/config.js" />inlayout.tsxand 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()orMath.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>
This is possibly related to #774.
I confirm that disable_external_dependency_loading: true works.
This bug is not related to Sentry, I don't have it on my project.
The same thing with Tanstack Start. I had to set disable_external_dependency_loading. Tried creating the instance in useEffect - didn't help.
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