왜 Azure Front Door가 내 Next.js 앱을 90초 동안 로드하게 만들었는지 (그리고 내가 해결한 방법)

발행: (2026년 2월 22일 오전 01:17 GMT+9)
8 분 소요
원문: Dev.to

Source: Dev.to

번역을 원하는 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다.

Source:

문제

우리는 Azure Front Door Premium와 Private Link 뒤에 Azure Container AppsNext.js 앱을 배포했습니다.
모든 설정은 표준적인 것이었고, 특이한 점은 없었습니다.

배포 후 모든 페이지가 약 90 초 정도 로드되는 현상이 발생했습니다.

  • HTML 문서는 정상적으로 로드되었습니다.
  • API 라우트는 빠르게 응답했습니다.
  • 모든 JavaScript 청크가 정확히 90 초 동안 정지한 뒤 브라우저에서 ERR_HTTP2_PROTOCOL_ERROR(기본 HTTP/2 스트림이 INTERNAL_ERROR (err 2)로 종료됨) 오류를 발생시켰습니다.

참고: 실패는 일부 청크가 아니라 전체 청크에 영향을 미쳤습니다.

환경

구성 요소상세
Azure Container Apps에서 실행되는 Next.js 16 (내부 환경, Private Link)
CDN/WAFAzure Front Door Premium
라우트• API – /api/*
• 정적 자산 – /_next/static/*
• 전체 매치 – /*
Next.js 설정compress: true (기본값)
헬스 프로브100 % 정상; 작은 응답은 문제 없음
SSR 페이지 (/sign‑in, 78 KB)전체 매치 라우트를 통해 ~300 ms에 로드 – 정적 자산 전달이 문제였습니다

문제 재현

동일한 JS 청크 – Accept‑Encoding 유무에 따라

# gzip 없이 — 303 ms, 전체 응답
curl -s -w "Total: %{time_total}s\n" -o /dev/null \
  "https://my-fd-endpoint.azurefd.net/_next/static/chunks/app.js"
#=> Total: 0.303414s
# gzip 사용 — 90 seconds, 불완전, HTTP/2 스트림 오류
curl -s -w "Total: %{time_total}s\n" -o /dev/null \
  -H "Accept-Encoding: gzip" \
  "https://my-fd-endpoint.azurefd.net/_next/static/chunks/app.js"
#=> Total: 90.245256s

두 요청 모두 같은 파일, 같은 라우트, 같은 오리진을 대상으로 합니다.
유일한 차이점은 클라이언트가 gzip 압축을 요청한다는 점입니다.

응답 헤더 (gzip 요청)

HTTP/2 200
content-type: application/javascript; charset=UTF-8
content-length: 112049
cache-control: public, max-age=31536000, immutable
content-encoding: gzip
vary: Accept-Encoding
x-cache: TCP_MISS
x-azure-ref: 20260220T195031Z-157f99bd8b842q87hC1CPH...
  • content-length: 112049 content-encoding: gzip 가 함께 존재합니다.
  • curl8,527 바이트만 수신하고 HTTP/2 스트림이 INTERNAL_ERROR (err 2) 로 종료됩니다.

SSR 페이지 (/sign‑in) – 정상 동작

HTTP/2 200
content-type: text/html; charset=utf-8
vary: rsc, next-router-state-tree, next-router-prefetch, ...
cache-control: private, no-cache, no-store, max-age=0, must-revalidate
x-cache: CONFIG_NOCACHE
  • content-encoding 없음.
  • content-length 없음 (청크 전송).

오리진은 SSR 응답에 대해 gzip 압축을 수행하지 않으며, 클라이언트가 압축을 요청해도 적용되지 않습니다. 그래서 SSR 페이지는 정상 작동하지만 정적 청크는 실패합니다.

우리가 시도한 내용

시도변경 내용결과
1Front Door 압축 비활성화 (compression_enabled = false)여전히 문제 – 이슈가 이중 압축이 아님을 증명함.
2캐시 블록을 완전히 제거함여전히 문제 – 캐시가 원인 아님.
3전달 프로토콜을 HttpOnly(Private Link를 통한 TLS)로 전환여전히 문제 – TLS 오버헤드가 원인이 아님.

결론: Front Door는 원본에서 이미 gzip 압축된 응답을 제대로 전달할 수 없으며; 정확히 90초 후에 연결을 중단하고 끊습니다. 이는 Front Door의 설정 불가능한 HTTP keep‑alive 유휴 타임아웃과 일치합니다.

이 동작은 Private Link에만 해당되는 것이 아닙니다. Microsoft는 PoP 전역에서 HTTP 준수를 강화한 후 동일한 실패를 설명하는 Health Advisory를 발표했습니다. 그들의 Q&A 스레드(첫 번째, 두 번째)는 동일한 해결책을 가리키고 있습니다: 원본 압축 비활성화.

수정

  1. 원본에서 압축을 비활성화합니다.
  2. Front Door가 엣지에서 압축하도록 합니다.

예시: Next.js

// next.config.js
const nextConfig = {
  compress: false, // Front Door will compress at the edge
  // …
};

module.exports = nextConfig;

예시: Front Door 라우트 구성 (Terraform)

resource "azurerm_cdn_frontdoor_route" "static" {
  # …
  cache {
    query_string_caching_behavior = "UseQueryString"
    compression_enabled           = true
    content_types_to_compress = [
      "text/html",
      "text/css",
      "text/javascript",
      "application/javascript",
      "application/x-javascript",
      "application/json",
      "image/svg+xml",
      "font/woff2",
    ]
  }
}

다른 플랫폼

플랫폼압축 비활성화 방법
Expresscompression 미들웨어를 제거하거나 비활성화합니다.
Azure App ServiceWEBSITES_DISABLE_CONTENT_COMPRESSION=1을 설정합니다.
Nginx (behind AFD)gzip을 끕니다 (gzip off;).

빠른 진단

동일한 자산에 대해 다음 두 curl 명령을 실행합니다:

# 1️⃣ 압축 없이
curl -s -w "TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
  -o /dev/null "https://your-fd-endpoint.azurefd.net/your-asset.js"

# 2️⃣ 압축 사용
curl -s -w "TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
  -o /dev/null -H "Accept-Encoding: gzip" \
  "https://your-fd-endpoint.azurefd.net/your-asset.js"

첫 번째가 밀리초 안에 완료되고 두 번째가 약 90 초 동안 멈춰 있다면, 이 문제가 발생한 것입니다.
손상된 응답의 x-azure-ref 헤더를 저장하세요 – Microsoft에 지원 티켓을 열 때 필요합니다.

예상 정상 응답 (수정 후)

HTTP/2 200
content-type: application/javascript; charset=UTF-8
content-length: 41182
cache-control: public, max-age=31536000, immutable
content-encoding: gzip
vary: Accept-Encoding
x-cache: TCP_HIT
x-azure-ref: …

이제 원본 서버는 압축되지 않은 데이터를 전송하고, Front Door는 한 번 압축한 뒤 자산을 즉시 전달합니다.

immutable
x-cache: TCP_HIT

원본 서버에서 content‑encoding이 없습니다. Front Door는 캐시에서 밀리초 단위로 제공했습니다. x-cache: TCP_HIT가 표시되고 지연이 없으면 정상입니다.

관련 리소스

0 조회
Back to Blog

관련 글

더 보기 »

서브넷팅 설명

Subnetting이란 무엇인가? 큰 아파트 건물을 여러 층으로 나누는 것과 같다. 각 층 서브넷은 자체 번호가 매겨진 유닛(hosts)을 가지고, 그리고 건물…