-
Notifications
You must be signed in to change notification settings - Fork 310
Description
Problem
OpenPanelComponent uses strategy="beforeInteractive" for the inline init script. In Next.js App Router, when the component is placed in a nested layout (e.g. app/[locale]/[params]/layout.tsx), the init script is serialized as RSC payload data but never rendered as an actual <script> tag in the HTML. The browser never executes it.
Root Cause
In Next.js App Router, beforeInteractive scripts are only promoted to real <script> tags in the <head> when placed in the root app/layout.tsx. In nested layouts, the Script component with beforeInteractive is treated as a regular React component and serialized into the RSC wire format:
self.__next_f.push([1,"...[\"$\",\"$L5c\",null,{\"strategy\":\"beforeInteractive\",\"dangerouslySetInnerHTML\":{\"__html\":\"window.op = ...\"}}]...
This is just JSON data describing the component's props — not an executable <script> tag. When React hydrates on the client, it sees strategy: "beforeInteractive" and does nothing, because beforeInteractive is designed to run before hydration, not after.
Result:
<script src="https://openpanel.dev/op1.js">loads fine (no strategy =afterInteractivedefault)- The inline init script (
window.op = ...; window.op('init', ...)) never executes window.opisundefined- No events are tracked
Reproduction
- Create a Next.js App Router project
- Place
<OpenPanelComponent clientId="..." trackScreenViews />in a nested layout (not rootapp/layout.tsx) - Load the page
- Check
window.opin browser console →undefined - Check
view-source:→window.oponly appears inside the RSC JSON payload (self.__next_f.push), not as a<script>tag
Suggested Fix
Change the init script strategy from beforeInteractive to afterInteractive:
- r.createElement(p, { strategy: "beforeInteractive", dangerouslySetInnerHTML: { __html: `...` } })
+ r.createElement(p, { id: "openpanel-init", strategy: "afterInteractive", dangerouslySetInnerHTML: { __html: `...` } })Or allow passing a strategy prop to override the default.
Workaround
Replace OpenPanelComponent with manual scripts:
import Script from 'next/script'
<Script
id="openpanel-init"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `window.op = window.op || function(...args) {(window.op.q = window.op.q || []).push(args)};
window.op('init', ${JSON.stringify({ clientId: 'your-client-id', trackScreenViews: true, sdk: 'nextjs', sdkVersion: '1.0.8' })});`,
}}
/>
<Script src="https://openpanel.dev/op1.js" strategy="afterInteractive" />Versions
@openpanel/nextjs: 1.0.8 (also confirmed in latest 1.1.3)- Next.js: 16