Upgrade to Pro — share decks privately, control downloads, hide ads and more …

웹 반응성 개선하기

kakao
PRO
December 08, 2022

웹 반응성 개선하기

#성능 #Responsiveness #Lighthouse #UserFlow #웹성능

웹 성능은 주로 로드 성능에 집중되었지만 웹의 역할이 커지면서 사용자 상호작용에 대한 성능도 중요해졌습니다. 웹이 로드 된 이후 사용자와 상호작용에 관련된 성능을 측정하고 개선하는 방법을 알아봅니다.

발표자 : ethan.eunwu
카카오 FE플랫폼팀에서 FE개발자를 위한 개발을 하는 에단입니다.

kakao
PRO

December 08, 2022
Tweet

More Decks by kakao

Other Decks in Programming

Transcript

  1. -JHIUIPVTF6TFSGMPXܳࢎਊೠਢ߈਽ࢿѐࢶӝ $PQZSJHIU,BLBP$PSQ"MMSJHIUTSFTFSWFE3FEJTUSJCVUJPOPSQVCMJDEJTQMBZJTOPUQFSNJUUFEXJUIPVUXSJUUFOQFSNJTTJPOGSPN,BLBP ਢ߈਽ࢿѐࢶೞӝ ߅਷਋&UIBOFVOXV ஠஠য় JG LBLBP 

  2. 1. 웹 성능이란? 2. 웹 반응성 3. 웹 반응성 지표

    4. 웹 반응성 측정 방법 5. 웹 반응성 개선 사례
  3. 1. 웹 성능이란? 2. 웹 반응성 3. 웹 반응성 지표

    4. 웹 반응성 측정 방법 5. 웹 반응성 개선 사례
  4. None
  5. Real User Monitoring Synthetic Monitoring Kakao의 성능 측정

  6. 성능 개선 지원 성능 개선 가이드 성능 개선 지원

  7. 페이지 로드 이후

  8. 페이지 로드 이후 Load Interaction

  9. 페이지 로드 이후 페이지 전환 페이지 전환 페이지 전환 인터렉션

    인터렉션 인터렉션 블로그 지도
  10. 1. 웹 성능이란? 2. 웹 반응성 3. 웹 반응성 지표

    4. 웹 반응성 측정 방법 5. 웹 반응성 개선 사례
  11. 웹 반응성 Poor Responsiveness Good Responsiveness

  12. 70% 2x 사용자 사용량 일주일에 한번 이상 끔찍한 반응성을 경험

    반응성이 나쁜 페이지보다 좋은 페이지 사용량이 2배 이상 웹 반응성 현황 출처: Chrome Data
  13. 사용자 입력으로부터 100ms 이내 반응 RAIL 모델 Response 성능 목표

  14. 1. 웹 성능이란? 2. 웹 반응성 3. 웹 반응성 지표

    4. 웹 반응성 측정 방법 5. 웹 반응성 개선 사례
  15. INP TBT CLS Total Blocking Time Interaction to Next Paint

    Cumulative Layout Shift 웹 반응성 지표
  16. TBT (Total Blocking Time) - Long Task : 메인 스레드에서

    50ms 이상 실행되는 작업 #MPDLJOH5JNF-POH5BTL઺NTܳઁ৻ೠݫੋझۨ٘੼ਬदр 아래 그림에서 Long Task는 총 3개, TBT는 200 + 40 + 105 = 345 Main thread timline (task blocking time) 200 40 105
  17. 사용자 입력이 발생하고 화면의 변화가 생길때까지의 시간 INP (Interaction to

    Next Paint) pointerup mouseup click Blocking tasks Procesing time Render Paint Compositing, GPU, and raster Procesing time Frame presented! Input received Input delay INP
  18. 사용자 입력이 발생하고 화면의 변화가 생길때까지의 시간 INP (Interaction to

    Next Paint) pointerup mouseup click Blocking tasks Procesing time Render Paint Compositing, GPU, and raster Procesing time Frame presented! Input received Input delay
  19. 사용자 입력이 발생하고 화면의 변화가 생길때까지의 시간 INP (Interaction to

    Next Paint) pointerup mouseup click Blocking tasks Procesing time Render Paint Compositing, GPU, and raster Procesing time Frame presented! Input received Input delay
  20. 사용자 입력이 발생하고 화면의 변화가 생길때까지의 시간 INP (Interaction to

    Next Paint) pointerup mouseup click Blocking tasks Procesing time Render Paint Compositing, GPU, and raster Procesing time Frame presented! Input received Input delay
  21. 페이지 전체 수명동안 발생하는 예기치 않은 레이아웃 이동 점수 CLS

    (Cumulative Layout Shift)
  22. 1. 웹 성능이란? 2. 웹 반응성 3. 웹 반응성 지표

    4. 웹 반응성 측정 방법 5. 웹 반응성 개선 사례
  23. 특정 환경에서 로드 성능을 포함한 다양한 성능을 측정합니다. Lighthouse User

    fl ow
  24. Snapshot Navigation Timespan 단일 페이지의 로드 성능 측정 사용자 인터렉션

    후 페이지의 상태 측정 임의의 시간동안 사용자 인터렉션 측정 Lighthouse User fl ow
  25. - 단일 페이지 로드 분석 - LCP와 Speed Index와 같은

    페이지 로드 성능 점수 제공 카테고리 - Performance - Accessibility - Best Practice - SEO - PWA Navigation 인터렉션 인터렉션 인터렉션 측정범위
  26. - 사용자 인터렉션 이후 특정 상태의 페이지 분석 - SPA나

    복잡한 폼의 접근성 이슈 확인 카테고리 - Performance - Accessibility - Best Practice - SEO Snapshot 인터렉션 인터렉션 인터렉션 측정범위
  27. - 임의의 시간동안 사용자 인터렉션 분석 - 수명이 긴 페이지,

    SPA 에서 성능 개선 포인트 제공 카테고리 - Performance - Best Practice - SEO Timespan 인터렉션 인터렉션 인터렉션 측정범위
  28. import puppeteer from 'puppeteer'; import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; const browser

    = await puppeteer.launch({headless: false}); const page = await browser.newPage(); const flow = await startFlow(page, {name: 'daum'}); await flow.startTimespan({stepName: 'routing'}); await page.goto("https://m.daum.net/"); await page.click("#mArticle > nav:nth-child(2) > li.news1 > a > span") await page.click("#mArticle > nav:nth-child(2) > li.sports > a > span") await page.click("#mArticle > nav:nth-child(2) > li.enter > a > span") await flow.endTimespan(); await browser.close(); const report = await flow.generateReport(); const json = await flow.createFlowResult();
  29. import puppeteer from 'puppeteer'; import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; const browser

    = await puppeteer.launch({headless: false}); const page = await browser.newPage(); const flow = await startFlow(page, {name: 'daum'}); await flow.startTimespan({stepName: 'routing'}); await page.goto("https://m.daum.net/"); await page.click("#mArticle > nav:nth-child(2) > li.news1 > a > span") await page.click("#mArticle > nav:nth-child(2) > li.sports > a > span") await page.click("#mArticle > nav:nth-child(2) > li.enter > a > span") await flow.endTimespan(); await browser.close(); const report = await flow.generateReport(); const json = await flow.createFlowResult();
  30. import puppeteer from 'puppeteer'; import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; const browser

    = await puppeteer.launch({headless: false}); const page = await browser.newPage(); const flow = await startFlow(page, {name: 'daum'}); await flow.startTimespan({stepName: 'routing'}); await page.goto("https://m.daum.net/"); await page.click("#mArticle > nav:nth-child(2) > li.news1 > a > span") await page.click("#mArticle > nav:nth-child(2) > li.sports > a > span") await page.click("#mArticle > nav:nth-child(2) > li.enter > a > span") await flow.endTimespan(); await browser.close(); const report = await flow.generateReport(); const json = await flow.createFlowResult();
  31. import puppeteer from 'puppeteer'; import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; const browser

    = await puppeteer.launch({headless: false}); const page = await browser.newPage(); const flow = await startFlow(page, {name: 'daum'}); await flow.startTimespan({stepName: 'routing'}); await page.goto("https://m.daum.net/"); await page.click("#mArticle > nav:nth-child(2) > li.news1 > a > span") await page.click("#mArticle > nav:nth-child(2) > li.sports > a > span") await page.click("#mArticle > nav:nth-child(2) > li.enter > a > span") await flow.endTimespan(); await browser.close(); const report = await flow.generateReport(); const json = await flow.createFlowResult();
  32. import puppeteer from 'puppeteer'; import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; const browser

    = await puppeteer.launch({headless: false}); const page = await browser.newPage(); const flow = await startFlow(page, {name: 'daum'}); await flow.startTimespan({stepName: 'routing'}); await page.goto("https://m.daum.net/"); await page.click("#mArticle > nav:nth-child(2) > li.news1 > a > span") await page.click("#mArticle > nav:nth-child(2) > li.sports > a > span") await page.click("#mArticle > nav:nth-child(2) > li.enter > a > span") await flow.endTimespan(); await browser.close(); const report = await flow.generateReport(); const json = await flow.createFlowResult();
  33. Chrome Devtools Recorder

  34. { "title": "daum", "steps": [ { "type": "navigate", "url": "https://m.daum.net/"

    }, { "type": "click", "target": "main", "selectors": [ [ "aria/઱ਃ ࢲ࠺झ ݫ׏ ಟ஖ӝ नӏ ࣗधঌܿ", "aria/[role=\"generic\"]" ], [ "#mArticle > nav:nth-child(2) > div > button > span" ] ]
  35. { "title": "daum", "steps": [ { "type": "navigate", "url": "https://m.daum.net/"

    }, { "type": "click", "target": "main", "selectors": [ [ "aria/઱ਃ ࢲ࠺झ ݫ׏ ಟ஖ӝ नӏ ࣗधঌܿ", "aria/[role=\"generic\"]" ], [ "#mArticle > nav:nth-child(2) > div > button > span" ] ]
  36. Chrome Devtools Recorder Type Property Required Description click, doubleClick offsetX,

    offsetY O 요소의 좌상단 기준 change value O 최종 값 keyDown, keyUp key O 키 이름 scroll x, y 절대 스크롤 x, y 위치값 navigate url O 대상 URL waitForElement operator >= | == (default) | <= waitForElement count 선택기로 식별되는 요소의 수 waitForExpression expression O Javascript 표현식
  37. Chrome Devtools Recorder Extension Puppeteer Cypress Nightwatch

  38. import puppeteer from 'puppeteer'; import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; const browser

    = await puppeteer.launch({headless: false}); const page = await browser.newPage(); const flow = await startFlow(page, {name: 'daum'}); await flow.startTimespan({stepName: 'routing'}); await page.goto("https://m.daum.net/"); await page.click("#mArticle > nav:nth-child(2) > li.news1 > a > span") await page.click("#mArticle > nav:nth-child(2) > li.sports > a > span") await page.click("#mArticle > nav:nth-child(2) > li.enter > a > span") await flow.endTimespan(); await browser.close(); const report = await flow.generateReport(); const json = await flow.createFlowResult();
  39. import puppeteer from 'puppeteer'; import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; const browser

    = await puppeteer.launch({headless: false}); const page = await browser.newPage(); const flow = await startFlow(page, {name: 'daum'}); await flow.startTimespan({stepName: 'routing'}); await flow.endTimespan(); await browser.close(); const report = await flow.generateReport(); const json = await flow.createFlowResult();
  40. import puppeteer from 'puppeteer'; import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; import {createRunner,

    PuppeteerRunnerExtension} from '@puppeteer/replay'; const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); const flow = await startFlow(page, {name: 'daum'}); await flow.startTimespan({stepName: 'routing'}); await flow.endTimespan(); await browser.close(); const report = await flow.generateReport(); const json = await flow.createFlowResult();
  41. import puppeteer from 'puppeteer'; import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js'; import {createRunner,

    PuppeteerRunnerExtension} from '@puppeteer/replay'; const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); const flow = await startFlow(page, {name: 'daum'}); await flow.startTimespan({stepName: 'routing'}); const runner = await createRunner(readJSON(‘/recording.json'), new PuppeteerRunnerExtension(browser, page)); await runner.run(); await flow.endTimespan(); await browser.close(); const report = await flow.generateReport(); const json = await flow.createFlowResult();
  42. Lighthouse User fl ow 의 timespan 모드 리포트

  43. 1. 웹 성능이란? 2. 웹 반응성 3. 웹 반응성 지표

    4. 웹 반응성 측정 방법 5. 웹 반응성 개선 사례
  44. TBT (Total Blocking Time)

  45. 챗봇 주문

  46. 챗봇 주문 ◼︎ TBT ◼︎ INP • CLS 300 ms

    350 ms 0 TBT Total Blocking Time GOOD NEEDS IMPROVEMENT POOR 200 600
  47. 챗봇 주문 화면 전환시 강제 re fl ow 발생

  48. Re fl ow (Critical Rendering Path) Composite Paint Layout Style

    Javascript
  49. Re fl ow (Critical Rendering Path) Composite Paint Layout Style

    Javascript
  50. Javascript Layout Style Re fl ow (Critical Rendering Path) Composite

    Paint Layout Style Javascript Javascript Javascript
  51. 챗봇 주문 프로젝트 개선전/후

  52. 챗봇 주문 ◼︎ TBT ◼︎ INP • CLS 300 ms

    350 ms 0 TBT Total Blocking Time GOOD NEEDS IMPROVEMENT POOR 200 600
  53. 챗봇 주문 • TBT • INP • CLS 200 ms

    200 ms 0 TBT Total Blocking Time GOOD NEEDS IMPROVEMENT POOR 200 600 33% 개선
  54. INP (Interaction to Next Paint)

  55. WPM (Web Performance Monitoring)

  56. WPM (Web Performance Monitoring) ▲ TBT ▲ INP • CLS

    960 ms 1,040 ms 0.008 INP Interaction to Next Paint GOOD NEEDS IMPROVEMENT POOR 200 500
  57. 전환 업데이트 긴급 업데이트 WPM (Web Performance Monitoring) React 팀에서

    정의한 상태 업데이트
  58. None
  59. None
  60. WPM 프로젝트 개선/후

  61. WPM (Web Performance Monitoring) ▲ TBT ▲ INP • CLS

    960 ms 1,040 ms 0.008 INP Interaction to Next Paint GOOD NEEDS IMPROVEMENT POOR 200 500
  62. WPM (Web Performance Monitoring) INP Interaction to Next Paint GOOD

    NEEDS IMPROVEMENT POOR 200 500 • TBT • INP • CLS 130 ms 80 ms 0.01 92% 개선
  63. CLS (Cumulative Layout Shift)

  64. 카카오페이 구매

  65. 카카오페이 구매 • TBT • INP ▲ CLS 20 ms

    140 ms 0.259 CLS Cumulative Layout Shift GOOD NEEDS IMPROVEMENT POOR 0.1 0.25
  66. 카카오페이 구매 카카오페이 구매 프로젝트 개선/후

  67. 카카오페이 구매 • TBT • INP ▲ CLS 20 ms

    140 ms 0.259 CLS Cumulative Layout Shift GOOD NEEDS IMPROVEMENT POOR 0.1 0.25
  68. 카카오페이 구매 • TBT • INP • CLS 20 ms

    80 ms 0 CLS Cumulative Layout Shift GOOD NEEDS IMPROVEMENT POOR 0.1 0.25 100% 개선
  69. 끝으로.. 1. 로드 성능은 여전히 중요하지만 반응성도 점점 중요해진다. 2.

    Lighthouse user fl ow로 측정하는 반응성 3. 반응성 지표별 개선 사례 - 블로킹 타임 발생시 강제 리플로우 확인 - 전체 화면 업데이트 발생시 중요한 부분 우선 업데이트 진행 - 레이아웃 시프트는 간단한 작업만으로 쉽게 개선 가능
  70. 끝으로.. 1. 로드 성능은 여전히 중요하지만 반응성도 점점 중요해진다. 2.

    Lighthouse user fl ow로 측정하는 반응성 3. 반응성 지표별 개선 사례 - 블로킹 타임 발생시 강제 리플로우 확인 - 전체 화면 업데이트 발생시 중요한 부분 우선 업데이트 진행 - 레이아웃 시프트는 간단한 작업만으로 쉽게 개선 가능
  71. Next Step - 웹 반응성 측정 자동화 - 웹 반응성

    측정과 e2e 테스트 통합 - 웹 반응성 개선 가이드