왜 Azure Front Door가 내 Next.js 앱을 90초 동안 로드하게 만들었는지 (그리고 내가 해결한 방법)
Source: Dev.to
번역을 원하는 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다.
Source: …
문제
우리는 Azure Front Door Premium와 Private Link 뒤에 Azure Container Apps에 Next.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/WAF | Azure 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가 함께 존재합니다.curl은 8,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 페이지는 정상 작동하지만 정적 청크는 실패합니다.
우리가 시도한 내용
| 시도 | 변경 내용 | 결과 |
|---|---|---|
| 1 | Front 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 스레드(첫 번째, 두 번째)는 동일한 해결책을 가리키고 있습니다: 원본 압축 비활성화.
수정
- 원본에서 압축을 비활성화합니다.
- 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",
]
}
}
다른 플랫폼
| 플랫폼 | 압축 비활성화 방법 |
|---|---|
| Express | compression 미들웨어를 제거하거나 비활성화합니다. |
| Azure App Service | WEBSITES_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가 표시되고 지연이 없으면 정상입니다.
관련 리소스
- 파일 압축 문제 해결 - Azure Front Door (Microsoft Learn)
- Accept-Encoding: gzip 사용 시 리소스 다운로드를 완료할 수 없음 (Microsoft Q&A)
- 정적 자산에 대한 Azure Front Door HTTP/2 프로토콜 오류 (Microsoft Q&A)
- Envoy + Azure CDN gzip 범위 요청 문제 (GitHub)
- Azure CDN에서 HTTP2_PROTOCOL_ERROR (Microsoft Q&A)
- Azure Front Door FAQ - 시간 초과 (Microsoft Learn)
- 파일 압축으로 성능 향상 (Microsoft Learn)