Next.js는 React 기반의 강력한 프레임워크로, 개발자들에게 다양한 최적화 기능을 제공합니다. 그중에서도 next/script 모듈의 Script 컴포넌트는 외부 JavaScript를 효율적으로 로드하는 데 특화된 기능입니다. 이 글에서는 Script 컴포넌트의 모든 측면을 상세히 알아보겠습니다.
왜 일반 <script> 태그 대신 Next.js의 Script 컴포넌트를 사용해야 할까요?
기존 HTML의 <script> 태그는 웹 페이지에 JavaScript를 로드하는 기본적인 방법입니다. 그러나 Next.js 애플리케이션에서는 next/script의 Script 컴포넌트를 사용하는 것이 여러 이점을 제공합니다:
- 자동 성능 최적화: 페이지 로딩 성능을 최적화하는 다양한 로딩 전략 제공
- 코드 분할과의 호환성: Next.js의 자동 코드 분할 기능과 원활하게 작동
- 스크립트 우선순위 관리: 중요도에 따라 스크립트 로딩 순서 제어
- 라우팅 성능 향상: 페이지 이동 시 스크립트 관리 최적화
기본 사용법
Script 컴포넌트를 사용하기 위해서는 먼저 Next.js 애플리케이션에서 모듈을 가져와야 합니다:
import Script from 'next/script';
가장 기본적인 사용 방법은 다음과 같습니다:
import Script from 'next/script';
function MyApp() {
return (
<>
<Script
src="https://example.com/script.js"
/>
<p>애플리케이션 콘텐츠...</p>
</>
);
}
이 코드는 외부 스크립트를 로드하지만, 단순히 <script> 태그를 사용하는 것보다 훨씬 더 많은 최적화가 적용됩니다.
로딩 전략 (strategy 속성)
Script 컴포넌트의 가장 강력한 기능 중 하나는 다양한 로딩 전략을 제공한다는 점입니다. strategy 속성을 통해 스크립트가 언제, 어떻게 로드될지 제어할 수 있습니다.
1. beforeInteractive
<Script
src="https://example.com/script.js"
strategy="beforeInteractive"
/>
- 페이지가 인터랙티브 상태가 되기 전에 로드됩니다.
- 페이지 초기 HTML와 함께 서버에서 전송되어 가장 먼저 실행됩니다.
- 사용 사례: 폴리필, 쿠키 동의, 봇 감지 등 페이지 렌더링 전에 필요한 중요 스크립트
2. afterInteractive (기본값)
<Script
src="https://example.com/script.js"
strategy="afterInteractive"
/>
- 페이지가 인터랙티브 상태가 된 직후에 로드됩니다.
- 브라우저에서 hydration(수화) 과정이 완료된 후 실행됩니다.
- 사용 사례: 분석 도구, 태그 관리자 등 중간 우선순위 스크립트
3. lazyOnload
<Script
src="https://example.com/script.js"
strategy="lazyOnload"
/>
- 브라우저의 유휴 시간에 지연 로드됩니다.
- 페이지의 모든 리소스가 로드된 후 가장 낮은 우선순위로 로드됩니다.
- 사용 사례: 채팅 위젯, 소셜 미디어 버튼 등 사용자 상호작용 없이도 작동할 수 있는 스크립트
4. worker (실험적 기능)
<Script
src="https://example.com/script.js"
strategy="worker"
/>
- Web Worker에서 오프 메인 스레드로 스크립트를 로드합니다.
- 아직 실험적 기능으로, 프로덕션에서는 주의하여 사용해야 합니다.
- 사용 사례: 메인 스레드에 영향을 주지 않아야 하는 무거운 계산이나 처리
다음 표는 각 전략 간의 차이점을 보여줍니다:
전략 로드 시점 우선순위 페이지 성능 영향 적합한 사용 사례
beforeInteractive | HTML와 함께 즉시 | 최상 | 높음 | 페이지 렌더링에 필수적인 스크립트 |
afterInteractive | hydration 후 | 중간 | 중간 | 분석, 태그 관리자 |
lazyOnload | 모든 리소스 후 | 최하 | 낮음 | 즉시 필요하지 않은 기능 |
worker | 별도 스레드 | 낮음 | 최소 | 무거운 계산, 백그라운드 처리 |
이벤트 핸들러와 생명주기 관리
Script 컴포넌트는 스크립트 로딩 상태를 처리하기 위한 세 가지 이벤트 핸들러를 제공합니다:
onLoad: 스크립트 로드 완료 시 실행
<Script
src="https://example.com/script.js"
onLoad={() => {
console.log('스크립트 로드 완료!');
// 스크립트가 로드된 후 초기화 코드 실행
window.exampleLibrary.initialize();
}}
/>
onReady: 스크립트 실행 완료 및 사용 준비 상태일 때 실행
<Script
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"
onReady={() => {
// 라이브러리가 완전히 초기화된 후 실행
const map = new google.maps.Map(document.getElementById('map'), {
center: { lat: -34.397, lng: 150.644 },
zoom: 8,
});
}}
/>
onReady는 페이지 이동 후에도 한 번만 호출됩니다. 반면, onLoad는 페이지 이동 시 스크립트가 다시 로드되면 여러 번 호출될 수 있습니다.
onError: 스크립트 로드 실패 시 실행
<Script
src="https://example.com/script.js"
onError={(e) => {
console.error('스크립트 로드 실패:', e);
// 오류 보고 또는 대체 기능 제공
sendErrorReport(e);
activateFallbackFeature();
}}
/>
인라인 스크립트 정의하기
외부 스크립트 URL 뿐만 아니라, 인라인 JavaScript 코드도 Script 컴포넌트에서 사용할 수 있습니다:
<Script id="show-banner">
{`
document.addEventListener('DOMContentLoaded', function() {
const banner = document.getElementById('promo-banner');
if (banner) {
banner.classList.remove('hidden');
}
});
`}
</Script>
인라인 스크립트를 사용할 때는 id 속성이 필수입니다. 이는 Next.js가 스크립트를 식별하고 관리하는 데 필요합니다.
타사 라이브러리 통합 사례
1. Google Analytics 통합
import Script from 'next/script';
export default function MyApp({ Component, pageProps }) {
return (
<>
{/* Google Analytics */}
<Script
strategy="afterInteractive"
src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_ID}`}
/>
<Script
id="google-analytics"
strategy="afterInteractive"
>
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${process.env.NEXT_PUBLIC_GA_ID}');
`}
</Script>
<Component {...pageProps} />
</>
);
}
2. Google Maps 통합
import { useEffect, useState } from 'react';
import Script from 'next/script';
export default function MapComponent() {
const [mapLoaded, setMapLoaded] = useState(false);
return (
<>
<Script
src={`https://maps.googleapis.com/maps/api/js?key=${process.env.NEXT_PUBLIC_MAPS_API_KEY}`}
strategy="lazyOnload"
onReady={() => setMapLoaded(true)}
/>
<div id="map" style={{ height: '400px', width: '100%' }}>
{!mapLoaded && <p>지도 로딩 중...</p>}
</div>
{mapLoaded && (
<button onClick={() => {
const map = new google.maps.Map(document.getElementById('map'), {
center: { lat: 37.5665, lng: 126.9780 },
zoom: 12,
});
}}>
서울 중심으로 지도 표시
</button>
)}
</>
);
}
3. Facebook Pixel 통합
import Script from 'next/script';
export default function Layout({ children }) {
return (
<>
{/* Facebook Pixel Code */}
<Script
id="facebook-pixel"
strategy="afterInteractive"
>
{`
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '${process.env.NEXT_PUBLIC_FB_PIXEL_ID}');
fbq('track', 'PageView');
`}
</Script>
{children}
</>
);
}
고급 기능 및 속성
1. nonce 속성: 콘텐츠 보안 정책(CSP) 지원
<Script
src="https://example.com/script.js"
nonce="random-nonce-value"
/>
콘텐츠 보안 정책(CSP)을 사용하는 경우, nonce 속성을 통해 스크립트에 고유한 암호화 논스를 제공할 수 있습니다.
2. data-* 속성: 커스텀 데이터 속성
<Script
src="https://example.com/script.js"
data-customer-id="12345"
data-theme="dark"
/>
필요에 따라 커스텀 데이터 속성을 추가하여 스크립트에 메타데이터를 제공할 수 있습니다.
3. onLoad와 함께 스크립트 지연 실행
<Script
src="https://example.com/heavy-library.js"
strategy="lazyOnload"
onLoad={() => {
// 라이브러리가 로드된 후 기능 초기화
setTimeout(() => {
window.heavyLibrary.initializeWithDelay({
delayLevel: 2,
priority: 'low'
});
}, 2000); // 2초 지연
}}
/>
4. 조건부 스크립트 로딩
export default function ConditionalScript({ shouldLoadScript }) {
return (
<div>
{shouldLoadScript && (
<Script
src="https://example.com/optional-feature.js"
strategy="lazyOnload"
/>
)}
<p>페이지 내용...</p>
</div>
);
}
다양한 컨텍스트에서의 Script 컴포넌트 활용
1. _app.js에서 글로벌 스크립트 로드
애플리케이션 전체에서 사용되는 스크립트는 _app.js에 추가하는 것이 좋습니다:
// pages/_app.js
import Script from 'next/script';
export default function MyApp({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
</>
);
}
2. 페이지별 스크립트 로드
특정 페이지에서만 필요한 스크립트는 해당 페이지 컴포넌트에 포함시킵니다:
// pages/contact.js
import Script from 'next/script';
export default function ContactPage() {
return (
<div>
<h1>연락처</h1>
<Script
src="https://cdn.example.com/contact-form-validator.js"
strategy="lazyOnload"
/>
{/* 페이지 내용 */}
</div>
);
}
3. App Router와 함께 사용 (Next.js 13+)
Next.js 13 이상에서 App Router를 사용하는 경우에도 Script 컴포넌트를 활용할 수 있습니다:
// app/layout.js
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
<html lang="ko">
<head />
<body>
{children}
<Script
src="https://example.com/script.js"
strategy="afterInteractive"
/>
</body>
</html>
);
}
4. 특정 라우트에서만 스크립트 사용 (App Router)
// app/products/layout.js
import Script from 'next/script';
export default function ProductsLayout({ children }) {
return (
<section>
{children}
<Script
src="https://cdn.example.com/product-viewer.js"
strategy="lazyOnload"
/>
</section>
);
}
성능 최적화 팁
1. 올바른 전략(strategy) 선택
// 잘못된 예: 불필요하게 beforeInteractive 사용
<Script
src="https://cdn.example.com/analytics.js"
strategy="beforeInteractive" // 분석 도구에는 과도한 우선순위
/>
// 좋은 예: 적절한 전략 사용
<Script
src="https://cdn.example.com/analytics.js"
strategy="afterInteractive" // 분석 도구에 적합한 우선순위
/>
각 스크립트의 중요도와 용도에 맞는 전략을 선택하세요:
- 중요 기능 (페이지 렌더링에 필요): beforeInteractive
- 분석/마케팅 도구: afterInteractive
- 보조 기능/위젯: lazyOnload
2. 불필요한 재로딩 방지
// pages/_app.js
import Script from 'next/script';
export default function MyApp({ Component, pageProps }) {
return (
<>
{/* 모든 페이지에서 한 번만 로드 */}
<Script
src="https://example.com/script.js"
strategy="afterInteractive"
id="example-script" // ID 추가로 추적
/>
<Component {...pageProps} />
</>
);
}
3. 로컬 스크립트 최적화
외부 CDN 대신 로컬 스크립트를 사용할 때는 Next.js의 최적화 기능을 활용하세요:
import Script from 'next/script';
export default function MyPage() {
return (
<>
<Script
src="/scripts/local-script.js"
strategy="lazyOnload"
/>
{/* 페이지 내용 */}
</>
);
}
문제 해결 및 디버깅
1. 스크립트가 로드되지 않는 경우
// 디버깅을 위한 로깅 추가 console.log('스크립트 로드됨')}
onError={(e) => console.error('스크립트 로드 실패:', e)}
/>
2. 스크립트 작동 확인
브라우저 개발자 도구의 Network 탭에서 스크립트 로드 상태를 확인하세요. 로드 시점은 설정한 strategy 값에 따라 달라집니다.
3. CSP 관련 문제
콘텐츠 보안 정책 오류가 발생하는 경우:
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: "script-src 'self' https://example.com;"
}
]
}
];
}
};
결론
Next.js의 Script 컴포넌트는 웹 애플리케이션에서 JavaScript를 로드하는 방식을 최적화하는 강력한 도구입니다. 단순히 HTML의 <script> 태그를 대체하는 것을 넘어, 성능과 사용자 경험을 향상시키는 다양한 기능을 제공합니다.
적절한 로딩 전략 선택, 이벤트 핸들러 활용, 그리고 애플리케이션 구조에 맞는 배치를 통해 외부 스크립트가 웹사이트 성능에 미치는 부정적 영향을 최소화할 수 있습니다. 특히 분석 도구, 마케팅 스크립트, 타사 위젯 등을 사용하는 현대 웹 애플리케이션에서 Script 컴포넌트는 필수적인 도구입니다.
Next.js 애플리케이션을 개발할 때는 항상 일반 <script> 태그 대신 next/script의 Script 컴포넌트를 사용하는 것을 고려하세요. 적절한 전략과 함께 사용하면 웹사이트의 성능, 사용자 경험, 그리고 개발자 경험 모두를 개선할 수 있습니다.
'TypeScript' 카테고리의 다른 글
브레드크럼(Breadcrumb): 사용자 경험을 향상시키는 네비게이션 요소 (1) | 2025.04.15 |
---|---|
Next.js에서 class-variance-authority(CVA)로 재사용 가능한 UI 컴포넌트 만들기 (0) | 2025.04.14 |
웹사이트 SEO를 위한 메타데이터 최적화 완벽 가이드: 검색 노출부터 소셜 공유까지 (1) | 2025.04.11 |
React Context API: 상태 관리 (0) | 2025.04.10 |
Next.js 프로젝트 구조의 모든 것: src/pages와 src/app 제대로 알기 (0) | 2025.04.09 |