How to set a Content Security Policy (CSP) for your Next.js application

김동현·2026년 3월 5일

next.js 공식문서 번역

목록 보기
43/79

콘텐츠 보안 정책(Content Security Policy, CSP)은 교차 사이트 스크립팅(XSS), 클릭재킹, 그리고 기타 코드 삽입 공격과 같은 다양한 보안 위협으로부터 여러분의 Next.js 애플리케이션을 안전하게 지키기 위해 정말 중요한 역할을 합니다.

개발자들은 CSP를 사용해서 콘텐츠 출처, 스크립트, 스타일시트, 이미지, 폰트, 객체, 미디어(오디오, 비디오), iframe 등에 대해 어떤 출처(Origin)만 허용할 것인지 명시적으로 지정할 수 있어요.

💡 강사의 보충 설명 & 팁
"교차 사이트 스크립팅(XSS)"이 뭔지 궁금하시죠? 악의적인 해커가 우리 웹사이트 게시판 같은 곳에 몰래 자바스크립트 코드를 심어두고, 다른 사용자가 그 게시글을 읽을 때 해커의 코드가 실행되게 해서 로그인 토큰 같은 개인정보를 빼가는 무서운 해킹 기법이에요. CSP를 잘 설정해두면, 해커가 몰래 심어둔 출처 불명의 스크립트는 브라우저가 아예 실행을 차단해버리기 때문에 이런 공격을 원천적으로 막을 수 있답니다! 실무에 가면 보안팀에서 가장 먼저 요구하는 설정 중 하나예요.

예제 코드 확인하기

Nonces (논스)

논스(nonce)딱 한 번만 사용하기 위해 만들어진 고유하고 무작위의 문자열입니다. 엄격한 CSP 지시어를 우회하여 우리가 허용한 특정 인라인 스크립트나 스타일만 선택적으로 실행할 수 있도록 CSP와 함께 짝을 이루어 사용된답니다.

💡 강사의 보충 설명 & 팁
Nonce는 'Number used ONCE(한 번만 쓰이는 숫자)'의 줄임말이에요. 여러분 은행에서 송금할 때 쓰는 OTP(일회용 비밀번호) 아시죠? 그거랑 똑같은 개념이라고 생각하시면 됩니다. 매번 요청이 올 때마다 새로운 비밀번호(논스)를 만들어서 우리가 작성한 스크립트에 달아두고, 브라우저에게 "이 비밀번호가 적힌 스크립트만 실행해!"라고 알려주는 거예요.

왜 논스(nonce)를 사용해야 할까요?

CSP는 악의적인 공격을 막기 위해 인라인 스크립트(HTML 파일 안에 직접 작성한 스크립트)와 외부 스크립트 모두를 차단할 수 있습니다. 하지만 때로는 우리가 만든 스크립트는 실행시켜야 하잖아요? 이럴 때 논스를 사용하면, 정확히 일치하는 논스 값을 포함한 특정 스크립트만 안전하게 실행되도록 허용할 수 있어요.

만약 해커가 우리 페이지에 악성 스크립트를 로드하고 싶다면, 그들은 이번 요청에서 생성된 논스 값을 정확히 추측해야만 합니다. 그렇기 때문에 논스는 매 요청마다 절대 예측할 수 없고 고유해야 하는 거죠.

Proxy로 논스 추가하기

Proxy 파일을 활용하면 페이지가 렌더링되기 전에 헤더를 추가하고 논스를 생성할 수 있습니다.

페이지를 조회할 때마다 항상 새로운 논스가 생성되어야 합니다. 이 말은 즉, 논스를 추가하려면 반드시 동적 렌더링(dynamic rendering)을 사용해야 한다는 뜻입니다.

예시를 한번 볼까요:

알아두면 좋은 정보: 개발(Development) 환경에서는 'unsafe-eval'을 허용해 주어야 합니다. 왜냐하면 React가 브라우저에서 서버 측 오류 스택을 재구성하는 등 향상된 디버깅 정보를 제공하기 위해 내부적으로 eval 함수를 사용하기 때문이죠. 프로덕션(Production, 실제 서비스) 환경에서는 unsafe-eval이 필요하지 않습니다. React나 Next.js 모두 기본적으로 프로덕션에서는 eval을 사용하지 않으니까 안심하세요!

import { NextRequest, NextResponse } from 'next/server'

export function proxy(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const isDev = process.env.NODE_ENV === 'development'
  const cspHeader = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic'${isDev ? " 'unsafe-eval'" : ''};
    style-src 'self' 'nonce-${nonce}';
    img-src 'self' blob: data:;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
    upgrade-insecure-requests;
`
  // Replace newline characters and spaces
  const contentSecurityPolicyHeaderValue = cspHeader
    .replace(/\s{2,}/g, ' ')
    .trim()

  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-nonce', nonce)

  requestHeaders.set(
    'Content-Security-Policy',
    contentSecurityPolicyHeaderValue
  )

  const response = NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  })
  response.headers.set(
    'Content-Security-Policy',
    contentSecurityPolicyHeaderValue
  )

  return response
}
import { NextResponse } from 'next/server'

export function proxy(request) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const isDev = process.env.NODE_ENV === 'development'
  const cspHeader = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic'${isDev ? " 'unsafe-eval'" : ''};
    style-src 'self' 'nonce-${nonce}';
    img-src 'self' blob: data:;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
    upgrade-insecure-requests;
`
  // Replace newline characters and spaces
  const contentSecurityPolicyHeaderValue = cspHeader
    .replace(/\s{2,}/g, ' ')
    .trim()

  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-nonce', nonce)
  requestHeaders.set(
    'Content-Security-Policy',
    contentSecurityPolicyHeaderValue
  )

  const response = NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  })
  response.headers.set(
    'Content-Security-Policy',
    contentSecurityPolicyHeaderValue
  )

  return response
}

💡 강사의 보충 설명 & 팁
위 코드에서 crypto.randomUUID()를 사용해서 논스 값을 만들고 있죠? 이건 웹 표준 암호화 API라서 아주 강력하고 무작위성이 뛰어납니다. 그리고 코드를 보시면 요청(request) 헤더와 응답(response) 헤더 양쪽 모두에 CSP 헤더를 세팅해주고 있는데, 이렇게 해야 Next.js가 렌더링하는 동안에도 값을 읽을 수 있고 최종적으로 클라이언트 브라우저에도 정책이 잘 전달됩니다.

기본적으로 Proxy는 모든 요청에 대해 실행됩니다. 하지만 matcher를 설정하면 특정 경로에서만 Proxy가 실행되도록 필터링할 수 있어요.

CSP 헤더가 굳이 필요 없는 정적 에셋(이미지, 폰트 등)이나 next/link에서 발생하는 프리패치(prefetch) 요청은 무시하도록 설정하는 것을 추천합니다.

export const config = {
  matcher: [
    /*
     * 다음으로 시작하는 경로를 제외한 모든 요청 경로와 일치시킵니다:
     * - api (API 라우트)
     * - _next/static (정적 파일)
     * - _next/image (이미지 최적화 파일)
     * - favicon.ico (파비콘 파일)
     */
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      missing: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },
  ],
}
export const config = {
  matcher: [
    /*
     * 다음으로 시작하는 경로를 제외한 모든 요청 경로와 일치시킵니다:
     * - api (API 라우트)
     * - _next/static (정적 파일)
     * - _next/image (이미지 최적화 파일)
     * - favicon.ico (파비콘 파일)
     */
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      missing: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },
  ],
}

💡 강사의 보충 설명 & 팁
Proxy(혹은 Middleware)는 사이트 트래픽이 많아질수록 성능에 직접적인 영향을 줍니다. 정적인 이미지나 CSS 파일 같은 것들까지 매번 검사해서 헤더를 달아줄 필요는 없겠죠? 그래서 저렇게 정규표현식을 써서 불필요한 연산을 줄여주는 최적화 작업은 실무에서 정말 칭찬받는 포인트입니다!

Next.js에서 논스(nonce)가 작동하는 원리

논스를 사용하려면, 해당 페이지는 반드시 동적 렌더링(dynamically rendered) 되어야 합니다. 왜냐하면 Next.js는 요청에 포함된 CSP 헤더를 기반으로 서버 사이드 렌더링(SSR) 과정 중에 논스를 적용하기 때문이에요. 빌드 타임에 미리 만들어두는 정적 페이지(Static pages)는 요청이나 응답 헤더 자체가 존재하지 않기 때문에 논스를 주입할 수가 없어요.

동적으로 렌더링되는 페이지에서 논스 지원이 어떻게 이루어지는지 순서대로 살펴볼까요?

  1. Proxy가 논스를 생성합니다: Proxy에서 해당 요청에 대한 고유한 논스를 만들고, 이를 Content-Security-Policy 헤더에 추가한 다음 사용자 정의 헤더인 x-nonce 에도 설정합니다.
  2. Next.js가 논스를 추출합니다: 렌더링하는 동안 Next.js는 Content-Security-Policy 헤더를 분석해서 'nonce-{value}' 패턴을 찾아내 논스 값을 빼냅니다.
  3. 논스가 자동으로 적용됩니다: Next.js는 이 논스 값을 알아서 다음 요소들에 달아줍니다:
    • 프레임워크 스크립트 (React, Next.js 런타임)
    • 페이지별 자바스크립트 번들 파일
    • Next.js가 자동으로 생성하는 인라인 스타일과 스크립트
    • nonce 속성(prop)을 사용하는 모든 <Script> 컴포넌트

이렇게 프레임워크가 알아서 처리해주기 때문에, 개발자인 여러분이 일일이 모든 태그를 찾아다니며 수동으로 논스를 추가할 필요가 없습니다. 아주 편리하죠!

강제로 동적 렌더링(Dynamic Rendering) 적용하기

논스를 사용 중이라면, 페이지가 강제로 동적 렌더링되도록 명시적인 설정을 해줘야 할 수도 있습니다:

import { connection } from 'next/server'

export default async function Page() {
  // 이 페이지를 렌더링하기 위해 들어오는 요청이 있을 때까지 기다립니다.
  await connection()
  // 페이지 콘텐츠 작성
}
import { connection } from 'next/server'

export default async function Page() {
  // 이 페이지를 렌더링하기 위해 들어오는 요청이 있을 때까지 기다립니다.
  await connection()
  // 페이지 콘텐츠 작성
}

💡 강사의 보충 설명 & 팁
await connection() 함수는 비교적 최신 버전에 도입된 API입니다. 예전에는 headers()cookies() 같은 동적 함수를 사용하면 알아서 동적 렌더링으로 전환되었지만, 명시적으로 "이 페이지는 무조건 동적 렌더링 할거야!" 라고 프레임워크에 알려주기 위해 사용하는 아주 직관적인 방법이랍니다.

논스(nonce) 값 읽어오기

서버 컴포넌트(Server Component) 내부에서 headers 함수를 사용하면 논스 값을 직접 읽어올 수 있어요:

import { headers } from 'next/headers'
import Script from 'next/script'

export default async function Page() {
  const nonce = (await headers()).get('x-nonce')

  return (
    <Script
      src="[https://www.googletagmanager.com/gtag/js](https://www.googletagmanager.com/gtag/js)"
      strategy="afterInteractive"
      nonce={nonce}
    />
  )
}
import { headers } from 'next/headers'
import Script from 'next/script'

export default async function Page() {
  const nonce = (await headers()).get('x-nonce')

  return (
    <Script
      src="[https://www.googletagmanager.com/gtag/js](https://www.googletagmanager.com/gtag/js)"
      strategy="afterInteractive"
      nonce={nonce}
    />
  )
}

정적 렌더링 vs 동적 렌더링과 CSP

논스를 사용한다는 것은 Next.js 애플리케이션의 렌더링 방식에 아주 중요한 영향을 미칩니다:

동적 렌더링의 필수화

CSP에 논스를 사용하게 되면, 반드시 모든 페이지가 동적으로 렌더링되어야 합니다. 이 말은 즉:

  • 빌드 자체는 성공적으로 끝나더라도, 동적 렌더링 설정이 제대로 되어있지 않으면 런타임 환경에서 에러가 발생할 수 있습니다.
  • 사용자가 요청을 보낼 때마다 새로운 논스가 포함된 새 페이지를 매번 생성해야 합니다.
  • 정적 최적화(Static optimization)와 점진적 정적 재생성(ISR) 기능이 비활성화됩니다.
  • 추가적인 설정 없이는 CDN(콘텐츠 전송 네트워크) 서버에 페이지를 캐싱(저장)할 수 없습니다.
  • 부분 사전 렌더링(Partial Prerendering, PPR)은 논스 기반의 CSP와 호환되지 않습니다. 미리 렌더링된 정적인 껍데기(shell) 스크립트들은 런타임에 만들어지는 논스 값에 접근할 수 없기 때문이죠.

성능에 미치는 영향

정적 렌더링에서 동적 렌더링으로의 전환은 성능에 다음과 같은 영향을 줍니다:

  • 초기 페이지 로딩 속도 저하: 매 요청마다 서버에서 페이지를 새로 그려야 하니까요.
  • 서버 부하 증가: 모든 요청이 서버 사이드 렌더링(SSR)을 거쳐야 합니다.
  • CDN 캐싱 불가: 동적 페이지는 기본적으로 엣지(Edge) 서버에서 캐싱될 수 없습니다.
  • 호스팅 비용 증가: 동적 렌더링을 처리하기 위해 더 많은 서버 자원이 필요해집니다.

💡 강사의 보충 설명 & 팁
이 부분이 정말 중요합니다! 보안을 챙기려다가 웹사이트의 속도가 느려지고 서버 비용이 폭탄이 될 수도 있다는 이야기죠. 블로그나 회사 소개 페이지처럼 내용이 잘 안 바뀌는 사이트라면 굳이 Nonce를 쓰기보다는 밑에서 배울 다른 방식(Static CSP나 SRI)을 고려하는 게 현명한 개발자의 판단입니다.

언제 논스를 사용해야 할까요?

다음과 같은 경우에 논스 사용을 고려해 보세요:

  • 'unsafe-inline'(인라인 스크립트 허용)을 절대 금지하는 아주 엄격한 보안 요구사항이 있을 때.
  • 애플리케이션이 민감한 사용자 데이터를 다룰 때 (예: 금융, 헬스케어).
  • 대부분의 인라인 스크립트는 차단하되, 아주 특정한 스크립트만 콕 집어서 실행을 허용해야 할 때.
  • 외부 보안 규정(컴플라이언스)을 준수하기 위해 엄격한 CSP가 강제될 때.

논스를 사용하지 않는 방식 (Without Nonces)

논스가 굳이 필요 없는 애플리케이션이라면, next.config.js 파일에 직접 CSP 헤더를 정적으로 설정할 수 있습니다. 이렇게 하면 정적 렌더링의 이점을 그대로 살릴 수 있죠:

const isDev = process.env.NODE_ENV === 'development'

const cspHeader = `
    default-src 'self';
    script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ''};
    style-src 'self' 'unsafe-inline';
    img-src 'self' blob: data:;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
    upgrade-insecure-requests;
`

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: cspHeader.replace(/\n/g, ''),
          },
        ],
      },
    ]
  },
}

하위 자원 무결성 (Subresource Integrity, 실험적 기능)

논스를 사용하는 방식의 대안으로, Next.js는 하위 자원 무결성(SRI)을 활용한 해시 기반 CSP에 대해 실험적인 지원을 제공하고 있습니다. 이 접근 방식을 사용하면, 엄격한 CSP 보안을 유지하면서도 정적 생성(Static Generation)의 이점을 포기하지 않을 수 있답니다!

알아두면 좋은 정보: 이 기능은 아직 실험적(Experimental) 단계이며, App Router 구조에서 웹팩(webpack) 번들러를 사용할 때만 사용 가능합니다.

SRI 작동 원리

논스를 매번 생성하는 대신, SRI는 애플리케이션을 빌드하는 단계에서 여러분의 자바스크립트 파일들에 대한 암호화 해시(hash) 값을 생성합니다. 이 해시 값들은 스크립트 태그의 integrity 속성으로 추가되며, 이를 통해 브라우저는 네트워크를 타고 파일이 전달되는 과정에서 해당 파일이 변조되지 않았음을 완벽하게 검증할 수 있습니다.

💡 강사의 보충 설명 & 팁
예를 들어 app.js 파일 내용으로 QWERTY... 라는 암호를 미리 만들어두고 브라우저에게 "이 파일은 해독하면 무조건 QWERTY... 가 나와야 해!"라고 알려주는 거예요. 만약 해커가 중간에 파일 내용을 조금이라도 바꾸면 암호가 달라지겠죠? 그럼 브라우저가 "어? 암호가 다르네? 악성 코드 섞였구나!" 하고 실행을 거부합니다. 정적 파일을 유지하면서도 보안을 챙길 수 있는 아주 멋진 기술이죠.

SRI 활성화하기

next.config.js 파일에 실험적 기능인 SRI 설정을 추가해 주세요:

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    sri: {
      algorithm: 'sha256', // 'sha384' 또는 'sha512' 도 사용 가능합니다
    },
  },
}

module.exports = nextConfig

SRI를 활용한 CSP 설정

SRI를 활성화했다면, 기존에 쓰던 CSP 정책을 그대로 계속 사용할 수 있습니다. SRI는 기존 자산에 integrity 속성을 덧붙이는 식으로 독립적으로 알아서 잘 작동하거든요:

알아두면 좋은 정보: 동적 렌더링이 필요한 상황이라면, 필요에 따라 Proxy를 통해 논스를 생성하는 방식을 함께 써서, SRI 무결성 검증과 논스 기반 CSP 방식을 적절히 섞어 쓸 수도 있습니다.

const isDev = process.env.NODE_ENV === 'development'

const cspHeader = `
    default-src 'self';
    script-src 'self'${isDev ? " 'unsafe-eval'" : ''};
    style-src 'self';
    img-src 'self' blob: data:;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
    upgrade-insecure-requests;
`

module.exports = {
  experimental: {
    sri: {
      algorithm: 'sha256',
    },
  },
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: cspHeader.replace(/\n/g, ''),
          },
        ],
      },
    ]
  },
}

논스와 비교했을 때 SRI의 장점

  • 정적 생성 유지: 페이지를 정적으로 생성(Static Generation)하고 캐싱할 수 있습니다.
  • CDN 완벽 호환: 정적 페이지이므로 CDN 캐싱이 문제없이 작동합니다.
  • 더 나은 성능: 매 요청마다 서버에서 무겁게 렌더링할 필요가 없어서 훨씬 빠릅니다.
  • 빌드 타임 보안 보장: 파일이 만들어지는 빌드 시점에 해시가 생성되므로, 코드의 무결성이 완벽히 보장됩니다.

SRI의 한계점

  • 실험적 기능: 언제든 기능 명세가 바뀌거나 아예 제거될 수도 있습니다.
  • 웹팩(Webpack) 전용: Turbopack에서는 아직 사용할 수 없습니다.
  • App Router 전용: 이전 버전의 Pages Router 방식에서는 지원하지 않습니다.
  • 빌드 타임 전용: 런타임에 동적으로 생성되는 스크립트는 이 방식으로 처리할 수 없습니다.

개발 환경과 프로덕션 환경에서의 고려사항

개발 환경과 실제 운영(프로덕션) 환경에서의 CSP 구현은 차이가 있습니다:

개발 환경 (Development Environment)

개발 모드에서는 'unsafe-eval' 정책을 반드시 허용해야 합니다. 왜냐하면 React가 여러분에게 브라우저 화면상에서 서버 측 오류가 어디서 시작되었는지 에러 스택을 예쁘게 그려주기 위해(Enhanced debugging information) 내부적으로 eval 기능을 활용하기 때문이죠:

export function proxy(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const isDev = process.env.NODE_ENV === 'development'

  const cspHeader = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ''};
    style-src 'self' ${isDev ? "'unsafe-inline'" : `'nonce-${nonce}'`};
    img-src 'self' blob: data:;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
    upgrade-insecure-requests;
`

  // 나머지 proxy 구현 로직
}
export function proxy(request) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const isDev = process.env.NODE_ENV === 'development'

  const cspHeader = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ''};
    style-src 'self' ${isDev ? "'unsafe-inline'" : `'nonce-${nonce}'`};
    img-src 'self' blob: data:;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
    upgrade-insecure-requests;
`

  // 나머지 proxy 구현 로직
}

프로덕션 배포 (Production Deployment)

실제 서비스에 배포했을 때 흔히 마주치는 문제점들이에요:

  • 논스가 적용되지 않음: 작성하신 Proxy(미들웨어) 로직이 필요한 모든 라우트(경로)에서 정상적으로 실행되고 있는지 점검해 보세요.
  • 정적 에셋 차단됨: 여러분이 설정한 CSP 정책이 Next.js가 필요로 하는 정적 파일들을 실수로 막고 있지는 않은지 확인해야 합니다.
  • 서드파티(Third-party) 스크립트 차단: 구글 애널리틱스 같은 외부 스크립트 도메인을 CSP 정책에 추가해주는 것을 잊지 마세요.

문제 해결 (Troubleshooting)

서드파티 스크립트 (Third-party Scripts) 연동하기

마케팅 툴이나 애널리틱스 같은 외부 스크립트와 CSP를 함께 쓸 때는 이렇게 해보세요:

import { GoogleTagManager } from '@next/third-parties/google'
import { headers } from 'next/headers'

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const nonce = (await headers()).get('x-nonce')

  return (
    <html lang="en">
      <body>
        {children}
        {/* 구글 태그 매니저에 nonce 속성을 전달해줍니다 */}
        <GoogleTagManager gtmId="GTM-XYZ" nonce={nonce} />
      </body>
    </html>
  )
}
import { GoogleTagManager } from '@next/third-parties/google'
import { headers } from 'next/headers'

export default async function RootLayout({ children }) {
  const nonce = (await headers()).get('x-nonce')

  return (
    <html lang="en">
      <body>
        {children}
        <GoogleTagManager gtmId="GTM-XYZ" nonce={nonce} />
      </body>
    </html>
  )
}

그리고 스크립트가 로드되는 외부 도메인 자체를 CSP에서 허용해 주도록 헤더를 업데이트해야 합니다:

const cspHeader = `
  default-src 'self';
  script-src 'self' 'nonce-${nonce}' 'strict-dynamic' [https://www.googletagmanager.com](https://www.googletagmanager.com);
  connect-src 'self' [https://www.google-analytics.com](https://www.google-analytics.com);
  img-src 'self' data: [https://www.google-analytics.com](https://www.google-analytics.com);
`
const cspHeader = `
  default-src 'self';
  script-src 'self' 'nonce-${nonce}' 'strict-dynamic' [https://www.googletagmanager.com](https://www.googletagmanager.com);
  connect-src 'self' [https://www.google-analytics.com](https://www.google-analytics.com);
  img-src 'self' data: [https://www.google-analytics.com](https://www.google-analytics.com);
`

💡 강사의 보충 설명 & 팁
실무에서 프론트엔드 개발자들이 가장 고통받는(?) 순간이 바로 마케팅팀에서 "이거 스크립트 좀 추가해 주세요!" 라고 할 때예요. GTM(구글 태그 매니저)이나 페이스북 픽셀 같은 걸 달면 CSP 정책에 계속 위반되어서 빨간 에러를 뿜어내거든요. 위 예시처럼 <GoogleTagManager> 컴포넌트에도 꼭 nonce를 넘겨주고, 해당 외부 스크립트 도메인을 Proxy 코드의 script-srcconnect-src에 반드시 허용 목록으로 추가해줘야 무사히 실행된답니다!

흔하게 발생하는 CSP 위반 사례들

  1. 인라인 스타일(Inline styles): style-src 정책 위반이 뜬다면, 논스를 지원하는 CSS-in-JS 라이브러리로 교체하거나, 아예 인라인 스타일을 외부 CSS 파일로 분리해 보세요.
  2. 동적 임포트(Dynamic imports): import() 문법을 사용 중이라면 script-src 정책에서 동적으로 가져오는 코드가 허용되어 있는지 확인하세요.
  3. WebAssembly (웹어셈블리): 만약 웹어셈블리를 사용하신다면 'wasm-unsafe-eval' 정책을 추가해야 합니다.
  4. 서비스 워커(Service workers): PWA 등을 위해 서비스 워커 스크립트를 쓰신다면 거기에 맞는 적절한 정책 추가가 필요합니다.

버전 관리 역사 (Version History)

버전(Version)변경 사항(Changes)
v14.0.0해시 기반 CSP를 위한 실험적인 하위 자원 무결성(SRI) 지원 기능이 추가되었습니다.
v13.4.20올바른 논스 처리 및 CSP 헤더 파싱을 위해 권장되는 버전입니다.
  • proxy.js
    • proxy.js 파일에 대한 API 레퍼런스 문서입니다.
  • headers
    • headers 함수에 대한 API 레퍼런스 문서입니다.

모든 문서에 대한 의미론적(semantic) 개요를 보고 싶으시다면, https://nextjs.org/docs/sitemap.md 를 참고해 주세요.

사용 가능한 전체 문서의 색인(Index)을 보시려면, https://nextjs.org/docs/llms.txt 를 참고해 주세요.


자, 여기까지 Next.js에서 Content Security Policy(CSP)를 설정하는 방법을 쭉 살펴보았습니다. 보안과 성능 사이의 트레이드오프(동적 렌더링 전환 여부)를 잘 고려해서, 여러분의 프로젝트에 맞는 최적의 보안 정책을 설계하시길 바랍니다. 수고하셨습니다! 😊

profile
프론트에_가까운_풀스택_개발자

0개의 댓글