TypeError: globalScope.addEventListener is not a function
nabusman opened this issue · 9 comments
Expected Behavior
Start the dev server without error
Current Behavior
After installing I tried running npm run dev
and got the following error:
TypeError: globalScope.addEventListener is not a function
at Object. (/.../node_modules/@amplitude/plugin-page-view-tracking-browser/src/page-view-tracking.ts:78:21)
at step (/.../node_modules/@amplitude/plugin-page-view-tracking-browser/node_modules/tslib/tslib.js:195:27)
at Object.next (/.../node_modules/@amplitude/plugin-page-view-tracking-browser/node_modules/tslib/tslib.js:176:57)
at /.../node_modules/@amplitude/plugin-page-view-tracking-browser/node_modules/tslib/tslib.js:169:75
at new Promise ()
at Object.__awaiter (/.../node_modules/@amplitude/plugin-page-view-tracking-browser/node_modules/tslib/tslib.js:165:16)
at Object.setup (/.../node_modules/@amplitude/plugin-page-view-tracking-browser/src/page-view-tracking.ts:71:63)
at Timeline. (/.../node_modules/@amplitude/analytics-core/src/timeline.ts:28:23)
at step (/.../node_modules/@amplitude/analytics-core/node_modules/tslib/tslib.js:195:27)
at Object.next (/.../node_modules/@amplitude/analytics-core/node_modules/tslib/tslib.js:176:57)
Steps to Reproduce
npm install @amplitude/analytics-browser
npm run dev
Environment
My dependencies in package.json:
"@amplitude/analytics-browser": "^2.1.2",
"next": "12.1.6",
"nextjs-cors": "^2.1.2",
"ngrok": "^4.3.1",
"react": "^18.1.0",
"react-dom": "18.1.0",
"react-markdown": "^8.0.7",
"sharp": "^0.30.7",
"swr": "^1.3.0"
Hi @nabusman, for NextJS we usually recommend using the Amplitude Node SDK. This is because NextJS code may be run on both on the client (browser) and the server (SSR).
Hope this helps and let me know if it doesn't!
Update:
Next.js is a full stack framework. Normally, we recommend to use Browser SDK on client-side and Node.js SDK on server-side. But to be careful to not run the SDK in an environment that the SDK doesn’t support. For example, it might cause an issue when using Browser SDK on Next.js server side.
@Mercy811 I am also facing this issue. And I have some questions too.
- Ideally, in the server-side case, tracking should be triggered on the client side. wdyt?
- The plugin
@amplitude/plugin-page-view-tracking-browser/
is the trigger for the issue. Would this plugin work with the Node SDK? And in general, does the NodeSDK work on the browser?
@Mercy811 what do you think of the following approach?
- use the browser-sdk and send all analytics from the browser when the component mound (
React.useEffect(track, [])
) - don't load ampli on the server-side (
if (typeof window === "undefined") return;
)
I don't think that the NodeSDK is a good fit for a NextJS app
Hi @glung, yes if you want to use @amplitude/plugin-page-view-tracking-browser
plugin, you have to use the browser SDK because the Node SDK doesn't support that. But have to be carefully and make sure that the the browser SDK only runs on client side as it include platform specific code which can not run on server side. Here is an example Next.js app using browser SDK. Hope this is helpful.
@Mercy811 I followed your recommendation yesterday and switched from Browser SDK to Node SDK. The errors disappeared but Amplitude is no longer capturing events.
But now you have crossed it out.
What is the correct recommendation here? How do we install the SDK in a Next.js 13 project which has both server and client? It's attempting to load Browser SDK in server, which fails obviously, but it looks like Node SDK doesn't work as expected on the client.
Update: This is what worked properly for the Next.js 13 project (client-side on app router). I think you can also set up Node SDK using npm install
if you need to track server-side events, but don't set up Browser SDK using npm install
.
How to set up Amplitude SDK for client side:
File: .env.local
and Vercel's Environment Variables configuration:
AMPLITUDE_API_KEY= < Place your Amplitude API key here >
File: app/layout.tsx
(please be sure to read the note below):
import Script from 'next/script'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang='en'>
<body>{children}</body>
<Script id='amplitude-browser-sdk'>
{`!function() ... (SEE NOTE BELOW) ... (window,document)}();
amplitude.init('${process.env.AMPLITUDE_API_KEY}');`}
</Script>
</html>
)
}
Note: Full function code (a very long line) comes from the "Select a Data Source" page in Amplitude, shown after choosing "Browser SDK", in the Snippet tab. You can also get it here. (I didn't include a copy in my comment so that you don't use an outdated version.)
Tip: I think you can also place the long line of code in a separate .js
asset file in public
and use <Script src='/amplitude.js' />
instead. No id
necessary. For the init
call you can do one of these:
- Inline the API key in
init
call in the.js
file itself and not place it in an environment variable anymore. - Use the
onLoad
callback as described here to do theinit
call with the environment variable, but this is only possible on a client component. You may need to prefixNEXT_PUBLIC_
to the environment variable.
How to track events:
Wherever you need to track events in a client component, you need to have access to a function.
const amplitude = () => (window as any).amplitude // or (window as any)['amplitude']
Note: This function is just for convenience. You can directly inline (window as any).amplitude
instead of calling this function if you wish (but all the below rules apply). I think you can also put the function in a common place and just import it. However, you cannot do these:
window.amplitude
- you'll have build time error because window object is not expected to have amplitude. (Property 'amplitude' does not exist on type 'Window & typeof globalThis'. ts(2339))window['amplitude']
- you'll have build time error because window object's child cannot be accessed with a non-numeric index. (Element implicitly has an 'any' type because index expression is not of type 'number'. ts(7015))const amplitude = (window as any).amplitude
- although this works in client, Next.js executes client component code in both server and client. So this throws "window is not defined" runtime error in server. No, using((window || {}) as any)
doesn't solve the problem.
You also cannot call this function anywhere you like. You must only call it in an event handler or some place that's guaranteed to execute only on the client. If you call it at a place which executes in both server and client, you'll have the same "window is not defined" error. This means you cannot call it in advance and store the return value in a variable. You must call it only when you need it.
To track an event, you need to execute the function (which grabs the amplitude object from window) and call track
on the returned object. For example:
'use client'
// EITHER:
// import amplitude from '/path/to/my/functions' // or just create function below
// OR:
const amplitude = () => (window as any).amplitude // or import above
export default function MyClientComponent() {
const handleClick = () => amplitude().track('Button Click Event')
return <button onClick={handleClick}>Click me!</button>
}
Note the difference that the official docs use amplitude.track
because it's an object already, but here we use amplitude().track
because it's a function that grabs the object. But if you want to directly inline, it will be (window as any).amplitude.track
.
Tip: You can also inline it:
<button onClick={() => amplitude().track('Button Click Event')}>Click me!</button>
If anything is not clear, please tag me in a comment. Happy to help!
I am also running into this issue. Could an option be added to completely disable this plugin (e.g. here)? The other default plugins are all wrapped in a check to see if they are needed before being loaded.
Hi @georgewitteman, thanks for your suggestion and choosing Amplitude. We've re-prioritize this and I will keep you updated.
Hi @georgewitteman, @amplitude/analytics-browser@2.4.1
supports to not install the page view plugin by either config.defaultTracking = false
or config.defaultTracking.pageViews = false
.