Next.js 미들웨어와 이미지 최적화로 완성하는 Core Web Vitals 실전 전략

Next.js 미들웨어와 이미지 최적화로 완성하는 Core Web Vitals 실전 전략

NextRequest/NextResponse의 정밀한 요청 제어와 WebP·AVIF 압축 알고리즘의 원리를 연결하면, 성능 최적화는 설정 몇 줄이 아니라 아키텍처의 문제가 된다.

Next.js 미들웨어 Core Web Vitals NextResponse NextRequest 이미지 최적화 WebP AVIF LCP 최적화 Edge Runtime
광고

성능 최적화, 왜 '미들웨어'와 '이미지'를 함께 봐야 하는가

Core Web Vitals를 개선하겠다고 마음먹으면 대부분 두 가지 길로 나뉜다. 하나는 LCP(Largest Contentful Paint)를 잡으려고 이미지 포맷을 바꾸는 것, 다른 하나는 TTFB(Time to First Byte)와 서버 응답 흐름을 제어하려고 미들웨어를 손보는 것. 그런데 실제 프로덕션에서 이 둘은 분리된 문제가 아니다. 어떤 이미지를 어떤 조건에서, 어떤 헤더와 함께 내려보낼지를 결정하는 로직이 바로 미들웨어 레이어에서 시작되기 때문이다.

NextRequest: 요청을 '읽는' 정밀함

Next.js의 NextRequest는 Web Request API를 확장한 객체다. 표면적으로는 쿠키를 읽고 쓰는 유틸리티처럼 보이지만, 실무에서 진짜 가치는 nextUrl에 있다. request.nextUrl.pathnamerequest.nextUrl.searchParams를 통해 미들웨어가 실행되는 시점에 이미 라우트 정보와 쿼리 파라미터를 파싱할 수 있다는 것 — 이는 Edge Runtime에서 별도의 파싱 비용 없이 요청 컨텍스트를 조합할 수 있다는 의미다.

예를 들어 A/B 테스트 실험 플래그를 request.cookies.getAll('experiments')로 읽어 동일한 이름의 쿠키가 여러 값을 가진 경우까지 처리할 수 있다. 이 레이어에서 사용자의 실험군을 판별하고, 그에 맞는 이미지 최적화 파라미터를 URL에 심어 rewrite()로 분기하는 패턴이 가능해진다.

NextResponse: 요청을 '조형'하는 도구

NextResponse는 단순한 응답 래퍼가 아니다. redirect(), rewrite(), next()라는 세 가지 메서드가 미들웨어의 핵심 분기 로직을 구성한다.

  • redirect(): 로그인 여부, 지역, 디바이스 타입에 따라 사용자를 다른 경로로 보낸다. request.nextUrl.pathnamefrom 파라미터로 심어두면 로그인 후 원래 페이지로 복귀하는 UX도 미들웨어 한 곳에서 처리된다.
  • rewrite(): 브라우저 URL은 그대로 유지하면서 실제 요청은 다른 경로로 프록시한다. 이미지 CDN 라우팅이나 지역별 콘텐츠 서빙처럼 URL 구조를 노출하지 않아야 하는 시나리오에 유용하다.
  • next(): 미들웨어를 통과시키되, request.headers를 수정해 업스트림(페이지, 서버 액션, Route Handler)에 컨텍스트를 전달한다. x-version, x-user-segment 같은 커스텀 헤더를 심어 서버 컴포넌트가 이를 읽어 분기하도록 설계할 수 있다.

다만 velog의 NextResponse 문서가 명확히 경고하듯, NextResponse.next({ headers })의 단축 표현은 사용하지 않는 것이 좋다. 수신된 요청 헤더를 그대로 복사하면 authorization, cookie 같은 민감한 데이터가 클라이언트나 외부 서비스로 유출될 수 있다. 허용 목록(allow-list) 방식으로 안전한 헤더만 선별해 forwarded 객체를 직접 구성하는 방어적 접근이 필수다.

이미지 압축 알고리즘: '설정'이 아니라 '이해'가 먼저다

dev.to의 이미지 압축 알고리즘 분석 글은 흔히 넘어가는 원리를 짚어준다. 1920×1080 RGB 이미지의 원본 크기는 6.2MB다. JPEG가 300KB로 줄이는 20:1의 비율은 단순한 압축이 아니라 인간 시각의 특성을 역설계한 결과다.

JPEG의 핵심은 색차 서브샘플링(Chroma Subsampling)이다. 인간의 눈은 밝기 변화에는 민감하고 색상 변화에는 둔감하다. JPEG는 이를 이용해 YCbCr 색 공간으로 변환한 뒤 색상 채널을 절반 해상도로 줄인다(4:2:0). 이 단계만으로 원본 대비 50% 크기가 줄어든다 — 양자화(Quantization)가 시작되기도 전에. 이걸 모르면 JPEG 품질 슬라이더를 조정하면서 원인과 결과를 잘못 해석하게 된다.

WebP와 AVIF는 왜 더 작은가. WebP는 VP8 코덱 기반으로 8×8 블록 대신 16×16 매크로블록을 사용하고, 허프만 코딩 대신 산술 코딩을 적용한다. 결과적으로 동일 품질에서 JPEG 대비 25~34% 작다. AVIF는 AV1 코덱의 정지 이미지 프로파일을 사용하며 최대 128×128 가변 크기 코딩 유닛으로 넓은 균일 영역(하늘, 배경)을 훨씬 효율적으로 표현한다. JPEG 대비 약 50% 작지만 인코딩 속도가 느리다는 트레이드오프가 있다.

미들웨어 + 이미지 최적화의 교차점: 실전 설계

Next.js next/image는 자동으로 WebP/AVIF를 제공하지만, 미들웨어와 조합하면 훨씬 정밀한 제어가 가능하다.

Accept 헤더 기반 포맷 분기. 미들웨어에서 request.headers.get('accept')를 읽어 AVIF 지원 여부를 판별하고, rewrite()/images/optimized/avif/hero.avif 또는 /images/optimized/webp/hero.webp로 분기할 수 있다. 브라우저 호환성 분기를 서버 사이드에서 처리하는 것이다.

디바이스 힌트 기반 품질 조정. Save-Data 헤더나 Device-Memory 힌트를 읽어 저사양 환경에서는 JPEG quality 75 수준의 파라미터를 URL 쿼리로 심고, 고사양 환경에서는 AVIF를 우선 제공하는 전략이 가능하다.

실험 플래그 연동. A/B 테스트 쿠키를 읽어 특정 사용자 그룹에게만 AVIF 포맷을 제공하며 LCP 개선 효과를 측정하는 것도 미들웨어 레이어에서 깔끔하게 처리된다.

시사점: '최적화'를 아키텍처 레벨로 끌어올리는 법

Core Web Vitals 개선은 종종 <Image> 컴포넌트의 priority prop 하나, 또는 next.config.jsformats 배열 설정으로 단순화된다. 하지만 실제 프로덕션 환경에서 LCP가 느린 이유는 이미지 포맷 때문만이 아니다. 어떤 조건에서 어떤 이미지가 요청되고, 그 요청이 어떤 헤더와 함께 어떤 경로로 처리되는지 — 이 흐름 전체가 성능을 결정한다.

압축 알고리즘의 원리를 이해하면 quality={85}라는 숫자가 왜 그 값인지 설명할 수 있게 된다. 품질 85는 양자화 테이블의 제수(divisor)가 시각적 아티팩트와 파일 크기 사이의 실용적 균형점에 놓이는 지점이다. 그리고 NextResponse.rewrite()의 URL 조작이 왜 브라우저 캐시와 CDN 동작에 영향을 주는지도, 미들웨어가 Edge Runtime에서 실행된다는 사실과 연결된다.

전망: 미들웨어가 더 많은 것을 결정하게 된다

Next.js의 Partial Prerendering(PPR)이 안정화되면 미들웨어의 역할은 더 커진다. 정적 셸과 동적 스트리밍의 경계를 어디에 그을지, 어떤 사용자에게 어떤 캐시된 이미지를 내려보낼지가 미들웨어 레이어의 판단에 달리게 된다. 이미지 최적화도 단순한 포맷 변환을 넘어, 요청 컨텍스트(디바이스, 네트워크, 사용자 세그먼트)를 읽어 동적으로 응답을 조형하는 방향으로 진화할 것이다.

지금 NextRequestNextResponse의 API를 정확히 이해하고, 이미지 압축 알고리즘의 원리를 파악해두는 것 — 이는 다음 세대의 성능 최적화 패턴을 먼저 설계할 수 있는 기반이 된다. 설정을 바꾸는 것과 아키텍처를 설계하는 것 사이의 거리는 결국 '왜'를 아는 것에서 갈린다.

출처

더 많은 AI 트렌드를 Seedora 앱에서 확인하세요