Vercel을 통해 배포한 프로젝트를 Lighthouse로 검사한 후 최적화를 진행해 보기로 했다.
Lighthouse는 웹 페이지의 성능, 접근성, SEO 및 모범 사례를 평가하여 개선할 수 있는 영역을 제시해 주기 때문이다.
1. Lighthouse로 검사 후 제공되는 보고서의 주요 항목은 다음과 같다.
- 성능 : 페이지 로딩 속도, 인터랙티브 시간, 이미지 최적화 등.
- 접근성 : 사용자 경험을 향상시키기 위한 접근성 기준 충족 여부.
- SEO : 검색 엔지 최적화 과년 점검.
- 모범 사례 : 보안 및 최저화 관련 체크 항목.
2. 최적화 개선 사항
Lighthouse에서 제시한 일반적인 최적화 방법
- 이미지 최적화 : 이미지 파일 크기를 줄이고 필요한 경우 포맷을 변경 (예 : WebP)
- 코드 스플리팅 : 필요하지 않은 코드를 제거하고, 페이지가 필요할 때만 로드하도록 설정한다.
- 캐싱 전략 : 정적 자산에 대한 적절한 캐싱 전략을 설정하여 페이지 로딩 속도를 개선한다.
- CSS 및 JavaScript 최적화 : 불필요한 CSS 및 JavaScript를 제거하고, 압축한다.
3. 분석
빨간색으로 뜨는 진단 항목 위주로 개선을 해보자.
# 1~4 페이지에 대한 분석
1. 렌더링 차단 리소스 제거하기 - 절감 가능치 : 110밀리 초
렌더링 차단 리소스 제거 | Lighthouse | Chrome for Developers
렌더링 차단 리소스 감사에 대해 알아봅니다.
developer.chrome.com
문제 상황
웹페이지가 로딩될 때, CSS나 JS 파일을 먼저 불러와야 한다. 그런데, 파일들이 렌더링을 차단하면, 화면에 내용이 늦게 보일 수 있다. 라이트하우스에서는 Preload를 사용하여 미리 로드 후 스타일시트를 적용하는 방법을 제시해 줬다.
Preload는 중요한 파일을 미리 불러와서 렌더링을 막지 않도록 도와주는 방법이다.
Preload 방식으로 CSS를 빠르게 불러오는 방법
- preload를 사용하면 미리 파일을 불러오기만 하고, 화면을 렌더링 할 때까지 기다린다.
- onload 이벤트를 이용해서 파일이 다 불러와지면 그때 스타일을 적용한다.
- 이렇게 하면 페이지가 빨리 보이고, 나중에 스타일이 추가된다.
쉽게 말하면
- 페이지가 빠르게 보일 수 있게 필요한 파일을 미리 준비하는 거다. 파일이 준비되면, 그때 화면에 스타일을 입힌다.
예를 들어 내가 사이트에서 아이콘 폰트를 추가하고 싶다. 그런데 이 아이콘을 불러오는 동안 페이지 로딩이 느려지면 사용자 경험이 나빠지겠지? 그래서, 이 아이콘을 preload로 미리 불러오고, 나중에 화면에 적용하게 만들면, 더 빠르게 페이지를 보여줄 수 있다.
어떻게 적용할까?
HTML 코드(원래 코드) :
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.3.0/fonts/remixicon.css" rel="stylesheet" />
Preload를 사용한 코드 :
<link
rel="preload"
href="https://cdn.jsdelivr.net/npm/remixicon@4.3.0/fonts/remixicon.css"
as="style"
onload="this.rel='stylesheet'"
crossorigin="anonymous"
/>
<noscript>
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.3.0/fonts/remixicon.css" rel="stylesheet" crossorigin="anonymous" />
</noscript>
* rel = "preload"
의미 : preload는 브라우저가 특정 리소스를 미리 다운로드하도록 지시하는 역할.
왜 사용했을까? : 이 속성은 페이지의 렌더링을 방해하지 않고, 해당 리소스를 미리 불러와서 준비해 두는 데 사용된다. 즉, 페이지가 빠르게 로드되는 동안 아이콘 폰트 파일을 백그라운드에서 미리 불러오는 거다. 렌더링 차단을 막고 성능을 향상시키는 방법이다.
* onload="this.rel='stylesheet'":
의미: onload는 해당 리소스가 완전히 로드되었을 때 실행할 동작을 정의.
왜 사용했을까?: 여기서는 파일이 완전히 불러와졌을 때 rel 속성을 stylesheet로 바꿔준다. 이렇게 함으로써 브라우저는 다 불러온 후에만 스타일을 적용하게 된다.
이렇게 하면: 처음에는 파일을 미리 불러오기만 하고, 페이지 렌더링을 방해하지 않게 해 두었다가, 파일이 다 로드되면 그때 비로소 스타일을 적용한다.
* crossorigin="anonymous"
의미 : 교차 출처 속성으로, 이 리소스는 CDN에서 제공되므로 이를 추가하면 CORS 문제를 방지하는 데 도움이 된다.
* <noscript> 태그:
의미: <noscript> 태그는 사용자의 브라우저에서 자바스크립트를 사용할 수 없는 경우에 실행할 내용을 정의.
왜 사용했을까?: 만약 사용자의 브라우저에서 자바스크립트가 꺼져 있다면, preload와 onload 기능이 동작하지 않는다. 그래서 자바스크립트가 꺼져 있는 경우에도 스타일을 정상적으로 적용하려고, <noscript> 안에 원래의 <link rel="stylesheet"> 태그를 사용한다.
* <link href="..."> (noscript 안에 있는 것):
의미: 이 태그는 자바스크립트가 꺼져 있을 때, 일반적인 방식으로 스타일시트를 불러오기 위한 대안이다.
왜 사용했을까?: 만약 자바스크립트가 꺼져 있을 때도 웹사이트의 스타일을 그대로 유지하려고 이 대체 방식을 사용한 것이다. 자바스크립트를 사용하지 않는 환경에서도 스타일이 깨지지 않도록 하기 위해서다.
2. 사용되지 않는 자바스크립트를 줄이고 로딩 지연해결하기
사용하지 않는 자바스크립트 삭제하기 | Lighthouse | Chrome for Developers
Lighthouse의 "사용하지 않는 자바스크립트 삭제" 감사를 통과하는 방법을 알아보세요.
developer.chrome.com
Tree Shaking 방법으로 위의 문제를 해결할 수 있다고 하길래. Tree Shaking에 대해서 알아보자.
Tree Shaking은 사용되지 않는 코드를 자동으로 제거하는 기법이다. 이 방법을 사용하면 자바스크립트 번들러(Webpack, Rollup 등)가 불필요한 코드를 감지하고 제거해 준단다.
하지만, CRA 프로젝트에서 Tree Shaking을 사용하는 것은 자동으로 지원이 된다는 사실이다. CRA가 좋긴 좋다.(최적화를 잘해놓아서 개발자들이 개발에만 집중하도록 해놓다니...)
Tree Shaking을 위한 기본 설정 :
CRA 프로젝트는 기본적으로 Webpack을 사용하고, Webpack은 프로덕션 모드에서 자동으로 Tree Shaking을 활성화한다. 그러나 몇 가지 추가적인 방법으로 Tree Shaking을 개선할 수 있다.
#1. 프로덕션 모드로 빌드하기
npm run build 명령어를 실행하면 CRA에서 자동으로 Tree Shaking을 포함한 여러 최적화 작업을 수행한 프로덕션 빌드를 생성한다. 이렇게 하면 사용되지 않는 코드가 가능한 한 제거된다.
npm run build
이 명령어를 실행하면 build 폴더 안에 최적화된 번들이 생성된다. 그 번들은 Tree Shaking을 포함해 코드가 최적화 상태가 된다.
#2. sideEffects 설정 추가
package.json에 sideEffects 옵션을 설정하여, Webpack이 불필요한 모듈을 더 활실하게 제거할 수 있게 도와줄 수 있는 이점이 있지만, 사이드 이펙트가 있는 코드가 제거되는 경우와 같은 잠재적인 문제가 있을 수 있다. 예를 들어, 의도치 않게 필요한 코드도 삭제될 수 있다는 말이다.
package.json에 추가하면 됨.
{
"name": "my-app",
"version": "1.0.0",
"sideEffects": false
}
물론, Vercel에 배포한 시점에 이미 npm run build가 자동으로 실행되어, Tree Shaking이 적용된 상태이지만, 혹시 프로젝트를 보충 및 보안한다고 추가로 작성한 코드가 커밋되지 않아. 적용되지 않았을 수도 있는 문제가 있어. 다시 build 후 커밋하여 배포하였다.
#3. 1,2번 방법이외에도 렌더링 차단 리소스 문제를 해결하기 위한 방법 몇가지가 있다.
* Code Splitting
- 페이지 별로 필요한 리소스만 분리하여 로드하는 방식
- 불필요한 자바스크립트 파일이 로드되는 것을 방지할 수 있음
- Next.js의 Dynamic Import 기능을 활용하면 쉽게 구현 가능
* Lazy Loading
- 페이지 로딩 시점에는 필수 리소스만 로드하고, 사용자 상호작용 시점에 추가 리소스 로드
- 초기 렌더링 속도를 높일 수 있음
- intersection observer API를 활용하여 구현 가능
* Bundler 최적화
- Webpack, Rollup 등 번들러 설정을 최적화하여 번들 크기 감소
- 트리 쉐이킹, 모듈 병합, 압축 등의 기법 활용
- Next.js의 경우 빌드 최적화 옵션 사용 가능
* 서버 렌더링 활용
- Next.js의 SSR(Server Side Rendering) 기능을 활용
- 초기 렌더링을 서버에서 수행하여 전송 시간을 줄일 수 있음
* Service Worker 도입
- 오프라인 대응 및 리소스 캐싱을 위해 Service Worker 활용
- 캐시된 리소스를 활용하여 재방문 시 빠른 로딩 가능
.....
Next.js를 걍 쓰는게 낫다라는 생각이 강하게 든다. 최적화 기능들을 손쉽게 활용할 수 있다니, 엄청 큰 메리트이다.
3. 버튼에 접근 가능한 이름이 없습니다.
https://dequeuniversity.com/rules/axe/4.9/button-name
Axe Rules | Deque University | Deque Systems
You have already given your feedback, thank you.. Your response was as follows: Was this information helpful? Date/Time feedback was submitted: Edit your response
dequeuniversity.com
문제 해결을 위한 접근 가능한 버튼 이름을 위한
올바른 마크업 패턴에는 여러 가지 방법이 있다는 것을 최적화를 진행하면서 새롭게 알았다.
1. 내부 텍스트 사용
<button id="text">Name</button>
- 버튼 내부에 직접 텍스트("Name")가 있어 접근할 수 있다.
2. aria-label 속성 사용
<button id="al" aria-label="Name"></button>
- aria-label 속성으로 버튼의 이름을 제공한다. 화면 판독기는 이 값을 읽을 수 있다.(오호)
3. aria-labelledby 속성 사용
<button id="alb" aria-labelledby="labeldiv"></button>
<div id="labeldiv">Button label</div>
- aria-labelledby 속성을 사용하여 다른 요소의 텍스트를 참조한다. 이 경우 labeldiv라는 div가 버튼의 이름을 제공한다.
4. 결합된 사용
<button id="combo" aria-label="Aria Name">Name</button>
- aria-label과 내부 텍스트를 동시에 사용한다.
5. title 속성 사용
<button id="buttonTitle" title="Title"></button>
- 버튼의 title 속성에 이름을 설정하여 접근할 수 있다.
이러한 속성값 설정이 왜 중요한가?
- 버튼에 이름이 없으면, 화면 판독기 사용자나 접근성 도구가 버튼의 목적을 알 수 없게 된다. 따라서 웹 접근성을 향상시키기 위해 모든 버튼을 명확하고 식별 가능한 이름을 제공하는 것이 중요하다.
4. 이미지 요소에 width 및 height가 명시되어 있지 않습니다.
이미지 요소에 명시적인 width 및 height를 설정하는 문제는 CLS(Cumulative Layout Shift), 즉 누적 레이아웃 이동 문제를 해결해야 한다. 이는 페이지가 로드되는 동안 이미지의 크기가 정해지지 않아서 레이아웃이 갑자기 변경되는 현상을 방지하기 위해서다.
기본 개념
- CLS는 페이지가 로드될 때 이미지, 폰트, 광고 같은 요소가 갑자기 나타나면서 레이아웃이 변동하는 현상을 말한다.
- 이미지에 width와 height를 미리 설정해 두면, 브라우저가 이미지를 로드하기 전에 그 자리에 일정한 공간을 확보한다. 이로 인해 이미지를 나중에 불러오더라도 레이아웃이 갑자기 바뀌지 않는다.
그런데 내 웹 사이트는 반응형으로 만들었기 때문에 직접적인 너비와 높이를 일부로 설정하지 않았다.
해결 방법
aspect-ratio 속성이나 width와 height 속성을 이용해 이미지의 비율을 미리 설정해 브라우저가 그 자리에 공간을 미리 확보하게 만들면 된다. 그러면 이미지를 불러올 때 레이아웃이 갑작스럽게 변경되지 않는다.
5. AVIF 또는 Webp 형식으로 이미지 변환 권장
AVIF 및 Wepb는 이전의 JPEG 및 PNG에 비해 압축 및 품질 특성이 뛰어난 이미지 형식이다.
이미지를 JPEG 또는 PNG가 아닌 이러한 형식으로 인코딩하면 로드 속도가 빨라지고 모바일 데이터를 적게 소비할 수 있어서 이를 권장한다.
이 웹사이트는 Unsplash API를 통해 JPG 이미지를 URL로 받아와 이를 화면에 뿌려주고 있다.
언스플래쉬 API의 쿼리로 이미지 확장자를 Webp으로 받아올 수 있나 확인했지만, 없다. 이를 위해서 서버를 만들어, 받아온 이미지를 라이브러리나 canvas api를 통해 변환 후 제공하는 방법이 좋다.
하지만 이 웹 사이트는 클라이언트로만 이뤄진 사이트다. 로드 속도를 아주 약간 향상시키기 위해 서버를 만들어 라이브러리를 설치하거나 canvas api를 통해 변환 후 가져오는 것은 현재로서 모기를 잡기 위해 초가집을 태우는 짓에 비교할 수 있을 거 같다.
물론, 경험치를 쌓는 면에서는 좋겠지만, 나에겐 시간이 없다. 이러한 경험은 추후 회사에 들어가 배우거나 따로 시간을 내서 경험해 보는 걸로 해야겠다.
아무튼 그래도 최대한 클라이언트단에서 canvas api를 이용해 변환을 할 수 있다. 하지만 초기 로딩 속도가 더 느려지는 문제가 있다.
1. 성능 문제
이미지 변환 작업은 "무거운" 작업이다. 이미지 변환은 메모리를 많이 소모하는 작업이다. 클라이언트(사용자의 브라우저)에서 이 작업을 하면, 특히 이미지 파일이 클 경우 사용자의 기기에 큰 부담을 줄 수 있다. 이는 페이지 로딩 속도를 느리게 만들고, 사용자 경험을 저하시킬 수 있다.
2. 네트워크 대역폭
고해상도 이미지는 파일 크기가 크기 때문에, 단순히 URL을 요청하는 것뿐만 아니라, 그 이미지를 다운로드하는 데도 많은 데이터가 소모된다.
만약 클라이언트에서 이 이미지를 Webp 형식으로 변환하고자 한다면, 이미지를 다운로드한 후 다시 그 이미지를 canvas api 같은 방법을 사용하여 변환해야 한다. 이 과정에서 이미지를 다시 메모리를 로드해야 하고, 다시 저장할 수 있는 Blob URL을 생성해야 한다.
요청한 이미지의 content - length는 3087718 bytes (약 3.08MB)이다. 이미지 데이터치고 꽤 큰 편이다.
큰 이미지를 다운로드하는 데 시간과 이를 변환하는 과정의 시간이 더해지면 로딩 시간이 기존의 시간보다 훨씬 길어진다.
3. 새로고침 후 이미지가 안 나오는 문제가 생길 수 있음
네트워크 상태나 로컬 스토리지 유효성, 비동기 처리 부분에서 문제가 있어서 그런지 새로고침을 하면 간혹 데이터 전송 실패로 인해 이미지를 불러오지 못하는 상황이 발생하는 등 문제가 생기는 부분이 많아서 상당한 시간이 소요될 것 같다.
성능 개선 진행 후 다시 라이트하우스로 검사를 진행해 보니 크게 달라지진 않았지만, 배울게 많았다.
라이브러리나 CDN을 사용하면 편리하지만, 그만큼 불필요한 JS 파일이나 리소스가 함께 로드되며, 성능에 영향을 미칠 수 있다. 이를 최적화하려면, 꼭 필요한 기능이나 리소스만을 선택적으로 사용하고, 가벼운 대안을 고려하는 것이 좋다.
예를 들어, 이쁜 아이콘을 사용하기 위해서 리믹스 아이콘의 라이브러리를 설치하거나, CDN을 사용하는 것보다 개별적 로드를 통해 SVG로 아이콘을 불러오는 방식말이다.
이런 방식으로 사용하지 않는 리소스와 코드를 줄이면, 네트워크 대역폭도 줄어들고, 페이지의 First Contentful Paint (FCP)나 Largest Contentful Paint (LCP) 같은 성능 지표를 올릴 수 있고 렌더링 차단 리소스가 줄어들어 사용자 경험도 향상된다.
'Project' 카테고리의 다른 글
싱글톤 자체는 메모리 사용량을 줄이지 않는다. (1) | 2024.11.09 |
---|---|
[React Project] Momentum ver 1 .1 .1 리팩토링 과정 중 문제&해결 (0) | 2024.10.18 |
[React Project] Momentum ver 1 .0 .1 (0) | 2024.10.08 |
[React Project]F.ROCK🥻(Shopping mall) (0) | 2024.08.15 |
[React Project] 파지직TV⚡[Ver2.0] (0) | 2024.07.27 |