Next.js에서 Script Component 효과적으로 사용하기

Seal Park·2022년 8월 7일
16

시작하기 전에 앞서..

Next.js로 스크립트 태그를 추가하다 점차 추가하다 보면 _document.tsx 안에 많은 script 태그들이 생겨나게 된 적이 있었어요. 처음에는 _document 파일이 뭔지도 모르고 모든 스크립트 태그 들을 다 집어 넣었던 적도 있었어요. 이래서 꼭 공식문서를 몇 번씩 봐야 하나봐요.. 봤는데도 실수를 저지르네요.

그 뒤로, 때로는 개발환경에서 제외시키고 싶거나 스크립트 태그 안에 있는 키 값을 변경시키고 싶을 때도 있었고, 그럴 때에는 어떻게 효과적으로 관리할 수 있는 지에 대해 고민을 했었습니다. 그 고민과, 어떻게 제 나름대로 해결 했는 지에 대해 공유하는 포스팅을 시작할까 합니다.

Next.js에서 스크립트 태그를 추가하는 방법

공식문서에도 볼 수 있듯, 크게 세 가지 방법이 있습니다.

  1. _doucment 파일 내에서 'next/document'Head 컴포넌트 안에 script태그 추가

    import { Html, Head, Main, NextScript } from 'next/document'
    
    export default function Document() {
      return (
        <Html>
          <Head>
            <script
              dangerouslySetInnerHTML={{
                __html: `
    				window.dataLayer = window.dataLayer || [];
    				function gtag(){window.dataLayer.push(arguments);}
    				gtag('js', new Date());
    							
    		        gtag('config', 'GA_MEASUREMENT_ID');
    				`,
    			}}
            />
          </Head>
          <body>
            <Main />
            <NextScript />
          </body>
        </Html>
      )
    }

    일반적으로 _document 파일 내에 스크립트 코드를 작성하게 되면, 오직 서버에서만 렌더 되고, 실제 head 태그와 같이 전체 페이지에서 사용되는 script 태그들만 생성합니다. 그리고 실제로 head 태그 내에 script 태그가 생성됩니다.

  2. _doucment 파일 외부에서 'next/head'Head 컴포넌트 안에 script태그 추가

    import Head from 'next/head';
    
    export default function Some() {
      return (
        <Head>
          <script
            dangerouslySetInnerHTML={{
              __html: `
    			window.dataLayer = window.dataLayer || [];
    			function gtag(){window.dataLayer.push(arguments);}
    			gtag('js', new Date());
    					
    			gtag('config', 'GA_MEASUREMENT_ID');
    			`,
            }}
          />
        </Head>
      );
    };

    위의 1번 예시와는 같은 이름의 Head 컴포넌트 이지만, 호출하는 경로(_document 파일 에서는 'next/document', 그 외의 파일 에서는 'next/head')가 다릅니다.

    'next/document'Head 컴포넌트는 오직 _document 파일에서만 사용이 가능하며, 'next/head'Head 컴포넌트는 _document 파일을 제외하고 사용이 가능합니다.

    실제 브라우저에서 렌더링 될 때에는 body 태그 내에 script 태그가 생성됩니다.

    그리고 공식문서에서는 해당 방법을 권장하지 않습니다. 권장하는 방법은 아래의 3번에 소개됩니다.

    We recommend using next/script in your component instead of manually creating a <script> in next/head.
    ref: https://nextjs.org/docs/api-reference/next/head

  3. _doucment 파일 외부에서 'next/script'Script 컴포넌트를 통해 추가

    import Script from 'next/script'
    
    export default function Home() {
      return (
        <>
          <Script id="google-analytics" strategy="afterInteractive">
            {`
              window.dataLayer = window.dataLayer || [];
              function gtag(){window.dataLayer.push(arguments);}
              gtag('js', new Date());
    
              gtag('config', 'GA_MEASUREMENT_ID');
            `}
          </Script>
        </>
      )
    }

    Script 컴포넌트의 속성을 보면 strategy속성을 볼 수 있습니다. strategy 속성에는 네 개의 값이 있습니다.

    • beforeInteractive: 페이지가 상호작용되기 전에 로드 됩니다.
    • afterInteractive : 페이지가 상호작용이 가능해진 직후에 즉시 로드 되며, strategy 속성의 기본 값입니다.
    • lazyOnload: idle time 동안에 로드 됩니다.
    • worker: (experimental) web worker에 로드 됩니다.

    이 중에서 이 번 포스팅의 핵심인 beforeInteractiveafterInteractive 에 대해 더 자세히 알아보겠습니다.

Script 태그의 strategy 값들 중 beforeInteractive 와 afterInteractive

  • beforeInteractive

    • 페이지가 상호작용되기 전에 로드 됩니다.

    • 서버에서 초기 Html을 보낼 때 주입되고, 번들이 된 자바스크립트가 실행하기 전에 먼저 실행됩니다.

    • 보통 페이지가 상호작용 되기 전에 반드시 스크립트가 필요로 할 때 생성됩니다.

    • 일반적으로는 오로지 _document 파일 안에만 실행할 수 있도록 디자인 되었습니다. 이유는 _doucment 파일을 벗어나는 순간 순서를 보장할 수 없기 때문입니다.

      위의 사진은 _document파일 외부에 strategy=”beforeInteractive”를 설정했을 때의 warning message입니다.

  • afterInteractive

    • 페이지가 상호작용이 가능해진 직후에 즉시 로드 되며, strategy 속성의 기본 값입니다.
    • 페이지에 수화(hydration)되고 나서 클라이언트 사이드에 주입하기 때문에 그만큼 초기에 페이지 로딩 시간을 단축 할 수 있다는 특징이 있습니다.

스크립트 태그를 나누는 기준

위의 예시 코드와 Script 컴포넌트의 strategy 속성을 통해 알 수 있듯, 현재 브라우저에서 실행 시키려는 페이지가 상호작용이 가능하기 전에 실행시켜야 하는지 아닌지에 대한 여부를 두고 스크립트 태그를 생성할 수 있습니다.

  • 페이지가 상호작용이 가능하기 전에 로드를 해야 하는 경우
    • _document 파일 안에서 Head 컴포넌트 안에 script 태그를 통해 생성합니다.
    • Script 컴포넌트를 통해 생성할수 있는 파일이 _document 파일 하나에서만 가능하다 보니, 한정적이고 일반적으로 다른 방법이 없습니다.
  • 페이지가 상호작용이 가능하고나서부터 로드를 해도 되는 경우
    • _document 파일 외부에 Head 컴포넌트 안에 script 태그를 생성하거나 Script 컴포넌트를 통해 생성합니다.

저는 위의 기준에서 많은 고민을 했던 부분은 ‘페이지가 상호작용이 가능하고나서부터 로드를 해도 되는 경우’ 였어요. 어디에 컴포넌트를 배치해야 좋을 지, 어떤 폴더에서 파일을 생성하면 좋을 지, 배포환경이 아닌 개발 환경일 때에는 어떻게 분기처리를 하면 좋을 지 등등 고민을 했었어요. 그 고민의 결과를 공유해보려 합니다.

보다 효과적으로 Script 컴포넌트를 관리하는 방법

Script 컴포넌트는 페이지에서 실행시키는 것이다 보니, 처음에는 페이지(’./pages’) 컴포넌트에 위치를 시키려 했어요. 하지만 대부분의 Script 컴포넌트들은 모든 페이지에서 필요로 하고, 특수한 경우에만 특정 페이지에서 필요로 했다보니, 이 안에서도 기준을 나누게 되었습니다.

  • 전체 페이지에서 필요로 하는 경우 → _app 파일에 추가
  • 특정 페이지에서만 필요로 하는 경우 → 그 페이지 파일에 추가

전체 페이지에서 필요로 하는 경우

저 같은 경우에는 components 폴더 안에 Scripts 라는 폴더를 만들고, 그 안에서 Script 컴포넌트들을 관리했어요. 그리고 그 안에서 전체 페이지에서 필요로 하는 경우, index 파일을 만들어 그 안에 컴포넌트들을 호출했습니다.

// 경로 : './components/Scripts/GoogleAnalytics.tsx'
import Script from 'next/script';

const GoogleAnalytics = () => {
  return (
    <>
      <Script
        src={`https://www.googletagmanager.com/gtag/js?id=${process.env.GA}`}
        strategy="afterInteractive"
      />

		  <Script id="nextjs-google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', '${process.env.GA}', {
            page_path: window.location.pathname,
          });
        `}
      </Script>
    </>
  );
};

export default GoogleAnalytics;
// 경로 : './components/Script/index.tsx'
import GoogleAnalytics from './GoogleAnalytics';
import KakaoDevelopers from './KakaoDevelopers';
import SomeScript from './SomeScript';

const Scripts = () => {
  return (
    <>
      <GoogleAnalytics />
      <KakaoDevelopers />
      <SomeScript />
    </>
  );
};

export default Scripts;
// 경로 : './pages/_app.tsx'
import type { AppProps } from 'next/app';

import Scripts from '@/components/Scripts';

const MyApp = ({ Component, pageProps }: AppProps) => {
  return (   
    <>
			{/* 
				전체 페이지에서 스크립트들을 필요로 하는 경우, 
				Scripts라는 컴포넌트로 한 단계 추상화 하여 호출한다. 
			*/}
      <Scripts />
      <Component {...pageProps} />
    </>
  );
};

export default MyApp;

특정 페이지에서만 필요로 하는 경우

해당 페이지에서 Script 컴포넌트를 호출합니다.

// 경로 : './pages/index.tsx'
import Main from '@/components/Main';
import SomeScript from '@/components/Scripts/SomeScript';

const MainPage = () => {
  return (
    <>
      <SomeScript />
      <Main />
    </>
  );
};

export default MainPage;

특정 상황에서만 호출하고 싶은 경우

혹시라도 배포 환경에서만 Script 컴포넌트를 호출하고 싶거나, 개발 환경일 때에만 호출하고 싶거나, 그 외에 등등 특정 분기조건이 필요한 경우에는 아래와 같이 설정합니다.

import Script from 'next/script';

const GoogleAnalytics = () => {
	// GA라는 환경변수가 있을 때에만 아래의 jsx 엘리먼트들을 호출하고, 그렇지 않으면 호출하지 않도록 설정
  if (!process.env.GA) {
    return null;
  }

  return (
    <>
      <Script
        src={`https://www.googletagmanager.com/gtag/js?id=${process.env.GA}`}
        strategy="afterInteractive"
      />

      <Script id="nextjs-google-analytics">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', '${process.env.GA}', {
            page_path: window.location.pathname,
          });
        `}
      </Script>
    </>
  );
};

export default GoogleAnalytics;

각 스크립트들을 하나의 파일로 만들면 분기처리가 필요한 경우에 보다 유연하게 분기 처리가 가능합니다.

References

https://nextjs.org/docs/basic-features/script

https://nextjs.org/docs/api-reference/next/head

1개의 댓글

comment-user-thumbnail
2023년 1월 17일

좋은정보 알아갑니다 감사해요!

답글 달기