[항해 플러스 프론트엔드 3기] 10주차 회고

osohyun0224·2024년 12월 2일
1
post-thumbnail

안녕하세요 프론트엔드 개발자 Garden, 오소현입니다:)
저는 요즘 항해 플러스 프론트엔드 3기 코스 과정에 참여하면서 공부하고 있는데요!
오늘은 그 마지막 발제 10주차의 회고를 진행해보려고 합니다 !

Intro. 과제 전체 회고

이번주차는 지난주에 이어서 실제 간단한 프로젝트로 성능 최적화를 진행하는 주차였습니다. 기본 과제로는 제가 지난주에 사이드 프로젝트로 진행한 것 처럼 간단하게 이미지 최적화, 자체 폰트 호스팅, 등등 라이트 하우스 지표를 통해 점점 성능을 개선해보는 주차였습니다.
심화과제는 3주차에 진행했던것 처럼 리액트 리렌더링 방지,메모제이션으로 dnd 성능을 개선해보는 과제였습니다
개인적으로 마지막 주차 심화과제가 정말 어려워서 눈물이,,, ㅠㅠ

[1] 기본 과제 회고

💁🏻‍♀️ 1. 과제 개요

1.1 과제 목적

  • 이 과제를 통해 Core Web Vitals의 중요성을 이해하고, 웹 성능 최적화를 위한 실질적인 개선 작업을 수행할 수 있습니다.
  • 성능 개선 작업 후, 성능 보고서를 작성하여 개선된 결과를 수치적으로 분석하고 평가할 수 있습니다.
  • 이를 통해 웹사이트의 사용자 경험을 향상시키고, 검색 엔진 최적화(SEO)에도 긍정적인 영향을 미칠 수 있는 기회를 제공합니다.
    최종적으로, 웹 성능 개선의 필요성과 효과를 체감하고, 향후 프로젝트에 적용할 수 있는 경험을 쌓는 것이 목표입니다.

📚 2. 주요 개념 정리

(1) Core Web Vitals

  • Core Web Vitals는 웹사이트의 성능을 측정하는 중요한 지표들이에요. 사용자 경험을 개선하기 위해 꼭 알아야 할 부분이죠. 이 지표들은 페이지 로딩 속도, 상호작용의 반응성, 그리고 레이아웃 안정성을 포함해요.

(2) LCP (Largest Contentful Paint)

  • LCP는 페이지에서 가장 큰 콘텐츠가 로드되는 시간을 측정해요. 이 시간이 짧을수록 사용자에게 더 빠르게 콘텐츠를 보여줄 수 있다는 뜻이에요. 예를 들어, 이미지나 큰 텍스트 블록이 화면에 나타나는 시간을 측정하는 거죠. LCP가 2.5초 이하가 되면 좋은 성능으로 평가받아요.

(3) INP (Input Delay)

  • INP는 사용자가 페이지에서 상호작용할 때의 반응 속도를 측정해요. 버튼 클릭이나 링크 클릭 후 페이지가 반응하는 데 걸리는 시간을 의미해요. 이 시간이 짧을수록 사용자 경험이 좋아지죠. 즉, 사용자가 원하는 작업을 얼마나 빠르게 수행할 수 있는지를 나타내는 지표예요.

(4) CLS (Cumulative Layout Shift)

  • CLS는 페이지 로딩 중 레이아웃이 얼마나 안정적인지를 측정해요. 페이지가 로드되는 동안 요소들이 이동하면 사용자에게 혼란을 줄 수 있어요. 예를 들어, 이미지가 로드되면서 다른 콘텐츠가 밀려나는 경우가 이에 해당하죠. CLS 점수가 0.1 이하일 때 안정적이라고 평가받아요.

3. 🔴 과제 초기 성능 상태

AS - ISTO - BE
성능62
접근성82
권장사항93
검색엔진 최적화45
AS-IS TO-BE
FCP: 1.9초 FCP: 0.4초
LCP: 1.9초 LCP: 0.8초
TBT: 910밀리초 TBT: 0밀리초
CLS: 0.516 CLS: 0.001
Speed Index: 2.7초 Speed Index: 0.5초

🟡 lighthouse 초기 성능 상태

지표초기 값점수 등급
성능 점수62🔴 낮음 (0-49)
접근성82🟡 중간 (50-89)
권장사항93🟢 높음 (90-100)
검색엔진 최적화45🔴 낮음 (0-49)

🟢 Core Web Vitals 세부 지표

지표상태
Largest Contentful Paint (LCP)14.63초🔴 불량 (목표: 2.5초 이하)
First Contentful Paint (FCP)N/A🟢 개선 필요 (목표: 1.8초 이하)
Cumulative Layout Shift (CLS)0.011🟢 양호 (목표: 0.1 이하)

개선 전 초기 성능 분석

앞선 결과로 인해 LCP(Largest Contentful Paint)와 검색엔진 최적화(SEO) 개선에 우선순위를 두고 성능 최적화 작업을 진행해야 할 예정입니다.

특히, LCP가 14.63초로 불량 상태이므로 이미지 최적화, 레이아웃 변경을 최소화, 렌더링 차단 리소스를 제거,자바스크립트 실행 최적화, 서버 응답 시간 개선 등이 주요 개선 작업의 목표가 될 것 같습니다.

🔗 4. 과제 링크

🔥 5. 성능 개선 과정 및 결과

5.1 전체적인 수치화된 표로 먼저 확인하기

개선 항목개선 이유개선 방법향상된 지표
서비스 Hero 이미지 최적화 1차이미지 리사이징서비스 내 큰 이미지들에 대해 비율 맞춰 리사이징이미지 요청 용량이 줄어듦
제품 이미지 형식 변환 최적화LCP 개선JPG에서 WebP, avif 형식으로 변환1220KiB 절감
Google Heebo 폰트 자체 호스팅FCP, CLS 개선ttf 파일이 아닌 최적화에 좋은 woff2 파일로 사용용량 절감
제품 이미지 내부 Lazy Loading첫 초기 로딩 시간 단축이미지 요소에 loading='lazy' 옵션 추가사용자가 필요한 이미지만 로드
태그 사용반응형 이미지 최적화다양한 크기와 이미지가 깨지지 않도록 제공다양한 화면 크기에 맞춰 적절한 이미지 소스를 선택할 수 있음
중요하지 않은 스크립트 지연 로딩초기 로딩 속도 개선중요하지 않은 스크립트 지연 로딩TBT 350ms → 720ms로 증가
국가 배너 레이아웃 시프트 제거랜더링 속도 최적화국가 배너 렌더링 로직 최적화페이지 로드 시
안정성 향상
명시적 이미지 크기 설정CLS 개선, 레이아웃 안정화모든 이미지에 width와 height 속성 추가레이아웃 시프트 방지
제품 로딩 및 무거운 연산 최적화 1차TBT 개선, 전반적 성능 향상로딩 최적화, 무거운 연산 개선사용자의 지연 대폭 감소
이미지 사이즈 명확히 지정접근성 지표 향상제품 데이터 로딩 최적화, 무거운 연산 개선사용자의 지연 대폭 감소
파비콘 이슈 대응접근성 지표를 떨어뜨리는 네트워크 에러 대응파비콘 아이콘 추가오류를 해결해 접근성 지표 향상
포그라운드 색상의 대비율 최적화접근성 지표 향상포그라운드 색상의 대비율 최적화접근성 지표 향상
cookieconsent 스크립트 대응접근성 지표 향상cookieconsent 스크립트를 올바르게 로드 할 수 있도록 추가접근성 지표 향상
메타 태그 추가하여 SEO 개선SEO 지표 향상메타 태그 추가SEO 지표 향상
서비스 이미지 최적화 2차이미지 형식 변환WebP로 변환되었던 이미지를 더 최적화된 AVIF로 변환1280KiB 절감
특정 요소가 뷰포트에 들어올 때만 이벤트를 발생시키는 방식을 도입하여 최적화초기 로딩 속도 개선특정 요소가 뷰포트에 들어올 때만 이벤트를 발생시키는 방식을 도입TBT 320ms → 700ms로 증가

5.2 자세한 개선 사항 보고

5.2.1 이미지 형식 변환으로 최적화 (jpg → webp → avif)

최적화 개요
WebP 및 AVIF와 같은 차세대 이미지 형식은 전통적인 PNG나 JPEG 형식에 비해 훨씬 높은 압축률을 제공합니다. 이로 인해 파일 크기가 줄어들어 다운로드 속도가 빨라지고, 데이터 소비량이 감소하여 사용자 경험이 향상됩니다.

변환 과정
저는 기존의 jpg 형식 이미지를 먼저 webp 형식으로 변환한 후, 추가적으로 AVIF 형식으로 변환하여 최적화를 완료했습니다. WebP는 JPEG보다 약 30% 더 작은 크기를 제공하고, AVIF는 WebP보다도 더 높은 압축률을 자랑합니다.

How to?
이미지 파일을 압축하고, viewport에 맞게 이미지 크기를 조정함으로써 페이지 로딩 속도를 개선했습니다.

AS-ISTO-BE개선율
LCP: 14.4초10.0초⬇️ 4.4초
TBT: 910 밀리초700 밀리초⬇️ 210 밀리초
이미지 리소스 1,240KiB330 KiB⬇️ 910 KiB

5.2.2 폰트 최적화 - 폰트 자체 호스팅 (woff2)

최적화 개요
기존에는 구글 폰트 링크를 통해 외부에서 폰트를 불러왔으나, 성능 최적화를 위해 폰트를 자체 호스팅하는 방식으로 변경했습니다. 특히, woff2 파일 형식은 ttf 형식에 비해 더 높은 압축률을 제공하여 파일 크기를 줄이고, 로딩 시간을 단축시킵니다.

변환 과정
저는 woff2 형식으로 변환하여 폰트 자체 호스팅으로 인한 최적화를 완료했습니다. 이 과정에서 폰트의 품질은 유지되면서도 파일 크기가 현저히 감소했습니다.

How to?
폰트를 변환한 후, 웹 서버에 호스팅하여 CSS 파일에서 직접 참조하도록 설정했습니다. 이를 통해 외부 요청을 줄이고, 페이지 로딩 속도를 개선했습니다.

5.2.3 이미지 내부 Lazy Loading

최적화 개요
중요한 리소스의 로드가 모두 완료된 후, 오프스크린 및 숨겨진 이미지를 지연 로드하는 것은 페이지의 상호작용 시작 시간을 줄이는 데 도움을 줍니다. 이를 통해 사용자는 페이지가 더 빠르게 반응하는 느낌을 받을 수 있습니다.

변환 과정
저는 <picture> 태그를 사용하여 다양한 화면 크기에 맞는 이미지를 조건부로 로드하도록 설정했습니다. 각 <source> 태그의 media 속성을 선언하여 특정 화면 크기에 맞는 이미지만 불러오도록 변경했습니다.

How to?
아래와 같이 <picture> 태그를 사용하여 이미지 소스를 설정했습니다.

<picture>
  <source media="(max-width: 575px)" srcset="images/Hero_Mobile.avif">
  <source media="(min-width: 576px) and (max-width: 960px)" srcset="images/Hero_Tablet.avif">
  <img src="images/Hero_Desktop.avif" alt="Hero">
</picture>
AS-ISTO-BE
LCP: 9.1초4.5초
불필요한 리소스 : 2,800KiB0 KiB

이렇게 구현함으로써 페이지의 로딩 성능을 크게 개선할 수 있었습니다. 지연 로드를 통해 사용자가 실제로 필요로 하는 이미지 리소스만을 로드하게 되어, 전체적인 사용자 경험과 성능을 향상했습니다.

5.2.4 중요하지 않은 스크립트 지연 로딩

최적화 개요
페이지의 첫 페인트를 차단하는 리소스는 사용자 경험에 부정적인 영향을 미칩니다. 이를 해결하기 위해 중요한 JavaScript 및 CSS를 인라인으로 전달하고, 중요하지 않은 모든 JavaScript 및 스타일을 지연 로드하였습니다.

개선 방식
저는 Google Tag Manager(GTM)와 쿠키 동의 스크립트에 defer 속성을 추가하여 스크립트가 비동기적으로 다운로드되도록 했습니다. HTML 파싱이 완료된 후에 스크립트가 실행되어 렌더링 차단을 최소화했습니다.

How to?
아래와 같이 스크립트에 defer 속성을 추가하여 구현했습니다.

<script defer type="text/javascript" src="//www.freeprivacypolicy.com/public/cookie-consent/4.1.0/cookie-consent.js" charset="UTF-8"></script>
<script defer type="text/javascript" charset="UTF-8">
document.addEventListener("DOMContentLoaded", function () {
cookieconsent.run({
notice_banner_type: "simple",
consent_type: "express",
palette: "light",
language: "en",
page_load_consent_levels: ["strictly-necessary"],
notice_banner_reject_button_hide: false,
preferences_center_close_button_hide: false,
page_refresh_confirmation_buttons: false,
website_name: "Performance Course",
});
});
</script>
AS-ISTO-BE
LCP: 8.1초3.5초

결국 렌더링 차단 리소스를 제거하고, 필요한 리소스만을 그때 로드하여 웹 성능을 올렸습니다.

5.2.5 웹 접근성 및 SEO 최적화

  1. 제목 요소를 내림차순으로 표시

    최적화 개요
    heading 태그는 내림차순으로 표시되어야 한다. 건너뛰는 단계 없이 순차적으로 나타나야 합니다. 이는 검색 엔진 최적화(SEO)와 사용자 경험을 향상시키는 데 중요한 요소이다.

    개선 방식: 각 정보에 맞는 마크업을 내림차순으로 표시했습니다.
    AS-IS

    <h5>Some Subheading</h5>
    <h4>Another Subheading</h4>
    <h3>Main Heading</h3>
    <p>Some paragraph text.</p>

    TO-BE

    <h2>Main Heading</h2>
    <p>Some paragraph text.</p>

    | 검색엔진 최적화 | 81점 | 92점 |

  2. 이미지 alt 속성 지정

    최적화 개요
    alt 속성은 사용자가 느린 네트워크 환경이나 이미지 로드 오류, 시각 장애인을 위한 스크린 리더 사용 등 다양한 이유로 이미지를 볼 수 없을 때 대체 정보를 합니다 접근성과 검색 엔진 최적화를 위해 필수적으로 지정해야하는 옵션입니다.

    개선 방식: img 태그를 사용할 때 alt 속성을 필수적으로 선언했습니다.
    AS-IS

    <img src="images/vr1.avif">

    TO-BE

    <img src="images/vr1.avif" alt="Apple Headset">
    접근성 점수82점92점
    검색엔진 최적화 점수82점94점
  3. 메타 설명 추가

    최적화 개요
    <meta name="description"> 요소는 검색 엔진이 검색 결과에 포함하는 페이지 콘텐츠의 요약을 제공하고 있습니다. 고품질의 고유한 메타 설명을 사용하면 페이지의 관련성을 높이고 검색 트래픽을 늘릴 수 있어요.

    개선 방식: 서비스에 대한 설명을 서술적으로 메타 정보에 추가했습니다.
    AS-IS

    TO-BE

    <meta name="description" content="Discover top-quality VR headsets from leading brands. Shop our best-selling virtual reality devices for immersive gaming and entertainment experiences." />

    검색엔진 최적화 점수 | 92점 | 100점 |

  1. 백그라운드 및 포그라운드 색상의 대비율 조정
    글자색과 배경색의 대비가 떨어지면 가독성에 문제가 생길 수 있습니다. 따라서 적절한 색상 대비를 유지하는 것이 중요합니다.

    개선 방식: 색상 대비 분석기 사이트를 사용하여 백그라운드에 맞는 대비 색상을 찾아 수정했습니다.
    AS-IS

    body {
      background-color: #f0f0f0;
      color: #ccc;
    }

    TO-BE

    body {
      background-color: #ffffff;
      color: #333333;
    }

    | 접근성 점수 | 92점 | 100점 |

    앞선 개선 사항을 통해 웹 페이지의 접근성과 검색 엔진 최적화를 크게 향상시킬 수 있었습니다. 각 요소의 최적화는 사용자 경험을 개선하고, 검색 엔진에서의 가시성을 높이는 데 기여했습니다!

📸 6. 프로젝트 배포

  1. 코드 변경 및 커밋:
    개발자는 로컬 환경에서 코드를 수정하고, 변경 사항을 Git의 main 브랜치에 커밋합니다.

  2. 푸시(Push):
    커밋된 변경 사항을 원격 저장소(GitHub 등)의 main 브랜치에 푸시합니다.

  3. GitHub Webhook:
    Vercel은 GitHub와 연결되어 있으며, main 브랜치에 푸시가 발생하면 GitHub에서 Vercel로 웹훅(Webhook) 알림을 보냅니다.

  4. Vercel의 배포 프로세스 시작:
    Vercel은 웹훅 알림을 수신하고, 해당 브랜치의 최신 코드를 가져옵니다.
    Vercel은 자동으로 빌드 프로세스를 시작합니다. 이 과정에서 Next.js 애플리케이션이 빌드되고, 필요한 종속성이 설치됩니다.

  5. 배포:
    빌드가 완료되면 Vercel은 새로운 버전을 생성하고, 이를 배포합니다.
    배포가 완료되면 Vercel은 새로운 URL을 생성하거나 기존 URL을 업데이트하여 사용자가 최신 버전의 애플리케이션에 접근할 수 있도록 합니다.

📚 7. 결론

  • PageSpeed Insights 점수를 42에서 98로 크게 개선하는 성과를 이루었습니다!
  • CP (Largest Contentful Paint) 또한 눈에 띄게 개선되었습니다. 초기 LCP가 14.4초에서 10.0초로 단축되어, 4.4초의 개선을 이루었습니다. 이는 사용자들이 콘텐츠를 더 빠르게 로드할 수 있게 되어, 체감 로딩 속도가 크게 향상되었음을 의미합니다.
  • TBT (Total Blocking Time) 역시 긍정적인 변화를 보였습니다. 초기 TBT가 910 밀리초에서 700 밀리초로 줄어들어, 210 밀리초의 개선을 진행하였습니다!
  • 이미지 최적화, 특히 구체적인 크기 지정이 성능 개선에 큰 영향을 미칠 수 있음을 확인했습니다.
  • 특히 이미지 리소스의 최적화가 중요하였습니다. 이미지 리소스의 크기가 1,240KiB에서 330KiB로 줄어들어, 910KiB의 절감 효과를 냈습니다!

2. 심화 과제 회고

1️⃣ API 호출 부분 최적화

구분평균 소요 시간최소 시간최대 시간평균 응답 시간최대 응답 시간성능 개선율
개선 전215.0 ms210 ms218 ms215.0 ms218 ms-
개선 후113.0 ms89 ms125 ms113.0 ms125 ms47.4% ⬇️

클로저를 사용하여 API 호출 결과를 캐시하고, 이후 호출 시 캐시된 데이터를 반환하도록 변경했습니다. 클로저는 아래와 같이 구현했어요!

const createCachedApiFetcher = <T>(apiCall: () => Promise<AxiosResponse<T>>) => {
  let cachedDataPromise: Promise<T> | null = null;
  return () => {
    if (cachedDataPromise) {
      return { data: cachedDataPromise };
    }
    const apiResponse = apiCall();
    cachedDataPromise = apiResponse.then((response) => response.data);
    return apiResponse;
  };
};

아래 사진으로 네트워크 탭에서 개선 된 호출은 1번 씩만 호출 되는 것을 알 수 있습니다 :)


2️⃣ 불필요한 연산, 랜더링 방지

SearchDialog의 불필요한 연산을 방지하기 위해서 아래와 같은 방법을 도입해 최적화하고자 노력했습니다 :)

1) useMemo를 통한 메모이제이션

강의 목록을 계산하고 , 강의 목록에서 전공을 추출하는 로직에, useMemo를 사용하여 page, lectures가 변경될 때만 재계산하도록 최적화하였습니다!

  const visibleLectures = useMemo(
    () => filteredLectures.slice(0, page * PAGE_SIZE),
    [filteredLectures, page]
  );
  
  ..
    const allMajors = useMemo(
    () => [...new Set(lectures.map((lecture) => lecture.major))],
    [lectures]
  );

2) useCallback을 통한 함수 메모이제이션

addSchedule 함수가 handleAddSchedule, searchInfo, onClose에 의존하여 메모이제이션 되도록 했습니다. 동일한 함수 인스턴스를 재사용하여 불필요한 재생성을 방지했어요!

  const addSchedule = useCallback(
    (lecture: Lecture) => {
      // ...
    },
    [handleAddSchedule, searchInfo, onClose]
  );

3) useEffect를 통한 상태 초기화 최적화

useEffect를 사용하여 searchInfo가 변경될 때만 상태를 초기화하게 하게 끔 구현해줬어요 상위 컴포넌트에서 랜더링해도 상태를 변경하지 않도록 구현해줬습니다..

  useEffect(() => {
    setInitialState({
      days: searchInfo?.day ? [searchInfo.day] : [],
      times: searchInfo?.time ? [searchInfo.time] : [],
    });
    setPage(1);
  }, [setInitialState, searchInfo]);

4) useFetchLectures와 useLectureFilter 커스텀 훅으로 리팩토링

useFetchLectures 훅으로 강의 데이터를 가져오고, useLectureFilter 훅을 사용하여 필터링 로직을 분리했는데 이 각 커스텀 훅에도 캐시를 사용하게 해서 가져오거나, useCallback, useMemo를 사용해서 불필요한 연산을 최대한 막도록 구현해보았습니다!

따라서 다음과 같이 불 필요한 연산이 개선되어서 최초에 한 번 검색한 이후에는 검색을 다시 하지 않도록 되었습니다 :)


3️⃣ 드래그 앤 드롭 랜더링 최적화

저는 드래그 앤 드롭 기능에 랜더링 최적화를 위하여 DragLectureBlock 컴포넌트로 따로 리팩토링하였고, 이 컴포넌트 내에서 memo 사용하고, useDraggable 훅을 사용하여 드래그 가능한 요소를 설정해 상태를 제어할 수 있도록 했습니다. 이 과정에서 사용하는 컴포넌트에 다 메모제이션을 우선 추가해주었습니다.

그리고 해당 컴포넌트를 사용하는 상위 컴포넌트인 ScheduleTables에서 useDndMonitor를 사용하여 드래그 앤 드롭 이벤트를 드래그 시작 및 종료 시에만 상태를 업데이트하여 불필요한 렌더링을 방지할 수 있도록 하였습니다.

따라서 개선 전에는 97ms -> 개선 후에는 39ms 로 개선되었습니다!

[2] 과제 결과

마지막 과제인 기본, 심화 과제 둘다 best를 받을 수 있엇습니다 ㅠㅠ
이번주에는 해먼드코치님께 받았는데 진짜 최고였습니다,,, 역시 항플의 연예인 코치님,,,!!

[4] 마무리

항해의 마지막 과제였던 만큼 힘들었지만 정말 아쉬웠습니다..
마무리는 전체 회고로 작성해보겠습니다!

[5] 항해 플러스 다음 기수 합류하기

저는 현재 항해 프로그램을 마쳤지만 10주간 아주 불태웠는데요!
바로 다음 기수인 항해 플러스 프론트엔드 코스 4기를 모집하고 있다고 하여 공유드립니다!

저도 3기 입과할때 슈퍼 얼리버드 기간에 합류해 추천인 할인까지해서 제일 최대 할인된 가격에 합류할 수 있었습니다 ㅎㅎ

또한 추천인 제도로 [추천인] 코드에 “fWHY9o”를 입력하시면 20만 원 추가 할인 혜택이 있으니 결제하실때 꼭 추천인 할인 코드도 함께 입력해주세요!

제 항해 플러스 프론트엔드 후기 글을 보고 궁금한 사항이 있으시다면 댓글이나, 벨로그 프로필 이메일, 링크드인으로 문의주세요 :)

profile
Garden / Junior Frontend Developer

0개의 댓글