이전 모듈에서는 중요한 렌더링 경로의 이론과 렌더링 차단 및 파서 차단 리소스가 페이지의 초기 렌더링을 지연시킬 수 있는 방법을 살펴보았습니다. 이제 이 이론의 일부를 이해했으므로 중요한 렌더링 경로를 최적화하는 몇 가지 기법을 알아볼 준비가 되었습니다.
페이지가 로드되면 HTML 내에서 많은 리소스가 참조되어 CSS를 통해 페이지에 모양과 레이아웃을 제공하고 JavaScript를 통해 상호작용을 제공합니다. 이 모듈에서는 이러한 리소스와 페이지의 로드 시간에 미치는 영향과 관련된 여러 가지 중요한 개념을 다룹니다.
렌더링 차단
이전 모듈에서 설명한 것처럼 CSS는 CSS 객체 모델(CSSOM)이 구성될 때까지 브라우저가 콘텐츠를 렌더링하지 못하도록 차단하므로 렌더링 차단 리소스입니다. 브라우저는 사용자 경험 관점에서 바람직하지 않은 스타일이 지정되지 않은 콘텐츠 깜박임 (FOUC)을 방지하기 위해 렌더링을 차단합니다.
위의 동영상에는 스타일이 지정되지 않은 페이지를 볼 수 있는 짧은 FOUC가 있습니다. 이후 페이지의 CSS가 네트워크에서 로드를 완료하면 모든 스타일이 적용되고 스타일이 지정되지 않은 버전의 페이지가 스타일이 지정된 버전으로 즉시 대체됩니다.
일반적으로 FOUC는 일반적으로 볼 수 있는 것이 아니지만 CSS가 다운로드되어 페이지에 적용될 때까지 브라우저가 페이지 렌더링을 차단하는 이유 를 이해하는 데 중요한 개념입니다. 렌더링 차단이 반드시 바람직하지 않은 것은 아니지만 CSS를 최적화하여 지속 시간을 최소화하는 것이 좋습니다.
파서 차단
파서 차단 리소스는 <script>
요소와 같은 HTML 파서를 방해합니다. async 또는 defer 속성이 없습니다. 파서가
<script> 요소를 발견하면 브라우저는 나머지 HTML 파싱을 진행하기 전에
스크립트를 평가하고 실행해야 합니다. 스크립트는 아직 구성 중인 동안 DOM을 수정하거나 액세스할 수 있으므로 이는 설계에 따른 것입니다.
<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>
외부 JavaScript 파일 (async 또는 defer 또는 기본적으로 defer인 type=module 없음)을 사용하는 경우 파일이 발견된 시점부터 다운로드, 파싱, 실행될 때까지 파서가 차단됩니다. 인라인 JavaScript를 사용하는 경우 인라인 스크립트가 파싱되고 실행될 때까지 파서가 유사하게 차단됩니다.
프리로드 스캐너
프리로드 스캐너는 기본 HTML 파서가 리소스를 검색하기 전에 원시 HTML 응답을 검사하여 리소스를 찾고 추측적으로 가져오는 보조 HTML
파서 형태의 브라우저 최적화입니다. 예를 들어 프리로드 스캐너를 사용하면 HTML 파서가 CSS 및 JavaScript와 같은 리소스를 가져오고 처리하는 동안 차단되더라도 브라우저가
<img> 요소에 지정된 리소스 다운로드를 시작할 수 있습니다.
프리로드 스캐너를 활용하려면 중요한 리소스를 서버에서 전송한 HTML 마크업에 포함해야 합니다. 다음 리소스 로드 패턴은 프리로드 스캐너에서 검색할 수 없습니다.
background-image속성을 사용하여 CSS에서 로드된 이미지. 이러한 이미지 참조는 CSS에 있으며 프리로드 스캐너에서 검색할 수 없습니다.- JavaScript 또는 동적
import()를 사용하여 로드된 모듈을 사용하여 DOM에 삽입된<script>요소 마크업 형태의 동적으로 로드된 스크립트. - JavaScript를 사용하여 클라이언트에서 렌더링된 HTML. 이러한 마크업은 JavaScript 리소스의 문자열 내에 포함되어 있으며 프리로드 스캐너에서 검색할 수 없습니다.
- CSS
@import선언.
이러한 리소스 로드 패턴은 모두 늦게 발견된 리소스이므로 프리로드 스캐너의 이점을 누릴 수 없습니다. 가능한 한 피하세요. 하지만 이러한 패턴을 피할 수 없는 경우
preload 힌트를 사용하여 리소스 검색 지연을 방지할 수 있습니다.
CSS
CSS는 페이지의 프레젠테이션과 레이아웃을 결정합니다. 앞서 설명한 것처럼 CSS는 렌더링 차단 리소스이므로 CSS를 최적화하면 전반적인 페이지 로드 시간에 상당한 영향을 미칠 수 있습니다.
압축
CSS 파일을 압축하면 CSS 리소스의 파일 크기가 줄어들어 더 빠르게 다운로드할 수 있습니다. 이는 주로 공백 및 기타 보이지 않는 문자와 같은 콘텐츠를 소스 CSS 파일에서 삭제하고 결과를 새로 최적화된 파일로 출력하여 이루어집니다.
/* Unminified CSS: */
/* Heading 1 */
h1 {
font-size: 2em;
color: #000000;
}
/* Heading 2 */
h2 {
font-size: 1.5em;
color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}
가장 기본적인 형태의 CSS 압축은 웹사이트의 FCP를 개선하고 경우에 따라 LCP까지 개선할 수 있는 효과적인 최적화입니다. 번들러와 같은 도구는 프로덕션 빌드에서 이 최적화를 자동으로 실행할 수 있습니다.
사용하지 않는 CSS 삭제
콘텐츠를 렌더링하기 전에 브라우저는 모든 스타일시트를 다운로드하고 파싱해야 합니다. 파싱을 완료하는 데 필요한 시간에는 현재 페이지에서 사용되지 않는 스타일도 포함됩니다. 모든 CSS 리소스를 단일 파일로 결합하는 번들러를 사용하는 경우 사용자는 현재 페이지를 렌더링하는 데 필요한 것보다 더 많은 CSS를 다운로드할 가능성이 높습니다.
현재 페이지에서 사용되지 않는 CSS를 찾으려면 Chrome DevTools의 범위 도구를 사용하세요.
사용하지 않는 CSS를 삭제하면 두 가지 효과가 있습니다. 다운로드 시간을 줄이는 것 외에도 브라우저가 처리해야 하는 CSS 규칙이 줄어들므로 렌더링 트리 구성을 최적화합니다.
CSS @import 선언 피하기
편리해 보일 수 있지만 CSS에서 @import 선언은 피해야 합니다.
/* Don't do this: */
@import url('style.css');
HTML에서 <link> 요소가 작동하는 방식과 마찬가지로 CSS의 @import 선언
을 사용하면 스타일시트 내에서 외부 CSS 리소스를 가져올 수 있습니다. 이
두 가지 접근 방식의 주요 차이점은 HTML <link> 요소
가 HTML 응답의 일부이므로 CSS
파일보다 훨씬 빨리 검색된다는 것입니다. @import 선언으로 다운로드했습니다.
그 이유는 @import 선언을 검색하려면 이를 포함하는 CSS 파일을 먼저 다운로드해야 하기 때문입니다. 이로 인해 요청 체인 이 발생하며, CSS의 경우 페이지가 처음 렌더링되는 데 걸리는 시간이 지연됩니다. 또 다른 단점은 @import 선언을 사용하여 로드된 스타일시트를 프리로드 스캐너에서 검색할 수 없으므로 늦게 발견된 렌더링 차단 리소스가 된다는 것입니다.
<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">
대부분의 경우 @import를 사용하여
<link rel="stylesheet"> 요소를 대체할 수 있습니다. <link> 요소는 스타일시트를
다운로드할 수 있으며 스타일시트를 연속으로 다운로드하는 @import
선언과 달리 전반적인 로드 시간을 줄입니다.
@import
구문을 사용합니다. 하지만 CSS 전처리기가
@import 선언을 발견하면 참조된 파일이 번들링되어
단일 스타일시트에 작성되므로 연속 요청 페널티를 피할 수 있습니다.
@import가 일반 CSS에서 발생시키는
중요한 인라인 CSS
CSS 파일을 다운로드하는 데 걸리는 시간은 페이지의 FCP를 늘릴 수 있습니다. 문서 <head>에 중요한 스타일을 인라인하면 CSS 리소스에 대한 네트워크 요청이 삭제되고 올바르게 실행되면 사용자의 브라우저 캐시가 준비되지 않은 경우 초기 로드 시간을 개선할 수 있습니다. 나머지 CSS는
비동기식으로 로드하거나 <body> 요소의 끝에 추가할 수 있습니다.
<head>
<title>Page Title</title>
<!-- ... -->
<style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
<!-- Other page markup... -->
<link rel="stylesheet" href="non-critical.css">
</body>
단점은 많은 양의 CSS를 인라인하면 초기 HTML 응답에 더 많은 바이트가 추가된다는 것입니다. HTML 리소스는 매우 오랫동안 또는 전혀 캐시할 수 없는 경우가 많으므로 인라인 CSS는 외부 스타일시트에서 동일한 CSS를 사용할 수 있는 후속 페이지에 캐시되지 않습니다. 페이지의 성능을 테스트하고 측정하여 절충안이 노력할 만한 가치가 있는지 확인하세요.
CSS 데모
JavaScript
JavaScript는 웹의 상호작용 대부분을 지원하지만 비용이 발생합니다. 너무 많은 JavaScript를 제공하면 페이지 로드 중에 웹페이지의 응답 속도가 느려질 수 있으며 상호작용 속도를 늦추는 응답성 문제가 발생할 수도 있습니다. 둘 다 사용자에게 불편을 초래할 수 있습니다.
렌더링 차단 JavaScript
defer 또는 async 속성이 없는 <script> 요소를 로드할 때
브라우저는 스크립트가 다운로드, 파싱, 실행될 때까지 파싱 및 렌더링을 차단합니다. 마찬가지로 인라인 스크립트는 스크립트가 파싱되고 실행될 때까지 파서를 차단합니다.
async 및 defer
async 및 defer를 사용하면 HTML
파서를 차단하지 않고 외부 스크립트를 로드할 수 있으며 type="module"이 있는 스크립트 (인라인 스크립트 포함)는 자동으로
지연됩니다. 하지만 async와 defer에는 이해해야 할 몇 가지 중요한 차이점이 있습니다.
async로 로드된 스크립트는 다운로드되는 즉시 파싱되고 실행되는 반면 defer로 로드된 스크립트는 HTML 문서 파싱이 완료되면 실행됩니다. 이는 브라우저의 DOMContentLoaded 이벤트와 동시에 발생합니다.
또한 async 스크립트는 순서가 지정되지 않은 상태로 실행될 수 있지만 defer 스크립트는 마크업에 표시되는 순서대로 실행됩니다.
클라이언트 측 렌더링
일반적으로 JavaScript를 사용하여 중요한 콘텐츠 또는 페이지의 LCP 요소를 렌더링하지 않는 것이 좋습니다. 이를 클라이언트 측 렌더링이라고 하며 단일 페이지 애플리케이션 (SPA)에서 광범위하게 사용되는 기법입니다.
JavaScript로 렌더링된 마크업은 클라이언트 렌더링된 마크업에 포함된 리소스 를 검색할 수 없으므로 프리로드 스캐너를 우회합니다. 이로 인해 LCP 이미지와 같은 중요한 리소스의 다운로드가 지연될 수 있습니다. 브라우저는 스크립트가 실행되고 DOM에 요소를 추가한 후에만 LCP 이미지 다운로드를 시작합니다. 스크립트는 검색, 다운로드, 파싱된 후에만 실행될 수 있습니다. 이를 중요한 요청 체인이라고 하며 피해야 합니다.
또한 JavaScript를 사용하여 마크업을 렌더링하면 탐색 요청에 대한 응답으로 서버에서 다운로드한 마크업보다 긴 작업이 생성될 가능성이 높습니다. HTML의 클라이언트 측 렌더링을 광범위하게 사용하면 상호작용 지연 시간에 부정적인 영향을 미칠 수 있습니다. JavaScript가 DOM을 수정할 때 상당한 렌더링 작업이 트리거되는 페이지의 DOM이 매우 큰 경우 특히 그렇습니다.
압축
CSS와 마찬가지로 JavaScript를 압축하면 스크립트 리소스의 파일 크기가 줄어듭니다. 이렇게 하면 다운로드 속도가 빨라져 브라우저가 JavaScript 파싱 및 컴파일 프로세스로 더 빠르게 이동할 수 있습니다.
또한 JavaScript 압축은 CSS와 같은 다른 애셋을 압축하는 것보다 한 단계 더 나아갑니다. JavaScript가 압축되면 공백, 탭, 주석과 같은 항목이 삭제될 뿐만 아니라 소스 JavaScript의 기호도 짧아집니다. 이 프로세스를 난독화 라고도 합니다. 차이점을 확인하려면 다음 JavaScript 소스 코드를 참고하세요.
// Unuglified JavaScript source code:
export function injectScript () {
const scriptElement = document.createElement('script');
scriptElement.src = '/js/scripts.js';
scriptElement.type = 'module';
document.body.appendChild(scriptElement);
}
위의 JavaScript 소스 코드가 난독화되면 결과는 다음 코드 스니펫과 비슷하게 보일 수 있습니다.
// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}
위의 스니펫에서 소스의 사람이 읽을 수 있는 변수
scriptElement가 t로 짧아진 것을 볼 수 있습니다. 대규모 스크립트 모음에 적용하면 웹사이트의 프로덕션 JavaScript가 제공하는 기능에 영향을 미치지 않고도 상당한 절감 효과를 얻을 수 있습니다.
번들러를 사용하여 웹사이트의 소스 코드를 처리하는 경우 프로덕션 빌드에 난독화가 자동으로 실행되는 경우가 많습니다. 예를 들어 Terser와 같은 난독화 도구는 구성 가능성이 높으므로 난독화 알고리즘의 공격성을 조정하여 최대한 절감할 수 있습니다. 하지만 난독화 도구의 기본값은 일반적으로 출력 크기와 기능 보존 간에 적절한 균형을 맞추기에 충분합니다.
JavaScript 데모
학습한 내용 테스트
브라우저에서 여러 CSS 파일을 로드하는 가장 좋은 방법은 무엇인가요?
@import 선언.<link> 요소.브라우저 프리로드 스캐너는 어떤 역할을 하나요?
<link rel="preload"> 요소를 감지합니다.
JavaScript 리소스를 다운로드할 때 브라우저가 기본적으로 HTML 파싱을 일시적으로 차단하는 이유는 무엇인가요?
다음: 리소스 힌트로 브라우저 지원
<head> 요소에서 로드된 리소스가 초기 페이지 로드 및 다양한 측정항목에 미치는 영향
을 파악했으므로 이제 다음 단계로 이동할 수 있습니다. 다음
모듈에서는 리소스 힌트와 리소스 힌트가 브라우저에 유용한 힌트를 제공하여 브라우저가 리소스 힌트가 없는 경우보다 더 빨리 리소스 로드를 시작하고 교차 출처
서버에 연결을 여는 방법을 살펴봅니다.