본문으로 건너뛰기

04 마크업과 스타일 최적화

HTML 청소하기

마크업 및 스타일 청소만으로도 사이트의 페이지 로딩 시간을 반으로 줄일 수 있다. 다음 항목들을 살펴보자.

  • 스타일시트로 옮길 필요가 있는 내장 스타일이나 코드 사이에 포함된 스타일
  • 특별한 스타일 적용이 필요하지 않은 요소(흔히 디비티스(divitis)라 고도 하는 불필요한 HTML 요소)
  • 오래되고 주석으로 처리되어 제거할 수 있는 코드

디비티스

디비티스(divitis)란 콘텐츠에 스타일을 적용하는 것처럼 사소한 이유로 HTML 내에 과하게 많은 요소가 사용된 상태를 말한다. 요소의 역할이나 문맥을 표현하기 위해 div 요소를 사용할 때 종종 발생하기 때문에 디비티스라고 부른다.

<div>
<div>
<header>
<div id="header">
<h1><span>사이트 이름</span></h1>
</div>
</header>
</div>
</div>

앞의 예를 보면 div 요소가 왜 여러 개 쓰여야 하는지 이유가 분명하지 않다. 보통 디비티스는 코드를 만드는 사람이 스타일의 계단식 표현을 제어하지 못하고 룩앤필을 덮어쓰기 위해 수정하고자 하는 대상에 CSS와 함께 부모요소를 추가했을 때 나타난다.

디비티스는 HTML과 CSS 모두를 쓸데 없이 비대하게 만든다. 불필요한 요소르 ㄹ제거해 사이트를 훨씬 더 간단한 계층구조로 만들어야 한다. 가능하면 HTML5 요소(header나 article 같은)를 사용하여 문맥 기반의 계층구조를 만들자. 그렇게 하면 CSS를 어떻게 작성할지 더 쉽게 알 수 있고, 동시에 재사용 가능한 디자인 패턴을 만들 기회도 얻을 수 있다.

문맥 기반

문맥 기반의 요소 이름은 요소 안의 콘텐츠 종류를 보여준다. 이때 left, blue 처럼 콘텐츠의 룩앤필을 의미하는, 문맥과 관계없는 이름보다 콘텐츠의 의미가 담긴 이름을 사용하면 좋다.

요소의 이름을 문맥에 맞게 바꾸는 것은 사이트를 더 나은 HTML 구조로 만드는 데 도움이 될 뿐 아니라 사이트 전반에 걸쳐 재사용이 가능한 디자인 패턴을 만들 수 있게 한다.

<div class="right">
<div id="form">
<form>
<p class="heading">Login</p>
<p>
<label for="username">Username:</label>
<input type="text" id="username"/>
</p>
<p>
<label for="password">Password:</label>
<input type="text" id="password" />
</p>
<input type="submit" value="Submit" />
</form>
</div>
</div>

현재 사용되는 요소들의 이름에는 특별한 의미가 없다. 문맥 기반의 코드를 만들어보자.

<div class="sidebar">
<form id="form">
<h2>Login</h2>
<ul>
<li>
<label for="username">Username:</label>
<input type="text" id="username"/>
</li>
<li>
<label for="password">Password:</label>
<input type="text" id="password" />
</li>
<li><input type="submit" value="Submit" /></li>
</ul>
</form>
</div>

파일 크기가 작으면 페이지 로딩 시간이 줄어드는 효과가 있으며 장기적으로 사이트의 페이지가 비대해지는 위험도 줄일 수 있다. 이처럼 문맥 기반의 이름은 무엇을 위한 코드인지 명확히 보여주고, 문맥 기반의 구조는 디자인과 스타일을 재사용할 수 있게 해주며 더 나은 사용자 경험을 사용자에게 제공할 수 있게 해준다.

접근성

간결한 HTML은 문맥 기반 마크업의 성능, 편집 용이성과 더불어 사이트의 접근성에 도움이 된다. 문맥 기반의 HTML은 브라우저나 검색엔진, 스크린 리더가 이해하기 좋은 콘텐츠 계층구조를 제공한다. HTML5의 새로운 태그인 post, aside 같은 것들과 heading, lists 같이 이미 있는 문맥 기반 구조의 태그를 이용하면 웹 콘텐츠에 더 용이하게 접근할 수 있다.

CSS 정리하기

사이트의 레이아웃을 잡을 때에는 다방면으로 생각한 후 HTML 계층구조를 만들어야 한다. 또 신중히 선택해 디자인을 해야 깔끔하고 편집이 용이하면서도 동시에 성능이 좋은 CSS를 만들 수 있다.

사이트에 사용된 기존 CSS를 정리할 때에는 HTML 계층구조와 디자인 결정이 CSS에도 반영되고 있는지 확인하자. 다음과 같은 부분을 살펴보면 된다.

  • 문맥상 의미가 없는 요소 이름
  • !important 선언
  • 특정 브라우저에만 적용되는 기능
  • 과도하게 사용된 셀렉터 특이성

사용하지 않는 스타일

CSS 정리 작업의 첫 번째는 사용하지 않는 스타일을 제거하는 것이다. 제거할 수 있는 CSS를 찾는 일을 도와주는 다양한 도구들이 있다. 파이어폭스와 오페라 브라우저를 지원하는 플러그인인 더스트-미 셀렉터는 웹사이트의 HTML에 포함되어 있는 사용하지 않는 셀렉터들을 검색해준다. 크롬 개발자 도구에는 웹 페이지 성능 검사를 하고 사용하지 않는 CSS 규칙을 보여주는 검사(Audit) 탭이 있다.

더스트-미 셀렉터는 사이트 내의 페이지를 놓치는 경우가 있다. 또한 크롬 개발자 도구는 현재 페이지의 CSS 셀렉터만 본다는 점을 주의하자.

스타일을 합치고 줄이기

스타일시트를 둘러보면서 스타일을 합치거나 줄일 기회가 있는지 살펴보자. 이는 성능과 코드 유지보수 모두에 도움이 된다.

자주 사용되는 디자인 패턴을 발견했다면 여러 클래스 이름을 쉼표로 이어서 쓰는 것보다 하나의 클래스로 합쳐 사이트 전체에 걸쳐 사용하면 좋다.

합칠 수 있다면 복잡성이 낮아져 유지보수가 쉬워질 뿐 아니라 CSS 파일의 코드 수도 줄어든다.

만약 LESS나 SASS와 같은 CSS 전처리기를 사용할 때에도 CSS 파일안에 디자인 패턴을 재사용할 기회나 스타일 코드를 합치거나 줄일 기회가 없는지 찾아보고 정리해야 한다. 추가로 믹스인(한 번만 정의한 후 재사용할 수 있는 스타일 블록)을 가능한 한 효율적으로 유지하는 데 초점을 두고 스타일시트의 결과물을 감시하자.

스타일시트 이미지 정리하기

스타일시트에서 사용하는 이미지들을 살펴봐야 한다. 스타일시트에서 사용되는 이미지의 숫자와 크기를 줄이면 사이트 페이지 로딩 시간을 줄이는 데 직접적인 영향을 준다. 첫 번째, 스프라이트로 만들 수 있는 이미지가 있는지 찾아보자. 사이트 전반에 걸쳐 사용되는 아이콘이나 작은 이미지가 많다면 콘텐츠의 요청 횟수를 최적화하는 데 스프라이트가 매우 유용할 것이다.

스프라이트를 정리한 후 더 적절한 파일 형식이나 더 높은 압출을 적용한 스프라이트를 다시 만들 수 있는지 찾아보자.

스타일시트 내의 이미지를 CSS3 그레이디언트나 데이터 URI, SVG로 대체할 수 있는 곳이 있는지 찾아보자. CSS3 그레이디언트는 현재 CSS로 구현되는 모든 반복 배경 이미지를 대체하기에 좋다. 수정이 매우 쉬우며 스타일시트를 통해 재사용할 수도 있다. 게다가 이미지를 CSS3로 바꾸면 사이트 속도도 매우 빨라질 것이다. 마찬가지로 스타일시트 이미지를 SVG로 교체하는 것도 페이지 로딩 시간에 긍정적인 영향을 줄 수 있다. SVG 파일이 스타일시트 내의 고해상도와 일반 해상도를 위한 이미지 모두를 동시에 대체할 수 있기 때문이다.

특이성 제거하기

CSS에서 특이성이란 브라우저가 어떤 CSS 규칙을 적용할 것인지 결정하는데 도움이 되도록 셀렉터를 작성하는 방법을 말한다. 셀렉터는 여러 가지가 있으며 각 셀렉터들은 자신만의 가중치가 있다.

두 셀렉터가 같은 요소에 적용되면 가중치에 따라 더 높은 특이성을 가진 셀렉터가 선택되는 것이다.

CSS가 효율적일수록 성능은 좋아진다. 특이성을 줄이면 추가적인 가중치나 !important 규칙을 사용하는 대신 CSS의 계층구조를 이용해 자연스럽게 스타일을 덮어쓸 수 있다. 비효율적인 셀렉터와 !important 규칙은 CSS 파일의 크기가 커지게 하므로 가능하면 항상 작고 가벼운 셀렉터부터 만들고 거기에 특이성을 추가하는게 좋다.

웹 글꼴 최적화

웹 글꼴은 사이트에 필요한 요청 횟수를 늘리고 페이지의 크기도 커지게 한다. 글꼴은 아름다움과 페이지 속도 사이에서 저울질하게 하는 고전적인 문제다.

WOFF(Web Open Font Format)의 지원이 늘어나고 있으므로 사용자의 브라우저와 사이트가 어떤 브라우저를 지원하는지에 따라 @font-face같은 더 짧은 선언 방법도 사용할 수 있다.

@font-face {
font-family: '글꼴 이름';
src: url('글꼴 이름.woff') format('woff');
}

그런 다음 font-family를 사용하여 글꼴을 셀렉터에 적용하고 사용자가 새로운 글꼴을 다운받지 못한 경우에 대비해 대체 글꼴도 포함한다.

body {
font-family: '글꼴 이름', 대체 글꼴, sans-serif;
}

문자 부분 집합은 웹 글꼴 파일의 크기를 줄일 수 있는 가장 효과적인 방법이다. 구글이 하는 것과 같은 글꼴 호스팅 서비스를 통해 글꼴을 사용하고 있다면 특정 문자 집합만 로딩하도록 선택할 수 있다. 다음은 구글의 Philosopher 글꼴에서 키릴 문자 부분 집합을 로딩하는 예제다.

<link href="http://fonts.googleapis.com/css?family=Philosopher&subset=cyrillic" rel="stylesheet" />

구글 호스팅 글꼴에서 특정 문자만 로딩하려면 다음과 같이 하면 된다. 예를 들어 Philosopher 글꼴에서 H, o, w. d, y 문자를 로딩한다고 하자.

<link href="http://fonts.googleapis.com/css?family=Philosopher&text=Howdy" rel="stylesheet" />

구글에서 하는 것처럼 외부에서 호스팅하는 글꼴은 방문자가 다른 사이트를 통해 글꼴 캐싱을 받아서 이미 가지고 있다면 성능상 더 나을 수도 있다.

웹 글꼴을 로딩할 때 쓸 수 있는 다른 최적화 방법은 화면이 클 때만 글꼴을 로딩하는 것이다. 이 방법은 스마트폰처럼 성능이 중요한 기기에서 글꼴 요청을 없애고 페이지 크기도 줄이는 효과를 낸다.

웹 글꼴은 다음처럼 미디어 쿼리를 사용하여 적용한다.

@media (min-width: 1000px) {
body {
font-family: '글꼴 이름', 대체 글꼴, sans-serif;
}
}

마크업에 대한 추가 고려사항

마크업 및 스타일을 정리한 후에도 페이지 로딩 시간을 개선하기 위해 자원 로딩 순서 변경, 최소화, 캐싱과 같은 추가적인 최적화 적용이 필요하다.

CSS와 자바스크립트 로딩

CSS와 자바스크립트를 로딩할 때는 두 가지 규칙이 있다.

  • <head>에서 CSS를 로딩한다.
  • 페이지 하단에서 자바스크립트를 로딩한다.

CSS는 렌더링을 방해한다. 스타일시트를 페이지의 하단 가까이에 넣으면 스타일시트는 페이지가 화면에 그려지는 것을 방해하게 된다. 브라우저는 그려야 할 콘텐츠의 스타일이 변경되고 있을 때 페이지의 요소를 화면에 다시 그리는 일을 피하려 하기 때문이다. 스타일시트를 <head>에 넣으면 브라우저가 더 이상 스타일 정보를 찾지 않으므로 사용자에게 화면이 순차적으로 보이도록 할 수 있다.

CSS는 작을수록 좋으며, 개인적으로는 필요에 따라 사이트 전체에 사용되는 스타일시트와 페이지별 스타일시트를 따로 두는 것이 더 나을 수도 있다. 이 방법을 통해 사이트 전체에 사용되는 스타일시트는 캐싱을 하고 페이지별 스타일시트만 다운받도록 운영하면 다운받는 횟수를 최소한으로 운영할 수 있다.

자바스크립트 파일은 페이지의 끝에서 가능한 한 비동기 방식으로 로딩되어야 한다. 그렇게 하면 더 빠르게 다른 페이지의 콘텐츠를 사용자에게 보여줄 수 있다. 이때 자바스크립트를 명시적으로 비동기라고 선언하지 않으면 DOM 만들기를 방해한다.

브라우저의 HTML 파서가 스크립트 태그를 발견하면 브라우저는 스크립트에 포함된 작업이 페이지의 렌더링 트리를 변경할 수도 있다고 판단한다. 그래서 스크립트가 하고자 하는 일을 끝낼 때까지 DOM 만들기를 잠시 중단한다. 스크립트의 작업이 끝나면 브라우저는 HTML 파서가 중단된 곳에서 DOM 만들기를 이어서 시작한다. 그러므로 스크립트를 페이지의 마지막에서 호출하도록 옮기고 비동기로 선언하는 과정을 통해 중요 렌더링 경로를 최적화함으로써 사용자의 체감 성능을 개선하고 렌더링-블로킹 문제를 없앨 수 있다.

스크립트 파일을 다른 파일로 부르게 만들었을 때 async 태그를 스크립트에 추가하면 브라우저에게 이 스크립트가 바로 실행되지 않아도 되므로 콘텐츠 렌더링을 막지 말라고 알려줄 수 있다.

<script src="main.js" async></script>

이 코드로 브라우저가 DOM 만들기를 계속하도록 하고 스크립트는 다운받은 후 준비가 되면 실행한다.

비동기 스크립트를 사용할 떄 주의해야할 점들이 있다.

나중에 로딩되어 콘텐츠가 밀리면 페이지 레이아웃에 영향을 줄 수도 있다. 비동기 속성은 로딩 순서를 보장하지 않으므로 의존성 문제를 일으킬 수 있다는 것을 기억해야 한다.

광고나 소셜 공유 버튼, 위젯 같은 타사의 콘텐츠는 사이트의 성능에 치명적일 수 있다. 최적화를 위해 이미 이런 것들을 비동기로 부르고 있겠지만, 외부에서 호스팅되는 자원이 사이트 장애의 주요 원인이 되지 않도록 해야 한다. 서드파티의 스크립트는 페이지 크기에 부담을 줄 수 있고 동시에 다른 곳에 있는 자원을 가져오기 위해 추가적인 DNS 조회가 필요하기 때문에 성능 문제도 생긴다. 또한 타사의 자원은 캐싱도 제어할 수 없다.

최소화와 GZIP

최소화를 위해 css Minifier나 JS Compress 같은 온라인 도구, 혹은 명령창 실행 도구를 사용할 수 있다. 텍스트 파일을 압축할 수 있는 또 다른 방법은 gzip을 적용하는 것이다. gzip 알고리즘은 텍스트 파일 안에 유사한 문자열을 찾은 후 찾은 문자열을 대체하여 전체 파일 크기를 줄인다. 반대로 브라우저는 압축된 파일을 받아 대체된 문자열을 디코딩한 후 사용자에게 올바른 콘텐츠를 보여준다.

gzip은 스타일시트와 HTML, 자바스크립트, 글꼴 등 모든 텍스트 파일에 적용할 수 있다. WOFF 글꼴 파일은 유일하게 이미 압축되어 있어 gzip을 적용할 수 없다.

자원 캐싱하기

캐싱은 사이트 성능에 매우 중요하다. 캐싱된 자원은 서버에서 다시 받을 필요가 없기 때문에 요청 횟수를 줄일 수 있다. 캐싱을 위한 정보는 브라우저와 서버 사이에 주고받는 요청의 HTTP 헤더에 들어 있다. HTTP 헤더에는 브라우저의 User-Agent나 쿠키 정보, 사용된 인코딩 종류등 많은 부가 정보가 들어 있는데 응답 헤더에 포함될 수 있는 캐싱에 관련된 파라미터는 두 가지다.

  • 서버에 새로운 자원이 있는지 확인하지 않고 캐싱된 자원을 사용할 수 있는 기간 정보 (Expires와 Cache-Control: max-age)
  • 브라우저가 캐싱된 자원인지 비교, 판단하는 데 사용되는 서버에 저장된 자원의 버전 정보(Last-Modified와 ETag)

모든 스태틱 자원(CSS 파일, 자바스크립트 파일, 이미지, PDF 파일, 글꼴 등)은 캐싱이 적용되어야 한다. 이때 다음을 기억하자.

  • Expires에는 만기일로부터 최대 1년까지만 설정한다. 1년 이상 값으로 설정하면 RFC 지침에 위배된다.
  • Last-Modified는 자원이 마지막으로 변경된 날짜를 설정한다.

자주 바뀔 파일이라 유효기간을 짧게 설정하고 싶다면 해도된다. 최소 일 개월 정도가 가장 좋다. 기간을 조절하는 대신 자원의 URL을 변경하여 사용자의 브라우저가 캐싱을 중단하고 새 버전을 가져가도록 강제할 수도 있다.