segmentio / analytics.js

The hassle-free way to integrate analytics into any web application.
https://segment.com/libraries/analytics.js
MIT License
4.77k stars 736 forks source link

Support and documentation for Server Side Rendering and Static Site Generation #648

Open bscaspar opened 4 years ago

bscaspar commented 4 years ago

SSR and SSG are increasingly popular web technologies, having well documented support for integrating Segment with these type of rendering would improve Segment's DX among many new projects, teams, and startups.

I use Next.js and had to do some searching along with trial and error to connect analytics.js to my app. Next does not use any static html files when developing - it pre-renders them at build/run time from jsx or tsx files. When the static pages are pre-rendered on the server (at build time/request time) the window variable doesn't exist, so the <script> snippet throws an error.

Generally, I would prefer to have control over the delivery of any javascript libraries, and I see similar sentiment across the issues for analytics.js and analytics.js-core.

Here's a workaround I found (so far) to be successful for adding the appropriate script tag in case anyone else is searching for this as I was. One note on the snippet itself - I wrote it inline at first to increase the readability and Typescript threw a number of errors, so you'll see a few tweaks and ts-ignore statements (even though it's in a template literal and TS doesn't parse it anymore).


/* _app.tsx */
  useEffect(() => {
     // add Segment
    const scriptEl = document.createElement('script');
    const scriptText = document.createTextNode(
      ` !(function () {
              // @ts-ignore
              const analytics = (window.analytics = window?.analytics || []);
              if (!analytics.initialize)
                if (analytics.invoked) {
                  window.console &&
                    console.error &&
                    console.error('Segment snippet included twice.');
                } else {
                  analytics.invoked = !0;
                  analytics.methods = [
                    'trackSubmit',
                    'trackClick',
                    'trackLink',
                    'trackForm',
                    'pageview',
                    'identify',
                    'reset',
                    'group',
                    'track',
                    'ready',
                    'alias',
                    'debug',
                    'page',
                    'once',
                    'off',
                    'on',
                    'addSourceMiddleware',
                    'addIntegrationMiddleware',
                    'setAnonymousId',
                    'addDestinationMiddleware',
                  ];
                  analytics.factory = function (e) {
                    return function () {
                      // eslint-disable-next-line prefer-rest-params
                      const t = Array.prototype.slice.call(arguments);
                      t.unshift(e);
                      analytics.push(t);
                      return analytics;
                    };
                  };
                  for (let e = 0; e < analytics.methods.length; e++) {
                    const key = analytics.methods[e];
                    analytics[key] = analytics.factory(key);
                  }
                  analytics.load = function (key, e) {
                    const t = document.createElement('script');
                    t.type = 'text/javascript';
                    t.async = !0;
                    t.src = 'https://cdn.segment.com/analytics.js/v1/' + key + '/analytics.min.js';
                    const n = document.getElementsByTagName('script')[0];
                    n.parentNode.insertBefore(t, n);
                    analytics._loadOptions = e;
                  };
                  analytics.SNIPPET_VERSION = '4.13.1';
                  analytics.load('${process.env.NEXT_PUBLIC_SEGMENTKEY}');
                  analytics.page();
                }
            })()`,
    );
    scriptEl.appendChild(scriptText);
    document.head.appendChild(scriptEl);
  }, []);
bscaspar commented 4 years ago

Update: The Next.js team provides an example of integrating Segment over in their repo.