Web Performance

사이트는 더 작은 요청이 아니라 더 적은 요청을 보내야 하는 이유

작은 파일도 공짜가 아닙니다. 현대 HTTP는 요청 오버헤드를 줄였을 뿐, 없애지는 않았습니다.

The Wux Webtools Team The Wux Webtools Team 12 읽기 최소 시간 AI 지원, 인간 검토
A stylized browser waterfall showing many small requests simplified into fewer intentional requests.
목차
  1. “모든 파일을 더 작게 만들면 된다”는 편안한 오해
  2. 요청은 단순한 bytes가 아닙니다
  3. “하지만 HTTP/2가 해결했잖아요” — 대체로 아닙니다
  4. 진실은 waterfall에 있습니다
  5. 더 작은 파일은 여전히 중요합니다 — 다만 모두 같은 정도는 아닙니다
  6. 많은 작은 파일의 숨은 비용
  7. 1. 늦은 발견
  8. 2. Header overhead
  9. 3. Main-thread interruption
  10. 4. Cache complexity
  11. Bundling이 돌아왔습니다. 다만 판단이 필요합니다
  12. Third-party requests는 더 의심해야 합니다
  13. 실용적인 요청 감소 checklist
  14. Remove
  15. Combine thoughtfully
  16. Defer
  17. Cache properly
  18. Re-measure
  19. 좋은 상태는 어떤 모습인가

“모든 파일을 더 작게 만들면 된다”는 편안한 오해

수년 동안 웹 성능 조언은 단순하게 들렸습니다. 모든 것을 압축하고, 모든 것을 minify하고, 모든 asset을 더 작게 만들라는 것이었습니다.

그 조언은 여전히 대체로 맞습니다. 40 KB script는 보통 400 KB script보다 낫습니다. 최적화된 이미지는 원본 export보다 낫습니다. Brotli, AVIF, CSS minification, tree shaking, font subsetting은 모두 중요합니다.

하지만 많은 프로덕션 사이트에서 더 큰 문제는 이제 지나치게 큰 파일 하나가 아닙니다. 페이지가 사용 가능하다고 느껴지기 전에 브라우저가 요청해야 하는 항목의 수입니다.

어떤 페이지는 파일 크기만 보면 잘 관리된 것처럼 보여도, 120개의 요청을 보내기 때문에 느릴 수 있습니다. CSS fragments, JavaScript chunks, third-party tags, font files, tracking pixels, icon sprites, JSON endpoints, preloads, analytics beacons, cache revalidations가 그 예입니다. 각각은 “작을” 수 있습니다. 하지만 합쳐지면 길고 취약한 waterfall을 만듭니다.

실용적인 원칙은 이렇습니다. 개별 asset이 적절히 압축된 상태라면, 모든 파일에서 몇 kilobytes를 더 깎는 것보다 요청 수를 줄이는 편이 사용자 경험을 더 자주 개선합니다.

요청은 단순한 bytes가 아닙니다

네트워크 요청은 데이터 전송만을 의미하지 않습니다. 그것은 일련의 작업입니다.

브라우저는 resource를 발견하고, 언제 가져올지 결정하고, 다른 resource와 경쟁하도록 schedule하고, headers를 보내고, 서버를 기다리고, headers를 받고, response를 parse하고, 종종 decompress한 뒤, 그것으로 유용한 일을 해야 합니다.

그 “유용한 일”은 비쌀 수 있습니다. JavaScript 파일은 parse, compile, execute되어야 합니다. CSS 파일은 rendering을 block할 수 있습니다. font 파일은 읽을 수 있는 text를 지연시키거나 layout shifts를 일으킬 수 있습니다. 이미지는 Largest Contentful Paint element에 영향을 줄 수 있습니다. third-party script는 자체 dependency chain을 가져올 수 있습니다.

그래서 파일이 작아도 요청 수는 여전히 중요합니다. 3 KB script가 rendering을 block하고, 늦게 도착하고, 잘못된 순간에 main thread에서 실행된다면 30 KB 이미지보다 더 나쁠 수 있습니다.

Lighthouse report를 읽다가 수십 개의 개별 경고에 벌을 받는 느낌이 든다면, 개별 score보다 request waterfall을 먼저 보세요. 당황하지 않고 Lighthouse report를 읽는 방법에 대한 실용적인 walkthrough도 있지만, 짧게 말하면 first render를 block하는 것과 main content를 지연시키는 것을 찾는 것입니다.

“하지만 HTTP/2가 해결했잖아요” — 대체로 아닙니다

HTTP/2와 HTTP/3는 요청의 경제성을 바꾸었습니다. multiplexing, header compression, 더 나은 connection behavior를 도입했습니다. 쉽게 말해, 브라우저가 더 적은 connection으로 여러 요청을 보내는 능력이 훨씬 좋아졌습니다.

이는 실제 개선이었습니다. 또한 connection limits를 피하기 위해서만 만들던 극단적인 CSS sprites와 거대한 concatenated bundles 같은 오래된 습관도 사라지게 했습니다.

하지만 HTTP/2가 요청을 공짜로 만든 것은 아닙니다.

multiplexing은 많은 resource가 connection을 공유할 때 도움이 되지만, 브라우저는 여전히 그것들의 priority를 정해야 합니다. 서버는 여전히 response해야 합니다. 클라이언트는 여전히 각 response를 처리해야 합니다. congestion, packet loss, TLS negotiation, DNS lookup, cache misses, main-thread pressure도 여전히 존재합니다.

HTTP/3는 특히 connection migration과 transport layer의 head-of-line blocking 주변에서 일부 transport behavior를 개선합니다. 하지만 resource를 발견하고, schedule하고, download하고, parse하고, execute하는 비용을 제거하지는 않습니다.

따라서 현대적인 목표는 “모든 것을 하나의 거대한 파일로 bundle하자”가 아닙니다. “critical request를 더 적게 보내고, 남은 요청은 의도를 갖고 보내자”입니다.

진실은 waterfall에 있습니다

성능 문제는 rarely 단일 metric으로 자신을 드러내지 않습니다. 형태로 나타납니다.

브라우저 network panel을 열고 처음 몇 초를 살펴보세요. 다음을 물어보세요.

  • main content가 나타나기 전에 몇 개의 요청이 시작되는가?
  • 어떤 요청이 rendering을 block하는가?
  • 중요한 resource가 늦게 발견되는가?
  • third-party scripts가 first-party CSS, fonts, images와 경쟁하는가?
  • 많은 파일이 cache에서 직접 제공되지 않고 304 responses를 반환하는가?
  • icons, fonts, UI fragments가 페이지가 필요로 하는 것보다 더 많은 파일로 쪼개져 있는가?

빠른 페이지는 보통 초기 waterfall이 단조롭습니다. 적은 수의 critical resources가 일찍 도착합니다. non-critical resources는 기다립니다. third-party scripts는 지연되거나, 제한되거나, 제거됩니다. 브라우저는 페이지를 paint하기 전에 스무 개의 priority를 juggling하도록 강요받지 않습니다.

느린 페이지는 흔히 불안한 waterfall을 가집니다. 작은 파일이 많고, origin이 많고, 늦게 발견되는 항목이 많습니다.

더 작은 파일은 여전히 중요합니다 — 다만 모두 같은 정도는 아닙니다

이 글은 compression이나 optimization에 반대하는 주장이 아닙니다. coordination을 무시한 채 bytes만 최적화하는 것에 반대하는 주장입니다.

더 작은 파일은 resource가 크거나, render-blocking이거나, main content path의 일부일 때 가장 중요합니다. 예를 들어:

  • hero image는 적절한 크기와 encoding이어야 합니다.
  • render-blocking CSS는 lean해야 합니다.
  • 첫 interaction에 필요한 JavaScript는 최소화되어야 합니다.
  • fonts는 subset, compressed되어야 하며 실제로 사용하는 weights로 제한되어야 합니다.

Fonts는 흔한 예입니다. 팀은 font 파일 하나가 24 KB인지 31 KB인지에 집착하면서, 여섯 개의 weights, 두 개의 styles, 여러 families를 배포하곤 합니다. 더 나은 해결책은 파일 하나에서 7 KB를 깎는 것이 아닙니다. 더 적은 font files를 보내는 것입니다. typography가 성능 작업의 일부라면, web fonts는 여전히 대부분의 사이트에서 가장 쉬운 성능 개선 중 하나입니다.

Images도 같은 패턴을 따릅니다. AVIF나 WebP는 의미 있는 bytes를 절약할 수 있지만, above the fold에 장식용 이미지 10개를 보내는 것은 여전히 나쁜 계획입니다. 더 나은 formats를 선택하세요. 그렇지만 각 이미지가 정말 요청되어야 하는지도 함께 질문해야 합니다. format 결정을 위해서는 AVIF가 WebP를 이기는 경우와 그렇지 않은 경우에 대한 가이드가 이 request-count 작업에 유용한 동반 자료가 됩니다.

많은 작은 파일의 숨은 비용

작은 요청이 많으면 총 transferred bytes만 볼 때는 드러나지 않는 문제가 생기기 쉽습니다.

1. 늦은 발견

브라우저는 발견하지 못한 것을 요청할 수 없습니다. CSS 파일은 font를 참조할 수 있습니다. script는 다른 script를 import할 수 있습니다. component는 hydration 후 JSON을 요청할 수 있습니다. 각 dependency는 chain에 또 다른 단계를 만듭니다.

chain이 깊을수록 중요한 작업은 더 늦게 시작됩니다.

2. Header overhead

모든 request와 response에는 headers가 포함됩니다. Header compression은 특히 HTTP/2와 HTTP/3에서 도움이 되지만 overhead를 제거하지는 않습니다. Cookies는 이를 훨씬 더 악화시킬 수 있습니다. 사이트가 모든 요청에 큰 cookies를 보낸다면, 작은 assets도 실제로는 덜 작아집니다.

이것이 static assets가 종종 cookie-free paths나 domains에 있어야 하는 이유 중 하나이며, cache headers에 주의를 기울여야 하는 이유이기도 합니다. 프로덕션에서 headers가 이상하게 동작한다면, 추측하는 것보다 redirects와 HTTP headers를 debugging하는 것이 보통 더 빠릅니다.

3. Main-thread interruption

많은 JavaScript chunks는 반복적인 parse와 execution 작업을 만들 수 있습니다. 각 chunk가 작더라도 브라우저는 코드를 evaluate하기 위해 계속 멈출 수 있습니다. 이는 Interaction to Next Paint를 해치고 페이지가 덜컥거리는 느낌을 줄 수 있습니다.

사용자는 각 파일이 작았는지에 관심이 없습니다. 메뉴를 탭하는 데 600 milliseconds가 걸렸다는 사실에 관심이 있습니다.

4. Cache complexity

asset splitting은 신중하게 수행하면 caching을 개선할 수 있습니다. 안정적인 vendor bundle과 자주 바뀌는 app bundle로 나누는 것은 좋은 split일 수 있습니다.

하지만 과도한 chunking은 역효과를 낼 수 있습니다. 파일이 많다는 것은 cache lookups가 많고, revalidation 기회가 많고, version coordination이 많고, 바뀔 필요가 없던 resource를 실수로 invalidate할 방법도 많다는 뜻입니다.

Bundling이 돌아왔습니다. 다만 판단이 필요합니다

웹 성능의 첫 번째 시대는 브라우저에 엄격한 connection limits가 있었기 때문에 bundling을 선호했습니다. 이후 HTTP/2가 등장했고 많은 팀이 aggressive code splitting 쪽으로 크게 기울었습니다. 그중 일부는 유용했습니다. 일부는 미신이 되었습니다.

합리적인 중간 지점은 route-aware bundling입니다.

일반적인 marketing site나 content site라면:

  • initial rendering에 필요한 CSS만 inline하거나 load합니다.
  • global JavaScript를 작게 유지합니다.
  • tiny modules를 별도의 network requests로 쪼개지 않습니다.
  • 즉시 필요하지 않은 interactive features는 지연시킵니다.
  • 비용을 정당화하지 못하는 third-party scripts를 제거합니다.

application이라면:

  • 모든 component가 아니라 route나 major feature 단위로 split합니다.
  • shared dependencies를 stable하고 cacheable하게 유지합니다.
  • 곧 반드시 필요한 resources만 preload합니다.
  • public pages에 admin, dashboard, editor, experiment code를 load하지 않습니다.
  • bundle size뿐 아니라 interaction cost를 측정합니다.

Bundling이 자동으로 좋은 것은 아닙니다. Code splitting이 자동으로 좋은 것도 아닙니다. 유용한 질문은 이것입니다. 이 split이 브라우저가 다음 meaningful user experience를 더 빨리 전달하는 데 도움이 되는가?

Third-party requests는 더 의심해야 합니다

First-party requests는 적어도 통제할 수 있습니다. Third-party requests는 흔히 보기보다 더 느리고, 덜 예측 가능하며, 더 비쌉니다.

tag manager 하나가 analytics, ads, heatmaps, chat widgets, A/B testing, consent tools, personalization scripts를 trigger할 수 있습니다. 각 vendor는 더 많은 requests를 가져올 수 있습니다. 일부는 일찍 실행됩니다. 일부는 main thread를 block합니다. 일부는 여러분의 release process 없이 변경됩니다.

최고의 third-party optimization은 삭제입니다. 두 번째로 좋은 것은 지연입니다.

third-party script를 추가하기 전에 물어보세요.

  • 사용자가 페이지를 보기 전에 이것을 load해야 하는가?
  • 모든 페이지에서 load해야 하는가?
  • consent, interaction, idle time 이후에 load할 수 있는가?
  • 내부에서 누가 ownership을 갖는가?
  • 어떤 metric이 이것이 performance cost를 감당할 가치가 있음을 증명하는가?

여기서 performance는 governance가 됩니다. 누군가는 아니라고 말할 권한을 가져야 합니다.

실용적인 요청 감소 checklist

가장 중요한 페이지부터 시작하세요. homepage, pricing page, product page, checkout, signup, 또는 top landing pages입니다. 그런 다음 waterfall을 따라 작업하세요.

Remove

  • 사용하지 않는 JavaScript와 CSS를 삭제합니다.
  • 오래된 experiments, 방치된 pixels, 중복 analytics를 제거합니다.
  • 사용하지 않는 font weights와 icon libraries를 제거합니다.
  • 적절한 경우 decorative images를 CSS로 대체합니다.

Combine thoughtfully

  • 항상 함께 load되는 tiny JavaScript modules를 bundle합니다.
  • 같은 render path를 block하는 작은 CSS files를 merge합니다.
  • repeated icons에 SVG sprites나 inline SVG를 사용하되, maintainability를 해치지 않으면서 requests를 줄일 때만 사용합니다.

Defer

  • below-the-fold images를 lazy-load합니다.
  • non-critical scripts를 first paint 또는 user interaction 이후로 지연시킵니다.
  • comments, embeds, maps, chat, video players는 필요할 때만 load합니다.

Cache properly

  • versioned static assets에는 long-lived caching을 사용합니다.
  • rarely change되는 파일에 불필요한 revalidation을 피합니다.
  • HTML은 fresh하게 유지하되, hashed assets는 cached 상태로 둡니다.

Re-measure

각 변경 후 waterfall을 다시 확인하세요. 목표는 perfect score가 아닙니다. 목표는 critical requests를 줄이고, useful rendering을 더 앞당기고, main-thread disruption을 줄이는 것입니다.

좋은 상태는 어떤 모습인가

건강한 페이지가 반드시 가능한 한 가장 적은 요청을 가진 것은 아닙니다. 작고 의도적인 critical path를 가집니다.

브라우저는 HTML, essential CSS, main content image가 있다면 그것, navigation이나 above-the-fold interaction에 필요한 작은 script 정도, 그리고 text를 읽을 수 있게 만드는 데 필요한 최소 font set을 받습니다. 나머지는 차례를 기다립니다.

그것이 단지 최적화된 페이지와 빠르게 느껴지는 페이지의 차이입니다.

파일을 줄이는 일은 여전히 가치가 있습니다. 하지만 사이트가 이미 적절히 압축되어 있다면, 다음 성능 개선은 보통 bundle에서 또 다른 2 KB를 절약하는 것이 아닙니다. block하는 request 하나를 줄이고, font file 하나를 줄이고, third-party script 하나를 줄이고, dependency chain 하나를 줄이는 것입니다.

요청이 적어지면 브라우저의 일이 단순해집니다. 우리가 인정하고 싶어 하는 것보다 더 자주, 단순함은 빠름입니다.

자주 묻는 질문

큰 bundle 하나가 많은 작은 파일보다 더 나은가요?
자동으로 그렇지는 않습니다. 거대한 bundle 하나는 특히 first load에서 모든 것을 지연시킬 수 있습니다. 많은 tiny files는 scheduling과 execution overhead를 만들 수 있습니다. 더 나은 패턴은 항상 함께 필요한 resources를 bundle하고, route나 major feature 단위로 split하는 것입니다.
HTTP/2라면 요청 수는 더 이상 중요하지 않나요?
아닙니다. HTTP/2는 multiplexing과 header compression을 통해 일부 connection overhead를 줄이지만, 각 request에는 여전히 discovery, prioritization, server, cache, parsing, execution costs가 있습니다.
모든 critical CSS를 inline해야 하나요?
진정으로 critical한 CSS를 소량 inline하면 first render에 도움이 될 수 있습니다. 하지만 너무 많이 inline하면 HTML이 무거워지고 cache하기 어려워집니다. 최소한으로 유지하고 효과를 측정하세요.
요청을 줄이기 가장 쉬운 곳은 어디인가요?
Fonts와 third-party scripts가 가장 빠른 개선점인 경우가 많습니다. 많은 사이트가 사용하지 않는 font weights, 중복 analytics, 오래된 pixels, chat widgets, 즉시 load할 필요가 없는 embeds를 배포합니다.
페이지에는 요청이 몇 개 있어야 하나요?
보편적인 목표값은 없습니다. 작은 content page는 critical requests가 매우 적어야 합니다. 복잡한 app은 더 많이 필요할 수 있습니다. first render 이전과 main interaction path 이전의 요청을 줄이는 데 집중하세요.

출처 및 추가 읽기

  1. MDN Web Docs: HTTP caching
  2. web.dev: Optimize Largest Contentful Paint
  3. RFC 9113: HTTP/2
  4. HTTP Archive Web Almanac: Page Weight
저자에 대하여
The Wux Webtools Team

마지막 업데이트:

계속 읽기