본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 12장: 모니터링, 보안, 프로덕션 배포
2026년 2월 11일·웹 개발·

12장: 모니터링, 보안, 프로덕션 배포

Next.js의 프로덕션 배포를 다룹니다. instrumentation.ts, OpenTelemetry, 보안 헤더, Docker 배포, Build Adapters, 셀프 호스팅 전략을 살펴봅니다.

15분990자10개 섹션
nextjsreacttypescriptfrontend
공유
nextjs-app-router12 / 13
12345678910111213
이전11장: 이미지, 폰트, 메타데이터 최적화다음13장: 실전 프로젝트 - Next.js 풀스택 앱 구축

11장에서 이미지, 폰트, 메타데이터 최적화를 다루었습니다. 이번 장에서는 애플리케이션을 프로덕션에 배포하고 운영하는 데 필요한 모니터링, 보안, 배포 전략을 살펴봅니다.

instrumentation.ts: 모니터링 인프라

register() 함수

Next.js 15에서 안정화된 instrumentation.ts는 서버가 시작될 때 한 번 실행되는 초기화 파일입니다. register() 함수에서 모니터링, 로깅, 트레이싱 인프라를 설정합니다.

instrumentation.ts
typescript
export async function register() {
  // 서버 시작 시 한 번 실행
  console.log('Server starting - initializing monitoring...');
 
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    // Node.js 런타임에서만 실행되는 초기화
    // Edge Runtime에서는 실행되지 않음
    const { initOpenTelemetry } = await import('./lib/telemetry');
    initOpenTelemetry();
  }
 
  if (process.env.NEXT_RUNTIME === 'edge') {
    // Edge Runtime 전용 초기화
    const { initEdgeMonitoring } = await import('./lib/edge-monitoring');
    initEdgeMonitoring();
  }
}
Info

register() 함수는 동적 import()를 사용하여 런타임에 따라 다른 모듈을 로드합니다. 이는 Edge Runtime에서 Node.js 전용 모듈이 번들에 포함되는 것을 방지합니다.

onRequestError 훅

onRequestError 훅은 서버 컴포넌트, Server Action, Route Handler에서 발생하는 에러를 중앙에서 캡처합니다.

instrumentation.ts - 에러 캡처
typescript
import type { Instrumentation } from 'next';
 
export const onRequestError: Instrumentation.onRequestError = async (
  error,
  request,
  context
) => {
  // context에서 에러 발생 위치 정보 획득
  const { routerKind, routePath, routeType } = context;
 
  // 에러 리포팅 서비스로 전송
  await fetch('https://error-reporting.example.com/api/errors', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      message: error.message,
      stack: error.stack,
      request: {
        method: request.method,
        url: request.url,
        headers: Object.fromEntries(request.headers),
      },
      context: {
        routerKind,  // 'Pages Router' | 'App Router'
        routePath,   // '/posts/[slug]'
        routeType,   // 'render' | 'route' | 'action' | 'middleware'
      },
      timestamp: new Date().toISOString(),
    }),
  });
};

OpenTelemetry 설정

Next.js는 OpenTelemetry를 공식 지원합니다. @vercel/otel 패키지로 간편하게 설정할 수 있습니다.

lib/telemetry.ts
typescript
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import {
  ATTR_SERVICE_NAME,
  ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';
 
export function initOpenTelemetry() {
  const sdk = new NodeSDK({
    resource: new Resource({
      [ATTR_SERVICE_NAME]: 'my-nextjs-app',
      [ATTR_SERVICE_VERSION]: process.env.APP_VERSION ?? '1.0.0',
    }),
    traceExporter: new OTLPTraceExporter({
      url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? 'http://localhost:4318',
    }),
  });
 
  sdk.start();
}

보안

Server Actions 보안 기능

8장에서 다룬 Server Actions의 보안 기능을 프로덕션 관점에서 재정리합니다.

기능설명설정
Origin 확인CSRF 공격 방지를 위한 출처 검증serverActions.allowedOrigins
암호화된 클로저서버 변수의 클라이언트 노출 방지자동 적용
데드 코드 제거미사용 Server Action을 빌드에서 제거자동 적용
Action ID함수명 대신 해시 ID로 Action 참조자동 적용

CSP(Content Security Policy) 헤더

CSP 헤더는 XSS 공격을 방지하는 핵심 보안 메커니즘입니다. 미들웨어에서 Nonce 기반 CSP를 설정합니다.

proxy.ts - CSP 설정
typescript
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import crypto from 'crypto';
 
export async function middleware(request: NextRequest) {
  // 요청마다 고유한 nonce 생성
  const nonce = crypto.randomBytes(16).toString('base64');
 
  // CSP 헤더 구성
  const cspHeader = [
    `default-src 'self'`,
    `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
    `style-src 'self' 'unsafe-inline'`, // Tailwind CSS 인라인 스타일
    `img-src 'self' data: https:`,
    `font-src 'self'`,
    `connect-src 'self' https://vitals.vercel-insights.com`,
    `frame-src 'self' https://giscus.app`,
    `object-src 'none'`,
    `base-uri 'self'`,
    `form-action 'self'`,
    `frame-ancestors 'none'`,
  ].join('; ');
 
  // nonce를 요청 헤더로 전달 (서버 컴포넌트에서 사용)
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-nonce', nonce);
 
  const response = NextResponse.next({
    request: { headers: requestHeaders },
  });
 
  response.headers.set('Content-Security-Policy', cspHeader);
 
  return response;
}
app/layout.tsx - nonce 적용
typescript
import { headers } from 'next/headers';
import Script from 'next/script';
 
export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const headersList = await headers();
  const nonce = headersList.get('x-nonce') ?? '';
 
  return (
    <html lang="ko">
      <body>
        {children}
        <Script
          src="https://www.googletagmanager.com/gtag/js"
          nonce={nonce}
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

CSRF 보호

Server Actions는 Origin 확인이 기본 적용되지만, API Route Handlers에서는 직접 CSRF 보호를 구현해야 합니다.

lib/csrf.ts
typescript
import { cookies, headers } from 'next/headers';
 
export async function validateCsrf(): Promise<boolean> {
  const headersList = await headers();
  const cookieStore = await cookies();
 
  const origin = headersList.get('origin');
  const host = headersList.get('host');
 
  // Origin과 Host 일치 확인
  if (!origin || !host) return false;
 
  try {
    const originUrl = new URL(origin);
    return originUrl.host === host;
  } catch {
    return false;
  }
}

배포 옵션

Vercel (Zero-Config)

Vercel은 Next.js의 공식 호스팅 플랫폼으로, 별도 설정 없이 배포할 수 있습니다.

Vercel 배포
bash
# Vercel CLI 설치 및 배포
pnpm add -g vercel
vercel
 
# 프로덕션 배포
vercel --prod

Vercel의 장점은 Edge Functions, ISR, Image Optimization이 플랫폼 레벨에서 통합된다는 것입니다. 단점은 비용과 vendor lock-in입니다.

Docker (output: standalone)

자체 인프라나 클라우드 서비스에 배포할 때는 Docker를 사용합니다. output: 'standalone' 설정으로 독립 실행 가능한 서버를 생성합니다.

next.config.ts
typescript
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  output: 'standalone',
};
 
export default nextConfig;
Dockerfile
dockerfile
FROM node:22-alpine AS base
 
# 의존성 설치
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile
 
# 빌드
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN corepack enable pnpm && pnpm build
 
# 프로덕션
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
 
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
 
# standalone 출력 복사
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
 
USER nextjs
EXPOSE 3000
ENV PORT=3000
 
CMD ["node", "server.js"]

output: 'standalone'은 node_modules에서 필요한 파일만 추출하여, Docker 이미지 크기를 크게 줄입니다. 일반적으로 1GB 이상의 node_modules가 50-100MB 수준으로 줄어듭니다.

Tip

sharp는 output: 'standalone' 모드에서 자동으로 포함됩니다. Next.js 15부터 sharp가 기본 이미지 처리 라이브러리이므로, 별도 설치가 불필요합니다.

AWS Amplify

AWS Amplify는 AWS 서울 리전에서 Next.js를 호스팅할 수 있는 서비스입니다.

amplify.yml
yaml
version: 1
frontend:
  phases:
    preBuild:
      commands:
        - corepack enable pnpm
        - pnpm install --frozen-lockfile
    build:
      commands:
        - pnpm build
  artifacts:
    baseDirectory: .next
    files:
      - '**/*'
  cache:
    paths:
      - node_modules/**/*
      - .next/cache/**/*

Amplify는 SSR, ISR, Image Optimization을 지원하며, CloudFront CDN이 자동으로 연결됩니다.

셀프 호스팅 고려사항

expireTime 설정

셀프 호스팅 환경에서 ISR의 캐시 만료 시간을 설정합니다. Vercel에서는 자동 관리되지만, 셀프 호스팅에서는 직접 설정해야 합니다.

next.config.ts
typescript
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  expireTime: 3600, // ISR 캐시 만료 시간 (초)
};
 
export default nextConfig;

Cache-Control 헤더

next.config.ts - 정적 자산 캐싱
typescript
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        // 정적 자산: 1년 캐싱 (파일명에 해시 포함)
        source: '/_next/static/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
      {
        // 폰트: 1년 캐싱
        source: '/fonts/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
      {
        // HTML: 재검증 필요
        source: '/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=0, must-revalidate',
          },
        ],
      },
    ];
  },
};
 
export default nextConfig;

Build Adapters (Next.js 16.2)

Next.js 16.2에서 안정화된 Build Adapters는 커스텀 배포 플랫폼을 위한 빌드 출력 변환 인터페이스입니다.

Build Adapter를 사용하면 Next.js의 빌드 출력을 특정 플랫폼의 형식으로 변환할 수 있습니다. 공식 어댑터가 없는 플랫폼에서도 Next.js를 배포할 수 있게 됩니다.

next.config.ts - Build Adapter
typescript
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  // 커스텀 빌드 어댑터 지정
  buildAdapter: '@my-platform/nextjs-adapter',
};
 
export default nextConfig;

next.config.ts TypeScript 지원

Next.js 15부터 next.config.ts가 공식 지원됩니다. JavaScript 설정 파일에서 마이그레이션할 수 있습니다.

next.config.ts - 타입 안전한 설정
typescript
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  // TypeScript의 자동 완성과 타입 검증을 활용
  reactStrictMode: true,
  poweredByHeader: false,
 
  // 환경 변수 타입 안전성
  env: {
    NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL ?? '',
  },
 
  // 리디렉트
  async redirects() {
    return [
      {
        source: '/blog/:slug',
        destination: '/tech/:slug',
        permanent: true,
      },
    ];
  },
};
 
export default nextConfig;

환경 변수 모범 사례

환경 변수 구분
typescript
// 1. 서버 전용 (브라우저에 노출되지 않음)
// DATABASE_URL=postgresql://...
// JWT_SECRET=my-secret-key
// OPENAI_API_KEY=sk-...
 
// 2. 클라이언트 노출 (NEXT_PUBLIC_ 접두사)
// NEXT_PUBLIC_SITE_URL=https://archive.kreathlab.com
// NEXT_PUBLIC_GA_ID=G-XXXXXXX
 
// 3. 런타임 vs 빌드 타임
// 빌드 타임: NEXT_PUBLIC_* 변수는 빌드 시 인라인됨
// 런타임: 서버 전용 변수는 런타임에 읽힘
Warning

NEXT_PUBLIC_ 접두사가 붙은 변수는 클라이언트 JavaScript 번들에 인라인됩니다. API 키, 시크릿, 데이터베이스 URL에는 절대 이 접두사를 사용하지 마세요. 브라우저 개발자 도구에서 누구나 볼 수 있습니다.

Static Exports vs Dynamic Rendering

특성Static ExportDynamic Rendering
설정output: 'export'기본값
서버불필요 (정적 파일만)Node.js 서버 필요
SSR불가가능
API Routes불가가능
ISR불가가능
Image Optimization불가 (외부 서비스 필요)next/image 사용 가능
호스팅S3, GitHub Pages, NetlifyVercel, Docker, Amplify
next.config.ts - Static Export
typescript
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  output: 'export',
  // Static Export에서는 이미지 최적화 비활성화
  images: {
    unoptimized: true,
  },
};
 
export default nextConfig;

Static Export는 서버가 필요 없는 단순한 사이트에 적합합니다. 블로그, 문서 사이트, 포트폴리오 등이 좋은 사례입니다. 하지만 Server Actions, ISR, 미들웨어 등 서버 기능은 사용할 수 없습니다.

프로덕션 배포 체크리스트

  1. 빌드 검증: pnpm build가 에러 없이 통과하는지 확인
  2. 환경 변수: 프로덕션 환경 변수가 모두 설정되었는지 확인
  3. 보안 헤더: CSP, HSTS, X-Frame-Options 등 보안 헤더 적용
  4. 이미지 최적화: next/image 사용, remotePatterns 설정
  5. 모니터링: instrumentation.ts에서 에러 리포팅과 트레이싱 설정
  6. 캐싱: 정적 자산 캐싱 헤더, ISR expireTime 설정
  7. 성능: Lighthouse 점수 확인, Core Web Vitals 모니터링
  8. SEO: sitemap.xml, robots.txt, 구조화 데이터 확인

핵심 요약

  • instrumentation.ts의 register() 함수로 서버 시작 시 모니터링 인프라를 초기화하고, onRequestError 훅으로 에러를 중앙에서 캡처합니다.
  • CSP Nonce, Origin 확인, CSRF 보호를 조합하여 보안 레이어를 구성합니다. Server Actions의 암호화된 클로저와 데드 코드 제거가 추가 보안을 제공합니다.
  • output: 'standalone'으로 Docker 이미지 크기를 최소화하고, AWS Amplify나 Vercel로 간편하게 배포할 수 있습니다.
  • Build Adapters(16.2 안정화)로 커스텀 플랫폼 배포를 지원하며, next.config.ts의 TypeScript 지원으로 타입 안전한 설정이 가능합니다.
  • 셀프 호스팅에서는 expireTime, Cache-Control 헤더, sharp 자동 포함 등을 직접 관리해야 합니다.

다음 장에서는 이 시리즈의 마지막으로, 지금까지 다룬 모든 기능을 활용하여 실전 프로젝트를 구축합니다. Link Shortener 앱을 만들며 시리즈 전체를 복습합니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#nextjs#react#typescript#frontend

관련 글

웹 개발

13장: 실전 프로젝트 - Next.js 풀스택 앱 구축

Link Shortener 앱을 구축하며 Next.js App Router의 모든 기능을 실전에 적용합니다. 프로젝트 구조 설계부터 배포까지 전 과정을 다룹니다.

2026년 2월 13일·21분
웹 개발

11장: 이미지, 폰트, 메타데이터 최적화

Next.js의 이미지, 폰트, 메타데이터 최적화를 다룹니다. next/image, next/font, generateMetadata, OG 이미지 생성, Core Web Vitals 전략을 살펴봅니다.

2026년 2월 9일·17분
웹 개발

10장: 미들웨어와 Proxy 고급 패턴

Next.js의 미들웨어 진화를 다룹니다. middleware.ts에서 proxy.ts로의 전환, 인증, i18n, A/B 테스팅, 속도 제한 등 고급 패턴을 살펴봅니다.

2026년 2월 7일·17분
이전 글11장: 이미지, 폰트, 메타데이터 최적화
다음 글13장: 실전 프로젝트 - Next.js 풀스택 앱 구축

댓글

목차

약 15분 남음
  • instrumentation.ts: 모니터링 인프라
    • register() 함수
    • onRequestError 훅
    • OpenTelemetry 설정
  • 보안
    • Server Actions 보안 기능
    • CSP(Content Security Policy) 헤더
    • CSRF 보호
  • 배포 옵션
    • Vercel (Zero-Config)
    • Docker (output: standalone)
    • AWS Amplify
  • 셀프 호스팅 고려사항
    • expireTime 설정
    • Cache-Control 헤더
  • Build Adapters (Next.js 16.2)
  • next.config.ts TypeScript 지원
  • 환경 변수 모범 사례
  • Static Exports vs Dynamic Rendering
  • 프로덕션 배포 체크리스트
  • 핵심 요약