[댕댕어디가 프로젝트 | 최적화 과정] Lighthouse를 사용해서 성능개선을 해보았다!#1
![[댕댕어디가 프로젝트 | 최적화 과정] Lighthouse를 사용해서 성능개선을 해보았다!#1](https://cdn.hashnode.com/res/hashnode/image/upload/v1739008177246/b208f452-048a-4d03-b225-47cd218b4e20.png)
오늘부터 최종프로젝트로 진행했던 댕댕어디가를 리팩토링 하기로 했다🥳 (기준은 본인이 맡은 페이지!)
그 과정을 기록하려고 한다.
리팩토링 대상 페이지
펫 CRUD 페이지
How-to-guide 페이지
리뷰 작성 페이지
리뷰 전체보기 페이지
알림 목록 페이지
내가 작성한 리뷰 페이지
마이페이지 페이지
오늘은 🚀마이페이지와 펫 CRUD🚀 페이지의 최적화를 진행하려고 한다.
그런데 작업을 하면서 느낀 점이 있는데, 로컬 환경에서 테스트한 Lighthouse 값과 배포된 서버에서 나온 값이 다르게 나온다는 점이다. 그래서 팀원들과 상의 후, 우선 로컬에서 성능 개선 작업을 진행하고, 나중에 배포된 서버에서 다시 확인해보면 성능이 향상되지 않을까 하는 생각으로 그렇게 진행하기로 했다.(목표는 각각 90점 넘기기)
빌드도구로 Vite를 사용했는데, Vite는 리소스를 gzip으로 압축하는 것을 기본으로 한다는 사실,,🤫
🛠️ 마이페이지
배포된 서버에서 성능

로컬에서의 성능

🔥접근성 높이기

이 부분에서 각 아이콘에 alt 표시가 없어서 접근성이랑 SEO에서 점수가 굉장히 낮아진 것 같았다.
//1.
<PetServiceImg src={mypageservice} alt="서비스 관리 아이콘"/>
//2.
<Icon src={icons[index]} alt="아이콘" />
각 아이콘에 alt를 넣어줬다.
🔥 SEO 높이기

robots.txt에 대한 문제 해결
모든 크롤러에게 특정 디렉터리 접근 금지로 robots.txt삽입
User-agent: *
Disallow: /private/
무조건 public 폴더에 넣어주면 된다
🧐 여기서 robotx.txt의 기능은 뭘까 ?
검색 엔진 봇(크롤러)에게 어떤 페이지를 크롤링하고 색인화할 수 있는지, 또는 할 수 없는지를 알려주는 역할을 하며, 이를 통해 웹사이트 소유자는 검색 엔진의 크롤러가 특정 페이지나 디렉터리에 접근하는 것을 제어할 수 있다.
robots.txt의 주요 기능
크롤링 방지:
특정 페이지나 디렉터리에 대해 검색 엔진 크롤러가 접근하지 않도록 할 수 있다
예를 들어, 로그인 페이지나 개인 정보를 다루는 페이지에 대한 접근을 막을 수 있다
크롤러의 접근 권한 설정:
- 모든 크롤러가 접근할 수 있도록 허용하거나, 특정 크롤러만 접근할 수 있게 설정할 수 있다
검색 엔진 최적화(SEO) 관리:
- 중요하지 않거나 검색 결과에 노출되면 안 되는 페이지나 파일을 크롤링하지 않도록 설정해, 검색 엔진의 성능과 결과를 더 깔끔하게 관리할 수 있다.
적용 후 라이트하우스 측정 결과

성능을 제외하고 많이 올랐다 🥳
🔥성능 높이기
로컬이랑 배포된 서버의 교집합만 수정하면 될 것 같았다.

배포된 서버에서 보면 CLS 부분이 가장 ,,, 안 좋게 나온걸 확인할 수 있다.
🧐 여기서 CLS(Cumulative Layout Shift)가 뭘까 ?
웹 페이지가 로드되는 동안 레이아웃 변화가 얼마나 발생했는지를 측정하는 지표를 의미한다. 웹 페이지의 콘텐츠가 로딩되는 동안 요소들이 예상치 못하게 움직이거나 변경되는 현상을 나타내며, 이는 사용자에게 불편을 줄 수 있다.
예를 들어, 페이지가 로딩되는데 이미지가 늦게 로드되거나 폰트가 나중에 적용되면서 텍스트가 이동하는 경우가 있다.
이런 현상이 CLS를 유발함!!
CLS는 0~1의 값으로 계산을 하게 되는데 0에 가까울수록 사용자가 웹 페이지를 이용하는 동안 레이아웃 이동이 거의 없음을 의미한다
✨ Skeleton UI 삽입
const SkeletonRouteItem = styled.div`
display: flex;
align-items: center;
margin-bottom: 10px;
padding: 8px;
background-color: #e0e0e0;
border-radius: 8px;
`;
const SkeletonCircle = styled.div`
width: 20px;
height: 20px;
background-color: #ccc;
border-radius: 50%;
`;
const SkeletonBar = styled.div`
margin-left: 10px;
height: 15px;
background-color: #ccc;
width: 100px;
`;
function RoutePage() {
const [loading, setLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 1000);
}, []);
<RouteContainer>
{loading ? (
Array.from({ length: 4 }).map((_, index) => (
<SkeletonRouteItem key={index}>
<SkeletonCircle />
<SkeletonBar />
</SkeletonRouteItem>
))
) : (
list.map((item, index) => (
<RouteItem key={index}>
<Icon src={icons[index]} alt="아이콘" />
<StyledLink to={routes[index]}>
<RouteText>{item}</RouteText>
</StyledLink>
</RouteItem>
))
)}
</RouteContainer>

배포된 서버 보다 CLS가 좋게 나와서 일단 배포 서버에 올려 보기로 했다
배포서버에서 측정

각 점수 90점 넘기기가 목표였는데 성공했다 🎉🥳
🛠️ 펫 등록 페이지
배포된 서버에서 성능

로컬에서의 성능

🔥 접근성 높이기

대비 부분은 색상을 전체적으로 변경해줘야 하는 부분이라서 일단 이름 및 라벨을 수정해보기로 했다.

견종 선택을 하는 부분에서 문제가 발생한 것,,!
🧐 문제가 발생한 이유
select 요소는 htmlFor 속성을 직접 가지고 있지 않기 때문에 이런 이슈가 발생한다.
이게 무슨 말이지?
댕댕이 이름과 댕댕이 견종의 코드 차이점을 살펴보자
수정 전 SelectLabel
import styled from "styled-components";
import PropTypes from "prop-types";
const LabelContainer = styled.p`
display: flex;
align-items: center;
font-size: 14px;
font-weight: 600;
color: #000;
span {
font-size: 14px;
margin-left: 4px;
color: #ff69a9;
font-weight: semibold;
}
`;
const SelectLabel = ({ label }) => {
return (
<LabelContainer>
{label}
<span>*</span>
</LabelContainer>
);
};
SelectLabel.propTypes = {
label: PropTypes.string.isRequired,
};
export default SelectLabel;
label만 넘겨주고 htmlFor을 넘겨주지 않는다(근본적인 원인임)
펫 이름을 입력하는 부분에서도 SelectLabel이 쓰였는데 여기서는
//RegisterInputForm.jsx
<PetTypeContainer>
<SelectLabel label="댕댕이 이름" />
<PetNameInput
value={petName}
onChange={handlePetNameChange}
placeholder="댕댕이 이름을 입력해주세요"
required />
//CommonStyle.jsx
export const PetNameInput = styled.input`
width: 96%;
height: 44px;
font-size: 14px;
border-radius: 5px;
border: 0.5px solid #e4e4e4;
margin-bottom: 10px;
padding: 10px;
&:focus {
outline: none;
border-color: #ff69a9;
}
`;
PetNameInput에 input을 사용했고 이 부분 덕분에 선택라벨오류가 발생하지 않았던 것이다.
이는 실제로 HTML <input> 요소로 렌더링된다.
<input>요소는 기본적으로 라벨과 쉽게 연관될 수 있는 폼 컨트롤이다.브라우저는 인접한
<label>과<input>요소를 자동으로 연관시키는 경향이 있다.사용자 인터페이스 관점에서, 라벨 바로 다음에 오는 입력 필드는 직관적으로 연결된 것으로 인식된다.
하지만
<SelectLabel label="견종" htmlFor="petType" />
<PetTypeOption id="petType" value={selectedPetType} onChange={handlePetTypeChange}>
<option value="" disabled>
견종을 선택하세요
</option>
{petTypeOptions.map((option) => (
<option key={option.code} value={option.code}>
{option.name}
</option>
))}
</PetTypeOption>
export const PetTypeOption = styled.select`
width: 96%;
height: 44px;
border: 0.5px solid #e4e4e4;
border-radius: 5px;
padding: 10px;
font-size: 13px;
color: ${(props) => (props.value === "" ? "#b3b3b3" : "#000")};
box-sizing: border-box;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background: url(${footerSearch}) no-repeat right 10px center;
background-size: 16px;
&:focus {
border-color: #FF69A9;
outline: none;
}
`;
펫 견종 선택에 사용되는 select 요소는
<select>요소는 암묵적인 연결이 이루어지지 않는다. 따라서 반드시label의htmlFor속성과<select>의id를 명시적으로 연결해야 한다.이는
<select>가 여러 옵션을 포함하는 특수한 폼 요소이기 때문이며, 스크린 리더 등 보조 기술이 이를 올바르게 인식하기 위해 명시적인 연결이 필요하다
이러한 차이점이 존재하기 때문에 꼭 htmlFor을 설정해줘야 한다는 것..!! 😏
수정 후 SelectLabel
import styled from "styled-components";
import PropTypes from "prop-types";
const LabelContainer = styled.label`
display: flex;
align-items: center;
font-size: 14px;
font-weight: 600;
color: #000;
span {
font-size: 14px;
margin-left: 4px;
color: #ff69a9;
font-weight: semibold;
}
`;
const SelectLabel = ({ label, htmlFor }) => {
return (
<LabelContainer htmlFor={htmlFor}>
{label}
<span>*</span>
</LabelContainer>
);
};
SelectLabel.propTypes = {
label: PropTypes.string.isRequired,
htmlFor: PropTypes.string.isRequired,
};
export default SelectLabel;
이렇게 수정했다 ! 🥳 htmlFor을 넣어주었고 p에서 label로 수정해줬다
수정 후 로컬에서 측정한 라이트하우스

변경 코드 적용 후 배포서버에서 측정

🛠️ 펫 정보 수정 페이지
배포된 서버에서 성능

로컬에서의 성능

접근성이 굉장히 낮다

여기도 라벨 이슈 때문,,!!!!
댕댕이 이름 : 라벨이 포함되어 있지 않는다
댕댕이 견종 : 선택 요소에 연결된 라벨 요소가 없다
//1.댕댕이 이름 부분에 htmlFor, id 삽입
<SelectLabel label="댕댕이 이름" htmlFor="petName"/>
<PetNameInput
id = "petName"
value={petName}
onChange={handlePetNameChange}
aria-label="댕댕이 이름"
/>
//2.댕댕이 견종 부분에 htmlFor, id 삽입
<SelectLabel label="견종" htmlFor="petType" />
<PetTypeOption id="petType" value={selectedPetType} onChange={handlePetTypeChange}>
수정 후 라이트하우스

굉장히 많이 올랐다🥳
정말 라벨이 더 더 더 중요하다는 것을 느낀 리팩토링 과정 ..💪🏻
변경된 코드 적용 후 서버에서 측정한 라이트하우스

😼 느낀점
HTML과 CSS 기초를 배울 때, 프론트엔드 개발자는 시각적으로 불편한 사람들이 컴퓨터를 편리하게 사용할 수 있도록 돕는 역할을 한다고 배웠다! 이를 위해 alt나 label 삽입이 정말 중요한데… 막상 개발할 때는 기능 구현에 집중하느라 소홀했던 내 모습을 돌아보게 되었다. 🥲
접근성을 고려한 개선점 작성
이러한 부분을 적용하여, 마이페이지 & 펫 CRUD 리팩토링 과정에서는 alt 속성을 빠뜨리지 않고 추가하고, 폼 요소에는 label을 명확하게 연결를 했다.. 정말 작은 습관이지만 이런 디테일이 쌓이면 더 많은 사람들이 편하게 사용할 수 있는 서비스가 될 거라고 생각한다 !!

![[React] 클라이언트에서 이미지를 압축해보자](https://cdn.hashnode.com/res/hashnode/image/upload/v1759903597964/227ea5dc-8ff1-4999-9869-1bceb71b210a.png)
![[책 추천] 프론트엔드 개발자라면 반드시 알아야 할 '웹 접근성' 이야기](https://cdn.hashnode.com/res/hashnode/image/upload/v1753777028409/0438e77e-a609-48d8-989a-68191214a5b1.png)
![[Next.js] 나만의 학습 블로그 만들기#3-다국어 지원 (Feat.next-i18next)](https://cdn.hashnode.com/res/hashnode/image/upload/v1752472189546/a25d48d1-715a-4c90-a530-7dded2981fe7.png)
![[Next.js] 나만의 학습 블로그 만들기#4 - 댓글 기능(Feat. Giscus)](https://cdn.hashnode.com/res/hashnode/image/upload/v1752477512377/e1e868c0-6968-49f0-8fc5-91083b01ef79.png)
![[React] React + localStorage로 하루에 한 번만 보이는 팝업 만들기](https://cdn.hashnode.com/res/hashnode/image/upload/v1751525304623/4d143f95-eeac-42fb-9a95-b21d12d2e476.png)