Next.js
로 스크립트 태그를 추가하다 점차 추가하다 보면 _document.tsx
안에 많은 script
태그들이 생겨나게 된 적이 있었어요. 처음에는 _document
파일이 뭔지도 모르고 모든 스크립트 태그 들을 다 집어 넣었던 적도 있었어요. 이래서 꼭 공식문서를 몇 번씩 봐야 하나봐요.. 봤는데도 실수를 저지르네요.
그 뒤로, 때로는 개발환경에서 제외시키고 싶거나 스크립트 태그 안에 있는 키 값을 변경시키고 싶을 때도 있었고, 그럴 때에는 어떻게 효과적으로 관리할 수 있는 지에 대해 고민을 했었습니다. 그 고민과, 어떻게 제 나름대로 해결 했는 지에 대해 공유하는 포스팅을 시작할까 합니다.
공식문서에도 볼 수 있듯, 크게 세 가지 방법이 있습니다.
_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
태그가 생성됩니다.
_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>
innext/head
.
ref: https://nextjs.org/docs/api-reference/next/head
_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에 로드 됩니다.이 중에서 이 번 포스팅의 핵심인 beforeInteractive
와 afterInteractive
에 대해 더 자세히 알아보겠습니다.
beforeInteractive
페이지가 상호작용되기 전에 로드 됩니다.
서버에서 초기 Html을 보낼 때 주입되고, 번들이 된 자바스크립트가 실행하기 전에 먼저 실행됩니다.
보통 페이지가 상호작용 되기 전에 반드시 스크립트가 필요로 할 때 생성됩니다.
일반적으로는 오로지 _document
파일 안에만 실행할 수 있도록 디자인 되었습니다. 이유는 _doucment
파일을 벗어나는 순간 순서를 보장할 수 없기 때문입니다.
위의 사진은 _document
파일 외부에 strategy=”beforeInteractive”
를 설정했을 때의 warning message입니다.
afterInteractive
strategy
속성의 기본 값입니다.위의 예시 코드와 Script
컴포넌트의 strategy
속성을 통해 알 수 있듯, 현재 브라우저에서 실행 시키려는 페이지가 상호작용이 가능하기 전에 실행시켜야 하는지 아닌지에 대한 여부를 두고 스크립트 태그를 생성할 수 있습니다.
_document
파일 안에서 Head
컴포넌트 안에 script
태그를 통해 생성합니다.Script
컴포넌트를 통해 생성할수 있는 파일이 _document
파일 하나에서만 가능하다 보니, 한정적이고 일반적으로 다른 방법이 없습니다._document
파일 외부에 Head
컴포넌트 안에 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;
각 스크립트들을 하나의 파일로 만들면 분기처리가 필요한 경우에 보다 유연하게 분기 처리가 가능합니다.
좋은정보 알아갑니다 감사해요!