안녕하세요! 프론트엔드 개발자 여러분, 오늘 우리가 함께 살펴볼 내용은 Next.js의 고급 기능 중 하나인 Instrumentation(계측) 설정 방법입니다.
실무에 나가시면 애플리케이션의 성능을 모니터링하고 에러를 추적하는 과정이 정말 중요한데요, 공식 문서를 보면서 하나하나 짚어보고 제 실무 경험도 팍팍 녹여서 쉽게 설명해 드릴게요! 자, 그럼 시작해볼까요?
문서 메타정보
Instrumentation(계측)은 코드에 모니터링 및 로깅 도구를 통합하는 과정을 말해요. 이를 통해서 여러분의 애플리케이션이 어떻게 동작하고 성능은 어떤지 추적할 수 있고, 실제 서비스 환경(Production)에서 발생하는 문제들을 디버깅할 수 있게 됩니다.
💡 강사의 팁 & 부연 설명:
"계측"이라는 말이 조금 낯설게 들릴 수 있죠? 쉽게 말해 우리 서비스에 청진기를 대는 작업이라고 생각하시면 돼요. 실무에서는 Sentry, Datadog, New Relic 같은 모니터링 툴을 정말 많이 사용하는데요. 서버가 시작될 때 이런 툴들이 가장 먼저 초기화되어야 모든 API 요청이나 에러를 빠짐없이 추적할 수 있겠죠? 이럴 때 바로 이 Instrumentation 기능을 활용하는 거랍니다.
이 기능을 설정하려면, 프로젝트의 최상위 디렉토리(루트 폴더)에 instrumentation.ts (또는 .js) 파일을 만들어주세요. (만약 src 폴더를 사용하고 계시다면 그 안에 넣으시면 됩니다.)
파일을 만들었다면, 그 안에서 register라는 함수를 export 해주셔야 해요. 이 함수는 Next.js 서버 인스턴스가 새롭게 시작될 때 딱 한 번만 호출된답니다. 그리고 중요한 건, 서버가 사용자들의 요청을 받을 준비를 마치기 전에 이 함수의 실행이 완전히 끝나야 한다는 거예요.
예를 들어, Next.js에서 OpenTelemetry와 @vercel/otel을 함께 사용하려면 다음과 같이 작성합니다:
import { registerOTel } from '@vercel/otel'
export function register() {
registerOTel('next-app')
}
import { registerOTel } from '@vercel/otel'
export function register() {
registerOTel('next-app')
}
완전한 구현 방법이 궁금하시다면 Next.js와 OpenTelemetry 예시를 확인해 보세요.
알아두면 좋은 점 (Good to know):
instrumentation파일은app이나pages디렉토리 안이 아니라 프로젝트의 루트에 있어야 합니다.src폴더를 사용하는 경우에는pages나app폴더와 같은 위치인src바로 아래에 파일을 두시면 됩니다.- 만약
pageExtensions설정 옵션을 사용해서 파일 확장자에 접미사를 추가했다면,instrumentation파일의 이름도 그에 맞춰서 업데이트해 주셔야 해요.
💡 강사의 팁 & 부연 설명:
register함수는 비동기 작업(async/await)을 지원해요! 데이터베이스 커넥션 풀을 미리 초기화하거나, 외부 API에서 초기 설정값을 땡겨와야 할 때 아주 유용하죠. 단, 이 함수의 실행이 끝나야 Next.js 서버가 비로소 트래픽을 받기 시작하니까 너무 오래 걸리는 무거운 작업(예: 대용량 데이터 전처리)을 넣는 것은 피하는 게 좋습니다. 자칫하면 배포 후 서버가 켜지는 데 한참이 걸릴 수 있거든요.
때로는 코드를 작성할 때, 어떤 파일이 일으키는 부수 효과(Side effects) 때문에 그 파일을 불러오는(Import) 게 유용할 때가 있어요. 예를 들어, 전역 변수들을 정의해두는 파일을 불러오기만 하고, 코드 내에서 그 파일의 내용을 직접적으로 명시해서 쓰지는 않는 경우를 들 수 있죠. 그래도 여러분은 그 패키지가 선언해둔 전역 변수들에는 여전히 접근할 수 있답니다.
저희는 register 함수 내부에서 JavaScript의 import 구문을 사용해서 파일을 불러오는 것을 권장해요. 다음 예시는 register 함수 안에서 import를 사용하는 기본적인 방법을 보여줍니다:
export async function register() {
await import('package-with-side-effect')
}
export async function register() {
await import('package-with-side-effect')
}
알아두면 좋은 점 (Good to know):
파일의 맨 위에서 파일을 전역으로 불러오는 것보다는,
register함수 내부에서 불러오는 것을 강력히 권장합니다. 이렇게 하면 코드가 일으키는 모든 부수 효과들을 코드의 한 곳(register 함수 안)에 모아둘 수 있고, 파일 최상단에서 전역으로 불러왔을 때 발생할 수 있는 의도치 않은 문제들을 피할 수 있어요.
💡 강사의 팁 & 부연 설명:
여기서 말하는 '부수 효과'란, 단순히 함수나 변수를 가져다 쓰기 위해import하는 것이 아니라, 불러오는(Importing) 행위 자체만으로도 어떠한 코드가 실행되는 것을 의미해요. (예:import './polyfill.js'같이 실행만 시키는 경우).
왜 꼭register함수 안에서 동적 임포트(await import())를 하라고 할까요? 파일 맨 위에 정적으로 선언해 버리면, 환경 변수(.env)가 완전히 로드되기도 전에 파일이 먼저 평가되어서 에러가 나는 경우가 실무에서 생각보다 자주 발생하기 때문입니다. 안전하게register안에서 부르세요!
Next.js는 모든 환경에서 register 함수를 호출해요. 그렇기 때문에 특정 런타임(Edge 또는 Node.js)을 지원하지 않는 코드가 있다면, 환경에 맞게 조건부로 불러오는(Conditionally import) 것이 매우 중요합니다. 현재 환경이 무엇인지 확인하려면 NEXT_RUNTIME 환경 변수를 사용하시면 됩니다:
export async function export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./instrumentation-node')
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('./instrumentation-edge')
}
}
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./instrumentation-node')
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('./instrumentation-edge')
}
}
💡 강사의 팁 & 부연 설명:
Next.js의 큰 특징 중 하나가 바로 라우트별로 Node.js 런타임과 Edge 런타임을 섞어서 쓸 수 있다는 거예요. Edge 런타임은 V8 엔진 기반으로 매우 가볍고 빠르지만, Node.js의 내장 모듈(예:fs,child_process)을 사용할 수 없다는 단점이 있어요.
만약 여러분이 쓰는 모니터링 툴(ex. Datadog APM)이 Node.js 전용 모듈을 내부적으로 쓰고 있다면, Edge 환경에서 실행될 때 서버가 펑 터지겠죠? 그래서 위 예시처럼NEXT_RUNTIME을 체크해서 각각의 환경에 맞는 초기화 코드를 따로 분리해서 불러오는 패턴이 정말 유용하답니다. 실무에서 에러를 방지하는 꿀팁이에요!
instrumentation.js 파일에 대한 API 레퍼런스입니다.모든 공식 문서의 의미론적 개요(Semantic overview)를 보고 싶으시다면, /docs/sitemap.md를 확인해 주세요.
사용 가능한 전체 문서의 색인(Index)은 /docs/llms.txt에서 확인하실 수 있습니다.
오늘 학습은 여기까지입니다! 문서 내용뿐만 아니라 실무적인 배경지식까지 함께 짚어봤는데 이해가 잘 되셨나요? 더 궁금한 점이 있거나, 이 기능을 활용해서 여러분만의 프로젝트에 적용해 보고 싶은 아이디어가 있다면 언제든 편하게 질문해 주세요! 제가 도와드릴까요?