Deallo

렌더링 · 성능

다운로드 프록시 제거 — CDN 직접 전송으로 대역폭 절반

결과만 보면 "다운로드 프록시를 지웠다" 한 줄이지만, 실제로는 문제를 발견해 백엔드에 스펙을 제안하고, 코드 리뷰 피드백을 받아 함께 구조를 다시 잡은 협업이 본체였다. 채팅 이미지 최적화에서 시작해 프론트의 임시 우회(프록시)를 CDN 엣지로 걷어내기까지 — 거의 전부가 대화였다.

시작 — 썸네일을 최적화하다 발견한 것

채팅 이미지·영상 로딩을 최적화하던 중, 미디어 API 응답의 thumbnailUrlnull로 오는 걸 발견했다.

  • 이미지: thumbnailUrl: null원본(2000px+)을 그대로 로드
  • 영상: 커버가 원본 프레임 캡처(3508px 등) → 역시 무거움
  • 채팅은 324px로 표시하는데, 정작 수십 MB 원본을 받고 있었다

그래서 백엔드 동료에게 스펙을 정리해 제안했다 — thumbnailUrl에 긴 변 800px 리사이즈를 함께 내려달라(이미지·영상 동일 필드로 통일, 용량 80~90% 절감 기대). 현재 인프라에서 가능한지부터 확인을 요청했고, 합의돼 반영됐다. "내가 안 해서 비어 있던 것"이라는 답이 돌아왔다 — 쓰는 쪽이 필요를 구체적으로 말해야 채워진다.

별개의 문제 — 프론트가 만든 다운로드 우회

같은 시기에 다른 문제도 있었다. 첨부파일 CloudFront URL에 Content-Disposition 헤더가 없어, 브라우저에서 열면 항상 인라인 미리보기로 떴다. 강제 다운로드가 안 됐다.

그래서 프론트가 자체 프록시(/api/download)를 만들어 Content-Disposition: attachment를 붙여 다운로드를 처리하고 있었다(인벤토리·바이어에 사용 중, 이메일에도 쓸 예정). 동작은 했다 — 하지만 우회였다.

코드 리뷰 피드백 — "프록시는 CDN을 무력화한다"

백엔드 리뷰에서 핵심을 찔렸다.

"프록시 자체를 태우면 CloudFront 쓰는 게 의미가 없다."

확인해보니 정확했다. 프론트 프록시는 파일을 이렇게 흘려보내고 있었다.

브라우저 → /api/download (Next 서버) → CloudFront → S3
  • CloudFront CDN 이점 무력화 (서버를 거치니 엣지 캐시가 의미 없음)
  • 서버가 파일 전체를 메모리에 로드(blob())
  • 대역폭 2배 소모 (S3 → 서버, 서버 → 브라우저)

프론트에서 빠르게 막으려고 만든 우회가, 알고 보니 CDN을 통째로 우회시키고 있었다.

합의 — 헤더를 "어느 단계에서 박을 것인가"

그래서 "다운로드 헤더를 어느 레이어에서 붙일지"를 같이 따졌다. 프론트 프록시(서버 경유)가 아니라, CDN 엣지에서 붙이는 게 맞았다. 두 가지로 합의했다.

  1. CloudFront Function — distribution의 viewer-response에 연결. ?download=1 쿼리가 있으면 엣지에서 Content-Disposition: attachment를 동적 주입. 서버를 안 거친다
  2. MediumMediumResponseDto 전환 — 응답에 url(미리보기)과 downloadUrl(다운로드) 두 URL을 함께 포함. 백엔드 동료 제안대로, 만료 없는 정적 URL이라 프론트가 별도 호출할 필요 없이 toResponse()에서 downloadUrl을 만들어 내려준다. 마침 엔티티를 직접 노출하던 게 안티패턴이라, DTO 전환을 겸했다

프론트가 한 번 더 호출하거나 헤더를 조립하는 대신, 데이터가 내려올 때 다운로드 URL이 이미 들어있게 한 것이다.

프론트 일괄 전환

백엔드가 선행 작업을 끝낸 뒤, 프론트는 프록시를 걷고 downloadUrl로 바꿨다.

// Before — 서버 프록시 URL 조립
href={`/api/download?url=${encodeURIComponent(url)}&filename=${filename}`}
 
// After — 백엔드가 내려준 CDN 직접 URL
href={medium.downloadUrl ?? medium.url}

인벤토리·바이어·이메일의 다운로드를 모두 전환하고, downloadUrl이 없는 구 데이터는 url로 fallback시킨 뒤, 프록시 라우트(api/download)를 삭제했다.

임팩트 · 배운 점

  • 대역폭 50% 절감(서버 경유 제거) + 서버 메모리 적재 제거 + 썸네일로 채팅 로딩 용량 80~90%↓
  • 엣지에서 끝낼 수 있는 일은 엣지에서. 헤더 주입 같은 작은 로직을 CloudFront Function으로 내리면 서버가 통째로 빠진다
  • 프론트의 우회는 증상 대응이었다. 진짜 답은 "프론트가 헤더를 붙이자"가 아니라 "이 로직은 어느 레이어에 살아야 하나"였고, 그건 혼자가 아니라 백엔드와 같이 정해야 깔끔했다. 문제를 발견한 사람이 스펙까지 제안하고, 리뷰 피드백으로 더 나은 구조에 합의하는 흐름이 결국 가장 빨랐다