이전 1차 노력(나이키 리팩토링)에서는 필터조건을 타인에게 공유하는 기능을 유지하면서도 렌더링 횟수를 최소화하기 위한 노력을 하였다.
허나 그러한 나의 노력은 라이트하우스 점수에 아무런 영향도 줄 수 없었다.
약간의 허탈함과 약간의 분노로 라이트하우스에서 제공하는 구체적인 지표를 향상시키기 위한 노력을 기록하였다.
하기부턴 각각의 항목들에 대한 해결 과정 및 그 결과 이다.
이미지 사이즈 최적화 요구.
즉, 서버로부터 가져오는 이미지가 브라우저에서 필요한 스펙보다 오버스펙인 이미지파일을 가져오기에 최적화를 요구하는것이다.
가져오는 이미지의 최적화를 통해 해결 가능.
해당 페이지에서의 이미지 크기는 고정되어있기에 이미지 CDN을 이용하여 적절한 크기의 이미지파일을 요청함으로서 해결이 가능할것이라 판단하였다.
기존 코드
<Image src={image.url} alt="이미지 슬라이드" />
const Image = styled.img`
width: 676px;
height: 415px;
border-radius: 5px;
`;
원본 이미지 URL로 img 태그의 src로 사용
개선 코드
<Image
src={
image.url + getParametersForUnsplash(676, 415, 'auto', 'avif')
}
alt="이미지 슬라이드"
/>
const Image = styled.img`
width: 676px;
height: 415px;
border-radius: 5px;
`;
unsplash 서버에서 이미지를 불러왔기 때문에 unsplash 이미지 cdn 사용하여
적절한 스펙의 이미지파일 요청.
기존
개선 후
불러오는 이미지 사이즈만 이미지 cdn을 사용하여 최적화해주더라도 Performance가 상당히 개선되는것을 확인할 수 있었다. (38 → 73)
해당 페이지를 구성함에 있어서 사용되지 않는 Javascript 파일로 인한 리소스 낭비
사용되지 않는 Javascript 파일 제거 또는 빌드 시 번들링파일 분할을 통해 해결 가능할것이라 판단.
코드 스플리팅이란 webpack 같은 모듈 번들러를 이용하여 만들어진 하나의 번들 파일을 여러개의 번들 파일로 나누는 것을 의미한다.
애초에 모듈 번들러를 사용하게 된 이유는 브라우저에서 호출하는 파일의 개수를 줄여 서버와의 통신에서 발생하는 비용을 감소시키기 위함이었다.
하지만 프로젝트가 커지고 동시에 번들링되는 파일의 크기도 점차 커지게 되어 하나의 번들파일로 내보냇다간 해당 파일을 로드하는 시간이 너무 길어지게 된것이다.
이는 곧 사용자가 페이지를 여는데 걸리는 시간이 늘어난다는 의미이고 사용자가 나간다는 의미이다.
코드 스플리팅은 이것을 방지하기 위하여 존재하는 방법이다.
즉, 하나의 번들 파일을 여러개의 번들 파일로 나눈 뒤 실제 로드될 화면에 필요한 번들 파일만 불러오고 나머지 번들 파일은 호출하지 않고 지연시킴으로써 작업량을 줄여 더 빠른 속도로 화면이 보일 수 있게 도와주는 방법이다.
(이렇게 나뉘어진 번들링 파일을 Chunk라고 한다. )
기존 코드
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Login from './pages/Login/Login';
import KakaoLogin from './pages/Login/KakaoLogin';
import Subscribe from './pages/Subscribe/Subscribe';
import Payment from './pages/Payment/Payment';
import LectureVideo from './pages/LectureVideo/LectureVideo';
import CreatorCenter from './pages/CreatorCenter/CreatorCenter';
import YesNav from './YesNav';
function Router() {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/kakaologin" element={<KakaoLogin />} />
<Route path="/subscribe" element={<Subscribe />} />
<Route path="/payment" element={<Payment />} />
<Route path="/creatorcenter" element={<CreatorCenter />} />
<Route path="/lecturevideo" element={<LectureVideo />} />
<Route path="/*" element={<YesNav />} />
</Routes>
</BrowserRouter>
);
}
export default Router;
개선 코드
import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Loading from './components/Loading/Loading';
const Login = lazy(() => import('./pages/Login/Login'));
const KakaoLogin = lazy(() => import('./pages/Login/KakaoLogin'));
const Subscribe = lazy(() => import('./pages/Subscribe/Subscribe'));
const Payment = lazy(() => import('./pages/Payment/Payment'));
const LectureVideo = lazy(() => import('./pages/LectureVideo/LectureVideo'));
const CreatorCenter = lazy(() => import('./pages/CreatorCenter/CreatorCenter'));
const YesNav = lazy(() => import('./YesNav'));
function Router() {
return (
<BrowserRouter>
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/kakaologin" element={<KakaoLogin />} />
<Route path="/subscribe" element={<Subscribe />} />
<Route path="/payment" element={<Payment />} />
<Route path="/creatorcenter" element={<CreatorCenter />} />
<Route path="/lecturevideo" element={<LectureVideo />} />
<Route path="/*" element={<YesNav />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
export default Router;
Rout-based code splitting 적용.
즉, Route(페이지)에 기반한 코드 분할을 적용하였다.
기존
개선 후
코드 스플리팅을 통하여 번들링 파일을 나눠줌으로서 TBT가 140ms→20ms 로 개선됨을 볼 수 있다.
또한 크롬 개발자도구의 네트워크 탭을 사용하여 불러오는 번들 사이즈의 크기를 비교해본 결과
실제 전체 파일을 하나로 번들링하는것보다 더 작은 chunk파일을 로드해온것을 볼 수 있다.
기존 네트워크 로드 정보
개선 후 네트워크 로드 정보
아이콘을 사용하기위한 라이브러리인 react-icons의 chunk 사이즈가 너무 큰것이 문제로 나타났다.
실제 라이브러리 중 아이콘을 사용하기 위한 Javascript 코드를 제외한 코드들이 너무 많이 로드되었다는 의미이다.
react-icons는 icon 종류별로 구분되어 있으며, 종류별로 하나의 js파일에 아이콘 전체를 포함하고 있다.
build시 react-icons라이브러리의 모든 파일이 포함되기 때문에 chunk 사이즈가 커지게 된다.
위와같은 불합리로 인해 react-icons에서는 @react-icons/all-files 라는 별도의 라이브러리를 제공한다.
@react-icons/all-files 라이브러리는 아이콘 별로 자바스크립트 파일을 별도로 가지고 있기 때문에,
빌드 시 트리쉐이킹 방식으로 더 적은 크기의 chunk를 만들 수 있다.
기존 라이브러리 삭제 후 새로운 라이브러리 적용
npm remove react-icons
npm i @react-icons/all-files
기존 코드
import { AiOutlineHeart, AiFillHeart } from 'react-icons/ai';
import { BsPlayBtn, BsPerson } from 'react-icons/bs';
import { FiShare, FiGift } from 'react-icons/fi';
개선 코드
import { AiOutlineHeart } from '@react-icons/all-files/ai/AiOutlineHeart';
import { AiFillHeart } from '@react-icons/all-files/ai/AiFillHeart';
import { BsPlayBtn } from 'react-icons/bs';
import { BsPerson } from '@react-icons/all-files/bs/BsPerson';
import { FiShare } from '@react-icons/all-files/fi/FiShare';
import { FiGift } from '@react-icons/all-files/fi/FiGift';
사용되지 않는 react-icons 라이브러리의 Javascript파일을 제외시키고 나니 TBT(Total Blocking Time)또한 감소하였고 Javascript 로딩시간이 감소한 여파로 LCP또한 감소하여 전체적인 Performance 점수 또한 상승하였다.
기존
개선 후
화면 비율을 키운 후 평가를 실행 시 Performance Score 가 저하되었다.
이유를 확인 시 CLS(Cumulative Layout Shift) 문제 인것으로 확인 되었다.
(Accessibility, Best Practice, SEO 항목까지 최적화 후 추후 발견한 사항이라 Score가 직전과 다름)
구체적으로 어느 위치에서 리플로우가 가장 크게 발생하는지 확인해보았더니 Footer가 0.225로 대부분의 비중을 차지하는것을 확인할 수 있었다.
명확한 원인파악을 위해 Performance Insight로 렌더링 과정을 분석해보았다.
렌더링 과정 분석 결과 클래스 리스트를 보여주는 Main 컴포넌트의 높이가 고정되어있지 않아 클래스 리스트를 렌더링하는 과정에서 Footer 컴포넌트가 리플로우되어 CLS에 악영향을 주는것으로 확인 Main 컴포넌트의 height를 미리 고정해두어 Footer 컴포넌트의 Layout Shift 를 최소화 하면 해결 가능할것이라 판단 하였다.
기존 코드
import React, { useEffect, useState } from 'react';
.
.
function Main() {
.
.
return (
<MainContainer>
.
.
</MainContainer>
);
}
const MainContainer = styled.section`
width: 100%;
`;
개선 코드
import React, { useEffect, useState } from 'react';
.
.
function Main() {
.
.
return (
<MainContainer>
.
.
</MainContainer>
);
}
const MainContainer = styled.section`
height: 2528.33px;
width: 100%;
`;
Main 컴포넌트의 높이를 미리 지정해둔 후 그 안에서 클래스 목록을 띄워주게 함으로서
Footer 컴포넌트가 렌더링 과정 중 Main 컴포넌트의 클래스 목록 렌더링에 의해 Layout Shift가 발생하지 않도록 해주었다.
Main 컴포넌트의 높이가 이미 지정이 되어있기에 Footer 컴포넌트가 상단에 붙어있다가 클래스 목록 렌더링 후 아래로 내려가는 현상이 없어짐을 확인할 수 있다.
Lighthouse는 웹 애플리케이션의 접근성을 검사한다. <img>
태그에 alt
속성이 있는지, <html>
태그에 lang
속성이 있는지, 배경색과 전경색의 대비가 충분한지와 같은 항목을 확인한다.
전형적인 사용자라는 좁은 범위를 벗어난 곳에 있는 사용자, 개발자가 예상하는 것과 다르게 웹페이지에 액세스하거나 상호 작용할지도 모르는 사용자가 경험하는 사용환경을 지칭한다. 구체적으로 말하면 어떤 유형의 결함이나 장애를 가진 사용자를 배려한 사용환경을 말함.
<div key={url}>
<ImgContainer>
<img src={url} alt="이미지 더보기" />
</ImgContainer>
</div>
Accessibility는 특히 사용자과의 상호작용에 관련이 깊다.
이러한 이유로 버튼태그는 대표적인 사용자와의 인터랙션 태그이다.
그렇기에 해당 버튼태그가 어떠한 역할을 하는지 명시해줄 필요가 있다.
기존 코드
<button
type="button"
onClick={recordStopoer}
>
Stop Recording
</button>
<button
type="button"
onClick={recordDownloader}
ref={downloader}
>
Download Video
</button>
태그에 대한 명시적 설명이 없는 button 태그
개선 코드
<button
aria-label="recording stop button"
type="button"
onClick={recordStopoer}
>
Stop Recording
</button>
<button
aria-label="download recording video button"
type="button"
onClick={recordDownloader}
ref={downloader}
>
Download Video
</button>
arina-label 속성을 이용한 태그의 구체적 설명은 담은 태그
기존
개선 후
사용자와의 주요 인터랙션 태그중 하나인 버튼태그의 역할을 명시적으로 표기해줌으로서
Accessibility Score 75→84 상승 확인
기존 코드
<div key={url}>
<ImgContainer>
<img src={url} />
</ImgContainer>
</div>
개선 코드
<div key={url}>
<ImgContainer>
<img src={url} alt="이미지 더보기" />
</ImgContainer>
</div>
모든 img 태그에 alt 속성 부여함.
기존
개선 후
사용자와의 주요 인터랙션 태그중 하나인 이미지태그의 설명을 명시적으로 표기해줌으로서
Accessibility Score 84→94 상승 확인
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>CLASS IOI LALALA</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
개선 코드
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="description"
content="강의 등록 및 수강 플랫폼, 클래스 아이오아이"
/>
<meta name="CLASS IOI" content="Lecture, Class, Video" />
<title>CLASS IOI LALALA</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
기존
개선 후
SEO Score 80->90 상승 확인
최근 관심을 가지고 집중하는 분야는 2가지이다.
이번 기록은 2번에 충족하는 공부기록이었다.
잘하는 프론트엔드 개발자가 되고싶다는 열망이 다시금 차오르기 시작했다.
꾸준한 노력과 시간투자로 많은곳에서 부름을 받는 F/E 개발자가 되어보자.