Skip to main content

Command Palette

Search for a command to run...

[댕댕어디가 프로젝트 | 최적화 과정] Lighthouse를 사용해서 성능개선을 해보았다!#1

Updated
6 min readView as Markdown
[댕댕어디가 프로젝트 | 최적화 과정] Lighthouse를 사용해서 성능개선을 해보았다!#1

오늘부터 최종프로젝트로 진행했던 댕댕어디가를 리팩토링 하기로 했다🥳 (기준은 본인이 맡은 페이지!)

그 과정을 기록하려고 한다.

  • 리팩토링 대상 페이지

    • 펫 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의 주요 기능

  1. 크롤링 방지:

    • 특정 페이지나 디렉터리에 대해 검색 엔진 크롤러가 접근하지 않도록 할 수 있다

    • 예를 들어, 로그인 페이지나 개인 정보를 다루는 페이지에 대한 접근을 막을 수 있다

  2. 크롤러의 접근 권한 설정:

    • 모든 크롤러가 접근할 수 있도록 허용하거나, 특정 크롤러만 접근할 수 있게 설정할 수 있다
  3. 검색 엔진 최적화(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; 
  }
`;

PetNameInputinput을 사용했고 이 부분 덕분에 선택라벨오류가 발생하지 않았던 것이다.

이는 실제로 HTML <input> 요소로 렌더링된다.

  1. <input> 요소는 기본적으로 라벨과 쉽게 연관될 수 있는 폼 컨트롤이다.

  2. 브라우저는 인접한 <label><input> 요소를 자동으로 연관시키는 경향이 있다.

  3. 사용자 인터페이스 관점에서, 라벨 바로 다음에 오는 입력 필드는 직관적으로 연결된 것으로 인식된다.

하지만

        <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 요소는

  1. <select> 요소는 암묵적인 연결이 이루어지지 않는다. 따라서 반드시 labelhtmlFor 속성과 <select>id를 명시적으로 연결해야 한다.

  2. 이는 <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. 댕댕이 이름 : 라벨이 포함되어 있지 않는다

  2. 댕댕이 견종 : 선택 요소에 연결된 라벨 요소가 없다

//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 기초를 배울 때, 프론트엔드 개발자는 시각적으로 불편한 사람들이 컴퓨터를 편리하게 사용할 수 있도록 돕는 역할을 한다고 배웠다! 이를 위해 altlabel 삽입이 정말 중요한데… 막상 개발할 때는 기능 구현에 집중하느라 소홀했던 내 모습을 돌아보게 되었다. 🥲

접근성을 고려한 개선점 작성

이러한 부분을 적용하여, 마이페이지 & 펫 CRUD 리팩토링 과정에서는 alt 속성을 빠뜨리지 않고 추가하고, 폼 요소에는 label을 명확하게 연결를 했다.. 정말 작은 습관이지만 이런 디테일이 쌓이면 더 많은 사람들이 편하게 사용할 수 있는 서비스가 될 거라고 생각한다 !!

More from this blog

[React] 클라이언트에서 이미지를 압축해보자

우리는 서버 비용을 직접 부담하고 있기 때문에 항상 최적화와 비용 절감 방법을 고민하게 된다. 현재 자유게시판이 활성화가 많이 되지는 않았지만 사용자들이 업로드하는 이미지가 평균 5MB 이상일 경우를 대비해서 클라이언트에서 이미지 압축을 진행했다. 웹에서는 크게 두 가지 방법이 있었는데 Canvas API를 직접 사용하는 방법 browser-image-compression 라이브러리를 활용하는 방법 이 있다. 여러 장점을 고려해 brow...

Oct 8, 20252 min read
[React] 클라이언트에서 이미지를 압축해보자

[책 추천] 프론트엔드 개발자라면 반드시 알아야 할 '웹 접근성' 이야기

내가 생각하는 프론트엔드 개발자는 단순히 보이는 화면만 구현하는 것을 넘어서 누구나 접근할 수 있는 웹을 만드는 데 중요한 역할을 해야 한다고 생각한다. 실무에 바로 적용하는 웹 접근성 가이드북 이번에 이 책을 받고 프엔들이 읽기에 너무 좋다고 생각해서 블로그에 추천 글까지 적게 되었다 ! 목차 Chatper1. 쉽게 이해하는 접근성 Chapter2. 웹 접근성의 기초 Chapter3. HTML 태그, 의미 있게 사용하기 Chpater4...

Jul 27, 20255 min read
[책 추천] 프론트엔드 개발자라면 반드시 알아야 할 '웹 접근성' 이야기

[Next.js] 나만의 학습 블로그 만들기#3-다국어 지원 (Feat.next-i18next)

오늘은 다국어 지원 가능한 기능 구현 과정을 적어보려고 한다. 원래는 댓글기능보다 먼저 구현하려고 했는데 ,, 갑자기 댓글기능 알아보다가 재밌어서 먼저 끝내버렸다,,,🙄 시작하기 전에 정말 어려웠다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 어려움이 많아서 기능 적용하는 데 시간이 꽤 걸렸다 ,,, 다국어 지원 기능 나는 영어랑 일본어를 추가해줬다 ! (해외취업도 관심 있기 때문에 나중에 이력서 낼 때 도움이 되지 않을까? ..🤭) 다국어 구현 방법 구현 방...

Jul 15, 20254 min read
[Next.js] 나만의 학습 블로그 만들기#3-다국어 지원 (Feat.next-i18next)

[Next.js] 나만의 학습 블로그 만들기#4 - 댓글 기능(Feat. Giscus)

댓글기능이 있으면 무조건 재밌을 거 같아서 넣어보려고 한다🤭 아무래도 개발에 관련된 학습 블로그라서 개발자들이 많을 것으로 예상했다 ! 그래서 깃허브 이슈 기반 댓글 시스템을 기반으로 기능을 구현해보려고 한다. 👉🏻 도움이 많이 된 블로그 ! 방법 Giscus | (https://giscus.app) utterances | (https://utteranc.es) 알아봤을 때는 이렇게 두 개가 유명하다고 한다. 그럼 각각의 특징과 장...

Jul 14, 20253 min read
[Next.js] 나만의 학습 블로그 만들기#4 - 댓글 기능(Feat. Giscus)

[React] React + localStorage로 하루에 한 번만 보이는 팝업 만들기

이번 프로젝트에 설문조사 배너를 제작했는데… 흠… 뭔가 별로라는 의견이 많았다! (팀원들 의견) 🙄 디자인이 문제일까? 해서 래퍼런스를 많이 찾아봤는데 … ! 일반 웹사이트는 설문조사 배너 자체를 안 만들어요,,,🤭 충격 그 자체 ( 난 아직 멀었음 ) 그리고 알아보니깐.. 설문조사 배너는 사용자 경험을 해치기 쉬워 잘 쓰이지 않는다고 한다! 그래서 나는 모달 팝업 방식으로 전환하기로 결정했고 실무에서 자주 사용하는 패턴으로 구현했다. 방...

Jul 9, 20253 min read
[React] React + localStorage로 하루에 한 번만 보이는 팝업 만들기
S

subin-dev-blog

27 posts

끊임없이 배우기 위해 노력합니다. 🌱