Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update injecting browser agent example #62

Closed
bizob2828 opened this issue May 26, 2022 · 10 comments · Fixed by #84
Closed

Update injecting browser agent example #62

bizob2828 opened this issue May 26, 2022 · 10 comments · Fixed by #84
Assignees
Labels
dnm2j documentation Improvements or additions to documentation good first issue Good for newcomers points: 1 1 day or less

Comments

@bizob2828
Copy link
Member

bizob2828 commented May 26, 2022

Is your feature request related to a problem? Please describe.

In 8.12.0 we introduced a flag(hasToRemoveScriptWrapper) to api.getBrowserTimingHeader that does not wrap the browser agent in a script tag. We can update the example to show how to properly put in the head of the html and reuse in a custom component.

@bizob2828 bizob2828 added documentation Improvements or additions to documentation points: 1 1 day or less labels May 26, 2022
@bizob2828 bizob2828 self-assigned this May 26, 2022
@newrelic-node-agent-team newrelic-node-agent-team added this to Triage Needed: Unprioritized Features in Node.js Engineering Board May 26, 2022
@bizob2828 bizob2828 removed their assignment May 26, 2022
@bizob2828 bizob2828 added good first issue Good for newcomers needs-triage and removed points: 1 1 day or less labels May 26, 2022
@coreyarnold coreyarnold added points: 1 1 day or less and removed needs-triage labels Jun 6, 2022
@coreyarnold coreyarnold moved this from Triage Needed: Unprioritized Features to To do: Features here are prioritized in Node.js Engineering Board Jun 6, 2022
@siuvdlec
Copy link
Contributor

siuvdlec commented Jul 7, 2022

Hi @bizob2828,

in your comment, you said that we could use the Script component, but which strategy can we use (beforeInteractive is allowed only in _document https://nextjs.org/docs/messages/no-before-interactive-script-outside-document)?

Thanks

@bizob2828
Copy link
Member Author

Notes here for whoever takes this

The way to deal with it currently is by creating a react component which returns a <script> tag with the agent injected, see the component below:

// NewRelicSnippet.tsx

const getNRBrowserAgent = () => {
  let browserTimingHeader;
  if (typeof window == "undefined") {
    const newrelic = require("newrelic");
    browserTimingHeader = newrelic.getBrowserTimingHeader({
      hasToRemoveScriptWrapper: true,
    });
  }
  return browserTimingHeader;
};

export const NewRelicSnippet = () => {
  return <script dangerouslySetInnerHTML={{ __html: getNRBrowserAgent() }} />;
};

The odd thing here (it’s a Nextjs thing) is that you are interested in running newrelic.getBrowserTimingHeader method only on the node side of things thus the typeof window == "undefined" condition.
There is a different way of injecting it as well (if you want to run front end code in the browser, as oppose to returning pre-rendered views to the browser from node (nextjs)) but let’s assume you just need to return pre-rendered views for the time being.
And then (depending on your app architecture) in your _app.tsx file you import the snippet and pass it to Head like so:

// _app.tex
import { AppProps } from "next/app";
import Head from "next/head";

import { NewRelicSnippet } from "../components/NewRelicSnippet";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <>
      <Head>
        <title>NewRelic NextJs Integration</title>
        <NewRelicSnippet />
      </Head>
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;

@bizob2828
Copy link
Member Author

Hi @bizob2828,

in your comment, you said that we could use the Script component, but which strategy can we use (beforeInteractive is allowed only in _document https://nextjs.org/docs/messages/no-before-interactive-script-outside-document)?

Thanks

Hi @siuvdlec I just provided a better example. As you can see we are not using the Script component due to the caveats you mentioned above before and after interactions

@siuvdlec
Copy link
Contributor

@bizob2828 thanks for clarifying.

This new snippet brought me two doubts.
1 - If we put the tracking code in the _app.tsx, this means that for the static pages getBrowserTimingHeader is called during build time and it never changes. Is this correct?
2 - During client-side navigation, the tracking code does not change, whereas in the previous snippet with getServerSideProps it used to change on each page/request also when they were loaded from the client. Doesn't this affect the type of tracked data?

@matewilk
Copy link
Contributor

matewilk commented Aug 2, 2022

Hi @siuvdlec,

Could you elaborate on the use cases you're interested in, there are multiple ways of integrating the NR Browser Agent with NextJs depending on the needs.
Since NextJs has multiple ways of rendering pages, could you let me know which ones of the following use cases you're interested in:

  • Static Site Generation - (getStaticProps)
    • Incremental Static Regeneration
  • Server Side Rendering - (getServerSideProps)
    • Hydration
    • React Server Components - (React 18 & Next 12 only)
  • Client Side Rendering - (with useEffect/useSWR)

@matewilk
Copy link
Contributor

matewilk commented Aug 2, 2022

Answering your question no 1 @siuvdlec,
I'm not sure what you mean by the tracking code exactly, but basically what happens here is that we are circumventing the issue you mentioned in your first post (related to NextJs _document.js) and we are not relying on the logic of NextJs because we are injecting the script tag into the head of the html document directly.
This means that every page returned from the server is going to have the NR Browser Agent in the head of the document

@siuvdlec
Copy link
Contributor

siuvdlec commented Aug 4, 2022

Hi @matewilk,

we use SSR, but sometimes it is possible that some projects use SSG at least for the error pages.

In my previous comment, when I used tracking code I meant the string returned from getBrowserTimingHeader.

With this last approach (script in _app.ts), for the SSG pages, we call getBrowserTimingHeader during build time, for the SSR pages we call getBrowserTimingHeader only the first time during server render.

If we don't have to call getBrowserTimingHeader for each page as in the previous snippet, why can't we put into _document.ts that it is rendered only on the server? But at the same time I see the string returned from getBrowserTimingHeader and I find a hash called transactionName so I think that I have to call getBrowserTimingHeader for each page not only during the first server render.

@matewilk
Copy link
Contributor

matewilk commented Aug 5, 2022

Hi @siuvdlec

I double checked that for you and you're absolutely right, you can inject the NR Browser agent into the _document.ts page, it would looks something like this:

const newrelic = require("newrelic");

export default class MyDocument extends Document {
  static async getIntialProps() {
    const browserTimingHeader = newrelic.getBrowserTimingHeader({
      hasToRemoveScriptWrapper: true,
    });

    return {
      browserTimingHeader,
    };
  }

  render() {
    return (
      <Html>
        <Head>
          <script
            type="text/javascript"
            dangerouslySetInnerHTML={{ __html: this.props.browserTimingHeader }}
          />
        </Head>
      </Html>
    );
  }
}

Using _document.ts has two benefits over using _app.ts

  • it does not render on the client-side (as you mentioned) - so there is no need to check whether you're on the front-end or the back-end side - which you could also deal with using your webpack config
  • there is no risk of re-rendering the component when React props change.

So all in all, _document.ts is a better place to inject the NR Browser agent script, thanks for bringing that up

@matewilk
Copy link
Contributor

matewilk commented Aug 15, 2022

Hi @siuvdlec

I've also looked into the beforeInteractive strategy and here is how you could inject the New Relic browser agent (tsx example)

const newrelic = require("newrelic");
import Document, {
  DocumentContext,
  DocumentInitialProps,
  Html,
  Head,
  Main,
  NextScript,
} from "next/document";
import Script from "next/script";

class MyDocument extends Document {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    const initialProps = await Document.getInitialProps(ctx);

    const browserTimingHeader = newrelic.getBrowserTimingHeader({
      hasToRemoveScriptWrapper: true,
    });

    return {
      ...initialProps,
      // @ts-ignore
      browserTimingHeader,
    };
  }

  render() {
    return (
      <Html>
        <Head>{/* whatever you need here */}</Head>
        <body>
          <Main />
          <NextScript />
          <Script
            // @ts-ignore
            dangerouslySetInnerHTML={{ __html: this.props.browserTimingHeader }}
            strategy="beforeInteractive"
          ></Script>
        </body>
      </Html>
    );
  }
}

export default MyDocument;

So both, injecting directly into <head> and using <Script> are possible

The <Script> method is only valid for Next.JS version 12 and above from what I've found out

@ShebangDog
Copy link

ShebangDog commented Sep 2, 2022

Hi @matewilk

Thank you your code!
Your code helped me a lot.❤️

btw your code include ts-ignore.
so I had a little trouble implementing this code for type.
The following code worked well for typing and I share it here.

I hope my code will be as helpful to others as yours.🦄

const newrelic = require("newrelic");
import Document, {
  DocumentContext,
  DocumentInitialProps,
  Html,
  Head,
  Main,
  NextScript,
} from "next/document";
import Script from "next/script";

// <------ ⚙️ add your custom type ------>
type DocumentProps = {
  browserTimingHeader: string
}
// <------ ⚙️pass your custom type to Document ------>
class MyDocument extends Document<DocumentProps> {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    const initialProps = await Document.getInitialProps(ctx);

    const browserTimingHeader = newrelic.getBrowserTimingHeader({
      hasToRemoveScriptWrapper: true,
    });

    return {
      ...initialProps,
      browserTimingHeader,
    };
  }

  render() {
    const { browserTimingHeader } = this.props

    return (
      <Html>
        <Head>{/* whatever you need here */}</Head>
        <body>
          <Main />
          <NextScript />
          <Script
            dangerouslySetInnerHTML={{ __html: browserTimingHeader }}
            strategy="beforeInteractive"
          ></Script>
        </body>
      </Html>
    );
  }
}

export default MyDocument;

@bizob2828 bizob2828 self-assigned this Sep 7, 2022
@bizob2828 bizob2828 moved this from To do: Features here are prioritized to In progress: Issues being worked on in Node.js Engineering Board Sep 7, 2022
@bizob2828 bizob2828 moved this from In progress: Issues being worked on to Needs PR Review in Node.js Engineering Board Sep 7, 2022
Node.js Engineering Board automation moved this from Needs PR Review to Done: Issues recently completed Sep 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dnm2j documentation Improvements or additions to documentation good first issue Good for newcomers points: 1 1 day or less
Projects
Node.js Engineering Board
  
Done: Issues recently completed
Development

Successfully merging a pull request may close this issue.

5 participants