cs)브라우저 동작 방식을 기반으로 한 웹 페이지 최적화

김명성·2023년 11월 18일

웹 페이지 최적화란 브라우저가 네트워크와 통신하는 과정을 포함하여 브라우저에서 페이지를 표시하는 과정에서의 응답 속도를 개선하는 것입니다.
즉 최적화란 성능 개선이라 할 수 있는데, 성능을 개선하기 위해서는 브라우저가 어떻게 동작하는지 이해하는 과정이 있어야 올바른 개선 방향을 잡을 수 있습니다.

브라우저의 동작 방식

모두가 알다시피, 사용자가 주소창에 주소를 입력하거나 링크를 클릭하여 웹 사이트로 진입하였을 때 브라우저는 서버에 요청을 보내 페이지를 구성하는 자원을 다운로드하고, 그 결과를 화면에 보여줍니다.

이때, 브라우저가 어떤 단계로 동작하는지, 단계별로 얼마나 시간을 걸리는지 정의한 연구 활동이 W3C의 네비게이션 타이밍 명세입니다.

네비게이션 타이밍 명세의 가장 큰 목적은 단계별 소요 시간을 측정하는 것이지만, 각 처리 단계를 정확하게 정의하고 있다는 점에도 큰 의미가 있습니다.

아래 그림은 네비게이션 타이밍 명세에서 사용자의 요청을 처리하는 순서를 정리한 프로세싱 모델입니다.

https://w3c.github.io/navigation-timing/#processing-model

모든 페이지의 마지막이자 시작은 Process Unload Event입니다.
그림에서도 이를 의도하여 Load 다음에 Process Unload Event를 나열시킨 것이 아닌 단계의 2번째 줄 맨 앞에 놓아 이를 분명히 하고 있습니다.

Process Unload Event서비스 이동 단계 라고 합니다.

서비스로 이동할 때 일련의 과정을 거친 뒤, 리다이렉트가 필요한 부분이 있다면 사용자가 요청한 URL에서 의도한 URL로 보내는 절차가 진행됩니다.

이후 이동된 페이지에서 캐시 데이터를 확인하는 Http Cache 단계, 캐싱되지 않은 데이터를 서버와 통신하여 구성 요소를 다운로드 하는 단계인 네트워크 통신 단계가 실행됩니다.

네트워크 통신 단계에서는 위 그림의 DNS, TCP, Request, Response가 포함됩니다.

구성요소 다운로드 이후 웹 페이지를 화면에 그리는 단계는 브라우저 처리 단계로, 그림의 Processing, Load 단계가 포함됩니다.

설명한 각 단계를 범주로 묶어보자면
서비스 이동 단계 - 리다이렉트 단계 - 캐시 확인 단계 - 네트워크 통신 단계 - 브라우저 처리 단계 로 묶을 수 있습니다.

이제 각 범주를 기준으로 성능 개선을 위해 고려할 내용은 무엇인지 살펴보겠습니다.


서비스 이동 단계에서의 최적화

서비스 이동 단계는 현재 페이지에서 다른 주소로 이동할 때 브라우저가 제일 먼저 실행하는 단계입니다.

이 단계의 작업은 다른 주소로 이동하기 전, 현재 보고 있는 페이지에서 실행하는데 모두 브라우저 내부에서 처리하기 때문에 브라우저의 성능과 직결됩니다.

웹 서비스를 이용하면 페이지가 표시될 때 우리도 모르게 이벤트가 할당되고 메모리를 조금씩 사용하게 됩니다. 이 메모리를 해제하기 위해서는 페이지를 떠날 때 해제해야 하므로 메모리 해제 작업이 서비스 이동 단계에서 실행하는 작업 가운데 하나가 되는 것입니다.

메모리를 해제시키는 주역은 가비지 컬렉션입니다. 브라우저에서 허용한 임계치를 넘었을 때 가비지 컬렉션이 동작하는데, 컬렉션이 동작하면 스크립트 실행이 중단되기 때문에 페이지가 느려지는 것입니다.

특히 DOM의 생성과 삭제가 빈번하거나, 한 페이지에 다수의 통신이 필요하거나, 바인딩된 이벤트가 많은 페이지, 사용자의 체류 시간이 긴 페이지를 개발할 때에는 필요없는 변수나 객체 삭제, 이벤트 해제 등을 활용해 메모리를 관리해야 합니다.

서비스 이동 단계의 작업은 브라우저 내부에서 자동으로 실행되기 때문에 가장 좋은 해결 방법은 최신 브라우저로 업데이트 하는 방법이 제일 좋습니다.

하지만 별도로 처리하는 방법도 존재합니다.
beforeunload 이벤트를 활용하는 방법으로, 메모리 해제를 담당하는 모듈을 해당 이벤트에 넣어 메모리를 직접 해제할 수도 있습니다.

리다이렉트 단계에서의 최적화

사용자가 요청한 URL에서 다른 URL로 보내는 단계인 리다이렉트를 우리가 쉽게 볼 수 있는 곳은 단축URL입니다.
사용자에게 안내할 URL이 너무 길어 글자 수 제한때문에 보내지 못한다거나, 미관상 보기 좋지 않을 때 https://me.do/qwdE2qks 와 같은 단축URL을 만들어 유저를 목적지로 이동시키는 방법을 우리는 많이 보아왔습니다.

리다이렉트가 발생하면 StatusCode 301, 302를 반환하는데
이는 해당 HTTP요청을 완수하기 위해 추가적으로 무언가를 해야 한다는 의미입니다.
이런 이유로 리다이렉트가 발생하면 어떤 자원도 다운로드하지 않으며, 브라우저에 일시적으로 빈 페이지가 보입니다.

그렇기 때문에 특정한 목적으로 리다이렉트를 사용하는 것은 어쩔 수 없지만 의도치 않게 또는 실수로 리다이렉트가 발생한다면 웹 페이지의 성능 향상을 위해 바로잡아야 합니다.

개발자가 이와 관련해서 흔히 저지르는 실수는 무엇일까요?

그것은 URL 뒤에 슬래시를 넣지 않는 것입니다.

https://www.naver.comhttps://www.naver.com/으로 리다이렉팅 되며 일시적으로 빈 페이지가 보입니다.
이는 크롬의 개발자 도구로도 확인할 수 있습니다.

307은 302와 유사하지만 307은 리다이렉트된 리소스에 대한 요청 시에 클라이언트가 원본 요청에서 사용한 메소드를 계속 사용해야 합니다. (POST <-> POST)

두번째로는 meta tag를 이용한 리다이렉트 방법인데, 현재는 많이 쓰이는 것 같지 않아 넘어가겠습니다.


캐시 확인 단계에서의 최적화

리다이렉트 단계가 끝난다면 이제 브라우저는 서버로부터 요청을 보냅니다.
서버는 사용자가 접속한 URL에 맞는 응답을 보냅니다.
만약 보내는 데이터가 마크업 데이터이고, 마크업 데이터 중에서 a.png라는 이미지 파일을 다운로드하는 태그를 만났을 때 브라우저가 처리하는 절차는 다음과 같습니다.

모든 리소스에 대해 개별적으로 캐시 설정을 하지 않아도 됩니다. 일반적으로 브라우저와 서버는 기본적으로 합리적인 캐시 전략을 가지고 있어, 명시적인 설정이 없는 경우에도 적절한 캐시 동작을 수행합니다.

  1. Expires 정보 확인
    브라우저가 a.gif 파일을 서버에 요청하기 전에 사용자 PC에 저장된 Expires 정보를 확인합니다.

  2. If-Modified-Since 정보 확인
    Expires 정보가 없거나 기한이 지났다면 If-Modified-Sind 정보가 있는지 확인합니다. 없다면 서버에 이미지를 요청해 받으며 있다면 If-Modified-Since 정보를 서버에 보냅니다.

  3. Last-Modified 정보 비교
    서버는 브라우저가 보낸 If-Modified-Since와 Last-Modified 정보를 비교하여 수정한 날짜가 같다면 304를 보내 사용자 PC에 있는 파일을 사용하게 하고, 다르다면 200과 함께 서버에서 파일을 보내줍니다.

위 과정에서 알 수 있듯이 사용자 PC에 캐시 파일이 있고 Expires 정보가 유효하다면 동적인 이미지 등을 제외한 나머지 정적인 요소 (이미지, 스타일시트, 스크립트파일)는 별도로 서버에 요청하지 않기 때문에 로딩 속도를 크게 향상시킬 수 있습니다.


네트워크 통신 단계에서의 최적화

프로세싱 모델(글의 첫번째 이미지)의 DNS, TCP, Request, Response는 모두 네트워크 통신에 관련된 단계입니다.
이 네 단계를 거치는 비용은 상당히 크며 이때의 비용을 줄인다면 성능 향상에 큰 효과를 볼 수 있습니다.

네트워크의 비용을 줄이는 첫 번째 방법은 위 캐시 확인에서의 최적화 단계에서 캐싱이 최대한 될 수 있도록 Expire, Cache Control 속성을 사용하여 사용자의 재방문시 캐시를 사용할 수 있게 만들어 주는 것입니다.

두 번째 방법은 스타일시트, 스크립트파일 등 파일을 합쳐서 서비스해도 문제가 없는 리소스를 합쳐 하나의 링크로 제공하여 요청 횟수를 줄이는 방법이 있습니다.

리소스를 합치는 작업을 번들링이라고 합니다.
가장 유명한 번들링 도구는 웹팩이라고 생각합니다.
웹팩은 모듈 번들링에 중점을 둔 강력한 도구로, JavaScript 뿐만 아니라 스타일시트, 이미지 등 다양한 종류의 리소스를 함께 처리할 수 있습니다.

DNS, Reqeust(send) 등에서도 최적화가 가능하지만 주로 호스팅과 관련된 스킬이거나 프론트엔드 단에서 처리할 수 없는 최적화이므로 생략하였습니다.


브라우저 처리 단계에서의 최적화

이제 캐싱된 파일 및 다운로드 받은 파일들을 조합하여 실제 사용자가 보는 화면을 만드는 단계입니다.

요청한 리소스가 모두 도착하면 브라우저는 DOM을 생성하기 시작합니다.

이렇게 생성된 DOM을 조작하는 시점에 따라 응답속도가 달라질 수 있습니다.

예전에는 HTML 파일 내부에서 인라인으로 이벤트를 바인딩하였습니다.

HTML에 대하여 의존성이 큰 방법이기때문에 모던웹에서는 기피해야하는 부분이며 이벤트 핸들러로 바인딩하는 방법을 사용해야 합니다.

이벤트 핸들러로 바인딩을 하기 위해 최우선 조건은 해당 DOM이 존재해야 한느 것입니다.

모든 DOM이 존재하는 그 시점은 자바스크립트의 DomContentLoaded, onload 이밴트가 발생하는 시점입니다.

그러나 두 이벤트는 발생하는 시점이 다릅니다.

domLoading > domInteractive > domContentLoaded > domComplete > onload

onload 이벤트는 문서에 있는 모든 이미지, 스타일시트, 자바스크립트 등이 모두 다운로드 될 때마다 발생하는 이밴트입니다.

이와 달리 DomContentLoaded는 기본적으로 DOM 생성에만 관련이 있으며 이미지나 다른 요소의 다운로드에는 관심이 없습니다.

만은 양의 이벤트를 바인딩하고, 이미지, 스타일시트의 개수가 많다면 DomContentLoaded이벤틀르적극적으로 사용하는게 좋습니다.

또는 페이지를 로딩하고 실제 사용자가 동작할 때까지 시간이 많이 지연되는 부분이 문제가 되었다면 꼭 DomContentLoaded를 사용하길 바랍니다.


하나의 웹 페이지가 나타날 대까지 브라우저의 내부 처리 과정은 굉장히 복잡합니다.

그렇기 때문에 하나를 개선했다고 하여 속도가 극적으로 빨라지지는 않을 것입니다.

다만 브라우저의 처리 과정을 이해함으로써 처음으로 성능 향상을 시도하는 개발자나 어디서부터 시작해야 할 지 몰랐던 개발자라면 무엇을 고려해야 하는지 대략적인 방향을 잡을 수 있을것이라 생각합니다.


0개의 댓글