웹 성능 측정은 웹사이트나 웹 애플리케이션이 사용자에게 얼마나 빠르고 효율적으로 작동하는지를 평가하는 과정이다.
이는 사용자 경험과 SEO(검색 엔진 최적화)에 중요한 영향을 미치기 때문에 매우 중요하다.
웹 성능 측정을 위한 도구는 여러개가 있지만 나는 그 중에서 Google Lighthouse를 사용하여 성능을 측정하고 개선하려고 한다.
본격적인 측정이 앞서, next로 개발한 페이지에서는 build를 하고 측정을 하는 것을 권장한다.
개발 환경에서는 프로덕션 환경보다 성능이 더 낮게 측정되어서 그렇다던데 궁금해서 한번 두가지 버전으로 검사를 다 해봤다
Chrome 개발자 도구 -> Lighthouse -> Analyze page load 실행
🔴build 안하고 검사한 ver
🟢build 하고 검사한 ver
확실히 차이가 난다. 특히 performance...정확한 측정을 위해 꼭 build를 하고 검사를 하자
//yarn
yarn build && yarn start
//or npm
npm run build && npm run start
Lighthouse에서는 개선을 위한 구체적인 권장 사항을 제시하므로 이를 적극적으로 참고하여 최적화 작업을 진행하는 게 좋다.(diagnostics 적극 활용)
performance 지표 항목은 아래와 같고 색은 다음을 나타낸다
빨간색: 성능이 매우 부족함을 나타내며, 개선이 필요. 점수로는 보통 0~49 사이에 해당
노란색: 성능이 평균 이하이거나 보통 수준. 개선이 가능하지만, 급한 문제는 아닐 수 있음 점수는 50~89 사이에 해당
녹색: 성능이 양호하다. 점수로는 90~100 사이에 해당
사용자가 페이지에 접속했을 때 브라우저가 DOM 컨텐츠의 첫 번째 부분을 렌더링하는 데 걸리는 시간
우수한 사용자 경험을 제공하려면 FCP가 1.8초 이하여야 한다.
FCP의 경우 페이지에 스플래시 화면이 표시되거나 로딩중 아이콘이 표시되는 경우도 포함한다.
페이지의 주요 콘텐츠가 완전히 로드되는 시간을 측정한다. 일반적으로 텍스트, 이미지, 비디오 등의 큰 요소가 여기에 포함된다
페이지에서 가장 용량이 큰 컨텐츠가 표시되는 시점을 나타낸다.
FMP(First Meaningful Paint)를 측정하는 것이 복잡하기 때문에 가장 용량이 큰 컨텐츠가 렌더링 되는 시기인 LCP를 주요 컨텐츠가 로드되는 시기로 판단하는 것을 권장하고 있다.
나는 LCP가 너무 커서 이것을 개선하려고 한다. diagnostics를 자세히 보자
1. TTFB (Time to First Byte)
TTFB는 첫 번째 바이트를 사용자에게 전송하는 데 걸리는 시간을 의미한다. 여기서 TTFB가 120ms로 표시되고 있는데, 이는 비교적 빠른 편이다. TTFB는 서버 응답 속도와 관련이 있으므로, 서버 성능이나 네트워크 최적화가 어느 정도 잘 되어 있다는 뜻이다.
2. Load Delay - 0ms
Load Delay는 리소스 로드 지연을 의미한다
3. Load Time - 0ms
Load Time은 리소스가 실제로 로드되는 시간을 나타낸다.
4. Render Delay - 5,470ms
Render Delay는 콘텐츠가 화면에 렌더링될 때까지의 지연 시간을 의미한다. 여기서 5,470ms로 측정되었는데, 이는 매우 긴 시간이다. 이 지연 시간은 페이지의 렌더링 최적화가 부족하거나, 렌더링 블로킹 자원이 많을 때 발생할 수 있다
현재 페이지의 경우 이미지나 영상과 같은 용량이 큰 컨텐츠가 없음에도 불구하고 lcp가 높게 측정되고 있다. 그것도 input 태그에서...
<input
className="outline-none w-full h-[80px] font-noto text-[30px] border-b-[3px] p-[20px]"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="제목을 입력해주세요"
/>
현재 이 코드에서 font 부분이 문제가 되고 있는 것 같다. 웹 페이지에서 커스텀 폰트를 사용하면, 브라우저가 해당 폰트를 다운로드하여 적용하는 과정이 필요하다. 이 과정에서 폰트 파일의 크기나 로딩 속도가 느리면 페이지 렌더링이 지연될 수 있다.
이럴때는 font-display 속성을 설정하면 된다. CSS에서 font-display 속성을 설정하면, 폰트 로딩 과정에서 텍스트가 보이지 않는 문제(FOIT)를 해결할 수 있다. font-display: swap;을 사용하면 폰트가 로드될 때까지 기본 폰트가 표시되고, 로드 후에 커스텀 폰트로 전환된다
aria label 속성을 활용하면 된다
aria-label
시각적 콘텐츠나 UI 요소에 대한 대체 텍스트를 제공하여, 화면 리더(screen reader)와 같은 보조 기술이 웹 페이지를 더 쉽게 이해하고, 사용자에게 정보를 전달할 수 있게 도와주는 속성이다.
aria-label 속성은 HTML 요소에 사용자에게 보이지 않는 텍스트 라벨을 추가하는 데 사용된다. 이 속성은 특히 시각 장애인들이 화면 리더를 사용할 때, 해당 요소의 목적이나 기능을 이해할 수 있도록 도움을 준다. 예를 들어, 이미지나 버튼 같은 요소에 텍스트 라벨이 없을 때, aria-label을 사용하여 이 요소에 대한 설명을 추가할 수 있다.
//before
<button
onClick={() => setIsMyinfoOpen(!isMyinfoOpen)}
>
<FaUserCircle />
</button>
//after
<button
onClick={() => setIsMyinfoOpen(!isMyinfoOpen)}
aria-label="user info"
>
<FaUserCircle />
</button>
button과 마찬가지로 aria-label를 붙인다
배경색과 전경색(텍스트 색상) 간의 명암 대비가 충분하지 않다는 것을 의미한다. 시각적으로 구별하기 어려운 색상 조합으로 인해 시각 장애인이나 시력이 약한 사용자들이 콘텐츠를 읽거나 이해하기 어렵게 만든다
https://dequeuniversity.com/rules/axe/4.9/color-contrast
이 사이트에서 모든 요건을 통과할때까지 색상을 조절해줬다
🔴통과전
🟢통과후
HTML에서 ul 또는 ol 요소 내에 li 요소만 포함되어야 한다는 규칙을 위반했음을 의미한다. HTML 표준에 따르면, ul과 ol 요소는 li 요소만을 자식으로 가져야 한다. 이는 접근성과 seo에도 영향을 미치니 준수하는게 좋다
//before
<ul>
<li className="relative text-center font-noto font-bold text-3xl hover:cursor-pointer group">
카테고리
</li>
<SearchBar />
<Link
href="/faq
>
<GoQuestion />
</Link>
</ul>
//after
<ul>
<li className="relative text-center font-noto font-bold text-3xl hover:cursor-pointer group">
카테고리
</li>
<li>
<SearchBar />
</li>
<li>
<Link href="/faq">
<GoQuestion />
</Link>
</li>
</ul>
메타데이터를 설정해주면 된다. 메타데이터는 브라우저의 탭 제목, 검색 엔진 최적화(SEO), 소셜 미디어에서의 미리보기 등에 영향을 미칩니다.
지금 내가 쓴 방법은 next 13이상 app 디렉토리 구조에서 가능하다
export const metadata: Metadata = {
title: "원하는 제목",
description: "원하는 설명",
};
export default function WritePostingPage() {
이번에는 이미지 비율이 상대적으로 높은 메인페이지다
아니나 다를까 LCP가 높다..
next의 Image 컴포넌트는 이미 상당부분 최적화를 진행시켜준다
- 자동 포맷 변환:
Next.js는 자동으로 최적의 이미지 포맷을 선택할 수 있다. WebP는 이미지의 크기를 줄이는 데 유리한 포맷으로, 브라우저가 WebP를 지원하면 next/image는 WebP 포맷으로 변환하여 제공한다. WebP는 JPEG보다 약 25-34% 더 나은 압축률,PNG보다 평균 26% 작은 파일 크기를 제공을 제공하면서도 비슷한 품질을 유지한다.- 최적화 및 리사이즈
이미지를 서버 측에서 자동으로 최적화하고, 다양한 크기로 리사이즈하여 제공할 수 있다. 이는 페이지 로딩 속도를 향상시키는 데 도움을 준다.- 로딩 성능 개선
next/image는 이미지 로딩 성능을 개선하기 위해 지연 로딩(lazy loading)을 기본적으로 지원한다. 이는 페이지 로딩 시 비동기적으로 이미지를 로드하여 초기 로딩 성능을 개선한다.
이와 같은 특징들은 웹 페이지의 로딩 속도를 개선하고, 데이터 사용량을 줄이는 데 도움이 됩니다.
하지만 나는 더 나은 성능을 원한다!!😈
webp보다 20% 더 높은 압축률을 보여준다는 avif를 적용해보자
Next.js는 v12.0.0부터 avif를 지원하기 시작했습니다. 다음과 같이 next.config.js를 설정하면 avif 지원을 활성화할 수 있다
module.exports = {
images: {
formats: ['image/avif', 'image/webp'], // default: ['image/webp']
},
};
avif를 지원하지 않는 브라우저면 webP로 포맷해준다
용량과 속도 모두 개선된 모습을 볼 수 있다
sharp는 다양한 크기의 JPEG, PNG, WebP, GIF, AVIF와 같은 이미지들을 더 작은 크기로, 그리고 웹에 진화적으로 변환해 주는 매우 빠른 속도의 모듈이다.
https://nextjs.org/docs/messages/sharp-missing-in-production
next 공식문서에 따르면 sharp 라이브러리를 사용할 것을 강력 권장하고있다.
SVG와 GIF는 최적화 되지 않는다. (PNG, JPEG 등을 지원한다.)
sharp는 Next 공식 문서에서도 production 환경에서 반드시 사용하도록 권장하는 라이브러리이다
NEXT.JS는 어떻게 sharp가 설치되어 있는지 그 내부 코드를 살펴보면
Next.js의 이미지 최적화 모듈은 이미지 처리 라이브러리인 sharp이 설치되어 있는지 확인한 후, sharp이 설치되어 있다면 이를 사용해 이미지를 최적화한다. sharp이 설치되지 않은 경우, Next.js는 기본적으로 Node.js의 내장 모듈인 squoosh로 대체하여 이미지를 처리한다
그래서 설치 이후에 적용하는 코드를 따로 작성할 필요가 없고 설치하면 바로 적용이 된다
avif변환만
avif변환 + sharp 적용
용량에선 차이가 없지만 로드 속도가 개선되었다
하지만 아직도 LCP가 2초대...
가장 시간이 오래 걸리는 부분을 살펴보니 상단에 위치한 배너캐러셀이었다.
const imgList = [
{ src: "/image/sample/1.jpg"},
{ src: "/image/sample/2.jpg"},
{ src: "/image/sample/3.jpg"},
{ src: "/image/sample/4.jpg" },
{ src: "/image/sample/1.jpg"},
];
{imgList.map((value, index) => (
<div className="w-full h-[350px]" key={index}>
<div>
<Image
src={value.src}
alt="bannerImage"
key={index}
priority={true}
/>
</div>
</div>
))}
priority 속성은 페이지 로딩 시 가장 먼저 로드해야 할 이미지를 지정하는 데 사용된다. 첫 인상이 중요한 랜딩 페이지나 초기 로딩 속도가 중요한 페이지에서는 이 속성을 사용해 성능을 극대화할 수 있다.
현재 이 이미지를 map을 돌리면서 렌더링을 하는데 priority 속성이 모두 true로 설정되어 오히려 성능이 저하된 것 같다.
첫번째 이미지만 priority true로 설정하고 그 다음 이미지들은 false로 설정을 하자. 두번째 이미지부터는 조금 늦게 로드되어도 괜찮으니!
const imgList = [
{ src: "/image/sample/1.jpg", priority: true },
{ src: "/image/sample/2.jpg", priority: false },
{ src: "/image/sample/3.jpg", priority: false },
{ src: "/image/sample/4.jpg", priority: false },
{ src: "/image/sample/1.jpg", priority: false },
];
{imgList.map((value, index) => (
<div className="w-full h-[350px]" key={index}>
<div>
<Image
src={value.src}
alt="bannerImage"
key={index}
priority={value.priority}
/>
</div>
</div>
))}
이렇게 이것저것 처리하고나니 LCP가 1초대로 줄어있었다 얏호!!
글 작성 페이지(이미지x)
<전>
<후>
메인페이지(이미지o)
<전>
<후>
두 페이지가 Best Practices와 SEO는 100,
글 작성페이지에서 성능 31.58% , 접근성 26.32% 개선
메인페이지에서 성능 10.71% , 접근성 9.09% 개선
완료하였다~!
lighthouse로 성능 측정하고 개선하기는 정말 예전부터 해보고 싶었던 작업이다. 하지만 괜히 어려워보이고 복잡해보여서 미루고 미루다가 이제서야 하게 되었는데, 구체적으로 어떠한 부분이 개선이 필요한지 알려주기도 하고 하나하나 개선해나가면서 점수가 오르는 모습을 보며 뿌듯했다
아!! 그리고 next가 정말 많은 것들 해주고 있구나를 깨달았다 특히 이미지 부분
또 크게 신경쓰지 않았던, 그리고 말로만 외쳤던 접근성과 성능을 눈에 보이는 수치로 개선을 하니 이제야 와닿고 속이 시원하다.
html 태그도 무지성 남발했는데 이제는 정말로 그러한 것들을 고려한 태그를 작성할 수 있을 것 같다
프론트엔드와 접근성은 뗄레야 뗄 수 없는 관계인 만큼 앞으로도 꾸준히 성능과 접근성을 고려한 코드를 짤 것이다.
🦉references:
https://velog.io/@iberis/%EC%9B%B9-%EC%B5%9C%EC%A0%81%ED%99%94-Lighthouse-CLI-%EC%84%B1%EB%8A%A5-%EC%A7%80%ED%91%9C
https://oliveyoung.tech/blog/2023-06-09/nextjs-image-optimization/
https://heycoding.tistory.com/130#4.%20avif%20%ED%8F%AC%EB%A7%B7%20%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0
https://is404notfound.github.io/DevBlogEasyoon/blog/next-js-sharp-image-optimization