<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>상추의 공간</title>
    <link>https://sanghee01.tistory.com/</link>
    <description>상추의 자아 성찰, 기록 끄적끄적 공간</description>
    <language>ko</language>
    <pubDate>Tue, 7 Apr 2026 06:18:59 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>sangchu</managingEditor>
    <image>
      <title>상추의 공간</title>
      <url>https://tistory1.daumcdn.net/tistory/5144217/attach/95e18141d5b24d92ade4dbfafc56eb2a</url>
      <link>https://sanghee01.tistory.com</link>
    </image>
    <item>
      <title>이미지 최적화 파이프라인 구축기 (2) - S3 이벤트 트리거 기반 Lambda 구현</title>
      <link>https://sanghee01.tistory.com/202</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sanghee01.tistory.com/201&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서는 LCP 7.6초라는 문제를 진단하고, 네 가지 방법을 비교해 S3 이벤트 기반 Lambda 비동기 처리 방식을 선택하기까지의 과정을 다뤘다.&lt;/p&gt;
&lt;figure id=&quot;og_1774511309890&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;이미지 최적화 파이프라인 구축기 (1) - 문제 진단과 방법 선택&quot; data-og-description=&quot;들어가며이전에 정적 이미지 최적화 방식을 다룬 글을 작성한 적 있다. 이번에는 사용자가 업로드한 이미지를 최적화한 경험을 정리해보려 한다. 이번 글에서는 LCP 7.6초라는 수치를 발견하고, &quot; data-og-host=&quot;sanghee01.tistory.com&quot; data-og-source-url=&quot;https://sanghee01.tistory.com/201&quot; data-og-url=&quot;https://sanghee01.tistory.com/201&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d7fUGE/dJMb8Z3qZkq/vFLifd2pskEyvBZGKfmSk0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ctpXhd/dJMb8TB9i7Z/eQ4NcXBiiI8au6zJwSzTaK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ba7nqx/dJMb86O1uTN/NGJb8NsUDCkTxnjXyqsOL1/img.png?width=497&amp;amp;height=934&amp;amp;face=0_0_497_934&quot;&gt;&lt;a href=&quot;https://sanghee01.tistory.com/201&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sanghee01.tistory.com/201&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d7fUGE/dJMb8Z3qZkq/vFLifd2pskEyvBZGKfmSk0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ctpXhd/dJMb8TB9i7Z/eQ4NcXBiiI8au6zJwSzTaK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ba7nqx/dJMb86O1uTN/NGJb8NsUDCkTxnjXyqsOL1/img.png?width=497&amp;amp;height=934&amp;amp;face=0_0_497_934');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;이미지 최적화 파이프라인 구축기 (1) - 문제 진단과 방법 선택&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며이전에 정적 이미지 최적화 방식을 다룬 글을 작성한 적 있다. 이번에는 사용자가 업로드한 이미지를 최적화한 경험을 정리해보려 한다. 이번 글에서는 LCP 7.6초라는 수치를 발견하고,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sanghee01.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 실제로 어떻게 구현했는지를 다룬다. S3 이벤트 트리거 설정부터 Lambda 함수 구현, 원본과 최적화 버킷 분리 설계까지 순서대로 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전체 파이프라인 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 업로드부터 최적화 과정,사용자에게 이미지가 보이기까지의 전체 흐름을 먼저 간단히 짚고 넘어가자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 이미지를 업로드하면, 백엔드로부터 발급받은 *Presigned URL을 통해 S3 원본 버킷에 직접 저장된다.&lt;/li&gt;
&lt;li&gt;이 업로드 이벤트가 Lambda를 트리거하고, Lambda는 Sharp를 통해 이미지 최적화를 진행한 뒤 별도의 S3 최적화 버킷에 저장한다.&lt;/li&gt;
&lt;li&gt;클라이언트는 최적화 버킷의 이미지를 먼저 시도하고, 실패 시 원본 버킷의 이미지로, 그마저도 실패하면 기본 이미지로 순차적으로 fallback된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;*Presigned URL: 클라이언트가 백엔드 서버를 거치지 않고 S3에 직접 파일을 올릴 수 있도록 임시로 발급되는 인증 URL&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;1277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BuV7H/dJMcab4FFPZ/vtUFSQQkdRQOEk6XOMt3IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BuV7H/dJMcab4FFPZ/vtUFSQQkdRQOEk6XOMt3IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BuV7H/dJMcab4FFPZ/vtUFSQQkdRQOEk6XOMt3IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBuV7H%2FdJMcab4FFPZ%2FvtUFSQQkdRQOEk6XOMt3IK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;689&quot; height=&quot;624&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;1277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;S3 이벤트 트리거 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 업로드 이후 최적화가 자동으로 실행되도록, S3 원본 버킷의 업로드 이벤트를 트리거로 Lambda 함수가 실행되도록 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lambda를 생성한 뒤, 다음과 같이 트리거 구성 과정을 진행했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9rnN9/dJMcajuQzFn/5j8bgArNuYnz8lzgoz6zd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9rnN9/dJMcajuQzFn/5j8bgArNuYnz8lzgoz6zd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9rnN9/dJMcajuQzFn/5j8bgArNuYnz8lzgoz6zd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9rnN9%2FdJMcajuQzFn%2F5j8bgArNuYnz8lzgoz6zd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1714&quot; height=&quot;839&quot; data-origin-width=&quot;1714&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;(1), (2) Bucket&lt;/b&gt;: S3 원본 버킷 지정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;(3) Event type&lt;/b&gt;: &lt;code&gt;s3:ObjectCreated:\*&lt;/code&gt; &amp;mdash; PUT을 포함한 모든 객체 생성 이벤트에 트리거&lt;/li&gt;
&lt;li&gt;&lt;b&gt;(4) Prefix&lt;/b&gt;: &lt;code&gt;upload/&lt;/code&gt; &amp;mdash; 원본 이미지가 저장되는 경로만 이벤트가 발생하도록 범위 제한
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;*모멘트 서비스는 원본 버킷은 &lt;code&gt;upload/&lt;/code&gt;, 최적화본 저장 버킷은 &lt;code&gt;upload-resize/&lt;/code&gt;로 되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3266&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nZ3Nm/dJMcafeYQ1X/jLabmI3ZSVnmYvqGNUW4dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nZ3Nm/dJMcafeYQ1X/jLabmI3ZSVnmYvqGNUW4dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nZ3Nm/dJMcafeYQ1X/jLabmI3ZSVnmYvqGNUW4dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnZ3Nm%2FdJMcafeYQ1X%2FjLabmI3ZSVnmYvqGNUW4dK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3266&quot; height=&quot;824&quot; data-origin-width=&quot;3266&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(5) 입력과 출력 버킷을 분리하는걸 권장한다는 안내 문구이다. 인지했으면 체크하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번역: 입력과 출력에 동일한 S3 버킷을 사용하는 것은 권장되지 않으며, 이러한 구성으로 인해 재귀 호출, Lambda 사용량 증가 및 비용 증가가 발생할 수 있음을 인지하고 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDtWPR/dJMcaa5Mbz8/g7iOOwTkJwcSdSit5F8H7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDtWPR/dJMcaa5Mbz8/g7iOOwTkJwcSdSit5F8H7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDtWPR/dJMcaa5Mbz8/g7iOOwTkJwcSdSit5F8H7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDtWPR%2FdJMcaa5Mbz8%2Fg7iOOwTkJwcSdSit5F8H7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;663&quot; height=&quot;259&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같은 설정으로 업로드 이벤트가 발생하는 순간 Lambda가 자동으로 실행되므로, 별도의 API 호출이나 수동 처리 없이 최적화가 비동기로 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Lambda 함수 구현&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sharp 라이브러리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lambda 함수 내부에서는 Node.js 환경에서 이미지 처리에 가장 널리 쓰이는 &lt;a href=&quot;https://www.npmjs.com/package/sharp&quot;&gt;Sharp&lt;/a&gt; 라이브러리를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sharp는 libvips 기반으로 동작해 처리 속도가 빠르고, 리사이즈&amp;middot;포맷 변환&amp;middot;품질 조정&amp;middot;메타데이터 제거를 하나의 체인으로 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용한 설정은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 람다 코드 중 이미지 최적화 설정 부분
const output = await sharp(content_buffer)
  .rotate()                             // EXIF orientation 기반 자동 회전 (메타데이터 제거 전 처리)
  .resize(400, 400, { fit: &quot;inside&quot; })  // 서비스 최대 사용 크기 기준 리사이즈
  .webp({ quality: 80 })                // WebP 변환 + 품질 80 (메타데이터는 Sharp 기본 동작으로 자동 제거)
  .toBuffer();                          // 처리된 이미지를 Lambda 컨테이너 메모리에 Buffer로 반환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 메서드를 하나씩 알아보겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Rotate - &lt;code&gt;.rotate()&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 설명할 예정이지만, 최적화 과정에서 이미지 메타데이터를 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;휴대폰 사진에는 EXIF의 Orientation 값이 있는데, 여기에는 회전 정보가 들어있다. 이 메타데이터를 제거하면 올바르지 않은 방향으로 이미지가 변환될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 최적화 진행 전, &lt;code&gt;.rotate()&lt;/code&gt;를 통해 EXIF의 Orientation 값 기반으로 이미지를 먼저 회전시켜야 정상적인 방향으로 변환될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 해당 메서드를 넣기 전, 아래 왼쪽 게시글처럼 이미지가 엉뚱하게 회전돼서 보였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z1m38/dJMcahKBOxe/CbaTcykOZ1nV56UqvVw5s0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z1m38/dJMcahKBOxe/CbaTcykOZ1nV56UqvVw5s0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z1m38/dJMcahKBOxe/CbaTcykOZ1nV56UqvVw5s0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz1m38%2FdJMcahKBOxe%2FCbaTcykOZ1nV56UqvVw5s0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;698&quot; height=&quot;313&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Resize - &lt;code&gt;.resize(400, 400, { fit: &quot;inside&quot; })&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 Lighthouse 분석을 했을 때, '화면에 필요한 크기보다 훨씬 큰 이미지가 그대로 전달되고 있다'는 내용이 있었고(아래 사진 2번 내용), 실제로 S3에 저장된 이미지가 원본 그대로였음을 확인했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chKG7L/dJMcabDCznT/UPbN1mW17dnceEFrWPjwP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chKG7L/dJMcabDCznT/UPbN1mW17dnceEFrWPjwP0/img.png&quot; data-alt=&quot;lighthouse 분석 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chKG7L/dJMcabDCznT/UPbN1mW17dnceEFrWPjwP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchKG7L%2FdJMcabDCznT%2FUPbN1mW17dnceEFrWPjwP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;554&quot; height=&quot;397&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;lighthouse 분석 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 반영해 서비스 최대 사용 크기를 기준으로 리사이즈를 적용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스에서 이미지가 사용되는 가장 큰 화면인 확대 이미지 모달 기준으로 크기를 결정했다. 썸네일과 확대 모달 모두 &lt;code&gt;400&amp;times;400&lt;/code&gt;으로 충분했기 때문에, 하나의 최적화본으로 두 가지 용도를 커버할 수 있도록 이 크기를 기준으로 잡았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, &lt;code&gt;fit: &quot;inside&quot;&lt;/code&gt; 옵션을 적용해 원본 비율을 유지하면서 &lt;code&gt;400&amp;times;400&lt;/code&gt; 안에 들어오도록 리사이즈했다. 이 옵션 없이 &lt;code&gt;resize(400, 400)&lt;/code&gt;만 적용하면 원본 비율과 관계없이 강제로 정사각형으로 만들어 이미지가 찌그러질 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kNX9z/dJMcaa5MbDB/S7rRdwB6U1Na9glzqcqK80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kNX9z/dJMcaa5MbDB/S7rRdwB6U1Na9glzqcqK80/img.png&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;375&quot; data-is-animation=&quot;false&quot; style=&quot;width: 66.8821%; margin-right: 10px;&quot; data-widthpercent=&quot;67.67&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kNX9z/dJMcaa5MbDB/S7rRdwB6U1Na9glzqcqK80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkNX9z%2FdJMcaa5MbDB%2FS7rRdwB6U1Na9glzqcqK80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;432&quot; height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kIzS2/dJMcaa5MbDM/V8q8fJrheRVX4B5Bl0EGH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kIzS2/dJMcaa5MbDM/V8q8fJrheRVX4B5Bl0EGH0/img.png&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;863&quot; data-is-animation=&quot;false&quot; style=&quot;width: 31.9551%;&quot; data-widthpercent=&quot;32.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kIzS2/dJMcaa5MbDM/V8q8fJrheRVX4B5Bl0EGH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkIzS2%2FdJMcaa5MbDM%2FV8q8fJrheRVX4B5Bl0EGH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;475&quot; height=&quot;863&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WebP 변환과 압축도 설정 - &lt;code&gt;.webp({ quality: 80 })&lt;/code&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장자를 WebP로 변환해 Lighthouse가 &quot;최신 이미지 포맷 사용 시 다운로드 용량을 줄일 수 있다&quot;고 제시한 개선 포인트를 반영했다.&amp;nbsp; (아래 사진 1번 내용)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y5f4R/dJMcacWN1GT/WpMakiKWg4Uc9FNGXDvZoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y5f4R/dJMcacWN1GT/WpMakiKWg4Uc9FNGXDvZoK/img.png&quot; data-alt=&quot;lighthouse 분석 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y5f4R/dJMcacWN1GT/WpMakiKWg4Uc9FNGXDvZoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY5f4R%2FdJMcacWN1GT%2FWpMakiKWg4Uc9FNGXDvZoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;554&quot; height=&quot;397&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;lighthouse 분석 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebP는 무손실 압축과 손실 압축 모드를 모두 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 업로드한 사진 이미지는 색 변화가 자연스럽고 세부 디테일이 많아, 손실 압축을 적용해도 사용자가 품질 저하를 인지하기 어렵고 용량 절감 효과가 크다. 따라서 손실 압축을 적용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;무손실 압축 vs 손실 압축&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;무손실 압축&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터를 하나도 버리지 않고 압축. 압축을 풀면 원본과 완전히 동일하며, 파일 크기 감소 효과는 상대적으로 작다.&lt;/li&gt;
&lt;li&gt;.webp({ lossless: true }&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;손실 압축&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사람 눈에 잘 안 보이는 데이터를 일부 버리면서 압축. 원본으로 되돌릴 수 없지만 파일 크기를 훨씬 많이 줄일 수 있다.&lt;/li&gt;
&lt;li&gt;.webp({ quality: 80 })&lt;/li&gt;
&lt;li&gt;Sharp에서 .webp({ quality: 80 })은 lossless: true를 명시하지 않았으므로 기본적으로 손실 압축 모드로 동작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;quality는 손실 압축 모드 안에서 &quot;얼마나 버릴 것인가&quot;를 조절하는 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;quality 75~85 구간은 눈에 띄지 않는 수준의 품질 손실과 함께 상당한 파일 크기 감소를 제공하는 실용적인 최적점으로 알려져 있다. 이 범위 내에서 80을 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 quality를 너무 낮게 주면 사진이 흐릿하게 보여져 오히려 사용자경험을 헤치게 될 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K2u3q/dJMcafMN6Zk/HCQj07gqg3RZYYJ2l72UoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K2u3q/dJMcafMN6Zk/HCQj07gqg3RZYYJ2l72UoK/img.png&quot; data-alt=&quot;전: 이미지를 너무 최적화해서 흐릿한 이미지 / 후: 적절히 최적화한 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K2u3q/dJMcafMN6Zk/HCQj07gqg3RZYYJ2l72UoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK2u3q%2FdJMcafMN6Zk%2FHCQj07gqg3RZYYJ2l72UoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;320&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;전: 이미지를 너무 최적화해서 흐릿한 이미지 / 후: 적절히 최적화한 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메타데이터 제거&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지에는 촬영 날짜, GPS 위치 정보 등이 EXIF 메타데이터로 포함될 수 있다. 서비스에서 필요하지 않은 정보일 뿐만 아니라, 다른 사용자가 이미지를 다운로드하면 이 정보가 그대로 노출되어 개인정보 보안 문제로 이어질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sharp는 출력 시 별도 설정 없이 기본적으로 모든 메타데이터를 제거한다. (&lt;a href=&quot;https://sharp.pixelplumbing.com/api-output/&quot;&gt;Sharp 공식 문서&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;toBuffer() 는 최적화된 이미지를 Lambda 컨테이너 메모리에 Buffer 형태로 반환한다. 파일로 저장하지 않고 메모리에서 곧바로 S3에 업로드할 수 있어, 불필요한 디스크 I/O 없이 처리가 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전체 코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주석 설명과 함께 전체 코드를 첨부한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// AWS S3와 통신하기 위한 SDK 라이브러리
const { S3Client, GetObjectCommand, PutObjectCommand } = require(&quot;@aws-sdk/client-s3&quot;);
// 이미지 처리 라이브러리
const sharp = require(&quot;sharp&quot;); 
// 스트림 데이터를 Buffer(바이트 배열)로 변환하기 위한 유틸리티
const { buffer } = require(&quot;stream/consumers&quot;);

// S3 클라이언트 초기화 (서울 리전)
const s3 = new S3Client({ region: &quot;ap-northeast-2&quot; });

// Lambda 함수 진입점 &amp;mdash; S3 이벤트 발생 시 자동으로 실행됨
exports.handler = async (event) =&amp;gt; {
  // S3는 한 번에 여러 이벤트를 묶어 전달할 수 있어 반복문으로 처리
  for (const record of event.Records ?? []) {
    // 이벤트에서 버킷명과 파일 경로(key) 추출
    const bucket = record.s3.bucket.name;
    const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, &quot; &quot;));

    // upload-resize/ 경로(최적화 버킷)에서 발생한 이벤트는 건너뜀
    // &amp;rarr; 최적화본 저장 시 다시 Lambda가 트리거되는 재귀 호출 방지
    if (key.startsWith(&quot;upload-resize/&quot;)) continue;

    // 원본 파일명에서 확장자를 제거하고 .webp로 변환한 저장 경로 생성
    // 예) upload/abc.jpg &amp;rarr; upload-resize/abc.webp
    const fileName = key.split(&quot;/&quot;).pop();
    const baseFileName = fileName.split(&quot;.&quot;).slice(0, -1).join(&quot;.&quot;);
    const dstKey = `upload-resize/${baseFileName}.webp`;

    try {
      // S3에서 원본 이미지 읽기
      const res = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
      // 스트림 데이터를 Buffer로 변환 (Sharp가 처리할 수 있는 형태)
      const content_buffer = await buffer(res.Body);

      // 이미지 최적화
      const output = await sharp(content_buffer)
        .rotate()                             // EXIF orientation 기반 자동 회전 (메타데이터 제거 전 처리)
        .resize(400, 400, { fit: &quot;inside&quot; })  // 서비스 최대 사용 크기 기준 리사이즈
        .webp({ quality: 80 })                // WebP 변환 + 품질 80 
          .toBuffer();                          // 처리된 이미지를 Lambda 컨테이너 메모리에 Buffer로 반환

      // 최적화본을 S3 최적화 버킷에 저장
      await s3.send(
        new PutObjectCommand({
          Bucket: bucket,
          Key: dstKey,       // 저장 경로: upload-resize/파일명.webp
          Body: output,      // 최적화된 이미지 데이터
          ContentType: &quot;image/webp&quot;,
        })
      );
    } catch (error) {
      console.error(&quot;FAIL&quot;, { key, dstKey, errorName: error?.name, errorMessage: error?.message });
      throw error;
    }
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원본/최적화 버킷 분리 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 이미지와 최적화본은 같은 버킷이 아닌 &lt;b&gt;별도의 버킷&lt;/b&gt;에 분리해서 저장했다. 이유는 두 가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;재귀 트리거 방지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 버킷을 입력과 출력으로 함께 사용하면, 최적화본을 저장할 때 다시 S3 업로드 이벤트가 발생해 Lambda가 무한히 트리거되는 재귀 호출 문제가 생긴다. 버킷을 분리하면 이 문제를 구조적으로 차단할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 S3 이벤트 트리거 설정에서 Prefix: upload/로 범위를 제한한 것도 같은 이유다. 혹시 같은 버킷을 써야 하는 상황이라면 Prefix 설정만으로도 재귀 호출을 막을 수 있지만, 우리 서비스는 구조적으로 더 명확하게 분리하는 방향을 택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최적화 실패 시 원본 fallback&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lambda 처리가 실패하거나 최적화본이 아직 생성되지 않은 상황에서도, 원본 버킷에 이미지가 보존되어 있어 사용자에게 이미지를 보여줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트의 useImageFallback 훅이 최적화 버킷 &amp;rarr; 원본 버킷 &amp;rarr; 기본 이미지 순서로 순차적으로 fallback 처리하도록 로직을 작성하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.webp&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6nIGa/dJMcagZcJtY/0rwz0A4glwsKmN6Fnsfe91/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6nIGa/dJMcagZcJtY/0rwz0A4glwsKmN6Fnsfe91/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6nIGa/dJMcagZcJtY/0rwz0A4glwsKmN6Fnsfe91/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6nIGa%2FdJMcagZcJtY%2F0rwz0A4glwsKmN6Fnsfe91%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;722&quot; height=&quot;340&quot; data-filename=&quot;image.webp&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 이벤트 기반 Lambda 파이프라인 구축 후 Lighthouse를 다시 측정했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;LCP&lt;/b&gt;: 7.6초 &amp;rarr; 2.1초 (Google Core Web Vitals 기준 '나쁨' &amp;rarr; '좋음' 구간)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Performance 점수&lt;/b&gt;: 70 &amp;rarr; 90&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LCP 2.5초 이하라는 Google의 좋은 사용자 경험 기준을 충족하게 됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmE45c/dJMcadVGo5Y/kHS7hqkN0M0rGMT7xbD5F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmE45c/dJMcadVGo5Y/kHS7hqkN0M0rGMT7xbD5F0/img.png&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;641&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50.71&quot; style=&quot;width: 50.1225%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmE45c/dJMcadVGo5Y/kHS7hqkN0M0rGMT7xbD5F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmE45c%2FdJMcadVGo5Y%2FkHS7hqkN0M0rGMT7xbD5F0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;634&quot; height=&quot;641&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLc9E2/dJMcaa5Mb1i/H9sUiW2NvL5nct203QVVW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLc9E2/dJMcaa5Mb1i/H9sUiW2NvL5nct203QVVW1/img.png&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;646&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;49.29&quot; style=&quot;width: 48.7147%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLc9E2/dJMcaa5Mb1i/H9sUiW2NvL5nct203QVVW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLc9E2%2FdJMcaa5Mb1i%2FH9sUiW2NvL5nct203QVVW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;646&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;전 / 후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Network 탭에서도 변화가 뚜렷했다. 문제 상황에서 주요 병목으로 지목했던 이미지 리소스의 Content Download 시간이 632ms에서 0.52ms로 단축됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 이미지 로드 시간도 702ms에서 15.71ms로 줄어, 이미지 다운로드 구간의 병목이 사실상 해소됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beMSdQ/dJMcaiiqg6I/UC3cBDMeAGjFs59ZMPRBYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beMSdQ/dJMcaiiqg6I/UC3cBDMeAGjFs59ZMPRBYK/img.png&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;1054&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.1948%; margin-right: 10px;&quot; data-widthpercent=&quot;50.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beMSdQ/dJMcaiiqg6I/UC3cBDMeAGjFs59ZMPRBYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeMSdQ%2FdJMcaiiqg6I%2FUC3cBDMeAGjFs59ZMPRBYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1912&quot; height=&quot;1054&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHSNCB/dJMcaf0hYrD/M6qMaNZFe0nQCE753z05c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHSNCB/dJMcaf0hYrD/M6qMaNZFe0nQCE753z05c1/img.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1165&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.6424%;&quot; data-widthpercent=&quot;49.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHSNCB/dJMcaf0hYrD/M6qMaNZFe0nQCE753z05c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHSNCB%2FdJMcaf0hYrD%2FM6qMaNZFe0nQCE753z05c1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;1165&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;전 / 후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 게시물을 탐색할 때 이미지가 이전 대비 거의 즉시 표시되어, 이전처럼 이미지를 기다려야 하는 상황이 많이 해소되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 일회성 개선이 아니라, 사용자가 이미지를 업로드하면 자동으로 최적화본이 생성되는 구조를 마련했다는 점에서 운영 측면에서도 의미 있는 결과다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 S3 이벤트 트리거 설정부터 Lambda 함수 구현, 원본/최적화 버킷 분리 설계까지 파이프라인을 어떻게 구축했는지 다뤘다. 그 결과 LCP를 7.6초에서 2.1초로 단축하고, Lighthouse Performance 점수를 70에서 90으로 개선할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 이미지를 압축하는 것에 그치지 않고, 기존 업로드 흐름을 유지하면서 이미지 처리를 서버와 완전히 분리한 구조를 만든 것이 핵심이었다. 덕분에 기존 API와 클라이언트 코드 수정 없이 적용할 수 있었고, 업로드량이 늘어도 API 서버 부담이 커지지 않는 구조를 갖출 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 사용자가 게시물을 탐색할 때 핵심 콘텐츠인 이미지를 지연 없이 확인할 수 있게 됐고, 처음 목표했던 이미지 노출 성능 개선을 달성할 수 있었다.&lt;/p&gt;</description>
      <category>문제 해결 &amp;amp; 구현 기록</category>
      <category>AWS</category>
      <category>lambda</category>
      <category>LCP</category>
      <category>lighthouse</category>
      <category>S3</category>
      <category>이미지</category>
      <category>최적화</category>
      <author>sangchu</author>
      <guid isPermaLink="true">https://sanghee01.tistory.com/202</guid>
      <comments>https://sanghee01.tistory.com/202#entry202comment</comments>
      <pubDate>Thu, 26 Mar 2026 16:14:37 +0900</pubDate>
    </item>
    <item>
      <title>이미지 최적화 파이프라인 구축기 (1) - 문제 진단과 방법 선택</title>
      <link>https://sanghee01.tistory.com/201</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 &lt;a href=&quot;https://sanghee01.tistory.com/199&quot;&gt;정적 이미지 최적화 방식&lt;/a&gt;을 다룬 글을 작성한 적 있다. 이번에는 사용자가 업로드한 이미지를 최적화한 경험을 정리해보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 LCP 7.6초라는 수치를 발견하고, 네 가지 최적화 방법을 비교해 S3 이벤트 기반 Lambda 방식을 선택하기까지의 과정을 다룬다. 이 작업을 통해 최종적으로 LCP를 7.6초에서 2.1초로 단축하고, Lighthouse Performance 점수를 70에서 90으로 개선했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모멘트는 사용자가 게시물에 텍스트와 이미지를 함께 업로드할 수 있는 SNS 커뮤니티 서비스다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eknk8E/dJMcafzdcnu/CFoJYJmg5iHUKndVQaRMK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eknk8E/dJMcafzdcnu/CFoJYJmg5iHUKndVQaRMK1/img.png&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;931&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.1198%; margin-right: 10px;&quot; data-widthpercent=&quot;32.88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eknk8E/dJMcafzdcnu/CFoJYJmg5iHUKndVQaRMK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feknk8E%2FdJMcafzdcnu%2FCFoJYJmg5iHUKndVQaRMK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;484&quot; height=&quot;931&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tkznd/dJMcabKmwG2/rJxd2dzLbZckDoQBHffs7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tkznd/dJMcabKmwG2/rJxd2dzLbZckDoQBHffs7K/img.png&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;934&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.6781%; margin-right: 10px;&quot; data-widthpercent=&quot;33.46&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tkznd/dJMcabKmwG2/rJxd2dzLbZckDoQBHffs7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftkznd%2FdJMcabKmwG2%2FrJxd2dzLbZckDoQBHffs7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;494&quot; height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMaSU4/dJMcad2ot7G/7KbRor050a2dmBFF3fV2CK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMaSU4/dJMcad2ot7G/7KbRor050a2dmBFF3fV2CK/img.png&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;934&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.8766%;&quot; data-widthpercent=&quot;33.66&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMaSU4/dJMcad2ot7G/7KbRor050a2dmBFF3fV2CK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMaSU4%2FdJMcad2ot7G%2F7KbRor050a2dmBFF3fV2CK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;497&quot; height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 사용자로부터 이미지 로드 속도가 느리다는 피드백을 받았고, 서비스 성능을 점검하기 위해 Lighthouse 측정을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수치로 확인해보니 문제는 생각보다 명확했다.&amp;nbsp;Performance 점수는 70점이었고, 그 중 주요 콘텐츠가 화면에 보이는 시간(LCP)이 7.6초로 측정됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpAWq0/dJMcagdOC3E/nfiEbYUoD8A9DMqvDuEAiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpAWq0/dJMcagdOC3E/nfiEbYUoD8A9DMqvDuEAiK/img.png&quot; data-alt=&quot;Lighthouse 측정 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpAWq0/dJMcagdOC3E/nfiEbYUoD8A9DMqvDuEAiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpAWq0%2FdJMcagdOC3E%2FnfiEbYUoD8A9DMqvDuEAiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;585&quot; height=&quot;505&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;1020&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Lighthouse 측정 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google의 Core Web Vitals 기준에서 좋은 사용자 경험으로 분류되려면 LCP가 2.5초 이하여야 한다. 7.6초는 '나쁨' 구간으로, 사용자가 핵심 콘텐츠를 보기까지 상당한 시간을 기다려야 하는 상태였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lptWx/dJMcaaq702N/YEAvBKO4Z8YpRKAis4iE7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lptWx/dJMcaaq702N/YEAvBKO4Z8YpRKAis4iE7K/img.png&quot; data-alt=&quot;출처: Google web.dev 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lptWx/dJMcaaq702N/YEAvBKO4Z8YpRKAis4iE7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlptWx%2FdJMcaaq702N%2FYEAvBKO4Z8YpRKAis4iE7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;125&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: Google web.dev 문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인을 더 구체적으로 파악하기 위해 Network 탭을 확인했다. 이미지 리소스의 Content Download 시간이 약 633ms로, 다른 리소스(JS, 폰트)의 평균 약 50ms 대비 약 12배 길었다. 이미지 다운로드 구간이 LCP 지연의 주요 원인임을 파악했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SRXKG/dJMcafspSv4/cxRKxgYEd30MEj7KchREt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SRXKG/dJMcafspSv4/cxRKxgYEd30MEj7KchREt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SRXKG/dJMcafspSv4/cxRKxgYEd30MEj7KchREt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSRXKG%2FdJMcafspSv4%2FcxRKxgYEd30MEj7KchREt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;454&quot; height=&quot;245&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모멘트는 이미지 중심의 SNS 서비스인 만큼, 이미지 로딩이 느리면 사용자는 핵심 콘텐츠를 확인하기 전까지 빈 화면을 봐야 하고 게시물 탐색 흐름이 끊긴다. 이는 서비스 품질에 직결되는 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 따라 '사용자가 피드의 핵심 콘텐츠를 지연 없이 확인할 수 있도록 이미지 노출 성능을 개선한다'는 목표를 세웠고, 이미지 로딩 병목 진단 및 최적화 구조 설계&amp;middot;구현을 진행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 해결을 위한 조사&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 원인을 더 정확히 파악하고 해결 방향을 결정하기 위해 Lighthouse 분석 결과, AWS 공식 문서, 이미지 최적화 관련 기술 블로그를 참고했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인 파악&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lighthouse는 이미지 다운로드 속도를 줄이기 위해 두 가지 개선 포인트를 제시하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1)WebP와 같은 최신 이미지 포맷을 사용하면 다운로드 용량을 줄일 수 있다는 것, (2)화면에 필요한 크기보다 훨씬 큰 이미지가 그대로 전달되고 있다는 것이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOwtzZ/dJMcadOQXrQ/NjWcCdsChchlyUteIMBCzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOwtzZ/dJMcadOQXrQ/NjWcCdsChchlyUteIMBCzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOwtzZ/dJMcadOQXrQ/NjWcCdsChchlyUteIMBCzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOwtzZ%2FdJMcadOQXrQ%2FNjWcCdsChchlyUteIMBCzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;536&quot; height=&quot;384&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 S3 버킷에 저장된 이미지를 확인해보니, 확장자는 jpg, png 등 제각각이었고 이미지 크기도 업로드된 원본 그대로 유지되고 있었다. 화면에 렌더링될 때 필요한 크기보다 훨씬 큰 데이터가 그대로 전송되고 있었던 것이다. 이것이 이미지 다운로드 구간이 길어지고, LCP가 7.6초까지 되는 직접적인 원인이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUAZHi/dJMcaio983Y/Svgav9PyHrkxZCo7K4t3DK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUAZHi/dJMcaio983Y/Svgav9PyHrkxZCo7K4t3DK/img.png&quot; data-alt=&quot;S3 버킷에 저장된 이미지 목록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUAZHi/dJMcaio983Y/Svgav9PyHrkxZCo7K4t3DK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUAZHi%2FdJMcaio983Y%2FSvgav9PyHrkxZCo7K4t3DK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;564&quot; height=&quot;196&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;S3 버킷에 저장된 이미지 목록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 방향 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 업로드한 이미지는 빌드 시점에 미리 최적화할 수 없다. 서비스가 운영되는 중에 동적으로 처리할 수 있는 계층에서 최적화를 진행해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 계층에서 처리할지를 결정하기 위해 기존 업로드 구조를 유지할 수 있는지, 서버 부하 없이 처리할 수 있는지, 이미지 크기 제한 없이 안정적으로 처리할 수 있는지를 기준으로 네 가지 방법을 검토했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방법 A: 백엔드 서버에서 직접 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 이미지를 수신해 처리하는 방식으로, 이미지 처리 로직을 중앙에서 관리할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 방식을 채택하면 이미지 데이터가 '클라이언트 &amp;rarr; 서버 &amp;rarr; S3' 경로로 두 번 전송되는 구조가 된다. 서버가 이미지 데이터를 직접 받아야 하기 때문에 네트워크 부담도 커진다. 우리 서비스는 서버 부하를 줄이기 위해 presigned URL 기반의 직접 업로드 구조를 사용하고 있었는데, 이 방식은 그 장점을 정면으로 훼손하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방법 B: 클라이언트에서 S3 업로드 전 압축&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/browser-image-compression&quot;&gt;browser-image-compression&lt;/a&gt; 같은 라이브러리를 활용하면 사용자가 이미지를 업로드하는 시점에 자동으로 압축을 수행할 수 있다. 서버를 거치기 전에 이미지 크기를 줄일 수 있어 전송 데이터 자체를 줄인다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 압축 작업이 사용자 기기에서 직접 수행되기 때문에, 저사양 기기에서는 압축 시간이 길어지면서 오히려 업로드 체감 속도가 느려질 수 있다. 또한 클라이언트 환경마다 결과가 달라질 수 있어 이미지 품질과 용량을 일관되게 관리하기 어렵다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;449&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3tqTg/dJMcacJgcFw/otfkGMY8bPOF8ucgt37ka0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3tqTg/dJMcacJgcFw/otfkGMY8bPOF8ucgt37ka0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3tqTg/dJMcacJgcFw/otfkGMY8bPOF8ucgt37ka0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3tqTg%2FdJMcacJgcFw%2FotfkGMY8bPOF8ucgt37ka0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1197&quot; height=&quot;449&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;449&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방법 C: CloudFront Lambda@Edge 기반 실시간 변환&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CDN 레이어에서 요청 시점에 이미지를 변환하는 방식으로, 하나의 원본 이미지를 썸네일, 리스트, 상세 화면 등 다양한 크기로 유연하게 제공할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciJ7OG/dJMcadnN9cG/YC1EhVPqrkN1LGiX5c4KL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciJ7OG/dJMcadnN9cG/YC1EhVPqrkN1LGiX5c4KL1/img.png&quot; data-alt=&quot;출처: AWS 공식 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciJ7OG/dJMcadnN9cG/YC1EhVPqrkN1LGiX5c4KL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciJ7OG%2FdJMcadnN9cG%2FYC1EhVPqrkN1LGiX5c4KL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;545&quot; height=&quot;194&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: AWS 공식 문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Lambda@Edge는 응답 크기에 제한이 있다. 우리 서비스는 사용자가 업로드한 원본 이미지가 수 MB 이상이 될 수 있었기 때문에, 안정적인 처리를 보장하기 어려웠다. 또한 요청 흐름 내에서 처리되기 때문에 실패 시 사용자에게 직접 영향을 준다는 점도 부담이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방법 D: S3 업로드 이벤트 트리거 &amp;rarr; Lambda 비동기 처리 (채택)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 세 방법은 각각 기존 업로드 구조를 훼손하거나, 결과의 일관성을 보장하기 어렵거나, 이미지 크기 제한이라는 구조적 한계가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3는 객체가 업로드되면 이벤트를 발생시킬 수 있고, 이를 트리거로 Lambda 함수를 실행할 수 있다. 이 방식은 기존 presigned URL 기반 업로드 흐름을 전혀 건드리지 않아도 되고, 이미지 처리가 사용자 요청 흐름과 완전히 분리되어 있어 실패해도 사용자 경험에 영향을 주지 않는다. 또한 이미지 처리 부하가 API 서버와 분리되어 있어 업로드량이 늘어도 서버 부담이 커지지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지 기준을 모두 충족하는 방식이었기 때문에 이 방법을 최종 선택했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wuO1b/dJMcacCtYKb/bZ0VERwFTZEKh0MHp7zByK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wuO1b/dJMcacCtYKb/bZ0VERwFTZEKh0MHp7zByK/img.png&quot; data-alt=&quot;S3 이벤트 기반 Lambda 설정 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wuO1b/dJMcacCtYKb/bZ0VERwFTZEKh0MHp7zByK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwuO1b%2FdJMcacCtYKb%2FbZ0VERwFTZEKh0MHp7zByK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;261&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;S3 이벤트 기반 Lambda 설정 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리 및 다음 단계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 LCP 7.6초라는 수치로 문제를 진단하고, 네 가지 이미지 최적화 방법을 비교해 S3 이벤트 기반 Lambda 비동기 처리 방식을 선택하기까지의 과정을 다뤘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sanghee01.tistory.com/202&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 실제로 S3 이벤트와 Lambda를 연결해 이미지 최적화 파이프라인을 구현한 과정을 다룬다. Sharp 설정값을 어떻게 결정했는지, 재귀 트리거 방지와 fallback 구조를 어떻게 설계했는지도 함께 정리할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1774523405527&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;이미지 최적화 파이프라인 구축기 (2) - S3 이벤트 트리거 기반 Lambda 구현&quot; data-og-description=&quot;들어가며지난 글에서는 LCP 7.6초라는 문제를 진단하고, 네 가지 방법을 비교해 S3 이벤트 기반 Lambda 비동기 처리 방식을 선택하기까지의 과정을 다뤘다. 이미지 최적화 파이프라인 구축기 (1) - 문&quot; data-og-host=&quot;sanghee01.tistory.com&quot; data-og-source-url=&quot;https://sanghee01.tistory.com/202&quot; data-og-url=&quot;https://sanghee01.tistory.com/202&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ClbD6/dJMb8U8TjyQ/M7wiaVHOQ9rXDFLNcLulRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/8KUMq/dJMb8SpHHhq/kgUpEsZQM4Apiy2p9lPRa1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bXbfU7/dJMb8YXLeDH/2oIfejU0kQ4k9npcQ1blvK/img.png?width=3266&amp;amp;height=824&amp;amp;face=0_0_3266_824&quot;&gt;&lt;a href=&quot;https://sanghee01.tistory.com/202&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sanghee01.tistory.com/202&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ClbD6/dJMb8U8TjyQ/M7wiaVHOQ9rXDFLNcLulRK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/8KUMq/dJMb8SpHHhq/kgUpEsZQM4Apiy2p9lPRa1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bXbfU7/dJMb8YXLeDH/2oIfejU0kQ4k9npcQ1blvK/img.png?width=3266&amp;amp;height=824&amp;amp;face=0_0_3266_824');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;이미지 최적화 파이프라인 구축기 (2) - S3 이벤트 트리거 기반 Lambda 구현&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며지난 글에서는 LCP 7.6초라는 문제를 진단하고, 네 가지 방법을 비교해 S3 이벤트 기반 Lambda 비동기 처리 방식을 선택하기까지의 과정을 다뤘다. 이미지 최적화 파이프라인 구축기 (1) - 문&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sanghee01.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>문제 해결 &amp;amp; 구현 기록</category>
      <category>AWS</category>
      <category>lambda</category>
      <category>LCP</category>
      <category>lighthouse</category>
      <category>이미지</category>
      <category>최적화</category>
      <author>sangchu</author>
      <guid isPermaLink="true">https://sanghee01.tistory.com/201</guid>
      <comments>https://sanghee01.tistory.com/201#entry201comment</comments>
      <pubDate>Mon, 23 Mar 2026 23:48:02 +0900</pubDate>
    </item>
    <item>
      <title>webpack 트리셰이킹이 안 된 진짜 이유: sideEffects 동명 옵션 혼동</title>
      <link>https://sanghee01.tistory.com/200</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유난히 번들 크기가 큰 패키지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모멘트 프로젝트에서 번들 분석을 하던 중, 아이콘 패키지 lucide-react의 비중이 유난히 크게 보였다.&lt;br /&gt;프로젝트에서 실제로 사용하는 아이콘은 약 20개였는데, 번들에 아이콘이 무수히 많이 포함되어있고, node_modules 번들의 1/4 이상을 차지하는 게 이상하다고 판단했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nVHsT/dJMcafTitHB/UH7Up6NxsX2Jcqw9fjFbn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nVHsT/dJMcafTitHB/UH7Up6NxsX2Jcqw9fjFbn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nVHsT/dJMcafTitHB/UH7Up6NxsX2Jcqw9fjFbn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnVHsT%2FdJMcafTitHB%2FUH7Up6NxsX2Jcqw9fjFbn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;617&quot; height=&quot;466&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;816&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리셰이킹과 관련되었다고 판단하여, 관련 설정을 하나씩 점검했고, 그 결과 특정 옵션을 수정해 번들 크기를 줄일 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 수정하고도 찝찝한 마음이 들었다. 그 옵션은 트리셰이킹을 돕기 위한 옵션으로 알고 있었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 내가 무엇을 오해했는지, 실제 원인이 무엇이었는지, 그리고 올바른 해결 방법은 무엇이었는지 정리하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사전 지식&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트리셰이킹&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3vyPO/dJMcaaLdtnw/4ktMRYGeoH8MCOe6xGXDEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3vyPO/dJMcaaLdtnw/4ktMRYGeoH8MCOe6xGXDEK/img.png&quot; data-alt=&quot;나무 털기 - 필요 없는 잎들은 떨어뜨린다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3vyPO/dJMcaaLdtnw/4ktMRYGeoH8MCOe6xGXDEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3vyPO%2FdJMcaaLdtnw%2F4ktMRYGeoH8MCOe6xGXDEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;589&quot; height=&quot;457&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;892&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;나무 털기 - 필요 없는 잎들은 떨어뜨린다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리셰이킹은 프로젝트에서 &lt;b&gt;실제로 사용되지 않는 코드를 제거&lt;/b&gt;하여 최종 번들 크기를 줄이는 최적화 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리셰이킹이 동작하려면 webpack이 빌드 타임에 어떤 코드가 실제로 쓰이는지, 모듈 간의 관계를 정적으로 파악할 수 있어야 한다. 이를 위해 모듈 시스템이 중요한데, JavaScript에는 두 가지 주요 모듈 시스템이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모듈 시스템 - CJS, ESM&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈은 재사용 가능한 코드 단위(파일)로, 각 모듈은 자체 스코프를 가지며 export로 공개하고 import로 가져온다. JavaScript에서 모듈을 다루는 방식에는 두 가지 주요 시스템이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심은 모듈 관계를 파악하는 시점인데, 이 시점이 트리셰이킹 여부를 결정한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 72.7907%; height: 163px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 22.8613%; height: 20px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 37.3311%; height: 20px;&quot;&gt;CommonJS (CJS)&lt;/td&gt;
&lt;td style=&quot;width: 39.8076%; height: 20px;&quot;&gt;ES Modules (ESM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 22.8613%; height: 20px;&quot;&gt;import 문법&lt;/td&gt;
&lt;td style=&quot;width: 37.3311%; height: 20px;&quot;&gt;require()&lt;/td&gt;
&lt;td style=&quot;width: 39.8076%; height: 20px;&quot;&gt;import&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 22.8613%; height: 20px;&quot;&gt;export 문법&lt;/td&gt;
&lt;td style=&quot;width: 37.3311%; height: 20px;&quot;&gt;module.exports, exports.x&lt;/td&gt;
&lt;td style=&quot;width: 39.8076%; height: 20px;&quot;&gt;export, export default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 22.8613%; height: 20px;&quot;&gt;모듈 관계 파악 시점&lt;/td&gt;
&lt;td style=&quot;width: 37.3311%; height: 20px;&quot;&gt;런타임 (코드 실행 중)&lt;/td&gt;
&lt;td style=&quot;width: 39.8076%; height: 20px;&quot;&gt;컴파일 타임 (실행 전)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 22.8613%; height: 25px;&quot;&gt;Tree Shaking&lt;/td&gt;
&lt;td style=&quot;width: 37.3311%; height: 25px;&quot;&gt;❌ 불가&lt;/td&gt;
&lt;td style=&quot;width: 39.8076%; height: 25px;&quot;&gt;✅ 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CJS의 &lt;code&gt;require()&lt;/code&gt;는 런타임에 동적으로 실행되기 때문에 webpack이 빌드 타임에 모듈 관계를 파악할 수 없다. 반면 ESM의 &lt;code&gt;import&lt;/code&gt;는 컴파일 타임에 정적으로 분석되므로 webpack이 사용되지 않는 코드를 미리 파악하고 제거할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESM은 &lt;code&gt;.mjs&lt;/code&gt; 확장자를 쓰면 적용되는데, &lt;code&gt;package.json&lt;/code&gt;의 &lt;code&gt;type&lt;/code&gt;에 &lt;code&gt;module&lt;/code&gt;로 지정하면 확장자를 &lt;code&gt;.js&lt;/code&gt;로 써도 ESM으로 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모멘트 프로젝트는 이를 지정했으므로 ESM 환경에서 동작한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A3zba/dJMcahDx8yQ/ZuHXizIKe8ULHiF2EfdY21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A3zba/dJMcahDx8yQ/ZuHXizIKe8ULHiF2EfdY21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A3zba/dJMcahDx8yQ/ZuHXizIKe8ULHiF2EfdY21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA3zba%2FdJMcahDx8yQ%2FZuHXizIKe8ULHiF2EfdY21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;215&quot; height=&quot;162&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 과정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;webpack의 sideEffects 옵션 수정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;optimization.sideEffects: false&lt;/code&gt; 설정을 제거하자 트리셰이킹이 다시 동작했고, 프로젝트에서 사용하고 있는 아이콘만 번들에 포함되어 있는 것을 확인했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1667&quot; data-origin-height=&quot;867&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kupc0/dJMcahXR9aG/KzaG3l8Ha9NwkKJEKfnkZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kupc0/dJMcahXR9aG/KzaG3l8Ha9NwkKJEKfnkZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kupc0/dJMcahXR9aG/KzaG3l8Ha9NwkKJEKfnkZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKupc0%2FdJMcahXR9aG%2FKzaG3l8Ha9NwkKJEKfnkZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1667&quot; height=&quot;867&quot; data-origin-width=&quot;1667&quot; data-origin-height=&quot;867&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;나의 찝찝함 &amp;mdash; 아니 이건 트리셰이킹 돕는 옵션 아닌가..?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 나는 해결하고도 찝찝한 마음을 가졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;sideEffects: false&lt;/code&gt;는 말 그대로 '사이드 이펙트가 없다'라는 뜻을 가지며, 번들러에게 '이 패키지의 모든 파일은 사이드이펙트가 없으니 사용되지 않으면 파일 전체를 통째로 제거해도 된다'는 힌트를 주는 옵션이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 예상과 달리 오히려 트리셰이킹이 제대로 동작하지 않았고, 트리셰이킹을 돕는다고 알고 있던 옵션을 제거했더니 결과가 더 좋아져서 계속 의문으로 남았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 옵션을 지움으로써 1차 해결은 됐다. 다른 태스크를 해야 했기에 &lt;code&gt;sideEffects&lt;/code&gt; 옵션이 왜 뜻대로 동작하지 않는지는 추후로 미뤘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;나의 오해 &amp;mdash; sideEffects 동명 옵션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 다시 보았을 때, 내가 오해했음을 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말한 트리셰이킹을 돕는 옵션은 &lt;code&gt;package.json&lt;/code&gt;의 &lt;code&gt;sideEffects&lt;/code&gt; 옵션이었고, 내가 잘못 설정한 옵션은 webpack의 &lt;code&gt;optimization.sideEffects&lt;/code&gt; 옵션이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PFfuB/dJMcahjiMpv/kg4U8QSSJG5YWDgKvk5uK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PFfuB/dJMcahjiMpv/kg4U8QSSJG5YWDgKvk5uK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PFfuB/dJMcahjiMpv/kg4U8QSSJG5YWDgKvk5uK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPFfuB%2FdJMcahjiMpv%2Fkg4U8QSSJG5YWDgKvk5uK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;835&quot; height=&quot;199&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json 파일의 sideEffects 플래그를 webpack이 인식하도록 합니다. &amp;mdash; webpack 공식문서 중&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPKQAV/dJMcadgRR1E/4XYikNVc5rOvu2fjpRpKm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPKQAV/dJMcadgRR1E/4XYikNVc5rOvu2fjpRpKm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPKQAV/dJMcadgRR1E/4XYikNVc5rOvu2fjpRpKm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPKQAV%2FdJMcadgRR1E%2F4XYikNVc5rOvu2fjpRpKm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;837&quot; height=&quot;257&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 나는 &lt;code&gt;webpack.config.js&lt;/code&gt;에서 해당 설정을 &lt;code&gt;false&lt;/code&gt;로 설정해, webpack이 &lt;code&gt;package.json&lt;/code&gt;파일의&amp;nbsp;&amp;nbsp;&lt;code&gt;sideEffects&lt;/code&gt;를 못읽도록 한 것이다. 그래서 해당 플래그를 활용한 파일 단위 최적화를 수행하지 않도록 만든 상태가 되었던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;옵션 올바르게 수정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webpack의 &lt;code&gt;optimization.sideEffects: true&lt;/code&gt; 로 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고로 기본값은 production 모드에서 &lt;code&gt;true&lt;/code&gt;, development 모드에서는 &lt;code&gt;'flag'&lt;/code&gt;다. &lt;code&gt;'flag'&lt;/code&gt;는 소스코드 분석 없이 &lt;code&gt;package.json&lt;/code&gt;의 &lt;code&gt;sideEffects&lt;/code&gt; 표시만 참고하는 모드다.)&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// webpack.common.js
optimization: {
    // ...
  sideEffects: true,  // false에서 true로 변경
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후, &lt;code&gt;package.json&lt;/code&gt;에 &lt;code&gt;sideEffects&lt;/code&gt;를 명시했다. 이 설정은 &amp;ldquo;사용되지 않더라도 제거하면 안 되는 모듈(파일)&amp;rdquo;을 번들러에 알려주는 힌트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를 들어 CSS import처럼 모듈을 실행하는 것 자체가 DOM 변경 등의 사이드 이펙트를 발생시키는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 프로젝트에서는 실제 사이드이펙트가 있는 파일들을 기입했다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;// package.json
{
  &quot;sideEffects&quot;: [
    &quot;*.css&quot;,
    &quot;@emotion/**/*&quot;,
    &quot;@sentry/**/*&quot;,
    &quot;react-ga4/**/*&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 파일을 기입한 이유는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;*.css&lt;/code&gt;: CSS import는 스타일 적용 자체가 목적이므로 side effect로 취급되어 제거되면 안 된다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@emotion/**/*&lt;/code&gt;: Emotion은 렌더링 시 스타일 태그를 생성, 삽입하고 업데이트하는 DOM 조작을 수행하므로 side effect가 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@sentry/**/*&lt;/code&gt;: Sentry는 런타임에서 에러 추적을 위해 전역 상태와 핸들러를 등록/변경하므로 side effect가 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;react-ga4/**/*&lt;/code&gt; : Google Analytics는 초기화시 전역 상태를 설정하고 네트워크 전송을 수행하므로 side effect가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;근본 원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node_modules 폴더를 통해 lucide-react 패키지를 까보니, &lt;code&gt;package.json&lt;/code&gt;에 &lt;code&gt;&quot;sideEffects&quot;: false&lt;/code&gt; 설정이 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZuOdA/dJMcajnMtE4/ZHX8cvTciNbEWpxv9Q45FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZuOdA/dJMcajnMtE4/ZHX8cvTciNbEWpxv9Q45FK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZuOdA/dJMcajnMtE4/ZHX8cvTciNbEWpxv9Q45FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZuOdA%2FdJMcajnMtE4%2FZHX8cvTciNbEWpxv9Q45FK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;501&quot; height=&quot;161&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이 설정이 중요한지 이해하려면 lucide-react의 구조를 봐야 한다. lucide-react의 진입점 파일은 모든 아이콘을 이렇게 export하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccxz08/dJMcafMyabs/LYZ9KGaeJQpsbYmsqc2Apk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccxz08/dJMcafMyabs/LYZ9KGaeJQpsbYmsqc2Apk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccxz08/dJMcafMyabs/LYZ9KGaeJQpsbYmsqc2Apk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fccxz08%2FdJMcafMyabs%2FLYZ9KGaeJQpsbYmsqc2Apk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;561&quot; height=&quot;117&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서는 다음과 같이 import해서 가져온다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import { Search } from 'lucide-react'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webpack은 기본적으로 모듈에 side effect가 있는지 확신할 수 없기 때문에 파일 단위 제거를 수행하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 패키지에서 &lt;code&gt;&quot;sideEffects&quot;: false&lt;/code&gt;를 선언하면 webpack은 해당 패키지의 모듈이 안전하게 제거 가능하다고 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 본 것처럼 lucide-react는 이미 &lt;code&gt;package.json&lt;/code&gt;에 &lt;code&gt;&quot;sideEffects&quot;: false&lt;/code&gt;를 선언해 두고 있었다. 즉, 패키지 입장에서는 &amp;ldquo;사용되지 않는 파일은 통째로 제거해도 안전하다&amp;rdquo;는 힌트를 제공하고 있었던 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 webpack에서 &lt;code&gt;optimization.sideEffects: false&lt;/code&gt;로 해당 힌트를 꺼버린 상태가 되어버려, 파일 단위 트리셰이킹이 동작하지 않았고 사용하지 않는 수백 개의 아이콘 파일이 번들에 전부 포함됐던 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;269&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHGpFv/dJMcaaEs7jC/WUkaS2hBxdbOHMdIlEewI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHGpFv/dJMcaaEs7jC/WUkaS2hBxdbOHMdIlEewI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHGpFv/dJMcaaEs7jC/WUkaS2hBxdbOHMdIlEewI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHGpFv%2FdJMcaaEs7jC%2FWUkaS2hBxdbOHMdIlEewI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;845&quot; height=&quot;269&quot; data-origin-width=&quot;845&quot; data-origin-height=&quot;269&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;번들 크기 변천사&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 lucide-react 아이콘 전체가 번들에 포함됐던 근본 원인을 살펴봤다. 이제 해결 과정에서 번들 크기가 어떻게 변했는지 단계별로 확인해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhQ1EK/dJMcafFLfWE/5Qdc2IHttpGlm5XHzIKoCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhQ1EK/dJMcafFLfWE/5Qdc2IHttpGlm5XHzIKoCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhQ1EK/dJMcafFLfWE/5Qdc2IHttpGlm5XHzIKoCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhQ1EK%2FdJMcafFLfWE%2F5Qdc2IHttpGlm5XHzIKoCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;244&quot; height=&quot;156&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 번들 지표에 대해 설명하고 넘어가겠다. 우리가 번들을 분석하면 다음과 같이 3가지 지표를 볼 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Stat&lt;/b&gt;: webpack 빌드 정보 기준 코드 크기 (최종 압축 전)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Parsed&lt;/b&gt;: 최종 번들 파일에 실제 들어간 코드 크기 (브라우저가 파싱할 기준 크기)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Gzipped&lt;/b&gt;: Parsed 번들을 gzip으로 압축했을 때의 크기 (네트워크 전송량)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 &lt;b&gt;production 환경 &amp;amp; Parsed 기준&lt;/b&gt; 측정을 진행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;code&gt;optimization.sideEffects: false&lt;/code&gt; : &lt;b&gt;1.51MB&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;932&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EoAdE/dJMb99ZR0jY/TKbC1zZcz6f5ObF6M2hCkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EoAdE/dJMb99ZR0jY/TKbC1zZcz6f5ObF6M2hCkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EoAdE/dJMb99ZR0jY/TKbC1zZcz6f5ObF6M2hCkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEoAdE%2FdJMb99ZR0jY%2FTKbC1zZcz6f5ObF6M2hCkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;788&quot; height=&quot;383&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;932&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;code&gt;optimization.sideEffects: false&lt;/code&gt; 옵션 제거만 한 상태(1차 해결): &lt;b&gt;769.56 KB&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xD0gE/dJMcahp2VNh/K2k51HOevo77msMLTqtI4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xD0gE/dJMcahp2VNh/K2k51HOevo77msMLTqtI4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xD0gE/dJMcahp2VNh/K2k51HOevo77msMLTqtI4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxD0gE%2FdJMcahp2VNh%2FK2k51HOevo77msMLTqtI4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;383&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;code&gt;optimization.sideEffects: true&lt;/code&gt; 및 package.json의 sideEffects 설정: &lt;b&gt;705.34 KB&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHV4KU/dJMcahQ6g16/nGZ5QNEb9n2ADfCVRcU0PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHV4KU/dJMcahQ6g16/nGZ5QNEb9n2ADfCVRcU0PK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHV4KU/dJMcahQ6g16/nGZ5QNEb9n2ADfCVRcU0PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHV4KU%2FdJMcahQ6g16%2FnGZ5QNEb9n2ADfCVRcU0PK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;782&quot; height=&quot;382&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;번들크기 1.51MB &amp;rarr; 705.34 KB (약 53.3% 감소)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;확장하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;번들 크기가 성능에 미치는 것&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번들 크기는 웹 애플리케이션의 로딩 성능에 영향을 줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번들 크기가 커지면 네트워크 전송 시간이 증가해 초기 로딩이 느려질 수 있다.&lt;br /&gt;또한 다운로드 이후 브라우저는 자바스크립트를 파싱하고 컴파일하고 실행해야 하는데, 코드 양이 많을수록 이 과정이 오래 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업은 메인 스레드에서 수행되기 때문에 자바스크립트가 많을수록 메인 스레드를 더 오래 점유하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 렌더링이나 사용자 인터랙션이 지연될 수 있으며, 경우에 따라 LCP와 같은 사용자 경험 지표에도 영향을 줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번들 최적화 이후 &lt;b&gt;LCP 측정치도 1.6s &amp;rarr; 1.3s&lt;/b&gt;로 개선되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 LCP는 이미지나 네트워크 상태 등 다양한 요인의 영향을 받지만, 자바스크립트 번들 크기 감소도 메인 스레드 부담을 줄여 렌더링 지연을 완화하는 데 도움을 줄 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ljKFf/dJMcadOEa7j/VPsM4E9BqKzUIK7NifFLI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ljKFf/dJMcadOEa7j/VPsM4E9BqKzUIK7NifFLI0/img.png&quot; data-alt=&quot;번들 최적화 전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ljKFf/dJMcadOEa7j/VPsM4E9BqKzUIK7NifFLI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FljKFf%2FdJMcadOEa7j%2FVPsM4E9BqKzUIK7NifFLI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;272&quot; height=&quot;81&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;번들 최적화 전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnnaia/dJMcaiiaA6e/KyOOzpJTnQ5OiaK9Ve7Ry0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnnaia/dJMcaiiaA6e/KyOOzpJTnQ5OiaK9Ve7Ry0/img.png&quot; data-alt=&quot;번들 최적화 후&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnnaia/dJMcaiiaA6e/KyOOzpJTnQ5OiaK9Ve7Ry0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdnnaia%2FdJMcaiiaA6e%2FKyOOzpJTnQ5OiaK9Ve7Ry0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;322&quot; height=&quot;93&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;136&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;번들 최적화 후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트리셰이킹 시, 번들러(webpack)는 사용되지 않는 코드를 어떻게 판단하는가?&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;먼저 코드 예시를 보자&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 트리셰이킹은 &lt;b&gt;정적 분석을 기반으로 작동&lt;/b&gt;한다 말했다.&lt;br /&gt;이는 import와 export 관계를 분석하여 사용 여부를 판단하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;math.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b; 
}

export function multiply(a, b) {
  return a * b; 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;index.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import { add } from './math.js'; // add만 사용

console.log(add(1, 2));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;번들링 결과 -&lt;/b&gt; 사용하지 않는 코드는 번들에 포함되지 않는다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// subtract, multiply는 제거됨
function add(a, b) {
  return a + b;
}

console.log(add(1, 2));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정적 분석의 동작 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 코드 예시를 보았다. 실제로 정적분석이 어떻게 진행되는 지 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번들러가 파일을 읽으면 코드를 AST(추상 구문 트리, Abstract Syntax Tree)로 파싱한다.&lt;br /&gt;AST는 코드를 실행하지 않고 구조적으로 표현한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원본 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;export function add(a, b) {
  return a + b;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추상 구문 트리&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;type&quot;: &quot;ExportNamedDeclaration&quot;,
  &quot;declaration&quot;: {
    &quot;type&quot;: &quot;FunctionDeclaration&quot;,
    &quot;id&quot;: { &quot;name&quot;: &quot;add&quot; }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번들러는 이 AST를 보고 &amp;ldquo;이 파일은 add라는 이름을 export한다&quot; 는 걸 실행 없이 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a style=&quot;color: #000000;&quot; contenteditable=&quot;false&quot; href=&quot;https://astexplorer.net/&quot; data-token-index=&quot;1&quot;&gt;&lt;span&gt;&lt;span&gt;AST explorer&lt;/span&gt;&lt;/span&gt;&lt;/a&gt; 에서 직접 파싱해서 확인해볼 수 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;번들러의 실제 분석 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모든 모듈(파일)을 AST로 파싱한다.&lt;/li&gt;
&lt;li&gt;각 모듈이 어떤 값을 export 하는지 &lt;code&gt;ExportNamedDeclaration&lt;/code&gt; 를 수집한다.&lt;/li&gt;
&lt;li&gt;다른 모듈에서 어떤 export를 import하는지 &lt;code&gt;ImportDeclaration&lt;/code&gt; 를 수집한다.&lt;/li&gt;
&lt;li&gt;export 목록과 실제 사용된 import를 대조한다.&lt;/li&gt;
&lt;li&gt;사용되지 않는 export는 제거 대상으로 표시된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트리셰이킹 &amp;mdash; usedExports, sideEffects 각각 뭐가 다른가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webpack은 트리셰이킹을 위해 여러 최적화 옵션을 제공하는데, 그 중 핵심적인 두 가지가 &lt;code&gt;usedExports&lt;/code&gt;와 &lt;code&gt;sideEffects&lt;/code&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 &lt;code&gt;usedExports&lt;/code&gt;다. 이는 코드 사용 여부를 분석해 표시하는 역할을 하고, 실제 코드 제거는 Terser 같은 minifier 단계에서 이루어진다. 앞서 살펴본 정적 분석(AST)이 바로 이 과정에서 쓰인다. 보통 트리셰이킹은 이걸로 더 잘 알려져있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 &lt;code&gt;sideEffects&lt;/code&gt;다. &lt;code&gt;usedExports&lt;/code&gt;와 달리 코드를 분석하는 게 아니라, &lt;code&gt;package.json&lt;/code&gt;에 개발자가 직접 선언한 힌트를 읽어 파일 자체를 번들에 넣을지 말지를 판단한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 74.3024%; height: 113px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.0871%;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 20.332%;&quot;&gt;판단 대상&lt;/td&gt;
&lt;td style=&quot;width: 48.8243%;&quot;&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.0871%;&quot;&gt;usedExports&lt;/td&gt;
&lt;td style=&quot;width: 20.332%;&quot;&gt;export 단위&lt;/td&gt;
&lt;td style=&quot;width: 48.8243%;&quot;&gt;어떤 export가 사용되는지 분석해 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.0871%;&quot;&gt;sideEffects&lt;/td&gt;
&lt;td style=&quot;width: 20.332%;&quot;&gt;모듈(파일) 전체&lt;/td&gt;
&lt;td style=&quot;width: 48.8243%;&quot;&gt;사용되지 않은 모듈을 통째로 제거해도 안전한지 판단&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제는 &lt;code&gt;usedExports&lt;/code&gt;(구문 단위)가 아니라 &lt;code&gt;sideEffects&lt;/code&gt;(파일 단위)가 막혀있던 것이었다. lucide-react는 아이콘이 파일별로 분리되어 있어서, &lt;code&gt;sideEffects: false&lt;/code&gt;가 제대로 작동했다면 사용하지 않는 아이콘 파일 자체가 통째로 제거됐을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 1차 해결하고 다른 태스크로 넘어갔지만, 찝찝함을 안고 다시 돌아간 덕분에 더 올바른 설정과 함께 번들 크기를 더 줄일 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름이 똑같은 옵션 하나 때문에 번들크기가 반절 이상 커졌었다. 이 경험으로 이 옵션이 누구를 위한 설정인지, 그리고 webpack이 이 정보를 어떻게 활용하는지 이해하는 계기가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 링크&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://webpack.js.org/guides/tree-shaking/&quot;&gt;https://webpack.js.org/guides/tree-shaking/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://webpack.js.org/guides/tree-shaking/#clarifying-tree-shaking-and-sideeffects&quot;&gt;https://webpack.js.org/guides/tree-shaking/#clarifying-tree-shaking-and-sideeffects&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://webpack.js.org/configuration/optimization/#optimizationusedexports&quot;&gt;https://webpack.js.org/configuration/optimization/#optimizationusedexports&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://web.dev/articles/reduce-javascript-payloads-with-tree-shaking?hl=ko&quot;&gt;https://web.dev/articles/reduce-javascript-payloads-with-tree-shaking?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://frontend-fundamentals.com/bundling/deep-dive/optimization/tree-shaking.html&quot;&gt;https://frontend-fundamentals.com/bundling/deep-dive/optimization/tree-shaking.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;color: #141413; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;main-content&quot;&gt;
&lt;div&gt;
&lt;div aria-hidden=&quot;false&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #faf9f5;&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p style=&quot;background-color: #ffffff;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #666666;&quot;&gt;내용 중 부정확한 부분이 있거나 궁금한 점이 있다면 댓글 달아주시면 감사하겠습니다 :)&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>문제 해결 &amp;amp; 구현 기록</category>
      <category>Optimization</category>
      <category>SideEffects</category>
      <category>treeshaking</category>
      <category>webpack</category>
      <category>번들러</category>
      <category>웹팩</category>
      <category>정적분석</category>
      <category>트리셰이킹</category>
      <author>sangchu</author>
      <guid isPermaLink="true">https://sanghee01.tistory.com/200</guid>
      <comments>https://sanghee01.tistory.com/200#entry200comment</comments>
      <pubDate>Fri, 6 Mar 2026 16:53:04 +0900</pubDate>
    </item>
    <item>
      <title>이미지 로딩 속도 개선기 (webp, squoosh)</title>
      <link>https://sanghee01.tistory.com/199</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;도입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모멘트 프로젝트에서 페이지에 사용되고 있는 정적 이미지(레벨, 아이콘 등)의 로딩 속도가 느린 것을 확인하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 단순히 네트워크 속도가 느리거나 기기 사양이 낮아서 로드 속도가 낮은 것이라고 생각했었는데, 웹 성능 최적화에 대해 학습한 이후 관점이 달라졌다. 단순 사용자의 기기 문제라고 끝낼게 아니라, 개발자가 충분히 개선할 수 있는 영역이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 이미지 로딩 속도를 어떻게 개선했는지 공유하고자 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말했듯 모멘트 프로젝트에 쓰이는 아이콘들의 로딩 속도가 느린 문제가 있었다. &lt;br /&gt;특히 저사양 기기일수록 이미지가 로드되는 속도는 더 느렸다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Adobe_Express_-_화면_기록_2025-09-23_21.50.36_(2).gif&quot; data-origin-width=&quot;2846&quot; data-origin-height=&quot;1838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nwmHS/dJMcaa4Xzry/0CLYVnyovGFPVL4m3AME41/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nwmHS/dJMcaa4Xzry/0CLYVnyovGFPVL4m3AME41/img.gif&quot; data-alt=&quot;정적 이미지(아이콘)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nwmHS/dJMcaa4Xzry/0CLYVnyovGFPVL4m3AME41/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/nwmHS/dJMcaa4Xzry/0CLYVnyovGFPVL4m3AME41/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;324&quot; data-filename=&quot;Adobe_Express_-_화면_기록_2025-09-23_21.50.36_(2).gif&quot; data-origin-width=&quot;2846&quot; data-origin-height=&quot;1838&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정적 이미지(아이콘)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Lighthouse 측정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview?hl=ko&quot;&gt;Lighthouse&lt;/a&gt; 측정을 해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lighthouse는 Chrome 개발자 도구에 내장된 성능 측정 도구로, 웹페이지의 로딩 속도, 접근성, SEO 등을 분석해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;랜딩 페이지를 분석해보니 Performance 점수가 76점으로 개선이 필요한 수준이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(일반적으로 90점 이상이면 양호한 편이며, 디바이스 성능에 따라 점수가 달라질 수 있다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 5가지의 측정 항목이 있는데 그 중에서도 LCP(Largest Contentful Paint)의 점수가 상당히 안좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LCP는 페이지의 주요 콘텐츠가 화면에 표시되는 시간을 측정하는 지표다. 이미지가 클수록 LCP가 길어져 체감 속도가 느려진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;1273&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyWJlp/dJMcabv2iv3/UKihjou2pHTWcCV0uQWg80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyWJlp/dJMcabv2iv3/UKihjou2pHTWcCV0uQWg80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyWJlp/dJMcabv2iv3/UKihjou2pHTWcCV0uQWg80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyWJlp%2FdJMcabv2iv3%2FUKihjou2pHTWcCV0uQWg80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;756&quot; height=&quot;614&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;1273&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내려보면 어느 부분에서 점수가 안좋게 나오는건지 리포트를 볼 수 있다. 대부분 images에 대한 내용이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1708&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LxKrS/dJMcaajAvrO/gbOVHfdEiG1OkPE80qzhZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LxKrS/dJMcaajAvrO/gbOVHfdEiG1OkPE80qzhZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LxKrS/dJMcaajAvrO/gbOVHfdEiG1OkPE80qzhZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLxKrS%2FdJMcaajAvrO%2FgbOVHfdEiG1OkPE80qzhZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;832&quot; height=&quot;210&quot; data-origin-width=&quot;1708&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽 토글을 누르면 더 세부적으로 볼 수 있는데, 해당 부분은 캡쳐하지 못해서, 다른 예시 사진으로 보면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 이미지 크기가 너무 커서 웹 성능에 영향이 갔다는 것을 1차적으로 확인 할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;1286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBsqYc/dJMcagqzvNK/AM8AHEDdfXQ089B62KkKwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBsqYc/dJMcagqzvNK/AM8AHEDdfXQ089B62KkKwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBsqYc/dJMcagqzvNK/AM8AHEDdfXQ089B62KkKwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBsqYc%2FdJMcagqzvNK%2FAM8AHEDdfXQ089B62KkKwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;510&quot; height=&quot;701&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;1286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 네트워크 탭 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 네트워크 탭에서 img 요청을 확인해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;waterfall이라는 부분이 보이는데, Content download하는데 1.76s 걸리는걸 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LU0El/dJMcaihC8rX/zzWxMMkAgI2UeUl8ZzkpVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LU0El/dJMcaihC8rX/zzWxMMkAgI2UeUl8ZzkpVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LU0El/dJMcaihC8rX/zzWxMMkAgI2UeUl8ZzkpVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLU0El%2FdJMcaihC8rX%2FzzWxMMkAgI2UeUl8ZzkpVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;475&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;475&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 행을 클릭해 자세히 봤는데, 이미지 사이즈가 2048x2048 였고, 용량도 2.3MB로 컸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 해당 이미지는 실제 웹 페이지에서는 40x40로 그려지는 이미지였다. 그에 비해 이미지 사이즈가 불필요하게 큰 상황이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;1210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5jMKq/dJMcaiu7jfQ/XBMCmmTqsdwFfhCMpeOVY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5jMKq/dJMcaiu7jfQ/XBMCmmTqsdwFfhCMpeOVY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5jMKq/dJMcaiu7jfQ/XBMCmmTqsdwFfhCMpeOVY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5jMKq%2FdJMcaiu7jfQ%2FXBMCmmTqsdwFfhCMpeOVY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;257&quot; height=&quot;266&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;1210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 Lighthouse와 네트워크 탭을 통해, 이미지 리소스가 웹 성능에 가장 큰 영향을 주고 있다는 것을 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 따라 &lt;b&gt;정적 이미지 최적화&lt;/b&gt;를 진행했다. 정적 이미지는 개수가 많지 않았고, 당장 적용해야 했기에&amp;nbsp;&lt;a href=&quot;https://squoosh.app/&quot;&gt;Squoosh 사이트&lt;/a&gt;를 통해 직접 최적화 작업을 수행했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 아래의 세 가지를 적용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 이미지 확장자 webp로 변환&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이미지 확장자 수정 계기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 정적 이미지들은 대부분 &lt;b&gt;PNG 포맷&lt;/b&gt;으로 관리되고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PNG는 무손실 포맷이기 때문에 품질은 뛰어나지만, 그만큼 &lt;b&gt;파일 용량이 크다&lt;/b&gt;는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lighthouse 리포트에서도 &amp;ldquo;Serve images in modern formats&amp;rdquo; 경고가 있었고, 이는 이미지 포맷이 성능 문제와 직접적으로 연결되어 있음을 의미했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이미지 확장자 종류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹에서 주로 사용하는 이미지 포맷은 다음과 같다. 우리는 이 중 &lt;b&gt;WebP 확장자&lt;/b&gt;를 선택했다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 91px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt;확장자&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;용량 효율&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;특징&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;주요 사용처&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;JPEG (.jpg)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;중간&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;손실 압축&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;사진, 배경 이미지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;PNG (.png)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;무손실 압축&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;로고, 아이콘, UI 이미지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;WebP (.webp)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;손실 / 무손실 압축 선택 가능&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;사진, 아이콘, 썸네일, 애니메이션&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;webp로 선택한 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebP를 선택한 이유는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 품질 기준에서 JPEG, PNG보다 더 &lt;b&gt;작은 용량&lt;/b&gt;을 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;손실 / 무손실 압축을 모두 지원&lt;/b&gt;해 이미지 특성에 따라 압축 방식을 선택할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 특성 덕분에 이미지 성격에 따라 JPEG, PNG, GIF 등으로 포맷을 나누어 관리할 필요 없이, 하나의 확장자를 기준으로 일관된 이미지 최적화 전략을 적용할 수 있다.&lt;br /&gt;&lt;br /&gt;즉, 가장 높은 압축 효율을 기본값으로 유지하면서도 손실 여부를 옵션으로 조절할 수 있어, 관리&amp;nbsp;복잡도를&amp;nbsp;줄이면서&amp;nbsp;웹&amp;nbsp;성능&amp;nbsp;개선을&amp;nbsp;동시에&amp;nbsp;달성할&amp;nbsp;수&amp;nbsp;있다고&amp;nbsp;판단해&amp;nbsp;WebP를&amp;nbsp;선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 WebP는 Chrome, Edge, Firefox, Safari 등 대부분의 최신 브라우저에서 이미 지원되고 있어, 실서비스에서 사용하기에 충분한 호환성을 갖추고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구형 브라우저 고려&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 일부 구형 브라우저에서는 WebP를 지원하지 않는 경우가 있기 때문에, 이를 고려한 fallback 전략이 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 아래 코드와 같이 &amp;lt;picture&amp;gt; 태그를 사용해 &lt;b&gt;WebP를 우선 제공하고, 미지원 브라우저에서는 PNG를 fallback으로 제공&lt;/b&gt;하도록 구현했다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;picture&amp;gt;
  &amp;lt;source srcset=&quot;/images/example.webp&quot; type=&quot;image/webp&quot; /&amp;gt;
  &amp;lt;img src=&quot;/images/example.png&quot; alt=&quot;example image&quot; /&amp;gt;
&amp;lt;/picture&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 다음과 같이 동작한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;브라우저 호환성:&lt;/b&gt; WebP를 지원하지 않는 브라우저에서는 자동으로 PNG를 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 최적화:&lt;/b&gt; WebP를 지원하는 브라우저에서는 높은 압축률의 WebP를 그대로 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 &lt;b&gt;구형 브라우저 대응과 최신 브라우저 성능 최적화를 동시에 만족&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 이미지 사이즈 resize&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 포맷을 WebP로 변경하더라도, &lt;b&gt;이미지 해상도가 실제 사용 크기에 비해 지나치게 크다면&lt;/b&gt; 성능 문제는 여전히 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 네트워크 탭을 통해 확인한 이미지 중에는 다음과 같은 사례가 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 화면 렌더링 크기: &lt;b&gt;40 &amp;times; 40&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;원본 이미지 크기: &lt;b&gt;2048 &amp;times; 2048&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 브라우저는 실제 화면에 필요한 크기보다 훨씬 큰 원본 이미지를 먼저 모두 다운로드한 뒤, 이를 화면 크기에 맞게 축소해서 렌더링하고 있었다. 이 문제를 해결하기 위해, &lt;b&gt;이미지를 실제 사용 크기에 맞게 resize&lt;/b&gt;를 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 이미지 압축&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 포맷을 WebP로 변경하고, 해상도를 실제 사용 크기에 맞게 resize하는 것만으로도 상당한 개선 효과를 얻을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 같은 해상도의 이미지라도 &lt;b&gt;압축 방식과 압축 강도에 따라 파일 용량은 더 줄일 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 압축에는 크게 &lt;b&gt;손실 압축과 무손실 압축&lt;/b&gt;이 있으며, 이 중 손실 압축은 이미지 정보를 일부 제거하는 방식이다. 이 방식은 사진처럼 색 변화가 자연스럽고 세부 디테일이 많은 이미지에서는 큰 용량 절감 효과를 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 아이콘이나 로고처럼 경계가 또렷하고 픽셀 하나하나가 중요한 이미지에 손실 압축을 적용하면, 경계가 흐려지거나 미세한 블러가 발생해 시각적인 품질 저하로 이어질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;압축을 무조건 적용하는 것이 아니라&lt;/b&gt;, 이미지의 용도와 시각적 특성을 기준으로 손실 여부를 구분하는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이미지의 성격에 따라 압축 기준을 다음과 같이 나누어 적용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;사용자 갤러리에서 업로드한 사진 이미지&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;손실 압축 적용&lt;/li&gt;
&lt;li&gt;용량 감소 효과가 크고, 사용자가 품질 저하를 인지하기 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;아이콘, 로고, UI 이미지&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무손실 압축 적용&lt;/li&gt;
&lt;li&gt;이미지 정보를 유지해 선명도를 보존&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 이미지 최적화 이후, 네트워크 요청 기준으로 가장 큰 변화를 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 파일 사이즈를 비교해보면 단일 이미지 기준으로 &lt;b&gt;약 800배 이상 용량이 감소&lt;/b&gt;했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;2059 KB &amp;rarr; 2.5 KB&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;973&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7JzMr/dJMcabv2iDK/QxfMTNWw7mLHIQXZwmQZn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7JzMr/dJMcabv2iDK/QxfMTNWw7mLHIQXZwmQZn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7JzMr/dJMcabv2iDK/QxfMTNWw7mLHIQXZwmQZn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7JzMr%2FdJMcabv2iDK%2FQxfMTNWw7mLHIQXZwmQZn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;845&quot; height=&quot;401&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;973&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 네트워크 탭에서 확인한 &lt;b&gt;Content Download 시간&lt;/b&gt; 역시 크게 개선되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;1.76s (1760ms) &amp;rarr; 2.62ms&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 이미지 한 장을 다운로드하는 데에만 1초 이상이 소요되었지만, 최적화 이후에는 0.0026초로 줄어든 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;469&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xBAWG/dJMcacoaXAu/VI4KHZcgpKIPjW4VII62sK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xBAWG/dJMcacoaXAu/VI4KHZcgpKIPjW4VII62sK/img.png&quot; data-alt=&quot;최적화 이전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xBAWG/dJMcacoaXAu/VI4KHZcgpKIPjW4VII62sK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxBAWG%2FdJMcacoaXAu%2FVI4KHZcgpKIPjW4VII62sK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;469&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;469&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최적화 이전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddFtsK/dJMcaaKE1QG/5F19DW0UKt7J5ogf4SEEK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddFtsK/dJMcaaKE1QG/5F19DW0UKt7J5ogf4SEEK1/img.png&quot; data-alt=&quot;최적화 후&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddFtsK/dJMcaaKE1QG/5F19DW0UKt7J5ogf4SEEK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddFtsK%2FdJMcaaKE1QG%2F5F19DW0UKt7J5ogf4SEEK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;485&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최적화 후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 변화는 단순히 수치상의 개선에 그치지 않았다. 실제로 아래 화면에서 볼 수 있듯이, 이전 대비 이미지가 로드되는 속도가 많이 개선되었음을 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면_기록_2025-12-15_10.51.43.gif&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kECRa/dJMcabW6piw/dAL488FzMw5HV8VT5c4WKK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kECRa/dJMcabW6piw/dAL488FzMw5HV8VT5c4WKK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kECRa/dJMcabW6piw/dAL488FzMw5HV8VT5c4WKK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/kECRa/dJMcabW6piw/dAL488FzMw5HV8VT5c4WKK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;458&quot; data-filename=&quot;화면_기록_2025-12-15_10.51.43.gif&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업을 통해 가장 크게 느낀 점은, 이미지 성능 문제는 &lt;b&gt;측정 가능하고 해결 가능한 문제&lt;/b&gt;라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글로 이미지 로딩 속도를 개선하고자 하는 사람들에게 도움이 되었으면 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sanghee01.tistory.com/201&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 글&lt;/a&gt;에서는 사용자가 업로드한 이미지와 같이, 빌드타임이 아닌 런타임에 업로드된 이미지는 어떻게 최적화 할 수 있는지 소개하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1774277536459&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;이미지 최적화 파이프라인 구축기 (1) - 문제 진단과 방법 선택&quot; data-og-description=&quot;들어가며이전에 정적 이미지 최적화 방식을 다룬 글을 작성한 적 있다. 이번에는 사용자가 업로드한 이미지를 최적화한 경험을 정리해보려 한다.이번 글에서는 LCP 7.6초라는 수치를 발견하고, &quot; data-og-host=&quot;sanghee01.tistory.com&quot; data-og-source-url=&quot;https://sanghee01.tistory.com/201&quot; data-og-url=&quot;https://sanghee01.tistory.com/201&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4rOPC/dJMb84XYa2e/ibpQl0W5pbyTBmMAfBCHLK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ttUgG/dJMb81fR3qA/7tuRbWkFM50HkrasmuRWR1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/tUxSa/dJMb86O1euU/GmVNDLxW66393N2unm8U31/img.png?width=497&amp;amp;height=934&amp;amp;face=0_0_497_934&quot;&gt;&lt;a href=&quot;https://sanghee01.tistory.com/201&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sanghee01.tistory.com/201&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4rOPC/dJMb84XYa2e/ibpQl0W5pbyTBmMAfBCHLK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ttUgG/dJMb81fR3qA/7tuRbWkFM50HkrasmuRWR1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/tUxSa/dJMb86O1euU/GmVNDLxW66393N2unm8U31/img.png?width=497&amp;amp;height=934&amp;amp;face=0_0_497_934');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;이미지 최적화 파이프라인 구축기 (1) - 문제 진단과 방법 선택&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며이전에 정적 이미지 최적화 방식을 다룬 글을 작성한 적 있다. 이번에는 사용자가 업로드한 이미지를 최적화한 경험을 정리해보려 한다.이번 글에서는 LCP 7.6초라는 수치를 발견하고,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sanghee01.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>문제 해결 &amp;amp; 구현 기록</category>
      <category>lighthouse</category>
      <category>Squoosh</category>
      <category>webp</category>
      <category>이미지</category>
      <category>최적화</category>
      <author>sangchu</author>
      <guid isPermaLink="true">https://sanghee01.tistory.com/199</guid>
      <comments>https://sanghee01.tistory.com/199#entry199comment</comments>
      <pubDate>Wed, 24 Dec 2025 14:57:01 +0900</pubDate>
    </item>
    <item>
      <title>우선순위 쪼개고 내려놓기, Connecting the dots - 우아한테크코스 레벨1, 방학 회고</title>
      <link>https://sanghee01.tistory.com/198</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;6주차 이후, 2주간 이야기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sanghee01.tistory.com/197&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;6주차까지의 회고&lt;/a&gt; 이후부터 7, 8주차, 방학까지의 이야기를 해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;우선순위 쪼개고 쪼개기, 그리고 내려놓기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6주차 이후, 거의 건강한 마인드로 미션에 임할 수 있었다. 당시, 다들 영화리뷰 미션 step2를 시작할 때, 나는 점뭐먹 미션 step2를 리팩토링도 아닌, 기본 기능 구현을 할 정도로 엄청 밀려있었다. 하지만 &lt;b&gt;다른 크루들 속도에 신경쓰지 않고 내 위치에서 최선을 다했다.&lt;/b&gt; 그래서 점뭐먹 미션, 영화 리뷰 미션은 정말 즐겁게 했던 것 같다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그때부터 스스로 고민을 하면서 구현하기 시작해서 그 시기에 많이 성장한 것 같다. 구현하면서 고민했던 내용과 겪은 문제 상황도 노션에 기록하며 진행했어서 내가 당시 뭘 고민했는지 더 잘 기억할 수 있었다. 또한, PR 본문에 리뷰어에게 내가 어떤 점을 고민했는지, 혹은 왜 그렇게 구현했는지에 대한 질문에 대해 답변을 작성할 때 이전에 비하면 술술 작성할 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;물론 아쉬웠던 점도 정말 한가득이다. 아무래도, 미션 일정이 거의 1~2주치가 밀려있는 상태에서 진행하다보니 어쩔 수 없이 내려 놓아야할 건 내려놓을 수 밖에 없었다. 이것은 나에겐 너무 힘든 일이었다.&lt;br /&gt;처음엔 우선순위는 정했으나 그저 정하기만 했을 뿐이다. 당시 내가 생각했던 &amp;lsquo;우선순위 정하기&amp;rsquo;의 정의는&lt;b&gt; &amp;lsquo;어차피 다 해야하는 것이고, 그냥 일의 순서를 정하는 것&amp;rsquo;&lt;/b&gt;이라고 생각했기 때문이다.&lt;br /&gt;하지만 시간은 한정되어있고, 방학식에 다가올수록 압박감과 조급함이 커졌다. 아무튼 방학식 전까지는 미션이 다 Merge 돼야하고, 마지막 주간은 레벨 인터뷰, 글쓰기 미션 일정도 있어서 더욱 시간이 없었기 때문이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;당시 크루들과 코치에게 공통적으로 조언을 받은 내용이 있다.&lt;br /&gt;&lt;b&gt;&amp;lsquo;우선순위 쪼개고 쪼개기, 그리고 내려놓기&amp;rsquo;&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;원래는 &amp;lsquo;우선순위를 두고 중요한거 위주로 먼저 하라&amp;rsquo;는 조언이었는데, 처음엔 잘 와닿지 않았다. 나에게 &lt;b&gt;우선순위 높은 일 자체가 한가득&lt;/b&gt;이었기 때문이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그 말을 하니 한 크루가 &amp;ldquo;그럼 그 우선순위를 더 쪼개고 쪼개고, 내려놓아야하는건 &lt;b&gt;어쩔수 없이 내려놓는건 어때?&lt;/b&gt;&amp;rdquo;라는 말을 해줬다.&lt;br /&gt;이 &amp;lsquo;&lt;b&gt;내려놓기&lt;/b&gt;&amp;rsquo;는 생각치 못했다. &amp;lsquo;그래도 되나? 아무튼 요구사항인데?&amp;rsquo; 하지만 말을 들어보니 당시 나의 상황에서는 내려놓는게 최선이었다. 그래서 정말 우선순위를 쪼개고 또 쪼갰다.&lt;br /&gt;그리고는 내가 정말 챙겨가고 싶었던 부분 위주로 고민했고, 결과물도 나름 만족스럽게 제출할 수 있었고, 그부분에 대하여 리뷰어들에게 긍정적인 평을 받았다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만, 정말 내가 정말 고민하고 싶었던 내용 위주로만 챙길 수 있었고, 그 외의 지식의 구멍들은 챙길 수 없었다는 점이 아쉽다.&lt;br /&gt;예를들어, 영화리뷰 미션에서는 비동기가 미션 주요 학습 포인트였는데, step1 페어프로그래밍때 페어가 비동기에 대해 잘 알고있어서, 큰 문제 없이 진행할 수 있었고, step2때 혼자 추가 기능 구현하며 겪은 비동기 문제는 조금의 고민으로도 해결할 수 있었다.&lt;br /&gt;하지만 리팩토링으로 기능 분리하는 과정에서 비동기 관련해서 많이 생각해봐야할 문제를 겪었지만, 굳이 분리를 안해도 되는 부분이어서 우선순위상으로 하지 않았고, 그럼에 따라 해당부분에 대해 깊게 고민하지 못했다. 그래서 지금 비동기에 대해 질문하면 정말 간단하게만 대답할 수 있을 것 같아 아쉽다.&lt;br /&gt;또한, TypeScript하고 E2E 테스트도 정말 간단한 부분만 적용했다. 내 코드 구조상 TypeScript를 모든 도메인 코드에 적용하기엔 &amp;lsquo;제네릭&amp;rsquo;이라는 문법을 알아야했고, 당장 GPT랑 티키타카하면서라도 하기에는 너무 정말 시간이 없었다. 시도는 했으나 이해할 시간이 너무 없었다. &amp;lsquo;그냥 그거 너가 짜줘&amp;rsquo; 식으로 갈 것 같고 그러긴 싫어서 과감히 버렸다. E2E도 비슷한 이유로 간단한 부분만 적용했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이렇게 놓친 학습 포인트, 미션하면서 발견한 구멍난 지식 부분이 있었다. 근데 그냥 어쩔수 없이 우선순위 높은거 위주로 진행했고, 고민하지 못한 부분은 학습 리스트에 적어두었다.&lt;br /&gt;아쉬운점도 많았지만, 이 과정에서 &amp;lsquo;우선순위 쪼개고 쪼개기&amp;rsquo; 역량을 기를 수 있었고, 결과물도 나름 만족스러운 편이다.&lt;br /&gt;구멍난 지식들은 레벨 2, 3, 4에서도 채울 수 있을거라고 생각한다. &lt;b&gt;중요한 개념들은 또 마주칠 것이기에&lt;/b&gt;.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나의 성장 그래프 그리기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레벨1 방학식 날, &amp;lsquo;나의 성장 그래프 그리기&amp;rsquo; 활동을 기반으로 레벨 1을 회고하는 시간을 가졌다.&lt;br /&gt;이는 각자 레벨1 기간인 2개월 동안 기억에 남는 순간들과 그때 감정들을 타임라인별로 성장 척도 기반으로 그리는 것이었다. 40분동안 각자 성장 그래프를 그리는 시간을 가지고, 또 40분동안 조원들과 공유하는 시간을 가졌다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아래는 내가 그린 나의 성장 그래프다. 노란색 포스트잇이 기술적 성장, 빨간색이 소프트스킬적 성장이다.&lt;br /&gt;왼쪽 상단에 나열되어있는 포스트잇은 조원들이 작성한 것으로, 내 경험을 들으면서 느낀 키워드들이고, 색깔은 아무 연관이 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CNdGE/btsNlnthr6a/M2OTLFIvYFkgGAyYDn79XK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CNdGE/btsNlnthr6a/M2OTLFIvYFkgGAyYDn79XK/img.png&quot; data-alt=&quot;나의 레벨 1 성장 그래프&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CNdGE/btsNlnthr6a/M2OTLFIvYFkgGAyYDn79XK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCNdGE%2FbtsNlnthr6a%2FM2OTLFIvYFkgGAyYDn79XK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;680&quot; height=&quot;471&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;850&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;나의 레벨 1 성장 그래프&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래프 내용에서, 미션1, 2의 맥락은 &lt;a href=&quot;https://sanghee01.tistory.com/197&quot; target=&quot;_self&quot;&gt;&lt;span&gt;6주차까지의 회고 글&lt;/span&gt;&lt;/a&gt;에서, 미션3, 4의 맥락은 앞서 &amp;lsquo;우선순위 쪼개기&amp;rsquo; 섹션에서 어느정도 언급해서 본론으로 넘어가겠다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Connecting the dots&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성장 그래프의 제목인 &amp;lsquo;상추의 Connecting the dots&amp;rsquo;는 조원들이 적어준 키워드에서 따왔다. 이는 스티브잡스가 말한 내용이라는데, 그 내용 풀이가 너무 와닿아서 제목으로 두었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;774&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d1eo26/btsNlAlByMQ/Uelz2xhYrj6ZudtY9nnwV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d1eo26/btsNlAlByMQ/Uelz2xhYrj6ZudtY9nnwV1/img.png&quot; data-alt=&quot;이미지 출처: https://brunch.co.kr/@ashlistar/119&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d1eo26/btsNlAlByMQ/Uelz2xhYrj6ZudtY9nnwV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd1eo26%2FbtsNlAlByMQ%2FUelz2xhYrj6ZudtY9nnwV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;675&quot; height=&quot;346&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;774&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처: https://brunch.co.kr/@ashlistar/119&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;앞서 나의 레벨 1 성장 그래프를 보면, 미션 4때 성장 점수가 갑자기 떨어지는데, 미션이 다 끝날때 쯤 갑자기 고민이 많아졌기 때문이다. 나는 우테코 레벨1 기간 동안 정말 많은 것을 학습한 것 같지만, 이상하게도 배운 것들이 마치 여기저기 흩어진 작은 점들처럼 느껴졌다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만 한 조원이 내 발표에 대한 감상 키워드로 스티브잡스가 한 말인 &amp;lsquo;&lt;b&gt;connecting the dots&lt;/b&gt;&amp;rsquo;라는 키워드를 줬다. 이는 내가 뭔갈 많이 학습했음에도 애매하게 흩어져 있다고 생각했는데, 알고보니 &lt;b&gt;이건 각각의 경험들이 하나의 점으로 연결되고 있는 과정&lt;/b&gt;이라는 맥락으로 얘기 해줬던 것 같다. 내 &lt;b&gt;현재 흩어져 있는 점들이 미래에 어떻게든 이어질 것이라는 믿음&lt;/b&gt;과 함께 말이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그 이야기를 듣고나서 내 그래프와 발표 내용을 다시 돌아보니, 미션 1, 2때 애매하게 이해했거나 이해 안됐던 부분이, 미션 3, 4가면서는 어느정도 해소되었음을 알 수 있었다. 또, 미션 3때 이해 안됐던 부분은 미션 4때 자연스럽게 깨달았던 부분도 있고 말이다.&lt;br /&gt;그래서 &amp;lsquo;Connecting the dots&amp;rsquo;라는 문장이 엄청 와닿았고, 이 내용을&lt;b&gt; 의식적으로 생각하고 있어야겠다&lt;/b&gt;는 생각이 들었다.&lt;br /&gt;이 이야기를 들은 코치 준은 &amp;ldquo;&lt;b&gt;그 점을 평범한 점이 아닌 별이 되도록, 상추만의 색깔이 담긴 별이 되도록 해주세요&lt;/b&gt;&amp;rdquo;라는 말을 덧붙여서 더 마음의 울림을 받았다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그 외에도 다른 조원이 준 코멘트 중에 &amp;ldquo;배운 것을 적용하고 싶지만 그러지 못할 때 좌절감을 느끼는 것 같다&amp;rdquo;는 말도 공감되었다. 은연 중에 생각했던건데, 다른 사람에게서 이 말을 들으니 이부분이 더 명확해졌다.&lt;b&gt; &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;lsquo;나는 배운걸 적용하고싶어하구나! 거기에서 동기를 얻구나!!&amp;rsquo; 나의 재미와 동기는 배운것을 적용하는 것.&lt;/b&gt;&lt;br /&gt;레벨2 때는 이를 집중적으로 실천하고자 한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;또한 &lt;b&gt;스스로를 잘 아는사람, 멘탈을 잘 지키는 사람, 극복에 대한 의지가 강한 사람&lt;/b&gt;이라는 키워드를 줬다. 내가 평소에 부족하다고 하는 역량을 높게 쳐줘서 놀라웠다. 이부분에 대해 좀 더 자신감을 가질 필요가 있는 것 같다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쉼과 자아성찰의 방학 기간&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;첫 1:1 커피챗 이야기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 미션이 다 끝날때 쯤 갑자기 고민이 많아졌다고 말했다. 이는 방학이 되고서도 지속되었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;레벨 1동안 크루들과 코치들, 선배들의 조언을 많이 받아왔고, 그 덕에 어느정도 불안감과 고민거리는 해소되었다. 하지만 내가 직접 그걸 경험하고 깨달은 게 아니어서 그런가? 어떻게 해야할진 알겠는데 아무튼 어떻게 실천해야할지, 어떤 마음가짐이어야할지 구체적이지 않아 마음가짐이 종종 흐려지곤 했다. 마치 앞서 말한 &amp;lsquo;우선순위 쪼개기&amp;rsquo; 섹션에서 말했듯이, 뭔말인지 알겠는데 구체적인 그 맥락과 액션을 알기 전까지는 어떻게 해야할 지 모르겠었던 것처럼 말이다.&lt;br /&gt;그래서 불안함 마음은 여전히 남아있었다. 뭔가 이대로 레벨2에 가면, 이 고민들로 또 발목 잡힐 것만 같았다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://sanghee01.tistory.com/197&quot; target=&quot;_self&quot;&gt;&lt;span&gt;6주차까지의 회고 글&lt;/span&gt;&lt;/a&gt;에 작성했던 선배와의 수다타임에서, 나에게 큰 동기부여와 영감을 주셨던 선배가 생각났다. 프론트엔드로 발표자를 맡았던 선배였다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;(작성 중 ... )&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;글쓰기와 말하기 역량에 대하여&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 평소에 글쓰기를 잘 못한다고 생각하고 있었다. 그래서 지금까지 내가 글을 대충, 영양가없이 작성했다고 생각했고, 주변 지인들이 내 블로그를 알게 되면서부터는 &lt;span style=&quot;color: #333333;&quot;&gt;더욱&lt;/span&gt;&amp;nbsp;움츠러들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 방학 동안 내가 작성한 블로그 글들을 처음부터 되돌아보니&lt;b&gt; 나만의 색깔&lt;/b&gt;이 보였다. 그건 바로 &amp;lsquo;&lt;b&gt;진솔함&lt;/b&gt;&amp;rsquo;인 것 같다. 지금까지 너무 기술적이지 않고 평범한 글만 썼다고 생각했는데, &lt;b&gt;색깔 관점에서 보니 내 글을 읽는게 재밌었다.&lt;/b&gt; 진심이 드러나서 더 그런 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;돌이켜보면, 애초에 내가 티스토리 블로그를 처음 만들었을 때도 이렇게 기록 목적으로 만들었던게 기억이난다. 그래서 블로그 이름도 '상추의 공간'으로 지었던 거고. 앞으로도, &lt;b&gt;남의 시선, 남의 글에 신경쓰지 않고 내가 쓰고 싶은 글을 작성하는게 스트레스도 안받고 즐겁게, 지속적으로 작성할 수 있을 것 같다.&lt;/b&gt;&amp;nbsp;&lt;br /&gt;그리고 &amp;lsquo;꾸준함&amp;rsquo;. 나는 그냥 내 블로그를 방치하고 있었다고 생각했는데, 멀리서 바라보니 꾸준히 무언가를 남기고 있었다. 이렇게 보니 1년에 1~2번이라도 글을 작성하는 것도 꾸준함이라는 생각이 든다. 어쨌든 &lt;b&gt;그 순간을 기록한 흔적은 시간이 지날수록 더욱 빛나는 자산&lt;/b&gt;이 되어있을테니까.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그리고 나는 평소 말하기도 잘 못한다고 생각했는데, 우테코에 들어오기 전의 나의 모습을 생각해보니, 그때에 비해서는 좀 더 구체적으로 풀어서, 내 생각을 담아 이야기를 하고 있다는게 느껴졌다. 교육기간 중에는 몰랐는데, 방학기간동안 마음의 여유가 있어서 그런가, 사람들과 대화를하는데 그걸 스스로도 느낄 수 있었다.&lt;br /&gt;하지만 아직 부족하다고 느낀다. 커피챗에서 얘기했던 것처럼, &lt;b&gt;레벨 2동안 스스로 대화하는 시간을 많이 가져 나의 주관을 찾고, 크루들에게도 내 생각과 의견을 자연스럽게 말할 수 있도록 의식적으로 노력하고자 한다.&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그 외&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외, 방학동안 일정에 치이느라 수업시간 외에는 거의 보지 못했던 LMS를 이제서야 복습하는 시간을 가졌다. 당시에는 수업을 들을 때 이해가 잘 안됐던 내용들이었는데, 지금와서 읽으니 내가 미션을 수행하면서 고민했던 내용들이 거의 담겨있어서 &amp;lsquo;헉 이런걸 배웠었다고?!&amp;rsquo; 라는 생각도 들었고 너무 잘 정리가 되어있어서 재밌게 읽을 수 있었다. 하지만 방학은 거의 쉼에 집중하느라 다 보진 못했다. 못본 LMS는 짬짬이 읽으면 좋을 것 같다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레벨 2도 많이 혼란을 겪으며 개념을 학습하고 미션을 수행해나갈 것 같다. 그 과정에서 또 무너지는 경험도 겪을 수 있고, 방황도 할 수 있을 것 같다.&lt;br /&gt;하지만, 레벨 1때처럼, 레벨 2가 끝났을 때 되돌아보면, 많이 성장해 있을 것 같다. 레벨 2도 많은 성장통을 겪으며 잘 극복해나가자.&lt;br /&gt;그리고 내 자신에 집중하면서 우테코를 즐겁게 몰입해보자.&amp;nbsp;&lt;/p&gt;</description>
      <category>회고 &amp;amp; 생각</category>
      <category>7기</category>
      <category>레벨1</category>
      <category>우아한테크코스</category>
      <category>우테코</category>
      <category>회고</category>
      <author>sangchu</author>
      <guid isPermaLink="true">https://sanghee01.tistory.com/198</guid>
      <comments>https://sanghee01.tistory.com/198#entry198comment</comments>
      <pubDate>Tue, 15 Apr 2025 02:07:19 +0900</pubDate>
    </item>
    <item>
      <title>혼란 속에서 용기 내기, 극복 하기 - 우아한테크코스 6주차까지의 회고</title>
      <link>https://sanghee01.tistory.com/197</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우테코가 시작된 지 어느덧 6주차가 되었다. 그동안 정말 많은 일이 있었다. 주간 회고로 경험을 정리하고 싶었지만, 우테코 커리큘럼에 치이고 혼란과 방황을 겪으며 여유를 갖기 어려웠다.&lt;br /&gt;하지만 최근, 우테코에 좀 더 집중할 수 있는 마음가짐을 갖게 되었고, 회고 스터디도 열게 되면서 이렇게 시간을 내어 처음으로 주간 회고를 작성하게 되었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이번 주는 용기를 많이 낸 한 주였다. 어쩌면 이 글을 쓰는 것도 큰 용기를 낸 행동일지도 모른다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5주차까지 있었던 일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6주차 회고에 앞서 5주차까지의 상황을 간략히 정리해보고자 한다.&lt;br /&gt;우테코의 5주는 정말 혼란스러웠다. &amp;lsquo;나만 우테코를 잘 못 따라가고 있나?&amp;rsquo;, &amp;lsquo;나 도대체 왜 합격된거지?&amp;rsquo;라는 생각이 자주 들었고, 그냥 우테코 크루들의 삶을 체험하러 온 이방인 같았다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;사실 우테코를 시작하면서 미션을 제대로 수행한 적이 없는 것 같다. 항상 코드 작성에 자신감이 없었고, 실제로 기능 구현도 제대로 하지 못해 스스로에게 부족함을 느꼈다.&lt;br /&gt;페어 프로그래밍에서도 내가 주도적으로 의견을 내는 일은 거의 없었고, 거의 페어의 의견대로 진행됐다. 왜냐면 의견을 내고 싶어도 어떻게 작성, 설계해야할 지 정말 막연한 마음이 들었기 때문이다. 그리고 페어가 내준 의견은 다 맞는말 같아서 그렇게 진행했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;저번주 주말에 점뭐먹(점심뭐먹지) 미션 step2를 진행하면서 이 감정은 극에 달했다.&lt;br /&gt;어째서인지 한 이슈때문에 내내 고통을 받았고, 결국 step2 기능 요구사항의 절반도 구현하지 못하고 제출을 했다. 그 과정에서 정말 고통스러웠다. 아무리 테코톡 발표 이슈가 있었더라도, 주말 내내 노력하고도 기본 기능조차 제대로 구현하지 못한 내 모습에 좌절했다. 계속 눈물이 나오고.. 근데 울 시간조차 아까워서 눈물이 나오려고하면 계속 뺨을 때리며 정신차리려고 노력했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;큰 벽을 마주한 기분이었다. '나는 더 이상 성장할 수 없는 사람인가?', '역시 나는 개발과 맞지 않는 사람인가?', '다른 준비하던 것을 했어야 하나?'라는 생각이 가득했다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나는 성장할 수 없는 사람일까?&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우테코에 들어오기 전부터, 어쩌면 고등학교 때부터 나는 학습 방법에 대해 늘 의구심을 가졌다.&lt;br /&gt;결과가 좋지 않거나 진전이 없다고 느낄 때마다 학습 방식을 바꿨다. 그래서인지 항상 학습 속도는 느렸고, 조금이라도 더 오래 책상 앞에 있으려 했던 것 같다. 하지만 그럼에도 계속 나 자신에 대한 의구심이 들었고, 이 때문에 집중력도 떨어져서 시간대비 학습한건 거의 없었던 것 같다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그래서 우테코에 너무 오고 싶었다. 나는 우테코에 지원한 이유가 더도말고 학습방법을 찾기 위해서였다. 하지만 여기서도 결국 학습하는 방법은 스스로 찾아야 했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;우테코에서도 계속 벽에 부딪히자 '나는 역시 성장할 수 없는 사람인가?'라는 생각이 들었다. 미션을 제대로 수행하지 못하면서, '이 상태로 가다간 우테코에서 얻을 수 있는 것의 30%밖에 못 얻는 건 아닐까?'라는 걱정이 들었다. 한번 뿐인 우테코에서 적어도 6~70%는 얻어가고 싶은데 말이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;점뭐먹 step2를 하며 이런 감정이 너무 커지자 이대로는 안 되겠다고 생각했다. 당장 이 상황을 극복하고 싶었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;용기 내기, 극복 방안 찾아가기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주는 현재 내 상황을 인정하고 용기를 내어 적극적으로 극복 방안을 찾으려 노력했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선배들에게 조언을 구하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수요일에는 '선배와의 수다 타임'이 있었다. 6기를 수료하신 선배님들이 우테코 생활을 어떻게 하면 잘 보낼 수 있을지에 대해 발표하는 자리였다. 지금 내 상황에 도움이 될 것 같아 참석했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이 자리에서 평소 하지 않던 행동을 했다. 질문 타임에 용기 내어 손을 들고 질문했고, 발표 후에도 선배들을 찾아가 질문했다. 내 상황을 말하는 것이 너무 두려웠고, 이런 자리에서 절대 먼저 질문을 못하는 성격이지만, 현재 내 상황을 극복하고 싶어서 용기를 냈다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이 시간동안 선배님들에게 좋은 조언을 많이 받았다. 간단하게 정리하자면 다음과 같다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;코치와 크루들을 괴롭혀라(질문을 많이하라)&lt;/b&gt;&lt;br /&gt;이 과정에서 '이 사람은 이렇게 생각하네? 그런데 나는 그렇게 생각하지 않아', '나는 이런 부분은 맞지 않다고 생각하는구나'와 같은 생각을 하면서 자신의 생각을 정리해 보라고 하셨다. &lt;br /&gt;또한 페어 프로그래밍을 할 때 궁금한 점이나 의문이 드는 부분을 모두 질문하고, 메모해두었다가 집에 가서 찾아보고 정리하면 좋다고 조언하셨다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;우테코 10개월동안 스스로랑 대화 많이해라&lt;/b&gt;&lt;br /&gt;스스로와의 대화를 통해 본인이 무엇을 모르는지, 무엇을 싫어하고 좋아하는지를 점차 알아가면서 자기 주관을 확립하는 것이 중요하다고 하셨다. 이 과정에서 크루들과 대화하는게 도움된다고 하셨다. 이렇게 입 밖으로 생각을 꺼내는것이 메타인지를 높이는 데 매우 좋다고 하셨다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;본인의 속도에 더 집중하자&lt;/b&gt;&lt;br /&gt;스터디는 하나에만 집중하고, 추가적인 참여는 정말 여유가 있을 때만 하라고 하셨다. 나와 같은 상황에서 매력적인 스터디가 많이 보이더라도 여러 곳에 참여하면 오히려 어느 것도 제대로 하지 못할 수 있다고 하셨다. 또한, 미션을 진행하면서 발견된 지식의 빈틈이나 기록한 내용을 집중적으로 학습하는 것이 더 도움될 것이라고 하셨다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;커피챗 많이하기&lt;/b&gt;&lt;br /&gt;커피챗도 적극적으로 하라고 하셨다. 지금처럼 막연하거나 궁금한 부분이 생겼을 때 리뷰어나 우테코 선배들과의 커피챗을 통해 조언을 얻으면 좋다고 권유하셨다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이러한 조언들을 통해 앞으로 어떻게 해나가야 할지 방향성을&amp;nbsp;조금 알게 된 것 같다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;크루들에게 조언을 구하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선배들과의 수다 타임 이후, 크루들에게도 적극적으로 조언을 구하기 시작했다. 이전에는 &amp;lsquo;결국 내가 해결하고 스스로 극복해야 할 일&amp;rsquo;이라고만 생각해서 잘 묻지 않았다. 또 내 상황을 솔직하게 말하는 것이 부끄럽고 쉽지 않아서 속으로만 고민했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만 이번엔 용기를 내어 내 상황을 설명하며 크루들에게 조언을 구했다. 처음엔 크루들의 시간을 뺏는 게 아닐까, 이상하게 보이지 않을까 걱정도 되었지만, 다들 진심을 담아 적극적으로 조언해주었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그리고 크루들에게 조언을 듣고 난 생각은, 항상 내가 생각하고 종종 말하던 &amp;lsquo;나는 경험이 별로 없다보니&amp;hellip;&amp;rsquo;, &amp;lsquo;많이 부족해서&amp;hellip;&amp;rsquo; 라는 말이 좀 웃기게 들리는 것 같다. 아직 주니어 개발자도 미치지 않는 우리는, 경험이 넓지 않은게 당연한건데, 나는 그냥 나 혼자 경험이 없다고 자책하고 있던 것이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;리뷰어가 한 말이 무슨 말인지 모르겠고, 기능을 어떻게 구현해야할지 모르겠으면, 구글링을 하든, 관련 부분을 책에서 찾아보든, 페어에게 물어보든, 다른 크루들에게 물어보든, gpt에게 물어보든 해야하는데. 그리고 그 과정에서 막연한 감정이 생겨도 그거에 대한 나의 생각을 정리해서 마찬가지로 해결안을 찾아나가면 되는건데, 단순히 &amp;lsquo;나는 부족해, 나는 감자야&amp;rsquo;라고만 생각하며 좌절하고 있었다.&lt;br /&gt;막연함은 그냥 디폴트고, 위 과정을 통해 열심히 헤집다보면 해결이 될것이고, 그 과정에서 나만의 주관이 생기지 않을까싶다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리뷰어에게 조언을 구하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에도 점뭐먹 리뷰어에게 슬랙 DM을 통해, 우테코 선배로서 현재 내 상태에서 어떤 마음가짐으로 미션을 진행하면 좋을지 조언을 구했었다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;리뷰어는 이러한 고민이 당연한 과정이라고 말씀해주셨다.&amp;nbsp; '어떤 코드가 좋은 코드지?', '이건 이렇게 해도 될까?', '내가 지금 이렇게 작성하는게 의미가 있을까?' 같은 질문은 본인도 수없이 해왔고, 현업에서도 그 고민의 결만 다를 뿐 여전히 같은 고민을 반복하고 있다고 하셨다. '&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #1d1c1d;&quot;&gt;어쩌면 우리는 보이지 않는 기준을 찾아 끝없는 탐험을 하고있다고 볼 수 있다&amp;lsquo;는 말에 조금은 위안이 되었다. 가장 중요한건 역시 기능이 동작되도록 만드는 것이라 하셨다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;또한, 부담을 덜고 천천히 가도 괜찮다고 하시며 우테코는 마치 마라톤과 같으니 스스로 페이스를 조절하라는 조언도 주셨다. 그리고 길이 보이지 않을 때는 지금처럼 주변 사람들에게 적극적으로 도움을 요청하라고 말씀하셨다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이 조언을 들은 시점이 5주차 주말때로, 점뭐먹 미션으로 혼란과 고통을 겪고 있던 때였다. 이 조언을 통해 당시에 그나마 마음을 다잡고 미션을 수행할 수 있었던 것 같다. 또한 이번주에 용기를 갖고 적극적으로 해결방안을 찾게 시작하게 된 첫 출발점이기도 하다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 상황에서 이번 주말에 막연해했던 점뭐먹 기능 구현을 시도하는데 생각보다 재밌게 진행할 수 있었다.&lt;br /&gt;&amp;lsquo;이 코드는 여기에 있으면 안 되는 것 같은데?&amp;rsquo;, &amp;lsquo;이 기능은 이 컴포넌트의 책임이 아닐까?&amp;rsquo; 등을 생각하며 개발했다..! 분명 저번주까지는 혼란속에서 마구잡이로 했었는데, 이번에 내가 생각을 하며 기능을 구현하고 있어서 놀라웠다.&lt;br /&gt;비록 생각보다 오래 걸려서 아직도 기본 기능 구현을 다 못했지만, 그럼에도 불구하고 조금씩 나아가고 있다는게 느껴진다. 그래서 이전에 비하면 무력하거나 좌절하는 기분은 크게 들지 않는다. 뭔가.. 할 수 있을 것 같다&amp;hellip;!!&lt;br /&gt;&amp;nbsp;&lt;br /&gt;받은 조언들을 잘 실천해보며, 다시 길을 잃을 때는 주저 없이 적극적으로 도움을 청하려 한다.&lt;br /&gt;또한 용기를 내어 만든 회고 스터디도 꾸준히 운영해보며, 매주 자신을 돌아보고 메타인지를 키워가고 싶다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;7주차도 잘 이겨내길 바라며.. 글을 마무리하겠다. 파이팅!&lt;/p&gt;</description>
      <category>회고 &amp;amp; 생각</category>
      <category>7기</category>
      <category>우아한테크코스</category>
      <category>우테코</category>
      <category>회고</category>
      <author>sangchu</author>
      <guid isPermaLink="true">https://sanghee01.tistory.com/197</guid>
      <comments>https://sanghee01.tistory.com/197#entry197comment</comments>
      <pubDate>Mon, 24 Mar 2025 11:56:18 +0900</pubDate>
    </item>
    <item>
      <title>Javascript Class에서 getter, setter는 무엇이고 어떻게 쓰는건가?</title>
      <link>https://sanghee01.tistory.com/195</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Class에 대해 공부하는 도중, getter와 setter가 왜 필요하고 어떻게 동작하는지 이해가 되지 않았다. 그래서 이번 기회에 정리해보고자 한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;getter&lt;/h2&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;객체의 값을 &lt;b&gt;가져올 때&lt;/b&gt;(읽을 때) 자동으로 호출되는 메서드이다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;get 키워드&lt;/b&gt;로 정의하며, 별도의 인자를 받지 않는다.&lt;/li&gt;&lt;li&gt;내부적으로는 함수지만, 외부에서 접근할 때는 obj.prop와 같이 프로퍼티처럼 보인다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;setter&lt;/h2&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;객체의 값을 &lt;b&gt;설정할 때&lt;/b&gt; 자동으로 호출되는 메서드이다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;set 키워드&lt;/b&gt;로 정의하며, &lt;b&gt;주로 하나의 인자&lt;/b&gt;를 받는다.&lt;/li&gt;&lt;li&gt;외부에서 obj.prop = 값 형태로 할당하면, 내부적으로 set prop(값)이 실행된다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;예시&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 Person 클래스가 있다. name 프로퍼티를 게터·세터로 관리해, 이름을 등록하거나(set) 가져온다(get).&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;class Person {
  constructor(name) {
    this._name = name;
  }

  // getter
  get name() {
    console.log('getter 호출');
    return this._name;
  }

  // setter
  set name(newName) {
    console.log('setter 호출');
    this._name = newName;
  }
}

const person = new Person('Alice');

// getter 작동
console.log(person.name);
// 콘솔 출력&amp;gt;
// getter 호출
// Alice

// setter 작동
person.name = 'Bob';
// 콘솔 출력&amp;gt;
// setter 호출

console.log(person.name);
// 콘솔 출력&amp;gt;
// getter 호출
// Bob

&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;person.name으로 값을 읽을 때, get name()이 자동으로 호출되어 this._name을 반환한다.&lt;br&gt;person.name = 'Bob'으로 값을 할당하면, set name(newName)이 실행되어 내부 _name 값을 업데이트한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;getter와 setter를 사용하는 이유&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 문법을 배울 때, 보통 생성자(constructor)와 메서드만 알아도 객체를 생성하고 사용하는 데 큰 어려움이 없어 보일 수 있다. 그래서 'getter와 setter를 꼭 써야 하나?'라는 의문이 들 수도 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;하지만 &lt;b&gt;캡슐화(encapsulation)&lt;/b&gt; 관점에서 보면, 객체 내부 속성을 직접 수정하는 것은 위험할 수 있다.&lt;br&gt;예를 들어, 나이가 음수가 되지 않도록 강제하거나, 이름이 특정 형식을 따라야 할 때, 게터·세터를 통해 검증 로직을 넣으면 &lt;b&gt;원치 않는 결과를 예방&lt;/b&gt;할 수 있다. 이는 객체 지향 프로그래밍(OOP)에서 중요한 개념이기도 하다.&lt;br&gt;&amp;nbsp;&lt;br&gt;아래 예시에서는 Person 클래스에 출생 연도를 저장해두고, 나이를 동적으로 계산해서 반환하는 게터가 있다. 하지만 세터가 없으므로 외부에서 나이를 임의로 바꾸는 것이 불가능하다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;class Person {
  constructor(name, birthYear) {
    this._name = name;
    this._birthYear = birthYear;
  }

  // 출생 연도를 이용해, 현재 나이를 계산해 반환
  get age() {
    const currentYear = new Date().getFullYear();
    return currentYear - this._birthYear;
  }
}

const person2 = new Person('Tom', 2000);
console.log(person2.age); // 25

// 세터가 없으므로 외부에서 age에 임의 할당 불가
person2.age = 99;
console.log(person2.age); // 여전히 25&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 시 주의사항&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 게터/세터 내부에서 자기 자신(this.name)을 부르면 무한 재귀가 발생한다&lt;/h3&gt;&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;class Person {
  constructor(name) {
    this.name = name; // -&amp;gt; set name() 호출
  }

  get name() {
    // ❌ 잘못된 사용: 내부에서 this.name 호출
    return this.name; // 다시 get name() 호출 -&amp;gt; 무한 재귀
  }

  set name(value) {
    // ❌ 잘못된 사용: this.name으로 재할당 -&amp;gt; 무한 재귀
    this.name = value;
  }
}

&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;게터 안에서 this.name을 사용하면 “프로퍼티 name에 접근 → 게터 재호출”로 이어져 무한 루프에 빠진다.&lt;br&gt;세터 안에서도 마찬가지로 this.name = value를 쓰면 같은 세터를 재호출하게 되어 역시 무한 루프에 빠진다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 내부에서는 _name(또는 #name) 같은 별도 프로퍼티로 접근해야 한다&lt;/h3&gt;&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class Person {
  constructor(name) {
    // 여기서도 set name()이 호출되지만,
    // 세터 내부에서는 _name에 값을 할당하므로 안전
    this.name = name;
  }

  get name() {
    return this._name;
  }

  set name(value) {
    this._name = value;
  }
}

&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 게터·세터가 서로를 재귀 호출하지 않고, 안정적으로 내부 값을 관리할 수 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;get, set의 내부 변수에서 언더스코어( _ )를 붙이는 이유&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 '왜 _name처럼 언더스코어를 쓰는가?' 의문이 들 수 있다. 사실 강제 규칙이 아니므로, 꼭 붙일 필요는 없다.&lt;br&gt;&amp;nbsp;&lt;br&gt;다만 ‘내부용 필드’임을 명시적으로 보여주어 &lt;b&gt;재귀적 호출 실수를 예방&lt;/b&gt;하고, 코드를 읽는 사람이 '&lt;b&gt;이건 내부용 프로퍼티구나~&lt;/b&gt;'라는 사실을 쉽게 파악할 수 있다는 장점이 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;또한, &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes/Private_class_fields&quot; target=&quot;_self&quot;&gt;&lt;span&gt;private field(#name)&lt;/span&gt;&lt;/a&gt; 문법을 사용하면 클래스 외부에서 접근을 완전히 차단할 수도 있다.&lt;br&gt;팀 규칙이나 상황에 따라 언더스코어 또는 # 필드를 쓰는 식으로 스타일이 달라질 수 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;get, set 메서드 이름 규칙&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 this.name = name으로 정의했는데, 게터·세터 내부에서는 this._name 등 실제 내부 변수명과 다를 수 있다.&lt;br&gt;그래도 문제가 없는 이유는 &lt;b&gt;게터/세터 이름이 곧 '외부에서 보이는 프로퍼티 이름'&lt;/b&gt;이기 때문에 가능한거다.&lt;br&gt;&amp;nbsp;&lt;br&gt;즉, 내부에서는 _name이든 _banana든 어떤 변수명을 사용해도, 게터/세터가 정의된 이름이 name이면, person.name으로 접근할 때 그 게터·세터가 자동 호출된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;아래 예시에서 게터·세터 이름은 age이지만, 내부에는 _banana라는 프로퍼티를 사용하고 있다.&lt;br&gt;하지만 콘솔로 출력해보면 문제 없이 30이 출력되는걸 확인할 수 있다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;class Person {
  constructor(age) {
    // 인스턴스 생성 시, 내부적으로 set age(...)가 호출됨
    this.age = age;
  }

  get age() {
    return this._banana;
  }

  set age(value) {
    this._banana = value;
  }
}

const p = new Person(30);
console.log(p.age); // 내부적으로 get age() 실행 -&amp;gt; this._banana 반환
// 콘솔 출력 &amp;gt; 30&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes#class_%EC%A0%95%EC%9D%98&quot; target=&quot;_self&quot;&gt;&lt;span&gt;MDN | Classes&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://ko.javascript.info/property-accessors&quot; target=&quot;_self&quot;&gt;&lt;span&gt;코어 자바스크립트 | 프로퍼티 getter와 setter&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=_DLhUBWsRtw&amp;amp;list=PLv2d7VI9OotTVOL4QmPfvJWPJvkmv6h-2&amp;amp;index=7&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Youtube | 드림코딩 - 자바스크립트 6. 클래스와 오브젝트의 차이점(class vs object), 객체지향 언어 클래스 정리&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>JavaScript</category>
      <category>Class</category>
      <category>GET</category>
      <category>getter</category>
      <category>javascript</category>
      <category>JS</category>
      <category>set</category>
      <category>setter</category>
      <author>sangchu</author>
      <guid isPermaLink="true">https://sanghee01.tistory.com/195</guid>
      <comments>https://sanghee01.tistory.com/195#entry195comment</comments>
      <pubDate>Sun, 9 Feb 2025 21:31:28 +0900</pubDate>
    </item>
    <item>
      <title>2024년 회고 (상반기)</title>
      <link>https://sanghee01.tistory.com/194</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;조금 늦은 2024년 회고.. 이런 회고글은 처음 쓰는지라 쓰고 지웠다를 반복하다가 지쳐 그냥 간단하게 일기식으로 쓰려고 한다. 매년 회고를 통해 경험정리를 하려고 마음을 먹지만 앞서 말한 이유로 쓰지 못했다. 이번엔 꼭 완성해야지!&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2024년 월간 요약&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 342px;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 10.3489%;&quot;&gt;월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%;&quot;&gt;요약&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 57px;&quot;&gt;
&lt;td style=&quot;width: 10.3489%; height: 57px;&quot;&gt;1월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%; height: 57px;&quot;&gt;- 캐나다 밴쿠버 단기 어학연수 &lt;br /&gt;- 밴쿠버, 몬트리올, 퀘벡 여행 &lt;br /&gt;- 감정 점수: 2/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 57px;&quot;&gt;
&lt;td style=&quot;width: 10.3489%; height: 57px;&quot;&gt;2월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%; height: 57px;&quot;&gt;- 샌프란시스코, 실리콘밸리, 라스베이거스 여행 &lt;br /&gt;- SW마에스트로 지원(불합) &lt;br /&gt;- 감정 점수: 8/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 57px;&quot;&gt;
&lt;td style=&quot;width: 10.3489%; height: 57px;&quot;&gt;3월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%; height: 57px;&quot;&gt;- 대학 4학년 1학기 시작 &lt;br /&gt;- 졸업작품 프로젝트 &lt;br /&gt;- 감정 점수: 7/10&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 10.3489%; height: 19px;&quot;&gt;4월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%; height: 19px;&quot;&gt;- ICT 학점연계 인턴 글로벌 과정 지원(불합) &lt;br /&gt;- 졸업작품 프로젝트 &lt;br /&gt;- 대학 중간고사 기간 &lt;br /&gt;- 감정 점수: 7/10&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 10.3489%; height: 19px;&quot;&gt;5월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%; height: 19px;&quot;&gt;- ICT 학점연계 인턴 글로벌 과정 2차 모집 지원(불합) &lt;br /&gt;- 졸업작품 프로젝트 &lt;br /&gt;- 글또 9기 끝 &lt;br /&gt;- 감정 점수: 4/10&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 10.3489%; height: 19px;&quot;&gt;6월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%; height: 19px;&quot;&gt;- 네이버 부스트캠프 웹 모바일 지원(불합) &lt;br /&gt;- 졸업작품 프로젝트 끝 &lt;br /&gt;- 외주 프로젝트&lt;br /&gt;- 대학 기말고사 기간 및 종강 &lt;br /&gt;- 감정 점수: 1/10&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 10.3489%; height: 19px;&quot;&gt;7월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%; height: 19px;&quot;&gt;- ICT 학점연계 인턴 국내 과정 지원(불합) &lt;br /&gt;- 정보처리기사 필기 시험 &lt;br /&gt;- 감정 점수: 4/10&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 10.3489%; height: 19px;&quot;&gt;8월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%; height: 19px;&quot;&gt;- GDSC KAIST 대학생 해커톤 참가&lt;br /&gt;- JS 딥다이브 북스터디&lt;br /&gt;- 감정 점수: 5/10&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 10.3489%; height: 19px;&quot;&gt;9월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%; height: 19px;&quot;&gt;- 대학 4학년 2학기 시작. 근데 1학점만 남아서 온라인 수업 들음 &lt;br /&gt;- JS 딥다이브 북스터디&lt;br /&gt;- 코딩테스트 스터디&lt;br /&gt;- 감정 점수: 1/10&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 10.3489%; height: 19px;&quot;&gt;10월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%; height: 19px;&quot;&gt;- 우아한테크코스 프리코스 시작&lt;br /&gt;- 코딩테스트 스터디&lt;br /&gt;- 글또 10기 시작&lt;br /&gt;- 감정 점수: 8/10&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 10.3489%; height: 19px;&quot;&gt;11월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%; height: 19px;&quot;&gt;- 우아한테크코스 프리코스 끝 &lt;br /&gt;- 코딩테스트 스터디 끝 &lt;br /&gt;- 국가우수장학생 성장지원 멘토링 데이 참가 &lt;br /&gt;- 감정 점수: 8/10&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 10.3489%; height: 19px;&quot;&gt;12월&lt;/td&gt;
&lt;td style=&quot;width: 89.6511%; height: 19px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;-&amp;nbsp;우아한테크코스 최종 코테(합격)&lt;/span&gt;&lt;br /&gt;- KOICA 아프리카 IT교육 해외봉사 지원(합격) &lt;br /&gt;- Cursor AI 관련 챌린지 참가&lt;br /&gt;- 감정 점수: 6/10&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해외 생활기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;캐나다 밴쿠버 단기 어학연수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 해외에 대해선 막연히 생각하고 있었는데, 2023년에 SW중심대학사업단을 통해 태국, 독일에 가서 IT 관련 활동들을 했던 경험이 너무 좋았어서 그때 이후로 해외에서의 삶을 조금씩 꿈꾸고 있었다. 근데 나는 영어를 못해서 좀 더 장기적이고, 실전 어학 능력을 늘릴 기회를 찾다가 어학연수에 도전하게 됐다. 여러 어학연수를 찾다가 결국 교내에서 보내주는 어학연수를 택했다. 4주 밖에 안돼서 아쉬웠지만 그래도 기대가 됐다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만 실제론 내가 생각했던 교육 환경과 너무 달라서 실망이었다. 나는 1:1 튜터링이나 회화 위주의 수업, 매일 단어 시험 등을 기대했지만, 실제 수업은 그냥 영어로 진행되는 일방적인 교양 강의에 가까웠다. 수업 중 대화하는 시간이 있긴 했지만 큰 도움이 되지 않았고, 반 학생들 대부분이 한국인이어서 내가 예상했던 분위기와는 완전히 달랐다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;사실 4주 동안 드라마틱하게 늘진 않을거라고 주변 사람들이나, 후기를 통해 어느정도 알고 있었다. 그래도 나는 아주 기본적인 회화정도는 하게될 줄 알았는데, 그렇게 못될거라고 확신이 들어 의욕이 엄청 꺾였었다. 그래서 무기력해지고 우울했다. 그러니 더욱 더 영어 학습에 집중할 수 없었다. 그 기간 동안 여행도 제대로 즐기지도 못했다. 어학연수에 투자한 비용에 대해 죄책감도 들고, 돈을 아껴써도 통장에서 돈이 술술 빠져나가서 그것도 스트레스였다. 1주차부터 마음의 병이 스멀스멀 왔었다. 정말 톡 치면 멘탈이 끊어질 것 같은 상태로 어떻게든 컨트롤하며 생활했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그러다 3주차가 끝나갈 때 쯤, 한국에 있는 친구에게 내 상황에 대해 얘기하게 됐다. 그 친구는 나와 mbti가 비슷한데(그 친구는 ESTJ, 나는 ISTJ), 그 친구가 &amp;ldquo;우리같은 STJ는 내가 원하는 결과가 있어야 원동력을 얻는데, 그게 없어서 많이 지치나보다&amp;rdquo;라고 말했다.&lt;br /&gt;그말을 듣고 큰 깨달음을 얻었다. 지금까지는 &amp;lsquo;영어 실력 상승&amp;rsquo;이라는 목표만을 생각했는데, 그게 될 가능성이 없다는 생각에 동력을 잃었는데, &amp;lsquo;아, 그럼 목표를 다른걸로 바꾸면 되겠네?&amp;rsquo;라는 생각이 들었다. 어떻게 보면 당연한 생각이지만, 정말 영어만을 보고 온것이기에 목표를 바꾼다는 것은 나에게 너무 어려운 일이었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그 후, 목표를 &amp;lsquo;여행&amp;rsquo;으로 바꿨다. 돈이 계속 나가는 것도 스트레스였지만, 목표를 여행으로 바꾸니 &amp;lsquo;남은 기간 동안 마음 편히 돈을 쓰자&amp;rsquo;는 새로운 결심이 생겼다. 그렇게 남은 기간 동안은 돈을 아까워하지 않고, 진짜 여행을 즐기며 행복한 시간을 보낼 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;캐나다, 미국 여행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말했듯이, 목표를 바꾼 후 마음의 평화가 찾아왔다. 남은 기간 동안은 행복한 순간들로 가득 찼다. 어학연수가 끝난 뒤, 함께 갔던 대학 친구와 몬트리올, 퀘벡, 샌프란시스코, 라스베이거스를 여행했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;가장 기억에 남았던 곳은 실리콘밸리였다. 원래 방문할 계획은 없었지만, 즉흥적으로 계획을 세워 다녀왔다. 이 경험을 통해 나는 여행에 있어서는 계획적이지 않다는 것을 알게됐다. 실리콘밸리에서는 스탠퍼드 대학교를 비롯해 Google, Apple, Github 등 IT 업계의 중심지를 탐방하며 꿈같은 시간을 보냈다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;물론 순탄치만은 않았다. 거의 매일 날씨가 좋지 않았고, 숨 쉬기 힘들 정도로 거센 허리케인도 겪었다. 덕분에 비행기 지연으로 24시간 넘게 공항에 갇혀 잠도 못 자며 노숙을 해야 했다. 그 여파로 그랜드캐년 투어를 못갔는데 환불도 못받았고, 그날 호텔 숙박 비용도 날렸다. 얼핏 보면 시간과 돈을 허비한 불행한 경험을 나열한 것 같지만, 이 기간동안의 얼렁뚱땅 경험들은 너무 즐겁고 소중한 추억으로 남아있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이번 경험을 통해 &amp;lsquo;목표&amp;rsquo;에 따라 마음가짐이 달라진다는 사실을 깊이 깨달았다. 6주 동안 많은 우여곡절을 겪었고, 우울했던 순간도, 행복했던 순간도 있었다. 하지만 그 모든 경험이 값지게 느껴진다. 무엇보다도, 새로운 나의 모습을 발견할 수 있었고, 해외에서의 삶을 더욱 꿈꾸게 된 것 같다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ICT 학점연계 프로젝트 인턴십 글로벌 과정 지원&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년, 대학 3학년이 되었을 때, &lt;a href=&quot;https://www.ictintern.or.kr/main.do&quot; target=&quot;_self&quot;&gt;&lt;span&gt;ICT 학점연계 프로젝트 인턴십&lt;/span&gt;&lt;/a&gt;을 처음 알게 되었다. 이 프로그램은 국내 과정과 글로벌 과정으로 나뉘는데, 글로벌 과정은 실리콘밸리에서 반년간 인턴을 경험할 수 있는 매력적인 기회였다. 하지만 4학년만 지원 가능했고, 경쟁률도 치열하다고 해서(지금 생각해보면 뭐든 치열한 것 같다) 일단 마음속에만 저장해 두었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그러다 2024년, 4학년이 되었다. 3월쯤 사전 공고가 떴고, 앞서 말했듯 해외에서 살아보고 싶은 꿈도 있던 터라 한 번 도전해볼까 싶어 확인해봤다. 그런데 우리 대학이 올해부터 참여 대학에서 제외되었다는 슬픈 사실을 알게 됐다. 원래 이 프로그램은 교내 SW중심대학사업단에서 관리했는데, 올해부터 사업단이 철폐되면서 지원이 불가능해진 것이었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;근데 너무 아쉬워서 본격 ICT 인턴십 살리기 프로젝트(?)를 진행했다. 결론부터 말하면 안될 줄 알았는데 살려냈다. 먼저 ICT 인턴십 측에 연락해서 혹시 우리 대학 지금이라도 추가해줄 수 없냐고 여쭤봤고, 거기서 가능하다며 그 과정을 설명해주셨다. 학교 측에서 직접 지원 서류를 작성해 제출해야했고, 그걸 진행하고 관리할 부서가 필요했다. 그래서 교수님께 자문을 구했고, 다행히 LINC사업단에서 담당해주신다고 했다. 그렇게 4월, 본 지원 기간 직전에 승인이 나서 글로벌 과정 지원이 가능해졌다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;본 안내 공고가 뜨고 제출까지 1주정도의 기간밖에 없어서 생각보다 엄청 촉박했다. 그래서 거의 벼락치기식으로 밤새가며 준비했다. 총 7개의 기업 중 5개만 지원할 수 있었는데, 내가 준비하는 분야인 웹 프론트엔드 개발과 관련된 기업은 2개뿐이라, 그 2개 기업하고 나머지 3개는 그나마 할 수 있을 것 같은 기업으로 지원했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;어떻게 보면 처음으로 기업에 지원하는 거였다. 그래서 기업 지원용 이력서나 포트폴리오는 처음 작성해봤다. 개발자 행사 플랫폼에서 이력서, 포폴 관련 특강이 있으면 다 신청해서 봤고, 인터넷에도 서치해서 작성해나갔다. 각 기업별로 이력서, 자기소개서, 포트폴리오를 다르게해서 제출했다. 처음 써보는거라 좀 헤매고 고민도 많이하고 혼란 그 자체였다. 그래서 밤도 새며 제출 마감 5분전까지 수정하며 제출했다. 코딩테스트도 봤는데, 영어로 5문제가 출제되었고, 제대로 된 대비 없이 시험을 치르다 보니 많이 못풀었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아마 서류 제출 마감하고 2주 뒤까지가 면접 진행 마감 기간이었다. 서류 합격여부 메일이 계속 안와서 우울해 있었던 참에, 거의 마지막에 딱 하나의 기업에서 서류합격 메일이 왔고, 3일 뒤로 구글미트로 면접 일정이 잡혔다. 근데 그 회사가 개발은 아니고 마케팅 직무여서 좀 고민을 했지만, 그래도 일단 붙고 생각하자는 생각으로 면접 준비에 몰두했다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;당시에 일기를 작성했는데, 그때 상황과 심정이 잘 정리되어 있길래 일기 내용도 첨부한다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24/04/26&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하.. 코테.. 결국 새벽 2시인가부터 아침 8시까지 봤다. 당연히 집중도 안되고 잘 못풀었다. 절망적이지만 그때의 나는 너무나 피로하고 뇌에 정신이 없고 정말 혹사적인 그 상태라 절망감이 크게는 안느껴졌다. 걍 &amp;lsquo;아 다음에는 정말 준비 열심히 해야겠다..&amp;rsquo; 라는 생각정도 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 아침 8시가 되고 바로 잤다. 그리고 아마 점심때쯤 일어났다. 그 다음날 중간고사 시작인데 아직 시험공부를 하나도 시작 안한 상태였다. 일욜날 나의 상태는 당연히 최악이고 집중이안돼서 빅데이터처리를 거의 21시에 처음봤다. 그래서 그 시험은 거의 망했다. 월욜날 상태는 더욱 최악이었다. 친구 만나서 대화하는데 진짜 뇌를 거치지않고 대화를 나눴다. 내 상황에대해서 털어놨다. 친구는 어서 잠좀 자라고 했다. 그 다음 시험은 시스템분석설계.. 16시에 있는데 정리만하고 공부는 안했다. 그래도 걍 기숙사가서 30분정도 잤더니 그나마 괜찮아졌고 시험도 잘 본것같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;암튼 이번주는 시험공부, 특히 체력회복에 집중했다. 그리고 목요일인 어제 시험이 끝났는데, 끝나고 면접준비를 해야했는데 너무나 집중력이 떨어져서 못했다. 오늘도 지금 저녁이되어가는데 못했다. 근데 아직도 서류결과가 안나왔다&amp;hellip; 떨어진건가? 차차리 뭔가.. 떨어졌으면 좋겠다 라는 말도안되는 생각도 계속 든다.. 지쳤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 화, 수에 ICT 인턴십 측에서 진행한 면접 교육을 받았는데 너무나도 소중한 정보들을 얻었다. 그리고 내가 제출한 이력서와 포폴는 형편없었다는것도(원래 인지하고 있었지만) 더욱더 깨달았다. 그래서 더욱 면접준비를 할 의욕이 없어졌다. 다시 처음부터 내가 진행한 활동, 프로젝트를 정리하고 기술면접도 준비해야한다. 근데 당장 챙기게에 너무 촉박해서 의욕이 떨어지는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하. 근데 그러면 안된다. 나는 알고있다. 지금까지 나는 이럴 때 중도포기를 해왔다. 그거에 대한 악순환을 끊기 위해 이 경쟁률이 치열하고 가망없어보이는 ict 글로벌 인턴 지원과정에 여기까지 온건데 이제와서 다시 나약해지다니&amp;hellip; 처음 다짐했을때를 기억하며 그래도 끝까지 해봐야지&amp;hellip;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 아까까지(지금도) 너무 의욕이없고 비참 우울했다.. 계속 떨어지더라도 최선을 다해 준비해보자 라고 마인드컨트롤해도 불안함이 날 잡아먹는것같다. 정말 떨어져도 괜찮아도 말이다.. 최선을 다하자는 것 때문인가.. 그냥 구석에 가서 숨고싶은 심정이다.. 멘탈전인가..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 내 안에 &amp;lsquo;그래도 합격하면 좋겠다&amp;lsquo; 라는 마음이 조금이라도 있어서 그런 것 같다. 그래도 예전엔 떨어지면 내 주위사람들이 나에대해 실망할까봐 그런 걱정이컸었는데, 지금은 그런건 없고 그저 내가 간절해서 불안하다. 나도 참 많이 변한것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24/04/28&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;담주가 면접 기간이라 당연히 이번주까지 서류 합불 메일이 올거라 생각했는데, 단 하나의 회사에서도 메일이 안왔었다. 역시 내 이력서와 포폴이 너무 별로였나, 코테도 망쳤으니 당연한 결과인가 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 면접은 한 회사라도 볼 줄 알았는데&amp;hellip; 떨어지더라도 면접은 보고 떨어지고 싶었는데.. 라는 생각으로 방금 점심을 깨작깨작 먹고있었다가 갑자기 확 &amp;lsquo;스팸메일함&amp;rsquo;이라는 단어가 머리에서 떠오르는거다. 그래서 엥? 혹시나 해서 스팸메일함에 봤는데 ㅠㅠ 한 회사가 서류 합격했다고 면접 날짜를 알려주는거다. 와&amp;hellip; 진짜 이거 보자마자 갑자기 없던 기운이 막 났다. 진짜 갑자기 의지가 불타올랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 원하는 포지션의 회사가 아니었지만 나를 면접기회에 붙여줘서 너무 감사한 마음이었다. 진짜 이 회사를 위해 면접준비를 불사러야겠다는 의지가 팔팔난다. 좀 늦게 시작한 감과 부족한점이 많지만, 그리고 떨어지는한이 있더라도 후회없이 준비할것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24/04/30&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 면접 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 조사, 내꺼 이력서 익히기, 예상 질문 및 답변 스크립트 쓰느라 정작 연습할 시간은 없었다. 그냥 스크립트를 쓰기만 했을뿐 외우진 못했다.. 자기소개, 지원동기는 그래도 외워야 할 것같아서 면접 시작 1시간 전에 외웠다&amp;hellip; 배도 막 아프고 화장실도 왔다갔다하고 &amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글미트 들어가기 직전까지 입장하기 버튼을 누르는걸 망설이며, 진짜 내가 너무 작아지고&amp;hellip; 너무 부족하고.. 시간도 촉박하고.. 도망치고 싶은 심정이었다. 그러다보니 면접 5분전이 되어서 시간보고 깜짝놀라서 바로 구글 미트에 들어갔다. 진짜&amp;hellip; 하 이게 맞나???? 지금 이 상태로 면접에 임하는게 맞나?? 너무 부족한데??? 이게 맞나?? 라는 생각이 가득했다. 그리고 에라잇 하고 입장하기 버튼을 눌렀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입장했더니 면접관이 두 분 계셨다. 알고보니 대표가 2명이고 그 두분이 참석하신거다. 면접 분위기는 걱정과 달리 정말 자유롭고 편안했다. 그래도 맨 처음에는 매우 긴장된 상태였다. 자기소개해보라해서 일단 영어로 준비해서 영어로 말하려고 했는데 한국어로 말해도 된다고 하셨다. 그래서 잠시 뜸들이며 &amp;lsquo;아 그래도 영어로 말하는게 플러스겠지? 하 근데 한국어로 말하고싶은데.. 에잇..!!&amp;rsquo; 하며 영어로 자기소개 했다. 면접관님 표정은 계속 미소를 짓고 계셨다(휴..) 이제 진짜 한국어로 말해도 된다며 질문을 이어나갔다. 그래서 그냥 한국어로 맘편히 말했다. 걱정과 달리 편안한 분위기여서 그런지 질문에 대한 답변을 술술 말할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(면접 질문과 답변 등 일기에 세세히 작성했지만 회고글이라 생략 한다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적으로 나를 너무 다 좋게보셨다. 이력서와 포트폴리오를 보고 딱 자기 회사에 필요한 인재여서 서류 합격을 시켜준거고, 실제로 면접으로 보니 자기 회사의 인재상이고 맘에 든다며&amp;hellip; 진짜 그냥 뽑힐 것 같다는게 짐작이 갈 정도로 계속 칭찬해주셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마케팅 직무에 대해 자세히는 몰라서 감히 말하기 조심스럽지만, 마케팅 직무에 관심 있어하는 내 친구도 내가 마케팅쪽을 하면 잘할 것 같다는 말을 해줬다. 그리고 아마도 내가 개발과 관련된 활동보단, 좀 비교적 다양한 활동을 해서 그런 것 같다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;면접을 진행하는데 의외로&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;전반적으로&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;좋게보셔서 살짝 기대를 했지만, 결국 불합격을 받았다. 딱 한 명만 선발하는 자리였고, 아쉽게도 거기에 들진 못했던 것 같다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그래도 처음으로 기업에 지원을 해봤고, 그 과정에서 내가 어떤 부분을 보완해야 하는지도 배웠다. 앞으로의 커리어를 어떻게 만들어갈지 더 구체적으로 고민하는 계기가 되었다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대학 졸업작품 프로젝트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3학년 2학기에 이어 4학년 1학기에도 두 번째 졸업작품 프로젝트를 진행했다. 지난 프로젝트에서 함께한 팀원들이 너무 좋았기에 이번에도 같은 팀원들과 함께했고, 팀장 역할도 연속해서 맡았다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이번 프로젝트에서는 아두이노를 활용해 식물을 원격으로 키우는 시뮬레이션 웹을 개발했다. 아두이노 센서를 이용해 습도, 온도, 조도를 측정하고, 그 데이터를 웹에서 활용하는 방식이었다. 이 과정에서 백엔드와 프론트엔드 간 데이터 전달 방식을 좀 더 이해할 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아쉬운 점이 있었다면 너무 시간에 쫓겨 개발하다보니 gpt에 의존했던 점이다. 초반에는 그래도 필요한 지식들을 공부해서 적용하고, gpt는 힌트 얻거나 리팩토링하는 용도로 사용했는데, 후반으로 갈수록 너무 시간이 없어서 거의 gpt에 의존하며 개발을 했던 것 같다&amp;hellip; 그 과정에서 기본기와 문제해결능력이 부족하다는 느낌이 들었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;또한, 프로젝트가 진행될수록 팀원들이 점점 힘들어하는 게 보였다. 팀장으로서 뭔가 놓치고 있다는 생각이 들어, 팀원들의 상황과 생각을 더 적극적으로 물어보기로 했다. 특히 회의 때보다는 개별적으로 대화를 나누는 것이 솔직한 의견을 듣는 데 더 효과적이었다. 그렇게 모은 의견을 반영해 회의에서 방향성을 조율했다. 예를 들어, 기능을 단순화하거나, 필수 기능만이라도 먼저 개발하는 방식으로 조정했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그 결과, 프로젝트를 보다 현실적인 방향으로 조정할 수 있었다. 최종적으로 목표했던 기능을 기간 안에 완성할 수 있었고, 발표까지 무사히 마무리했다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;외주 프로젝트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;졸업작품이 끝나갈 무렵, 학교 관련 업체에서 외주 공고가 들어왔다. 하지만 당시 ICT 인턴십 글로벌 과정 2차 매칭 지원, 네이버 부스트캠프 지원, 대학 기말고사 준비까지 겹쳐 있어 마음의 여유가 전혀 없었다. 처음에는 전혀 고려하지 않았지만, 졸업작품 팀원 중 한 명이 같이 하면 좋을 것 같다고 제안했고, 업체 측에서도 난이도가 쉽다고 해서 고민 끝에 수락했다. 다른 팀원 한 명도 합류해, 총 세 명이 함께 진행하게 되었다. 사실, 대학생 신분으로 외주 개발을 해보는 것이 나의 버킷리스트 중 하나이기도 했다. 그리고 곧 졸업을 앞두고 있었기에 이번이 아니면 기회가 없을 것 같아서 하기로 했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;외주 작업은 업체의 상품 소개 홈페이지를 새로 만드는 작업이었다. 문제는, 업체에서 기획부터 디자인까지 전적으로 우리에게 맡겼다는 점이었다. 심지어 개발 기한이 단 2주였다. 여러 상품 소개 사이트를 조사하며 벤치마킹하고 디자인을 구상한 후, 빠르게 개발을 진행했다. 바쁜 일정 속에서도 결국 완성은 해냈다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만 몇 가지 힘들었던 점도 있었다. 먼저, 업체와의 소통이 원활하지 않았다. 업체에서 연락을 잘 받지 않아, 단기간에 개발을 완수해야하는데 필요한 자료를 제때 받지 못하는 등 난감한 상황이 자주 발생했다. 그리고 계약서의 중요성을 절실히 깨달았다. 우리는 구두 계약만 했는데, 혹시 말을 바꿀까 봐 팀원들 모두 불안함을 느꼈다. 실제로 완성 후에도 업체가 바빠서 홈페이지를 아직 못 봤다며 돈을 계속 미루는 상황이 발생했다. 결국 마무리는 잘 되었지만, 앞으로 외주를 할 때는 반드시 계약서를 작성해야겠다는 교훈을 얻었다. 그리고 이건 큰 문제는 아니지만.. 시중 가격 대비 매우 저렴한 비용으로 계약을 했기에, 뭔가 손해 보는 느낌이 들었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;앞서 말했듯이 여러 가지를 병행하는 와중에, 예상보다 외주 개발 과정에서 고려할 점이 많아 스트레스가 극심했다. 그래서 처음보는 나의 모습을 볼 수 있었다. 살면서 가족 외에는 예민하게 대한적이 없는데, 한번은 카톡에서   팀원에게 날카롭게 답을 한 적이 있다. 그때 너무 깜짝 놀라서 10분 뒤에 바로 사과했다. 이 대화를 본 내 친구는 내 말투에 아무런 문제가 없다고 말했지만 내가볼땐 너무 T 같아서 바로 사과했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그걸 인지한 다음에는 생각 정리를 하며 좀 마음을 내려놓은 것 같다. &amp;lsquo;나도 이렇게 예민할 수 있구나, 이럴 땐 어떻게 해야할까?&amp;rsquo; 그걸 정리하다보니 당시 개발할땐 약간 마음의 여유가 없는 상태에서 개발했는데, 다시 정신 차리고 밝은 상태를 유지하며 프로젝트에 집중할 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;정말 예민하고 힘든 기간이었지만, 잘 마무리 됐다. 이 프로젝트를 통해 SEO(검색 엔진 최적화)에 대해 공부하고 적용할 수 있었다.&lt;br /&gt;아쉬운 점이 있다면 2주 안에 기획, 디자인, 개발까지 마쳐야 했던 탓에 완성도가 아쉬웠다. 이후 추가 개선을 하려 했지만, 우선순위에서 밀려 손을 대지 못했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그래도 실제 운영되는 사이트를 만들었다는 것과, 방문자 통계를 보면 사이트에 들어오는 사람들이 있어서 뿌듯하다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 2024년 상반기까지 회고글을 작성했다. 생각보다 좀 길게 쓴 것 같다.. 그리고 너무 자세히 썼나 싶다. 이부분은 추후 다듬어봐야겠다.&lt;br /&gt;그래도 작성하는 과정에서 잊혀지고 있었던 경험들을 다시 기억할 수 있어서 좋은 것 같다!&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이어서 하반기를 작성해서 글을 업데이트할 예정이다.&amp;nbsp;&lt;/p&gt;</description>
      <category>회고 &amp;amp; 생각</category>
      <category>2024</category>
      <category>회고</category>
      <author>sangchu</author>
      <guid isPermaLink="true">https://sanghee01.tistory.com/194</guid>
      <comments>https://sanghee01.tistory.com/194#entry194comment</comments>
      <pubDate>Sun, 2 Feb 2025 23:12:54 +0900</pubDate>
    </item>
    <item>
      <title>우아한테크코스 7기 지원부터 최종 코테까지 총 회고</title>
      <link>https://sanghee01.tistory.com/192</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;지원 계기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 나는 컴퓨터공학을 전공했지만, 방황과 시행착오를 겪어온 시간이 많아 4학년임에도 불구하고 많은 부족함을 느끼고 있었다.&lt;br /&gt;대학생활동안 팀 프로젝트를 완성도 있게 하거나 함께 성장할 스터디원을 찾는 일은 쉽지 않았다. 교내에서 스터디원을 모집해보기도 했지만, 관심사가 맞는 사람을 찾는 것은 어려웠다.&lt;br /&gt;&amp;nbsp; 온라인 스터디에서는 비교적 관심사가 맞는 사람들을 만날 수 있었고, 혼자 공부할 때보다 동기부여도 되었지만, 체계적이지 못한 진행과 참여율 저하로 종종 무산되는 일이 많았다. 스터디를 직접 운영하며 개선하려 했지만 나의 미숙함과 경험 부족으로 효율적인 환경을 만들지 못했다. 이런 과정에서 좌절감을 느끼며, 제대로 된 학습 방향을 잡지 못하고 방황하는 시간이 길어졌다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 그리고 그 과정에서 &lt;a href=&quot;https://www.woowacourse.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;우아한테크코스&lt;/span&gt;&lt;/a&gt;를 알게 되었고, 몰입과 메타인지, 미션 수행, 페어 프로그래밍, 코드 리뷰 같은 학습 방식을 통해 내가 원하는 성장을 할 수 있는 환경이라는 생각이 들었다. 우아한테크코스는 방황하던 내게 나침반처럼 느껴졌고, 언젠가 꼭 지원해야겠다고 마음먹었다.&lt;br /&gt;&amp;nbsp; 하지만 매년 여러 이유로 지원을 망설였다. 학점 부담, 졸업작품 프로젝트, 실력 부족에 대한 두려움 등으로 미루는 일이 반복됐다.&lt;br /&gt;그러던 중 올해 &lt;a href=&quot;https://www.youtube.com/watch?v=QVItR4OTgP4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;우아한테크코스 설명회&lt;/span&gt;&lt;/a&gt;를 접하게 되었고, &amp;lsquo;메타인지&amp;rsquo; 키워드에 관한 내용들을 들으며 마음이 울리게 됐다. 내가 처음에 우아한테크코스를 꿈꾸게 된 이유를 다시 떠올릴 수 있었으며, 이 과정이 단순히 합격과 불합격을 넘어 나 자신을 돌아보고 성장할 기회라는 생각이 들었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 결국 이번이 마지막 기회라는 생각으로, 프리코스만으로도 충분히 많은 것을 배울 수 있을 것 같아 지원을 결심했다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1407&quot; data-origin-height=&quot;1706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eG5cei/btsLATzTklg/MFIX2KTG7p4fbMkSwj0RE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eG5cei/btsLATzTklg/MFIX2KTG7p4fbMkSwj0RE0/img.png&quot; data-alt=&quot;우아한테크코스 7기 모집 포스터&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eG5cei/btsLATzTklg/MFIX2KTG7p4fbMkSwj0RE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeG5cei%2FbtsLATzTklg%2FMFIX2KTG7p4fbMkSwj0RE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;485&quot; height=&quot;588&quot; data-origin-width=&quot;1407&quot; data-origin-height=&quot;1706&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;우아한테크코스 7기 모집 포스터&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;지원 과정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 지원을 결심하기까지 좀 오래걸렸어서 서류 마감까지 약 5일정도밖에 안남은 상황이었다.&lt;br /&gt;나는 먼저 입학 설명회에서 들었던 이야기를 정리하고 되새기는 데 집중했다. 왜 우아한테크코스에 지원하고 싶은지, 그리고 이 과정을 통해 무엇을 얻고 싶은지를 고민하며 생각들을 노트에 하나씩 적어 나갔다.&lt;br /&gt;&amp;nbsp; 이전 지원자들의 지원 후기 블로그 글도 참고하며 나는 어떤 방향으로 작성해야 할지 고민했고, 나만의 진솔한 이야기를 쓰기 위한 밑그림을 그렸다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;지원서 작성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 내가 지원서 각 항목에 작성한 내용을 요약한 내용이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 프로그래머가 되려는 이유와 지원 동기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대학교에 진학한 후 웹 프로그래머를 희망하게 된 계기를 적었다. 또한 방황과 좌절을 반복했던 지난날을 떠올리며, 내가 어떤 문제를 겪어왔고 이를 극복하기 위해 어떤 노력을 했는지를 풀어냈다. 그리고 우아한테크코스가 그런 나에게 나침반이 되어줄 것이라는 믿음을 담아냈다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 오랜 시간 몰입했던 경험과 도전&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교내 IT 중앙동아리 임원으로 활동했던 1년간의 경험을 이야기했다. 내향적이고 자신감이 부족했던 내가 임원일에 도전하며 겪었던 역경과 이를 극복하기 위해 기울였던 노력, 그 과정에서 얻은 성장과 도전이 내 삶에 미친 변화를 구체적으로 서술했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 프리코스 목표 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 프리코스를 어떻게 준비하고 임할 것인지 구체적으로 작성하며 내 목표와 다짐을 분명히 했다.단순히 기능 구현에 그치지 않고, &quot;왜?&quot;와 &quot;어떻게?&quot;를 고민하며 코드를 작성하겠다는 목표를 적었다. 또한, LLM에 의존하지 않고 문제를 해결하려는 자세와, 프리코스에 참여하는 사람들과 함께 성장하는 경험을 하고자 하는 계획도 담았다.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프리코스 진행 과정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 프리코스는 우아한테크코스 과정을 미리 체험할 수 있는 전형으로, 4주 동안 스스로 학습하며 매주 주어진 미션을 수행하는 방식으로 진행된다. 매주 공통 피드백이 제공되며, 참여자들은 미션 수행 후 서로의 코드를 리뷰하거나 디스코드 커뮤니티를 통해 정보 교류와 토론을 나누며 소통할 수 있다.&lt;br /&gt;커뮤니티에 대해서는 배민다움의 &lt;a href=&quot;https://story.baemin.com/6193/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;경쟁자와 함께 성장하라고요?&lt;/span&gt;&lt;/a&gt; 글에서 더 자세히 알 수 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 프리코스가 본격적으로 시작되기 전, 커뮤니티에서 많은 사람들이 스터디를 모집하는 모습을 볼 수 있었다. 혼자보다 함께 학습할 때 더 큰 동기 부여를 얻고, 좀 더 몰입하고 성장하기에 좋은 환경을 만들 수 있을 것 같아 나도 한 스터디에 지원해 참여했다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1주차&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 1주차 미션은 프리코스의 첫 과제로, 나에게는 프리코스의 진행 방식을 이해하고 워밍업하는 시간이었다.&lt;br /&gt;처음 과제를 진행하면서 생소한 입출력 방식과 테스트 코드 작성 등 낯설고 모르는 점이 많아 좌절감을 느끼기도 했다. 하지만 이를 배워가는 과정으로 여기며 기대감을 품었다. 특히, 테스트 코드를 통과했음에도 내가 작성한 코드가 올바르게 작성한 코드인지 확신이 들지 않았다. 어서 다른 사람의 코드를 보고 리뷰하며 발전하고 싶다는 생각이 가득했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 코드 리뷰 스터디에 참여하면서 다른 스터디원들의 코드와 문제 해결 방식을 접할 수 있었다. 한 스터디원은 미션 요구사항 중 한 기능을 다양한 방식으로 구현하며 정규식 등을 활용해 간결하게 코드를 작성했고, 또 다른 스터디원은 기능별로 파일을 나누어 유지 보수성과 가독성을 높이는 모습을 보여주었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 나는 1주차 미션이 비교적 단순했기 때문에 함수를 나누거나 파일로 분리할 필요성을 크게 느끼지 못했다. 그러나 스터디원들은 각자 나름대로 고민하며 함수를 쪼개고, 객체지향적으로 작성하고, 파일을 분리해 작성하는 모습을 보여주었다. 나는 이를 보며 많이 놀랐고, 그러한 고민조차 하지 않았던 내 자신이 부끄럽게 느껴졌다.&lt;br /&gt;&amp;nbsp; 스터디원들은 자신이 선택한 방식에 대해 설명하며, 문제 해결과 코드 작성의 다양한 접근법을 공유했다. 그들의 설명을 들으며 코드 구조화의 중요성과 새로운 시각을 배울 수 있었다. 특히, 객체지향 프로그래밍과 SOLID 원칙 같은 개념을 접하면서, 이러한 원칙들이 코드 품질에 어떤 영향을 미치는지 고민하게 되었다. 2주차에는 이에 대해 공부하고 적용하고자 계획을 세웠다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2주차&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2주차에는 1주차 코드 리뷰와 공통 피드백에서 받은 내용을 학습하고 적용하는 데 초점을 맞췄다.&lt;br /&gt;커뮤니티에서는 MVC 패턴에 대한 논의가 활발했지만, 나는 객체 지향 프로그래밍(OOP)과 SOLID 원칙에 대해 더 알고 싶어 이를 먼저 학습하고 코드에 적용하려 했다.&lt;br /&gt;&amp;nbsp; 그 과정에서 우테코 캡틴 포비님께서 말씀해주신 내용이 큰 힘이 됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCeWZ9/btsLAEEu6iL/s2QvkDOBYDNUEDk3viAPL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCeWZ9/btsLAEEu6iL/s2QvkDOBYDNUEDk3viAPL1/img.png&quot; data-alt=&quot;캡틴 포비님의 조언&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCeWZ9/btsLAEEu6iL/s2QvkDOBYDNUEDk3viAPL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCeWZ9%2FbtsLAEEu6iL%2Fs2QvkDOBYDNUEDk3viAPL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;521&quot; height=&quot;364&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;캡틴 포비님의 조언&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 그러나 학습하면서 개념을 이해하는 것과 이를 실제로 구현하는 것은 큰 차이가 있었다. 막상 OOP로 코드를 작성하려니 막막함을 느꼈다. 하지만 학습한 내용을 바탕으로 스터디원들의 코드와 피드백을 참고하며 조금씩 적용해 나갈 수 있었다. 또한, 코드를 기능별로 나누는 연습을 통해 가독성과 유지 보수성을 높이고자 노력했다.&lt;br /&gt;&amp;nbsp; 기능 목록 작성을 통해 코드 작성의 방향성을 명확히 하고자 했다. 구현 전에 요구사항을 세세히 고민하고 기능을 정리한 덕분에 코드 작성 과정에서 더 큰 확신을 가지고 작업할 수 있었다.&lt;br /&gt;&amp;nbsp; 또한, 스터디 코드 리뷰에서 한 스터디원이 테스트 코드를 적극적으로 활용하며 자신의 로직에 대한 확신을 쌓아가는 모습을 보며 테스트 코드 작성의 중요성을 다시금 느꼈다. 나도 테스트 코드를 작성하며 내가 작성한 코드에 대해 자신감을 얻을 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 이 과정에서 우아한테크코스에서 강조했던 '메타인지'의 중요성을 체감했다. 단순히 코드를 작성하는 데 그치지 않고, &quot;왜 이렇게 작성했는지&quot;, &quot;어떻게 개선할 수 있는지&quot;를 깊이 고민하며 1주차에 비해 더 나은 코드를 작성할 수 있었다. 하지만 이번 주차도 내가 올바르게 코드를 작성한 것이 맞는지, 좋은 코드를 작성한 것인지에 대한 확신은 부족했다. 그래서 코드 리뷰 시간이 더욱 기대되었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Crfk/btsLyEjUEj3/sDyQHnQvURaA6WAXs6Qqhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Crfk/btsLyEjUEj3/sDyQHnQvURaA6WAXs6Qqhk/img.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;684&quot; style=&quot;width: 64.095%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Crfk/btsLyEjUEj3/sDyQHnQvURaA6WAXs6Qqhk/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Crfk%2FbtsLyEjUEj3%2FsDyQHnQvURaA6WAXs6Qqhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1026&quot; height=&quot;684&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p11ja/btsLx5WgNv1/mLXPguIFRRiqw30p8LO7r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p11ja/btsLx5WgNv1/mLXPguIFRRiqw30p8LO7r1/img.png&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;1776&quot; style=&quot;width: 34.7422%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p11ja/btsLx5WgNv1/mLXPguIFRRiqw30p8LO7r1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp11ja%2FbtsLx5WgNv1%2FmLXPguIFRRiqw30p8LO7r1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1444&quot; height=&quot;1776&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;2주차 때 공부 및 정리한 내용 / 공부한 내용 일부(해당 내용은 알코님 강좌 보고 정리한 것)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 해당 주차 코드 리뷰에서도 많은 것을 배울 수 있었다. 내 코드도 나름 많이 개선됐다고 생각했는데, 다른 분들도 마찬가지로 많이 개선됐음을 볼 수 있었다. 각자 어떤 어려움을 겪었고, 어떻게 학습했으며, 어떻게 적용했는지 이야기를 나눴다.&lt;br /&gt;&amp;nbsp; 그중 가장 기억에 남는 것은 DI(Dependency Injection)였다. 처음에는 DI를 적용한 스터디원의 코드를 보고 너무 복잡하게 느껴졌고, 왜 이렇게까지 코드를 작성해야 하는지 이해가 안됐다. 질문을 통해 들은 설명에 따르면, DI를 지켜 코드를 작성하면 종속성과 결합도를 낮춰 유지 보수성과 확장성을 향상시키고, 사이드 이펙트도 줄일 수 있다고 했다. 코드를 통해 구체적인 설명을 들으니 DI의 매력과 유용성을 느낄 수 있었다. 3주차에는 DI에 대해 학습하고 적용하고자 계획을 세웠다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3주차&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 3주차는 몸 상태가 좋지 않아 유독 힘들고 어려운 한 주였다. 2주차 코드 리뷰와 공통 피드백에서 받은 내용을 학습하고 적용하려는 계획이 있었지만, 주말이 될 때까지 몸 상태가 나아지지 않아 기본 구현조차 제대로 시작하지 못했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 주말에 겨우 미션 수행을 시작하려 했으나, 피드백 받은 내용을 학습하고 구현할 시간이 부족하다는 생각에 좌절감을 느꼈다. 남은 날이 3일밖에 없었고, 지금 상황에서는 학습은커녕 기본 구현도 해내기 힘들 것 같다는 절망감에 빠졌다. 점점 멘탈 관리가 힘들어졌고, 심지어 과제를 포기할까 하는 생각까지 들었다.&amp;nbsp;&lt;br /&gt;&amp;nbsp; 이러면 안되겠다 싶어 일기장을 꺼내 내가 프리코스에 도전한 이유와 목표를 다시 떠올렸다. '나는 합격 불합격을 떠나서 배워가고 싶었던 것인데, 왜 분명 불합격 될거니까 포기하자고 생각을 한거지?' 이런 생각이 들자 마음가짐을 되잡으며, 일단 가능한 부분부터 시작해보기로 했다. 피드백을 적용하지 못하더라도 4주차에 개선하면 된다는 생각으로, 현재 상황에서 내가 할 수 있는 최선을 다하기로 결심했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 결국 주어진 기능을 요구사항에 맞춰 겨우 구현해낼 수 있었다. 비록 너무 주먹구구식으로 작성된 것 같고 아쉬운 점도 많았지만, 끝까지 포기하지 않고 마무리했다는 점에서 자신에게 칭찬을 줄 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4주차&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 4주차 편의점 문제는 미션 자체도 구현해야 할 사항이 다른 주차에 비해 엄청 많았고, 난이도 또한 어려웠다.&lt;br /&gt;그래서 지금까지 받은 코드 리뷰와 피드백을 통해 배운 점을 적용하고 싶었지만, 기본 요구사항 구현조차 쉽지 않았다. 아쉽게도 해당 주에도 기본 구현만 겨우 마치고 제출했다. 심지어 테스트 코드도 로컬에서는 다 통과하는데 지원페이지에서는 이상하게도 5개 중 4개만 성공했다.&lt;br /&gt;&amp;nbsp; 마감 직전까지 남은 테스트 케이스가 왜 통과되지 않는지 분석했으나, 끝내 원인을 찾아내지 못해 아쉬움이 남았다. 이후 다른 사람들과 이야기를 나누며 원인을 추측해보니, 파일을 불러오는 방식 중 내가 선택한 방법이 테스트 케이스에 적합하지 않았던 것 같았다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프리코스 후기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 4주차가 끝나고 프리코스 전체를 돌아보며, 프리코스를 시작하기 전보다 자신이 많이 발전했음을 느낄 수 있었다. 비록 DI나 커뮤니티에서 자주 언급된 MVC 패턴에 대해 깊이 학습하지는 못했지만, 처음 설정했던 목표를 어느 정도 달성했다고 생각했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 프리코스에서의 목표는 LLM에 의존하지 않고, 내가 작성한 코드가 &quot;왜 이렇게 작성되었는지&quot;를 명확히 이해하는 것이었다. 이를 위해 기능 목록 작성과 코드 구조에 대한 고민을 꾸준히 실천했고, 이러한 노력을 통해 보다 체계적인 접근 방식을 익힐 수 있었다. 또한, 스터디원들과의 코드 리뷰 시간을 통해 서로 배우고 성장하는 경험을 했다. 스터디원들로부터 많이 배우고 동기부여를 받았던 것 같다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 계획대로 진행되지 않아 막히는 부분도 많았지만, 각 주차마다 피드백과 개선 과정을 통해 조금씩 성장할 수 있었다. 특히, 스터디원들과 OOP, SRP, DI, MVC 패턴에 대해 논의하며, 사람들이 왜 이러한 개념들에 주목하는지를 이해하게 되었다. DI는 여전히 낯설고 복잡하게 느껴지지만, 이를 적용한 스터디원의 코드를 보며 사이드 이펙트를 줄이고 유지 보수성을 높이는 방법을 조금씩 이해할 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 스터디원들은 다양한 백그라운드와 접근 방식을 가지고 있었지만, 그들의 노력과 성장은 큰 동기부여가 되었다. 한 스터디원은 프리코스 시작 한달 전에 javascript를 &lt;span style=&quot;color: #333333;&quot;&gt;처음&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;공부하기 시작한 비전공자임에도 불구하고 객체 지향적 설계를 시도하며 코드를 구조화했고, 또 다른 스터디원은 DI와 SOLID 원칙을 철저히 지켜 작성한 코드를 보여주며 깊은 인상을 남겼다. 이들의 코드리뷰와 피드백을 통해 나도 더 나은 코드를 작성하고자 노력할 수 있었던 것 같다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프리코스 마무리 줌 웨비나&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리코스를 마친 후 포비님과 리사님이 진행하는 프리코스 마무리 줌 웨비나에 참여했다. 이 시간은 개발자로서의 성장, 실패를 대하는 태도, 학습 방향성, 그리고 AI 시대에서 문제 해결 능력의 중요성에 대해 깊이 있는 조언을 들을 수 있었던 뜻깊은 자리였다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최종 코딩테스트&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최종 코딩테스트 발표 날까지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 사실 최종 코테는 거의 생각하지 않고 있었다. 4주차 테스트도 모두 통과하지 못했고, 그냥 나는 합격이랑은 거리가 멀 것 같아서 기대하지 않았다. 다만, 최종 코테 준비를 위해서가 아니라 개인적으로 스터디와 피드백을 통해 배운 내용들을 적용하고 리팩토링하고 싶어서 학습하고 연습할 계획을 세웠다. 하지만 당시 병행하던 코딩 테스트 스터디에서 어려운 챕터를 진행하느라 많은 시간을 쏟아야 했다. 그리고 또 병행했던 팀 프로젝트를 3주차때부터는 많이 투자하지 못했어서 다시 집중해야했고, 다른 개인적인 일까지 더해지면서 프리코스 복습은 제대로 하지 못하고 있었다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 그리고 12월 9일, 최종 코테 발표일이 찾아왔다. 오후 3시, 우테코 프리코스 결과 메일이 도착했다. 아무 생각 없이 메일을 열었는데, 믿기 힘든 문구가 눈에 들어왔다.&lt;br /&gt;&amp;ldquo;이 메일을 받는 분들은 최종 코딩테스트 대상자입니다.&amp;rdquo;&lt;br /&gt;순간 너무 놀라서 심장이 두근거리고 온몸에 땀이 났다. 거의 한 시간 동안 감격스러운 기분에 휩싸였고, 마치 최종 합격한 것마냥 기뻤다. 흥분이 가라앉고 나서도 내가 정말 대상자로 선정되었다는 사실이 믿기지 않았다. &quot;내가 그래도 헛되이 살진 않았구나,&quot;라는 생각과 함께, 나의 경험이 가치 있게 받아들여졌다는 점에 큰 영광과 자신감을 느낄 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buMX1E/btsLz8xs0ui/JSjVO4GEgfOzl9lmphDzJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buMX1E/btsLz8xs0ui/JSjVO4GEgfOzl9lmphDzJ1/img.png&quot; data-alt=&quot;최종 코딩테스트 대상자 메일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buMX1E/btsLz8xs0ui/JSjVO4GEgfOzl9lmphDzJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuMX1E%2FbtsLz8xs0ui%2FJSjVO4GEgfOzl9lmphDzJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;583&quot; height=&quot;378&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;698&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최종 코딩테스트 대상자 메일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최종 코딩테스트 연습 과정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 앞서 말했듯이 최종 코테 준비를 충분히 하지 못한 상태였다. 그래서 막연한 초조함 속에서 지금 상황에서 가장 효과적으로 준비할 방법을 고민했다. 그러다 프리코스 마무리 줌 웨비나에서 코치님들이 해주셨던 조언이 떠올랐다. 코치님들은 &quot;미션 하나를 정해 여유를 가지고 반복 구현해보라&quot;고 조언하며, 그 과정에서 새로운 깨달음을 얻을 수 있을 것이라고 하셨다.&lt;br /&gt;이에 나는 먼저 프리코스에서 진행했던 문제들을 복습하고, 그중에서도 3주차 과제였던 로또 문제를 여러 번 풀어봤다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;처음에는 막막했지만, 여러 번 반복해 구현해보며 최종 코테 때 5시간 내에 어떻게 문제를 풀어나갈지 점차 감을 잡을 수 있었다. 또한 단순히 문제를 다시 푸는 것을 넘어, 더 나은 코드를 작성할 수 있는 방법들을 생각할 수 있었다. 특히, 객체지향 프로그래밍, 기능별 파일 분리, 함수 분리 등을 연습하며 프리코스때 어색했던 부분들이 조금씩 익숙해질 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 그리고 최종 코테 날에 대비해 참고할 문서를 미리 정리했다. 최종 코테때는 모든 ai 사용은 금지이며, 구글링이나 미리 준비한 문서 등은 볼 수 있었다. InputView, OutputView, Validation 등 주요 파일을 간단한 구조로 나누어 5시간 내에 작성할 수 있도록 준비하고, 각 기능별 기본 틀을 스니펫으로 작성해 두었다. 그리고 각 시간별 목표를 설정하고, 긴장감을 관리하기 위한 멘탈 케어할 문구도 작성해두었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최종 코딩테스트 날&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 최종 코딩테스트는 선릉역 근처 우아한테크코스 교육장에서 진행됐다&lt;span style=&quot;color: #333333;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 나는 먼 지방에서 가야했기에&lt;/span&gt; 아침 7시에 일어나 버스를 타고 서울로 향했다. 12시부터 입실이 가능한데, 12시 30분쯤 교육장에 도착하니 이미 많은 사람들이 와 있었다. 출석 체크를 하고 제공된 간식을 챙긴 후 지정된 자리에 앉았다. 책상 위에는 기념품이 놓여 있었다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0f8H6/btsLz0Nd7zJ/ahkm6LYc5rnWECKBB1uhyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0f8H6/btsLz0Nd7zJ/ahkm6LYc5rnWECKBB1uhyk/img.png&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;1916&quot; style=&quot;width: 32.5581%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0f8H6/btsLz0Nd7zJ/ahkm6LYc5rnWECKBB1uhyk/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0f8H6%2FbtsLz0Nd7zJ%2Fahkm6LYc5rnWECKBB1uhyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1438&quot; height=&quot;1916&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bafKXI/btsLxPlU8r6/wNSxMryqnD3K8MFkJ8EU60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bafKXI/btsLxPlU8r6/wNSxMryqnD3K8MFkJ8EU60/img.png&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;1916&quot; style=&quot;width: 32.5581%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bafKXI/btsLxPlU8r6/wNSxMryqnD3K8MFkJ8EU60/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbafKXI%2FbtsLxPlU8r6%2FwNSxMryqnD3K8MFkJ8EU60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1438&quot; height=&quot;1916&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4qVQq/btsLzwyQiOq/5BKcU9JkAZdP9oo5aP5Kp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4qVQq/btsLzwyQiOq/5BKcU9JkAZdP9oo5aP5Kp1/img.png&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;1916&quot; style=&quot;width: 32.5581%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4qVQq/btsLzwyQiOq/5BKcU9JkAZdP9oo5aP5Kp1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4qVQq%2FbtsLzwyQiOq%2F5BKcU9JkAZdP9oo5aP5Kp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1438&quot; height=&quot;1916&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;시험장 모습 / 휴식 공간&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 오후 1시가 되고 시험이 시작되자, '지금까지 연습한 대로 해보자'는 마음가짐으로 문제를 읽었다. 참고로 시험은 5시간동안 진행된다. 이번에는 출석에 관한 문제였다. 연습했던 방식대로 README 문서에 기능 명세서를 작성하며 문제를 파악했더니 빠르게 이해할 수 있었다. 하지만 곧 난관에 부딪혔다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 시험 문제는 파일 입출력을 다루고 있었다. 4주차 편의점 과제에서 파일 불러오는 작업을 처음 접했을 때도 많이 헤맸었는데, 이번에는 한 번도 다뤄본 적 없는 csv 파일이어서 더욱 당황스러웠다.&lt;br /&gt;&amp;nbsp; 다행히 미리 정리해둔 파일 로더 스니펫과 4주차 과제 코드를 참고해 비교적 빠르게 구현을 마칠 수 있었다. 하지만 1~2시간정도 지난 뒤 테스트를 실행하자 파일 로더와 관련된 에러가 발생했다. 파일 불러오는 방식이 Jest에서 지원하지 않는 방식이었다. 그때부터 식은땀이 흐르기 시작했다. 그래도 침착하게 구글링을 진행해 다행히 해결책을 찾아낼 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 또한, 구현 과정에서 출석에 관련된 코드 구조를 몇 번이나 변경해야 했다. 한 기능을 구현하면 다른 기능과의 연관성을 고려하지 않아 구조를 수정하거나 새로운 함수를 추가해야 하는 상황이 반복되었다. 처음부터 충분히 설계하지 못한 탓이었다.&lt;br /&gt;다행히 매번 머리를 쥐어짜며 어떻게든 해결했지만, 계속 이러한 난관을 겪으니 체력적으로나 정신적으로 큰 부담이었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 그리고 역시 가장 큰 난관은 테스트를 통과하는거였다. 그래도 초반엔 테스트케이스 5개중 3개를 통과했었는데, 당시에 나머지 두개는 왜 통과가 안되는지 실패 내역을 봐도, 테스트 코드를 봐도 이해가 안됐다.&lt;br /&gt;&amp;nbsp; 게다가 막바지에는 기능 하나를 완성하지 못한 상태에서 테스트 코드를 통과되도록 문제점을 찾아 통과되도록 해결할지, 마지막 기능을 구현할지 선택해야 했다. 결국 후자를 택했고, 모든 기능은 어느정도 완성했지만 결과적으로 테스트 케이스 5개 중 2개만 통과하는 아쉬운 결과를 남겼다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 시험 내내 구현해야 할 요구사항이 많아 정신없이 달려야 했다. 결과적으로 완벽한 코드는 아니었지만, 그래도 모든 기능을 완전하진 않지만 어느정도 구현해냈다.&lt;br /&gt;&amp;nbsp; 하지만 작성된 코드는 App.js에 모든 로직이 몰려 있었고, 시간이 부족해서 파일 분리나 상수 분리 등 기본적인 리팩토링조차 하지 못했다. 또한 동작하는 쓰레기에도 다가가지 못한 것 같아 아쉬움이 크다. 소감문도 촉박한 시간 때문에 하고 싶었던 말을 다 작성하지 못했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 비록 아쉬움이 너무 크지만, 최종 코테에 도달했다는 사실만으로도 큰 의미가 있었다. 이 과정에서 내가 그렇게 못난 사람은 아니라는 자신감을 얻었고, 앞으로의 성장을 위한 중요한 경험을 쌓았다는 점에서 값진 시간이었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 이번 프리코스를 마치며, 우아한테크코스에 지원할까 말까 고민하는 사람들에게 꼭 도전하라고 말하고 싶다. 나 역시 매년 지원을 망설이며 미루다 결국 4학년이 되어서야 용기를 내어 지원했다.&lt;br /&gt;&amp;nbsp; 이 기간동안 만난 분들 중에도 우테코에 관심은 있었지만, 지원을 망설이다 하지 않았다는 사람들이 있었다. 그들의 고민은 과거의 나와 크게 다르지 않았다. &quot;시간이 부족할까 봐&quot;, &quot;내 실력으로 가능할까?&quot;, &quot;떨어지면 어쩌지?&quot; 같은 두려움과 망설임들이었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 하지만 프리코스에 참여했던 사람들 중에는 인턴이나 직장을 병행하거나, 타 부트캠프를 참여하면서 바쁜 일정 속에서도 도전한 이들이 있었다. 심지어 프로그래밍을 막 시작한 초심자들조차 있었다. 놀라운 점은, 이들 중에서도 최종 코테까지 간 사람들이 있었다는 것이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp; 프리코스는 단순히 합격 여부와 상관없이, 배우고 성장할 수 있는 소중한 기회였다. 나는 이 과정을 통해 메타인지의 중요성을 깨달았고, 나를 되돌아보고 더 나은 방향으로 나아갈 수 있는 계기를 얻었다. 그렇기에 우아한테크코스 지원을 망설이고 있다면, 주저하지 말고 도전해보라고 말하고 싶다. 도전 그 자체로 값진 경험이 될 것이다.&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp; 최종 합격, 불합격 여부는 조만간 곧 나오는데, 사실 최종 코딩테스트 결과가 너무 아쉬워서 크게 기대하고있진 않다... 아무튼 결과가 나오면 업데이트하겠다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1728&quot; data-origin-height=&quot;1296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUNDjU/btsLA0y1fmj/lczDP2lqD74XRcK79GRRqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUNDjU/btsLA0y1fmj/lczDP2lqD74XRcK79GRRqk/img.png&quot; data-alt=&quot;마무리는 귀여운 기념품 사진으로..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUNDjU/btsLA0y1fmj/lczDP2lqD74XRcK79GRRqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUNDjU%2FbtsLA0y1fmj%2FlczDP2lqD74XRcK79GRRqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;532&quot; height=&quot;399&quot; data-origin-width=&quot;1728&quot; data-origin-height=&quot;1296&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;마무리는 귀여운 기념품 사진으로..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최종 합격&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12월 27일 15시, 결과 메일이 왔다. 결과를 마주하는게 너무 두려워 메일을 확인하기 전까지 너무 심장이 뛰었다.&lt;br /&gt;그리고 메일을 확인하자마자 소리를 질렀다!! 합격이다!! 너무 행복하고 영광스럽다. 앞으로의 1년이 기대된다!&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctAM28/btsLAyJXQtB/FM4KjgTPrfRJXwOFdasdxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctAM28/btsLAyJXQtB/FM4KjgTPrfRJXwOFdasdxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctAM28/btsLAyJXQtB/FM4KjgTPrfRJXwOFdasdxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctAM28%2FbtsLAyJXQtB%2FFM4KjgTPrfRJXwOFdasdxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;592&quot; height=&quot;468&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>회고 &amp;amp; 생각</category>
      <category>7기</category>
      <category>우아한테크코스</category>
      <category>우테코</category>
      <category>프론트엔드</category>
      <category>프리코스</category>
      <category>합격</category>
      <category>회고</category>
      <category>후기</category>
      <author>sangchu</author>
      <guid isPermaLink="true">https://sanghee01.tistory.com/192</guid>
      <comments>https://sanghee01.tistory.com/192#entry192comment</comments>
      <pubDate>Thu, 26 Dec 2024 22:26:41 +0900</pubDate>
    </item>
    <item>
      <title>firebase 디지털 지문 SHA 이미 다른 프로젝트에 등록된 키 문제 해결 방법</title>
      <link>https://sanghee01.tistory.com/191</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;firebase에서 &lt;span style=&quot;color: #37352f;&quot; data-token-index=&quot;1&quot;&gt;디지털 지문 추가하는 과정에서&lt;/span&gt; &quot;다른 프로젝트에 동일한 SHA-1 디지털 지문과 패키지 이름 조합을 사용하는 OAuth 2.0 클라이언트가 포함되어있습니다 &quot; 문제가 생겼다. 다른 프로젝트에 이미 사용하고 있어서 생긴 문제인 것 같다. 찾아보니 프로젝트별로 SHA 키는 고유해야한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 해당 프로젝트는 안쓰는 프로젝트라 제거했는데도, 문제가 해결되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2038&quot; data-origin-height=&quot;1316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEbAYe/btsLeCFsnds/PiDJLdPiHkc6pG4tcTG3W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEbAYe/btsLeCFsnds/PiDJLdPiHkc6pG4tcTG3W1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEbAYe/btsLeCFsnds/PiDJLdPiHkc6pG4tcTG3W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEbAYe%2FbtsLeCFsnds%2FPiDJLdPiHkc6pG4tcTG3W1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2038&quot; height=&quot;1316&quot; data-origin-width=&quot;2038&quot; data-origin-height=&quot;1316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결방법은 SHA 인증서 지문을 새로 생성하는 것이다. 그러려면 터미널에 다음 명령어를 치면 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 다음과 같이 정보를 입력하는 과정이 진행이된다. 나는 그냥 대충 썼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한건 마지막 질문에 &amp;lsquo;~(가) 맞습니까?&amp;rsquo;로 질문이 나오는데(사진 참고), 여기에 y를 입력해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;네&amp;rsquo;, &amp;lsquo;아니오&amp;rsquo;(?), &amp;lsquo;yes&amp;rsquo;를 다 입력해봤는데 다시 처음질문으로 돌아갔다. y를 입력하니 생성이 됐다. 참고한 어떤 글에서는 &amp;lsquo;yes&amp;rsquo;를 쳐야한다고 나와있던데, 환경마다 다른가보다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oy3Rp/btsLegCNr6D/GRFfhUJlN6HfI7LAITbTv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oy3Rp/btsLegCNr6D/GRFfhUJlN6HfI7LAITbTv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oy3Rp/btsLegCNr6D/GRFfhUJlN6HfI7LAITbTv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foy3Rp%2FbtsLegCNr6D%2FGRFfhUJlN6HfI7LAITbTv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;693&quot; height=&quot;299&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 Mac기준 다음 명령어로 SHA Key를 확인한다. 근데 이 명령어로는 내가 새로 생성한 SHA 키가 없었다. 원래 기존에 있는것만 있었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;cd android &lt;br /&gt;./gradlew signingReport&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다음 명령어 입력하면 새로운 SHA1 키를 볼 수 있다! SHA256 키도 같이 새로 발급된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;keytool -list -v -keystore /Users/sanghee/upload-keystore.jks -alias upload&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Firebase SHA 인증서 지문에 다시 새로 발급된 키를 등록하면 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 기존의 google-services.json 파일은 삭제하고, 새로 다운로드후 프로젝트에 알맞은 폴더에 추가해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 안된다면 firebase에 등록한 SHA 값과 프로젝트의 SHA값이 맞는지도 다시 확인해보자.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;keytool -list -v -keystore debug.keystore&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>문제 해결 &amp;amp; 구현 기록</category>
      <category>firebase</category>
      <category>Key</category>
      <category>SHA</category>
      <category>디지털지문</category>
      <author>sangchu</author>
      <guid isPermaLink="true">https://sanghee01.tistory.com/191</guid>
      <comments>https://sanghee01.tistory.com/191#entry191comment</comments>
      <pubDate>Tue, 10 Dec 2024 17:20:05 +0900</pubDate>
    </item>
  </channel>
</rss>