<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>minyoung_.log</title>
        <link>https://velog.io/</link>
        <description>Frontend Developer</description>
        <lastBuildDate>Tue, 15 Apr 2025 04:12:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>minyoung_.log</title>
            <url>https://velog.velcdn.com/images/minyoung_/profile/e99b134c-ea13-43a3-8152-4fc2deeff506/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. minyoung_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/minyoung_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[모노레포 전환 실전: Turborepo 도입부터 구성까지]]></title>
            <link>https://velog.io/@minyoung_/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC-%EC%A0%84%ED%99%98-%EC%8B%A4%EC%A0%84-Turborepo-%EB%8F%84%EC%9E%85%EB%B6%80%ED%84%B0-%EA%B5%AC%EC%84%B1%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@minyoung_/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC-%EC%A0%84%ED%99%98-%EC%8B%A4%EC%A0%84-Turborepo-%EB%8F%84%EC%9E%85%EB%B6%80%ED%84%B0-%EA%B5%AC%EC%84%B1%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Tue, 15 Apr 2025 04:12:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이전에 모놀리식 구조에서 모노레포 구조로 전환하기 위한 과정에 대한 고민들을 벨로그에 정리했었다.
오늘은 실제로 Turborepo를 사용해 모노레포를 구성한 과정을 정리해보고자 한다.</p>
</blockquote>
<h2 id="1-왜-turborepo인가">1. 왜 Turborepo인가?</h2>
<p>우선 Turborepo를 선택하기 전에, 모노레포를 구축할 수 있는 방법에 대해 먼저 알아봤었다.
모노레포를 지원하는 도구들은 다양하게 존재하는데, 대표적으로는 Turborepo, Lerna, Nx, Yarn Workspaces, npm Workspaces, pnpm 등이 있다.
그 중에서도 실제 도입을 고민했던 도구는 Turborepo, Nx, npm Workspaces였다.</p>
<h3 id="🔧-npm-workspaces">🔧 npm Workspaces</h3>
<ul>
<li>이미 사내에서 기본적으로 사용하고 있던 패키지 매니저이기도 했고, </li>
<li>별도의 설정 없이도 다중 패키지를 하나의 저장소에서 관리할 수 있다는 점이 간단하게 구성하기 좋은 거 같다.</li>
<li>하지만 빌드 최적화, 캐싱, 병렬 처리 등의 기능은 별도로 구성해줘야 했고, 프로젝트가 커질수록 관리가 복잡해질 가능성이 있었다.</li>
</ul>
<h3 id="🧰-nx">🧰 Nx</h3>
<ul>
<li>Angular 팀 출신들이 만든 도구로, 대규모 모노레포 운영에 특화된 고급 기능들을 제공한다.</li>
<li>의존성 그래프를 자동으로 생성해서 변경된 부분만 빌드하거나 테스트하는 증분 실행이 가능하다.</li>
<li>React, Next.js, Angular, NestJS 등 다양한 프레임워크를 위한 플러그인 생태계가 잘 갖춰져 있어서, 팀의 기술 스택에 맞게 쉽게 확장 가능하다.</li>
<li>공통된 기능을 라이브러리로 분리하고, 여러 프로젝트 간에 쉽게 공유할 수 있다.</li>
<li>단점으로는 처음 접할 땐 설정이 복잡하게 느껴질 수 있고, 학습 곡선이 존재한다는 점이다. 하지만 복잡한 아키텍처와 대규모 프로젝트에선 오히려 강점이 될 수 있는 부분이다.</li>
</ul>
<h3 id="⚡-turborepo">⚡ Turborepo</h3>
<ul>
<li>Vercel에서 개발한 모노레포 관리 도구로, 특히 프론트엔드 프로젝트에서 빠르게 떠오르고 있는 솔루션이다.</li>
<li>간단한 설정만으로도 강력한 기능을 제공하여 빠르게 프로젝트를 시작할 수 있다.</li>
<li>이전에 빌드된 아티팩트를 재사용하여 빌드 속도를 대폭 향상시키며, 변경된 부분만 선택적으로 빌드하는 증분 빌드를 지원하여 불필요한 작업을 최소화 한다.</li>
<li>태스크 간의 의존성을 분석하여 병렬로 실행 가능한 태스크를 식별하고, 최적의 실행 순서를 결정한다.</li>
<li>React, Next.js, Vue, Node.js 등 다양한 스택에서 유연하게 활용 가능이 가능하다. </li>
<li>하지만, Nx에 비하면 다양한 고급 기능(= 플러그인)들이 제공되지 않는다. </li>
</ul>
<br />

<h3 id="📌-왜-turborepo를-선택했는가">📌 왜 Turborepo를 선택했는가?</h3>
<p>** ✅ 내가 중요하게 본 기준은 다음 두 가지였다:**</p>
<p><strong>1. 모노레포로 전환할 수 있는 시간이 넉넉하지 않았기 때문에, 학습 곡선이 완만해야 한다.</strong>
<strong>2. 빌드 속도가 빨라야 한다.</strong>
<br /></p>
<ul>
<li>npm workspaces는 보일러플레이트가 적다는 점에서 좋았지만, 빌드 최적화를 직접 구성해야 한다는 점에서 손이 많이 갔다.</li>
<li>Nx는 고급 기능과 확장성 면에서 훌륭했지만, 설정이 복잡하고 학습 비용이 상대적으로 높았다.</li>
<li>반면, <strong>Turborepo는 작고 빠르게 시작할 수 있고, 팀과 프로젝트의 구조를 크게 변경하지 않고도 바로 도입할 수 있다는 점에서 유리했다.</strong></li>
</ul>
<ul>
<li>추가로, Nx의 고급 기능들(예: 다양한 플러그인 등)은 우리 팀 상황에서는 아직 필요하지 않았고,</li>
<li>Turborepo는 Vercel이 관리하고 있다는 점에서 앞으로도 꾸준히 플러그인이나 기능이 발전할 것이라는 기대감도 있었다.</li>
</ul>
<blockquote>
<p><strong>결론적으로, 적은 설정으로도 빠르고 효율적인 모노레포 환경을 만들 수 있다는 점에서 Turborepo를 선택하게 되었다.</strong></p>
</blockquote>
<hr>
<h2 id="2-turborepo-기본-개념-이해하기">2. Turborepo 기본 개념 이해하기</h2>
<p>앞서 설명했던 Turborepo는 Vercel에서 만든 모노레포 빌드 시스템으로, 다중 패키지를 하나의 저장소에서 효과적으로 관리하고, 빠르게 빌드/배포할 수 있게 해주는 도구이다.</p>
<h3 id="⚡-turborepo는-어떤-문제를-해결하는가">⚡ Turborepo는 어떤 문제를 해결하는가?</h3>
<p><strong>1. 느린 빌드 시간</strong>
매번 전체를 빌드하는 대신, <strong>변경된 부분만 빌드</strong>하는 <strong>증분 빌드 (Incremental Build)</strong> 지원
→ 시간 절약 + 빠른 피드백</p>
<p><strong>2. 반복 작업의 낭비</strong>
<strong>로컬 및 원격 캐시</strong>를 통해 이전에 실행한 작업 결과를 저장하고, 동일한 작업은 다시 실행하지 않음
→ 빌드/테스트/타입체크 등의 작업을 재사용</p>
<p><strong>3. 작업 순서의 혼란과 비효율</strong>
작업 간의 의존성을 분석하는 <strong>의존성 그래프 (Dependency Graph)</strong> 기반으로 자동으로 실행 순서를 최적화
→ 순서 문제 해결 + 병렬 실행 가능</p>
<p><strong>4. 복잡한 설정</strong>
<strong>최소한의 설정</strong>으로 바로 시작 가능하며, 필요에 따라 점진적으로 확장 가능
→ 처음 쓰는 팀에도 부담 없이 도입 가능</p>
<hr>
<h2 id="3-turborepo-설치하기">3. Turborepo 설치하기</h2>
<h3 id="🛠️-turborepo-설치-방법">🛠️ Turborepo 설치 방법</h3>
<h4 id="1-create-turbo를-이용해서-설치하기">1. create-turbo를 이용해서 설치하기</h4>
<p>가장 간편한 방법은 Turborepo에서 제공하는 템플릿을 사용하는 거다. 아래 명령어를 실행하면 프로젝트 이름과 패키지 매니저를 선택하라는 프롬프트가 나타나고, 기본적인 프로젝트 구조가 자동으로 생성된다.</p>
<pre><code class="language-javascript">npx create-turbo@latest</code></pre>
<p>이 명령어를 실행하면 <code>apps</code>와 <code>packages</code> 폴더가 포함된 모노레포 구조가 생성되고, <code>turbo.json</code> 파일도 함께 생성된다.</p>
<br />


<h4 id="2-수동-설치">2. 수동 설치</h4>
<p>이미 존재하는 프로젝트에 Turborepo를 도입하려면 수동으로 설치할 수 있다.</p>
<pre><code class="language-javascript">npm install turbo --save-dev</code></pre>
<p>설치 후, <code>turbo.json</code> 파일을 생성하고 필요한 설정을 추가하면 된다.</p>
<hr>
<h2 id="4-turborepo-살펴보기">4. Turborepo 살펴보기</h2>
<h3 id="📁-프로젝트-폴더-구조-살펴보기">📁 프로젝트 폴더 구조 살펴보기</h3>
<p>create-turbo를 이용해서 설치하면 Turborepo의 기본적인 폴더 구조는 아래와 같다.
<img src="https://velog.velcdn.com/images/minyoung_/post/075f8f0d-a136-4409-991b-57f087d85f42/image.png" alt=""></p>
<ul>
<li><p><code>apps</code>: 실제 애플리케이션이 위치하는 폴더로, 예를 들어 web, docs 등의 프로젝트가 여기에 들어간다.</p>
</li>
<li><p><code>packages/</code>: 여러 앱에서 공통으로 사용하는 패키지들을 모아두는 폴더이다. 예를 들어 UI 컴포넌트, ESLint 설정, TypeScript 설정 등이 여기에 위치한다.</p>
</li>
</ul>
<br />

<h3 id="⚙️-turbojson-기본-구조와-의미">⚙️ turbo.json 기본 구조와 의미</h3>
<p><code>turbo.json</code> 파일은 <strong>Turborepo의 핵심 설정 파일</strong>로,
각 작업(task) 간의 <strong>의존성, 입력/출력 정의, 캐싱 전략, 병렬 실행 여부</strong> 등을 정의한다.
이 설정을 통해 <strong>변경된 부분만 빠르게 빌드하고, 작업 순서를 자동으로 계산</strong>하며, <strong>중복 작업을 캐싱으로 건너뛴다.</strong></p>
<pre><code class="language-javascript">{
  &quot;$schema&quot;: &quot;https://turbo.build/schema.json&quot;,
  &quot;ui&quot;: &quot;tui&quot;,
  &quot;tasks&quot;: { //프로젝트 내에서 사용할 각 작업(task)을 정의
    &quot;build&quot;: {
      &quot;dependsOn&quot;: [&quot;^build&quot;],
      &quot;inputs&quot;: [&quot;$TURBO_DEFAULT$&quot;, &quot;.env*&quot;],
      &quot;outputs&quot;: [&quot;.next/**&quot;, &quot;!.next/cache/**&quot;]
    },
    &quot;lint&quot;: {
      &quot;dependsOn&quot;: [&quot;^lint&quot;]
    },
    &quot;check-types&quot;: {
      &quot;dependsOn&quot;: [&quot;^check-types&quot;]
    },
    &quot;dev&quot;: {
      &quot;cache&quot;: false,
      &quot;persistent&quot;: true
    }
  }
}</code></pre>
<h4 id="🛠️-tasks-필드-설명">🛠️ tasks 필드 설명</h4>
<p>tasks 필드 안에서 프로젝트 내에서 사용할 <code>작업(task)</code> 들을 정의하며, 각 작업은 아래와 같은 설정을 통해 작동 방식을 지정할 수 있다.</p>
<ul>
<li><p><code>dependsOn</code>: 이 작업을 실행하기 전에 먼저 실행되어야 하는 작업을 지정한다. 
예: <code>&quot;^build&quot;</code>는 상위 의존 항목의 <code>build</code> 작업을 먼저 실행함을 의미한다.</p>
</li>
<li><p><code>inputs</code>: 작업에 영향을 주는 파일 목록을 설정한다. 이 값이 변경되면 캐시가 무효화되어 작업이 다시 실행된다.
예: <code>&quot;$TURBO_DEFAULT$&quot;</code>는 기본 입력값(package.json, tsconfig 등)을 의미하고, .env*는 환경변수 파일 변경도 감지하겠다는 의미.</p>
</li>
<li><p><code>outputs</code>:      작업 결과로 생성되는 파일 경로를 명시한다. 이 결과는 캐싱되어, 다음 빌드 시 재사용된다.
예: <code>.next/**</code>는 Next.js의 빌드 결과, <code>!.next/cache/**</code>는 캐싱 제외</p>
</li>
<li><p><code>cache</code>: <code>false</code>로 설정하면 해당 작업은 캐시되지 않는다. 주로 <code>dev</code>와 같이 지속적으로 실행되는 작업에 사용된다.</p>
</li>
<li><p><code>persistent</code>: <code>true</code>로 설정하면 작업이 영구적으로 실행된다. <code>dev</code> 서버처럼 종료되지 않고 계속 실행되어야 하는 작업에 사용된다.</p>
</li>
</ul>
<h3 id="turbojson-추가-설정">turbo.json 추가 설정</h3>
<pre><code class="language-javascript">&quot;test&quot;: {
  &quot;dependsOn&quot;: [&quot;^test&quot;],
  &quot;outputs&quot;: []
}
</code></pre>
<p>위 코드 처럼 <code>test</code>, <code>storybook</code>, <code>e2e</code> 등 원하는 작업을 직접 추가할 수 있으며,
Turborepo는 이를 캐싱, 병렬 실행 등의 규칙에 따라 자동으로 관리해준다.</p>
<hr>
<h2 id="5-워크스페이스-설정과-의존성-관리">5. 워크스페이스 설정과 의존성 관리</h2>
<p><strong>모노레포를 구성할 때 핵심이 되는 기능 중 하나가 워크스페이스(Workspaces)이다.</strong>
워크스페이스를 활용하면 여러 개의 프로젝트(패키지)를 하나의 저장소 안에서 통합적으로 관리할 수 있도록 해주는 기능이다.
Turborepo는 <code>npm</code>, <code>Yarn</code>, <code>pnpm</code>의 워크스페이스 기능을 활용하여 하위 패키지를 인식하고 연결한다.</p>
<h3 id="📦-워크스페이스란">📦 워크스페이스란?</h3>
<p>워크스페이스는 각각의 독립적인 패키지 단위를 의미하며, 각 패키지는 자신만의 <code>package.json</code> 파일을 가지고 있다.
앱, UI 컴포넌트 라이브러리, 유틸 함수 등 다양한 목적의 패키지를 분리해 구성할 수 있으며,
이러한 워크스페이스들을 하나의 저장소에서 효율적으로 관리하는 것이 바로 모노레포의 핵심이다.</p>
<p>워크스페이스에 정의된 의존성은 각 패키지의 <code>package.json</code>에 명시하지만, 실제 패키지는 루트의 <code>node_modules</code>에 통합 설치된다.
예를 들어 여러 워크스페이스에서 react 패키지를 공통으로 사용한다면, 해당 패키지는 한 번만 다운로드되어 루트에 설치된다.</p>
<p>이를 통해 다음과 같은 이점을 얻을 수 있다:</p>
<h4 id="✅-워크스페이스의-장점">✅ 워크스페이스의 장점</h4>
<ul>
<li><p><strong>의존성 통합 관리</strong>
루트에서 모든 워크스페이스의 의존성을 한 번에 설치하고 관리할 수 있다.</p>
</li>
<li><p><strong>중복 설치 방지</strong>
공통된 패키지는 한 번만 설치되므로 설치 시간 단축과 디스크 공간 절약이 가능하다.</p>
</li>
<li><p><strong>로컬 개발에 유리</strong>
워크스페이스 간 의존성을 symlink로 연결하므로, 코드 변경이 즉시 반영되어 빠른 피드백이 가능하다.</p>
</li>
<li><p><strong>코드 재사용 용이</strong>
공통된 모듈을 별도의 패키지로 분리하여 여러 앱에서 쉽게 불러올 수 있어 유지보수성과 재사용성이 향상된다.</p>
</li>
</ul>
<br/>


<h3 id="⚙️-루트-packagejson에서-설정하기">⚙️ 루트 package.json에서 설정하기</h3>
<p>워크스페이스는 루트의 package.json 파일에서 아래와 같이 정의한다.</p>
<pre><code class="language-javascript">{
  &quot;name&quot;: &quot;my-turborepo&quot;,
  &quot;private&quot;: true,
  &quot;workspaces&quot;: [
    &quot;apps/*&quot;,
    &quot;packages/*&quot;
  ]
}
</code></pre>
<p>이렇게 설정 <code>apps</code>와 <code>packages</code> 디렉토리 내의 모든 하위 폴더들이 워크스페이스로 인식된다.</p>
<p><strong>💡 &quot;private&quot;: true는 워크스페이스를 사용하는 프로젝트에서 반드시 필요하다. (npm publish 방지용)</strong></p>
<br />

<h3 id="🔗-워크스페이스-간-의존성-관리">🔗 워크스페이스 간 의존성 관리</h3>
<p>워크스페이스의 가장 큰 장점 중 하나는 로컬에서 패키지 간 의존성을 직접 연결할 수 있다는 점이다.
예를 들어, <code>apps/web</code>에서 <code>packages/ui</code>를 사용하고 싶다면 다음처럼 ui를 의존성으로 추가하면 된다:</p>
<pre><code class="language-javascript">// apps/web/package.json
{
  &quot;dependencies&quot;: {
    &quot;ui&quot;: &quot;*&quot;
  }
}</code></pre>
<p>이 경우 실제 <code>NPM</code>에서 설치하는 것이 아니라, 로컬의 <code>packages/ui</code> 디렉토리를 참조하여 symlink로 연결된다.
이렇게 하면 <strong>버전 없이도 바로 사용 가능</strong>하고, <strong>수정사항이 즉시 반영</strong>되어 개발 속도가 빨라진다.</p>
<br />

<blockquote>
<p><strong>워크스페이스는 모노레포의 기반을 이루는 중요한 개념이며, 단일 저장소에서 여러 프로젝트를 효율적으로 운영하고 협업하기 위한 기반이 된다.
Turborepo는 이 워크스페이스 구조 위에 캐싱, 병렬 실행, 증분 빌드 등의 강력한 기능을 얹어 모노레포의 생산성과 관리 효율성을 극대화를 해준다.</strong></p>
</blockquote>
<hr>
<h2 id="6-실제로-써보며-느낀-점">6. 실제로 써보며 느낀 점</h2>
<p>기존 프로젝트를 Turborepo 구조에 맞게 옮기는 작업은 꽤 번거롭고 시간이 많이 걸렸다.
특히 설정 파일을 모듈화하거나 의존성을 정리하는 과정에서 생각보다 신경 써야 할 부분이 많았기 때문에, 도입 초기엔  <strong>&quot;시간 대비 효과가 있을까?&quot;</strong> 하는 의심도 있었다.</p>
<p>하지만 실제로 적용해보니 <strong>체감되는 변화가 꽤 컸다.</strong></p>
<p>가장 먼저 눈에 띄는 건 <strong>빌드 속도 개선</strong>이었다.
Turborepo의 <strong>로컬/원격 캐시 기능</strong> 덕분에 변경되지 않은 작업은 캐시된 결과를 바로 재사용하고, 변경된 부분만 다시 빌드하기 때문에 전체 빌드 시간이 확연히 줄어들었다.
특히 전체 프로젝트를 다시 빌드해야 할 상황에서도 이전보다 훨씬 빠르게 완료되는 걸 보면서, 이점이 분명하다고 느꼈다.</p>
<p>또한, <strong>모노레포 구조로 전환한 뒤로는 의존성 관리와 공통 모듈 재사용이 훨씬 명확해졌다.</strong>
이전에는 각 프로젝트마다 중복된 설정이나 유틸 코드가 흩어져 있었지만, <code>packages/</code> 폴더에 공통 코드를 묶어두면서 한 곳에서 효율적으로 관리할 수 있게 됐다.
덕분에 코드 수정도 일관성 있게 반영되고, 팀원 간 충돌이나 버전 불일치 문제도 크게 줄어들었다.</p>
<h3 id="🤝-팀-협업에-생긴-변화">🤝 팀 협업에 생긴 변화</h3>
<p>구조 전환 이후, 팀원 간 협업도 훨씬 효율적으로 이루어지기 시작했다.
예전에는 A 개발자가 만든 공통 컴포넌트를 B 개발자가 다른 레포에서 사용하는 경우, 패키지를 따로 배포하거나 수동으로 옮겨야 했다.
하지만 이제는 같은 저장소 내에서 의존 관계를 바로 연결할 수 있고, 변경사항도 즉시 확인되기 때문에
<strong>&quot;공통 컴포넌트 수정 → 테스트 → 반영&quot;의 흐름이 훨씬 매끄러워졌다.</strong></p>
<h3 id="✍️-결론">✍️ 결론</h3>
<p>기에 구조를 바꾸는 데 드는 비용은 분명 존재하지만, <strong>한 번 세팅해두면 장기적으로 관리가 훨씬 쉬워지고 협업 생산성도 올라간다.</strong>
모노레포를 고민하고 있다면 Turborepo는 진입 장벽이 낮으면서도 실용적인 선택지라고 생각한다. 특히 큰 설정 없이도 캐시, 병렬 빌드, 의존성 기반 실행 같은 고급 기능을 바로 활용할 수 있다는 점에서 추천할 만하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[모놀리식에서 모노레포로, 그 사이의 과정들]]></title>
            <link>https://velog.io/@minyoung_/%EB%AA%A8%EB%86%80%EB%A6%AC%EC%8B%9D%EC%97%90%EC%84%9C-%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EB%A1%9C-%EA%B7%B8-%EC%82%AC%EC%9D%B4%EC%9D%98-%EA%B3%BC%EC%A0%95%EB%93%A4</link>
            <guid>https://velog.io/@minyoung_/%EB%AA%A8%EB%86%80%EB%A6%AC%EC%8B%9D%EC%97%90%EC%84%9C-%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EB%A1%9C-%EA%B7%B8-%EC%82%AC%EC%9D%B4%EC%9D%98-%EA%B3%BC%EC%A0%95%EB%93%A4</guid>
            <pubDate>Sat, 15 Mar 2025 15:10:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>오늘은 회사 서비스 구조를 모놀리식에서 모노레포로 전환하는 과정에 있어 고민했던 과정들을 기록해보고자 한다.</p>
</blockquote>
<p>현재 다니고 있는 회사에서는 다양한 서비스들이 운영되고 있다.
메인 서비스 5개, 세부 서비스 2개, 어드민 서비스 2개, 디자인 시스템 1개까지 합하면 총 10개의 서비스가 존재한다.</p>
<p>각 서비스는 서로 다른 기술 스택을 사용하고 있으며, 새로운 서비스에 투입될 때마다 최신 버전의 라이브러리를 설치했었다. 예를 들어, 어떤 서비스는 Next.js + TypeScript, 어떤 서비스는 React + TypeScript, 또 다른 서비스는 React + JavaScript, Vue + JavaScript 등 다양한 스택을 사용하고 있었다. </p>
<p>게다가, 모든 프로젝트가 하나의 레포지토리에 포함된 <strong>모놀리식 레포지토리(monolithic repository)</strong> 구조로 되어 있었다.</p>
<h2 id="모놀리식-구조로-인해-불편했던-부분들">모놀리식 구조로 인해 불편했던 부분들</h2>
<h3 id="모놀리식-레포지토리란❓"><strong>모놀리식 레포지토리란❓</strong></h3>
<blockquote>
<p>모든 애플리케이션(=서비스)이 하나의 코드베이스에 통합되어, 백엔드, 프론트엔드, 데이터베이스 등 모든 구성 요소가 함께 배포되는 전통적인 방식이다. 주로 작은 규모의 애플리케이션에서 사용되며, 모든 코드를 단일 프로젝트로 관리하는걸 뜻한다.</p>
</blockquote>
<p>초기에는 서비스 개수가 많지 않다 보니 큰 불편함이 없었다. 그러나 점점 서비스가 늘어나면서 여러 문제들이 나타나기 시작했다.</p>
<h3 id="✅-모놀리식-구조의-불편한-점">✅ 모놀리식 구조의 불편한 점</h3>
<h4 id="1️⃣-패키지-버전-관리의-어려움">1️⃣ 패키지 버전 관리의 어려움</h4>
<p>각 서비스마다 사용하는 라이브러리와 패키지의 버전이 다르게 설정되어 있다. 특정 서비스에서는 최신 버전을 사용하고, 다른 서비스에서는 구버전을 유지하는 식으로 일관성이 깨졌다.</p>
<h4 id="2️⃣-공통-ui-및-로직-중복-문제">2️⃣ 공통 UI 및 로직 중복 문제</h4>
<p>여러 서비스에서 공통으로 사용하는 UI 컴포넌트나 로직이 있었지만, 각각의 서비스에 개별적으로 복사해서 사용해야 했다. 이는 유지보수를 어렵게 만들었고, 하나의 변경 사항을 여러 곳에서 반영해야 하는 비효율적인 구조를 초래했다.</p>
<h4 id="3️⃣-새로운-서비스-추가-시-환경-세팅의-번거로움">3️⃣ 새로운 서비스 추가 시 환경 세팅의 번거로움</h4>
<p>새로운 서비스가 추가될 때마다 프로젝트를 처음부터 설정해야 했다. 공통적인 설정이 존재하지 않아 매번 동일한 초기 작업을 반복해야 했고, 이는 개발 생산성을 떨어뜨렸다.</p>
<h4 id="4️⃣-소스-코드-관리의-복잡성">4️⃣ 소스 코드 관리의 복잡성</h4>
<p>모든 서비스가 하나의 레포지토리에서 관리되다 보니, 각 서비스마다 별도의 브랜치를 생성해야 했다. 심지어, 그 브랜치 내에서도 dev와 main을 나눠야 했기 때문에 협업 시 소스 코드 관리를 효율적으로 하기 어려웠다. 브랜치 전략이 복잡해지고, 작은 변경 사항도 여러 서비스에 영향을 미칠 가능성이 높아졌다.</p>
<br />
이러한 문제들이 누적되면서, 모놀리식 레포지토리의 한계를 절감하게 되었고, 이를 해결하기 위해 모노레포(Monorepo) 구조로의 전환을 고민하게 되었다. 

<br />

<hr>
<h2 id="멀티레포로-전환">멀티레포로 전환...</h2>
<p>처음부터 모노레포로 전환하고 싶었지만, 당시 진행 중이던 프로젝트도 있어서 구조 변경에 많은 시간을 투자하기 어려웠다.
게다가, 기존 모놀리식 구조에서 각 서비스의 기술 스택이 제각각이라 이를 한꺼번에 분리하고 종속성을 정리하는 데는 꽤 오랜 시간이 걸릴 것이라고 판단했다.</p>
<p>그래서 우선, 모놀리식 구조에서 발생했던 문제점 중 소스 코드 관리의 복잡성을 먼저 해결하고자 했다.
이를 위해 <strong>멀티레포(Multi-Repo)</strong> 구조로 전환하는 것이 최선이라고 생각했다.</p>
<h3 id="멀티-레포지토리란❓"><strong>멀티 레포지토리란❓</strong></h3>
<blockquote>
<p>멀티 레포지토리(Multi-Repo)란 각 서비스나 모듈을 별도의 레포지토리에서 개별적으로 관리하는 방식이다.
각 서비스는 독립적인 코드베이스를 가지며, 배포 및 종속성 관리도 각각 따로 이루어진다.</p>
</blockquote>
<h3 id="모놀리식-→-멀티레포-전환-과정">모놀리식 → 멀티레포 전환 과정</h3>
<p>모놀리식에서 멀티레포로의 전환 자체는 오래 걸리지 않았다.
이미 기존 모놀리식 구조에서 각 서비스마다 종속성을 개별적으로 관리하고 있었기 때문에, 브랜치로 관리하던 서비스들을 각기 다른 레포지토리로 분리하는 방식으로 진행하면 됐다.</p>
<p>또한, 우리 회사에서는 CI/CD를 Jenkins(젠킨스) 를 통해 관리하고 있었는데,
각 서비스의 레포지토리에 별도의 Jenkins 파일을 두어 관리하는 방식으로 적용할 수 있었다.</p>
<h3 id="✅-멀티레포-전환-후의-변화">✅ 멀티레포 전환 후의 변화</h3>
<p>멀티레포로 전환한 후, 가장 큰 변화는 <strong>서비스별 소스 코드 관리가 훨씬 명확해졌다는 점</strong>이다.
각 서비스가 개별적인 레포지토리를 가지게 되면서,</p>
<ul>
<li><strong>코드 변경 이력이 서비스별로 분리되어 가독성이 좋아졌다.</strong></li>
<li><strong>브랜치 충돌이나 소스 코드 관리 이슈가 줄어들었다.</strong></li>
</ul>
<br />

<p>하지만 각 레포지토리가 독립적으로 관리되면서, 패키지 종속성 관리의 복잡성과 공통 UI 컴포넌트 및 로직의 공유 문제는 여전히 해결되지 않았다.
서비스마다 중복된 코드가 발생했고, 변경 사항을 반영할 때마다 각 레포지토리를 개별적으로 수정해야 하는 번거로움이 있었다.</p>
<p><strong>결국, 유지보수에 드는 비용(=시간)이 점점 커지면서, 
이러한 문제를 근본적으로 해결하기 위해 모노레포(Monorepo) 전환을 다시 고민하게 되었다.</strong></p>
<hr>
<h2 id="모노레포-전환-전-고민했던-것들-🤔">모노레포 전환 전 고민했던 것들 🤔</h2>
<p>모노레포로 전환하면 확실히 공통 코드 관리가 쉬워지겠지만, 구조를 통째로 바꾸는 작업이 만만치 않다는 점이 걸렸다.
서비스마다 독립적으로 운영되던 레포지토리를 하나로 합치는 과정에서 예상보다 많은 리소스가 필요할 것 같았고,
<strong>&quot;모노레포가 아닌 다른 방법으로 위에서 언급한 불편함을 해결할 수는 없을까?&quot;</strong> 라는 고민이 들었다.</p>
<p>특히, 기존 멀티레포 구조에서는</p>
<ul>
<li><strong>패키지 종속성 버전이 일관되지 않음</strong> → 서비스마다 사용하는 라이브러리 버전이 제각각이라 유지보수가 어려움</li>
<li><strong>공통 UI 컴포넌트 및 로직 공유가 어려움</strong> → 각 레포지토리에 중복된 코드가 계속 생김</li>
</ul>
<p>이 두 가지 문제가 가장 컸다.
그래서 다음과 같은 대안들을 고려해봤다.</p>
<br />

<h3 id="📌-서브모듈-방식">📌 서브모듈 방식</h3>
<p><strong>스토리북으로 만들어놓은 공통 UI 컴포넌트나 공통 로직을 각 프로젝트에 서브모듈로 연결해서 사용하는 방식</strong></p>
<p><strong>✅ 장점</strong></p>
<ul>
<li>가장 간단한 해결책이다.</li>
<li>기존 레포지토리 구조를 변경할 필요가 없고, 패키지 배포 없이 Git 서브모듈만 등록하면 된다.</li>
</ul>
<p><strong>❌ 단점</strong></p>
<ul>
<li><strong>업데이트 반영이 번거롭다</strong> → 공통 컴포넌트나 로직에 변경 사항이 생기면, 각 프로젝트에서 직접 서브모듈을 업데이트해야 한다.</li>
<li><strong>패키지 종속성 문제 해결 불가능</strong> → 각 프로젝트의 패키지 버전이 다 제각각이라 종속성 문제를 해결하는 데 도움이 되지 않는다.</li>
<li><strong>초기 설정이 복잡할 수 있다</strong> → 각 프로젝트에 서브모듈을 연결하는 과정이 다소 번거로울 수 있다.</li>
</ul>
<br />

<h3 id="📌-패키지로-배포하여-사내-라이브러리-개발">📌 패키지로 배포하여 사내 라이브러리 개발</h3>
<p><strong>공통 UI 컴포넌트나 공통 로직을 private 패키지로 배포하여, 사내 라이브러리처럼 활용하는 방식</strong></p>
<p><strong>✅ 장점</strong></p>
<ul>
<li>기존 레포지토리 구조를 변경할 필요가 없다.</li>
<li><strong>중앙 집중식으로 관리할 수 있어, 버전 관리가 용이하다.</strong></li>
<li>각 프로젝트에서 원하는 버전의 패키지를 쉽게 설치하거나 업데이트할 수 있다.</li>
</ul>
<p><strong>❌ 단점</strong></p>
<ul>
<li><strong>패키지 종속성 문제 해결이 어려움</strong> → 각 프로젝트가 개별적으로 패키지를 업데이트해야 해서, 모든 서비스가 동일한 버전을 유지하기 어렵다.</li>
<li><strong>배포 및 관리의 자동화가 필요하다</strong> → 컴포넌트 업데이트 후 패키지를 배포하는 과정을 자동화하거나, 수동으로 관리해야 한다.</li>
<li><strong>진입장벽이 높다.</strong> → 우리 팀에서는 아직 패키지를 배포해본 경험이 없어, 이를 처음 도입하는 데 시간이 필요할 것 같다.</li>
</ul>
<p><strong>💰 추가 비용 고려</strong></p>
<ul>
<li>NPM 패키지 (Private 기준) : 인당 매달 $7</li>
<li>GitHub 패키지 (Private 기준) : 500MB까지 무료, 그 이상은 유료</li>
</ul>
<br />

<p>이런 대안들을 고민해봤지만, 여전히 패키지 종속성 문제와 공통 코드 관리의 번거로움이 해결되지 않았다.</p>
<blockquote>
<ul>
<li>서브모듈 방식은 업데이트 반영이 번거롭고, 패키지 종속성 문제를 해결할 수 없었다.</li>
</ul>
</blockquote>
<ul>
<li>사내 라이브러리 배포 방식은 중앙 관리가 가능하지만, 모든 프로젝트의 패키지 버전을 통일하는 문제까지 해결하지는 못하고 추가적인 비용도 발생된다.</li>
</ul>
<br />

<p>*<em>결국, 장기적으로 유지보수 비용을 줄이고, 패키지 종속성과 공통 코드 관리 문제를 동시에 해결하기 위해 모노레포 전환이 필요하다고 판단했다.
*</em></p>
<hr>
<h2 id="결국-돌고-돌아-모노레포로️">결국, 돌고 돌아 모노레포로‼️</h2>
<p>다양한 해결책을 고민해봤지만, 장기적인 유지보수 효율성과 개발 생산성을 고려해
결국 <strong>모노레포(Monorepo)</strong>로 전환하기로 결정했다.</p>
<h3 id="모노-레포지토리란❓">모노 레포지토리란❓</h3>
<blockquote>
<p><strong>모노레포(Monorepo) 는 여러 개의 프로젝트(서비스)를 하나의 레포지토리에서 관리하는 방식을 말한다.</strong>
독립적인 애플리케이션이더라도 공통된 코드, 패키지, 설정 등을 쉽게 공유할 수 있어, 협업과 유지보수의 효율성이 높일 수 있다.</p>
</blockquote>
<br />

<h3 id="✅-모노레포-도입-시-얻을-수-있는-이점들">✅ 모노레포 도입 시 얻을 수 있는 이점들</h3>
<h4 id="1️⃣-공통-코드공통-ui-유틸-함수-등의-일관된-관리">1️⃣ 공통 코드(공통 UI, 유틸 함수 등)의 일관된 관리</h4>
<p>🔹 별도의 패키지 배포 없이 같은 레포지토리 내에서 즉시 공유 가능
🔹 모든 서비스에서 동일한 버전의 공통 코드를 사용하므로, 불필요한 버전 충돌 발생 방지</p>
<blockquote>
<p><strong>기존 방식</strong></p>
</blockquote>
<ul>
<li>공통 UI/유틸을 각 프로젝트에 중복 추가로 인해 코드 </li>
<li>코드 수정 시 모든 프로젝트를 개별적으로 수정해 유지보수 비용 증가</li>
</ul>
<blockquote>
<p><strong>모노레포 전환 후</strong></p>
</blockquote>
<ul>
<li>단일 코드베이스에서 직접 참조하여 코드 관리 가능</li>
<li>공통 코드 수정 시 한 곳에서만 변경하면 모든 서비스에 자동 반영</li>
</ul>
<br />

<h4 id="2️⃣--패키지-종속성-관리가-간편해짐">2️⃣  패키지 종속성 관리가 간편해짐</h4>
<p>🔹 의존성(Dependencies)을 중앙에서 통합적으로 관리할 수 있음
🔹 패키지 업데이트 시 모든 서비스가 동일한 버전 유지 가능
🔹 중복 설치된 패키지를 줄여서 빌드 최적화</p>
<blockquote>
<p><strong>기존 방식</strong></p>
</blockquote>
<ul>
<li>프로젝트마다 종속성이 달라서 버전 충돌 및 호환성 문제가 자주 발생</li>
<li>신규 패키지를 추가할 때 각 레포지토리에서 따로 설치해야 했음</li>
</ul>
<blockquote>
<p><strong>모노레포 전환 후</strong></p>
</blockquote>
<ul>
<li>하나의 <code>package.json</code>에서 공통 의존성을 통합 관리</li>
<li>모든 서비스에서 동일한 패키지 버전 사용 가능</li>
<li>빌드 시 패키지를 공유하므로, 빌드 속도 최적화</li>
</ul>
<br />


<h4 id="3️⃣--개발-환경-설정의-일관성-유지">3️⃣  개발 환경 설정의 일관성 유지</h4>
<p>🔹 서비스마다 일일이 환경을 설정할 필요 없이 동일한 도구와 설정을 적용 가능
🔹 신규 서비스 추가 시 복잡한 초기 환경 세팅 없이 빠르게 시작 가능</p>
<blockquote>
<p><strong>기존 방식</strong></p>
</blockquote>
<ul>
<li>프로젝트마다 <strong>ESLint, Prettier, Webpack/Vite</strong> 설정이 제각각</li>
<li>신규 프로젝트를 만들 때 환경 설정을 처음부터 다시 해야 했음</li>
</ul>
<blockquote>
<p><strong>모노레포 전환 후</strong></p>
</blockquote>
<ul>
<li>공통 설정을 공유하여 일관된 개발 환경 유지</li>
<li>신규 프로젝트 추가 시 기존에 설정된 환경을 그대로 가져와 초기 세팅 없이 바로 개발 가능</li>
</ul>
<hr>
<h2 id="모노레포로-전환-🚀">모노레포로 전환! 🚀</h2>
<p>이러한 장점들을 고려해 최종적으로 <strong>모노레포로 전환</strong>하기로 결정했고,
현재는 <strong>Turorepbo</strong>를 활용하여 모노레포 환경을 구축한 상태다.</p>
<p>Turborepo를 사용하면</p>
<ul>
<li><strong>패키지 캐싱을 활용한 빌드 속도 최적화</strong></li>
<li><strong>각 프로젝트 간 종속성 관리 자동화</strong></li>
<li><strong>공통 패키지 및 설정 공유</strong></li>
</ul>
<p>등의 장점이 있어, 대규모 서비스에서도 효율적으로 활용할 수 있다.
<Br /></p>
<h3 id="모노레포-전환-과정과-트러블슈팅은-다음-포스팅에서">모노레포 전환 과정과 트러블슈팅은 다음 포스팅에서!</h3>
<p>오늘은 모노레포로 전환하기까지의 고민 과정을 정리한 포스팅이므로,
실제 모노레포로 전환하는 과정과 그 과정에서 겪었던 트러블슈팅은 이후 포스팅에서 자세히 다뤄보겠다. 😉</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Actions으로 CI/CD 구축하기]]></title>
            <link>https://velog.io/@minyoung_/Github-Actions%EC%9C%BC%EB%A1%9C-CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@minyoung_/Github-Actions%EC%9C%BC%EB%A1%9C-CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 05 Mar 2025 16:25:57 GMT</pubDate>
            <description><![CDATA[<h2 id="1-cicd란❓">1. CI/CD란❓</h2>
<p>CI/CD는 <strong>Continuous Integration(지속적인 통합)</strong>과 <strong>Continuous Deployment/Delivery(지속적인 배포/제공)</strong>의 약자로, 소프트웨어 개발에서 코드 변경을 자동으로 빌드, 테스트, 배포하는 프로세스를 의미한다. 이를 통해 개발자는 더욱 빠르고 안정적으로 제품을 출시할 수 있다.</p>
<h3 id="11-✅-cicontinuous-integration">1.1 ✅ CI(Continuous Integration)</h3>
<p>지속적인 통합이라는 의미로, 개발자가 작업한 코드를 주기적으로 빌드하고 테스트한 후, 메인 레포지토리에 병합(merge)하는 과정을 말한다.</p>
<p><strong>🔹 CI의 핵심 목표:</strong></p>
<ul>
<li>개발자들이 짧은 주기로 코드를 병합함으로써 코드 충돌을 최소화</li>
<li>자동화된 테스트를 통해 버그를 빠르게 감지 및 수정</li>
<li>안정적인 코드 상태 유지</li>
<li>배포 전 코드 품질을 지속적으로 검증</li>
</ul>
<h3 id="12-✅-cdcontinuous-deploy-continuous-delivery란">1.2 ✅ CD(Continuous Deploy, Continuous Delivery)란?</h3>
<p>CD는 <strong>Continuous Deployment(지속적인 배포)</strong>와 Continuous Delivery(지속적인 제공) 두 가지 의미를 포함하며, CI 이후의 배포 과정을 자동화하는 단계이다.</p>
<p><strong>🔹 CD의 주요 목적:</strong></p>
<ul>
<li>코드를 빠르고 안전하게 프로덕션 환경에 배포</li>
<li>배포 과정의 자동화를 통해 수작업 부담 감소</li>
<li>빠른 피드백을 반영하여 사용자 경험 개선</li>
</ul>
<p><strong>📌 CD의 두 가지 형태</strong></p>
<p><strong>Continuous Delivery(지속적인 제공)</strong></p>
<ol>
<li>CI 이후 자동으로 배포 가능한 상태까지 준비하지만, 실제 배포는 사람이 수동으로 결정</li>
<li>예: QA 팀의 검토 후 프로덕션에 배포</li>
</ol>
<p><strong>Continuous Deployment(지속적인 배포)</strong></p>
<ol>
<li>코드가 테스트를 통과하면 자동으로 프로덕션 환경에 배포</li>
<li>빠른 반복과 신속한 기능 제공이 가능하지만, 높은 수준의 자동화가 필요</li>
</ol>
<hr>
<h2 id="2-github-actions란❓">2. Github Actions란❓</h2>
<p>GitHub Actions는 Github에서 제공해주는 CI/CD(연속 통합 및 지속적인 배포)를 자동화할 수 있는 서비스이다. GitHub에서 코드를 관리하고 있는 소프트웨어 프로젝트에서 사용할 수 있으며 개인은 누구나 GitHub에서 코드 저장소를 무료로 만들 수 있기 때문에 다른 CI/CD 서비스 대비 진입장벽이 낮은 편이다.</p>
<p>GitHub Actions는 기존 CI/CD 서비스 대비 간편한 설정과 높은 접근성으로 특히 개발자들 사이에서 많은 호응을 얻고 있다. 예전에는 CI/CD가 DevOps 엔지니어의 전유물로만 여겨지곤 했었는데, GitHub Actions을 통해서 일반 개발자들도 스스로 CI/CD 설정을 어렵지 않게 할 수 있다.</p>
<h4 id="✅-github-actions을-활용한-cicd-전체-흐름">✅ Github Actions을 활용한 CI/CD 전체 흐름</h4>
<blockquote>
<ol>
<li>개발자가 코드를 수정하고 GitHub에 push</li>
<li>GitHub Actions Workflow가 자동으로 실행되어 빌드 및 테스트 진행</li>
<li>테스트에 통과하면 PR을 병합하거나 자동으로 배포 프로세스 진행</li>
<li>GitHub Actions가 스테이징 또는 프로덕션 환경에 배포</li>
<li>배포 후 GitHub Actions에서 모니터링 및 오류 감지 (Slack, Discord 알림 연동 가능)</li>
</ol>
</blockquote>
<hr>
<h2 id="3-github-actions-핵심-개념">3. Github Actions 핵심 개념</h2>
<h3 id="31-workflow-job-step-action-개념">3.1 Workflow, Job, Step, Action 개념</h3>
<h4 id="📌-workflow">📌 Workflow</h4>
<ul>
<li>GitHub Actions에서 가장 상위 개념으로,** 자동화된 작업의 전체 흐름**을 의미한다.</li>
<li><code>.github/workflows/</code> 폴더 내의 YAML 파일로 설정하며, 여러 개의 Workflow를 만들 수 있다.</li>
<li>특정 이벤트(<code>push, pull_request, schedule</code> 등)를 트리거로 실행된다.<pre><code class="language-javascript">name: CI/CD Workflow  # Workflow 이름
on: push  # push 이벤트 발생 시 실행
jobs: 
(생략...)</code></pre>
</li>
</ul>
<h4 id="📌-job">📌 Job</h4>
<ul>
<li><strong>Workflow 내부에서 실행되는 작업 단위</strong></li>
<li>하나의 Workflow는 여러 개의 Job을 포함할 수 있으며, 각 Job은 독립적으로 실행되거나 순차적으로 실행될 수 있다.</li>
<li><code>runs-on</code>을 사용하여 실행 환경(예: <code>ubuntu-latest, windows-latest</code>)을 지정한다.</li>
</ul>
<pre><code class="language-javascript">jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Run Build
        run: echo &quot;Building the project...&quot;</code></pre>
<p>위 예제에서 <code>build</code> Job이 실행되며, <code>ubuntu-latest</code> 환경에서 실행된다.</p>
<h4 id="📌-step">📌 Step</h4>
<ul>
<li>Job 내부에서 실행되는 단위 작업</li>
<li>여러 개의 Step이 모여 하나의 Job을 구성한다.</li>
<li><code>uses</code>를 사용하여 미리 정의된 Action을 실행하거나, <code>run</code>을 사용하여 직접 명령어를 실행할 수 있다.</li>
</ul>
<pre><code class="language-javascript">jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3  # 미리 정의된 Action 사용
      - name: Run Tests
        run: npm test  # 직접 실행할 명령어</code></pre>
<h4 id="📌-action">📌 Action</h4>
<ul>
<li>GitHub Actions에서 사용되는 개별 작업 모듈</li>
<li>Step에서 <code>uses</code>를 통해 호출하며, 공식 GitHub Actions Marketplace에서 제공하는 Action을 사용할 수도 있고, 직접 생성할 수도 있다.</li>
<li>예: <code>actions/checkout</code>, <code>actions/setup-node</code></li>
</ul>
<pre><code class="language-javascript">steps:
  - name: Checkout Code
    uses: actions/checkout@v3  # 레포지토리 코드 다운로드
  - name: Setup Node.js
    uses: actions/setup-node@v3
    with:
      node-version: 18  # Node.js 버전 지정</code></pre>
<ul>
<li><code>actions/checkout@v3</code> → GitHub 레포지토리의 코드를 가져오는 Action</li>
<li><code>actions/setup-node@v3</code> → Node.js 환경을 설정하는 Action</li>
</ul>
<h3 id="32-runner와-실행-환경">3.2 Runner와 실행 환경</h3>
<h4 id="📌-runner란">📌 Runner란?</h4>
<ul>
<li>GitHub Actions의 Workflow를 실행하는 서버 또는 머신</li>
<li>GitHub이 제공하는 호스팅 Runner와, 직접 관리하는 Self-hosted Runner 두 가지 방식이 있다.</li>
<li>Workflow에서 <code>runs-on</code>을 설정하여 어떤 Runner에서 실행할지 지정할 수 있다.</li>
</ul>
<h4 id="📌-github-호스팅-runner">📌 GitHub 호스팅 Runner</h4>
<ul>
<li>GitHub에서 제공하는 머신으로, 자동으로 제공되어 따로 설치할 필요가 없다. </li>
<li>환경에 따라 <code>ubuntu-latest(기본값), windows-latest, macos-latest</code> 등
지정할 수 있다. </li>
</ul>
<pre><code class="language-javascript">jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    (생략...)</code></pre>
<h4 id="📌-self-hosted-runner">📌 Self-hosted Runner</h4>
<ul>
<li>사용자가 직접 운영하는 머신으로, 직접 설치가 필요하다. </li>
<li>특정 서버에 <code>self-hosted Runner</code> 등록 후 사용이 가능하다.</li>
</ul>
<pre><code class="language-javascript">jobs:
  deploy:
    runs-on: self-hosted  # 직접 운영하는 Runner 사용</code></pre>
<hr>
<h2 id="4-github-actions-기본-설정">4. Github Actions 기본 설정</h2>
<h3 id="41-githubworkflows-폴더와-yaml-파일">4.1 <code>.github/workflows</code> 폴더와 YAML 파일</h3>
<ul>
<li>GitHub Actions의 Workflow 설정 파일은 <code>.github/workflows/</code> 폴더에 위치한다.</li>
<li>Workflow는 <strong>YAML(.yml 또는 .yaml)</strong> 파일로 작성되며, 여러 개의 Workflow 파일을 만들 수 있다.<h4 id="📌-예시-폴더-구조">📌 예시: 폴더 구조</h4>
<pre><code>📦 프로젝트 루트
┣ 📂 .github
┃ ┗ 📂 workflows
┃   ┣ 📜 ci.yml  # CI 관련 Workflow
┃   ┣ 📜 cd.yml  # CD 관련 Workflow
┃   ┗ 📜 deploy.yml  # 배포 관련 Workflow</code></pre></li>
</ul>
<h3 id="42-workflow-트리거이벤트">4.2 Workflow 트리거(이벤트)</h3>
<ul>
<li>GitHub Actions는 특정 이벤트 발생 시 자동으로 실행되며, 주요 트리거는 다음과 같다.</li>
<li><code>push</code> : 브랜치에 코드가 푸시될 때 실행</li>
<li><code>pull_request</code> : PR이 생성, 업데이트, 병합될 때 실행</li>
<li><code>schedule</code> : 일정 시간마다 자동 실행 (CRON 방식)</li>
<li><code>workflow_dispatch</code> : 수동 실행 (GitHub UI에서 실행 가능)</li>
</ul>
<pre><code class="language-javascript">on:
  push:
    branches:
      - main  # main 브랜치에 푸시될 때 실행
  pull_request:
    branches:
      - main  # main 브랜치로 PR이 생성될 때 실행
  schedule:
    - cron: &quot;0 0 * * *&quot;  # 매일 자정에 실행 (UTC 기준)
  workflow_dispatch:  # 수동 실행
</code></pre>
<ul>
<li><code>push</code> → main 브랜치에 코드가 푸시되면 실행</li>
<li><code>pull_request</code>  → main 브랜치에 대한 PR이 생성되면 실행</li>
<li><code>schedule</code> → 매일 자정(UTC 00:00)에 실행</li>
<li><code>workflow_dispatch</code> → GitHub Actions에서 직접 실행 버튼을 눌러 실행</li>
</ul>
<h3 id="43-기본적인-workflow-예제">4.3 기본적인 Workflow 예제</h3>
<pre><code class="language-javascript">name: CI Workflow  # Workflow 이름

on: push  # push 이벤트 발생 시 실행

jobs:
  build:
    runs-on: ubuntu-latest  # 실행 환경 지정

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3  # 코드 가져오기

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 18  # Node.js 18 버전 사용

      - name: Install Dependencies
        run: npm install  # 패키지 설치

      - name: Run Tests
        run: npm test  # 테스트 실행
</code></pre>
<p>이렇게 설정하면 코드가 푸시될 때마다 자동으로 빌드 및 테스트가 실행된다! 🚀</p>
<hr>
<h2 id="5-ci-continuous-integration-구축하기">5. CI (Continuous Integration) 구축하기</h2>
<h3 id="51-ci-구축하기">5.1 CI 구축하기</h3>
<p>CI(지속적 통합)는 코드가 변경될 때마다 자동으로 빌드, 테스트, 코드 스타일 검사를 수행하여 품질을 유지하는 과정이다.</p>
<h4 id="✅-ci-구축-기본-흐름">✅ CI 구축 기본 흐름</h4>
<ol>
<li>개발자가 코드를 변경하고 push 또는 PR 생성</li>
<li>GitHub Actions가 자동으로 빌드 및 테스트 실행</li>
<li>테스트 통과 시 코드 병합 가능 (PR에서 확인 가능)</li>
<li>테스트 실패 시 PR에 오류 표시, 개발자가 수정 후 다시 실행</li>
</ol>
<pre><code class="language-javascript">name: CI Pipeline

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build-test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 18

      - name: Install Dependencies
        run: npm install

      - name: Run Linter
        run: npm run lint  # ESLint 실행

      - name: Run Tests
        run: npm test  # Jest 테스트 실행
</code></pre>
<hr>
<h2 id="6-cd-continuous-deployment-구축하기">6. CD (Continuous Deployment) 구축하기</h2>
<h3 id="61-cd-구축하기">6.1 CD 구축하기</h3>
<p>CD(지속적 배포)는 CI 과정이 끝난 후, 테스트를 통과한 코드가 자동으로 배포되는 과정이다.</p>
<h4 id="✅-cd-구축-흐름">✅ CD 구축 흐름</h4>
<ol>
<li>코드 변경 → CI 과정 실행 (빌드 &amp; 테스트)</li>
<li>테스트 통과 시 자동으로 배포</li>
</ol>
<h4 id="📌-예제-aws-ec2--nginx로-배포">📌 예제: AWS EC2 + Nginx로 배포</h4>
<pre><code class="language-javascript">name: CD Pipeline

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3

      - name: Install Dependencies
        run: npm install

      - name: Build Project
        run: npm run build

      - name: Deploy to EC2
        uses: appleboy/ssh-action@v0.1.6
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USER }}
          key: ${{ secrets.EC2_PRIVATE_KEY }}
          script: |
            cd /home/ec2-user/app
            git pull origin main
            npm install
            npm run build
            pm2 restart all
</code></pre>
<ul>
<li>appleboy/ssh-action을 사용해 EC2에 SSH로 접속 후 배포</li>
<li>secrets를 이용해 민감한 정보 보호</li>
</ul>
<h3 id="62-secrets-설정-및-환경-변수-관리">6.2 Secrets 설정 및 환경 변수 관리</h3>
<p>배포 과정에서 API 키, SSH 키, DB 정보 같은 중요한 정보는 GitHub Secrets에 저장하여 관리한다.</p>
<h4 id="✅-secrets-설정-방법">✅ Secrets 설정 방법</h4>
<ol>
<li><p>GitHub 저장소 → Settings → Secrets → New repository secret</p>
</li>
<li><p>환경변수 추가</p>
</li>
</ol>
<ul>
<li>EC2_HOST: EC2 서버 주소</li>
<li>EC2_USER: EC2 접속 사용자</li>
<li>EC2_PRIVATE_KEY: SSH 접속 키</li>
</ul>
<p>참고로 환경변수의 변수명은 사용자 임의로 지어도 된다. </p>
<h4 id="✅-yaml에서-secrets-사용-예시">✅ YAML에서 Secrets 사용 예시</h4>
<pre><code class="language-javascript">with:
  host: ${{ secrets.EC2_HOST }}
  username: ${{ secrets.EC2_USER }}
  key: ${{ secrets.EC2_PRIVATE_KEY }}</code></pre>
<h4 id="✅-env-파일을-github-actions에서-사용하는-방법">✅ .env 파일을 GitHub Actions에서 사용하는 방법</h4>
<ol>
<li><code>.env</code> 파일을 저장소에 올리지 않고 <strong>GitHub Secrets</strong>에 등록</li>
<li><code>secrets</code>에서 .env 파일을 생성 후 사용</li>
</ol>
<pre><code class="language-javascript">- name: Create .env file
  run: |
    echo &quot;API_KEY=${{ secrets.API_KEY }}&quot; &gt;&gt; .env
    echo &quot;DB_URL=${{ secrets.DB_URL }}&quot; &gt;&gt; .env</code></pre>
<ol start="3">
<li>이후 빌드 및 실행 과정에서 .env 사용 가능</li>
</ol>
<br />
<br />

<h4 id="출처">출처</h4>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=0Emq5FypiMM&amp;t=2s">https://www.youtube.com/watch?v=0Emq5FypiMM&amp;t=2s</a>
<a href="https://docs.github.com/ko/actions/about-github-actions/about-continuous-integration-with-github-actions">https://docs.github.com/ko/actions/about-github-actions/about-continuous-integration-with-github-actions</a>
<a href="https://www.daleseo.com/github-actions-basics/">https://www.daleseo.com/github-actions-basics/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Storybook 배포 자동화: Chromatic with GitHub Actions]]></title>
            <link>https://velog.io/@minyoung_/Chromatic%EC%9C%BC%EB%A1%9C-Storybook-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@minyoung_/Chromatic%EC%9C%BC%EB%A1%9C-Storybook-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 26 Feb 2025 16:30:34 GMT</pubDate>
            <description><![CDATA[<h3 id="서론">서론</h3>
<p>Storybook은 UI 컴포넌트를 독립적으로 개발하고 테스트할 수 있는 강력한 도구다. 그런데 개발만큼 중요한 게 바로 공유와 협업이다. 개발한 컴포넌트를 디자인팀이나 PM이 바로 확인할 수 있어야 하고, 수정이 필요한 부분이 있으면 빠르게 피드백을 주고받을 수 있어야 한다.</p>
<p>보통 이런 정적 웹페이지를 배포하려면 Vercel이나 Netlify 같은 호스팅 서비스를 사용하면 된다. 하지만 Storybook에 특화된 배포 솔루션이 있다면 더 편하지 않을까? Chromatic을 통해 Storybook을 배포해보자!</p>
<h2 id="storybook--chromatic-조합은-어떤게-좋을까❓">Storybook + Chromatic 조합은 어떤게 좋을까❓</h2>
<p><strong>1. 배포가 자동으로 된다</strong>
Chromatic을 설정하면 GitHub에 코드만 푸시해도 Storybook이 자동으로 배포된다. 따로 빌드하고 배포하는 과정 없이, 개발자가 코드를 수정하면 변경된 UI가 바로 적용된 Storybook을 볼 수 있다. </p>
<p><strong>2. 시각적 테스트 (Visual Test) 지원</strong>
코드를 수정할 때 UI가 깨지는 경우가 많다. 그런데 작은 디자인 변화가 코드 레벨에서는 티가 잘 안 나서 놓치는 경우도 있다. Chromatic은 비교 기능을 제공해서, 변경 전후 UI를 이미지로 비교해준다. 덕분에 예상치 못한 디자인 깨짐(Regression)도 쉽게 발견할 수 있다.</p>
<p><strong>3. 디자인팀과 협업이 편리하다</strong>
Chromatic은 단순히 배포만 되는 게 아니라, 디자인팀이 직접 확인하고 댓글도 남길 수 있다. 예를 들어 버튼 색상이 마음에 안 들면 Storybook에서 바로 피드백을 남길 수 있다. 개발팀은 이 피드백을 보고 바로 수정하면 된다. 따로 Figma 캡처해서 공유할 필요 없이, 실시간으로 의견을 주고받을 수 있는 거다.</p>
<p><strong>4. 접근성이 좋다</strong>
배포된 Storybook 링크만 있으면 누구나 접속해서 확인할 수 있다. 개발팀뿐만 아니라 기획자, 마케터, QA팀도 바로 들어와서 보면서 의견을 줄 수 있다. UI 검토 과정이 훨씬 매끄러워진다.</p>
<blockquote>
<p>Storybook과 Chromatic을 같이 쓰면 배포, 시각적 테스트, 피드백, 협업이 한 번에 해결된다. 개발팀도 편하고, 디자인팀도 편하고, 결국 서비스 품질까지 올라간다. 그래서 Storybook을 쓰는 팀이라면 Chromatic을 같이 쓰는 걸 강력 추천한다! 🚀</p>
</blockquote>
<h2 id="chromatic으로-storybook-배포하는-방법-🚀">Chromatic으로 Storybook 배포하는 방법 🚀</h2>
<h3 id="0chromatic-chromatic-와-github-repository-연동">0.Chromatic Chromatic 와 Github Repository 연동</h3>
<br />

<p>0.1 깃허브  계정으로 chromatic에 가입한다.
<img src="https://velog.velcdn.com/images/minyoung_/post/ad32c118-b572-46ee-97f3-9dc70eaf95ca/image.png" alt=""></p>
<p>0.2  Choose Github Repo 버튼을 눌러 Github Repository와 동기화 한다.
<img src="https://velog.velcdn.com/images/minyoung_/post/52bfae5b-20cc-4c79-90f1-5d5ee1697882/image.png" alt=""></p>
<p>0.3 프로젝트 설정을 Storybook으로 선택한다.
<img src="https://velog.velcdn.com/images/minyoung_/post/12e781ed-ad77-4dea-baa1-5dc73b0e9263/image.png" alt=""></p>
<p>0.4 고유한 project-token을 복사해준다.
<img src="https://velog.velcdn.com/images/minyoung_/post/52471407-c014-4d79-a2eb-fc8d88957133/image.png" alt=""></p>
<h3 id="1-프로젝트에-chromatic-설치">1. 프로젝트에 Chromatic 설치</h3>
<ul>
<li>Storybook 프로젝트에 Chromatic 패키지를 설치한다.</li>
<li>설치를 위해 아래 명령어를 입력한다.</li>
<li><code>npm install --save-dev chromatic</code></li>
</ul>
<br />

<h3 id="2-chromatic에-프로젝트-연결">2. Chromatic에 프로젝트 연결</h3>
<ul>
<li><p>Storybook 프로젝트를 Chromatic에 연결하려면  아래 명령어를 실행한다.</p>
</li>
<li><p><code>npx chromatic --project-token=&lt;your-project-token&gt;</code></p>
</li>
<li><p>여기서 <code>&lt;your-project-token&gt;</code>은 아까 복사한 고유한 token 값을 사용하면 된다.</p>
</li>
</ul>
<ul>
<li>명령어 실행 후 아래와 같은 메시지가 나타나는데, 여기서 y를 입력하면 package.json의 scripts에 chromatic 명령어가 추가된다.</li>
</ul>
<pre><code class="language-javascript">⚠ No &#39;chromatic&#39; script found in your package.json  
Would you like me to add it for you? [y/N]</code></pre>
<br />

<h3 id="3-storybook-빌드-후-chromatic에-배포">3. Storybook 빌드 후 Chromatic에 배포</h3>
<ul>
<li>이제 Storybook을 빌드하고 Chromatic에 배포를 한다.</li>
<li><code>npx chromatic</code></li>
</ul>
<blockquote>
<p>이 명령어를 실행하면:</p>
</blockquote>
<ol>
<li>Storybook이 빌드됨</li>
<li>Chromatic 서버에 배포됨</li>
<li>공유 가능한 URL이 생성됨</li>
</ol>
<ul>
<li>빌드가 완료되면 Chromatic 페이지에서 배포된 결과를 확인할 수 있다.</li>
<li>이제 해당 URL을 디자인팀이나 다른 팀원들에게 공유하면 끝이다!!!</li>
</ul>
<br />

<h2 id="github-actions-사용하여-배포-자동화하기🚀">GitHub Actions 사용하여 배포 자동화하기🚀</h2>
<p>Chromatic을 GitHub Actions과 연동하면, 코드가 변경될 때마다 Storybook을 자동으로 배포할 수 있다. 이렇게 하면 배포 과정을 수동으로 실행할 필요 없이 항상 최신 UI를 유지할 수 있다.</p>
<h3 id="1github-chromatic-token-생성">1.GitHub Chromatic Token 생성</h3>
<p>먼저, GitHub Actions에서 Chromatic을 실행할 수 있도록 Chromatic Token을 GitHub Secrets에 추가해야 한다.</p>
<ol>
<li><p>Chromatic Dashboard에서 프로젝트의 project-token을 확인한다.</p>
</li>
<li><p>GitHub 저장소로 이동하여 <strong>Settings &gt; Secrets and variables &gt; Actions</strong> 로 들어간다.</p>
</li>
<li><p><strong>New repository secret</strong>을 클릭하고:</p>
<ul>
<li><p>Name: CHROMATIC_PROJECT_TOKEN(다른 변수명으로 해도 됨)</p>
</li>
<li><p>Value: 아까 복사한 project-token 값 입력</p>
</li>
<li><p><strong>Add secret</strong> 버튼 클릭</p>
</li>
</ul>
</li>
</ol>
<p>이제 GitHub Actions에서 사용할 준비가 완료되었다.</p>
<h3 id="2github-actions-workflow-생성">2.GitHub Actions Workflow 생성</h3>
<p>GitHub Actions에서 Storybook을 자동으로 배포하려면, <code>.github/workflows/chromatic.yml</code> 파일을 만들어야 한다.</p>
<p><strong>📌 chromatic.yml 파일 생성</strong></p>
<pre><code class="language-javascript">name: &#39;Deploy Storybook to Chromatic&#39;

on:
  push:
    branches:
      - main  # main 브랜치에 푸시될 때 실행
  pull_request:
    branches:
      - main  # PR이 생성될 때 실행

env:
  CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

jobs:
  chromatic-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Install dependencies
        run: npm install

      - name: Build Storybook
        run: npm run build-storybook

      - name: Publish to Chromatic
        run: npx chromatic --project-token=${{ secrets.CHROMATIC_PROJECT_TOKEN }}</code></pre>
<h3 id="3-github-actions-실행-확인">3. GitHub Actions 실행 확인</h3>
<p>이제 main 브랜치에 코드가 푸시되거나 PR이 생성되면 자동으로 Chromatic에 Storybook이 배포된다. 실행 결과는 GitHub Actions의 <strong>Actions</strong> 탭에서 확인할 수 있다.</p>
<ul>
<li><p><strong>✅ 성공하면 Storybook이 배포된 URL이 표시됨</strong></p>
</li>
<li><p><strong>❌ 실패하면 로그를 보고 수정하면 됨</strong></p>
</li>
</ul>
<br />

<h3 id="출처">출처</h3>
<blockquote>
<p><a href="https://storybook.js.org/tutorials/intro-to-storybook/react/ko/deploy/">https://storybook.js.org/tutorials/intro-to-storybook/react/ko/deploy/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인시스템 도입]]></title>
            <link>https://velog.io/@minyoung_/%EB%94%94%EC%9E%90%EC%9D%B8%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%8F%84%EC%9E%85</link>
            <guid>https://velog.io/@minyoung_/%EB%94%94%EC%9E%90%EC%9D%B8%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%8F%84%EC%9E%85</guid>
            <pubDate>Tue, 25 Feb 2025 16:50:47 GMT</pubDate>
            <description><![CDATA[<h3 id="0-서론">0. 서론</h3>
<p>이번에 회사 서비스의 개발 일정이 어느 정도 마무리되어 시간이 남았다. 그동안 바쁘게 개발하느라 하지 못했던 코드 리팩토링을 진행하면서, 협업 시 불편했던 부분을 개선하기 위해 디자인시스템 도입을 추진하게 되었다.
<br /></p>
<h2 id="1-디자인시스템을-왜-도입하지❓">1. 디자인시스템을 왜 도입하지❓</h2>
<p>회사에 Storybook을 도입하게 된 이유는 다음과 같다.</p>
<h4 id="11-비효율적인-ui-확인-과정"><strong>1.1 비효율적인 UI 확인 과정</strong></h4>
<ul>
<li>디자이너와 협업 시, 로컬 환경에서 UI 컴포넌트를 직접 코드로 실행해야만 확인할 수 있었다.</li>
<li>이와 같은 과정을 1년 넘게 반복되면서  많은 시간(=비용)이 소모됨을 깨달았다.</li>
</ul>
<h4 id="12-중복-코드-증가">1.2 중복 코드 증가</h4>
<ul>
<li>회사 내 7개의 서비스에서 동일한 UI 컴포넌트가 많이 사용되었다. </li>
<li>하지만 CDD(Component-Driven Development) 방식을 적용하지 않아, 각 서비스별로 UI 컴포넌트를 개별적으로 관리해야만 했다.</li>
<li>이로 인해 중복 코드가 많아지며, 불필요한 유지보수 시간(=비용)이 들었다.</li>
</ul>
<h4 id="13-ui-일관성-부족">1.3 UI 일관성 부족</h4>
<ul>
<li>서비스가 늘어나면서, 신규 서비스마다 UI가 계속 변경되었다.</li>
<li>이는 일관되지 않은 UI는 브랜드 신뢰도 저하로 이어질 가능성이 있다.</li>
</ul>
<p>이러한 문제들을 해결하기 위해 Storybook을 도입하게 되었으며, 이를 통해 개발 효율성 향상, 코드 중복 감소, UI 일관성 유지 등의 긍정적인 효과를 기대하고 있다.
<br /></p>
<h2 id="2-왜-storybook-인지❓">2. 왜 Storybook 인지❓</h2>
<p>디자인 시스템을 구축하기 위해 사용할 수 있는 여러 가지 툴이 있지만, 위에 문제들을 해결하려면 UI 컴포넌트 관리 및 문서화가 가능한 툴이 필요했다. 이를 위해 Storybook, Histoire, Ladle 등을 검토했다.</p>
<h4 id="21-histoire-제외-vue-중심의-툴">2.1 Histoire 제외: Vue 중심의 툴</h4>
<p>Histoire는 Vue 생태계에서 최적화된 UI 문서화 툴로, 성능이 가볍고 빠르다는 장점이 있다. 하지만, 우리 회사의 서비스는 Vue 기반도 일부 있지만, 대다수의 서비스가 React로 구축되어 있다.
➡️ Vue 전용 툴을 도입하는 것은 비효율적이므로 제외했다.</p>
<h4 id="22-ladle-제외-설정이-간단하지만-유지보수-리스크">2.2 Ladle 제외: 설정이 간단하지만 유지보수 리스크</h4>
<p>Ladle은 Storybook보다 가볍고 설정이 간단해 빠르게 적용할 수 있는 장점이 있다. 하지만, 사용자가 많지 않아 레퍼런스가 부족하고 Storybook만큼의 확장성과 커뮤니티 지원이 부족해 이후 유지보수에 어려움이 있을 가능성이 컸다.
➡️ 장기적인 유지보수를 고려해 Ladle은 사용하지 않기로 했다.</p>
<h4 id="23-storybook-선택-확장성과-디자인-툴-연동">2.3 Storybook 선택: 확장성과 디자인 툴 연동</h4>
<p>결과적으로 Storybook이 가장 적합하다고 판단했다. Storybook을 선택한 이유는 다음과 같다.</p>
<p><strong>✅ 디자인 툴과 연동 가능 → Figma, Zeplin 등과 연결하여 디자이너-개발자 협업 효율성 향상
✅ 다양한 애드온 지원 → 접근성 검사(@storybook/addon-a11y), 인터랙션 테스트 등 기능 확장 가능
✅ 넓은 커뮤니티 및 레퍼런스 → 유지보수 및 개선이 용이
✅ CDD(Component-Driven Development) 방식 적용 가능 → 컴포넌트 단위 개발로 중복 코드 감소</strong>
<br /></p>
<h2 id="3-storybook-알아보기-🔍">3. Storybook 알아보기 🔍</h2>
<p>Storybook이 어떤 역할을 하는지 자세히 알아보자.
Storybook은 UI 컴포넌트를 독립적으로 개발하고 문서화할 수 있는 도구로, 개발자와 디자이너 간 협업을 원활하게 만들어준다. 컴포넌트를 하나의 서비스에 종속되지 않고 개별적으로 확인하고 테스트할 수 있어 재사용성과 유지보수성을 높이는 데 효과적이다.</p>
<p><strong>1️⃣ 독립적인 UI 개발 및 테스트</strong>
애플리케이션과 분리된 환경에서 UI 컴포넌트를 개별적으로 개발하고 확인할 수 있다.
다양한 상태(Props, Event 등)를 시뮬레이션하여 실제 서비스에서 발생할 수 있는 문제를 미리 검토할 수 있다.</p>
<p><strong>2️⃣ 문서화 및 디자인 시스템 구축</strong>
Storybook을 사용하면 컴포넌트별 문서화를 자동화할 수 있어, 새로운 팀원이 쉽게 컴포넌트 사용법을 익힐 수 있다.
디자인 시스템을 체계적으로 관리하여 UI의 일관성을 유지할 수 있다.</p>
<p><strong>3️⃣ 디자이너, 개발자, 기획자 간 원활한 협업</strong>
UI를 직접 실행하지 않아도, Storybook을 통해 디자인 피드백을 효율적으로 주고받을 수 있다.
Figma, Zeplin 같은 디자인 툴과 연동하면 디자인과 개발 간의 갭을 줄일 수 있다.</p>
<p><strong>4️⃣ 다양한 UI 상태 및 반응형 테스트 지원</strong>
Controls, Viewport, a11y(웹 접근성) 등 다양한 애드온을 통해 다양한 환경에서 UI를 미리 검토할 수 있다.
다크 모드, 다국어 지원, 반응형 레이아웃을 쉽게 테스트할 수 있어 더욱 완성도 높은 UI 개발이 가능하다.</p>
<p><strong>5️⃣ 컴포넌트 재사용성 증가</strong>
Storybook을 통해 UI 컴포넌트를 관리하면 여러 프로젝트에서 동일한 컴포넌트를 재사용하기 쉬워진다.
중복 코드 작성이 줄어들어 유지보수 비용을 절감할 수 있다.
<br /></p>
<h2 id="4-storybook으로-디자인시스템-구축하기🤔">4. Storybook으로 디자인시스템 구축하기🤔</h2>
<h3 id="41-storybook-설치">4.1 Storybook 설치</h3>
<p>Storybook을 설치하려면 먼저 프로젝트에 Storybook 패키지를 추가해야 한다.</p>
<pre><code class="language-javascript">npm create storybook@latest</code></pre>
<p>이 명령어로 기본적인 Storybook 설정이 자동으로 추가된다. package.json에 Storybook 관련 스크립트가 추가된다.</p>
<br />

<h3 id="42-스토리-파일">4.2 스토리 파일</h3>
<p>Storybook에서 컴포넌트를 관리하려면 각 컴포넌트별로 스토리 파일을 만들어야 한다. 스토리 파일은 *.stories.js 또는 *.stories.tsx 형태로 작성된다.</p>
<p>예시: Button.stories.js</p>
<pre><code class="language-javascript">import React from &#39;react&#39;;
import { Button } from &#39;./Button&#39;;

export default {
  title: &#39;Example/Button&#39;,
  component: Button,
};

export const Primary = () =&gt; &lt;Button primary&gt;Primary Button&lt;/Button&gt;;
export const Secondary = () =&gt; &lt;Button&gt;Secondary Button&lt;/Button&gt;;</code></pre>
<br />


<h3 id="43-mainjs-설정">4.3 main.js 설정</h3>
<p>main.js는 Storybook의 핵심 설정 파일로, 애드온 추가, 스토리 파일 위치 지정, Webpack 혹은 Vite 설정 등을 할 수 있다.</p>
<pre><code class="language-javascript">module.exports = {
  stories: [&#39;../src/**/*.stories.js&#39;],  // 스토리 파일 위치 지정
  addons: [
    &#39;@storybook/addon-actions&#39;,
    &#39;@storybook/addon-links&#39;,
    &#39;@storybook/addon-knobs&#39;,
  ],  // 사용할 애드온 목록
  webpackFinal: (config) =&gt; {
    // Webpack 커스터마이징 (필요 시)
    return config;
  },
};</code></pre>
<br />

<h3 id="44-previewjs-설정">4.4 preview.js 설정</h3>
<p>preview.js 파일은 Storybook의 컴포넌트를 렌더링할 때 적용할 전역 데코레이터와 설정을 담당한다. 공통 스타일이나 CSS를 설정할 때 사용한다.
글로벌 스타일 및 폰트, 테마 등을 여기서 설정할 수 있다.</p>
<pre><code class="language-javascript">import &#39;../src/styles.css&#39;;  // 글로벌 스타일 적용

export const decorators = [
  (Story) =&gt; (
    &lt;div style={{ padding: &#39;20px&#39;, backgroundColor: &#39;#f0f0f0&#39; }}&gt;
      &lt;Story /&gt;
    &lt;/div&gt;
  ),
];  // 전역 데코레이터 설정</code></pre>
<br />

<h3 id="45-managerjs-설정">4.5 manager.js 설정</h3>
<p>manager.js는 Storybook의 관리 UI를 커스터마이징하는 파일로, 사이드바, 레이아웃, 색상 등을 설정할 수 있다.
Storybook의 테마를 변경하거나 UI의 색상과 레이아웃을 설정할 수 있다.</p>
<pre><code class="language-javascript">import { addons } from &#39;@storybook/addons&#39;;
import { create } from &#39;@storybook/theming&#39;;

addons.setConfig({
  theme: create({
    base: &#39;light&#39;,
    colorPrimary: &#39;#FF4785&#39;,  // 사이드바 색상 설정
    colorSecondary: &#39;#1EA7FD&#39;,
  }),
});</code></pre>
<br />

<h3 id="46-storybook-실행">4.6 Storybook 실행</h3>
<p>아래 명령어로 Storybook을 실행할 수 있다.</p>
<pre><code>npm run storybook</code></pre><p>이 명령어로 로컬 서버에서 Storybook을 실행할 수 있고, 브라우저에서 확인할 수 있다. 기본적으로 <a href="http://localhost:6006%EC%97%90%EC%84%9C">http://localhost:6006에서</a> Storybook을 확인할 수 있다.
<br /></p>
<h3 id="47-애드온-활용">4.7 애드온 활용</h3>
<p>Storybook에서 다양한 애드온을 사용할 수 있다. 예시로 Action 애드온은 컴포넌트에서 발생하는 이벤트를 기록할 수 있다.</p>
<pre><code class="language-javascript">export const ButtonClicked = () =&gt; {
  action(&#39;button-clicked&#39;)();
  return &lt;Button&gt;Click me!&lt;/Button&gt;;
};</code></pre>
<br />

<h4 id="출처">출처</h4>
<blockquote>
<p><a href="https://www.daleseo.com/storybook/">https://www.daleseo.com/storybook/</a>
<a href="https://storybook.js.org/docs/get-started/install">https://storybook.js.org/docs/get-started/install</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 잘하기 위한 8가지 방법]]></title>
            <link>https://velog.io/@minyoung_/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%9E%98%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-8%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@minyoung_/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%9E%98%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-8%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Tue, 04 Feb 2025 11:39:55 GMT</pubDate>
            <description><![CDATA[<h3 id="서론">서론</h3>
<p>리액트라는 UI 라이브러리를 사용한 지 3년이 넘었지만, 그동안 ‘나는 리액트라는 라이브러리를 잘 사용하고 있는 걸까?’ 혹은 ‘리액트를 잘 사용한다는 기준은 어떤걸까?’ 에 대해 깊이 고민해 본 적은 없었다. 그러던 중 우연히 유튜브에서 한 영상을 보게 되었고, 다시 한 번 내 접근 방식을 돌아봐야겠다는 생각이 들었다. 이번 포스팅에서는 그동안의 경험을 되돌아보고, 리액트를 효과적으로 사용하기 위한 몇 가지 중요한 포인트들을 정리해 보고자 한다.</p>
<h2 id="1-상태-관리">1. 상태 관리</h2>
<p>어플리케이션이 복잡해지면 상태의 양도 많아지고, 이를 전역에서 관리하는 것이 어려워질 수 있다. 특히, 계층 구조가 깊은 컴포넌트 트리에서 최하단 컴포넌트에서만 사용되는 상태를 상위 부모 컴포넌트에서 관리하거나, 외부 상태 관리 라이브러리를 과도하게 사용하는 방식은 바람직하지 않다.</p>
<p>상태 관리에 접근할 때는 다음의 세 가지 원칙을 고려할 수 있다.</p>
<h3 id="간단한-상태의-경우"><strong>간단한 상태의 경우</strong></h3>
<p>간단하거나 지역적인 상태는 리액트 내장 API인 useState, useReducer 등을 사용하여 충분히 관리할 수 있다.
불필요하게 외부 상태 관리 라이브러리를 도입하면 오히려 코드의 복잡도가 증가할 수 있으므로, 상태의 복잡도와 범위를 고려하여 적절한 도구를 선택하는 것이 중요하다.</p>
<h3 id="복잡한-상태의-경우"><strong>복잡한 상태의 경우</strong></h3>
<p>여러 컴포넌트에 걸쳐 공유되거나 복잡한 로직이 필요한 상태는 관리가 어려워지므로, 상태를 분리하고 모듈화하는 것이 중요하다.
컴포넌트 간 데이터 흐름을 명확하게 하기 위해 상태를 분리하거나 컨텍스트(Context) API 등을 적절히 활용하는 것이 좋다.
또한, 필요한 경우 외부 상태 관리 라이브러리를 도입하여 상태의 변화와 흐름을 체계적으로 관리하는 것이 바람직하다.</p>
<h3 id="서버와-연동되는-상태의-경우"><strong>서버와 연동되는 상태의 경우</strong></h3>
<p>서버에서 연동되는 데이터는 &#39;서버 상태&#39;로 구분하여 관리하는 것이 좋다.
이 경우, SWR이나 Tanstack Query와 같은 라이브러리를 사용하면 비동기 데이터 처리, 캐싱 및 재검증 등의 작업을 효과적으로 수행할 수 있다.</p>
<br />

<h2 id="2구조아키텍쳐">2.구조아키텍쳐</h2>
<p>프론트엔드 디자인 패턴은 MVC, MVP, MVVM, Component Pattern, Flux Pattern 등 굉장히 다양하다. 여러 디자인 패턴은 애플리케이션의 복잡도와 요구사항에 따라 선택할 수 있지만, 우선은 디렉토리 구조를 잘 만드는 것이 장기적인 유지보수와 협업에 결정적인 영향을 미친다. 디자인 패턴과 마찬가지로 디렉토리 구조에도 정답은 없지만, 아래의 두 가지 원칙을 지키는 것이 좋다.</p>
<h3 id="1-디렉토리를-계층적으로-두자"><strong>1. 디렉토리를 계층적으로 두자.</strong></h3>
<p>파일과 컴포넌트가 한 곳에 모두 몰리면 파일 간 의존 관계 파악과 수정이 어려워진다. 계층적으로 구성할 경우, 기능별 또는 도메인별로 파일을 분리하여 모듈화할 수 있다. 이렇게 하면 특정 기능을 수정하거나 확장할 때 관련 파일만 집중적으로 살펴볼 수 있어 유지보수와 협업이 용이해진다. 또한, 팀 내 새로운 개발자들이 전체 프로젝트 구조를 빠르게 파악할 수 있다는 장점이 있다. </p>
<h3 id="2-찾기-쉬운-서랍장처럼-디렉토리-구조를-만들자"><strong>2. 찾기 쉬운 서랍장처럼 디렉토리 구조를 만들자.</strong></h3>
<p>“서랍장이 정리되어 있으면 원하는 물건을 빠르게 찾을 수 있다”는 비유처럼, 명확한 규칙과 일관된 명명 규칙을 적용한 디렉토리 구조는 파일의 위치를 예측 가능하게 만든다.  파일 위치가 명확하게 정해져 있으면 신규 개발자나 협업하는 팀원들이 프로젝트에 빠르게 적응할 수 있고, 코드 가독성과 관리 효율성이 크게 향상된다.</p>
<br />

<h2 id="3-읽기-좋은-코드">3. 읽기 좋은 코드</h2>
<p>리액트 프로젝트 역시 &quot;읽기 좋은 코드&quot;가 유지보수와 협업에 있어 핵심 요소이다.  코드의 가독성이 높으면 의도를 명확하게 파악할 수 있어 버그를 줄이고 생산성을 향상시킬 수 있다. 리액트와 같은 컴포넌트 기반 라이브러리도 예외는 아니며, 클린 코드를 지향하는 원칙들이 그대로 적용된다.</p>
<p>읽기 좋다는 것은 결국 가독성과 예측 가능성을 의미한다. 즉, 코드의 의도가 명확하게 드러나고, 동일한 규칙이 일관되게 적용되면 코드의 수정과 확장이 쉬워진다. 여러 실무 사례와 오픈소스 프로젝트에서 볼 수 있듯이, 네이밍 규칙과 파일 구조가 일관적이면 신규 개발자나 협업하는 팀원들이 코드를 빠르게 이해할 수 있다. 읽기 좋은 코드를 구성하기 위한 네이밍 규칙은 다음과 같다.</p>
<h3 id="1-디렉토리-이름과-파일-이름은-동사가-아닌-명사로-짓자"><strong>1. 디렉토리 이름과 파일 이름은 동사가 아닌 명사로 짓자.</strong></h3>
<p>디렉토리와 파일은 프로젝트의 구성 요소를 나타내므로, “컴포넌트”, “페이지”, “서비스”처럼 고정된 개념을 드러내는 명사로 작성하는 것이 좋다. 예를 들어, <code>Button.js</code>나 <code>UserProfile</code> 폴더처럼 명사 형태로 작성하면, 해당 파일이나 폴더가 무엇을 의미하는지 명확해진다. 이는 클린 코드 원칙 중 “의도를 분명하게 드러내라”는 부분과도 일치한다.</p>
<h3 id="2-함수명은-동사--명사-형태로-짓자"><strong>2. 함수명은 동사 + 명사 형태로 짓자.</strong></h3>
<p>함수는 어떤 행동을 수행하므로, 함수 이름에 동사를 포함하여 해당 함수의 동작을 예측 가능하게 만드는 것이 중요하다. 예를 들어, <code>getUserInfo()</code>, <code>updateProfile()</code> 등은 함수가 무엇을 하는지 바로 알 수 있게 해준다. 이런 방식은 코드 리뷰와 유지보수 시에도 큰 도움이 된다.</p>
<h3 id="3-변수명은-주로-명사로-짓자"><strong>3. 변수명은 주로 명사로 짓자.</strong></h3>
<p>변수는 데이터를 나타내므로, 해당 변수에 담긴 값의 역할을 쉽게 파악할 수 있도록 명사형을 사용하는 것이 바람직하다. 예를 들어, <code>userList</code>, <code>profileData</code>와 같이 작성하면, 해당 변수가 무엇을 의미하는지 직관적으로 이해할 수 있다.</p>
<br />

<h2 id="4-컴포넌트스러운-컴포넌트">4. 컴포넌트스러운 컴포넌트</h2>
<p>리액트 컴포넌트는 결국 하나의 함수로 동작한다. 따라서 컴포넌트를 설계할 때에도 일반적인 함수 작성 원칙, 즉 단일 책임 원칙(SRP)을 적용할 수 있다. 여러 개발 서적과 커뮤니티에서도 “하나의 컴포넌트는 하나의 역할만 수행해야 한다”는 점을 강조한다. 이는 코드 가독성을 높이고, 유지보수 및 재사용성을 극대화하는 데 중요한 역할을 한다.</p>
<h3 id="1-하나의-컴포넌트는-하나의-역할을-하도록-하자"><strong>1. 하나의 컴포넌트는 하나의 역할을 하도록 하자.</strong></h3>
<p>단일 책임 원칙에 따르면, 한 컴포넌트가 여러 가지 역할을 수행하면 코드의 복잡도가 높아지고, 변경 사항이 생길 때 다른 기능에까지 영향을 미치기 쉽다. 예를 들어, UI의 표현과 비즈니스 로직을 한 컴포넌트에 모두 넣으면, 해당 컴포넌트를 이해하기 어려워지고 테스트 또한 복잡해진다. 따라서 컴포넌트는 명확하게 어떤 UI 요소나 기능을 담당하는지 표현되어야 한다. 이러한 원칙을 기반으로 컴포넌트를 작게 분리하면 재사용성과 독립성을 확보할 수 있다.</p>
<h3 id="2-하나의-컴포넌트의-코드가-너무-크지-않도록-구현하자"><strong>2. 하나의 컴포넌트의 코드가 너무 크지 않도록 구현하자.</strong></h3>
<p>만약 컴포넌트 코드가 길어져 스크롤이 길어지는 상황이라면, 이는 해당 컴포넌트에 너무 많은 책임이 몰렸다는 신호이다. 코드가 길어지면 가독성이 떨어지고, 추후 수정 시 오류가 발생할 가능성이 높아진다. 이런 경우에는 하위 컴포넌트로 분리하여 각 컴포넌트가 담당하는 역할을 명확히 하고, 코드를 모듈화하는 것이 좋다.</p>
<h3 id="3-컴포넌트를-작게-나누자"><strong>3. 컴포넌트를 작게 나누자.</strong></h3>
<p>컴포넌트를 작게 분할하면, 각 컴포넌트 간 의존성이 줄어들어 한 컴포넌트에 변경이 생겨도 다른 컴포넌트에 미치는 영향을 최소화할 수 있다. 예를 들어, 최하단(UI의 가장 작은 단위) 컴포넌트에 수정이 발생하더라도, 해당 컴포넌트만 재렌더링되거나 수정되므로 전체 애플리케이션에 미치는 영향이 줄어든다. 또한, 작고 독립적인 컴포넌트는 재사용성이 높아 여러 UI에서 동일한 컴포넌트를 활용할 수 있으므로, 코드의 중복을 줄이고 개발 생산성을 향상시킨다.</p>
<br />

<h2 id="5-중복코드">5. 중복코드</h2>
<p>리액트 프로젝트에서도 DRY(Don&#39;t Repeat Yourself) 원칙을 준수하는 것은 유지보수성과 확장성을 높이는 데 매우 중요하다. 중복 코드가 많으면 동일한 수정 사항을 여러 군데 반영해야 하고, 이는 버그 발생 위험을 높이며 코드의 일관성을 해칠 수 있다. </p>
<h3 id="1-역할이-비슷한-컴포넌트는-재사용이-가능한-컴포넌트로-만들자"><strong>1. 역할이 비슷한 컴포넌트는 재사용이 가능한 컴포넌트로 만들자.</strong></h3>
<p>예를 들어, 약간 다른 역할을 하는 버튼 컴포넌트가 2개 있다면, 두 컴포넌트를 따로 관리하기보다는 공통된 기능과 스타일을 추출하여 하나의 범용 버튼 컴포넌트를 만드는 것이 좋다. 이렇게 하면 나중에 버튼의 스타일이나 기능을 변경할 때 한 군데만 수정하면 되어 유지보수가 쉬워진다. 또한, 상속 구조나 mixin, 또는 컴포지션을 활용해 공통 로직을 분리하면 코드 중복을 더욱 효과적으로 줄일 수 있다.</p>
<h3 id="2-컴포넌트-내의-복잡한-비즈니스-로직이나-이벤트-처리가-있다면-커스텀-훅으로-활용하자"><strong>2. 컴포넌트 내의 복잡한 비즈니스 로직이나 이벤트 처리가 있다면 커스텀 훅으로 활용하자.</strong></h3>
<p>여러 컴포넌트에서 동일한 비즈니스 로직이나 이벤트 처리가 반복된다면, 이를 커스텀 훅으로 분리하여 관리하는 것이 바람직하다. 커스텀 훅을 사용하면 로직을 한 곳에 집중시킬 수 있어 코드의 일관성을 유지하고, 테스트 및 유지보수가 용이해진다. 리액트 공식 문서와 다수의 커뮤니티 자료에서도 커스텀 훅을 활용해 공통 로직을 재사용하는 방법이 권장되고 있다.</p>
<br />

<h2 id="6-최적화-api를-잘-활용하기">6. 최적화 API를 잘 활용하기</h2>
<p>복잡한 애플리케이션에서는 특정 부분에서 성능 병목현상이 발생할 수 있다. 이러한 문제를 해결하기 위해서는 우선 크롬 개발자 도구나 React Profiler 등으로 어느 부분에서 불필요한 리렌더링이나 과도한 계산이 일어나는지 진단해야 한다. 실제로 React 공식 문서와 여러 성능 최적화 관련 자료에서도, 진단 없이 최적화 API를 무작정 적용하는 것보다 문제를 정확히 파악한 후 적절히 적용하는 것이 중요하다고 강조한다</p>
<h3 id="1-최적화-api를-활용해서-개발하자"><strong>1. 최적화 API를 활용해서 개발하자.</strong></h3>
<p>리액트는 <code>React.memo</code>, <code>useMemo</code>, <code>useCallback</code> 등 다양한 최적화 API를 제공한다. 이들 API는 컴포넌트의 불필요한 리렌더링을 방지하거나, 값의 재계산을 줄이는 데 효과적이다. 하지만 이러한 API들은 실제로 병목현상이 발생하는 부분에 한해서 적용해야 하며, 모든 컴포넌트에 무작정 적용하면 오히려 코드의 복잡도를 높이고 유지보수를 어렵게 만들 수 있다. 성능 진단 도구로 문제가 되는 부분을 확인한 후에 최적화 API를 적용하는 것이 바람직하다.</p>
<h3 id="2-라이브러리를-사용하고-있다면-캐싱-기능도-잘-활용하자"><strong>2. 라이브러리를 사용하고 있다면 캐싱 기능도 잘 활용하자.</strong></h3>
<p>프론트엔드에서 가장 많은 시간을 소비하는 부분 중 하나는 서버와의 데이터 통신이다. 데이터 페칭에 소요되는 시간을 줄이기 위해, SWR이나 React Query와 같은 라이브러리에서 제공하는 캐싱 기능을 활용하면, 이미 받아온 데이터를 재사용함으로써 네트워크 요청을 최소화할 수 있다. 이는 사용자에게 보다 빠른 응답성을 제공하며, 전체 애플리케이션의 성능 최적화에 큰 도움이 된다</p>
<br />

<h2 id="7-끊김없는-ux">7. 끊김없는 UX</h2>
<p>프론트엔드에서 사용자 경험(UX)은 단순히 디자인만이 아니라, 앱이 얼마나 빠르고 부드럽게 동작하는지에 크게 좌우된다. 특히, 복잡한 어플리케이션에서는 데이터 통신, 렌더링, 업데이트 과정에서 발생하는 지연이나 “끊김” 현상이 사용자 불만으로 이어질 수 있다. 이에 따라, 개발자는 성능 진단 도구와 최적화 API를 활용하여 UX 성능을 개선해야 한다.</p>
<h3 id="1-데이터-통신의-직렬과-병렬-처리"><strong>1. 데이터 통신의 직렬과 병렬 처리</strong></h3>
<p>여러 API 호출이 필요한 경우, 직렬(순차적) 요청과 병렬 요청의 차이를 이해하고 적절히 활용하는 것이 중요하다.</p>
<ul>
<li>직렬 처리: 요청이 서로 의존적이라면 순차적으로 실행하여 결과의 일관성을 유지해야 한다.</li>
<li>병렬 처리: 독립적인 요청들은 <code>Promise.all</code> 등의 기법을 사용해 동시에 실행하면 전체 응답 시간을 단축할 수 있다.</li>
</ul>
<h3 id="2-loading-indicator-활용"><strong>2. Loading Indicator 활용</strong></h3>
<p>사용자에게 진행 상황을 시각적으로 전달하는 것은 끊김없는 UX를 위한 핵심 요소다.</p>
<ul>
<li><p>로딩 스피너 또는 스켈레톤 UI: 데이터가 로드되는 동안 해당 영역에 로딩 인디케이터를 표시하면, 사용자는 “작업이 진행 중”임을 인지하여 불안감을 줄일 수 있다.</p>
</li>
<li><p>Fallback UI와 Suspense: 리액트의 <code>&lt;Suspense&gt;</code> 컴포넌트를 활용하면, 로딩 중인 콘텐츠 대신 가벼운 로딩 인디케이터를 보여줄 수 있다. (React 공식 문서의 Suspense 를 참고하면, fallback 프로퍼티를 통해 로딩 중 대체 UI를 지정하는 방법을 확인할 수 있다. <a href="https://ko.react.dev/reference/react/Suspense">Suspense 공식문서 참고</a>)</p>
</li>
</ul>
<h3 id="3-usetransition--usedeferredvalue-등의-훅-활용-">*<em>3. useTransition / useDeferredValue 등의 훅 활용 *</em></h3>
<p>리액트 18부터 도입된 useTransition과 useDeferredValue 훅은, UI 업데이트를 비동기적으로 처리하여 사용자 인터랙션을 블록하지 않도록 돕는다.</p>
<ul>
<li><p>useTransition: 상태 업데이트를 “Transition”으로 표시하여, 긴급하지 않은 업데이트가 백그라운드에서 처리되도록 함으로써 이미 화면에 표시된 콘텐츠가 갑자기 사라지지 않게 한다.</p>
</li>
<li><p>useDeferredValue: 값 업데이트에 약간의 지연을 주어, 입력과 같이 빠르게 변화하는 데이터에 대해 부드러운 업데이트를 구현할 수 있다.</p>
</li>
</ul>
<h3 id="4-ssr과-suspense-활용"><strong>4. SSR과 Suspense 활용</strong></h3>
<p>서버 사이드 렌더링(SSR)을 사용하는 경우, Suspense를 적절히 활용하면 초기 로딩 시 사용자에게 빠르게 콘텐츠를 전달하면서도, 필요한 데이터가 준비될 때까지 fallback UI를 표시할 수 있다.</p>
<ul>
<li>Suspense와 SSR: Next.js와 같은 프레임워크에서는 Suspense를 활용해 데이터가 준비될 때까지 로딩 인디케이터를 보여주고, 이후 완성된 페이지로 자연스럽게 전환할 수 있다. 이처럼, SSR 환경에서도 Suspense를 활용하면, 사용자에게 끊김없이 콘텐츠를 제공할 수 있다.</li>
</ul>
<br />

<h2 id="8-비즈니스-요구사항에-따른-리팩토링">8. 비즈니스 요구사항에 따른 리팩토링</h2>
<p>앞서 소개한 여러 원칙들(상태 관리, 아키텍처 설계, 클린 코드, 컴포넌트 분리, 중복 제거, 최적화, 끊김없는 UX) 모두 중요한 요소이지만, 결국 가장 중요한 것은 회사의 핵심 비즈니스 요구사항에 신속하게 대응할 수 있는 구조를 유지하는 것이다. 즉, 변화하는 비즈니스 환경에 맞춰 코드를 유연하게 수정하고 확장할 수 있도록 하는 것이 우선이다.</p>
<p>비즈니스 환경은 끊임없이 변화하며, 이에 따라 서비스 요구사항도 달라진다. 따라서 코드와 아키텍처는 이러한 변화에 빠르게 대응할 수 있어야 한다.</p>
<p>자주 변경되는 기능이나 모듈을 미리 파악하고, 해당 부분을 독립적인 모듈이나 컴포넌트로 분리해 두면, 비즈니스 요구사항이 변경되었을 때 해당 부분만 집중적으로 리팩토링할 수 있어 전체 시스템에 미치는 영향을 최소화할 수 있다.</p>
<p>이러한 구조적 접근은 장기적으로 유지보수 비용을 낮추고, 빠른 시장 대응력을 갖추는 데 큰 도움이 된다.</p>
<br />
<br />


<h3 id="결론">결론</h3>
<p>이번 포스팅을 통해, 내가 리액트를 사용하면서 무의식적으로 따랐던 여러 원칙들을 다시 한 번 상기하게 되었다. 이 원칙들만 잘 지키면 SPA 프레임워크를 기반으로 한 협업에서도 큰 불편함 없이 개발할 수 있다고 생각한다. 이 글을 읽는 여러분도 각자의 방식으로 리액트를 더욱 잘 활용해보시길 바란다.</p>
<h4 id="출처">출처</h4>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=RzbBeRSnvRo&amp;t=204s">https://www.youtube.com/watch?v=RzbBeRSnvRo&amp;t=204s</a>
<a href="https://wikidocs.net/257837">https://wikidocs.net/257837</a>
<a href="https://ko.react.dev/reference/react/Suspense">https://ko.react.dev/reference/react/Suspense</a>
<a href="https://ko.react.dev/reference/react/useTransition">https://ko.react.dev/reference/react/useTransition</a>
<a href="https://frontend-fundamentals.com/code/">https://frontend-fundamentals.com/code/</a></p>
</blockquote>
<br />
<br />
혹시라도 잘못된 내용이 있거나 질문사항 있으시면 댓글 남겨주세요! 어떤 댓글이든 달게 받겠습니다!]]></description>
        </item>
        <item>
            <title><![CDATA[웹 성능을 위한 웹폰트 최적화 기법들]]></title>
            <link>https://velog.io/@minyoung_/%EC%9B%B9-%EC%84%B1%EB%8A%A5%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%9B%B9%ED%8F%B0%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95%EB%93%A4</link>
            <guid>https://velog.io/@minyoung_/%EC%9B%B9-%EC%84%B1%EB%8A%A5%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%9B%B9%ED%8F%B0%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95%EB%93%A4</guid>
            <pubDate>Tue, 21 Jan 2025 13:26:12 GMT</pubDate>
            <description><![CDATA[<h3 id="🤔-웹폰트가-웹-성능에-어떤-영향을-끼칠까">🤔 웹폰트가 웹 성능에 어떤 영향을 끼칠까??</h3>
<p>웹폰트는 웹사이트의 디자인과 브랜드 정체성을 강화하는 데 중요한 역할을 하지만, 동시에 웹 성능에 영향을 미칠 수 있다. 웹폰트를 사용하는 경우, 사용자의 브라우저는 폰트를 서버에서 다운로드해야 하며, 이 과정이 페이지 로딩 속도에 영향을 미칠 수 있다.</p>
<p>특히, 웹폰트 파일의 크기가 크거나 여러 종류의 웹폰트를 로드해야 하는 경우, 폰트 다운로드 시간이 길어져 텍스트 콘텐츠가 표시되기까지 지연되는 <strong>FOIT(Fallback On Invisible Text)</strong> 또는 <strong>FOUT(Flash of Unstyled Text)</strong> 현상이 발생할 수 있다. 이는 사용자 경험을 저하시킬 뿐 아니라 웹사이트의 첫인상을 악화시킬 위험이 있다.</p>
<p>게다가, 특정 폰트 형식(WOFF2, WOFF, TTF 등)이 브라우저에 따라 지원되지 않을 수 있기 때문에, 여러 형식을 준비해야 하며 이는 추가적인 리소스 관리 부담으로 이어질 수 있다. 웹폰트 최적화는 이러한 문제를 완화하는 데 핵심적인 역할을 한다. 
<br /></p>
<h3 id="🔎-foit랑-fout는-어떤-현상일까">🔎 FOIT랑 FOUT는 어떤 현상일까?</h3>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/480c85e3-fb0b-4e56-9242-ffd5e4bcbffa/image.gif" alt=""></p>
<h4 id="foitfallback-on-invisible-text">FOIT(Fallback On Invisible Text)</h4>
<p>웹폰트가 다운로드되는 동안 텍스트 콘텐츠를 화면에 표시하지 않고 대기 상태로 유지하다가, 웹폰트 다운로드가 완료되면 해당 폰트가 적용된 텍스트 콘텐츠를 보여주는 현상이다. 이로 인해 사용자는 한동안 빈 화면을 보게 되어 사용자 경험이 저하될 수 있다.</p>
<br />


<h4 id="foutflash-of-unstyled-text">FOUT(Flash of Unstyled Text)</h4>
<p>웹폰트가 다운로드되기 전까지 브라우저의 기본 폰트를 사용해 텍스트 콘텐츠를 먼저 표시하고, 웹폰트가 다운로드되면 이후 웹폰트가 적용된 텍스트 콘텐츠로 대체되는 현상이다. 이로 인해 텍스트의 스타일이 갑작스럽게 변경되는 느낌을 줄 수 있어 일관된 사용자 경험을 방해할 수 있다.</p>
<h1 id="웹폰트-최적화-기법들">웹폰트 최적화 기법들</h1>
<h2 id="1-폰트-적용-시점-컨트롤하기">1. 폰트 적용 시점 컨트롤하기</h2>
<p>웹폰트의 다운로드 완료 시점에 따라 텍스트의 표시 방식을 제어하려면 <code>font-display</code> CSS 속성을 사용한다. 이를 통해 폰트가 로드되는 동안 발생할 수 있는 FOUT (Flash of Unstyled Text) 또는 FOIT (Flash of Invisible Text) 현상을 관리할 수 있다.
<br /></p>
<p><strong>주요 옵션</strong></p>
<ul>
<li><p><strong>auto</strong>: 브라우저 기본 동작을 따른다.</p>
</li>
<li><p><strong>swap</strong>: 폰트가 로드될 때까지 텍스트는 기본 폰트로 렌더링된다. 이후 웹폰트가 로드되면 즉시 해당 폰트로 교체된다. 이 방식은 FOUT현상을 유발할 수 있지만, 폰트가 로드되지 않은 상태에서 사용자에게 빈 화면을 보여주는 것보다는 더 나은 사용자 경험을 제공한다.</p>
</li>
<li><p><strong>block</strong>: 웹폰트가 완전히 로드될 때까지 텍스트가 보이지 않게 숨겨준다. 이 방식은 FOIT 현상을 일으켜 폰트가 로드되기 전까지 사용자에게 비어있는 화면을 보여주게 된다.</p>
</li>
<li><p><strong>fallback</strong>: 웹폰트를 3초 이내에 로드하지 못한 경우, 기본 폰트로 텍스트를 렌더링한다. 이후 웹폰트가 로드되면 캐시에서 로드되어 교체된다.</p>
</li>
<li><p><strong>optional</strong>: 네트워크 상태에 따라, 웹폰트를 로드할지 기본 폰트를 유지할지 결정한다. 네트워크 속도가 느리거나 웹폰트가 로드되지 않으면 기본 폰트를 사용하며, 이후 웹폰트를 로드하지 않을 수도 있다.</p>
</li>
</ul>
<p><strong>적용 예시</strong></p>
<pre><code class="language-css">@font-face {
  font-family: &#39;CustomFont&#39;;
  src: url(&#39;custom-font.woff2&#39;) format(&#39;woff2&#39;);
  font-display: swap;
}</code></pre>
<p>위와 같이 <code>font-display: swap;</code>을 설정하면, 웹폰트가 로드되기 전에 기본 폰트로 텍스트를 표시하고, 폰트가 로드되면 해당 폰트로 즉시 교체된다. 이를 통해 페이지 로드 시간이 길어지는 동안에도 텍스트가 빈 화면으로 표시되지 않도록 할 수 있다.
<br /></p>
<h2 id="2-폰트-용량-줄이기">2. 폰트 용량 줄이기</h2>
<p>폰트 용량을 줄이면 서버에서 웹폰트를 다운로드하는 시간을 단축시킬 수 있어 페이지 로딩 속도를 개선할 수 있다. 아래 방법들을 활용해 폰트 최적화를 진행할 수 있다. </p>
<h3 id="0-웹폰트-변환-사이트">0. 웹폰트 변환 사이트</h3>
<blockquote>
<p><a href="https://transfonter.org/">https://transfonter.org/</a></p>
</blockquote>
<p>Transfonter는 웹 개발자나 디자이너가 웹폰트를 쉽고 빠르게 변환하고 최적화할 수 있도록 도와주는 웹사이트 이다. 해당 사이트는 폰트 포맷 변환, 웹폰트 키트 생성, 서브셋 생성, 압축 및 최적화와 같은 작업을 간편하게 할 수 있다. 또한 사용이 간단하고 무료로 제공되니 해당 사이트를 참고해보자.</p>
<h3 id="1-웹폰트-포맷format-설정하기">1. 웹폰트 포맷(format) 설정하기</h3>
<p>웹폰트 포맷은 브라우저 호환성과 파일 크기에 따라 선택해야 한다. 주요 포맷은 다음과 같다.</p>
<ul>
<li><p><strong>EOT</strong>: 매우 오래된 포맷으로 IE 6~8에서만 사용하며, 최신 브라우저에서는 지원되지 않는다. 압축 기능이 거의 없어 파일 크기가 크다.</p>
</li>
<li><p><strong>TTF/OTF</strong>: 파일 크기가 EOT보다는 작지만, 웹 최적화를 고려한 포맷은 아니다. 대체로 모든 브라우저에서 지원된다.</p>
</li>
<li><p><strong>WOFF</strong>: W3C에서 웹 환경에 적합한 폰트를 제공하기 위해 만든 포맷이다. 대부분의 브라우저에서 지원되며, 현재도 많이 사용되지만 WOFF2에 비해 효율이 낮아 점차 대체되는 추세이다.</p>
</li>
<li><p><strong>WOFF2</strong>: 기존 WOFF 대비 30% 이상 작은 파일 크기를 제공한다. 가장 효율적인 웹폰트 포맷이지만 최신브라우저에서만 지원하기 때문에 구형 브라우저와 호환되지 않는다.</p>
</li>
</ul>
<br />

<p>웹폰트 포맷에 따른 웹폰트 압축은 위에 소개해준 Transfer 사이트에서 할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/8302e605-fced-4ba7-a690-0914bd5fcb06/image.jpg" alt=""></p>
<p>사이트로 이동하여 변환을 원하는 폰트를 등록하고, Formats에서 원하는 형식의 format을 선택한 후 다운로드 받으면 된다.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/50040986-83f1-4b79-8a81-adf7b9ba2786/image.png" alt=""></p>
<p>WOFF2, WOFF가 압출륙이 좋지만 브라우저마다 지원하는 폰트가 다르기 때문에 <code>@font-face</code> 규칙을 적용하여 여러 포맷을 지정해 브라우저가 지원하는 파일을 우선 다운로드하도록 설정해야 한다.</p>
<p><strong>적용 예시</strong></p>
<pre><code class="language-css">@font-face {
  font-family: &#39;CustomFont&#39;;
  src: url(&#39;customfont.woff2&#39;) format(&#39;woff2&#39;), /* 최신 브라우저용 */
       url(&#39;customfont.woff&#39;) format(&#39;woff&#39;), /* 구형 브라우저용 */
       url(&#39;customfont.ttf&#39;) format(&#39;truetype&#39;);
  font-display: swap;
}</code></pre>
<h3 id="2-local-폰트-사용하기">2. local 폰트 사용하기</h3>
<p>만약 사용자의 브라우저에 서버에서 다운로드할 웹폰트가 존재한다면, 추가로 웹폰트를 다운로드하지 않아도 되므로 로딩 시간을 단축할 수 있다.</p>
<p><strong>적용 예시</strong></p>
<pre><code class="language-css">@font-face {
  font-family: &#39;LocalFont&#39;;
  src: local(&#39;LocalFont&#39;), /* 사용자의 시스템에서 폰트를 검색 */
         url(&#39;localfont.woff2&#39;) format(&#39;woff2&#39;), /* 로컬에 폰트가 설치되어있지 않다면 다음 폰트들을 적용한다 */
       url(&#39;localfont.woff&#39;) format(&#39;woff&#39;), 
       url(&#39;localfont.ttf&#39;) format(&#39;truetype&#39;);
}</code></pre>
<h3 id="3-subset-폰트-사용하기">3. subset 폰트 사용하기</h3>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/d26d92c0-a6c9-40b6-8d69-c5b75b6f6ce4/image.png" alt=""></p>
<p>한글은 자음과 모음의 조합으로 이루어져 있어, 모든 조합을 포함하면 만 개가 넘는 글자가 생성된다. 하지만 실제 서비스에서는 사용하지 않는 조합(예: 긗, 귶)도 많다.</p>
<p>웹폰트에 모든 글리프(문자)를 포함하면 파일 크기가 불필요하게 커지는데, 실제 사용하는 문자만 남기고 나머지를 제거한 서브셋 폰트를 생성하면 용량을 크게 줄일 수 있다. 이를 통해 폰트 파일 크기를 최적화하고 페이지 로딩 속도를 개선할 수 있다.</p>
<p>다만, 서브셋 폰트를 사용하는 경우 주의할 점이 있다. 텍스트가 동적으로 생성되는 페이지에서는 서브셋 폰트에 포함되지 않은 글자가 나타날 가능성이 있다. 이런 상황에서는 일관성 있는 텍스트 디자인을 제공하지 못할 수 있다. 따라서 서브셋 폰트는 주로 사용되는 문자가 고정된 랜딩 페이지나 정적인 콘텐츠에 적합하다.</p>
<br />

<p>서브셋 폰트 또한 Transfonter 사이트에서 생성할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/cc25d740-8095-4473-8de5-e0d4e42b90f4/image.jpg" alt=""></p>
<p>사이트로 이동하여 변환을 원하는 폰트를 등록하고, characters에 원하는 글자 목록을 추가하면 된다.</p>
<h3 id="4-unicode-range-속성-사용하기">4. unicode-range 속성 사용하기</h3>
<p>한 페이지에서 특정 문자 집합만 사용하는 경우, CSS의 <code>unicode-range</code> 속성을 활용하면 브라우저가 필요한 글리프(문자)만 로드하도록 설정할 수 있다. 이를 통해 웹폰트 파일 크기를 줄이고 로딩 속도를 최적화할 수 있다.</p>
<p><strong>기본 예시</strong>
아래 예제는 기본 라틴 문자만 로드하도록 설정한 경우다.</p>
<pre><code class="language-css">@font-face {
  font-family: &#39;MyFont&#39;;
  src: url(&#39;myfont.woff2&#39;) format(&#39;woff2&#39;);
  unicode-range: U+0000-007F; /* 기본 라틴 문자만 사용 */
}</code></pre>
<p><strong>다국어 웹폰트 설정</strong>
다국어 서비스를 제공하는 경우, <code>unicode-range</code>를 사용해 나라별로 필요한 폰트를 구분하여 로드할 수 있다. 이렇게 하면 모든 폰트를 한 번에 다운로드하지 않아도 된다.</p>
<pre><code class="language-css">/* 한글 전용 폰트 */
@font-face {
  font-family: &#39;koreaFont&#39;;
  src: url(&#39;koreafont.woff2&#39;) format(&#39;woff2&#39;);
  unicode-range: U+1100-U+11FF; /* 한글 문자만 */
}

/* 라틴어 전용 폰트 */
@font-face {
  font-family: &#39;latinFont&#39;;
   src: url(&#39;latinfont.woff2&#39;) format(&#39;woff2&#39;);
  unicode-range: U+0000-005FF; /* 라틴 문자만 */
}
</code></pre>
<h3 id="5-data-uri-변환하기">5. data-uri 변환하기</h3>
<p>data-uri 방식은 웹폰트 리소스를 CSS 파일에 직접 포함하여 추가 네트워크 요청을 줄이고 초기 로딩 속도를 개선할 수 있다는 장점이 있다. 또한, CSS와 리소스를 하나의 파일로 묶어 관리가 간편해진다는 이점도 있다. </p>
<p>그러나 Base64 인코딩으로 인해 파일 크기가 원본보다 약 33% 커지고, 포함된 리소스는 브라우저에서 캐싱되지 않아 반복 요청 시 비효율적일 수 있다. 더불어, 긴 Base64 문자열은 읽기 어렵고 수정이나 디버깅이 불편하다는 단점도 있다.</p>
<p>data-uri는 작은 리소스를 포함해 초기 로딩 속도를 개선하는 데 효과적이지만, 파일 크기와 캐싱 문제를 고려해 신중히 활용해야 한다.</p>
<br />

<p>data-uri 또한 Transfonter 사이트에서 변환할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/5c9b6b4f-ed74-4b25-8167-af3568c0424c/image.jpg" alt=""></p>
<p>사이트로 이동하여 변환을 원하는 폰트를 등록하고, Base64 encode를 활성화 하면 된다.</p>
<p><strong>적용 예시</strong></p>
<pre><code class="language-css">@font-face {
  font-family: &#39;MyFont&#39;;
  src: url(data:font/woff2;base64,...); /* Base64 인코딩된 폰트 데이터 */
}</code></pre>
<h2 id="3-웹폰트-preload">3. 웹폰트 Preload</h2>
<p>웹폰트 Preload는 브라우저가 폰트 파일을 CSS가 로드되기 전에 미리 다운로드하도록 지시하는 기법이다. 이를 통해 FOUT(Flash of Unstyled Text) 또는 FOIT(Flash of Invisible Text)를 방지하고, 페이지 로딩 속도를 향상시킬 수 있다.</p>
<p>Preload는 HTML의 <code>&lt;link&gt;</code> 태그를 사용하여 명시하며, 브라우저는 이를 최우선 순위로 로드한다. 이를 통해 웹폰트가 CSS보다 먼저 로드되도록 보장한다.</p>
<p>다만, Preload는 잘못 사용하면 리소스 로딩 순서가 뒤섞여 성능이 저하를 유발할 수 있다.  따라서 초기 화면에 필요한 폰트만 Preload하는 것이 중요하다. Preload로 폰트를 로드한 후에는, CSS에서 <code>@font-face</code> 규칙을 사용해 폰트를 정의해야 정상적으로 사용할 수 있다.</p>
<p><strong>적용 예시</strong></p>
<pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
  &lt;meta charset=&quot;UTF-8&quot;&gt;
  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
  &lt;title&gt;Webfont Preload Example&lt;/title&gt;

  &lt;!-- 웹폰트 Preload --&gt;
  &lt;!--  rel=&quot;preload&quot;: 리소스를 미리 로드하도록 지시.--&gt;
  &lt;!-- href: 폰트 파일 경로.--&gt;
  &lt;!-- as=&quot;font&quot;: 리소스 타입을 지정(폰트).--&gt;
  &lt;!-- type=&quot;font/woff2&quot;: 폰트 파일의 포맷 지정.--&gt;
  &lt;!-- crossorigin=&quot;anonymous&quot;: 크로스 오리진 요청 시 사용(외부 폰트 URL이 있는 경우 필요). --&gt;
  &lt;link rel=&quot;preload&quot; href=&quot;/fonts/myfont.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin=&quot;anonymous&quot;&gt;

  &lt;!-- CSS 파일 --&gt;
  &lt;link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot;&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1 style=&quot;font-family: &#39;MyFont&#39;;&quot;&gt;웹폰트 Preload 테스트&lt;/h1&gt;
&lt;/body&gt;
&lt;/html&gt;

</code></pre><h2 id="4-웹팩을-이용한-preload-리소스-관리하기">4. 웹팩을 이용한 Preload 리소스 관리하기</h2>
<p>웹팩(Webpack)이나 비트(Vite) 같은 번들링 도구로 웹 애플리케이션을 빌드할 때, 폰트 파일, 이미지, 스크립트 같은 정적 리소스에 해시값(hash)이 추가된다. 이렇게 하면 캐시를 무효화할 수 있어서 항상 최신 리소스를 로드할 수 있다.</p>
<p>하지만 이 해시값 때문에 HTML의 <code>&lt;link rel=&quot;preload&quot;&gt;</code> 태그 경로를 일일이 업데이트해야 하는 번거로움이 생긴다. 이걸 해결하려면 웹팩 플러그인을 써서 Preload 태그를 자동으로 생성하고 관리할 수 있다.</p>
<p><strong>1. PreloadWebpackPlugin 설치 (Preload 태그 자동 생성)</strong>
먼저 Preload 태그 자동 생성으로 생성해주는 <code>preload-webpack-plugin</code>을 설치한다.</p>
<pre><code>npm install preload-webpack-plugin --save-dev</code></pre><p><strong>2. 웹팩 설정 파일 작성</strong>
웹팩 설정 파일(webpack.config.js)에 PreloadWebpackPlugin을 추가해서 자동화한다.</p>
<pre><code class="language-javascript">const path = require(&#39;path&#39;);
const HtmlWebpackPlugin = require(&#39;html-webpack-plugin&#39;);
const PreloadWebpackPlugin = require(&#39;preload-webpack-plugin&#39;);

module.exports = {
  entry: &#39;./src/index.js&#39;, // 애플리케이션 진입점
  output: {
    path: path.resolve(__dirname, &#39;build&#39;), // 빌드 출력 경로
    filename: &#39;[name].[contenthash].js&#39;, // JS 파일 이름에 해시 추가
    assetModuleFilename: &#39;fonts/[name].[contenthash][ext]&#39;, // 폰트 파일 이름에 해시 추가
  },
  module: {
    rules: [
      {
        test: /\.css$/, // CSS 처리
        use: [&#39;style-loader&#39;, &#39;css-loader&#39;],
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/, // 폰트 파일 처리
        type: &#39;asset/resource&#39;,// 웹팩 5의 기본 정적 파일 처리 방식
      },
    ],
  },
  //플러그인
  plugins: [
    new HtmlWebpackPlugin({
      template: &#39;./public/index.html&#39;, // HTML 템플릿 경로
    }),
    new PreloadWebpackPlugin({
      rel: &#39;preload&#39;, // Preload 태그 생성
      as: (entry) =&gt; {
        if (/\.woff2?$/.test(entry)) return &#39;font&#39;; // 폰트 파일은 font로 설정
        if (/\.css$/.test(entry)) return &#39;style&#39;; // CSS 파일은 style로 설정
        if (/\.js$/.test(entry)) return &#39;script&#39;; // JS 파일은 script로 설정
        return &#39;auto&#39;;
      },
      include: &#39;allAssets&#39;, // 모든 에셋 포함
      fileWhitelist: [/\.(woff|woff2)$/i], // woff, woff2 파일만 포함
      crossorigin: &#39;anonymous&#39;, // CORS 지원을 위해 추가
    }),
  ],
  devServer: {
    static: &#39;./build&#39;, // 개발 서버 정적 파일 경로
    port: 3000, // 개발 서버 포트
    open: true, // 브라우저 자동 열기
  },
};</code></pre>
<p><strong>3. 설정 주요 포인트</strong></p>
<ul>
<li><p>Preload 태그의 파일 타입 관리
<code>as</code> 옵션을 사용해서 파일 형식에 맞는 <code>as</code> 속성을 설정한다. 예를 들어, 폰트 파일은 <code>as=&quot;font&quot;</code>, 스크립트는 <code>as=&quot;script&quot;</code>로 설정한다.</p>
</li>
<li><p>파일 필터링
<code>fileWhitelist</code> 옵션을 사용해서 특정 형식의 파일만 <code>Preload</code> 태그에 포함시킬 수 있다. 위 예시에서는 woff와 woff2 파일만 포함시킨다.</p>
</li>
<li><p>CORS 지원
웹폰트는 CORS 정책에 의해 리소스가 다운로드되지 않을 수 있으므로, <code>Preload</code> 태그를 생성할 때 <code>crossorigin=&quot;anonymous&quot;</code> 속성을 추가하는 것이 중요하다. 이 속성은 웹폰트를 안전하게 로드할 수 있도록 도와준다.</p>
</li>
</ul>
<p><strong>4. 빌드 및 실행</strong>
설정이 완료되었으니 빌드하거나 개발 서버를 실행한다.(스크립트 설정에 따라 명령어는 달라질 수 있다.)</p>
<ul>
<li><p>빌드</p>
<pre><code>npm run build</code></pre></li>
<li><p>개발 서버 실행</p>
<pre><code>npm start</code></pre></li>
</ul>
<p><strong>5. 결과 확인</strong>
빌드된 HTML 파일에서 Preload 태그가 자동으로 생성된 걸 확인할 수 있다.</p>
<pre><code class="language-html">&lt;link rel=&quot;preload&quot; href=&quot;assets/myfont.abcdef.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin=&quot;anonymous&quot;&gt;
</code></pre>
<br />

<h4 id="출처">출처</h4>
<blockquote>
<ul>
<li><a href="https://www.inflearn.com/course/%EC%9B%B9-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-2">https://www.inflearn.com/course/%EC%9B%B9-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-2</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://www.w3schools.com/css/css3_fonts.asp">https://www.w3schools.com/css/css3_fonts.asp</a></li>
</ul>
<br />
<br />
혹시라도 잘못된 내용이 있거나 질문사항 있으시면 댓글 남겨주세요! 어떤 댓글이든 달게 받겠습니다!]]></description>
        </item>
        <item>
            <title><![CDATA[웹 성능을 위한 이미지 최적화 기법들]]></title>
            <link>https://velog.io/@minyoung_/%EC%9B%B9-%EC%84%B1%EB%8A%A5%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95%EB%93%A4</link>
            <guid>https://velog.io/@minyoung_/%EC%9B%B9-%EC%84%B1%EB%8A%A5%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95%EB%93%A4</guid>
            <pubDate>Mon, 13 Jan 2025 08:54:03 GMT</pubDate>
            <description><![CDATA[<h3 id="🤔-이미지가-웹-성능에-어떤-영향을-끼칠까">🤔 이미지가 웹 성능에 어떤 영향을 끼칠까??</h3>
<p>이미지는 웹사이트의 시각적 매력을 높이는 중요한 요소지만, 동시에 웹 성능에 큰 영향을 미친다. 브라우저에서 이미지를 표시하려면 이미지를 다운로드해야 하는데, 이미지 파일의 크기가 클수록 다운로드 시간이 길어져 페이지 로딩 속도가 지연될 수 있다. 특히, 이미지의 해상도와 용량이 사용자의 화면 크기와 요구를 초과할 경우, 불필요하게 큰 데이터 전송으로 인해 성능 저하가 발생한다.</p>
<p>대부분의 기기는 매우 고해상도를 필요로 하지 않는다. 따라서, 화면 크기에 비해 지나치게 큰 이미지를 사용하면 실제로 필요한 데이터보다 더 많은 용량을 다운로드하게 되어 로딩 속도가 느려질 뿐만 아니라 사용자가 콘텐츠를 확인하는 데까지의 시간이 길어질 수 있다.</p>
<p>이미지 최적화는 이러한 문제를 해결하는 효과적인 방법이다. 최적화를 통해 이미지 용량을 줄이면 브라우저가 데이터를 더 빠르게 다운로드할 수 있어 페이지 로딩 속도가 향상된다. 이는 사용자 경험을 크게 개선하며, 사이트를 방문하는 사용자들에게 더 빠르고 매끄러운 인터페이스를 제공한다.</p>
<p>또한, 최적화된 이미지는 서버에서 전송되는 데이터 양을 줄여 호스팅 비용과 대역폭 사용량을 절감할 수 있다. 특히, 방문자가 많은 웹사이트의 경우 최적화는 리소스를 효율적으로 관리하는 데 필수적인 역할을 한다. 결과적으로 이미지 최적화는 사용자 경험과 사이트 운영 비용 모두를 개선할 수 있는 중요한 전략이다.</p>
<br />


<h1 id="이미지-최적화-기법들">이미지 최적화 기법들</h1>
<h2 id="1-이미지-픽셀-사이즈-줄이기">1. 이미지 픽셀 사이즈 줄이기</h2>
<p>클라이언트 내에 정적 이미지 파일을 로드할 때 제일 간단한 방법은 <strong>이미지 파일 자체의 픽셀 사이즈를 줄이는 것</strong>이다. </p>
<p>예를 들어, 화면에 렌더링되는 이미지의 크기가 100×100 픽셀이라고 가정해보자. 하지만 실제 이미지의 크기가 1000×1000 픽셀이면, 필요한 크기보다 넓이 기준으로 10배, 넓이와 높이를 곱한 면적 기준으로는 100배 큰 이미지를 사용하는 셈이다. 이렇게 불필요하게 큰 이미지를 사용하면 데이터 낭비와 함께 성능 저하를 초래한다.</p>
<p>일반적으로, 렌더링 크기(100×100 픽셀)로 이미지를 사용하는 것이 적합해 보이지만, 요즘 많이 사용되는 Retina 디스플레이에서는 한 공간에 더 많은 픽셀을 그릴 수 있으므로, 너비와 높이 기준으로 약 2배 큰 이미지를 사용하는 것이 적절하다.</p>
<p>즉, 위 예시의 경우 200×200 픽셀 크기로 이미지를 준비하는 것이 바람직하다. 이를 통해 디스플레이에서 품질 저하를 방지하면서도 불필요한 리소스 사용을 줄일 수 있다.</p>
<p><strong>✅아래 converter사이트에서 이미지를 원하는 형식이나 사이즈로 바꿀 수 있다.</strong></p>
<blockquote>
<p><a href="https://squoosh.app/">https://squoosh.app/</a></p>
</blockquote>
<br />
<br />

<h2 id="2-이미지-cdn-사용하기">2. 이미지 CDN 사용하기</h2>
<p>위에서는 클라이언트 내 정적 이미지 파일을 사용시에 대한 설명이었다. 반대로 서버에서  API 요청으로 받은 이미지의 사이즈는 어떻게 최적화 하면 좋을까? 그럴때는 CDN을 사용할 수 있다.</p>
<p><strong>❓ CDN(Contents Delivery Network)</strong></p>
<blockquote>
<p>CDN은 물리적 거리의 한계를 극복하기 위해 소비자(사용자)와 가까운 곳에 컨텐츠 서버를 두는 기술을 의미한다.</p>
</blockquote>
<p>한국에 있는 사용자가 미국에 있는 서버에서 이미지를 다운로드하려고 할 때, 인터넷 속도가 아무리 빨라졌다고 해도 물리적인 거리 때문에 다운로드에 시간이 걸릴 수 있다. 이를 해결하기 위해, 미국 서버의 데이터를 미리 한국 서버로 복사해 두면, 사용자가 이미지를 다운로드할 때 물리적 거리가 줄어들어 전송 시간이 크게 단축된다. 이러한 개념이 바로 CDN(Content Delivery Network)의 기본 원리이다.</p>
<p>그렇다면 이미지 CDN은 무엇일까? 이미지 CDN은 일반적인 CDN의 기능을 넘어, 이미지를 사용자에게 전송하기 전에 특정 요구 사항에 맞게 가공한다. 이미지 크기를 조정하거나 이미지 포맷을 변경한 뒤, 최적화된 형태의 이미지를 사용자에게 제공할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/1021c704-e0b6-45f3-ba9c-cc2da8e1dce0/image.png" alt=""></p>
<p>위 URL은 이미지 CDN의 예시이다. 전달하고자 하는 원본 이미지의 소스와 원하는 결과의 이미지 정보를 파라미터로 넘겨주면, 원본 이미지를 파라미터 형태로 가공을 해서 사용자에게 전달해준다.</p>
<p>이미지 CDN은 직접 구축하거나 이미지 CDN 솔루션을 사용해서 최적화 할 수 있다.
대표적인 이미지 CDN 솔루션 사이트는 아래 링크를 걸어놓을테니 참고해보면 좋을 거 같다.</p>
<blockquote>
<p><a href="https://www.imgix.com/">https://www.imgix.com/</a></p>
</blockquote>
<br />
<br />


<h2 id="3-이미지-파일-포맷format-설정">3. 이미지 파일 포맷(format) 설정</h2>
<p>파일 용량과 이미지의 품질 간의 균형을 고려하여 각 포맷(형식)을 적절하게 선택하면 이미지를 최적화할 수 있다.
대표적인 이미지 파일 확장자는 아래와 같다.</p>
<ul>
<li><p><strong>PNG</strong> : 투명도를 지원하는 비트맵 이미지 포맷이다. 손실 없는 압축 방식으로, 이미지 품질이 그대로 유지됩니다. 다만, 일반적으로 파일 크기가 JPEG보다 크기 때문에 복잡한 이미지에서는 비효율적일 수 있다.</p>
</li>
<li><p><strong>JPEG</strong> : 사진과 같은 복잡한 이미지를 저장할 때 적합한 포맷이다. 손실 압축 방식으로, 이미지 품질을 유지하면서 파일 크기를 줄일 수 있다. 이미지의 투명도가 필요하지 않는 이상 보통은 JPEG 이미지를 사용하도록 권장한다.</p>
</li>
<li><p><strong>WEBP</strong> : 구글에서 나온 차세대 이미지 포맷이다. JPEG, PNG, GIF보다 파일 크기가 작으면서도 더 좋은 품질을 유지할 수 있다. 손실 압축과 무손실 압축을 모두 지원하며, 대부분의 현대 브라우저에서 지원되긴 하지만 인터넷 익스플로어는 아직 지원되지 않는다. </p>
</li>
<li><p><strong>AVIF</strong>: 최신 이미지 포맷으로, WebP보다 더 좋은 압축 성능을 제공한다. 손실 압축을 지원하며, 높은 이미지 품질을 유지하면서도 매우 작은 파일 크기를 구현할 수 있다. 그러나 아직 일부 구형 브라우저에서는 지원되지 않을 수 있다.</p>
</li>
<li><p><strong>SVG</strong> : 벡터 형식의 이미지로, 크기를 확장해도 해상도가 손상되지 않는다. 주로 로고나 아이콘 등 선명한 라인으로 구성된 이미지에 사용된다. 파일 크기가 매우 작고, CSS나 JavaScript로 스타일을 변경할 수 있는 장점이 있다.</p>
<br />

</li>
</ul>
<p>이미지 사이즈 비교시 아래와 같다.(JPEG 10MB 기준)</p>
<blockquote>
<p>PNG(15MB ~ 20MB) &gt; JPEG(10MB) &gt; WebP(6.5MB ~ 7.5MB) &gt; AVIF(5MB)</p>
</blockquote>
<p>하지만 위에 설명에 나와있듯이 WEBP와 AVIF는 일부 구형 브라우저에서 지원되지 않을 수 있다.</p>
<p><strong>WEBP</strong>
<img src="https://velog.velcdn.com/images/minyoung_/post/b99ab059-f8a8-41f9-9a46-08f828ecdbf4/image.png" alt=""></p>
<p><strong>AVIF</strong>
<img src="https://velog.velcdn.com/images/minyoung_/post/213dcc1a-2e56-4946-b655-3fb758e78ef4/image.png" alt=""></p>
<p>그렇다면 WEBP나 AVIF가 지원되지 않는 브라우저에는 이미지를 아예 볼 수가 없을텐데, 이를 어떻게 해결할까? 그럴땐 이미지 분기가 필요하다. 예를들면 특정 브라우저가 WEBP가 지원되는 브라우저면 WEBP를 로드하고, WEBP가 지원되지 않는다면 JPG를 로드할 수 있도록 이미지 분기 처리가 필요하다. 이미지 분기 처리는 <code>&lt;picture&gt;</code>  태그를 사용하면 된다.</p>
<p><strong>❓ <code>&lt;picture&gt;</code>태그</strong></p>
<blockquote>
<ul>
<li>HTML의 <code>&lt;picture&gt;</code> 태그는 반응형 이미지를 제공하거나 상황에 따라 적합한 이미지를 선택할 수 있도록 도와주는 태그 이다.</li>
</ul>
</blockquote>
<ul>
<li>브라우저의 화면 크기, 픽셀, 또는 미디어 조건에 따라 서로 다른 이미지를 렌더링할 수 있도록 구성해준다. 성능 최적화와 다양한 디바이스 환경에서 적절한 이미지를 제공하는 데 유용하다.</li>
</ul>
<pre><code class="language-javascript">&lt;picture&gt;
    &lt;source srcset=&quot;awesome_new_main.avif&quot; type=&quot;image/avif&quot;&gt;
    &lt;source srcset=&quot;new_main.webp&quot; type=&quot;image/webp&quot;&gt;
    &lt;img src=&quot;old_main.jpeg&quot; alt=&quot;구형 이미지 포맷&quot;&gt;// 마지막에는 구형 이미지 포맷을 모든 브라우저에서 사용할 수 있는 이미지 포맷으로 배치해야 함.  
&lt;/picture&gt;</code></pre>
<p>위 코드는 브라우저가 지원하는 이미지 형식에 따라 최신 포맷(AVIF, WebP)을 우선 로드하고, 지원하지 않는 경우 호환성을 위해 기본 포맷(JPEG) 이미지를 로드하는 구조이다. <code>&lt;picture&gt;</code>태그를 사용하면 브라우저 환경에 따라 최적화된 이미지 포맷을 보여줄 수 있다.</p>
<br />
<br />

<h2 id="4-이미지-preloading">4. 이미지 preloading</h2>
<p>웹사이트에 진입했을 때, 이미지가 늦게 뜨거나 위에서부터 천천히 로드되는 것을 본 적이 있을 것이다. 이는 이미지가 서버에서 로드되기까지 시간이 걸리기 때문으로, 사용자 경험(UX)에 부정적인 영향을 미칠 수 있다. 이러한 문제는 <strong>이미지 프리로딩(preloading)</strong>을 통해 최적화할 수 있다.</p>
<p><strong>❓ 이미지 preloading</strong>
이미지가 화면에 노출되기 전에 미리 로드해두는 작업을 말한다. 이미지를 화면에 표시하지 않아도 특정 코드나 메커니즘을 통해 로드할 수 있다. 이렇게 하면 사용자가 해당 이미지를 보려는 시점에 로딩이 이미 완료된 상태가 되어 즉각적으로 표시된다.</p>
<p><strong>✅ 리액트에서 이미지 프리로딩 방법</strong>
React에서는 <code>useEffect</code> 훅을 활용하여 컴포넌트가 마운트된 후 즉시 이미지 로드를 시작할 수 있다. 이미지를 로드하기 위해 JavaScript의 <code>Image</code> 객체를 사용하며, 로드 완료 및 실패 시 콜백을 추가해 상태를 추적할 수도 있다.
아래 코드를 참고해보자.</p>
<h3 id="1-단순-반복문으로-이미지를-순차적으로-로드하는-방식">1. 단순 반복문으로 이미지를 순차적으로 로드하는 방식</h3>
<p>각각의 이미지 로드 완료 여부는 독립적으로 처리되며, 전체 로드 상태를 한 번에 관리하지 않기 때문에 이미지의 로드 상태를 개별적으로 처리하거나 단순히 로드 작업만 수행할 때 적합하다.</p>
<pre><code class="language-javascript">import React, { useEffect } from &quot;react&quot;;

const App = () =&gt; {
  const preloadImagesSequentially = (urls: string[]) =&gt; {
    urls.forEach((url) =&gt; {
      const img = new Image();
      img.src = url;

      img.onload = () =&gt; {
        console.log(`로드 성공: ${url}`);
      };

      img.onerror = (error) =&gt; {
        console.error(`로드 실패: ${url}`, error);
      };
    });
  };

  useEffect(() =&gt; {
    const imageUrls = [
      &quot;https://example_1.jpg&quot;,
      &quot;https://example_2.jpg&quot;,
      &quot;https://example_3.jpg&quot;,
    ];

    preloadImagesSequentially(imageUrls);
  }, []);

  return &lt;div&gt;이미지 프리로딩: 순차 처리&lt;/div&gt;;
};

export default App;
</code></pre>
<br />

<h3 id="2-promise를-사용해-병렬적으로-이미지를-가져오는-방식">2. Promise를 사용해 병렬적으로 이미지를 가져오는 방식</h3>
<p><code>Promise</code>와 <code>Promise.all</code>을 활용해 병렬적으로 이미지를 로드하고, 모든 이미지가 성공적으로 로드되었을 때 한 번에 완료 처리를 할 수 있다. 모든 이미지가 로드된 후에 UI를 업데이트하거나 특정 작업을 진행해야 하는 경우에 적합하다.</p>
<pre><code class="language-javascript">import React, { useEffect } from &quot;react&quot;;

const App = () =&gt; {
  const preloadImagesWithPromise = (urls: string[]) =&gt; {
    const loadImages = urls.map((url) =&gt; {
      return new Promise&lt;void&gt;((resolve, reject) =&gt; {
        const img = new Image();
        img.src = url;

        img.onload = () =&gt; {
          console.log(`로드 성공: ${url}`);
          resolve();
        };

        img.onerror = (error) =&gt; {
          console.error(`로드 실패: ${url}`, error);
          reject(error);
        };
      });
    });

    return Promise.all(loadImages); // 모든 Promise가 완료될 때까지 대기
  };

  useEffect(() =&gt; {
    const imageUrls = [
      &quot;https://example_1.jpg&quot;,
      &quot;https://example_2.jpg&quot;,
      &quot;https://example_3.jpg&quot;,
    ];

    const preload = async () =&gt; {
      try {
        await preloadImagesWithPromise(imageUrls);
      } catch (error) {
        console.error(`이미지 로드 오류 발생: ${error}`);
      }
    };

    preload();
  }, []); // 의존성 배열에서 preloadImagesWithPromise가 빠짐

  return &lt;div&gt;이미지 프리로딩: 병렬 처리&lt;/div&gt;;
};

export default App;
</code></pre>
<h2 id="5-이미지-lazy-loading">5. 이미지 lazy loading</h2>
<p>웹 사이트에 서버에 다운받아야 할 이미지 리소스가 1000개 정도라고 생각해보자. 그 많은 이미지를 한 번에 로드할 경우 초기 페이지 로드되는 시간이 꽤나 걸리며 사용자 경험이 느려질 수 있다. 이러한 문제는 이미지 <strong>레이지 로딩(lazy loading)</strong> 을 통해 최적화 할 수 있다.</p>
<p><strong>❓ 이미지 lazy loading</strong>
이미지가 초기 페이지 로드 시점에 즉시 로드되지 않고, 사용자가 해당 이미지를 볼 가능성이 있는 시점에 로드되도록 하는 작업을 말한다. 화면에 보여지는 이미지만 우선 로드하므로 초기 로딩 시간이 단축되고, 사용자가 해당 이미지를 볼 필요가 없으면 네트워크 리소스를 아낄 수 있어 효율적이다.</p>
<p>lazy loading을 하는 방법은 대표적으로 아래 두가지가 있다.</p>
<h3 id="1-intersection-observer-사용해서-lazy-로드하는-방식">1. Intersection Observer 사용해서 lazy 로드하는 방식</h3>
<p><code>Intersection Observer API</code>는 요소가 뷰포트에 들어왔는지, 또는 다른 요소와 교차하는지를 비동기적으로 감지하는 API 이다. 이 API를 사용하여 요소가 화면에 보일 때(예: 이미지, 비디오 등) 해당 요소를 로드하거나 처리할 수 있다.</p>
<pre><code class="language-javascript">import React, { useEffect, useRef } from &quot;react&quot;;

const App = () =&gt; {
  const imageUrls = [
    &quot;https://example_1.jpg&quot;,
    &quot;https://example_2.jpg&quot;,
    &quot;https://example_3.jpg&quot;,
  ];

  const observerRef = useRef&lt;IntersectionObserver | null&gt;(null);

  useEffect(() =&gt; {
    const handleObserver: IntersectionObserverCallback = (entries, observer) =&gt; {
      entries.forEach((entry) =&gt; {
        if (entry.isIntersecting) {
          const img = entry.target as HTMLImageElement;;
          if (img.dataset.src) {
            img.src = img.dataset.src; // data-src 속성에서 실제 이미지 URL을 가져옴
            observer.unobserve(img); // 이미지가 보이면 더 이상 관찰하지 않음
          }
        }
      });
    };

    const options = {
      threshold: 0.1 // 10% 이상 보일 때 로드
    };

    observerRef.current = new IntersectionObserver(handleObserver, options);

    return () =&gt; {
      // 컴포넌트 언마운트 시 Observer 해제
      observerRef.current?.disconnect();
    };
  }, []);


  return (
    &lt;div className=&quot;img-container&quot;&gt;
      {imageUrls.map((image, index) =&gt; (
        &lt;div key={image}&gt;
          &lt;img
            data-src={image} // 실제 이미지는 data-src에 저장
            alt={`Image ${index + 1}`}
            width=&quot;300&quot;
            height=&quot;200&quot;
          /&gt;
        &lt;/div&gt;
      ))}
    &lt;/div&gt;
  );
};

export default App;</code></pre>
<br />

<h3 id="2-react-lazyload-라이브러리-사용해서-lazy-로드하는-방식">2. react-lazyload 라이브러리 사용해서 lazy 로드하는 방식</h3>
<p><code>react-lazyload</code>는 React에서 간편하게 이미지 또는 기타 콘텐츠에 대해 Lazy Loading을 구현할 수 있는 라이브러리 이다. 이 라이브러리는 <code>Scroll</code> 이벤트를 내부적으로 활용하여, 요소가 뷰포트에 들어왔을 때 자동으로 콘텐츠를 로드한다. </p>
<h4 id="1-react-lazyload-라이브러리-설치">1. <code>react-lazyload</code> 라이브러리 설치</h4>
<pre><code>npm install react-lazyload</code></pre><h4 id="2-코드-예시">2. 코드 예시</h4>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import LazyLoad from &quot;react-lazyload&quot;;

const App = () =&gt; {
  const imageUrls = [
    &quot;https://example_1.jpg&quot;,
    &quot;https://example_2.jpg&quot;,
    &quot;https://example_3.jpg&quot;,
  ];

  return (
  &lt;div className=&quot;img-container&quot;&gt;
      {imageUrls.map((image, index) =&gt; (
        &lt;LazyLoad key={image} height={200} offset={100}&gt; //height, offset 등의 옵션을 설정하여 더 세부적으로 제어할 수 있다.
          &lt;img src={image} alt={`Image ${index + 1}`} /&gt;
        &lt;/LazyLoad&gt;
      ))}
    &lt;/div&gt;
  );
};

export default App;</code></pre>
<br />
<br />

<p>지금까지 대표적인 이미지 최적화 기법들에 대해 살펴보았다. 각 상황에 맞게 이미지 최적화 기법을 적용한다면 웹 성능을 더욱 향상시킬 수 있을 것이다.
<br /></p>
<h4 id="출처">출처</h4>
<blockquote>
<ul>
<li><a href="https://www.cloudflare.com/resources/assets/slt3lc6tev37/6BVIJvRAQBrUfcZVyhY8hY/2a593ed9c28283fd0b81b07d52c01e64/Whitepaper_Getting-Faster-Know-your-website-know-whats-slowing-it-down_Korean_2021022.pdf">https://www.cloudflare.com/resources/assets/slt3lc6tev37/6BVIJvRAQBrUfcZVyhY8hY/2a593ed9c28283fd0b81b07d52c01e64/Whitepaper_Getting-Faster-Know-your-website-know-whats-slowing-it-down_Korean_2021022.pdf</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://caniuse.com/">https://caniuse.com/</a></li>
<li><a href="https://www.inflearn.com/course/%EC%9B%B9-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-1">https://www.inflearn.com/course/%EC%9B%B9-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-1</a></li>
<li><a href="https://www.inflearn.com/course/%EC%9B%B9-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-2">https://www.inflearn.com/course/%EC%9B%B9-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%EB%A6%AC%EC%95%A1%ED%8A%B8-2</a></li>
</ul>
<br />
<br />
혹시라도 잘못된 내용이 있거나 질문사항 있으시면 댓글 남겨주세요! 어떤 댓글이든 달게 받겠습니다!]]></description>
        </item>
        <item>
            <title><![CDATA[성능 최적화를 위한 브라우저 렌더링 원리]]></title>
            <link>https://velog.io/@minyoung_/%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@minyoung_/%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Sun, 05 Jan 2025 13:13:56 GMT</pubDate>
            <description><![CDATA[<h3 id="🤔-성능-최적화를-하려면-왜-브라우저-렌더링-원리를-알아야-할까">🤔 성능 최적화를 하려면 왜 브라우저 렌더링 원리를 알아야 할까?</h3>
<p>성능 최적화와 브라우저 렌더링 원리는 밀접하게 연관되어 있다. DOM, CSSOM, 그리고 렌더 트리 생성 및 업데이트 과정에서 발생하는 비용을 줄이는 것이 성능 최적화의 핵심인데, 이 과정에서 브라우저가 화면을 그릴 때 발생하는 다양한 비용을 최소화하는 것이 중요하다. 예를 들어, DOM 조작이나 CSS 변경, 레이아웃 계산, 애니메이션 등의 작업은 렌더링 과정을 트리거하는 요소들이다. 이러한 작업들을 최소화하면 브라우저는 더 적은 리소스를 사용해 화면을 그릴 수 있다.</p>
<p>따라서 브라우저 렌더링 원리를 이해하는 것이 웹 성능 문제를 해결하는 데 큰 도움이 된다. 렌더링 과정에서 어느 단계에서 성능 문제가 발생하는지 빠르게 파악할 수 있고, 문제의 원인을 정확히 이해한 뒤 해결 방법을 신속하게 찾을 수 있기 때문이다. 웹 성능 최적화를 위한 첫걸음은 바로 이 렌더링 원리를 이해하는 것이라고 할 수 있다.</p>
<h2 id="✅-브라우저-구성-요소">✅ 브라우저 구성 요소</h2>
<p>브라우저는 운영 체제(OS) 위에서 실행되는 애플리케이션으로, 사용자가 웹사이트를 탐색하고 정보를 검색하며 소통할 수 있도록 돕는다. 브라우저가 이러한 역할을 할 수 있는 이유는 다양한 구성 요소들이 협력하여 웹 페이지를 로드하고 렌더링하며 사용자와 상호작용하는 방식으로 작동하기 때문이다. 브라우저는 다음과 같은 주요 요소들로 구성된다.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/5173a6c5-c2bb-4654-a1b8-b5c5b0da058f/image.png" alt=""></p>
<p><strong>1. 사용자 인터페이스(User Interface)</strong>
사용자가 브라우저와 상호작용하는 주요 인터페이스이다. 주소창, 뒤로 가기/앞으로 가기 버튼, 탭, 북마크, 메뉴 등 다양한 기능을 제공한다.</p>
<p><strong>2. 브라우저 엔진 (Browser Engine)</strong>
사용자 인터페이스와 렌더링 엔진 사이의 인터페이스 역할을 한다. 주어진 URL 을 읽어, 앞/뒤 페이지로 가거나 새로고침을 하는 등 기초적인 액션을 담당한다. </p>
<p><strong>3. 렌더링 엔진 (Rendering Engine)</strong>
 HTML, CSS, JavaScript 파일을 해석하여 화면에 웹 페이지를 렌더링한다. 웹 페이지를 시각적으로 구성하는 핵심 엔진이다.
렌더링 엔진은 사용하는 브라우저마다 다르다.</p>
<blockquote>
<p>Google : Blink
IE : Trident
FireFox : Gecko
Crome for IOS : WebKit</p>
</blockquote>
<p><strong>4. 네트워크 레이어(Network)</strong>
서버와의 통신을 담당하며, HTTP 요청 및 응답을 처리한다. 브라우저가 웹 페이지나 리소스를 요청하고, 이를 서버에서 받아오는 과정이 포함된다. </p>
<p><strong>5. 자바스크립트 인터프리터(JavaScript Interpreter)</strong>
웹 페이지에서 동적으로 실행되는 JavaScript 코드를 해석하고 실행한다.웹 페이지 내의 JavaScript 코드를 실행하여 DOM을 조작하고, 사용자와의 상호작용을 처리한다.</p>
<p><strong>6. ui 백엔드</strong>
os에서 제공하는 UI메서드를 사용해 버튼, 콤보박스 등 기본적인 위젯을 그려준다.</p>
<p><strong>7. 스토리지 레이어</strong>
자료를 저장하는 레이어다. 쿠키나 로컬/세션 스토리지 등 모든 종류의 자원을 하드디스크에 저장한다.</p>
<p>** 사용자가 URI를 입력하면 브라우저 렌더링의 구성요소들이 어떻게 동작하는지까지 정리하고 싶지만 오늘의 주제인 렌더링 원리에서는 좀 벗어나는 거 같아 그건 따로 적지 않겠다!</p>
<h2 id="✅-브라우저-렌더링-원리">✅ 브라우저 렌더링 원리</h2>
<p>이제 렌더링 엔진이 리소스를 처리하고 UI를 화면에 표시하는 부분에 대해 좀 더 자세히 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/a63984f2-ff0a-4cd6-9749-54eaeb79dcaf/image.png" alt=""></p>
<blockquote>
</blockquote>
<p>1) HTML 파싱 → DOM Tree 생성
2) CSS 파싱 → CSSOM Tree 생성
3) DOM Tree + CSSOM Tree → Render Tree 생성
4) Layout: Render Tree 기반으로 요소의 위치와 크기 계산 
5) Paint: Render Tree와 Layout 정보로 화면에 그릴 픽셀 준비
6) Composite: 픽셀을 화면에 최종 렌더링</p>
<h3 id="1-dom-tree-생성"><strong>1. DOM Tree 생성</strong></h3>
<p>DOM (Document Object Model) Tree는 HTML 문서를 브라우저가 파싱하여 계층적 트리 구조로 표현한 객체 모델이다. 과정은 아래와 같이 이루어진다.</p>
<p><strong>1. HTML 문서로드</strong></p>
<ul>
<li>브라우저는 네트워크 요청을 통해 HTML 문서를 서버로부터 받아온다. </li>
<li>HTML은 문자열로 전송되며, 렌더링 엔진이 이를 처리하게 된다.</li>
</ul>
<p><strong>2. 토큰화 (Tokenization)</strong></p>
<ul>
<li>HTML 문자열이 HTML 파서에 의해 처리된다.</li>
<li>파서는 HTML 문서를 순차적으로 읽으며, 토큰(Token)으로 분리한다.</li>
</ul>
<p><strong>3. 트리 생성 (Tree Construction)</strong></p>
<ul>
<li>토큰화된 결과를 바탕으로 DOM Tree가 점진적으로 구축된다. 따라서 HTML 문서가 완전히 로드되지 않아도 DOM Tree는 생성 중일 수 도 있다.</li>
<li>HTML 태그는 최상위 노드(Node)로 변환되며, 노드는 부모-자식 관계를 통해 트리 구조를 형성한다.</li>
<li>DOM Tree는 트리구조이기에 이후 렌더링 단계에서 각 노드들의 위치와 크기는 부모 노드를 기준으로 하여 상대적으로 작성할 수 있다.</li>
</ul>
<p>예시 코드</p>
<pre><code class="language-html">&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Document&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div&gt;
      &lt;p&gt;Hello, World!&lt;/p&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>예시 DOM 트리 구조</p>
<pre><code class="language-css">html
├── head
│   └── title
└── body
    └── div
        └── p
            └── &quot;Hello, World!&quot;</code></pre>
<p>HTML 문서를 모두 파싱한 후 최종적으로 DOM Tree가 완성된다.</p>
<h3 id="2-cssom-생성"><strong>2. CSSOM 생성</strong></h3>
<p>CSSOM (CSS Object Model) Tree는 CSS 문서를 파싱하여 브라우저가 스타일 정보를 표현하는 계층적 트리 구조이다. 과정은 아래와 같이 이루어진다.</p>
<p><strong>1. CSS 리소스 로드</strong></p>
<ul>
<li>브라우저는 HTML 문서에서 <code>&lt;style&gt;</code> 태그나 <code>&lt;link&gt;</code> 태그를 만나면 CSS 리소스를 가져온다.<ul>
<li>네트워크 요청을 통해 외부 CSS 파일을 가져오거나, HTML에서 CSS 코드를 바로 읽어들인다. </li>
</ul>
</li>
</ul>
<p><strong>2. CSS 파싱 시작</strong></p>
<ul>
<li>로드된 CSS 코드(문자열 형식)는 CSS 파서에 의해 처리된다.</li>
<li>CSS 파서는 스타일 규칙을 토큰(Token)으로 분리한다.</li>
</ul>
<p><strong>3. 토큰 → 스타일 규칙 변환</strong></p>
<ul>
<li>파싱된 토큰은 스타일 규칙으로 변환된다.</li>
<li>각 스타일 규칙은 다음 두 가지를 포함한다:</li>
</ul>
<p>선택자: 스타일이 적용될 HTML 요소
선언 블록: 선택자에 적용될 스타일 속성과 값</p>
<p><strong>4. CSSOM Tree 생성</strong></p>
<ul>
<li>스타일 규칙이 계층적 구조로 구성되어 CSSOM Tree를 형성한다.</li>
<li>CSSOM Tree는 선택자와 그에 따른 스타일 속성을 연결한 구조이다.</li>
</ul>
<p>CSSOM Tree</p>
<pre><code class="language-css">- body
  ├── margin: 0
  └── font-size: 16px
- h1
  ├── color: blue
  └── text-align: center</code></pre>
<p>모든 CSS를 파싱하고 상속, 우선순위, 미디어 쿼리 등을 처리한 뒤 최종적으로 CSSOM Tree를 완성한다.</p>
<h3 id="3-render-tree-생성"><strong>3. Render Tree 생성</strong></h3>
<p>Render Tree는 브라우저가 화면에 요소를 배치하고 그릴 때 필요한 구조로, DOM Tree와 CSSOM Tree를 결합하여 생성된다.Render Tree는 화면에 실제로 표시되어야 하는 노드만 포함하며, 각 노드에 스타일과 레이아웃 정보를 담는다. </p>
<p><strong>DOM Tree와 CSSOM Tree 결합</strong></p>
<ul>
<li>Render Tree 는 DOM 의 형태를 순회하며 각 노드들이 CSSOM Tree에서 정의된 관계 형태를 따르는지를 확인하며 해당하는 스타일 규칙을 적용한다.</li>
</ul>
<p><strong>시각적 노드만 포함</strong></p>
<ul>
<li>Render Tree는 실제로 화면에 렌더링되는 요소들만 포함한다. 다음과 같은 요소는 Render Tree에 포함되지 않는다.<blockquote>
<p><code>display: none;</code>: 완전히 숨겨지는 요소.(DOM Tree와 CSSOM Tree에는 포함되지만, Render Tree에는 포함되지 않음)
메타 태그와 같은 시각적으로 표시되지 않는 요소.</p>
</blockquote>
</li>
</ul>
<p><strong>Render Tree 생성</strong></p>
<ul>
<li>Render Tree는 화면 렌더링의 핵심 중간 데이터로, 사용자가 보여지는 최종 화면 구성의 틀이다.</li>
</ul>
<h3 id="4-layout"><strong>4. Layout</strong></h3>
<p>Layout은 Render Tree를 기반으로 각 요소의 위치와 크기를 계산하는 단계이다. 이 과정은 박스 모델(Box Model)텍스트을 적용하여 요소들이 화면에 어떻게 배치될지 결정한다.</p>
<p><strong>상대적 크기와 위치 계산</strong></p>
<ul>
<li>Render Tree의 각 노드를 탐색하며 부모-자식 관계를 기준으로 요소의 위치와 크기를 계산한다.</li>
<li>탐색 중에 %, em, rem 등 상대 단위를 픽셀 값으로 변환한다.</li>
<li>상속되는 CSS 속성들도 이 단계에서 구체적인 값으로 평가된다.</li>
</ul>
<p><strong>플로우와 컨텍스트 설정</strong></p>
<ul>
<li>Normal Flow, Flexbox, Grid, Positioning 등 레이아웃 방식에 따라 위치와 크기를 계산한다.</li>
</ul>
<p><strong>뷰포트와 스크롤 영역 계산</strong></p>
<ul>
<li>요소들이 화면(뷰포트) 내에 어떻게 보이는지를 계산한다.</li>
<li>콘텐츠가 뷰포트를 넘어서면, 스크롤 영역이 설정된다.</li>
</ul>
<blockquote>
<p>❗️ Layout은 문서 전체 또는 일부를 다시 계산해야 하므로 성능에 큰 영향을 미친다.</p>
</blockquote>
<h3 id="5-paint"><strong>5. Paint</strong></h3>
<p>Paint는 Layout 단계에서 계산된 요소의 위치와 스타일을 기반으로 각 요소를 픽셀 단위로 표현하는 단계이다.
브라우저는 이 단계에서 요소의 시각적 속성(색상, 그림자, 배경, 텍스트 등)을 픽셀 단위로 계산하고 그린다. </p>
<p><strong>각 요소에 대한 스타일 처리</strong></p>
<ul>
<li>Render Tree를 탐색하며 화면에 그릴 요소를 처리한다.</li>
<li>텍스트 색상, 테두리, 배경색, 그림자 등을 계산하여 화면에 표현한다.</li>
</ul>
<p><strong>레이어 생성</strong></p>
<ul>
<li>Paint 과정에서는 레이어별로 나뉘어 그려질 요소를 결정한다.</li>
<li>레이어는 z-index, CSS 속성(예: transform, will-change) 등에 의해 생성된다.</li>
</ul>
<h3 id="6-composite"><strong>6. Composite</strong></h3>
<p>Composite 단계는 GPU가 여러 레이어에서 준비된 픽셀 데이터를 결합하여 최종적으로 화면에 렌더링하는 단계이다. 레이어 수가 많을수록 합성이 오래 걸리기 때문에 레이어 수가 많으면 성능에 좋지 않다. </p>
<h2 id="✅-각-렌더링-과정에서-성능에-영향을-미치는-요소와-해결책">✅ 각 렌더링 과정에서 성능에 영향을 미치는 요소와 해결책</h2>
<h3 id="1dom-tree-생성시-성능에-영향을-미치는-요소">1.DOM Tree 생성시 성능에 영향을 미치는 요소</h3>
<blockquote>
<p>** DOM Tree를 생성하는 동안 JavaScript 파일을 동기적으로 불러오면 HTML 파싱이 중단된다.**
❓ 브라우저는 HTML 문서를 위에서 아래로 순차적으로 파싱하며, 이 과정에서 <code>&lt;script&gt;</code> 태그를 만나면 잠시 파싱을 중단한다. 이는 JavaScript 코드가 HTML 문서의 상태에 영향을 줄 수 있기 때문이다. 브라우저는 해당 스크립트를 로드하고 실행한 후에야 다시 HTML 파싱을 재개하므로, 전체 파싱 시간이 늘어날 수 있다.</p>
</blockquote>
<ol>
<li><p>*<em><code>&lt;script&gt;</code> 태그는 가능한 한 HTML의 body 끝에 위치하도록 하자. *</em></p>
<br /></li>
<li><p>** 비동기 로딩을 하자.**
<code>defer</code> 속성 사용 :
HTML 파싱이 완료된 후 스크립트가 실행된다. 순차적인 실행이 필요할 때 유용하다.</p>
<pre><code class="language-javascript">&lt;script src=&quot;script.js&quot; defer&gt;&lt;/script&gt;</code></pre>
<p><code>async</code> 속성 사용:
JavaScript 파일이 병렬로 로드되며, 로드 완료 즉시 실행된다. 주로 독립적인 스크립트에 적합하다.</p>
<pre><code class="language-javascript">&lt;script src=&quot;script.js&quot; async&gt;&lt;/script&gt;</code></pre>
<br />

</li>
</ol>
<h3 id="2-cssom-tree-생성시-성능에-영향을-미치는-요소">2. CSSOM Tree 생성시 성능에 영향을 미치는 요소</h3>
<blockquote>
<p><strong>CSS 파일의 크기와 복잡성이 CSSOM 트리 생성 성능에 영향을 미친다.</strong>
❓ 브라우저는 HTML을 파싱하면서 스타일 시트를 로드하여 CSSOM 트리를 생성한다. 이 과정에서 CSS 파일이 너무 크거나 복잡하면 브라우저가 이를 해석하는 데 시간이 더 걸리게 된다. 특히, 여러 개의 CSS 파일을 불러오거나 복잡한 선택자가 많을 경우, CSSOM 트리 생성 시간이 길어져 렌더링 성능에 영향을 줄 수 있다.</p>
</blockquote>
<p><strong>1. CSS 파일 크기는 가능한 한 최적화하자. **
  여러 개의 CSS 파일을 병합하여 하나의 파일로 만드는 것이 HTTP 요청 수를 줄이고 로딩 시간을 단축시킬 수 있다. (</strong> SSR일 경우)</p>
<p><strong>2. 사용하지 않는 CSS는 제거하자.</strong>
  사용하지 않는 CSS를 제거하면, 불필요한 스타일 규칙이 CSSOM 트리에 포함되지 않아 CSSOM 트리 크기를 줄인다. 또한, 파일 크기가 줄어들어 페이지 로딩 시간을 단축할 수 있다.</p>
<p><strong>3. CSS 복잡성 최소화하기.</strong> 
복잡한 선택자나 스타일 규칙이 많으면 브라우저가 이를 해석하는 데 시간이 더 걸린다. <code>!important</code>나 중첩된 선택자들을 과도하게 사용하는 경우 성능에 악영향을 미칠 수 있다.
  <br /></p>
<h3 id="3-render-tree-생성시-성능에-영향을-미치는-요소">3. Render Tree 생성시 성능에 영향을 미치는 요소</h3>
<blockquote>
<p> <strong>CSS 선택자가 깊이가 깊거나 불피요한 태그가 중첩되어 있으면  Render Tree 생성이 느려질 수 있다.</strong>
❓ Render Tree를 생성하려면 브라우저는 HTML의 각 요소(DOM 노드)에 적합한 스타일을 적용하기 위해 CSSOM과 DOM Tree를 결합해야 한다. 이 과정에서 브라우저는 CSS 선택자를 DOM 요소에 일치시키는 작업을 수행힌다. 예를 들어, header &gt; div &gt; ul &gt; li &gt; span &gt; p와 같은 CSS 선택자로 스타일이 정의된 경우, 브라우저는 p 태그를 만날 때마다 모든 계층의 부모 요소를 확인해야 하므로 연산 비용이 증가한다. 또한, 태그가 불필요하게 중첩되어 있다면 스타일을 적용하는 과정에서 부모 요소를 하나하나 확인해야 하므로 성능이 더욱 저하될 수 있다.</p>
</blockquote>
<p><strong>1. CSS 선택자를 단순화하고 클래스를 활용하자.</strong>
  복잡한 CSS 선택자 <code>header &gt; div &gt; ul &gt; li &gt; span &gt; p</code>  대신 클래스나 ID를 사용하자.</p>
<pre><code class="language-css">  /* 비효율적 */
header &gt; div &gt; ul &gt; li &gt; span &gt; p { color: red; }
/* 효율적 */
.highlight { color: red; }</code></pre>
<p><strong>2. 복잡한 DOM 트리를 지양하자.</strong>
DOM 트리가 깊고, 자식 요소가 많을수록 DOM 트리는 커지고 그에 따른 계산이 추가되므로, 불필요한 태그 중첩을 최소화 하자.</p>
<pre><code class="language-javascript">/* 비효율적 */
&lt;div&gt;
  &lt;div&gt;
    &lt;div&gt;
      &lt;p&gt;텍스트&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
/*효율적*/
&lt;div&gt;
  &lt;p&gt;텍스트&lt;/p&gt;
&lt;/div&gt;</code></pre>
<h3 id="4-layout-과정-중-성능에-영향을-미치는-요소">4. Layout 과정 중 성능에 영향을 미치는 요소</h3>
<blockquote>
<p><strong>불필요한 리플로우(리렌더링)를 최소화 해야 한다.</strong>
  ❓ 리플로우(Reflow)는 DOM의 구조나 스타일에 변화가 생기면 브라우저가 새로 레이아웃을 계산하는 과정을 말한다. 브라우저는 요소의 크기, 위치, 정렬 등을 계산하여 최종 화면에 표시될 요소들을 결정하는데, 이 과정에서 많은 계산과 메모리 작업이 발생한다. 특히, 레이아웃이 여러 번 재계산되면 성능에 큰 부하를 일으킬 수 있다. </p>
</blockquote>
<p><strong>1. DOM 변경을 한번에 처리하자.</strong>
여러 개의 DOM 변경이 필요한 경우, 한 번에 처리하여 리플로우를 최소화해야 한다. DOM을 여러 번 수정하는 것보다 한 번에 수정하면 브라우저는 레이아웃을 한 번만 계산하게 된다.</p>
<p><strong>2. 상위 요소에서 레이아웃 변경을 최소화하자.</strong>
부모 요소나 상위 요소의 레이아웃이 변경되면 자식 요소의 레이아웃도 함께 변경되어 성능에 영향을 미친다.  가능한 한 자식 요소에만 스타일을 변경하거나, 부모 요소의 크기나 위치를 고정하여 상위 요소의 레이아웃 변경을 피하는 것이 좋다.</p>
<p><strong>3.스타일을 동적으로 변경할 때 GPU에서 처리하도록 하자.</strong>
스타일을 동적으로 변경할 때, 특히 <code>width, height, padding, margin, top, left</code> 등의 레이아웃에 영향을 주는 스타일 속성은 리플로우를 유발한다. 
그러나 <code>transform</code>과 <code>opacity</code> 속성은 레이아웃에 영향을 미치지 않고, GPU에서 하드웨어 가속을 받아 처리되기 때문에 애니메이션이나 스타일 동적으로 변경할 때는 <code>transform</code>과 <code>opacity</code>를 사용하는 것이 좋다.</p>
<p>*<em>4. 요소를 숨길때 <code>visibility: hidden</code> 사용하자. *</em>
<code>display: none</code>은 요소 자체를 숨기기 때문에 DOM 변경이 일어나기 때문에, 요소를 숨길 때 <code>display: none</code>을 사용하는 것보다 <code>visibility: hidden</code>을 사용하는 것이 성능에 유리하다.</p>
<p><strong>5. Reflow를 발생시키는 CSS 속성을 피하자.</strong></p>
<pre><code class="language-position">box-sizing / border-color / text-align / border / border-width / 
font-family / float / font-size / font-weight / line-height / vertical-align / 
white-space / word-wrap / text-overflow / text-shadow ...</code></pre>
<h3 id="5-paint-과정-중-성능에-영향을-미치는-요소">5. Paint 과정 중 성능에 영향을 미치는 요소</h3>
<blockquote>
<p><strong>불필요한 리페인트를 최소화 해야 한다.</strong>
❓ 리페인트(Paint)는 요소의 스타일이나 색상이 변경될 때 발생하며, 리플로우보다는 비용이 적게 들지만 여전히 성능에 영향을 미칠 수 있다. 브라우저는 스타일이나 색상 변경에 따라 화면에 다시 그리기를 수행하며, 이 과정이 반복되면 화면 깜빡임이나 렌더링 지연이 발생할 수 있다. 불필요한 리페인트를 최소화하면 성능을 최적화하고, 더욱 부드러운 사용자 경험을 제공할 수 있다.</p>
</blockquote>
<p><strong>1. 스타일을 변경을 한 번에 처리하자.</strong>
스타일 변경은 리페인트를 유발하므로, 자주 스타일을 변경하는 것은 성능에 영향을 미친다. 여러 번 스타일을 변경하기보다는, 한 번에 여러 스타일을 변경하는 것이 좋다. </p>
<p><strong>2. Repaint를 발생시키는 CSS 속성을 피하자.</strong></p>
<pre><code class="language-color">background-image / background-position / background-repeat / background-size / 
text-decoration / outline / outline-style / outline-color / outline-width / 
border-radius / box-shadow ...</code></pre>
<br />
<br />
혹시라도 잘못된 내용이 있거나 질문사항 있으시면 댓글 남겨주세요! 어떤 댓글이든 달게 받겠습니다!]]></description>
        </item>
        <item>
            <title><![CDATA[성능 최적화는 왜 필요할까?]]></title>
            <link>https://velog.io/@minyoung_/%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94%EB%8A%94-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@minyoung_/%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94%EB%8A%94-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Sat, 04 Jan 2025 09:36:02 GMT</pubDate>
            <description><![CDATA[<p>요새 성능 최적화를 공부하면서 성능 최적화를 왜 해야하는 지 이유부터 확실하게 짚고 넘어가면 좋을거 같아서 이렇게 포스팅을 하게 됐다.</p>
<h3 id="🤔-성능-최적화는-왜-필요할까">🤔 성능 최적화는 왜 필요할까?</h3>
<p><strong>1. 더 나은 사용자 경험과 기업 수익 창출</strong>
이상적인 웹사이트의 로딩 속도는 2초 이내로 유지되는 것이 좋다. 만약 로딩 속도가 3초를 넘어가게 되면, 그에 따른 부정적인 영향이 나타날 수 있다. 예를 들어, 로딩 시간이 1초씩 증가할 때마다 사용자의 이탈률은 10%씩 늘어나고, 전환율은 7%씩 감소한다.</p>
<p>실제로, 우리가 잘 아는 기업인 Walmart의 사례를 보면 로딩 시간이 1초에서 4초로 증가했을 때 전환율이 급격히 감소한 것을 알 수 있다. 반면, Pinterest는 로딩 시간을 40% 단축하면서 가입자가 15% 증가하는 긍정적인 효과를 얻었다.</p>
<p>이처럼 빠르고 원활한 웹사이트는 사용자가 서비스를 계속 이용하도록 유도해 이탈률을 낮추는 데 중요한 역할을 한다. 결과적으로 사용자가 늘어나고 전환율이 높아져, 기업이 수익을 창출할 수 있는 탄탄한 기반을 마련할 수 있다.</p>
<p><strong>2. 웹 개발의 트렌드 변화</strong>
과거에는 새로운 기술을 도입하고 다양한 기능을 추가하는 것이 웹 개발의 주요 목표였다. 하지만 최근의 웹 개발 트렌드는 기술 자체보다는 성능 최적화에 중점을 두고 있는 편이다. 단순히 좋은 기술을 선보이는 것을 넘어, 페이지 로딩 속도, 사용자 인터페이스 반응 속도, 네트워크 효율성을 종합적으로 개선하려는 노력으로 나타나고 있다. </p>
<p>특히, 로딩 속도가 몇 초 차이만 나더라도 사용자의 이탈률과 전환율에 큰 영향을 미치는 만큼, 성능 최적화는 현대 웹 개발 트렌드의 방향성을 잘 보여준다.</p>
<p><strong>3. 경쟁력 있는 프론트엔드 개발자로 성장</strong>
오늘날 성능 최적화는 프론트엔드 개발자의 핵심 경쟁력으로 자리 잡고 있다. 웹 애플리케이션의 성능은 사용자 경험과 직결되며, 이는 곧 비즈니스 성공에도 영향을 미친다. 따라서, 성능 최적화를 위한 기술적 역량을 갖추고 이를 프로젝트에 적용할 수 있는 개발자는 시장에서 경쟁력 있는 개발자로 성장할 수 있다.</p>
<br />
<br />

<h3 id="🤔성능을-결정하는-요소는-어떻게-될까">🤔성능을 결정하는 요소는 어떻게 될까?</h3>
<p>성능을 결정하는 요소는 크게 두가지로 볼 수 있다.</p>
<h3 id="1-로딩-성능">1. 로딩 성능</h3>
<p>로딩 성능은 사용자가 웹페이지에 처음 접근할 때, 브라우저가 페이지를 다운로드하고 사용자에게 보여주기까지 걸리는 시간을 의미한다.</p>
<p><strong>영향을 주는 요소:</strong></p>
<ul>
<li><p><strong>자원 크기</strong>: HTML, CSS, JavaScript, 이미지 파일 크기. 파일 크기가 클수록 로딩에 시간이 더 걸리기 때문에, 다운로드와 렌더링까지의 시간이 증가한다.</p>
</li>
<li><p><strong>네트워크 속도</strong>: 사용자 환경에서의 다운로드 속도. 느린 네트워크 환경에서는 데이터를 다운로드하는 데 시간이 더 걸리기 때문에 페이지 로드가 지연된다.</p>
</li>
<li><p><strong>HTTP 요청</strong>: 여러 개의 요청을 하나로 묶으면 서버와의 왕복 횟수를 줄여 로딩 시간을 단축할 수 있다.</p>
</li>
</ul>
<h3 id="2-렌더링-성능">2. 렌더링 성능</h3>
<p>렌더링 성능은 웹페이지가 사용자에게 시각적으로 표시되기까지의 시간과 관련이 있으며, 페이지 로드 후 브라우저가 DOM을 구성하고 스타일을 적용하고 최종적으로 화면에 표시하는 데 걸리는 시간을 의미한다.</p>
<p><strong>영향을 주는 요소</strong></p>
<ul>
<li><p><strong>DOM 크기</strong>: HTML 문서의 구조와 요소의 개수. DOM 트리가 커지면 브라우저가 DOM을 처리하는 데 시간이 더 걸려 렌더링이 지연될 수 있다.</p>
</li>
<li><p><strong>CSS 스타일 적용</strong>: CSS 파일의 크기와 복잡도, 적용되는 스타일 규칙의 수. 스타일 계산과 렌더 트리 생성에 시간이 소요되며, 복잡한 스타일 규칙은 렌더링 성능을 저하시킬 수 있다.</p>
</li>
<li><p><strong>JavaScript 실행</strong>: JavaScript의 실행 속도. 렌더링을 차단하는 JavaScript 코드(예: document.write(), 동기적 스크립트)는 렌더링 흐름을 방해하고, 스크립트가 실행되는 동안 페이지가 표시되지 않게 만들 수 있다.</p>
</li>
<li><p><strong>Reflow와 Repaint</strong>: DOM이나 스타일이 변경될 때마다 레이아웃 계산(reflow)과 화면 다시 그리기(repaint)가 발생한다. 과도한 Reflow 및 Repaint는 렌더링 성능을 저하시킬 수 있다.</p>
</li>
<li><p><strong>컴포넌트 리렌더링</strong>: React나 Vue와 같은 프레임워크에서 컴포넌트의 리렌더링이 불필요하게 자주 일어날 경우, 성능 저하를 초래할 수 있다.</p>
</li>
<li><p><strong>이미지 및 미디어 요소</strong>: 이미지나 비디오 파일이 크거나 처리 과정에서 렌더링에 영향을 줄 수 있다.</p>
</li>
</ul>
<br />
<br />

<h2 id="✅성능-분석-방법">✅성능 분석 방법</h2>
<p>웹 성능을 분석할때 아래와 같은 도구를 사용한다.</p>
<p>크롬 개발자도구</p>
<ul>
<li><a href="https://velog.io/@minyoung_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-Network-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0">Network 패널</a></li>
<li><a href="https://velog.io/@minyoung_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-Performance-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0">Performance 패널</a></li>
<li><a href="https://velog.io/@minyoung_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-Lighthouse-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0">Lighthouse 패널</a></li>
<li><a href="https://velog.io/@minyoung_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-Coverage-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0">Coverage 패널</a></li>
</ul>
<p>번들 사이즈 분석 라이브러리</p>
<ul>
<li><a href="https://velog.io/@minyoung_/webpack-bundle-analyzer-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">webpack-bundle-analyzer</a></li>
</ul>
<p>각 도구 사용법에 대한 정리 글을 링크 걸어놨다. 참고하시면 좋을 거 같다.</p>
<br />
<br />

<h2 id="✅성능-최적화-방법">✅성능 최적화 방법</h2>
<p>위에 도구들을 이용하여 현재 개선해야할 성능의 우선순위를 파악했다면 아래에 기법들을 통해 성능 최적화를 할 수 있다.</p>
<h4 id="1-리소스-최적화">1. 리소스 최적화</h4>
<p>리소스 최적화는 자바스크립트, CSS, 이미지 파일 등 웹 페이지에서 사용하는 리소스의 크기를 최소화하는 작업이다. 이를 통해 페이지 로딩 시간을 단축시키고, 네트워크 대역폭 사용을 줄일 수 있다.</p>
<h4 id="2-코드-스플리팅-code-splitting">2. 코드 스플리팅 (Code Splitting)</h4>
<p>코드 스플리팅은 애플리케이션에서 필요한 코드만 로드하는 방법이다. 이를 통해 초기 로딩 시간을 단축시킬 수 있다. 사용자가 애플리케이션을 사용하는 동안 불필요한 코드가 로드되지 않도록 하여 성능을 개선한다.</p>
<h4 id="3-이미지-최적화">3. 이미지 최적화</h4>
<p>이미지는 웹 페이지 로딩 시간에 큰 영향을 미친다. 이를 최적화하면 성능을 크게 향상시킬 수 있다.</p>
<h4 id="4-캐싱-전략">4. 캐싱 전략</h4>
<p>캐싱은 서버나 브라우저에서 이미 요청된 리소스를 저장하여, 같은 리소스를 다시 요청할 때 불필요한 네트워크 요청을 줄이는 방법이다. 효과적인 캐싱 전략은 페이지 로딩 시간을 크게 단축시킬 수 있다.</p>
<br />
성능 최적화 방법에 대해서는 간단하게만 적겠다. 위 방법들은 추후에 하나씩 올릴 예정이다!

<h4 id="출처">출처</h4>
<ul>
<li><a href="https://www.cloudflare.com/resources/assets/slt3lc6tev37/6BVIJvRAQBrUfcZVyhY8hY/2a593ed9c28283fd0b81b07d52c01e64/Whitepaper_Getting-Faster-Know-your-website-know-whats-slowing-it-down_Korean_2021022.pdf">https://www.cloudflare.com/resources/assets/slt3lc6tev37/6BVIJvRAQBrUfcZVyhY8hY/2a593ed9c28283fd0b81b07d52c01e64/Whitepaper_Getting-Faster-Know-your-website-know-whats-slowing-it-down_Korean_2021022.pdf</a></li>
</ul>
<br />
<br />
혹시라도 잘못된 내용이 있거나 질문사항 있으시면 댓글 남겨주세요! 어떤 댓글이든 달게 받겠습니다!]]></description>
        </item>
        <item>
            <title><![CDATA[webpack-bundle-analyzer 사용하기]]></title>
            <link>https://velog.io/@minyoung_/webpack-bundle-analyzer-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@minyoung_/webpack-bundle-analyzer-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 04 Jan 2025 08:44:01 GMT</pubDate>
            <description><![CDATA[<h3 id="🤔-webpack-bundle-analyzer란-무엇인가">🤔 webpack-bundle-analyzer란 무엇인가?</h3>
<p>webpack-bundle-analyzer는 Webpack에서 생성된 번들의 크기를 시각적으로 분석할 수 있는 도구이다.</p>
<ul>
<li>Webpack 빌드 과정에서 생성된 번들을 트리 형태의 인터랙티브한 시각화로 보여준다.</li>
<li>번들의 구조를 분석하여 어떤 모듈이 포함되어 있고, 각각의 모듈이 얼마나 많은 크기를 차지하는지 한눈에 파악할 수 있다.<br />

</li>
</ul>
<h3 id="🔎-webpack-bundle-analyzer-활용법">🔎 webpack-bundle-analyzer 활용법</h3>
<p>webpack-bundle-analyzer는 아래와 같은 상황에서 활용된다.</p>
<ul>
<li><p><strong>번들 크기 개선</strong>: 번들에 포함된 불필요하거나 중복된 모듈을 식별하여 번들 크기를 줄일 수 있다.</p>
</li>
<li><p><strong>로딩 성능 개선</strong>: 번들의 크기가 클수록 로딩 시간이 증가하므로, 이를 최적화하여 사용자 경험을 개선할 수 있다.</p>
</li>
<li><p><strong>디펜던시 관리</strong>: 예상보다 큰 외부 라이브러리나 불필요한 라이브러리를 발견하여 효율적으로 관리할 수 있다.</p>
</li>
<li><p><strong>코드 분리 검토</strong>: 코드 스플리팅 상태를 확인하고, 적절히 분리되지 않은 모듈을 찾아 수정할 수 있다.</p>
</li>
<li><p><strong>의존성 추가 후 점검</strong>: 새로운 라이브러리를 추가한 후, 번들 크기와 성능에 미치는 영향을 분석할 수 있다.</p>
</li>
<li><p><strong>불필요한 모듈 제거</strong>: 중복되거나 사용하지 않는 모듈을 제거할 필요성을 파악할 수 있다.</p>
</li>
</ul>
<h2 id="✅webpack-bundle-analyzer-사용-방법">✅webpack-bundle-analyzer 사용 방법</h2>
<ol>
<li><p>설치 </p>
<pre><code class="language-javascript">npm install --save-dev webpack-bundle-analyzer</code></pre>
<pre><code class="language-javascript">yarn add --dev webpack-bundle-analyzer</code></pre>
</li>
<li><p>Webpack 설정에 플러그인 추가</p>
</li>
</ol>
<pre><code class="language-javascript">const { BundleAnalyzerPlugin } = require(&#39;webpack-bundle-analyzer&#39;);

module.exports = {
  // Webpack 설정...
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: &#39;server&#39;, // &#39;server&#39;, &#39;static&#39;, &#39;disabled&#39; 옵션 가능
      openAnalyzer: true,    // 브라우저에서 결과 자동 오픈 여부
      generateStatsFile: false, // stats.json 파일 생성 여부
      statsFilename: &#39;stats.json&#39;, // stats 파일 이름
      logLevel: &#39;info&#39;,      // 로그 수준
    }),
  ],
};</code></pre>
<p><strong>분석 모드 설정</strong></p>
<ul>
<li>server: 로컬 서버를 실행하여 브라우저에서 결과를 볼 수 있다.</li>
<li>static: HTML 파일로 번들 분석 결과를 생성한다.</li>
<li>disabled: 분석 도구를 비활성화한다.</li>
</ul>
<ol start="4">
<li>Webpack 빌드 실행</li>
</ol>
<pre><code class="language-javascript">npm run build</code></pre>
<p>빌드가 완료되면 브라우저에 분석 결과가 나타난다(또는 HTML 파일이 생성된다)</p>
<br />
<br />
혹시라도 잘못된 내용이 있거나 질문사항 있으시면 댓글 남겨주세요! 어떤 댓글이든 달게 받겠습니다!

]]></description>
        </item>
        <item>
            <title><![CDATA[개발자도구 Coverage 살펴보기]]></title>
            <link>https://velog.io/@minyoung_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-Coverage-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@minyoung_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-Coverage-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 04 Jan 2025 06:23:18 GMT</pubDate>
            <description><![CDATA[<p>지금까지 성능 최적화를 위한 개발자도구로 Network, Performance, Lighthouse까지 알아봤다. 마지막으로 Coverage에 대해 알아보자!</p>
<h3 id="🙄coverage-란">🙄Coverage 란?</h3>
<blockquote>
<p>개발자도구의 Coverage탭은 웹 애플리케이션의 코드(HTML, CSS, JavaScript)의 사용량을 분석하여 어떤 코드가 실제로 사용되고 있는지, 그리고 어떤 코드가 사용되지 않고 있는지를 보여준다. 이를 통해 코드의 최적화와 불필요한 코드 제거에 유용하다.</p>
</blockquote>
<br />
<br />

<h3 id="🔎coverage탭-열어보기">🔎Coverage탭 열어보기</h3>
<p>Coverage 탭은 기본적으로 숨겨져 있는 기능이다. Chrome 개발자 도구(DevTools)에서 자주 사용하는 탭(예: Console, Elements, Network)은 기본적으로 보이지만, Coverage와 같은 고급 분석 도구는 기본으로 표시되지 않기 때문에 직접 탭을 열어야 한다.</p>
<p><strong>방법 1</strong></p>
<ul>
<li>Chrome 개발자 도구를 열고, 오른쪽 상단의 메뉴(점 세 개)를 클릭한다.</li>
<li>More Tools → Coverage를 선택한다.</li>
</ul>
<p><strong>방법 2</strong></p>
<ul>
<li>개발자 도구에서 window: ctrl + shift + / mac: cmd + shift + p  를 누르고 &#39;show coverage&#39;를 검색해 선택한다.</li>
</ul>
<h2 id="✅coverage-살펴보기">✅Coverage 살펴보기</h2>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/b18ece41-bab6-4eef-aedf-4ecf1de72c09/image.png" alt=""></p>
<p><strong>1. 새로고침 버튼 (⟳)</strong>
페이지를 새로고침하며, 로드된 리소스(HTML, CSS, JS 등)의 사용 여부를 실시간으로 기록한다.</p>
<p><strong>2. 리소스 유형 필터링</strong>
 특정 리소스 유형(CSS, JavaScript 등)만 필터링해서 확인할 수 있습니다. </p>
<p><strong>3. Content Scripts</strong>
Chrome 확장 프로그램에서 로드한 스크립트를 포함해 분석하려면 이 옵션을 활성화한다. 확장 프로그램이 페이지 성능에 미치는 영향을 분석할 때 사용한다.</p>
<p><strong>4. URL 검색 필드</strong>
특정 URL이나 파일 이름을 입력하여 원하는 리소스를 검색한다.</p>
<p><strong>5. 다운로드 버튼 (⤓)</strong>
분석된 Coverage 데이터를 JSON 파일로 다운로드합니다.</p>
<p><strong>6. Per Function / Per Block</strong>
코드 사용량을 분석할 단위를 선택한다.</p>
<ul>
<li>Per Function: 함수 단위로 사용 여부를 분석한다.</li>
<li>Per Block: 코드 블록(구문 단위)별로 더 세밀하게 분석한다.<br />
<br />

</li>
</ul>
<h2 id="✅coverage-데이터-읽고-해석하기">✅Coverage 데이터 읽고 해석하기</h2>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/a8d707c6-675b-4ff5-99f9-5effa6dd676f/image.png" alt=""></p>
<p><strong>1. URL</strong>
해당 리소스의 경로(파일 경로)가 표시된다. 클릭하면 세부 분석이 가능하며, 사용된 코드와 사용되지 않은 코드가 구분된다.</p>
<p><strong>2. Type</strong>
리소스의 유형을 나타낸다.</p>
<ul>
<li>JS: JavaScript 파일</li>
<li>CSS: 스타일시트 파일</li>
<li>Other: 기타 파일 형식(이미지, HTML 등)</li>
</ul>
<p><strong>3. Total Bytes</strong>
리소스의 전체 크기를 바이트 단위로 표시한다. 리소스가 얼마나 무거운지 확인할 수 있다.</p>
<p><strong>4. Unused Bytes</strong>
로드된 리소스 중 사용되지 않은 부분의 크기를 바이트 단위로 표시한다. 최적화가 필요한 리소스를 쉽게 식별할 수 있다.</p>
<p><strong>5. Usage Visualization</strong>
사용된 코드와 사용되지 않은 코드의 비율을 시각적으로 보여준다.</p>
<ul>
<li>빨간색: 사용되지 않은 코드 비율.</li>
<li>회색/없음: 사용된 코드 비율.</li>
</ul>
<p>빨간색이 많을수록 사용되지 않은 코드가 많아, 최적화 필요성이 높다.
<br /></p>
<p><strong>해석 예시</strong></p>
<blockquote>
<p>파일: localhost:3000/static/js/2.chunk.js
Total Bytes: 1,876,849 bytes (약 1.8MB)
Unused Bytes: 1,835,203 bytes (약 97.8% 미사용)
Usage Visualization: 빨간색이 거의 전체를 차지함.
해석: 대부분 사용되지 않은 코드로, 최적화하거나 분리하여 코드 크기를 줄일 필요가 있음.</p>
</blockquote>
<blockquote>
<p>파일: localhost:3000/static/js/1.chunk.js
Total Bytes: 2,229,941 bytes (약 2.2MB)
Unused Bytes: 186,087 bytes (약 8.3% 미사용)
Usage Visualization: 빨간색이 거의 없음.
해석: 대부분의 코드가 사용되고 있으므로 최적화 우선순위는 낮음.</p>
</blockquote>
<p>Coverage탭은 위처럼 미사용 코드 비율을 기준으로 최적화 우선순위를 정하는 데 유용하다. 이를 통해 사용되지 않은 코드를 식별하고, 최적화를 통해 리소스 크기를 줄이고 로딩 성능을 개선할 수 있다.</p>
<br />
<br />

<p>혹시라도 잘못된 내용이 있거나 질문사항 있으시면 댓글 남겨주세요! 어떤 댓글이든 달게 받겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자도구 Lighthouse 살펴보기]]></title>
            <link>https://velog.io/@minyoung_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-Lighthouse-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@minyoung_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-Lighthouse-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 03 Jan 2025 10:36:14 GMT</pubDate>
            <description><![CDATA[<p>Lighthouse는 이전에 부트캠프에서 2차프로젝트 때 사용해본 경험이 있다. 그때는 벨로그에 올린거만 보고 무작정 해봤지만 이후에 잘 사용하지 않았다. 이번에 성능 최적화 공부하면서 Lighthouse도 정리해보는게 좋을 거 같아 포스팅을 해본다.</p>
<p>이번엔 Lighthouse에 대해 살펴보자!</p>
<h3 id="🙄lighthouse-란">🙄Lighthouse 란?</h3>
<blockquote>
</blockquote>
<p>Lighthouse는 웹 애플리케이션의 품질과 성능을 평가하고 개선할 수 있도록 도와주는 오픈소스 자동화 도구이다. 구글이 개발하여 Google Chrome 브라우저 개발자 도구에 통합되어 있으며, 확장 프로그램 형태로도 사용할 수 있다. 
Lighthouse는 웹 페이지를 분석하여 성능, 접근성, SEO, PWA(Progressive Web App) 등 다양한 항목에 대해 점수와 구체적인 개선 방안을 제공해준다.</p>
<br />
<br />

<h2 id="✅-lighthouse-살펴보기">✅ Lighthouse 살펴보기</h2>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/7e5ba38e-2583-4c75-b9ab-7db719112b84/image.png" alt=""></p>
<h4 id="1-mode">1. Mode</h4>
<p>Lighthouse가 페이지를 분석하는 방식이다.</p>
<ul>
<li>Navigation (Default): 페이지 로드를 시뮬레이션하고 전체 성능을 측정한다. 일반적으로 초기 로딩 성능을 측정할 때 사용한다.</li>
<li>Timespan: 특정 시간 동안 사용자 작업을 측정한다. 페이지 간 탐색이나 상호작용 성능을 평가할 때 유용하다.</li>
<li>Snapshot: 현재 상태의 페이지를 스냅샷으로 분석한다. 동적 콘텐츠가 많지 않은 페이지의 정적 성능을 측정할 때 적합하다. </li>
</ul>
<h4 id="2-device">2. Device</h4>
<p>분석할 디바이스 유형을 선택한다.</p>
<ul>
<li>Mobile: 모바일 환경을 시뮬레이션한다.</li>
<li>Desktop: 데스크탑 환경을 시뮬레이션한다.</li>
</ul>
<h4 id="3-categories">3. Categories</h4>
<p>보고서에 포함할 카테고리를 선택한다.</p>
<ul>
<li>Performance: 페이지의 로딩 속도와 사용자 경험의 효율성을 측정하는 지표이다.</li>
<li>Accessibility: 웹 콘텐츠가 장애가 있는 사용자를 포함해 모든 사용자에게 얼마나 접근 가능한지 평가한다.</li>
<li>Best Practices: 웹사이트가 최신 웹 개발 표준과 보안 규칙을 준수하는지 평가한다. </li>
<li>SEO (검색 엔진 최적화): 검색 엔진 크롤러가 사이트를 효율적으로 탐색하고 색인할 수 있는지 평가한다.</li>
</ul>
<p>각 항목들은 아래 보고서를 보면서 좀 더 자세히 살펴보자.</p>
<h4 id="4analyze-page-load">4.Analyze Page Load</h4>
<ul>
<li>선택된 설정에 따라 Lighthouse 보고서를 생성한다. 보고서에는 선택한 카테고리에 대한 점수와 상세한 분석 결과가 포함된다.</li>
</ul>
<br />
<br />

<h2 id="✅lighthouse-보고서-보고-이해하기">✅Lighthouse 보고서 보고 이해하기</h2>
<p>Analyze Page Load 버튼을 누르면 자동으로 분석을 해준다. 분석 후 나온 보고서에서 Performance(성능), Accessibility(접근성), Best Practices(권장사항), SEO(검색엔진 최적화)에 대해 좀 더 자세히 보자.</p>
<h3 id="performance성능">Performance(성능)</h3>
<blockquote>
<p>Performance는 웹 성능을 측정하여 화면에 콘텐츠가 표시되는 데 걸리는 시간, 콘텐츠가 표시된 후 사용자와 상호작용할 수 있는 상태가 되기까지의 시간, 그리고 화면에 불안정한 요소(레이아웃 이동 등)가 있는지 등을 평가한다.</p>
</blockquote>
<p><strong>Performance 점수는 왜 중요한가?</strong>
사용자는 로딩이 느린 페이지를 떠나버리는 경향이 있다. (3초 이상 지연되면 53%의 사용자가 이탈)
특히 모바일 환경에서는 더욱 빠른 반응성을 요구한다.
결국 빠른 로딩 속도는 사용자 경험에 직접적인 영향을 미치기 때문에 사용자 이탈을 막기 위해서는 Performance 점수를 신경써야 한다.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/55ef67ef-9971-46b4-bfa2-107f948da801/image.png" alt=""></p>
<p>위 사진을 보면 성능 점수가 60점으로 나오는데 성능 점수는 METRICS에 보이는 5가지 지표에 가중치를 적용해 평균 낸 점수이다.
각 지표마다 가중치는 아래에 표에서 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/128c4d47-62d7-46b6-92cd-54b7e73ed9da/image.png" alt="">
<strong>주요 측정 항목:</strong></p>
<ul>
<li><strong>First Contentful Paint (FCP)</strong>: 첫 번째로 페이지의 콘텐츠(텍스트, 이미지 등)가 화면에 렌더링되는 시간</li>
<li><strong>Largest Contentful Paint (LCP)</strong>: 페이지의 가장 큰 콘텐츠가 화면에 렌더링되는 시간. 페이지 로딩 경험의 핵심 지표</li>
<li><strong>Cumulative Layout Shift (CLS)</strong>: 페이지 로드 중 예상치 못한 레이아웃 변경 정도를 측정. 레이아웃 안정성을 평가한다.</li>
<li><strong>Total Blocking Time (TBT)</strong>: 페이지가 사용자와 상호작용할 수 없는 시간. JavaScript 실행 시간과 밀접하게 연관된다.</li>
<li><strong>Speed Index(SI)</strong>: 콘텐츠가 화면에 얼마나 빠르게 표시되는지 측정</li>
</ul>
<br />
<br />

<h3 id="accessibility접근성">Accessibility(접근성)</h3>
<blockquote>
<p>Accessibility는 웹 콘텐츠에 장애가 있는 사용자를 포함한 모든 사용자가 접근할 수 있는지 평가하며, 키보드 네비게이션, 적절한 색 대비, 대체 텍스트 제공 여부 등 접근성 지표를 측정한다.</p>
</blockquote>
<p><strong>Accessibility 점수는 왜 중요한가?</strong>
접근성이 낮은 사이트는 장애가 있는 사용자(시각, 청각, 운동 장애 등)가 이용할 수 없다. 이는 사용자층을 제한하고, 법적 소송의 위험을 높인다.
Accessibility 점수를 올리면 더 많은 사용자가 사이트를 사용할 수 있게 되고, 기업에서는 포용적이고 윤리적인 브랜드 이미지를 구축할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/c99c82c4-a430-48a9-b6cd-20c8adc26f23/image.png" alt=""></p>
<p><strong>주요 측정 항목:</strong></p>
<ul>
<li>색 대비: 텍스트와 배경의 대비가 충분한지 확인한다. (시각 장애인을 위한 중요 요소)</li>
<li>적절한 ARIA 속성: ARIA 역할 및 태그가 올바르게 사용되었는지 점검</li>
<li>키보드 내비게이션 가능 여부: 마우스를 사용하지 않고 키보드만으로 페이지를 탐색할 수 있는지 평가</li>
<li>명확한 라벨 및 설명: 버튼, 링크, 폼 필드에 적절한 라벨이 있는지 확인</li>
<li>언어 속성 지정 여부: html 태그에 lang 속성이 정의되었는지 점검</li>
</ul>
<br />
<br />

<h3 id="best-practices권장사항">Best Practices(권장사항)</h3>
<blockquote>
<p>Best Practices는 웹 페이지가 HTTPS를 사용하는지, 최신 기술과 API를 활용하는지, 불필요한 리소스 요청을 줄였는지, 그리고 보안 및 성능을 강화하는 웹 개발 모범 사례를 준수하고 있는지 평가한다.</p>
</blockquote>
<p><strong>Best Practices 점수는 왜 중요한가?</strong>
Best Practices는 보안과 안정성을 높이고, 최신 기술 표준을 준수하는 데 핵심적인 역할을 한다.
점수가 낮을 경우 HTTPS 미사용, 오래된 API, 취약 라이브러리 사용 등의 문제로 인해 보안 사고가 발생할 수 있다.
사용자 데이터 유출은 서비스 신뢰도를 하락시키고 금전적 손실로 이어진다.
Best Practices 점수를 올리면 사이트의 안정성과 보안이 강화되어 사용자 신뢰를 높일 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/0cedc851-2005-4e93-978f-0636fafd861e/image.png" alt="">
<strong>주요 측정 항목:</strong></p>
<ul>
<li>HTTPS 사용: 모든 네트워크 요청이 안전한 HTTPS를 사용하는지 확인</li>
<li>안전한 JavaScript 라이브러리: 알려진 취약점을 가진 라이브러리를 사용하는지 평가</li>
<li>브라우저 호환성: 최신 브라우저에서 예상대로 작동하는지 점검</li>
<li>이미지 및 비디오 포맷 사용 권장: WebP, AVIF 등 최신 포맷 사용 여부</li>
<li>스크립트 로드 최적화: JavaScript 및 CSS를 비동기로 로드하여 차단을 방지</li>
</ul>
<br />
<br />

<h3 id="seo검색엔진-최적화">SEO(검색엔진 최적화)</h3>
<blockquote>
<p>SEO는 검색 엔진에서의 가시성을 높이기 위해 사이트의 콘텐츠 구조, 메타 태그, 키워드 최적화 여부, 모바일 친화성, 그리고 크롤링에 문제가 없는지 등을 평가한다.</p>
</blockquote>
<p><strong>SEO 점수는 왜 중요한가?</strong>
SEO는 검색 엔진에서의 가시성을 높이는 핵심 지표다.
검색 엔진 결과 페이지(SERP)에서 상위에 표시되지 않으면 사용자가 사이트를 찾을 확률이 급격히 낮아진다.
특히, 제품 판매나 서비스 제공 사이트의 경우 SEO 점수가 매출에 직결된다.
SEO 점수를 올리면 검색 순위가 상승하여 더 많은 트래픽을 유도할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/dd6a5327-3eb1-41be-be0d-d9d942100514/image.png" alt="">
<strong>주요 측정 항목:</strong></p>
<ul>
<li>메타 태그: 제목 및 설명이 설정되었는지 확인. (검색 결과의 클릭률에 영향)</li>
<li>모바일 친화성: 페이지가 모바일 환경에서 제대로 표시되는지 점검</li>
<li>링크 속성: 외부 링크에 rel=&quot;nofollow&quot;나 rel=&quot;noopener&quot;가 올바르게 설정되었는지 확인</li>
<li>Canonical 태그: 중복 콘텐츠를 방지하기 위해 설정되었는지 확인</li>
</ul>
<br />
<br />
혹시라도 잘못된 내용이 있거나 질문사항 있으시면 댓글 남겨주세요! 어떤 댓글이든 달게 받겠습니다!]]></description>
        </item>
        <item>
            <title><![CDATA[개발자도구 Performance 살펴보기]]></title>
            <link>https://velog.io/@minyoung_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-Performance-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@minyoung_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-Performance-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Thu, 02 Jan 2025 16:10:10 GMT</pubDate>
            <description><![CDATA[<p>지금까지 개발을 하면서 기능 구현에만 집중하다 보니 성능 최적화에 신경 쓸 여유가 없었다. 그러다 보니 브라우저 개발자 도구의 Performance 탭에 대해 잘 알지 못했고, 실제로 활용해 본 적도 없었다. 하지만 최근 웹 성능 최적화에 관심을 가지면서 처음으로 Performance 탭을 직접 사용해 보았다.
아직 Network 탭만큼 익숙하게 다루지는 못하지만, 공부할 겸 Performance탭을 정리해 보려고 한다. </p>
<p>이번엔 Performance 탭에 대해 살펴보자!
<br /></p>
<h3 id="🙄performance-탭이란">🙄Performance 탭이란?</h3>
<blockquote>
<p>개발자도구의 퍼포먼스 탭은 웹 애플리케이션의 성능을 분석하고 최적화할 수 있는 도구로, 웹 페이지의 로딩 및 사용자 상호작용 과정에서 발생하는 모든 작업(스크립트 실행, 렌더링, 이벤트 처리 등)을 시각적으로 확인할 수 있다.
이 탭을 사용하면 성능 병목 구간을 찾아내고, 페이지 로드 속도 및 렌더링 성능을 개선할 수 있다. 특히, 애니메이션, 스크롤 성능, 자바스크립트 실행 최적화 등 다양한 성능 문제를 디버깅하는 데 유용하다.</p>
</blockquote>
<br />
<br />


<h2 id="✅performance-탭-살펴보기">✅Performance 탭 살펴보기</h2>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/8a7def31-9bfa-4c50-9953-5a702630b95e/image.png" alt=""></p>
<p><strong>1. 녹화 버튼 (●)</strong></p>
<ul>
<li>성능 기록을 시작한다. 클릭하면 녹화가 시작되고, 다시 클릭(■)하면 녹화가 중지된다.</li>
</ul>
<p>*<em>2. Record and Reload (🔄)  *</em></p>
<ul>
<li>페이지를 새로고침하면서 자동으로 성능 데이터를 기록한다. 초기 로드 성능을 분석할 때 유용하다.</li>
</ul>
<p><strong>3. Clear 버튼 (⊘)</strong></p>
<ul>
<li>이전에 기록한 성능 데이터를 지우고 초기화한다.</li>
</ul>
<p><strong>4. Download 버튼 (⬇)</strong></p>
<ul>
<li>녹화된 성능 데이터를 파일로 다운로드한다. 나중에 분석하거나 공유할 때 사용할 수 있다.</li>
</ul>
<p><strong>5. Screenshots</strong></p>
<ul>
<li>성능 기록 중 페이지 상태의 스크린샷을 캡처한다. 이를 통해 시간에 따른 페이지 변화를 시각적으로 확인할 수 있다.</li>
</ul>
<p><strong>6. Memory</strong></p>
<ul>
<li>메모리 사용량을 기록한다. 메모리 누수나 과도한 메모리 사용 문제를 분석할 때 유용하다.</li>
</ul>
<br />
<br />

<h2 id="✅performance-탭-성능-기록하는-방법">✅Performance 탭 성능 기록하는 방법</h2>
<p>성능 기록 방법에는 두 가지가 있다.</p>
<h4 id="1-녹화-버튼●">1. 녹화 버튼(●)</h4>
<ul>
<li>특정 사용자 상호작용(클릭, 스크롤, 애니메이션 등)이나 동작의 성능 문제를 분석</li>
<li>페이지 로드 이후 발생하는 성능 문제를 디버깅할 때 적합</li>
</ul>
<p><strong>사용예시</strong></p>
<ul>
<li>버튼 클릭 후 반응 시간이 느리거나 이벤트 실행 시간이 긴 경우</li>
<li>애니메이션이나 스크롤 동작이 부드럽지 않은 경우</li>
<li>특정 DOM 조작이나 JavaScript 코드의 성능 문제를 확인하고자 할 때<br />

</li>
</ul>
<p><strong>사용방법</strong></p>
<blockquote>
<ol>
<li>Performance 탭을 열고 녹화 버튼(●)을 클릭한다.</li>
<li>성능을 분석하려는 작업을 수행한다.(예: 버튼 클릭, 페이지 전환, 드래그 및 드롭 등)</li>
<li>작업이 완료되면 녹화 버튼(■)을 클릭하여 기록을 중지한다.</li>
<li>수집된 데이터를 분석한다.</li>
</ol>
</blockquote>
<br />

<h4 id="2-record-and-reload-버튼🔄">2. Record and Reload 버튼(🔄)</h4>
<ul>
<li>페이지 초기 로드 성능을 분석</li>
<li>새로고침과 함께 페이지의 로딩 과정에서 발생하는 문제를 디버깅할 때 적합</li>
</ul>
<p><strong>사용예시</strong></p>
<ul>
<li>페이지의 초기 렌더링 시간(FCP, LCP) 문제를 분석.</li>
<li>HTML, CSS, JS 등의 리소스 로드 시간 최적화.</li>
<li>서버 응답 시간이나 네트워크 병목 문제를 디버깅.<br />

</li>
</ul>
<p><strong>사용방법</strong></p>
<blockquote>
<ol>
<li>Performance 탭을 열고 Record and Reload 버튼(🔄)을 클릭한다.</li>
<li>페이지가 새로고침되며 성능 데이터가 자동으로 수집된다.</li>
<li>새로고침이 완료되면 브라우저가 녹화를 중지하고 데이터를 표시한다.</li>
<li>초기 로드와 관련된 데이터를 분석한다.</li>
</ol>
</blockquote>
<h2 id="✅performance-성능-보고서-보고-이해하기">✅Performance 성능 보고서 보고 이해하기</h2>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/004c8bce-a417-4287-8300-92f5e910032d/image.png" alt=""></p>
<p>위에 사진은 Record and Reload 버튼을 사용하여 페이지 초기 로드 성능을 분석한 성능 보고서이다.
굉장히 복잡해보이는데 아래에서 하나씩 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/796ebc3b-e468-4ab6-bf7d-c07e96b98d98/image.png" alt=""></p>
<h3 id="1-상단-타임라인-overview"><strong>1. 상단 타임라인 (Overview)</strong></h3>
<p>타임라인은 페이지 로드와 상호작용 동안의 주요 성능 데이터를 요약해준다.</p>
<ul>
<li><p>CPU 타임라인: CPU 사용량을 그래프로 보여준다. CPU가 과도하게 사용되는 구간을 식별할 수 있다. JavaScript(노란색) 및 레이아웃 작업(보라색)과 같은 다양한 유형의 작업으로 인해 CPU가 얼마나 달하는지 보여준다.</p>
</li>
<li><p>NET (Network): 네트워크 요청과 응답 시간을 보여준다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/04593efa-1089-4ecb-9dc2-27b8158f13f2/image.png" alt=""></p>
<h3 id="2-메인-작업-타임라인-main-section"><strong>2. 메인 작업 타임라인 (Main Section)</strong></h3>
<h4 id="1-network"><strong>1. Network</strong></h4>
<p> 네트워크 요청과 응답의 타이밍 정보를 표시하며, 각 리소스(HTML, CSS, JS, 이미지, API 요청 등)가 로드된 시간을 확인할 수 있다.</p>
<ul>
<li>요청이 시작된 시간, 응답이 완료된 시간, 대기 시간 등을 시각적으로 표시</li>
<li>네트워크 지연이나 특정 리소스 로딩 시간이 오래 걸리는 문제를 진단</li>
<li>리소스가 순차적으로 로드되었는지 또는 병렬 로드되는지 확인 가능</li>
</ul>
<h4 id="2-frames"><strong>2. Frames</strong></h4>
<p>브라우저가 각 프레임(화면 업데이트)을 렌더링하는 과정을 표시한다. Frames는 초당 렌더링되는 프레임(Frame Per Second, FPS)을 시각화하여 표시하며, 프레임별 작업 시간을 보여준다.</p>
<ul>
<li>프레임 드롭(낮은 FPS)이 발생한 위치를 파악</li>
<li>각 프레임에서 실행된 작업(스크립트 실행, 스타일 계산, 레이아웃, 페인팅 등)의 소요 시간을 확인</li>
<li>렌더링 속도를 최적화하는 데 유용</li>
</ul>
<h4 id="3-timings"><strong>3. Timings</strong></h4>
<p>페이지 로딩 중 발생한 주요 이벤트의 타이밍 정보를 표시한다.</p>
<ul>
<li>페이지 로딩 성능을 평가</li>
<li>DCL, FCP, LCP 간의 시간 간격을 분석하여 병목 현상을 파악<blockquote>
<p>DOMContentLoaded (DCL): DOM이 완전히 파싱된 시점
First Paint (FP): 화면에 첫 픽셀이 렌더링된 시점
First Contentful Paint (FCP): 텍스트나 이미지 등 페이지 콘텐츠가 처음 렌더링된 시점
Largest Contentful Paint (LCP): 가장 큰 콘텐츠가 렌더링된 시점
Time to Interactive (TTI): 페이지가 사용 가능한 상태가 되는 시간</p>
</blockquote>
</li>
</ul>
<h4 id="4-layout-shifts"><strong>4. Layout Shifts</strong></h4>
<ul>
<li><strong>Cumulative Layout Shift (CLS)</strong>와 관련된 정보를 표시하며, 페이지 로딩 중 레이아웃 이동이 발생한 위치와 정도를 보여준다.</li>
<li>높은 CLS 점수는 사용자 경험을 저하시킬 수 있으므로, 이 섹션을 통해 개선 방안을 모색이 필요하다.</li>
</ul>
<h4 id="5-main"><strong>5. Main</strong></h4>
<p>메인 스레드(Main Thread)에서 실행된 작업들을 타임라인 형식으로 보여준다.</p>
<ul>
<li>작업이 오래 걸리는 영역(병목 현상)을 분석</li>
<li>스크립트, 스타일 계산, 레이아웃 작업 중 어떤 부분에서 시간이 많이 소요되는지 파악</li>
<li>메인 스레드가 막히는 구간을 찾아 최적화</li>
</ul>
<p><strong>작업 범주</strong></p>
<blockquote>
<p>HTML 파싱 (Parse HTML): HTML 파일을 파싱하여 DOM을 생성하는 작업.
Script 실행 (Evaluate Script): JavaScript 코드 실행 작업.
Style 계산 (Style): CSS 계산 작업.
레이아웃 계산 (Layout): DOM 요소들의 위치와 크기 계산.
페인팅 및 합성 (Painting, Compositing): 요소를 화면에 렌더링</p>
</blockquote>
<h3 id="3-하단-탭들"><strong>3. 하단 탭들</strong></h3>
<h4 id="summary"><strong>Summary</strong></h4>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/876d1a70-7eb8-4fe9-9437-710710eb73d0/image.png" alt=""></p>
<ul>
<li>기록된 성능 데이터의 전체적인 작업 시간 분포를 요약하여 보여주는 섹션이다.</li>
<li>페이지 로드나 사용자 작업이 실행되는 동안 CPU가 소모한 시간을 기반으로, 각각의 작업이 얼마나 많은 비율을 차지하는지 시각적으로 파악할 수 있다.</li>
<li>원형 차트를 통해 각 작업(Loading, Scripting, Rendering, Painting 등)이 전체 시간에서 차지하는 비율을 한눈에 확인할 수 있다.</li>
</ul>
<p><strong>원형 차트 색상 의미</strong></p>
<blockquote>
</blockquote>
<p>연한 파란색 (Loading): HTML, CSS, JavaScript, 이미지 등 리소스를 네트워크를 통해 다운로드하는 데 사용된 시간
노란색 (Scripting): JavaScript 파일을 실행하고, 이벤트 핸들러를 처리하며, 동적으로 DOM을 조작하는 작업에 소요된 시간
보라색 (Rendering): CSS 스타일을 계산하고 DOM을 레이아웃 작업으로 변환(Layout), 화면에 표시하기 위해 준비하는 과정
초록색 (Painting): 픽셀 데이터를 생성하고 이를 화면에 그리는 작업
진한 회색 (System): 브라우저의 시스템 내부 처리 시간으로, 개발자가 직접 최적화하기 어려운 영역
연한 회색 (Idle): CPU가 작업을 하지 않고 대기한 시간, Idle 시간이 높다면 리소스 사용이 효율적이라는 신호일 수 있음
흰색(Total): 모든 작업의 총합 시간을 표시, 위 이미지의 경우 총 작업 시간은 약 3123ms로, 페이지 로딩 완료까지 걸린 시간을 나타냄</p>
<p><strong>Summary 활용 방법</strong>
병목 지점(가장 많은 시간을 차지하는 작업)을 확인하여 최적화가 필요한 부분을 우선적으로 파악할 수 있다. </p>
<ul>
<li>Scripting이 많다 → JavaScript 최적화 필요.</li>
<li>Rendering이 많다 → DOM 구조를 간소화하거나 스타일 최적화 필요.</li>
<li>Loading이 많다 → 네트워크 요청 병렬화, 리소스 크기 최적화 필요.</li>
</ul>
<br />
<br />

<h4 id="bottom-up"><strong>Bottom-up</strong></h4>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/e35036e8-3deb-4f68-bc1e-0d7fc4748a5b/image.png" alt=""></p>
<ul>
<li>성능 기록 데이터를 가장 낮은 레벨의 함수 호출에서 시작해 상위 작업까지의 시간 소비 분석을 제공한다.</li>
<li>각 함수가 얼마나 많은 시간을 소비했는지와 해당 함수가 호출 계층에서 차지하는 비율을 보여주어, 최적화가 필요한 병목 함수나 작업을 찾아내는 데 유용하다.<br />
<br />


</li>
</ul>
<h4 id="calltree">CallTree</h4>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/1ab9c511-acde-4f22-92de-ad099a2a2e10/image.png" alt=""></p>
<ul>
<li>호출 계층 구조를 계층적으로 정리해 상위 작업에서 하위 작업까지 추적한다.</li>
<li>상위 함수에서 하위 함수로 이어지는 전체 호출 경로를 확인이 가능해 특정 함수의 호출 흐름과 관련된 성능 문제를 파악하는데 유용하다.<br />
<br />

</li>
</ul>
<h4 id="event-log">Event Log</h4>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/79e15e14-721f-4458-b46c-02e03c673159/image.png" alt=""></p>
<ul>
<li>성능 기록에서 발생한 이벤트를 시간순으로 나열한 목록이다.</li>
<li>주요 이벤트(예: 스크립트 실행, 스타일 계산, DOM 변경 등)와 관련된 로그를 확인할 수 있다.</li>
<li>작업 발생 시점과 순서를 확인하고 문제 원인 추적할 때 유용하다.</li>
</ul>
<blockquote>
<p> Self Time: 함수 자체가 직접 소비한 시간으로 하위 함수 호출에서 소요된 시간은 포함되지 않는다. 힘수 자체의 성능 문제를 확인하는 데 유용하다.
Total Time: 특정 작업에서 소비된 총 시간으로 특정 작업 자체의  Self Time + 모든 하위 작업들의 실행 시간을 포함한다. 함수 호출이 상위 프로세스에 얼마나 영향을 미치는지 확인하는 데 유용하다.
Activity: 작업의 유형 또는 실행된 특정 활동을 나타낸다.</p>
</blockquote>
<br />
<br />
혹시라도 잘못된 내용이 있거나 질문사항 있으시면 댓글 남겨주세요! 어떤 댓글이든 달게 받겠습니다!]]></description>
        </item>
        <item>
            <title><![CDATA[개발자도구 Network 살펴보기]]></title>
            <link>https://velog.io/@minyoung_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-Network-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@minyoung_/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8F%84%EA%B5%AC-Network-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Thu, 02 Jan 2025 09:47:00 GMT</pubDate>
            <description><![CDATA[<p>요즘 웹 성능 최적화에 대해 공부하고 있다. 성능 최적화를 위해 개발자 도구에서 주로 사용되는 탭에는 Network, Performance, Lighthouse 등이 있다. Network탭은 기존에 개발하면서 자주 사용하던 기능이지만, 복습할 겸 오늘은 Network 탭에 대해 다시 정리해보려고 한다.</p>
<h3 id="🙄network-탭이란">🙄Network 탭이란?</h3>
<blockquote>
<p>개발자도구의 네트워크 탭은 웹 브라우저와 서버 간의 네트워크 요청과 응답을 실시간으로 모니터링할 수 있는 탭이다. 웹 페이지가 로드되거나 사용자 상호작용으로 발생하는 모든 네트워크 활동(예: HTTP 요청, 파일 로드, 데이터 전송 등)을 상세히 확인할 수 있다. 
네트워크 탭은 개발자가 페이지 성능, 네트워크 오류, API 디버깅 등 다양한 문제를 해결할 때 사용할 수 있어 웹 개발자에겐 매우매우 필수이다!!</p>
</blockquote>
<br />

<h2 id="✅-network-탭-살펴보기">✅ Network 탭 살펴보기</h2>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/48696df6-c8f2-4dbb-81e6-1073c7c60df9/image.png" alt=""></p>
<p>위는 우리가 잘 아는 크롬 개발자도구에 네트워크 탭이다. 네트워크 탭에서 각 영역과 기능에 대해 살펴보자.</p>
<p><strong>1. Filter (필터)</strong></p>
<ul>
<li><p>필터는 Fetch/XHR, Doc, CSS, JS등 네트워크 요청을 유형별로 필터링한다. 특정 파일 형식이나 특정 요청 유형만 보고 싶다면 필터링을 해서 볼 수 있다. </p>
<blockquote>
<p>Fetch/XHR: API 요청(예: fetch, axios 등).
Doc: HTML 문서.
CSS: 스타일시트.
JS: JavaScript 파일.
Img: 이미지 파일.
Font: 폰트 파일.
Media: 비디오나 오디오 파일.
Manifest: 웹 애플리케이션 매니페스트 파일.
WS (WebSocket): WebSocket 연결.
Other: 기타 유형 요청.</p>
</blockquote>
</li>
<li><p>또한 특정 HTTP 메소드만 필터링해서 보고 싶다면 필터링 텍스트 박스에 입력해서 볼 수 있다.
ex) HTTP Method 가 GET 인 인터페이스만 필터링 하고 싶다면
<img src="https://velog.velcdn.com/images/minyoung_/post/111f3bcf-1da5-4de4-8eee-3563e3c7fbd2/image.png" alt=""></p>
</li>
</ul>
<p><strong>2. Preserve log (로그 유지)</strong></p>
<ul>
<li>기본적으로 새로고침을 하거나 페이지를 이동할 경우 네트워크 로그가 초기화된다. 이 옵션을 활성화하면 새로고침 및 페이지 이동 후에도 네트워크 요청 기록이 유지된다.</li>
</ul>
<p><strong>3. Disable cache (캐시 비활성화)</strong></p>
<ul>
<li>브라우저 캐시를 비활성화한다. 페이지를 새로고침할 때 항상 서버에서 최신 리소스를 로드하도록 한다.</li>
</ul>
<p><strong>4. Throttling (네트워크 속도 제한)</strong></p>
<ul>
<li>네트워크 속도를 시뮬레이션한다. 예를 들어, 느린 3G 환경에서의 성능 테스트를 위해 속도를 제한할 수 있다.</li>
</ul>
<p><strong>5. Request Overview</strong></p>
<ul>
<li><p>시간 축(타임라인)을 기준으로 네트워크 요청을 시각적으로 표시한다.</p>
</li>
<li><p>색상 막대는 각 요청의 진행 상태를 나타낸다.</p>
<blockquote>
<p>파란색: 네트워크 요청이 시작되어 데이터를 다운로드하는 시간.
노란색: 브라우저가 응답을 처리하는 시간.
녹색(없음): HTTP 응답을 기다리는 시간.</p>
</blockquote>
</li>
<li><p>각 요청에 대한 소요 시간을 확인할 수 있다.</p>
</li>
</ul>
<p><strong>6. Big request rows</strong></p>
<ul>
<li>요청 행의 높이를 키워 요청 세부 정보를 더 쉽게 볼 수 있게 한다.</li>
</ul>
<p><strong>7. Group by frame (프레임별 그룹화)</strong></p>
<ul>
<li>여러 프레임(예: iframe 포함)이 있는 페이지에서 요청을 프레임별로 그룹화한다.</li>
</ul>
<p><strong>8. Screenshots (스크린샷)</strong></p>
<ul>
<li>네트워크 요청 타임라인에 따른 페이지 스크린샷을 표시한다. 성능 디버깅에 유용하다.</li>
</ul>
<br />
<br />

<h2 id="✅-network-탭-컬럼들-살펴보기">✅ Network 탭 컬럼들 살펴보기</h2>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/f262cabf-3edb-43fe-87ab-61288307421b/image.png" alt="">
아마 웹 개발자분들이라면 대부분 알거라고 생각하지만 간단하게만 살펴보자.</p>
<p><strong>1. Name (이름)</strong></p>
<ul>
<li>요청된 리소스의 이름 또는 URL의 마지막 부분을 표시한다.</li>
<li>이름을 클릭하면 요청에 대한 자세한 정보를 확인할 수 있다.</li>
</ul>
<p><strong>2. Status (상태 코드)</strong></p>
<ul>
<li>HTTP 상태 코드를 보여 준다. </li>
</ul>
<p><strong>3. Type (유형)</strong></p>
<ul>
<li>요청된 리소스의 유형을 표시한다. 브라우저가 MIME 타입을 기반으로 리소스를 분류한다.<blockquote>
<p>document: HTML 문서.
script: JavaScript 파일.
jpeg, png: 이미지 파일.
fetch: API 요청(데이터 호출).</p>
</blockquote>
</li>
</ul>
<p><strong>4. Initiator (요청자)</strong></p>
<ul>
<li>리소스를 로드하게 만든 소스 또는 파일을 나타낸다.</li>
<li>디버깅 시 어떤 코드가 요청을 발생시켰는지 파악하는 데 유용하다. </li>
</ul>
<p><strong>5. Size (크기)</strong></p>
<ul>
<li>다운로드된 데이터의 크기(바이트 단위)를 나타낸다.</li>
</ul>
<p><strong>6. Time (시간)</strong></p>
<ul>
<li>각 요청에 걸린 총 시간을 표시한다(밀리초 단위)</li>
</ul>
<br />
<br />

<h2 id="✅-network-요청-상세보기">✅ Network 요청 상세보기</h2>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/803daf2b-75b4-49e0-914a-809e3c6fd68b/image.png" alt=""></p>
<p>** Headers (헤더)**</p>
<ul>
<li>요청(Request)과 응답(Response)에 대한 헤더 정보를 확인할 수 있다.</li>
<li>General: 요청 URL, HTTP 메서드(GET, POST 등), 상태 코드(200, 404 등), 원격 주소 등 기본 정보를 표시한다.</li>
<li>Response Headers (응답 헤더): 서버가 클라이언트에 반환한 응답 헤더를 보여준다.</li>
<li>Request Headers (요청 헤더): 클라이언트가 서버로 전송한 요청 헤더를 표시한다.<br />


</li>
</ul>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/87ac6230-07d9-4d64-b28f-8e81016ddcba/image.png" alt="">
<strong>Preview (미리보기)</strong></p>
<ul>
<li>서버에서 반환된 응답 데이터를 보기 쉽게 구조화해 보여준다.</li>
<li>JSON, HTML, XML 등의 데이터가 계층적 구조로 표시된다.</li>
<li>주로 API 응답 데이터를 빠르게 확인할 때 사용한다.<br />

</li>
</ul>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/3dc92884-bef7-48a6-8250-50bc9f731308/image.png" alt="">
<strong>Response (응답)</strong></p>
<ul>
<li>서버가 반환한 응답 데이터를 원시(raw) 형태로 보여준다.</li>
<li>Preview와 달리 데이터를 가공하지 않고 그대로 출력한다.</li>
<li>데이터의 원본을 확인하거나 디버깅할 때 유용하다.<br />

</li>
</ul>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/aa823f9f-4aae-4b0e-ad50-edf6aaa934f4/image.png" alt="">
<strong>Initiator (요청자)</strong></p>
<ul>
<li>해당 요청이 어떤 코드나 파일로 인해 발생했는지를 보여준다. </li>
<li>어떤 부분에서 요청이 발생했는지 추적하거나 불필요한 요청을 제거할 때 유용하다.<br />

</li>
</ul>
<p><img src="https://velog.velcdn.com/images/minyoung_/post/1162c38f-ca8e-4da1-b5f7-ee44907ba48b/image.png" alt="">
** Timing (타이밍)**</p>
<ul>
<li>요청과 응답에 걸린 시간 분포를 상세히 보여준다.</li>
<li>주요 항목:<blockquote>
<p>Queueing: 요청이 대기열에 머문 시간.
DNS Lookup: DNS 조회 시간.
Initial Connection: 서버와의 연결 설정 시간.
Request Sent: 요청이 서버로 전송된 시간.
Waiting (TTFB): 서버 응답을 기다린 시간(Time to First Byte).
Content Download: 응답 데이터를 다운로드한 시간.</p>
</blockquote>
</li>
<li>요청의 병목 구간을 확인하고 성능 최적화에 도움을 준다.<br />
<br />

</li>
</ul>
<p>혹시라도 잘못된 내용이 있거나 질문사항 있으시면 댓글 남겨주세요! 어떤 댓글이든 달게 받겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[인프런 자바스크립트 알고리즘] 마구간 정하기(결정알고리즘)]]></title>
            <link>https://velog.io/@minyoung_/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%A7%88%EA%B5%AC%EA%B0%84-%EC%A0%95%ED%95%98%EA%B8%B0%EA%B2%B0%EC%A0%95%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@minyoung_/%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%A7%88%EA%B5%AC%EA%B0%84-%EC%A0%95%ED%95%98%EA%B8%B0%EA%B2%B0%EC%A0%95%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Thu, 26 Dec 2024 06:31:50 GMT</pubDate>
            <description><![CDATA[<p>요새 알고리즘 공부를 다시 하고 있는데 그 중에 결정알고리즘은 좀 헤매면서 풀어서 복기할 겸 포스팅을 하게 되었다. </p>
<p>결정알고리즘은 주어진 범위내에서 원하는 값 또는 원하는 조건에 가장 일치하는 값을 찾아내는 알고리즘이다. 결정알고리즘은 이진탐색을 활용해서 풀 수 있다.</p>
<p>먼저 이진탐색에 대해 알아보자.</p>
<h3 id="이진탐색binary-search">이진탐색(Binary Search)</h3>
<ul>
<li><p>이진탐색이란 정렬된 데이터에서 특정 값을 빠르게 찾이 위해 사용하는 탐색 알고리즘이다. </p>
</li>
<li><p>이진 탐색은 데이터를 반복적으로 반으로 나누어서 검색 범위를 줄이는 방식으로 동작한다.</p>
</li>
<li><p>시간 복잡도가 𝑂(log𝑛) 이다.</p>
<br />

</li>
</ul>
<p><strong>예제 문제</strong></p>
<blockquote>
<p>임의의 N개의 숫자가 입력으로 주어집니다.  N개의 수를 오름차순으로 정렬한 다음 N개의 수 
중 한 개의 수인 M이 주어지면 이분검색으로 M이 정렬된 상태에서 몇 번째에 있는지 구하는 
프로그램을 작성하세요. 단 중복값은 존재하지 않습니다</p>
</blockquote>
<br />

<p><strong>예제 코드</strong></p>
<pre><code class="language-javascript">function solution(target, arr) {
  arr.sort((a, b) =&gt; a - b);// 오름차순으로 정렬

  let lt = 0; // 시작 포인트
  let rt = arr.length - 1; // 끝 포인트

  while (lt &lt;= rt) {
    let mid = Math.floor(lt + rt / 2); // 중간 숫자
    if (arr[mid] === target) {
      return mid + 1;
    } else if (arr[mid] &gt; target) {
      rt = mid - 1; // 더 작은 숫자 탐색
    } else {
      lt = mid + 1; // 더 큰 숫자 탐색
    }
  }

  return -1;// 탐색 실패 시 -1 반환
}

console.log(solution(32, [23, 87, 65, 12, 57, 32, 99, 81]));
</code></pre>
<br />

<p><strong>동작원리</strong></p>
<ol>
<li>초기설정</li>
</ol>
<ul>
<li>데이터는 반드시 오름차순 혹은 내림차순으로 정렬되어 있어야 한다.</li>
<li>탐색 범위의 시작점과 끝점을 설정한다.</li>
</ul>
<ol start="2">
<li>중간값 확인</li>
</ol>
<ul>
<li>현재 탐색 범위의 중간 인덱스를 계산한다.<blockquote>
<p>  let mid = Math.floor(lt + rt / 2);</p>
</blockquote>
</li>
</ul>
<ol start="3">
<li>조건 확인</li>
</ol>
<ul>
<li>목표 값이 중간 값과 같은 탐색 성공</li>
<li>목표 값이 중간 값보다 작으면 탐색 범위를 중간보다 왼쪽으로 좁힘</li>
<li>목표 값이 중간 값보다 크다면 탐색 범위를 중간보다 오른쪽으로 좁힘</li>
</ul>
<ol start="4">
<li>반복</li>
</ol>
<ul>
<li>탐색 범위를 줄여가며 계속 반복한다.</li>
<li>시작포인트 &gt; 끝 포인트가 되면 탐색 실패</li>
</ul>
<br />
<br />
그럼 이제 결정 알고리즘을 풀어보자.
<br />
<br />

<h3 id="마구간-정하기결정알고리즘-문제">마구간 정하기(결정알고리즘) 문제</h3>
<blockquote>
<p>N개의 마구간이 수직선상에 있습니다. 각 마구간은 x1, x2, x3, ......, xN의 좌표를 가지며, 마구간간에 좌표가 중복되는 일은 없습니다. 현수는 C마리의 말을 가지고 있는데, 이 말들은 서로 가까이 있는 것을 좋아하지 않습니다.
각 마구간에는 한 마리의 말만 넣을 수 있고, 가장 가까운 두 말의 거리가 최대가 되게 말을 마구간에 배치하고 싶습니다. C마리의 말을 N개의 마구간에 배치했을 때 가장 가까운 두 말의 거리가 최대가 되는 그 최대값을 출력하는 프로그램을 작성하세요.</p>
</blockquote>
<h3 id="문제풀이">문제풀이</h3>
<pre><code class="language-javascript">
function count(mid, coordinate) {
  let cnt = 1; // 첫 번째 말을 배치함
  let endPosition = coordinate[0]; // 첫 번째 마구간 좌표

  for (let i = 1; i &lt; coordinate.length; i++) {
    if (coordinate[i] - endPosition &gt;= mid) {
      cnt++;
      endPosition = coordinate[i];
    }
  }
  return cnt;
}

function solution(c, coordinate) {
  let answer = 0;
  coordinate.sort((a, b) =&gt; a - b); // 좌표를 정렬

  //범위 설정
  let lt = 1; // 가능한 최소 거리
  let rt = coordinate[coordinate.length - 1] - coordinate[0]; // 가능한 최대 거리
  while (lt &lt;= rt) {
    let mid = Math.floor((lt + rt) / 2); // 중간 거리
    if (count(mid, coordinate) &gt;= c) {
      // C마리 이상 배치 가능
      answer = mid; // 거리 후보를 업데이트
      lt = mid + 1; // 더 큰 거리 탐색
    } else {
      rt = mid - 1; // 더 작은 거리 탐색
    }
  }
  return answer;
}

console.log(solution(3, [1, 2, 8, 4, 9])); // 출력: 3</code></pre>
<p>해당 문제에서 요구하는 것은 <strong>c마리의 말을 배치할 대 가장 가까운 두 말의 최대값</strong>이다.</p>
<p>이를 결정 알고리즘으로 바꾸면 </p>
<blockquote>
<p>가장 가까운 두 말의 거리가 mid일 때, 말을 C마리 이상 배치할 수 있는가?</p>
</blockquote>
<p>이 질문에따라 조건을 좁혀 나가야 한다.</p>
<p>동작원리는 위에 이진탐색 알고리즘과 동일하고 말을 배치하는 로직만 추가로 구현하면 된다.</p>
<br />
<br />



]]></description>
        </item>
        <item>
            <title><![CDATA[2024년 회고록]]></title>
            <link>https://velog.io/@minyoung_/2024%EB%85%84-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@minyoung_/2024%EB%85%84-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Mon, 16 Dec 2024 01:44:40 GMT</pubDate>
            <description><![CDATA[<p>어느덧 2024년 한 해가 지나간다.
지금까지 회고록을 쓴 적이 없지만 올해는 많은 일이 있었기도 하고, 한 번쯤은 개발자로서 회고록을 작성해 보고 싶어 이렇게 작성하게 됐다.</p>
<p>나는 개발 공부를 2022년 초에 본격적으로 시작했는데 이제는 만 3년이 되어간다.
2022년에는 개발자를 목표로 해서 무작정 개발 공부만 하다가 좋은 기회가 생겨 프리랜서 개발자로 잠깐 실무 경험도 해봤다. 실무를 경험해 보니 혼자서 개발 공부를 했던 나는 협업 경험과 실무적으로 여러 부족함을 깨닫고 2023년 초 엘리스 코딩이라는 부트 캠프를 들어가 협업 경험을 많이 쌓게 되었다.</p>
<p>부트 캠프 종료 후 2023년 중순쯤 개발자 취업시장에 도전했다.
나는 비전공자 출신이기에 수많은 전공자들 사이에서 어떻게 살아남을지에 대한 걱정과 채용 시장이 많이 안 좋았던 상황에 미래에 대한 두려움과 막막함이 컸었다.
4개월간 여러 시행착오 끝에 2023년 11월 27일 결국 개발자로 취업하게 되었다.</p>
<p>지금 글을 쓰는 시점을 기준으로 나는 이제 만 1년이 넘는 개발자가 되었다. 그리고 1년 넘게 현재 회사에서 개발자로 근무하면서 느꼈던 점들을 기록해 보고자 한다.</p>
<h3 id="참여한-프로젝트">참여한 프로젝트</h3>
<ol>
<li>전국 병원 결핵 지표 프로젝트 (B2B) (2023.12 - 2024.2)</li>
<li>안전 관리 자동화 프로젝트 (SAAS) (2024.02 - 2024.06)</li>
<li>기존 솔루션 사이트 -&gt; MSA구조 + Vue3 마이그레이션 2차 작업 (2024.06 - 2024.12)</li>
<li>보고서 자동화 프로젝트 (SAAS) (2024.08 - 2024.12)</li>
<li>CSAP 인증서 갱신 (2024.08)</li>
</ol>
<p>첫번째 프로젝트에서는 기존 웹사이트가 PHP로 작업이 되어있어서 PHP도 써보고, 파트너사 요청사항으로 인해 프레임워크 없이 자바스크립트만으로 SPA 렌더링도 직접 구현해보았다. SPA의 원리를 깊게 공부할 수 있는 시간이어서 개인적으로는 제일 기억에 남는 프로젝트인 거 같다. </p>
<p>두번째 프로젝트에서는 개발을 좀 하실 수 있는 퍼블리셔 분이랑 같이 작업을 하게 되었는데 어쩌다보니 해당 프로젝트를 내가 이끌게 되었다. 아직 많이 부족한 나지만.. 해당 프로젝트는 기술 스택이 리액트였고, 그나마 자신있는 기술 스택이라서 자신감을 가지고 열심히 이끌어 나갔다. 
해당 프로젝트의 프론트엔드 개발은 90% 정도는 맡았던지라 시간이 많이 부족했다. 그래서 결국 두달내내 매일 세시간 이상씩 야근을 했었다..야근 때문에 많이 힘들었지만 해당 프로젝트에 애착이 생기게 됐고 완성하니 너무 뿌듯했었다. </p>
<p>8월에는 회사에 개발자 두 분이 퇴사를 하셨다. 백엔드 개발자는 4명에서 3명이 되었고 프론트엔드 개발자는 3명에서 2명이 되었다.
그러다가 8월 말 작년에 받은 CSAP 인증을 올 해 다시 갱신을 해야해서 일주일은 CSAP 인증에 몰두했다.</p>
<p>개발자 두 분 퇴사로 인해 회사에 개발자 인원이 많이 부족했던 상황이라 SAAS 프로젝트 마지막 두개는 동시에 진행을 했다. 
세번째 프로젝트는 기존에 Vue로 마이그레이션 1차 작업이 되어있었기에 나도 Vue를 사용해서 마이그레이션을 이어 나갔다. 처음에는 Vue 문법이 적응이 안됐지만 쓰다보니 리액트랑 비슷한 부분도 많아서 생각보다는 금방 적응한 거 같다.<br>12월 중순까지 체험판이 나와야 했던 상황이라 마지막 2달 동안은 야근 + 주말 출근까지 병행했다..잦은 야근과 주말 출근으로 인해 몸이 많이 힘들었지만 조금씩 완성되어가는 걸 보면 뿌듯하고 보람찼다.</p>
<h3 id="올해-개인적으로-공부한-것들">올해 개인적으로 공부한 것들</h3>
<ul>
<li>ReactNative</li>
<li>알고리즘 (탐색 알고리즘, 재귀함수 위주)</li>
<li>자료구조 (트리)</li>
<li>백엔드 공부 (Node.js, express, MySQL)</li>
<li>인프라 공부 (AWS, Nginx)</li>
</ul>
<p>ReactNative는 강의 결제 후 1/3 정도 공부하고, 이후 회사 프로젝트가 많이 바빠져서 공부를 못하게 되었다.
핑계아닌 핑계지만 내년에는 제대로 공부해보고 개인 프로젝트도 만들 생각이다.  </p>
<p>백엔드, 인프라, 알고리즘의 경우 개인 프로젝트를 하면서 공부하게 되었다.
내가 알고리즘 중 탐색 알고리즘(BFS, DFS 등), 재귀함수 부분이 많이 약해서 공부할 겸 트리구조를 활용할 수 있는 마인드맵 프로젝트를 만들게 되었다. 마인드맵 자체가 트리 구조이기에 트리구조를 구현 및 렌더링 하면서 재귀 및 탐색 알고리즘을 쓰게 된다.
처음에는 db 저장없이 클라이언트 부분만 간단하게 구현할 생각이었으나 하다보니 욕심이 생겨 db에 데이터를 저장하고 싶었고, 그러다 보니 자연스럽게 여러 기능에도 욕심이 생겨서 전체적인 웹 서비스의 프로젝트가 되어버렸다.
한 번쯤은 프론트 + 백 + 인프라까지 전체적인 플로우를 혼자서 다 해보고 싶었다.그래서 백엔드와 인프라도 공부하면서 혼자서 클라이언트 구현, API 구현, DB 설계, 배포 까지 다 해보았다. 
백엔드와 인프라는 시간상 깊게 공부하지는 못했지만 그래도 개인 프로젝트 정도는 누군가의 도움 없이는 할 수 있을 정도의 실력은 된 거 같다.   </p>
<p>아직은 버그도 많고 해서 누군가한테 보여줄 정도는 아닌거 같고, 추가 기능 구현해야할 것도 너무 많다..</p>
<br/>

<h3 id="한-해가-지나가면서">한 해가 지나가면서...</h3>
<p>2023년에는 취업 문제로 인해 굉장히 힘든 시기였다. 비전공자이기에 어떻게해야 전공자들보다 눈에 띌 수 있을까 고민이 많았다. 그래도 어찌저찌 해서 결국 개발자로 취업했고 2024년에는 정말 쉴틈없이 달린 거 같다.   </p>
<p>전체적으로 생각해보면 현재 회사에는 선임이 없고 프론트엔드팀 전원 신입이라 누군가 이끌어 줄 수 없고 코드 리팩토링도 자체적으로 할 수 밖에 없는 상황이다. 그렇지만 팀원들끼리 서로 안되는 부분을 도와주기도 하고 같이 해결해 나가기도 하면서 서로 의지하게 되고 사이가 돈독해진 거 같다. 소중한 동료가 생기게 되어 2024년에 제일 큰 수확인 거 같다. 
그리고 매 프로젝트마다 설계 + 환경세팅부터 하게 되어 개발 실력적으로는 많이 늘게 된 거 같다.</p>
<p>또 야근을 하는 와중에 개인 프로젝트도 했었는데 하루에 5시간반씩 자면서 했다. 지금은 플로우는 돌아갈 정도는 됐기에 6시간 정도는 잔다. 많이 힘들었지만 개인 프로젝트를 통해 배운게 굉장히 많았고 많이 성장한 거 같아서 뿌듯하다.</p>
<p>올해 남은 기간동안 개인 프로젝트 마무리를 하고 이제는 또 다른 공부를 할 생각이다. 내년에는 npm에 패키지(모듈)도 등록 해보고 싶고 ReactNative도 마저 공부하고 Next도 다시 공부해보고 싶다. 알고리즘도 계속 공부해야지...</p>
<p>정말 개발 공부는 해도 해도 끝이 없는 거 같다. 가끔은 끝이 없는 개발 공부를 생각하면 언제 다 할지 막막하고 두렵지만, 그래도 조금씩이라도 성장하고 있는 나 자신을 보면 뿌듯하고 보람차다. 지금은 여러모로 많이 부족한 개발자지만 열심히 하다보면 좋은 결과가 있지 않을까 한다.</p>
<p>올 한 해 잘 마무리하고 내년에도 열심히 하자!
2024년 회고록 종료...!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CentOS] swap 메모리 늘리기]]></title>
            <link>https://velog.io/@minyoung_/CentOS-swap-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%8A%98%EB%A6%AC%EA%B8%B0</link>
            <guid>https://velog.io/@minyoung_/CentOS-swap-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%8A%98%EB%A6%AC%EA%B8%B0</guid>
            <pubDate>Tue, 10 Dec 2024 15:34:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>개인 프로젝트를 하면서 AWS 1년 무료 서버를 사용중인데 ec2 인스턴스 서버당 메모리 1GB, 총 디스크 사용량 30GB 밖에 안된다.
용량이 적어서 클라이언트 코드를 인스턴스 서버에 빌드할 때마다 cpu 사용률이 100%에 달하게 되고, 빌드가 중단되는 사태가 자주 발생하였다.</p>
</blockquote>
<p>그래서 이를 해결하려고 여러가지 방법을 모색하던 중 가장 간단하게 할 수 있는 방법이 인스턴스 서버 내 swap 메모리를 늘리기였다.</p>
<h3 id="🙄swap-메모리란">🙄swap 메모리란?</h3>
<blockquote>
<p>스왑 메모리는 <strong>시스템의 물리적 메모리(RAM)가 부족할 때, 디스크 공간을 임시로 사용하는 메커니즘</strong>이다. 
운영체제는 스왑 메모리를 사용하여 RAM의 일부 데이터를 디스크로 이동시켜 메모리 부담을 줄이고, 추가 작업을 처리할 수 있도록 한다.</p>
</blockquote>
<p>참고로 스왑 메모리는 물리적 메모리(RAM)가 부족한 상황에서 유용하지만, 디스크 성능이 낮은 경우 과도한 스왑 사용은 시스템 속도를 저하시킬 수 있으므로 적절히 관리해서 사용해야 한다.</p>
<h2 id="✅swap-메모리-설정하기">✅swap 메모리 설정하기</h2>
<p><strong>1. 스왑 파일 생성</strong></p>
<p>디스크에 스왑 파일을 만든다. 예를 들어, 2GB 크기의 스왑 파일을 생성한다고 가정하자.</p>
<pre><code class="language-javascript">sudo fallocate -l 2G /swapfile</code></pre>
<ul>
<li>fallocate: 디스크에 지정한 크기의 파일을 만든디.</li>
<li>-l 2G: 파일 크기를 2GB로 지정한다.</li>
<li>/swapfile: 스왑 파일의 이름 및 경로.</li>
</ul>
<p><strong>2. 스왑 파일 권한 설정</strong></p>
<p>스왑 파일은 보안상의 이유로 루트만 접근할 수 있어야 한다.</p>
<pre><code class="language-javascript">sudo chmod 600 /swapfile</code></pre>
<p><strong>3. 스왑 영역 초기화</strong></p>
<p>스왑 파일을 스왑 영역으로 초기화한다.</p>
<pre><code class="language-javascript">sudo mkswap /swapfile</code></pre>
<ul>
<li>mkswap: 지정된 파일을 스왑 영역으로 설정한다.</li>
</ul>
<p><strong>4. 스왑 활성화</strong></p>
<p>생성한 스왑 파일을 활성화한다.</p>
<pre><code class="language-javascript">sudo swapon /swapfile</code></pre>
<ul>
<li>swapon: 스왑 파일 또는 스왑 파티션을 활성화.</li>
</ul>
<p><strong>5. 활성화된 스왑 상태 확인</strong></p>
<pre><code class="language-javascript">free -h
//또는 
swapon --show
</code></pre>
<p><strong>6. 재부팅 시 스왑 유지</strong>
시스템 재부팅 후에도 스왑 파일을 유지하려면 /etc/fstab에 추가해야한다.</p>
<ul>
<li>/etc/fstab 파일을 편집<pre><code class="language-javascript">sudo nano /etc/fstab</code></pre>
</li>
<li>코드 추가<pre><code class="language-javascript">/swapfile none swap sw 0 0</code></pre>
</li>
<li>저장 후 종료</li>
</ul>
<br />
<br />
<br />

<p><strong>추가로 스왑 메모리를 비활성화 하려면</strong></p>
<ul>
<li><p>스왑 메모리 비활성화</p>
<pre><code class="language-javascript">sudo swapoff /swapfile</code></pre>
</li>
<li><p>스왑 파일 삭제</p>
<pre><code class="language-javascript">sudo rm /swapfile</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Node.js에서 CORS 허용 설정]]></title>
            <link>https://velog.io/@minyoung_/Node.js%EC%97%90%EC%84%9C-CORS-%ED%97%88%EC%9A%A9-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@minyoung_/Node.js%EC%97%90%EC%84%9C-CORS-%ED%97%88%EC%9A%A9-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Wed, 06 Nov 2024 06:19:49 GMT</pubDate>
            <description><![CDATA[<h3 id="🙄cors란">🙄CORS란?</h3>
<blockquote>
<p>CORS(Cross-Origin Resource Sharing)는 브라우저의 보안 정책 중 하나로, 서로 다른 출처(도메인, 프로토콜, 또는 포트)의 리소스에 접근할 때 발생하는 문제를 해결하기 위해 사용된다.</p>
</blockquote>
<p> 예를 들어, 클라이언트 애플리케이션이 <a href="http://localhost:3000%EC%97%90%EC%84%9C">http://localhost:3000에서</a> 실행되고, 서버 API가 <a href="http://localhost:8080%EC%97%90%EC%84%9C">http://localhost:8080에서</a> 동작할 때, 브라우저는 다른 출처로 간주하여 기본적으로 서버 API에 접근을 차단한다.</p>
<p> CORS 설정을 통해 특정 출처에서 서버에 접근할 수 있도록 허용해주면, 클라이언트가 서버 리소스에 접근할 수 있게된다. 
 <BR>
 <BR></p>
<h2 id="✅nodejs에서-cors-허용-설정">✅Node.js에서 CORS 허용 설정</h2>
<p> Node.js에서  CORS를 허용하기 위해 CORS 미들웨어를 설치하고 설정할 수 있다. Express와 함께 사용하는 경우가 많으므로, Express 환경에서의 설정 방법을 예를 들어보겠다. 
   <br></p>
<ol>
<li><p>아래 명령어로 CORS 미들웨어를 설치한다</p>
<pre><code>npm install cors</code></pre><p>추가로 타입스크립트로 사용할 경우 cors의 타입 정보를 알기 위해 아래 명령어로 @types/cors 패키지를 추가로 설치해주자</p>
<pre><code>npm install @types/cors</code></pre></li>
<li><p>모든 request에 대해 CORS 요청을 설정하기</p>
<pre><code class="language-javascript">import express, { Request, Response } from &quot;express&quot;;
import cors from &quot;cors&quot;;

const app = express();

app.use(cors()); // 미들웨어로 CORS 활성화

app.get(&quot;/&quot;, (req: Request, res: Response): void =&gt; {
  res.send(&quot;hello world&quot;);
});

app.listen(8080, () =&gt; console.log(&quot;8080번 포트 &quot;));
</code></pre>
<ol start="3">
<li>선택적인 출처에 대한 CORS 허용하기<pre><code class="language-javascript">import express, { Request, Response } from &quot;express&quot;;
import cors from &quot;cors&quot;;
</code></pre>
</li>
</ol>
<p>const app = express();</p>
<p>//선택적인 출처에 대해서만 CORS 활성화
const corsOptions = {
   origin: &#39;<a href="http://localhost:3000&#39;">http://localhost:3000&#39;</a>
}</p>
<p>app.use(cors(corsOptions));</p>
<p>app.get(&quot;/&quot;, (req: Request, res: Response): void =&gt; {
res.send(&quot;hello world&quot;);
});</p>
<p>app.listen(8080, () =&gt; console.log(&quot;8080번 포트 &quot;));</p>
<pre><code>4. CORS 옵션 커스터마이징 하기

```javascript
 const corsOptions = {
    origin: &#39;http://localhost:3000&#39;, // 특정 출처
    methods: [&#39;GET&#39;, &#39;POST&#39;],        // 허용할 HTTP 메소드
    allowedHeaders: [&#39;Content-Type&#39;, &#39;Authorization&#39;], // 허용할 헤더
    credentials: true                // 자격 증명 허용 (쿠키 전송 허용)
};

  app.use(cors(corsOptions));</code></pre></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Express 초기 세팅 정리(feat.Typescript)]]></title>
            <link>https://velog.io/@minyoung_/Express-%EC%B4%88%EA%B8%B0-%EC%84%B8%ED%8C%85-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@minyoung_/Express-%EC%B4%88%EA%B8%B0-%EC%84%B8%ED%8C%85-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 05 Nov 2024 05:27:38 GMT</pubDate>
            <description><![CDATA[<h1 id="1-express-설치-및-설정">1. Express 설치 및 설정</h1>
<h3 id="🙄express란">🙄Express란?</h3>
<blockquote>
<p>Node.js는 기본적으로 서버 기능을 제공하지만, 애플리케이션을 개발할 때 개발자가 많은 코드를 직접 작성해야 하고 라우팅과 미들웨어 같은 기능을 직접 구현해야 한다. Express는 이러한 기능을 기본적으로 제공하여 개발 과정을 단순화하고 생산성을 높여주는 프레임워크이다. </p>
</blockquote>
<br>

<h3 id="✅express-설치">✅Express 설치</h3>
<ol>
<li><p>프로젝트를 진행할 폴더를 만든다. </p>
</li>
<li><p>해당 폴더 경로로 진입하여 아래 명령어로 Express를 설치한다. </p>
<pre><code class="language-javascript">npm install express</code></pre>
</li>
<li><p>배포용으로 쓰일 패키지임을 명시하는package.json 파일 내 dependency에 자동으로  express  항목에 버전이 명시되는지 확인한다.
<img src="https://velog.velcdn.com/images/minyoung_/post/83e44acc-c2bd-45cc-9cec-c475809a1dd5/image.png" alt=""></p>
<br>
<br>



</li>
</ol>
<h1 id="2typescript-관련-패키지-설치-및-설정">2.Typescript 관련 패키지 설치 및 설정</h1>
<h3 id="✅typescript-관련-패키지-설치">✅Typescript 관련 패키지 설치</h3>
<ol>
<li>TypeScript 관련 패키지를 아래 명령어로 추가로 설치한다.<pre><code class="language-javascript">npm install typescript @types/node @types/express ts-node</code></pre>
<blockquote>
<ul>
<li>typescript: TypeScript 컴파일러</li>
</ul>
</blockquote>
</li>
</ol>
<ul>
<li>@types/node, @types/express: Node.js와 Express의 TypeScript 타입 정의 파일</li>
<li>ts-node: TypeScript 파일을 직접 실행할 수 있게 해주는 모듈</li>
</ul>
<ol start="2">
<li>아래 명령어로 타입스크립트 설정 파일 (tsconfig.json )을 생성해준다.<pre><code>npx tsc --init</code></pre><br>
<br>


</li>
</ol>
<h1 id="3-nodemon-설치">3. nodemon 설치</h1>
<h3 id="🙄nodemon이란">🙄nodemon이란?</h3>
<blockquote>
<p>기본적으로 Node.js는 앱을 개발하는 동안 코드를 수정할 때마다 직접 서버를 종료하고 다시 실행해야 하는 번거로움이 있는데, nodemon은 코드에 변경이 생기면 애플리케이션을 자동으로 다시 시작해주는 기능을 제공하여 번거로움을 덜어준다.</p>
</blockquote>
<br>

<h3 id="✅nodemon-설치">✅nodemon 설치</h3>
<ol>
<li><p>아래 명령어로 nodemon을 설치한다</p>
<pre><code class="language-javascript">npm install nodemon</code></pre>
</li>
<li><p>웹 서버 파일을 만든다</p>
</li>
</ol>
<p>app.ts</p>
<pre><code class="language-javascript">import express, { Request, Response } from &quot;express&quot;;

const app = express();

app.get(&quot;/&quot;, (req: Request, res: Response): void =&gt; {
  res.send(&quot;hello world&quot;);
});

app.listen(8080, () =&gt; console.log(&quot;8080번 포트 &quot;));
</code></pre>
<ol start="3">
<li>package.json에서 실행 명령어를 설정한다
<img src="https://velog.velcdn.com/images/minyoung_/post/1517ee58-2c0f-4072-ad14-eaf5896f3e1b/image.png" alt=""></li>
</ol>
<ol start="4">
<li>터미널에 npm run dev 명령어로 서버 실행 후  <a href="http://localhost:8080">http://localhost:8080</a> 으로 접속해보자.
<img src="https://velog.velcdn.com/images/minyoung_/post/9b353148-59f8-4ac9-90b3-d625dd45ddf8/image.png" alt=""></li>
</ol>
<br>
<br>



]]></description>
        </item>
    </channel>
</rss>