Home Blogs Lighthouse v6.0에 맞게 웹 사이트 최적화
Applications

Lighthouse v6.0에 맞게 웹 사이트 최적화

About The Author

Outline

Layer0은 오픈 소스 이커머스 PWA 프레임워크인 React Storefront의 주요 기여자입니다. 올해 초, 우리는 많은 새로운 기능과 최적화를 제공했습니다. React Storefront v7가장 중요한 두 가지는 Next.js로의 전환과 몇 가지 주요 종속성(예: MobX)을 제거하여 useState 후크와 컨텍스트 API와 같은 상태 관리를 위한 React의 새로운 내장 기능을 활용한 것이었습니다. 이로 인해 브라우저 번들 크기가 거의 절반으로 줄어들었습니다.

이는 당시에는 좋은 성과였으며 PageSpeed Insights(PSI)로 측정된 80년대부터 90년대까지의 일반적인 React Storefront 앱에 대한 BUMP Lighthouse(v5.7) 성능 점수를 도왔습니다. 맥락에서 83 + 점수는 Lighthouse v5.7에서 상위 500 전자 상거래 웹 사이트의 99 %를 능가했습니다. Lighthouse v6.0이 폭탄처럼 떨어지고 모든 사람의 성능 점수가 지워지는 몇 달 동안 번들 감소가 얼마나 필수적인지를 깨닫지 못했습니다.

v6.0이 떨어졌을 때 주요 전자 상거래 웹 사이트의 PSI에서 측정된 Lighthouse 점수의 분포가 어떻게 변했는지 알아보십시오.

이 게시물에서는 React Storefront에 대한 Lighthouse v6.0 점수를 개선한 방법을 공유하지만 다른 프레임워크에도 적용 할 수 있습니다.

또한 Google은 2020 년 5 월 28 일, 2021 년에 사이트의 순위를 지정하는 데 사용할 구체적인 지표를 발표했습니다. Lighthouse 성능 점수는 사용되지 않지만 점수를 결정하는 데 사용되는 일부 요소는 사용되며, 심지어 Lighthouse와 같은 합성 테스트를 사용하여 측정되지 않고 Chrome 사용자 경험 보고서(Crux)의 실제 필드 데이터로 측정됩니다.

Lighthouse 6.0의 새로운 지표: TBT, LCP 및 CLS

Lighthouse v6.0은 몇 가지 새로운 지각 속도 메트릭을 도입하고 전체 메트릭이 페이지의 Lighthouse 성능 점수에 미치는 영향을 재구성합니다.

등대 5.7 무게 등대 6.0 무게
첫 번째 내용 페인트(FCP) 20% 첫 번째 내용 페인트(FCP) 15%
속도 지수(SI) 27% 속도 지수(SI) 15%
첫 번째 의미 있는 페인트(FMP) 7% 최대 도색(LCP) 25%
첫 번째 CPU 유휴(FCI) 13% 총 차단 시간(TBT) 15%
대화식 시간(TTI) 33% 대화식 시간(TTI) 15%
- - CLS(Cumulative Layout Shift) 5%

총 차단 시간(TBT)

Total blocking time은 Lighthouse v6.0에 포함된 새로운 메트릭으로, JavaScript의 구문 분석 및 실행이 페이지를 로드하는 동안 메인 스레드를 차단하는 시간을 측정합니다. 이 메트릭은 JavaScript가 많은 현대식 SPA/PWA를 매우 가혹하게 취급합니다. React Storefront 7의 번들 크기가 50% 감소한 경우에도 제품 페이지의 Lighthouse v6.0 성능 점수가 20-30점 감소했습니다. 이는 TBT가 전체 성능 점수의 25%에 영향을 미치는 지표로 포함되었기 때문입니다.

서버측 렌더링을 지원하는 Next.js와 같은 동형 프레임워크를 사용하는 경우, TBT는 대부분 번들 크기와 수화 시간에 따라 결정됩니다. 간단히 말해, TBT를 개선하는 유일한 방법은 종속성을 제거하거나, 구성 요소를 최적화하거나, 구성 요소 수를 줄여 사이트를 단순화하는 것입니다.

LCP(Largest Contentful Paint)

LCP는 전체 Lighthouse v6.0 점수에 비해 25%의 가중치를 갖는 새로운 메트릭입니다. LCP는 가장 큰 콘텐츠 요소가 페인팅을 완료하는 데 걸리는 시간을 관찰하여 사용자가 초기 페이지 로드 성능을 어떻게 인식하는지 수치화하는 것을 목표로 합니다. 대부분의 사이트, 특히 전자 상거래 웹 사이트에서 가장 큰 만족스러운 요소는 영웅 이미지입니다. React Storefront 앱의 제품 페이지의 경우 가장 큰 만족을 주는 요소는 주요 제품 이미지입니다. 귀하의 사이트에 어떤 요소가 있는지 확실하지 않은 경우 PSI가 알려줍니다.

LCP를 최적화하려면 이미지가 가능한 한 빨리 로드되도록 해야 합니다.

CLS(Cumulative Layout Shift)

누적 레이아웃 이동은 초기 페이지 로드 중에 페이지 레이아웃이 이동하는 정도를 측정합니다. 레이아웃 변경은 가장 일반적으로 이미지에 의해 발생하며, 네트워크에서 데이터를 다운로드한 후 이미지에 맞게 크기를 조정할 때 이미지 주위에 요소를 밀어 넣는 경향이 있습니다. 이미지를 로드하기 전에 각 이미지에 대한 공간을 예약하면 레이아웃 이동을 완전히 제거할 수 있습니다. 다행히도 React Storefront의 Image 구성 요소는 이미 이를 수행하므로 React Storefront 스타터 앱은 즉시 0의 완벽한 CLS 점수를 자랑합니다.

가난한 CLS의 다른 일반적인 범인은 페이지가 처음 그려진 후에 나타나는 배너와 팝업입니다. 사용자들은 그들을 싫어하고 이제 라이트하우스도 그렇습니다.

Lighthouse v6.0용 React Storefront 최적화 방법

Lighthouse v6.0에서 React Storefront 스타터 앱의 제품 페이지를 PageSpeed Insights를 사용하여 처음 테스트했을 때 60점 이하의 점수를 받았습니다.

점수를 높이기 위해 먼저 LCP에 대한 시야를 설정했습니다. 2.5초에서 FCP는 매우 높았지만(나중에 다룰 예정), FCP와 LCP 사이의 거의 3초 간격이 개선이 필요한 것으로 나타났습니다.

LCP 최적화

React Storefront의 주요 제품 이미지(자리 표시자 – 중앙에 “Product 1″이 있는 녹색 상자)는 https://via.placeholder.com 를 가리키는 src URL이 있는 HTML img 태그로 렌더링되었습니다. 이 사이트는 괜찮은 TTFB (약 150ms)를 가지고 있으며, Layer0 CDN-as-JavaScript와 같은 더 빠른 CDN으로 이동하여 개선 될 수 있습니다. 그럼에도 불구하고 우리는 150ms가 FCP와 LCP 사이의 거의 3 초 간격을 차지한다는 것을 의심했습니다. 이러한 차이를 없애기 위해 다음과 같은 base64 데이터 URL을 사용하여 이미지를 인라인했습니다.

				
					<img decoding="async" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAA…"/>

				
			

우리는 메인 이미지를 다운로드하여 base64로 변환한 다음, Layer0의 서버리스 자바스크립트 클라우드에서 서버측 렌더링 중에 src 속성에 채우는 방식으로 이 작업을 수행했습니다. 다음은 제품 API 엔드포인트의 줄입니다.

				
					const mainProductImage = result.product.media.full[0]
mainProductImage.src = await getBase64ForImage(mainProductImage.src)
				
			

그리고 여기에 이미지를 가져와서 64진수로 변환하는 코드가 있습니다.

				
					import fetch from 'node-fetch'

export default async function getBase64ForImage(src) {
  const res = await fetch(src)
  const contentType = res.headers.get('content-type')
  const buffer = await res.buffer()
  return `data:${contentType};base64,${buffer.toString('base64')}`
return `data:${contentType};base64,${buffer.toString('base64')}` return `data:${contentType};base64,${buffer.toString('base64')}` return `data:${contentType};base64,${buffer.toString('base64')}`
}

				
			

아주 간단하고 오래된 학교. 점수에 미치는 영향은?

LCP를 5.3초에서 2.8초로 떨어뜨리면서 페이지의 Lighthouse v6.0 점수에서 21점을 얻었습니다! 이러한 작은 변화가 Lighthouse v6.0 점수에 극적으로 영향을 미칠 수있는 방법은 약간 불안하지만, 우리는 그것을 취할 것입니다. 모든 지표는 달리기마다 다소 다르지만 전체 점수는 80 년대에 일관되게 나타났습니다. 맥락에서 보면, PSI에서 측정된 v6.0 점수 87에서 가장 높은 성과를 기록한 주요 전자 상거래 웹 사이트는 90년대에 나온 것으로 보입니다. www.rockauto.com

위에서 보여준 FCP와 LCP 사이의 격차는 우리가 여러 번의 실행에서 보았던 것 만큼이나 큽니다. 대부분의 시간 간격은 100ms ~ 300ms 범위였습니다. 때때로 FCP와 LCP는 동일했습니다.

TBT 최적화

다음으로, 우리는 TBT를 개선하려고 노력했습니다. 이것은 꽤 도전적이었습니다. 앞서 언급했듯이 LCP를 개선하려면 JavaScript 번들의 크기를 줄이거나 수화 시간을 개선해야 합니다. 대부분의 앱에서는 종속성을 제거하여 번들을 더 작게 만드는 것이 불가능합니다. React Storefront 7로 구축된 앱은 Next.js의 웹팩 구성에서 제공하는 번들 크기를 최소화하는 많은 컴파일 타임 최적화와 Material UI 를 위한 특정 바벨 최적화 기능을 이미 활용하고 있습니다. 그렇다면 우리는 어디에서 개선할 수 있을까요? 수분 공급 시간.

승리를 위한 게으른 수분 공급!

다행히 React Storefront 커뮤니티는 Lighthouse v6.0이 떨어지기 전에 이미 게으른 수분 공급을 지원하는 작업을 시작했습니다. 이것은 확실히 우리의 노력을 가속화 시켰습니다.

모르는 경우, Hydration은 React가 서버에서 렌더링된 HTML 요소를 제어하여 상호 작용할 수 있도록 하는 것을 말합니다. 버튼을 클릭할 수 있게 되고 회전식 슬라이드가 회전할 수 있게 됩니다. 페이지에 구성 요소가 많을수록 수분 공급 시간이 길어집니다. 기본 메뉴 및 제품 이미지 캐루젤과 같은 복잡한 구성 요소는 훨씬 더 오래 걸립니다.

게으른 수분 공급은 특정 구성 요소의 수분 공급이 절대적으로 필요하고 가장 중요한 초기 페이지 로드 후(그리고 TBT 계산 후) 지연되는 것을 수반합니다. 게으른 수분 공급은 위험할 수 있습니다. 사용자가 페이지 요소와 상호 작용하기 전에 사용자 입력에 응답할 준비가 되어 있는지 확인해야 합니다.

React Storefront에 게으른 하이드레이션을 구현하는 것은 구성 요소가 하이드레이션된 후에만 문서에 동적으로 스타일을 추가하는 CSS-in-JS에 Material UI가 의존하기 때문에 상당히 어려웠습니다. 다른 시간을 위해 세부 정보를 저장하겠습니다. 결국, 개발자가 컴포넌트 트리의 아무 곳에나 삽입할 수 있는 LazyHydrate 컴포넌트를 구축하여, 뷰포트로 요소를 스크롤하거나 화면을 터치하는 등의 특정 이벤트가 발생할 때까지 수화를 지연시킬 수 있습니다.

다음은 주요 제품 이미지를 표시하는 MediaCarousel에 수분을 공급하는 예입니다.

				
					 import LazyHydrate from 'react-storefront/LazyHydrate'
<LazyHydrate id="carousel" on="touch">
  <MediaCarousel
    ...
  />
</LazyHydrate> 

				
			

우리는 애플리케이션의 여러 영역에 게으른 수분을 적용했는데, 특히 다음과 같습니다.

  • 슬라이드 인 메뉴 : 사용자가 햄버거 버튼을 누르면 수분을 공급합니다.
  • 접기 아래의 모든 컨트롤: 크기, 색상 및 수량 선택기와 제품 정보 탭을 포함합니다.
  • 메인 이미지 캐러셀: 이 메뉴와 메인 메뉴는 아마도 가장 많은 기능을 가진 구성 요소일 것이며, 따라서 수화에 가장 비용이 많이 듭니다.

게으른 수분을 적용한 Lighthouse v6.0 점수는 다음과 같습니다.

게으른 수분 공급은 TBT를 거의 40 % 줄이고 TTI (v6.0에서 점수보다 15 % 체중)를 700ms 줄였습니다. 이는 전체 Lighthouse v6.0 점수에서 6 점 증가를 기록했습니다.

FCP가 약간 올라갔지만 LCP가 내려간 것을 알 수 있습니다. 이러한 작은 변화는 본질적으로 PageSpeed Insights를 실행할 때 얻을 수있는 “소음 내에서”입니다. 모든 점수는 러닝 사이에 약간 변동합니다.

FCP 최적화

위의 점수를 바탕으로, 우리는 FCP 및 / 또는 LCP가 더 개선 될 수 있다고 느꼈다. 스크립트가 렌더링을 차단할 수 있다는 것을 알고 있으므로 Next.js가 스크립트를 문서로 가져오는 방법을 살펴보았습니다.

				
					<scriptsrc="/_next/static/runtime/main-cb5fd281935517c43f30.js"async=""></script>
				
			

여기서 비동기화를 사용하는 것이 최선의 선택이 아닐 수도 있습니다. 렌더링 중에 스크립트를 다운로드하면 스크립트가 평가되는 동안 렌더링을 일시 중지할 수 있으며, 이로 인해 FCP 및 LCP 시간이 모두 증가합니다. async 대신 defer를 사용하면 문서를 그린 후에만 스크립트가 평가됩니다.

안타깝게도 Next.js에서는 스크립트를 가져오는 방법을 변경할 수 없으므로 다음과 같이 NextScript 구성 요소를 확장해야 했습니다.

				
					import React from 'react'
import { NextScript as OriginalNextScript } from 'next/document'
/**
 * A replacement for NextScript from `next/document` that gives you greater control over how script elements are rendered.
 * This should be used in the body of `pages/_document.js` in place of `NextScript`.
 */
export default class NextScript extends OriginalNextScript {
  static propTypes = {
    /**
     * Set to `defer` to use `defer` instead of `async` when rendering script elements.
     */
    mode: PropTypes.oneOf(['async', 'defer']),
  }
  static defaultProps = {
    mode: 'async',
  }
  getScripts() {
    return super.getScripts().map(script => {
      return React.cloneElement(script, {
        key: script.props.src,
        defer: this.props.mode === 'defer' ? true : undefined,
        async: this.props.mode === 'async' ? true : undefined,
      })
    })
  }
}

				
			

그 다음 pages/_document.js에 다음을 추가했습니다.

				
					 <NextScript mode="defer" />
				
			

이 덕분에 LCP와 전체 점수가 개선되었습니다.

또한 많은 실행에서 FCP를 약간 부딪혔지만 “소음” 내에 있을 수 있습니다. 그럼에도 불구하고 연기 vs 비동기 사용 시 전체 점수는 지속적으로 2-3점 높았습니다.

요약

2020년 5월 말에 Lighthouse v6.0이 출시되었을 때 React Storefront 앱을 포함한 많은 사이트의 성능 점수가 급락했습니다. 최적화 이전에는 React Storefront 스타터 앱의 PDP 성능이 60년대 초반에 미치지 못했습니다. 이러한 최적화를 통해 우리는 1990년대 초반의 희귀한 분위기에 빠져들었습니다. 이 시점에서 점수를 더욱 향상시킬 수 있는 유일한 방법은 종속성을 제거하는 것뿐이라고 생각하며, 이는 애플리케이션 성능과 개발자 생산성을 절충하는 것을 의미할 수 있습니다.

그것은 다른 시간에 대한 토론입니다. 우리가 시도한 몇 가지 효과가 없었던 것들을 남겨 드리겠습니다.

Preact

Preact는 번들 크기를 20-30% 작게 만들었지만 Lighthouse v6.0 점수는 TTI를 포함한 모든 메트릭에서 일관되게 나빴습니다. 우리는 이유를 모르지만, 우리는 이것이 Lighthouse v6.0에 새롭거나 독점적이지 않다는 것을 알고 있습니다. Lighthouse v5.7에서도 느려졌습니다. 우리는 정기적으로 체크인을 계속하고 언젠가 이것이 해결되기를 바랍니다.

청킹

next.js는 최근 브라우저 자산의 세분화된 청킹을 도입했습니다. 이것이 Next.js 9.1에서 처음 소개되었을 때, 우리는 더 작은 청크가 실제로 TTI를 악화시킨다는 것을 발견했습니다. 브라우저 캐시를 더 잘 활용할 수 있기 때문에 새 버전이 출시 된 후 앱을 더 빨리 되돌릴 수 있지만 Lighthouse는 그 중 어느 것도 신경 쓰지 않습니다. 따라서 React Storefront는 한동안 브라우저 청크 수를 하나로 제한했습니다.

				
					const webpack = require('webpack')
const withReactStorefront = require('react-storefront/plugins/withReactStorefront')
module.exports = withReactStorefront({
  target: 'serverless',
  webpack: config => {
    config.plugins.push(
      new webpack.optimize.LimitChunkCountPlugin({
        maxChunks: 1,
      })
    )
    return config
  },
})

				
			

웹 글꼴

대부분의 사이트에서는 사용자 지정 웹 글꼴을 사용합니다. 기본적으로 React Storefront는 Roboto를 사용합니다(변경하거나 제거할 수 있음). 사용자 정의 웹 글꼴은 성능을 죽이고, 평범하고 간단합니다. 웹 글꼴을 제거하면 약 1초의 FCP를 얻을 수 있습니다.

분석과 마찬가지로 이해 관계자는 특정 글꼴을 사용하기 위해 성능을 기꺼이 교환하는 것처럼 보입니다. React Storefront는 사이트와 동일한 오리진에서 웹 글꼴을 제공하므로 Google 글꼴과 같은 타사 CDN에서 글꼴을 로드할 때 발생하는 TLS 협상 시간을 없애줍니다. 구체적으로, 우리는 npm의 서체-로봇 패키지를 사용합니다. 웹팩의 css-loader 와 결합하면 서체-roboto 를 사용하면 브라우저가 다운로드하고 구문 분석해야 하는 별도의 CSS 파일을 통해 글꼴을 가져올 수 있습니다. 우리는 문서에 CSS를 인라이닝하면 성능에 도움이 될 수 있다고 생각했지만 그렇지 않았습니다.

성능에 관해서는 항상 측정해야합니다. 이론적으로 작동해야 하는 최적화는 실제로 실행되지 않을 수 있습니다.

라이트하우스 점수를 높이세요

Lighthouse v6.0에서 상위 500대 이커머스 웹 사이트 중 95번째 백분위수를 기록한 고객입니다.

상담식 대화를 예약하십시오!