<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>k_gu_wae123.log</title>
        <link>https://velog.io/</link>
        <description>사용자 경험 개선을 목표로, 효율적인 팀 소통을 통해 가치를 창출하는 프론트엔드 개발자</description>
        <lastBuildDate>Sun, 21 Dec 2025 08:02:31 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>k_gu_wae123.log</title>
            <url>https://velog.velcdn.com/images/k_gu_wae123/profile/d1a82df2-e004-4d5c-803a-8251e4777774/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. k_gu_wae123.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/k_gu_wae123" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[블로그를 이전하게 되었습니다.]]></title>
            <link>https://velog.io/@k_gu_wae123/new-blog</link>
            <guid>https://velog.io/@k_gu_wae123/new-blog</guid>
            <pubDate>Sun, 21 Dec 2025 08:02:31 GMT</pubDate>
            <description><![CDATA[<h2 id="👋-안녕하세요">👋 안녕하세요!</h2>
<p>블로그를 새로 만들었습니다. 기존에 사용하던 <a href="https://velog.io/@k_gu_wae123/posts">Velog</a>에서 Gatsby 기반의 개인 블로그로 이전하게 되었어요.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/7cdebbd7-83a0-41d3-babc-9ece622a4453/image.png" alt=""></p>
<p>🔗 <strong>새 블로그 주소</strong>: <a href="https://kimkyuhoi.github.io/blog">kimkyuhoi.github.io/blog</a><br>📦 <strong>GitHub 저장소</strong>: <a href="https://github.com/KimKyuHoi/blog">KimKyuHoi/blog</a></p>
<hr>
<h2 id="🤔-왜-벨로그를-떠났나요">🤔 왜 벨로그를 떠났나요?</h2>
<p>Velog는 정말 좋은 플랫폼입니다. 마크다운 기반의 깔끔한 에디터와 개발자 커뮤니티, 그리고 쉬운 사용성 덕분에 처음 기술 블로그를 시작하기에 최적이었어요.</p>
<p>하지만 블로그를 운영하면서 몇 가지 아쉬운 점들이 생겼습니다.</p>
<h3 id="1-커스터마이징의-한계">1. 커스터마이징의 한계</h3>
<p>Velog는 플랫폼 특성상 디자인을 자유롭게 바꿀 수 없었습니다. 다크 모드의 색상이나 폰트 같은 세부적인 부분을 조절하고 싶었고, 나만의 개성이 담긴 블로그를 갖고 싶었어요.</p>
<h3 id="2-프론트엔드-개발자로서의-도전">2. 프론트엔드 개발자로서의 도전</h3>
<p>직접 블로그를 만들어보는 것 자체가 좋은 경험이라고 생각했습니다. 작은 서비스 하나를 직접 운영하면서 해보고 싶은 것들을 자유롭게 시도해보고 싶었어요.</p>
<p>이번 인턴을 하면서 느낀 점이 있는데, 혼자서 &quot;이런 문제가 생기면 어떡하지?&quot; 하고 섀도우 복싱하며 고민하는 것보다, 실제로 서비스를 운영하면서 마주치는 문제들을 해결해나가는 게 훨씬 가치 있는 경험이었어요. 가상의 문제가 아니라 실제로 발생하는 DX, UX 이슈들을 대응하다 보면 진짜 필요한, 검증된 고민을 할 수 있거든요. 블로그를 직접 운영하면서 그런 경험을 계속 쌓아가고 싶었습니다.</p>
<h3 id="3-내-글에-온전히-집중하고-싶어서">3. 내 글에 온전히 집중하고 싶어서</h3>
<p>물론 최근에는 관리가 많이 개선된 것 같지만, 예전에는 Velog에 광고성 글이나 개발과 무관한 콘텐츠들이 많이 올라와서 개발자 커뮤니티 분위기가 희석된 것 같아 아쉬웠어요.</p>
<p>하지만 플랫폼의 변화와 별개로, 이제는 제 글에 온전히 집중하고 싶었어요. 내가 쓴 글을 직접 관리하고, 어떻게 하면 더 좋은 글을 쓸 수 있을지 고민하는 습관을 기르고 싶었습니다. 개인 블로그라면 오롯이 제 콘텐츠와 성장에만 집중할 수 있을 것 같았어요.</p>
<hr>
<h2 id="✨-새-블로그에서-구현한-기능들">✨ 새 블로그에서 구현한 기능들</h2>
<p>UI를 고민하면서 <a href="https://toss.tech/">Toss 기술 블로그</a> 스타일의 깔끔한 디자인을 많이 참고했어요.</p>
<p>군더더기 없이 콘텐츠에 집중할 수 있으면서도, 다양한 글들을 쉽게 탐색할 수 있는 구조를 만들고 싶었습니다. 그리고 글을 읽으면서 댓글을 통해 독자분들과 이야기를 나눌 수 있는 공간이 되었으면 했어요.</p>
<h3 id="🌓-다크라이트-모드">🌓 다크/라이트 모드</h3>
<p>시스템 설정에 따라 자동으로 전환되고, 사용자가 직접 선택할 수도 있습니다. 밤에 글을 읽을 때 눈이 편안해요.</p>
<h3 id="📱-반응형-디자인">📱 반응형 디자인</h3>
<p>모바일, 태블릿, 데스크탑 어디서든 최적화된 화면을 보여줍니다. 어디서든 편하게 읽을 수 있어요.</p>
<h3 id="📑-table-of-contents-toc">📑 Table of Contents (TOC)</h3>
<p>긴 글도 쉽게 탐색할 수 있도록 목차 기능을 넣었습니다. 원하는 섹션으로 바로 이동할 수 있어요.</p>
<h3 id="💬-댓글-시스템">💬 댓글 시스템</h3>
<p>GitHub Issues 기반의 <strong>Utterances</strong>를 활용해서 댓글 기능을 구현했습니다. GitHub 계정만 있으면 바로 댓글을 남길 수 있어요.</p>
<h3 id="☕-buy-me-a-coffee">☕ Buy Me a Coffee</h3>
<p>글이 도움이 되셨다면 커피 한 잔 사주실 수 있는 기능도 넣어봤어요. 작은 응원이 큰 동기부여가 됩니다!</p>
<hr>
<h2 id="🛠-기술-스택">🛠 기술 스택</h2>
<p>이번 블로그는 다음 기술들로 만들어졌습니다:</p>
<table>
<thead>
<tr>
<th>분류</th>
<th>기술</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Framework</strong></td>
<td>Gatsby</td>
</tr>
<tr>
<td><strong>Language</strong></td>
<td>TypeScript</td>
</tr>
<tr>
<td><strong>Styling</strong></td>
<td>Emotion (CSS-in-JS)</td>
</tr>
<tr>
<td><strong>State</strong></td>
<td>Jotai</td>
</tr>
<tr>
<td><strong>Deploy</strong></td>
<td>GitHub Pages</td>
</tr>
</tbody></table>
<p>특히 <strong>Gatsby</strong>를 선택한 이유는:</p>
<ul>
<li>React 기반이라 익숙하고</li>
<li>정적 사이트 생성(SSG)으로 빠른 로딩 속도</li>
<li>풍부한 플러그인 생태계</li>
<li>File Based 기반의 Routing</li>
<li>많은 개발자 블로거들이 사용하고 있어서 비슷한 고민을 나누고 해결책을 찾아볼 수 있는 커뮤니티가 활발하다는 점도 좋았어요</li>
</ul>
<hr>
<h2 id="🚀-앞으로의-계획">🚀 앞으로의 계획</h2>
<p>블로그를 만든 것으로 끝이 아닙니다. 앞으로도 계속 개선해나갈 예정이에요.</p>
<ul>
<li><strong>검색 기능</strong>: 키워드로 원하는 글을 빠르게 찾을 수 있도록</li>
<li><strong>시리즈 기능</strong>: 연재물처럼 연결된 글들을 한눈에 볼 수 있도록</li>
<li><strong>조회수/좋아요</strong>: 어떤 글이 도움이 됐는지 피드백을 받을 수 있도록</li>
<li><strong>Playground</strong>: 사이드 프로젝트들을 직접 체험해볼 수 있는 공간</li>
</ul>
<hr>
<h2 id="🙏-마무리">🙏 마무리</h2>
<p>개인 블로그를 직접 만들어보는 건 정말 좋은 경험이었습니다. 프론트엔드 개발자로서 내 손으로 만든 공간에서 글을 쓴다는 건 특별한 기분이에요.</p>
<p>앞으로 이 블로그에서 개발하면서 배운 것들, 삽질한 경험들, 그리고 다양한 이야기들을 공유할 예정입니다.</p>
<p>많은 관심 부탁드립니다! 🙌</p>
<hr>
<blockquote>
<p>혹시 블로그에 대해 궁금한 점이나 피드백이 있으시면 아래 댓글로 남겨주세요! 😊</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[연휴와 함께하는 인턴으로 살아남기 5주차 회고록]]></title>
            <link>https://velog.io/@k_gu_wae123/pinkfong-company-5</link>
            <guid>https://velog.io/@k_gu_wae123/pinkfong-company-5</guid>
            <pubDate>Sun, 12 Oct 2025 05:55:04 GMT</pubDate>
            <description><![CDATA[<p>다들 추석 연휴 잘 보내고 계신가요?
회사에 들어와 처음 맞는 연휴라 그런지, 정신없이 달려왔던 지난 몇 주가 문득 낯설게 느껴집니다.
‘이렇게 여유롭게 쉬어도 되는 걸까?’ 하는 생각이 들면서도, 오랜만에 제대로 쉬며 그동안의 시간을 돌아보고 있습니다.
이제 다시 회사로 돌아갈 준비를 하며, 연휴 전까지의 여정을 정리해보려 합니다.</p>
<blockquote>
<p>그리고 회사 기술 블로그에 글이 게시되어 링크를 함께 공유드립니다.
부족하지만 많이 봐주시고 피드백 주시면 감사하겠습니다! ☺️</p>
<p><a href="https://medium.com/pinkfong/next-js-app-router%EC%97%90%EC%84%9C-prefetchquery%EC%99%80-suspense%EB%A1%9C-%EC%9A%B0%EC%95%84%ED%95%98%EA%B2%8C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%8A%A4%ED%8A%B8%EB%A6%AC%EB%B0%8D%ED%95%98%EA%B8%B0-acb3d90cd5bc">🔗 Next.js App Router에서 prefetchQuery와 Suspense로 뚜루루뚜루 데이터 스트리밍하기</a></p>
</blockquote>
<h2 id="새로운-과제">새로운 과제</h2>
<p>프로덕션 배포 이후, 새로운 인턴 과제를 맡게 되었습니다.
주제는 <strong>“대용량 파일 업로드/다운로드 과정에서의 안정적 개선 (유사 구글 드라이브)”</strong>.
100GB 이상의 파일도 고려해야 한다는 조건이 붙어 있었고, 2주 동안 정말 많은 고민을 하게 되었습니다.</p>
<h3 id="안정적인-업로드와-다운로드를-향한-고민">안정적인 업로드와 다운로드를 향한 고민</h3>
<p>개발을 시작하고 나서야 깨달았습니다.
1GB가 넘는 파일은 업로드든 다운로드든 단순한 요청 한 번으로 끝나지 않는다는 사실을요. 평소엔 당연하게 느꼈던 전송 과정이 실제로는 얼마나 복잡한지, 이번 과제를 통해 처음 체감했습니다.</p>
<h4 id="업로드-구현하기">업로드 구현하기</h4>
<p>그리고 사용자 환경이 항상 쾌적하다는 보장은 없고, 실제로 브라우저를 강제 종료하거나 탭을 닫는 일은 흔하다는 겁니다.
그래서 구글링을 통해 서버 트래픽이 몰리지 않는 선에서 S3의 Multipart Upload API를 기반으로, 각 파일을 사용자의 네트워크 환경에 따라 동적으로 나뉘어 청크로 쪼개 병렬로 업로드하도록 설계했습니다. 또한 강제 새로고침이나 업로드 중단이 발생했을 때는 listParts API를 통해 이미 업로드된 파트를 복원하고, 남은 청크만 이어서 업로드하도록 구현했습니다. 
이 구조를 통해, 네트워크 중단 이후에도 5분 내 재개 가능한 업로드 안정성을 확보할 수 있었습니다.</p>
<h4 id="다운로드-구현하기">다운로드 구현하기</h4>
<p>업로드가 안정적이라면, 다운로드는 “빠르고 복구 가능한 구조”여야 했습니다.
특히 해외 고객사 환경을 고려해 CloudFront CDN 기반 다운로드 구조를 설계하라고 과제를 받게 되었습니다.</p>
<p>개발을 진행하며 느낀 점은,
다운로드는 업로드보다 훨씬 더 신중해야 한다는 것이었습니다.
50GB가 넘는 파일을 받다가 네트워크가 끊기면, 다시 처음부터 받는 건 현실적으로 너무 고통스러웠습니다. 실제로 한 번 테스트 중에 그런 상황을 겪고 나서, “이건 꼭 이어받기가 되어야 한다”는 확신이 생겼습니다.</p>
<p>그래서 다운로드 진행률을 localStorage에 저장하고, 강제 새로고침이나 탭 종료 후에도 이어받기가 가능하도록 했습니다. 
여기서 고민을 했었던 점은 Cloudfront는 놀랍게도 캐싱이 적용되기 위해서는 50GB이하 까지만 요청할 수 있다는 점입니다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/9e981d14-c517-4549-ba06-950d9cb12aee/image.png" alt=""></p>
<p>이 부분에 대해서 업로드에서 청크 방식으로 했던것처럼 다운로드에서 적용하게 되면 50GB가 넘는 파일을 다운로드를 하게 되더라도 청크방식으로 다운로드를 받게 되면 캐시 제한 정책을 해결할 수 있지않을까? 라는 생각을 하게 되었습니다.
그래서 업로드와 마찬가지로, 다운로드 역시 청크 단위 병렬 다운로드 구조를 적용했습니다.</p>
<h2 id="정리하며">정리하며</h2>
<p>이번 과제는 단순히 파일을 전송하는 기능이 아니라,
<strong>“사용자가 신뢰할 수 있는 전송 경험”</strong>을 만드는 과정이었습니다.</p>
<p>그 외에도 인증 방식을 구글 OAuth 2.0과 JWT 쿠키 기반으로 분리해 보안성을 강화했고, 마지막 주차에는 업로드한 영상을 YouTube처럼 바로 스트리밍 재생할 수 있는 기능까지 확장할 예정입니다.</p>
<p>시간은 빠듯하지만,하나씩 구조를 세우고 기술적 문제를 해결해나가는 과정이
정말 “개발자로 성장하고 있다”는 실감을 주는 한 주였습니다.</p>
<p>이 글을 읽으시면서 추가적으로 고려할 점이나 더 나은 접근 방식에 대한 인사이트가 있으시다면, 댓글로 남겨주시면 정말 감사하겠습니다 ☺️
요즘 댓글을 통해 새로운 시각과 배움을 얻는 경험이 많아지면서,
개발자끼리 생각을 나누는 일이 얼마나 즐겁고 값진 일인지 새삼 느끼고 있습니다.</p>
<p>오늘도 글 읽어주셔서 정말 감사드립니다! 좋은 하루되세요!</p>
<h4 id="참고하며-공부했던-링크">참고하며 공부했던 링크</h4>
<ul>
<li><a href="https://techblog.woowahan.com/11392/">Spring Boot에서 S3에 파일을 업로드하는 세 가지 방법</a></li>
<li><a href="https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html">CloudFront AWS 캐시 정책 Docs</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[인턴으로 살아남기 3.5주차  회고록]]></title>
            <link>https://velog.io/@k_gu_wae123/pinkfong-company-3</link>
            <guid>https://velog.io/@k_gu_wae123/pinkfong-company-3</guid>
            <pubDate>Thu, 25 Sep 2025 12:37:01 GMT</pubDate>
            <description><![CDATA[<p>요즘 출근하면서 출근길 지하철에서 아기상어 캐릭터를 마주할 때마다 묘한 기분이 든다.<br>내가 다니는 회사에서 이런 콘텐츠를 만들고 세상 곳곳에 퍼뜨린다는 사실이 아직도 신기하다.<br>한편으로는 “아직 회사에 완전히 적응하지 못한 건가?” 싶다가도, 이런 순간들이야말로 회사 생활이 실감 나는 순간임을 느낀다.  </p>
<h2 id="다듬고-또-다듬기-0925-프로덕션-배포까지의-여정">다듬고 또 다듬기: 09.25 프로덕션 배포까지의 여정</h2>
<p>이번 주는 말 그대로 정신없이 흘러갔다. 목표는 단 하나, 프로덕션 배포.<br>하지만 그 목표에 다가가는 길은 단순히 코드를 작성하는 것에서 끝나지 않았다.<br>리팩토링, QA, 협업, 그리고 예상치 못한 의사 충돌까지—예상보다 많은 일이 한꺼번에 몰려왔다.  </p>
<p>3주를 목표로 잡았던 일정은 계속 밀렸고, 남은 건 단 1주일.<br>짧다면 짧은 시간이었지만, 그 속에는 수많은 과제가 얽혀 있었다.  </p>
<h2 id="qa-없는-팀-그래서-더-치열한-qa">QA 없는 팀, 그래서 더 치열한 QA</h2>
<p>우리 팀에는 전담 QA 조직이 없다.<br>그렇기 때문에 작은 버그 하나라도 <strong>개발자가 직접 찾아내고 해결해야 한다.</strong>  </p>
<p>“이 버튼은 의도대로 동작하는가?”<br>“예외 케이스를 놓치진 않았는가?”<br>“실제 사용자가 마주한다면 불편하지는 않을까?”  </p>
<p>이런 질문을 계속 떠올리며 코드를 점검하다 보면, 하루의 에너지가 빠르게 고갈된다.<br>하지만 내가 만든 기능이 실제 환경에서 문제없이 돌아가는 순간을 기대하는 마음이, 다시 집중력을 붙잡아 주었다.  </p>
<h2 id="새로운-기술-그리고-첫-번째-스테이징">새로운 기술, 그리고 첫 번째 스테이징</h2>
<p>주 초반, 팀원 한 분이 멘토님을 통해 도입된 새로운 기술을 이해하는 데 어려움을 겪으셨다.<br>첫 스테이징 회의를 앞두고, 나는 단순히 개념을 설명하는 것에서 그치지 않고 <strong>“도입 전과 후의 차이”</strong>를 코드 예시로 준비했다.  </p>
<p>또한 같은 문제를 다른 방식으로 접근했을 때 어떤 장단점이 있는지도 비교해 보여드렸다.<br>생각보다 설명은 효과적이었고, 멘토님들께서는 “다른 팀원들에게 발표해도 좋겠다”는 피드백을 주셨다.<br>그 과정은 회사 기술블로그에 글을 기고하는 기회로까지 이어졌다.  </p>
<p>개발은 결국 <strong>소통의 과정</strong>이라는 걸 느꼈다. 문제를 푸는 것만큼이나, 그 과정을 어떻게 설명하느냐도 중요하다는 걸 배웠다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/dafe664f-b9ad-4134-975f-f7ecdbcd6cd3/image.png" alt=""></p>
<blockquote>
<p>곧 글 올라갑니다~ 개봉박두</p>
</blockquote>
<h2 id="첫-의사-충돌-배럴-파일-논쟁">첫 의사 충돌, 배럴 파일 논쟁</h2>
<p>이번 주 가장 깊은 고민을 안겨준 사건은 배럴 파일 사용 여부였다.<br>팀원분이 이미 작성해둔 코드가 있었는데, 나는 배럴 파일이 <strong>편리함</strong>을 주는 동시에 빌드 속도와 번들 크기 측면에서는 <strong>부정적인 영향</strong>을 줄 수 있다고 생각했다.  </p>
<p>문제는 <strong>어떻게 말하느냐</strong>였다.<br>“배럴 파일은 안 좋아요”라고만 말하면 괜히 상대방의 노력을 부정하는 것처럼 들릴 수 있었다.<br>그래서 나는 직접 <strong>Next Bundle Analyzer</strong>를 설치해, “제거 전/후 번들 크기 차이”를 시각 자료로 보여주었다.  </p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/b3223636-2ac6-4dc8-bcaa-1580454455e9/image.png" alt=""></p>
<p>눈으로 확인할 수 있는 데이터는 말보다 훨씬 설득력이 있었다. 불필요한 감정 소모 없이 “아, 이게 진짜 영향을 주는구나”라는 공감대를 만들 수 있었고, 자연스럽게 제 의견을 받아들여 주셨다.</p>
<p>이번 경험을 통해 깨달았다. 협업에서는 기술적인 옳고 그름보다, <strong>문제를 어떻게 공유하고 대화하느냐</strong>가 더 중요한 순간이 많다는 걸. 단순한 주장보다 <strong>근거와 데이터</strong>가 있을 때 대화가 훨씬 건설적으로 흘러간다는 점을 다시 확인했다.</p>
<h2 id="수많은-qa-끝에-드디어-프로덕션">수많은 QA 끝에, 드디어 프로덕션</h2>
<p>그리고 마침내… 9월 25일(목).<br>수십 차례 QA와 버그 수정, 끝없는 리팩토링 끝에 프로덕션 배포라는 최종 관문을 맞이했다.  </p>
<p>배포 버튼을 누른 순간, 화면에 흘러가는 로그 하나하나가 유난히 느리게 지나가는 듯했다.<br>혹시 예상치 못한 에러가 튀어나오지 않을까, 롤백되진 않을까—손끝까지 긴장감이 번졌다.  </p>
<p>그런데 마침내 모니터에 <strong>“배포 완료”</strong>라는 문구가 떴을 때, 이상할 만큼의 해방감이 찾아왔다.<br>내 코드가 더 이상 깃허브 PR 속 텍스트가 아니라, 실제 사내 서비스의 일부가 되었다는 사실이 현실처럼 다가왔기 때문이다.  </p>
<p>그 감정은 단순히 “기쁘다”로는 설명하기 부족했다.<br>수많은 커밋들이 연결되어 하나의 완성된 선을 그린 듯한 감각.<br>누군가 내일 아침부터 내가 만든 기능을 아무렇지 않게 사용하리라는 상상은 벅찬 책임감으로 다가왔다.  </p>
<figure>
  <img src="https://velog.velcdn.com/images/k_gu_wae123/post/56fe2dd9-efa1-4011-8595-d8d254407ed6/image.png" alt="배포 완료 화면" />
  <figcaption>배포 완료 직후의 모니터. 긴장 끝에 찾아온 해방의 순간.</figcaption>
</figure>

<h2 id="협업은-결국-사람의-일">협업은 결국 “사람”의 일</h2>
<p>이번 주는 단순히 코드를 짜는 시간이 아니었다.<br>버그를 추적하며 로그를 까보고, 리팩토링 방향을 두고 토론하다가, 잠시 쉬는 시간엔 농담을 주고받으며 웃기도 했다.  </p>
<p>돌이켜보면 협업이란 단순히 일을 나누는 게 아니다.<br>서로의 관점을 존중하면서 더 나은 답을 찾아가고, 때로는 설득하고 양보하며 합의를 만드는 과정이다.  </p>
<p>결국 가장 까다로운 건 코드가 아니라 <strong>사람 사이의 대화</strong>였다.<br>하지만 그 대화를 넘어섰을 때, 비로소 혼자가 아닌 ‘팀’으로 움직이고 있음을 실감할 수 있었다.  </p>
<h2 id="개발-외의-소소한-즐거움">개발 외의 소소한 즐거움</h2>
<p>추석을 앞두고 회사에서 준비한 작은 행사들도 있었다.<br>깜짝 선물과 이벤트는 바쁜 일정 속에서 잠시 숨을 고르게 해주었다.  </p>
<figure>
  <img src="https://velog.velcdn.com/images/k_gu_wae123/post/87f02695-bf23-44fc-b900-8f7105584395/image.jpg" alt="행사" />
  <figcaption>추석맞이 행사 게임</figcaption>
</figure>


<p>“아, 회사 생활이란 이런 재미도 있구나.”<br>단순히 일만 하는 곳이 아니라, 함께 웃고 즐기며 추억을 쌓을 수 있는 공간이라는 사실이 새삼 고마웠다.  </p>
<h2 id="마치며">마치며</h2>
<p>이번 주는 그야말로 <strong>‘다듬고 또 다듬는 과정’</strong>이었다.<br>코드도, 협업도, 그리고 나 자신도 계속 점검하고 개선해야 했다.  </p>
<p>그 과정은 고되고 지치지만, 분명히 나를 성장시킨다.<br>작은 성취와 즐거움을 쌓아가며, 점점 더 <strong>팀의 일원으로 자리 잡아가는 기분</strong>.  
그게 바로 이번 주, 내가 얻은 가장 큰 배움이었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인턴으로 살아남기 2주차 회고록]]></title>
            <link>https://velog.io/@k_gu_wae123/pinkfong-company-internship-2</link>
            <guid>https://velog.io/@k_gu_wae123/pinkfong-company-internship-2</guid>
            <pubDate>Mon, 15 Sep 2025 14:02:07 GMT</pubDate>
            <description><![CDATA[<h2 id="2주차-회고록--streaming-ssr과-서버-컴포넌트-전환">2주차 회고록 – Streaming SSR과 서버 컴포넌트 전환</h2>
<p>이번 2주차는 정말 눈 깜짝할 사이에 지나갔다. 새로운 과제들은 대부분 내가 처음 해보는 것들이라, 모든 순간이 생생하면서도 어렵고 동시에 흥미로웠다. 해결하고 싶은 문제가 생기면 집에 돌아와서도 12시가 넘도록 고민하고 코드를 붙잡고 있었으니, 확실히 몰입의 강도가 달랐다.</p>
<h3 id="서버클라이언트-컴포넌트-분리와-본격적인-마이그레이션">서버/클라이언트 컴포넌트 분리와 본격적인 마이그레이션</h3>
<p>1주차가 Page Router에서 App Router로 옮겨오는 작업에 집중했다면, 이번 주는 Server Component와 Client Component를 체계적으로 분리하는 단계였다. 처음에는 단순히 “어떤 컴포넌트를 서버로 두고, 어떤 걸 클라이언트에 남길까?” 정도의 선택 문제라고 생각했다. 하지만 막상 손을 대보니 데이터 접근 방식, 쿠키 로직, 라이브러리 호환성까지 줄줄이 얽혀 있었다. 특히 쿠키 접근을 서버 전용으로 바꾸고, 클라이언트에서는 Proxy API를 통해서만 전달받도록 구조를 전환하면서, Next.js가 지향하는 서버 중심 아키텍처를 몸소 느낄 수 있었다.</p>
<h3 id="데이터-프리페칭과-streaming-ssr">데이터 프리페칭과 Streaming SSR</h3>
<p>이번 주의 메인 미션은 단연 데이터 프리페칭과 Streaming SSR 구축이었다.</p>
<h4 id="멘토님의-퀘스트">멘토님의 퀘스트</h4>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/a8777739-3233-4d6e-a271-b91be4d633a5/image.png" alt=""></p>
<p>사수님께서 “await 없이 streaming하게 불러오는 SSR 구조를 만들어보라”는 퀘스트를 주셨다. 늘 개념으로만 접하던 Streaming SSR을 직접 구현할 기회가 온 것이다. 호기심과 도전 정신이 동시에 솟아올랐다.</p>
<h4 id="고군분투의-시간">고군분투의 시간</h4>
<p>하지만 현실은 만만치 않았다. 서버에서 await을 제거하면서 생긴 queryKey 동기화 문제, await이라는 고삐없이 데이터가 출발해버리니까 터져버리는 hydration mismatch 에러, 빌드 환경에서 Next.js의 정적 최적화와 쿠키 로직 충돌 같은 예기치 못한 에러들이 연달아 터졌다. 결국 force-dynamic 옵션으로 서버 런타임을 강제로 유지하면서 하나하나 풀어냈다.
“Next.js 14에서 App Router가 빌드 시 자동으로 정적 최적화를 시도한다”는 사실도 이번에 진짜 뼈저리게 배웠다.</p>
<p>특히 중복 요청 문제 때문에 며칠을 끙끙대다가, React Query 공식 문서에서 다음 문장을 보고는 진짜 망치로 머리를 맞은 듯했다.</p>
<blockquote>
<p>As of React Query v5.40.0, you don&#39;t have to await all prefetches for this to work, as pending Queries can also be dehydrated and sent to the client. This lets you kick off prefetches as early as possible without letting them block an entire Suspense boundary, and streams the data to the client as the query finishes. </p>
<p>공식문서 : <a href="https://tanstack.com/query/v5/docs/framework/react/guides/advanced-ssr">https://tanstack.com/query/v5/docs/framework/react/guides/advanced-ssr</a></p>
</blockquote>
<p>“아… 이걸 모르고 3일을 헤맸구나…” 싶었던 순간이었다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/d66e77f6-96c0-48e9-ab5b-887aad51a6f4/image.png" alt=""></p>
<h3 id="해결과-성과">해결과 성과</h3>
<p>React Query를 v5로 올리고, <code>useSuspenseQuery</code>와 <code>dehydrate</code>를 활용하면서 구조가 한결 정리되었다. 덕분에 주요 페이지의 초기 로딩 속도가 확실히 개선되었고, Fallback UI가 보일 틈도 없이 콘텐츠가 눈앞에 나타났다. 말 그대로 신세계였다.
게다가 Suspense 기반 스켈레톤 UI를 일관되게 적용하면서 로딩 UX도 깔끔하게 통합할 수 있었다.</p>
<h3 id="테스트-환경-정비와-번들-최적화">테스트 환경 정비와 번들 최적화</h3>
<p>구현만 한 게 아니라 테스트 환경과 번들 최적화도 병행했다.</p>
<ul>
<li><strong>MSW v2</strong>로 마이그레이션 → Node.js 환경 호환성 확보</li>
<li><strong>Jest v29</strong>로 업그레이드 → React 18 Suspense 테스트 호환성 보장</li>
<li><strong>lodash 제거 &amp; es-toolkit 도입</strong> → 불필요한 의존성 축소 + 타입 안정성 강화</li>
</ul>
<p>테스트와 빌드가 매끄럽게 돌아가는 환경이 마련되니, 마음 놓고 리팩토링과 최적화를 시도할 수 있었다.</p>
<h3 id="성능-개선과-배운-점">성능 개선과 배운 점</h3>
<p>수치로도 성과가 드러났다.</p>
<ul>
<li><strong>초기 JS 번들 크기</strong>: 372kB → 155kB (-58%) 감소</li>
<li><strong>데스크톱 환경 CLS</strong>: 0.034 → 0.007 개선</li>
<li><strong>모바일 환경 FCP</strong> : 7.0s -&gt; 2.3s 개선 </li>
<li><strong>모바일 환경 Speed Index</strong> : 7.0s -&gt; 2.5s 개선</li>
<li><strong>Lighthouse 점수</strong>: 모바일 58 → 79, 데스크톱 89 → 95</li>
</ul>
<p>이건 단순히 “빠른 서비스”를 만든 차원이 아니었다. Server Component, Suspense, Streaming SSR 같은 개념들이 실제로 사용자 경험을 어떻게 바꾸는지 체감할 수 있었던 순간이었다.</p>
<h3 id="한-주를-마치며">한 주를 마치며</h3>
<p>2주차를 지나며 깨달은 건, 이제 단순히 기능만 구현하는 단계는 끝났다는 것이다. 아키텍처와 사용자 경험을 함께 고민하며, 코드 한 줄 한 줄이 실제 서비스의 성능과 UX에 직접적인 영향을 준다는 점을 체감했다.</p>
<p>이번 주의 가장 큰 보람은, 멘토님과 끊임없이 의견을 주고받으며 함께 머리를 맞대고 새로운 난제를 해결한 경험이었다. 처음에는 막막했던 Streaming SSR 구축과 데이터 프리페칭 문제를, 여러 시행착오 끝에 해결하면서 마치 목표로 했던 높은 산을 넘어선 듯한 성취감을 느꼈다.</p>
<p>앞으로는 React 18의 Concurrent Feature까지 활용해 더 탄탄하고 효율적인 구조를 만들어보고 싶다. 이번 주의 경험은, 단순한 코드 작성이 아니라 문제를 정의하고, 전략을 세우고, 실행하며 극복하는 과정 자체가 성장의 열쇠임을 다시 한번 깨닫게 해주었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인턴으로 살아남기 1주차 회고록]]></title>
            <link>https://velog.io/@k_gu_wae123/pinkfong-internship</link>
            <guid>https://velog.io/@k_gu_wae123/pinkfong-internship</guid>
            <pubDate>Tue, 09 Sep 2025 13:37:45 GMT</pubDate>
            <description><![CDATA[<h2 id="사람은-나면-서울로-보내고-말은-나면-제주도로-보내라">사람은 나면 서울로 보내고 말은 나면 제주도로 보내라</h2>
<p>‘사람은 나면 서울로 보내고, 말은 나면 제주도로 보내라’라는 속담이 있다. 뛰어난 재능을 가진 사람은 더 많은 기회가 있는 서울로 보내 배우고 성장해야 하고, 말(馬)은 말을 기르기 좋은 천혜의 환경을 가진 제주도에서 길러야 한다는 뜻이다.</p>
<p>나 역시 대구에서 GDGoC KNU Organizer 활동을 하며, 많은 서울 분들과 네트워킹을 하면서 인사이트를 쌓았다. 인프라 차이를 실감하면서, 직장을 구하기 위해서는 서울로 가야 한다는 생각이 들었다. 결국 수도권으로 취업 준비를 시작했고, 이번에 전환형 인턴십으로 4개월간 더핑크퐁컴퍼니에 합류하게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/c42beb80-1d92-44b9-8633-8045052b2e96/image.png" alt=""></p>
<h2 id="회사-첫날-온보딩">회사 첫날 온보딩</h2>
<p>첫날, 회사 곳곳을 구경하며 유튜브의 다양한 버튼들을 눈으로 확인했다. 실버버튼, 골드버튼, 다이아버튼까지… 이렇게 많은 버튼들이 있다는 사실만으로도 놀라웠다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/b35c3f07-23cc-481d-a31b-2dde2acfff65/image.png" alt=""></p>
<p>그 후 곧바로 온보딩이 진행되었다. 아기상어를 비롯한 귀여운 캐릭터들이 등장하는 PPT를 통해 회사가 소개되었는데, 단순히 귀엽다는 느낌을 넘어, 회사가 가진 콘텐츠 IP를 활용해 다양한 시도를 하고 있다는 점이 인상적이었다.</p>
<p>1일차가 이렇게 끝나는 줄 알았지만, 온보딩을 마치고 나서 곧바로 멘토님들과 첫 인턴 과제 관련 회의가 이어졌다. 처음에는 단순히 프레임워크 버전을 올리고 테스트만 통과시키면 되는 줄 알았다. 그러나 막상 마이그레이션을 시작하자 예상치 못한 문제들이 줄줄이 튀어나오며 긴장감이 감돌았다. Next.js 14와 React 18로의 업그레이드는 단순한 버전 업이 아니라, 프로젝트 구조, 라우팅 방식, 컴포넌트 구분 등 모든 층위에 영향을 미치는 큰 작업이었다.</p>
<p>코드를 한 줄 한 줄 뜯어보며 문제를 해결해야 하는 순간마다 머리가 복잡해지고, ‘이거 정말 끝까지 해낼 수 있을까?’라는 생각이 스쳤다. 하지만 동시에, 이런 난관 속에서 얻을 수 있는 경험과 배움에 대한 기대감도 커졌다. 이 첫 날의 긴장감과 도전은 이후 인턴 생활 전반에 걸쳐 나를 한 단계 성장하게 만드는 원동력이 되리라는 것을 직감할 수 있었다.</p>
<h2 id="1주차-업무-시작">1주차 업무 시작(?)</h2>
<p>업무를 시작하면서 예상과 달랐던 점은, 인턴에게 주어지는 권한이 생각보다 제한적이지 않았다는 것이다. 마이그레이션을 진행하면서 권한이 필요할 때 멘토님께 요청하면 즉시 권한을 부여받을 수 있어, 개발 과정에 큰 제약 없이 자유롭게 작업할 수 있었다.</p>
<p>첫 번째 난관은 Page Router에서 App Router로의 전환 준비였다. 두 라우터 구조는 근본적으로 큰 차이가 있었기 때문에, 서버 컴포넌트 구조를 이해하고 Suspense와 ErrorBoundary를 적용하는 과정에서 많은 고민이 필요했다. 특히 Ant Design Mobile과 TUI Editor 같은 라이브러리들은 App Router 환경에서 예상치 못한 동작을 보였고, placeholder 초기화 문제와 CSS 충돌 문제를 해결하는 데 상당한 시간이 소요되었다.</p>
<p>마이그레이션 과정 중에는 서비스 워커와 Fast Refresh 충돌로 인해 무한 리로드가 발생하기도 했다. 브라우저 캐싱과 HMR이 충돌하며 발생한 문제였는데, 개발 환경에서 서비스 워커를 비활성화하고 DevTools에서 캐시를 초기화하는 방식으로 해결했다. 이를 통해 단순히 코드를 수정하는 것만으로는 문제를 해결할 수 없고, 개발 환경과 도구까지 이해해야 한다는 것을 깨달았다.</p>
<p>이 과정에서 얻은 가장 큰 깨달음은, 마이그레이션을 진행할 때 단계적 전략이 필요하다는 점이었다. 처음부터 모든 것을 완벽히 마이그레이션하려고 하면 코드 구조를 충분히 이해하지 못한 상태에서 속도가 나지 않았다. 그래서 단계적으로</p>
<ol>
<li>버전 업</li>
<li>Page Router → App Router 전환 (모든 컴포넌트는 일단 클라이언트 컴포넌트 유지)</li>
<li>서버 컴포넌트 전환 &amp;&amp; Streaming SSR 적용</li>
<li>테스트 코드 적용하기</li>
</ol>
<p>과 같이 전략을 수립했고 순차적으로 진행했다. 처음에는 단순히 버전 업이 목표였지만, 이 과정을 거치며 Page Router와 App Router의 동작 방식을 깊이 공부하게 되었고, 수많은 시행착오와 골머리를 앓는 경험도 함께 겪었다.</p>
<p>PR이 올라올 때마다, 학생 시절과는 비교도 안 될 만큼 심도 있는 멘토님의 코드 리뷰가 쏟아졌다. 예전 같으면 쉽게 답할 수 있는 부분도 많았지만, 이번에는 “이 부분에 대해 반박해보라”는 적극적인 피드백과 함께 고민할 만한 리뷰들이 이어졌다. 덕분에 기술적인 고민을 깊게 할 수 있었고, 개념이나 코드 스타일 등 이전에는 몰랐던 세세한 부분까지 배울 수 있었다. 매번 리뷰를 받을 때마다 조금씩 성장하는 느낌이 들었고, 그 과정 자체가 큰 즐거움이자 배움이 되었다.</p>
<p>1주차가 끝날 무렵, 프로젝트는 Next.js 14 + React 18 환경에서 정상 동작했고, Page Router에서 App Router까지 전환을 완료했다. 또한 초기 JS 번들이 372kB에서 151kB로 약 59% 감소하는 성과도 거두었다. 하지만 이 과정에서 겪은 수많은 트러블슈팅 경험과 고민도 있었지만 내가 해냈다는 성취감과 함께 재미난 트러블 슈팅을 많이 겪어서 정말 재밌다라는 생각이 들었다.</p>
<h2 id="한주를-되돌아보며">한주를 되돌아보며</h2>
<p>서울로 올라오면서 나는 하나의 목표를 세웠다. 바로 아침 6시에 일어나 러닝을 하고, 여유롭게 출근하는 ‘미라클 모닝’을 실천하는 것이다. 실제로 지금은 매일 아침 6시에 일어나 러닝을 하고 있고, 이 습관 덕분에 하루를 상쾌하게 시작하고 있다.</p>
<p>집에 돌아와서는 밤 12시까지도 코드를 다듬고, 새로운 문제를 고민하며 배우는 시간이 이어졌다. 피곤함이 몰려올 때도 있었지만, 매 순간 스스로의 성장과 성취를 체감하며 묘한 흥분과 즐거움을 느꼈다. 이 과정에서 스마일게이트 회고록 때 썼던 ‘난로 찾기’ 글이 떠오르며 힘이 솟았다.</p>
<p>이번 한 주를 돌아보며 깨달은 것은, 단순히 하루를 버티는 것이 아니라 매 순간 배우고 도전하는 과정 자체가 나를 한 단계 더 성장하게 만든다는 점이었다. 이 경험을 통해 앞으로 남은 인턴 기간에도 더 많은 것을 배우고, 스스로의 한계를 시험하며 성장하고자 하는 강한 의지가 생겼다. 이제 막 첫 걸음을 내디뎠지만, 매일이 기대와 설렘으로 가득하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[나의 늦은 상반기 회고록]]></title>
            <link>https://velog.io/@k_gu_wae123/my-retrospect-2025-1</link>
            <guid>https://velog.io/@k_gu_wae123/my-retrospect-2025-1</guid>
            <pubDate>Wed, 06 Aug 2025 05:13:02 GMT</pubDate>
            <description><![CDATA[<p>Organizer로서의 활동을 내려놓고 한 달여가 지난 지금, 2025년 상반기를 돌아보고 정리하는 시간을 가져보고자 한다. 최근 운 좋게도 채용연계형 인턴직무에 최종 합격하게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/8d9ef32e-60b4-47a5-a680-7d82775a10e1/image.png" alt=""></p>
<h2 id="이건-첫번째-레슨---gdgoc-knu-리드로서의-활동">이건 첫번째 레슨 - GDGoC KNU 리드로서의 활동</h2>
<p>GDGoC KNU의 Organizer로서 보낸 지난 시간들은 내게 정말 특별하고 값진 경험이었다. 행사를 기획하고 진행하면서 많은 사람들과 함께 일했고, 그 과정에서 팀원들과 함께 여러 도전을 극복해 나갔다. 돌이켜보면 때로는 부담스럽고 어려운 일도 많았지만, 하나씩 이뤄갈 때마다 그만큼 더 큰 보람과 성취감을 느낄 수 있었다.</p>
<p>특히, 리더로서의 마지막 순간들이 아직도 생생하게 기억난다. 역할을 내려놓는 순간, 예상치 못한 많은 감사 메시지와 응원을 받았다. 사실 리드를 하면서도 내가 잘하고 있는지 끊임없이 고민하고 의구심을 가졌었다. 그러나 이렇게 진심 어린 감사와 격려를 받을 때마다 내가 했던 활동들이 정말 의미 있는 일이었음을 깨닫게 되었고, 모든 노력이 헛되지 않았음을 알게 되어 정말 감사하고 행복했다. 덕분에 다음 리드에게도 자신 있게 자리를 넘겨줄 수 있었다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/k_gu_wae123/post/eae0d586-da80-4420-9776-7f789aee9a32/image.jpg" alt=""></th>
<th><img src="https://velog.velcdn.com/images/k_gu_wae123/post/7271b018-32d9-4dcd-aea9-779effb45d20/image.JPG" alt=""></th>
<th><img src="https://velog.velcdn.com/images/k_gu_wae123/post/d428a86f-ba67-4610-9162-8f85411c8d43/image.jpg" alt=""></th>
<th><img src="https://velog.velcdn.com/images/k_gu_wae123/post/49147a03-05a6-4692-a2b8-cf55249c5f1b/image.JPG" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td><figcaption align="center">[정말 고맙고도 소중했던 멤버분들]</figcaption></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<hr>
<h2 id="취업-준비">취업 준비</h2>
<p>2024년 11월부터 본격적인 취업 준비를 시작했다. 총 120건의 지원서 중 20% 정도가 서류 합격했고, 최종적으로 4곳에서 합격 통보를 받았다. 1월 말 최초로 합격한 곳은 준비가 부족하다고 느껴 더 준비해보고 싶었고, 이후 2월부터 더 집중적으로 준비하여 추가로 3곳에서 최종 합격을 받을 수 있었다. 결국 지금의 채용연계형 인턴직무를 선택하게 되었다.</p>
<h2 id="이제-두-번째-레슨---서류-작성하기">이제 두 번째 레슨 - 서류 작성하기</h2>
<p>피그마를 활용해 이력서와 포트폴리오를 작성했다. 제출하고 수정하는 과정을 반복하며 어떻게 하면 처음 내 서류를 보는 사람이 한눈에 명확히 이해할 수 있을까를 계속 고민했다. 특히 개발자가 아닌 일반인의 시선에서도 내가 어떤 고민을 가지고 그것을 해결하기 위해 어떤 노력을 했는지 쉽게 파악할 수 있도록 표현하는 것에 집중했다. 이러한 과정 끝에 이력서는 버전 14, 포트폴리오는 버전 10이 되었고, 결과적으로 가독성을 높이는 데 유리한 피그마를 사용한 것이 많은 도움이 되었다. (+ 같이 취업을 준비하시는 현재 네이버 가신 지인분이 있었는데 하루종일 이력서 다듬고 서로 피드백하고, 코멘트 달기를 수백번은 한 것 같다.)</p>
<blockquote>
<p>우스갯소리로 이제 기본 피그마 활용도는 잘 다루지 않을까 싶다.</p>
</blockquote>
<p align="center"> 
  <img src="https://velog.velcdn.com/images/k_gu_wae123/post/ff89fe28-7f8d-4d3e-8822-0d803537bacf/image.png"
       style="padding: 0;margin:0;"> 
  [수많은 이력서와 포트폴리오들]
</p>


<h2 id="드디어-세-번째-레슨---면접-준비와-성장">드디어 세 번째 레슨 - 면접 준비와 성장</h2>
<p>면접 준비 과정에서 내가 가장 크게 느낀 점은 기본적인 지식과 기초 개념에 대한 깊이 있는 이해가 부족하다는 것이었다. 면접을 준비할 때마다 단순히 개념을 암기하는 수준이 아니라, 그 개념이 실제 어떻게 동작하고, 왜 필요한지까지 깊이 파고드는 학습이 필요하다는 것을 알게 되었다. 면접장에서 받았던 질문들이 생각보다 더 깊이 있고 체계적인 답변을 요구했기 때문에, 매번 면접 후에는 내가 어떤 부분에서 이해가 부족했는지 명확히 정리하고 부족한 CS 기초나 React 관련 개념을 다시 복습하는 시간을 꾸준히 가졌다.</p>
<p>이 과정에서 GPT를 활용하면서, 모르는 부분에 대해 단순히 답변을 얻는 것이 아니라, 왜 그런 답이 나오는지, 어떤 원리와 과정을 통해 그 결과가 도출되는지를 집요하게 묻고 이해하려고 노력했다. GPT의 도움으로 스스로의 사고 과정을 명확히 하고, 더욱 깊이 있는 질문을 던지며 학습할 수 있었다. 또한 면접 과정에서 예상하지 못했던 신선하고 깊이 있는 질문들을 받을 때마다 큰 충격을 받았는데, 이러한 충격이 오히려 더 열심히 공부하게 되는 원동력이 되었다. 결국 면접 준비 기간 동안 실제 프로젝트 개발할 때보다 더 크고 빠르게 성장할 수 있었다고 느낀다.</p>
<p align="center"> 
  <img src="https://velog.velcdn.com/images/k_gu_wae123/post/12b33fde-b3b5-46e9-9433-0623870e96ef/image.png"
       style="padding: 0;margin:0;"> 
  [면접 준비하면서 기초 공부하기]
</p>

<h2 id="마지막-네-번째-레슨---나를-되돌아보며">마지막 네 번째 레슨 - 나를 되돌아보며</h2>
<p>개발자라는 직무는 취업 후에도 끊임없이 성장하고 학습해야 하는 직업이라 생각한다. 계속 꾸준히 성장하며 그 과정을 즐기면서 나아가면 좋겠다. 요즘 취업 준비를 하는 분들도 포기하지 말고, 자신이 개발을 진정으로 좋아한다면 꾸준히 노력하면 반드시 좋은 결과를 얻을 수 있을 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Smilegate Devcamp] CI/CD 개선기]]></title>
            <link>https://velog.io/@k_gu_wae123/Smilegate-Devcamp-CICD</link>
            <guid>https://velog.io/@k_gu_wae123/Smilegate-Devcamp-CICD</guid>
            <pubDate>Fri, 14 Mar 2025 07:47:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Smilegate Devcamp내에서 프로젝트를 하면서 겪었던 트러블 슈팅과 이에 대해 고민했던 글입니다.</p>
</blockquote>
<p>빠른 코드 통합 및 자동 검증을 위해, GitHub Actions 내에서 pnpm과 Turborepo를 활용하여 Lint, build, type-check 등의 프로세스를 그룹화하여 CI 파이프라인을 구축했습니다. 하지만 초기 파이프라인 설계에서는 몇 가지 성능 병목이 발생했고, 이를 해결하기 위해 여러 단계의 최적화를 거쳤습니다. 이번 글에서는 CI/CD 환경을 최적화하는 과정과 성능 개선 결과를 정리해보겠습니다.</p>
<h2 id="초기-cicd의-문제점">초기 CI/CD의 문제점</h2>
<ol>
<li><strong>Job Runner 리소스 할당:</strong></li>
</ol>
<ul>
<li>각 job(<code>changes</code>, <code>type-check</code>, <code>lint</code>, <code>build</code>, <code>required</code>)은 별도의 ubuntu-latest 러너에서 실행됩니다.</li>
<li>GitHub Actions의 <code>ubuntu-latest</code> 러너는 기본적으로 2-core CPU, 7GB RAM, 14GB SSD를 제공합니다.</li>
</ul>
<ol start="2">
<li><strong>비효율적인 리소스 활용:</strong></li>
</ol>
<ul>
<li>각 job마다 새로운 환경을 설정하고 있습니다(<code>actions/checkout</code>, <code>pnpm/action-setup</code>, 의존성 설치 등).</li>
<li>모든 job에서 pnpm install을 반복 실행하고 있습니다.</li>
<li>작업 간에 캐시를 공유하려고 시도하지만, 매번 새 환경을 설정하는 오버헤드가 있습니다.</li>
</ul>
<ol start="3">
<li><strong>병렬성과 의존성 문제:</strong></li>
</ol>
<ul>
<li><code>type-check</code>와 <code>lint</code>는 <code>changes</code> 완료 후 병렬로 실행될 수 있지만, 각각 별도의 러너에서 실행됩니다.</li>
<li><code>build</code>는 <code>changes</code>, <code>lint</code>, <code>type-check</code> 모두 완료될 때까지 기다려야 합니다.</li>
<li><code>required</code>는 모든 작업이 완료될 때까지 기다린 후 상태를 확인합니다.</li>
</ul>
<ol start="4">
<li><strong>메모리 관리 문제:</strong></li>
</ol>
<ul>
<li>각 job에서 <code>NODE_OPTIONS: --max-old-space-size=4096</code> 설정은 메모리 제한을 4GB로 설정하지만, 이는 7GB RAM 환경에서 여전히 제한적입니다.</li>
</ul>
<ol start="5">
<li><strong>불필요한 중복 작업:</strong></li>
</ol>
<ul>
<li>모든 job에서 코드 체크아웃과 환경 설정을 반복합니다.</li>
<li>각 단계마다 <code>pnpm install</code>을 실행하므로, 의존성 설치 시간이 중복됩니다.</li>
</ul>
<ol start="6">
<li><strong>파이프라인 구조의 비효율성:</strong></li>
</ol>
<ul>
<li><code>needs</code> 구문을 통해 작업 간 의존성을 설정하지만, 이로 인해 병렬 처리의 이점이 감소합니다.</li>
<li><code>required</code> job이 모든 작업 상태를 확인하기 위해 별도의 러너를 사용하는 것은 리소스 낭비입니다.</li>
</ul>
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/47c25884-1de8-43fa-9c07-5755f643ce89/image.png" alt="" width='500px'/>

<h2 id="1차-최적화">1차 최적화</h2>
<ol>
<li><strong>단일 Job Runner로 통합:</strong></li>
</ol>
<ul>
<li>모든 작업이 하나의 ubuntu-latest 러너에서 실행되어 환경 설정 중복을 제거하였습니다.</li>
</ul>
<ol start="2">
<li><strong>효율적인 캐싱:</strong></li>
</ol>
<ul>
<li><code>actions/setup-node</code>의 캐시 기능 활용하였습니다. (<code>cache: &#39;pnpm&#39;</code>)</li>
<li>명확한 캐시 의존성 경로 지정하였습니다. (<code>cache-dependency-path</code>)</li>
<li>오프라인 모드 선호 (<code>--prefer-offline</code>)로 네트워크 요청 최소화하였습니다.</li>
</ul>
<ol start="3">
<li><strong>Turborepo를 활용한 병렬 실행:</strong></li>
</ol>
<ul>
<li><code>--parallel</code> 플래그로 가능한 작업을 동시에 실행하였습니다.</li>
<li><code>--filter</code> 옵션으로 필요한 패키지만 선택적으로 처리하였습니다.</li>
</ul>
<ol start="4">
<li><strong>간소화된 프로세스:</strong> </li>
</ol>
<ul>
<li><code>type-check</code>는 제외하고 중요 작업인 <code>lint</code>와 <code>build</code>에만 집중하였습니다.</li>
<li>불필요한 반복 작업과 중간 확인 단계 제거하였습니다.</li>
</ul>
<ol start="5">
<li><strong>타임아웃 설정 최적화:</strong></li>
</ol>
<ul>
<li>타임아웃을 15분에서 10분으로 줄여 불필요한 러너 사용 시간 방지하였습니다.</li>
</ul>
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/a17d241c-edb8-45a6-b688-5e993659418a/image.png" alt="" width='500px'/>

<h3 id="🧐-1차-개선-후-여전히-발생했던-문제점-및-고민들">🧐 1차 개선 후 여전히 발생했던 문제점 및 고민들</h3>
<p>CI 시간내에서 51s가 그렇게 빠르다고 매력적으로 다가오지 못했었고 여전한 하나의 job runner가 3개의 task가 전부 돌아간다라고 파악을 했기 때문에 오히려 메모리 경합(memory contention)과 리소스 사용 비효율성이 발생한다고 생각했습니다.</p>
<h3 id="🔍-세부-분석">🔍 세부 분석</h3>
<ol>
<li><strong>하나의 Job Runner에서 3개의 Task가 동시에 실행됨 → 리소스 경합 발생</strong></li>
</ol>
<ul>
<li>CI/CD 환경에서 논리 CPU 2개, 7GB RAM을 가진 GitHub Actions Runner는 기본적으로 멀티태스킹이 제한적입니다.</li>
<li>Lint, Build, Type-Check를 동시에 실행하면 CPU 및 메모리를 경쟁적으로 사용하게 되
며, 컨텍스트 스위칭이 과도하게 발생합니다.</li>
</ul>
<ol start="2">
<li><strong>과도한 컨텍스트 스위칭으로 인한 성능 저하</strong><ul>
<li>OS 스케줄러는 각 태스크를 번갈아 실행하며 <strong>컨텍스트 스위칭(Context Switching)</strong>을 수행합니다.</li>
<li>이 과정에서 CPU가 태스크 상태를 저장/복구하는 오버헤드가 발생하여 실행 시간이 늘어납니다.</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>메모리 누수(Memory Leak) vs 메모리 경합(Memory Contention)이란?</strong></p>
<ul>
<li>메모리 누수(Memory Leak): 프로그램이 사용한 메모리를 해제하지 않아 점점 늘어나는 현상</li>
<li>메모리 경합(Memory Contention): 여러 프로세스가 한정된 메모리를 공유하면서 경쟁하는 현상</li>
</ul>
<p>➡️ 결과적으로 CI 속도가 드라마틱하게 개선이 되지 않았습니다.</p>
</blockquote>
<h2 id="2차-최적화">2차 최적화</h2>
<p>이후 테스트 코드를 점진적으로 작성을 하게 되면서 CI 시간은 다시 1분 14초대로 늘어나게 되어버렸습니다. 그래서 추가적인 개선이 필요했고 두 번째 개선을 진행하였습니다.</p>
<ol>
<li><strong>3개의 Job Runner로 분리:</strong></li>
</ol>
<ul>
<li>모든 작업을 각각의 job runner로 분리하여 ubuntu-latest 러너에서 실행되어 메모리 경합과 CPU Bound의 작업 처리 내에서 오버헤드가 발생하지 않도록 하였습니다.</li>
</ul>
<ol start="2">
<li><strong>효율적인 캐싱:</strong></li>
</ol>
<ul>
<li><code>actions/setup-node</code>의 캐시 기능 활용하였습니다. (<code>cache: &#39;pnpm&#39;</code>)</li>
<li>명확한 캐시 의존성 경로 지정하였습니다. (<code>cache-dependency-path</code>)</li>
<li>오프라인 모드 선호 (<code>--prefer-offline</code>)로 네트워크 요청 최소화하였습니다.</li>
</ul>
<ol start="3">
<li><strong>Turborepo를 활용한 병렬 실행:</strong></li>
</ol>
<ul>
<li><code>--parallel</code> 플래그로 가능한 작업을 동시에 실행하였습니다.</li>
<li><code>--filter</code> 옵션으로 필요한 패키지만 선택적으로 처리하였습니다.</li>
</ul>
<ol start="4">
<li><strong>간소화된 프로세스:</strong> </li>
</ol>
<ul>
<li><code>type-check</code>는 제외하고 중요 작업인 <code>lint</code>와 <code>build</code>에만 집중하였습니다.</li>
<li>불필요한 반복 작업과 중간 확인 단계 제거하였습니다.</li>
</ul>
<ol start="5">
<li><strong>타임아웃 설정 최적화:</strong></li>
</ol>
<ul>
<li>타임아웃을 15분에서 10분으로 줄여 불필요한 러너 사용 시간 방지하였습니다.</li>
</ul>
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/30bd3812-3362-4b68-8635-484af3525c2a/image.png" alt="" width='600px'/>

<h2 id="3차-최적화">3차 최적화</h2>
<p>2차 최적화 이후 49s까지 최적화하였으나 결국 build랑 test같은 경우 각 job runner 하나씩 job하나를 처리하고 있으므로 idle 상태가 생긴다고 판단했었습니다. 그래서 태스크 작업의 각 특성을 파악하여 새롭게 추가적으로 최적화를 진행하였습니다.</p>
<ol>
<li><strong>2개의 Job Runner로 분리:</strong></li>
</ol>
<ul>
<li>test와 lint같은 경우 I/O Bound에 해당한다고 생각했고 build 같은 경우 CPU Bound라고 생각했기 때문에 test와 lint는 하나의 job runner로 같이 묶었고 matrix를 활용하여 git action내에서 병렬 실행할 수 있도록 하였습니다. Build는 온전히 하나의 job runner에 분배하여 vCPU 2개를 온전히 할당하도록 하였습니다.</li>
</ul>
<ol start="2">
<li><strong>효율적인 캐싱:</strong></li>
</ol>
<ul>
<li><code>.turb</code> 캐시를 추가하였습니다.</li>
</ul>
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/19ebdc33-fba8-4349-9c39-b688224009c5/image.png" alt="" width='600px'/>

<img src="https://velog.velcdn.com/images/k_gu_wae123/post/4b82ae2d-8934-4e8d-8d2f-1350f31dffa1/image.png" alt="" width='300px'/>


<blockquote>
<p>🚀 결과적으로 30s까지 최대 29s까지 개선할 수 있었습니다!!!</p>
</blockquote>
<h3 id="🔍-3차-개선에서-찾아보고-고민했던-내용들">🔍 3차 개선에서 찾아보고 고민했던 내용들</h3>
<h4 id="1-cpu-bound와-io-bound가-뭔가요">1. <strong>CPU Bound와 IO Bound가 뭔가요?</strong></h4>
<p>CPU Bound와 IO Bound는 컴퓨터 프로그램이나 작업의 성능 병목 현상이 어디에서 발생하는지 설명하는 용어입니다.</p>
<p><strong>CPU Bound (CPU 바운드)</strong></p>
<ul>
<li>CPU의 처리 능력에 제한을 받는 작업입니다.</li>
<li>주로 복잡한 계산, 알고리즘 실행, 데이터 처리와 같은 연산 집약적 작업입니다.</li>
<li>특징: CPU 사용률이 높고(종종 100%에 가까움), CPU가 더 빠르면 작업 속도가 향상됩니다.</li>
</ul>
<p><strong>IO Bound (입출력 바운드)</strong></p>
<ul>
<li>입출력 작업(디스크, 네트워크, 데이터베이스 등)에 제한을 받는 작업입니다.</li>
<li>주로 데이터를 읽고 쓰는 작업으로, 이 과정에서 CPU는 대부분 대기 상태입니다.</li>
<li>병렬 작업을 할수록 성능이 향상됩니다.</li>
<li>특징: CPU 사용률이 낮고, 더 빠른 스토리지, 네트워크, 또는 IO 시스템으로 성능이 향상됩니다.</li>
</ul>
<blockquote>
<p><strong>🧐 CI/CD 파이프라인에서 판단한 기준은요?</strong></p>
</blockquote>
<ul>
<li><code>lint</code>와 <code>test</code>는 주로 많은 파일을 읽고 간단한 검증을 수행하므로 IO Bound에 가깝다라고 판단했습니다.</li>
<li>Code <code>build</code>, Bundling, Optimization은 복잡한 변환을 수행하므로 CPU Bound에 가깝습니다.</li>
</ul>
<hr>
<h4 id="2-🧐-lint-test를-하나의-job-runner에-분배하면-메모리-경합과-리소스-경합이-일어나는것-아닌가요"><strong>2. 🧐 Lint, Test를 하나의 job runner에 분배하면 메모리 경합과 리소스 경합이 일어나는것 아닌가요?</strong></h4>
<p><strong>IO Bound의 핵심 특성</strong> </p>
<ul>
<li>IO Bound 작업은 CPU를 100% 활용하지 않습니다. 대부분의 시간은 파일을 읽거나 쓰는 것을 기다리는 대기 시간입니다.</li>
<li>CPU가 한 작업에서 IO를 기다리는 동안, 다른 작업을 처리할 수 있습니다.</li>
<li>예를 들어 lint:web이 파일을 디스크에서 읽는 동안, CPU는 test:web 작업을 진행할 수 있습니다.</li>
</ul>
<p><strong>리소스 활용의 상보성</strong></p>
<ul>
<li>IO Bound 작업은 다른 시스템 리소스(디스크 IO, 네트워크)를 주로 사용합니다.</li>
<li>이런 작업들이 동시에 실행되면 CPU, 메모리, IO를 골고루 활용하게 됩니다.</li>
<li>작업들이 서로 다른 리소스를 주로 사용하므로 경합이 적습니다.</li>
</ul>
<p><strong>Turborepo의 최적화</strong></p>
<ul>
<li>Turborepo는 태스크 간의 의존성을 스마트하게 관리하고 병렬 실행을 최적화합니다.</li>
<li>내부적으로 작업 스케줄링을 효율적으로 처리하여 리소스 활용도를 높입니다.</li>
<li><code>--parallel</code> 플래그를 사용하면 가능한 한 많은 태스크를 동시에 실행하면서도 리소스 제약을 고려합니다.</li>
</ul>
<p><strong>캐싱의 영향</strong></p>
<ul>
<li>Turborepo 캐시는 이전에 실행한 작업의 결과를 저장하여 변경되지 않은 파일에 대한 작업을 건너뜁니다.</li>
<li>이로 인해 파일 시스템 IO가 크게 줄어들고, 실제로 필요한 작업만 수행됩니다.</li>
</ul>
<blockquote>
<p><strong>🔍 모던 운영체제의 스케줄링</strong></p>
<p>현대 운영체제는 IO 작업과 CPU 작업을 효율적으로 스케줄링하도록 설계되어 있습니다.
IO 대기 시간 동안 다른 프로세스에 CPU를 할당하는 것을 잘 처리합니다.
2개의 vCPU 환경에서도 여러 IO Bound 작업을 효율적으로 멀티태스킹할 수 있습니다.</p>
</blockquote>
<p><strong>🎊 결론</strong></p>
<ul>
<li>따라서 <code>lint:web</code>, <code>lint:@workspace/ui</code>, <code>test:web</code>과 같은 IO Bound 작업들은 하나의 Job Runner에서 동시에 실행되어도 서로 크게 방해하지 않고, 오히려 시스템 리소스를 더 효율적으로 활용할 수 있습니다. </li>
<li>반면 CPU Bound 작업인 <code>build</code>는 별도의 Job Runner에서 실행하는 것이 더 효율적입니다.</li>
</ul>
<h4 id="참고했던-자료">참고했던 자료</h4>
<ul>
<li>Git Action 워크플로우 및 청구 : <a href="https://docs.github.com/ko/enterprise-cloud@latest/actions/administering-github-actions/usage-limits-billing-and-administration">https://docs.github.com/ko/enterprise-cloud@latest/actions/administering-github-actions/usage-limits-billing-and-administration</a></li>
<li>CPU Bound &amp; IO Bound : <a href="https://velog.io/@carrykim/%EB%B6%84%EC%82%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C-2-3.-CPU-Bound-IO-Bound">https://velog.io/@carrykim/%EB%B6%84%EC%82%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C-2-3.-CPU-Bound-IO-Bound</a></li>
</ul>
<h1 id="최적화를-통한-본인-회고">최적화를 통한 본인 회고</h1>
<p>2차 개선 이후 3차 개선을 진행하면서 운영체제의 특성을 고려한 최적화를 적용했습니다. 이 과정에서 제가 설정한 한계를 깨고 더 나은 방향으로 개선함으로써 큰 성취감을 느꼈습니다. 동시에 이전에 제한된 지식으로 쉽게 만족하려 했던 점을 반성하게 되었습니다. 시스템 최적화에는 CS 기초 지식이 얼마나 중요한지 실감했고, 이를 계기로 CS 공부를 더욱 깊이 있게 진행할 예정입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Smilegate Dev Camp 회고] 고민하는 방법 배우기]]></title>
            <link>https://velog.io/@k_gu_wae123/Smilegate-Dev-Camp-reflection</link>
            <guid>https://velog.io/@k_gu_wae123/Smilegate-Dev-Camp-reflection</guid>
            <pubDate>Wed, 12 Mar 2025 07:18:31 GMT</pubDate>
            <description><![CDATA[<p>Dev Camp에서의 2개월 여정이 끝났다. 합격 소식에 설레던 순간이 엊그제 같은데, 눈 깜짝할 사이에 시간이 흘러버렸다. 이 짧고도 길었던 2개월을 돌아보며, 새롭게 얻은 것들과 깨달음을 정리해보고자 한다.</p>
<h1 id="2달간의-일정">2달간의 일정</h1>
<h2 id="캠프-일정">캠프 일정</h2>
<p>2024년 12월 27일부터 총 2개월간 캠프를 진행했다. 작년과 달리 올해는 프로젝트에 집중하여 약 2주간 PMP 및 아키텍처 설정 기간을 거친 후, 나머지 기간 동안 본 프로젝트를 수행했다.
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/7695dd83-b754-4d3f-8cf2-ede696e4eaec/image.jpg" alt=""></p>
<h3 id="정말-잘-왔다라고-느껴졌던-순간">정말 잘 왔다라고 느껴졌던 순간</h3>
<p>회사 복지가 미쳤다. 스마일게이트라는 회사가 직원들을 정말 많이 생각해고 배려해준다라는 느낌을 엄청 많이 받았던 것 같다.</p>
<p>우선 구내식당 밥이 정말 맛있다. 캠프내에서 인솔을 도와주시는 과장님께서 판교에서 제일 맛있는 구내식당이라 얘기해주셨는데 먹고나서야 이해해버렸다.</p>
<div style="display: flex; gap: 3px;">
  <img src="https://velog.velcdn.com/images/k_gu_wae123/post/76cf08c5-14a3-4fda-8fb1-4bc8cecf8482/image.jpg" alt="" width='200px'/>
  <img src="https://velog.velcdn.com/images/k_gu_wae123/post/0b17c734-0cab-4673-9cab-d1fa20ddae54/image.jpg" alt="" width='200px'/>
  <img src="https://velog.velcdn.com/images/k_gu_wae123/post/07764269-3712-4955-bbfc-6336bce6c258/image.jpg" alt="" width='200px'/>
</div>

<blockquote>
<p>2달간 먹었던 밥들 중 top3라고 생각했던 음식들, 퀼리티가 진짜 미쳤다. 덕분에 체중이 늘었다. 🐷</p>
</blockquote>
<p>그리고 놀랍게도 회사 휴게실에 다트 게임기가 있다. 그래서 개발 도중 스트레스를 받거나 캠퍼분들과 친목을 위해 다트를 많이 애용했던 것 같다.</p>
  <img src="https://velog.velcdn.com/images/k_gu_wae123/post/57dc013b-cb9c-4b5e-aa2d-a992b9a7a1b0/image.jpg" alt="" width='400px'/>

<blockquote>
<p>Smilegate내에 당당히 1위 점수 달성하고 이름을 등록하고 왔다(?) 🎯🎯</p>
</blockquote>
<p>제일 신기했던 점은 캠프 기간동안 만들어지긴 했지만 회사내에 폭포가 있다는 것이다. 구내식당 바로 옆에 있는데 먹고 나오면서 폭포를 보며 많이 리프레쉬 했던 것 같다.</p>
  <img src="https://velog.velcdn.com/images/k_gu_wae123/post/d7d89105-a1db-4e60-be22-4500b686dff6/image.jpg" alt="" width='400px'/>


<h3 id="캠프를-통해서-배웠던-것들">캠프를 통해서 배웠던 것들</h3>
<p>이번 캠프에서 27일부터 12월 30일까지 &#39;성장&#39;이라는 키워드를 중심으로 교육을 들었다. 
이후 프로젝트를 진행하면서 일반 프로젝트와 비교했을 때, 이 캠프에서 정말 유니크한 경험을 얻은 것 같다. 특히 내가 어떤 목표를 가지고 어떤 방향으로 나아가야 할지 고민하는 방법을 배웠다고 생각한다.</p>
<h1 id="성장이란-무엇인가">성장이란 무엇인가</h1>
<h2 id="개발자는-어떻게-성장할까">개발자는 어떻게 성장할까?</h2>
<p>개발자는 어떻게 성장을 할까?
개발자는 프로젝트라는 경험을 내에서 목표설정을 통한 과정, 회고의 pipleline을 통한 정량적인 산출물을 통해 성장을 한다.
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/5e8bc5ae-82f9-4581-a366-949d1169e1f8/image.png" alt=""></p>
<h3 id="목표설정">목표설정</h3>
<p>이 과정에서 의미 있는 산출물을 내기 위해서는 올바른 목표 설정이 필수적이며, 그 목표를 향해 일관되게 나아가는 것이 중요하다.
그럼 목표란 어떤 것인가?</p>
<blockquote>
<p>목표(目標)
: 달성하고자 하는 바람직한 장래의 상태</p>
</blockquote>
<p>위는 목표는 사전적 정의이다. 목표는 무엇을 만든다. 무엇을 위한 구체적인 실행과정이 나올 수 있어야한다. 이렇게 구체적이고 계획이 설정 가능하며, 지향점이 명확해야한다.</p>
<h3 id="회고">회고</h3>
<p>이후 자신의 행동과 경험을 되돌아보며 회고를 하게 된다. 그러나 과거의 일을 단순히 떠올리기만 하는 것이 과연 회고의 본질일까?
일반적으로 우리가 알고 있는 성찰은 블로그에서 흔히 볼 수 있는 형태이다: 
&quot;프로젝트에서 이러한 고민을 했고, 이런 것들을 배웠으며, 이를 통해 저러한 것들을 깨달았다&quot;는 식의 단답형 글로 마무리되는 경우가 많습니다.
과연 이런 것이 진정한 성찰일까요? 그저 순간순간에 따른 단편적인 반성에 그치는 것은 아닐지 의문이 들었다.
그러면 어떻게 성찰을 해야할까? 결국 성찰이란 통찰을 통한 앞서 우리가 얘기했던 목표설정과 과정, 회고의 pipeline을 통한 정량적인 데이터를 통해 결국 성찰이 이어지게 된다.</p>
<h2 id="그럼-본인은">그럼 본인은?</h2>
<p>이번 캠프를 통해 나는 무엇을 얻었을까?</p>
<p>사실 이 부분은 3월 4일부터 쓰기 시작했지만, 한동안 손을 놓고 있었다. 정확하게는 글을 쓸 수가 없었다. 분명 성장을 위해 노력했고, 그 결과로 성과도 있었다. 2달 동안 개발하며 2023년 FE 개발에 깊이 빠지려고 1학기 휴학했을 때 느꼈던 그 즐거움을 다시 만끽했다. 그래서 12월 27일부터 2월 28일까지 새벽 3~4시까지 코드를 짜고, 아침 7시 반에 회사에 출근했다. 알람도 잘 못 듣고 잠을 좋아하는 나에게는 기적 같은 일이었다. 그런데 신기하게도 힘들지 않았다.</p>
<p>그런 기분을 느끼며 보냈지만, 내가 본질적으로 무엇을 얻었는지 고민하다 보니 단순히 과거를 떠올리는 내용밖에 생각나지 않았다.</p>
<p>캠프에 처음 들어올 때는 올해는 서울·경기권에서 버티며, 캠프가 끝난 뒤라도 회사 하나는 붙는 것이 목표였다. 하지만 캠프 중 감기에 걸리고, 몸이 무거운 상태에서 과제 테스트를 진행하던 기업을 포기했다. 결국 오퍼를 제안받은 회사마저 포기하고 말았다. 캠프를 수료할 때까지 나는 FE 개발자로서 지속할 이유와 방향성을 찾지 못하고 대구로 다시 내려오게 되었다.</p>
<p>그래서 난 캠프를 통해 얻은 성찰을 결국 내가 FE 개발자라는 직군을 포기하지 않고 앞으로 10년, 20년 지속적으로 계속할 수 있는 목표를 찾는 것으로 삼고, 7일 동안 고민하는 시간을 가졌다.</p>
<h2 id="짜장면-아닌-난로-찾기">짜장면 아닌 난로 찾기</h2>
<p>웃기게도 이 고민을 7일 동안 했지만 내가 정말 어떤 목표를 위해 나아가는지 찾지 못했다.</p>
<p>처음에는 내가 FE 개발자로서 앞으로 10년, 20년 지속적으로 계속할 수 있는 목표는 FE 업계 내에서 흔히 말하는 유명 개발자가 되는 것이라고 생각했다. 하지만 어떻게 보면 이건 내가 FE 개발자로서 지속적인 삶을 살아가는 목표보다는 단순한 동기부여가 아닐까 하는 생각이 들었다.</p>
<p>그러면 정말 내가 굳이 FE라는 직군의 개발자를 지속적으로 하는 이유가 뭘까에 대해 고민하던 찰나, 이력서와 포트폴리오를 정리하는 도중 재미난 사건이 발생했다.</p>
<p>포트폴리오 작성 중 CI/CD 파이프라인을 개선하는 내용을 정리하다가 &#39;조금 더 개선해볼 만하겠는데&#39;라는 생각이 머리에 불현듯 스쳐지나갔다. 그래서 github action 무료 자체 스펙을 검색하게 됐고, 이를 CPU와 IO bound인 부분까지 고민하면서 최적화했다. 그 결과 pnpm이라는 빌드 툴에서 CI를 평균 30초대까지 단축시켰다. 이전까지는 yarn berry라는 빌드 툴보다 pnpm이 속도 면에서 뒤처질 수밖에 없어서 CI 속도를 줄이는 데 한계가 있다고 생각했다. 하지만 결국 내가 무지했기에 틀에 사로잡혀 이런 생각을 못했던 것 같다. 내가 생각했던 한계를 깨부수고 개선했을 때 시계를 보니, CI 파이프라인 포트폴리오를 정리하기 시작한 00시 30분에서 어느새 07시가 지나 있었다. 놀랍게도 기분이 엄청 좋았고 정신이 말끔했다.</p>
<p>이 과정에서 난 딥다이브 과정을 통해 내가 생각했던 한계를 깨부쉈을 때 정말 희열을 느낀다는 것을 깨달았고, 내가 개발을 정말 좋아한다는 것을 알게 됐다.이를 통해 본인을 되돌아보니 캠프에서도 항상 내 관심사에 대해서는 정말 깊게 파고 이야기하는 것도 깨닫게 되었다.</p>
<p>그래서 개발자를 앞으로도 지속적으로 10년, 20년을 하는 이유는, 내가 한계라고 생각했던 프레임을 깨뜨렸을 때 느끼는 그 짜릿한 희열을 계속 맛보고 싶어서 개발자라는 직군을 지속적으로 선택하는 것이 아닐까?</p>
<p>추가적으로 현업을 다니고 있는 친구를 통해 FE개발을 처음 접하게 되면서 초반 개발 도중에 생겼던 기술적인 고민을 친구랑 자주 얘기하게 되면서 많은 인사이트를 확장시키면서 얻었던 희열을 얻었고 삶을 살아가면서 이런 것을 얻게 해준 것이 Frontend라는 스택이 처음이었던 것 같아서 FE라는 직군을 계속적으로 하고 싶다라는 생각이 들었던 것 같다. 비유를 하자면 Frontend라는 직군에 첫사랑을 빠지게 되어서 계속 쭉 이어져오고 있는 것 아닐까?</p>
<h3 id="난로를-통한-퍼즐-맞추기">난로를 통한 퍼즐 맞추기</h3>
<p>개발 문화에는 커피챗이라는 문화가 있는데, 현업에 계신 개발자분들이 내가 고민했던 내용을 털어놓으면 정말 친절하게 가르쳐주신다. 하지만 유명하거나 현업에 다니시는 개발자분들께 콜드메일을 보내 커피챗을 요청할 때 처음에는 용기가 많이 필요하다. 그래서 일반 학생들은 보통 커피챗보다는 부트캠프나 전국연합 IT 동아리에 들어가 멘토링을 통해 고민을 물어보고 인사이트를 얻는 경우가 많다.</p>
<p>나는 친구를 통해 자유롭게 기술적인 고민을 얘기할 수 있지만, 다른 사람들에게는 이런 기회를 얻기가 쉽지 않다는 것에 공감했다. 그래서 FE 개발을 하는 지인들과 내가 깨닫게 되거나 얻었던 인사이트를 최대한 서로 공유하고 최대한 고민을 서로 해결하고 싶다라는 생각이 들었던 것 같다.</p>
<p>이런 경험들이 쌓이다 보니, 지인들뿐만 아니라 더 넓은 FE 개발 생태계의 개발자들에게도 내가 고민했던 내용과 해결책들을 공유하고 싶다는 욕구가 생겼고, 그래서 대한민국 FE 업계 최대 컨퍼런스인 FE conf에 연사로 나가보고 싶다는 목표가 생겼던 것 같다. 또한 학생 개발자들이 느꼈던 고민을 내가 아는 선에서 최대한 도움을 주고 싶다는 생각에 부트캠프 멘토로 참가해 멘토링도 하고 싶은 목표가 생겼던 것 같다. 그래서 처음 항상 남들이 나에게 꿈이 뭔가요? 라고 물어볼때 이런 내용들을 답한게 아닐까 싶다.</p>
<p>마지막으로, 오퍼를 받았음에도 취준을 포기하고 다시 대구로 내려온 이유는, 단순히 관종이어서가 아니라 캠프에서 한계를 넘어선 고민까지 해보며 얻은 깨달음을 온전히 녹여내지 못한 채 취업에 성공하는 것이 내 자신에게 최선을 다하지 않은 것 같다는 생각이 들었다. 지금의 이 시간이, 미래의 내가 진정으로 원하는 길을 찾아가는 데 꼭 필요한 과정이지 않을까라는 생각이 든다.</p>
<h1 id="smilgate-devcamp-지원을-고민하는-분들께">Smilgate Devcamp 지원을 고민하는 분들께</h1>
<p>이때까지 FE 개발을 공부하며 다양한 활동에 참여했고, 여러 대외활동에 매진하느라 방향성에 관한 깊은 고민은 해보지 못했던 것 같다. 하지만 Smilegate Devcamp를 통해 그런 계기를 마련했다고 생각한다. 이제 돌이켜보면, 캠프 내에서 단순히 &#39;개발을 했다&#39;, &#39;무언가를 만들어냈다&#39;보다는 &#39;성장&#39;이라는 키워드를 처음부터 끝까지 강조한 이유를 어렴풋이 알 것 같다. 진정 본인이 원하는 목표가 무엇인지, 내가 정말 무엇을 얻어가고 싶은지 깊이 고민해보고, 그 과정을 통해 스스로의 성장을 경험하고자 하는 사람이라면 한 번쯤 지원해보면 좋겠다는 생각이 든다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[원점 회귀(어느 평범한 프론트엔드 취준 개발자의 회귀록)]]></title>
            <link>https://velog.io/@k_gu_wae123/Regression</link>
            <guid>https://velog.io/@k_gu_wae123/Regression</guid>
            <pubDate>Wed, 25 Dec 2024 06:04:53 GMT</pubDate>
            <description><![CDATA[<h1 id="올해를-되돌아보며">올해를 되돌아보며</h1>
<p>올해를 한 단어로 표현하자면 고군분투라고 표현하고 싶다. 정말 성장하고 싶었고 학교내에서 다른 사람들에게 있어서 프론트엔드 개발하면 김규회?라는 이런 이름을 듣고 싶었다.</p>
<h1 id="내가-프론트엔드를-선택했었던-이유">내가 프론트엔드를 선택했었던 이유</h1>
<p>사실 나는 컴퓨터학부에 처음부터 관심이 없었다. 성적을 맞추기 위해 들어갔고, 재수와 수능 준비로 인해 공부에 대한 피로도가 극에 달해 이미 오래전부터 공부에 싫증이 났었다. 그래서 1학년 때는 술만 마시고 학교 성적에는 전혀 신경 쓰지 않았던 것 같다.</p>
<p>그 후 군대를 다녀왔고, 여전히 컴공 공부에 흥미가 없던 나는 친구와 함께 사진에 빠져 살았다. 새로운 삐까번쩍한 장비를 사는 것이 너무 기분 좋았고, 사진을 찍어 SNS에 올려 좋아요 반응을 보며 만족감을 느꼈다. 덕분에 운 좋게 아마추어 사진 잡지에도 실리기도 했다.</p>
<p>그러다 친구 덕분에 GDSC KNU 커뮤니티에 들어가게 되었고, 처음으로 내가 하고 싶은 것을 화면에 구현해보니 정말 재미있었다. 그 흥미 덕분에 공부도 재미있어졌고, 프론트엔드 개발에 미친 듯이 손을 대며 계속해서 공부해왔다. 물론 3학년부터는 학교 공부도 손에 잡으려 했지만, 오랜만이라 암기가 잘 되지 않았다. 그럼에도 불구하고 프론트엔드 개발만큼은 내가 노력한 만큼 실력도 올랐고 성과도 잘 나왔다. 덕분에 여러 대외활동을 할 수 있었고, 좋은 멘토님도 만날 수 있었다.</p>
<h1 id="일벌려놓기">일벌려놓기</h1>
<p>올해의 목표는 경북대 재학생 중 프론트엔드 최고가 되는 것이었다. 그러다 보니 내게 개발적으로 도움이 되는 활동이라면 몸이 부서지는 한이 있더라도 하려고 했던 것 같다.</p>
<h2 id="smilegate-online-camp">SmileGate online camp</h2>
<p>작년에 처음으로 스마일게이트에서 면접 기회를 얻게 되어서 지원했는데, 준비도 부족했고 결국 탈락하게 되었다. 하지만 캠프장님께서 탈락자 중에서도 온라인 캠프의 기회를 주셔서 덕분에 1월부터 2월까지 온라인 캠프를 진행할 수 있었다. 한 번 해보고 싶었던 기술을 활용하여 프로젝트를 한 달간 진행했고, 캠프를 통해 많은 것을 배웠다.</p>
<h2 id="멋쟁이사자-univ-tf-프론트엔드">멋쟁이사자 Univ TF 프론트엔드</h2>
<blockquote>
</blockquote>
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/a001acc1-5cec-4b60-85d7-39133cf249e2/image.png" width='500'/>
일본에서 당시 멋사 유니브 공고 보고 지원해야겠다라는 다짐을 하게 되었을 때

<p>캠프가 끝나고 학기로 들어오면서 성장한 나의 모습을 더 증명해보고 싶었다. 그래서 멋쟁이사자 경북대 운영진으로 활동하던 중, 전국 커뮤니티 단톡방에 멋쟁이사자 커뮤니티팀 신설 공고가 올라왔다. 대학생으로 구성된 멋쟁이사자 Univ TF 개발팀에 프론트엔드로 지원하여 면접을 보고 최종 합격했다. 멋쟁이사자 유니브의 홈페이지를 맡아 개발하면서 특히 해커톤 랜딩페이지를 개발했다. 힘들었지만 재미도 있었고, 덕분에 여름 방학 때 열렸던 멋쟁이사자 해커톤의 Thanks to 영상에 김규회라는 이름이 나오니 뿌듯함을 느꼈다.</p>
<h2 id="gdg-on-campus-knu">GDG on Campus KNU</h2>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/k_gu_wae123/post/42b3d2c7-21df-4f75-b482-caa2036f458f/image.png" width='500'/></th>
<th><img src="https://velog.velcdn.com/images/k_gu_wae123/post/7a7438fa-6b76-4411-846a-8f0eace489ae/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/k_gu_wae123/post/36783395-46db-4cdc-b2e0-798a9addf0d0/image.png" width='500'/></th>
</tr>
</thead>
<tbody><tr>
<td>&gt; <strong>구글 코리아 리드 온보딩</strong></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<p>방학 때 드디어 3기를 마무리하고 4기를 시작하면서 내가 대표자리를 맡게 되었다. 흥미를 느껴 2기 때부터 쭉 참여해오며 동경했었던 GDG on Campus의 리드 자리를 맡게 되어 매우 기뻤다. 반학기를 리드로 지내오면서 GDSC가 GDG on Campus로 바뀌고 다양한 해프닝도 많았지만, 그래도 재미있고 열심히 운영하려고 노력했다. 2, 3기에서 다들 부족하다고 피드백을 주던 부분을 중점적으로 개선하고, 모두가 친한 커뮤니티를 만들기 위해 많은 노력을 기울였다. 
내년 7월까지 총 4기가 진행되는데 그때까지 최대한 성공적으로 마무리해보기 위해 노력할 예정이다.</p>
<h2 id="카카오테크캠퍼스-2기">카카오테크캠퍼스 2기</h2>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/k_gu_wae123/post/3ec4b193-75ed-4f43-9c6d-4856cb568f7e/image.png" alt="image.jpg1"></th>
<th><img src="https://velog.velcdn.com/images/k_gu_wae123/post/23a1dc7e-44a8-4bd9-bbf9-ef7a7c639595/image.png" alt="image.jpg2"></th>
<th><img src="https://velog.velcdn.com/images/k_gu_wae123/post/219aa377-51e3-4745-a90e-96d18d73b9f3/image.png" alt="image.jpg3"></th>
</tr>
</thead>
</table>
<blockquote>
<p><strong>멘토님 회사탐방!</strong></p>
</blockquote>
<p>카카오테크캠퍼스를 통해 나는 GDG on Campus에서 느꼈던 한계를 한층 깨부수었다고 생각한다. 나에게 있어서 카카오테크캠퍼스는 대장장이와도 같았다. 
5개 대학 중 유일하게 Step 1, 2, 3 동안 전부 강연자로 뽑혀 카테톡-er로 기술 강연을 할 수 있어서 정말 뿌듯했고, 멘토님을 통해서 정말 많이 배웠다. 
특히 시야를 넓힐 수 있었던 것이 가장 큰 수확이었다. 덕분에 깔끔한 코드에 대해 고민했던 부분을 말끔하게 해결할 수 있었고, 소통할 때 공격적으로 대했던 나를 반성하는 계기가 되었다. 사람을 대하는 법을 배울 수 있었고, 이는 나의 성장을 더욱 촉진시켜 주었다.</p>
<h1 id="한계돌파">한계돌파?</h1>
<p>다양한 활동을 통해 개발 공부에 매진했던 나에게도 이제는 슬슬 정체되어 있다는 기분이 들었고, 한 번 더 성장하고 싶었다. GDG 커뮤니티 덕분에 서울에 올라갈 기회가 자주 있었고, 다양한 현업자들과 이야기를 나누며 학교에서도 나름 인정받는 분들이 생겼다. 하지만 여전히 세상에는 더 잘하는 사람들이 많다는 생각이 들었다. 특히, 동학번 친구들이 졸업하면서 별 생각 없이 이야기를 나누다가 후배들과의 대화에서 기술적으로 소통할 정도로 다들 놀라울정도로 빠르게 성장하는 모습을 보게 되었다. 이를 보며 나도 다시 한 번 더 성장해서 한계를 깨부셔야겠다는 생각이 들었다.</p>
<h3 id="치열하게-살면서-주변-사람들을-보다보니">치열하게 살면서 주변 사람들을 보다보니</h3>
<p>요즘 취업이 어렵다는 것을 확실히 느낀다. 유튜브나 영상 매체에서 서류 탈락 사례를 많이 보았지만, 실제로 겪어보니 생각보다 뼈아프다. 나 또한 서류를 많이 제출했지만, 주변 사람들을 보니 예전 같았으면 충분히 합격할 수 있었던 분들이 현재는 취업에 어려움을 겪고 있어 안타깝다. 특히 개발자에서 금융권이나 공기업을 목표로 방향을 선회하는 분들을 보니 아쉬움이 크다. 같은 목표를 향해 나아가는 동료 한 분이 포기하는 모습을 보니 더욱 그렇다.</p>
<h1 id="re-다시-한번-더">RE 다시 한번 더?</h1>
<p>올해와 내년 취업 준비를 앞두고 인턴 경험을 쌓아야겠다는 결심을 하고, 2달 전부터 본격적으로 취업 준비에 뛰어들기 시작했다. 대략 12개의 서류를 제출했지만, 2개를 제외하고는 모두 탈락했다. 갑작스러운 준비였던 만큼 이력서를 내고 고치고를 반복해야 했다.</p>
<p>그 와중에 작년 12월에 떨어졌던 스마일게이트 Dev Camp에 올해는 최종 합격하게 되었다. 작년에는 온라인으로 캠프에 참가했지만, 올해는 다시 신청하여 성장한 모습을 캠프장님께 보여드리고 싶었고, 취업에 있어서 조금 더 한층 성장하고 싶어서 지원했다. 사실 면접을 너무 못 봐서 떨어질 줄 알았는데, 최종 합격 소식을 듣고 정말 깜짝 놀랐다. 그래서 올해 1월에도 온라인으로 서울에 자주 올라가 개발을 했었는데, 내년에는 판교에서 생활할 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인시스템: StoryBook, Chromatic과 Git Action설정하기 : Visual Regression Test]]></title>
            <link>https://velog.io/@k_gu_wae123/Visual-Regression-Test</link>
            <guid>https://velog.io/@k_gu_wae123/Visual-Regression-Test</guid>
            <pubDate>Fri, 13 Dec 2024 13:39:07 GMT</pubDate>
            <description><![CDATA[<p>Main: <a href="https://github.com/zagdang/design-system/pull/7">https://github.com/zagdang/design-system/pull/7</a></p>
<h1 id="디자인-시스템-배포-자동화-storybook-chromatic-github-actions-활용기">디자인 시스템 배포 자동화: Storybook, Chromatic, GitHub Actions 활용기</h1>
<h2 id="도입-배경">도입 배경</h2>
<p>디자인 시스템을 운영하면서 가장 중요하게 생각한 부분은 &#39;팀 간 원활한 소통&#39;과 &#39;UI 일관성 유지&#39;였다. 특히 여러 팀원들과 디자인 토큰을 공유하고 피드백을 주고받는 과정에서, 지속적인 배포와 시각적 테스트의 필요성을 느꼈다.</p>
<h2 id="storybook--chromatic-설정">Storybook &amp; Chromatic 설정</h2>
<h3 id="1-chromatic-설정">1. Chromatic 설정</h3>
<pre><code class="language-yaml">name: &#39;Chromatic Deploy&#39;

on:
  push:
    branches: [main, develop]

jobs:
  deploy:
    name: &#39;Deploy Storybook&#39;
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: &#39;18&#39;

      - name: Enable Corepack
        run: |
          corepack enable
          corepack prepare yarn@4.5.3 --activate

      - name: Install dependencies
        run: |
          yarn install --immutable
          yarn dlx @yarnpkg/sdks vscode

      - name: Build Storybook
        run: yarn workspace @zagdang/ui build-storybook

      - name: Deploy to Chromatic
        uses: chromaui/action@latest
        with:
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
          workingDir: packages/ui
          storybookBuildDir: storybook-static
          autoAcceptChanges: true</code></pre>
<p>이렇게 설정하면:</p>
<ul>
<li>모든 push 이벤트에서 Chromatic 배포가 자동으로 실행된다</li>
<li>UI 변경사항을 시각적으로 확인할 수 있다</li>
<li>브랜치/커밋 간의 차이를 쉽게 비교할 수 있다</li>
</ul>
<h3 id="2-pr-체크-자동화">2. PR 체크 자동화</h3>
<pre><code class="language-yaml"># .github/workflows/pr-check.yml
name: &#39;Chromatic PR Check&#39;

permissions:
  pull-requests: write
  contents: read

on:
  pull_request:
    types: [opened, synchronize, reopened]
    branches: [main, develop]

jobs:
  review:
    name: &#39;Review Changes&#39;
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: &#39;18&#39;

      - name: Enable Corepack
        run: |
          corepack enable
          corepack prepare yarn@4.5.3 --activate

      - name: Install dependencies
        run: |
          yarn install --immutable
          yarn dlx @yarnpkg/sdks vscode

      - name: Build Storybook
        run: yarn workspace @zagdang/ui build-storybook --webpack-stats-json

      - name: Create Preview
        id: chromatic
        uses: chromaui/action@latest
        with:
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
          workingDir: packages/ui
          storybookBuildDir: storybook-static
          onlyChanged: true
          exitZeroOnChanges: true

      - name: Set URL
        if: success()
        run: echo &quot;STORYBOOK_URL=${{ steps.chromatic.outputs.storybookUrl }}&quot; &gt;&gt; $GITHUB_ENV

      - name: Comment PR
        uses: thollander/actions-comment-pull-request@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          message: |
            📚 Storybook Preview Ready!
            🔍 Review URL: ${{ env.STORYBOOK_URL }}</code></pre>
<p>PR 체크 워크플로우는:</p>
<ul>
<li>main 또는 develop 브랜치로 PR 생성</li>
<li>GitHub Actions가 자동으로 Chromatic PR Check 워크플로우 시작</li>
<li>PR이 업데이트되거나 재오픈될 때도 동일한 프로세스 실행</li>
<li>개발 환경 준비 (Yarn Berry, Node.js, code 체크아웃 설정)</li>
<li>의존성 설치 및 빌드(Storybook 빌드, 패키지 설치)</li>
<li>Chromatic 테스트<ul>
<li>빌드된 Storybook을 Chromatic에 업로드</li>
<li>각 스토리별 스크린샷 생성</li>
<li>이전 버전과 비교</li>
</ul>
</li>
<li>PR 업데이트<ul>
<li>프리뷰 URL 생성</li>
<li>환경변수에 URL 저장</li>
<li>자동으로 PR에 코멘트 추가</li>
<li>Storybook 프리뷰 링크 포함</li>
</ul>
</li>
<li>리뷰 프로세스</li>
</ul>
<h2 id="visual-regression-test의-이점">Visual Regression Test의 이점</h2>
<p>실제로 얻은 장점들:</p>
<ol>
<li><p><strong>디자인 일관성 유지</strong></p>
<ul>
<li>의도치 않은 UI 변경을 즉시 감지할 수 있었다</li>
<li>변경사항을 시각적으로 확인하고 승인할 수 있었다</li>
<li>실수로 인한 디자인 깨짐을 방지할 수 있었다</li>
</ul>
</li>
<li><p><strong>팀 커뮤니케이션 개선</strong></p>
<ul>
<li>UI 변경사항을 링크로 공유할 수 있어 피드백이 수월했다</li>
<li>브랜치별로 독립된 Storybook 환경을 제공받을 수 있었다</li>
<li>리뷰 프로세스가 더 효율적으로 변했다</li>
</ul>
</li>
<li><p><strong>배포 자동화</strong></p>
<ul>
<li>GitHub Actions를 통한 자동 배포로 시간을 절약했다</li>
<li>PR마다 자동으로 시각적 테스트가 실행됐다</li>
<li>수동 작업이 크게 줄었다</li>
</ul>
</li>
</ol>
<h2 id="구체적인-워크플로우">구체적인 워크플로우</h2>
<ol>
<li><p><strong>개발 과정</strong></p>
<pre><code class="language-bash"># 새로운 기능 브랜치 생성
git checkout -b feature/new-component

# 컴포넌트 개발 및 스토리 작성
yarn storybook

# 변경사항 커밋
git commit -m &quot;feat: Add new component&quot;

# PR 생성
git push origin feature/new-component</code></pre>
</li>
<li><p><strong>자동화된 검증</strong></p>
<ul>
<li>PR 생성 시 자동으로 pr-check.yml 실행</li>
<li>Chromatic에서 시각적 테스트 수행</li>
<li>리뷰어에게 변경사항 링크 자동 공유</li>
</ul>
</li>
<li><p><strong>배포 프로세스</strong></p>
<ul>
<li>main 브랜치 머지 시 chromatic-deploy.yml 실행</li>
<li>자동으로 최신 버전 Storybook 배포</li>
<li>GitHub Pages를 통해 문서 공개</li>
</ul>
</li>
</ol>
<h2 id="실제-사용해본-chromatic의-장점">실제 사용해본 Chromatic의 장점</h2>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/9288287e-656d-4af5-840e-521850d95af4/image.png" alt=""></p>
<h3 id="1-상세한-ui-리뷰-기능">1. 상세한 UI 리뷰 기능</h3>
<p><strong>픽셀 단위의 변경사항 감지</strong>
실제 경험했던 사례를 보면:</p>
<ul>
<li>Button 컴포넌트의 패딩값이 1px만 바뀌어도 바로 캐치할 수 있었다</li>
<li>폰트 사이즈나 line-height 변경도 즉시 감지됐다</li>
<li>심지어 그라데이션 각도가 살짝 틀어진 것도 발견할 수 있었다</li>
</ul>
<p>예를 들어 이런 작은 변경사항도 감지했다 ( shadcn이라 다른예시를 가져와봤습니다. 예시로만 봐주세요!):</p>
<pre><code class="language-css">.button {
  padding: 8px 16px;  /* 이전 */
  padding: 8px 15px;  /* 변경 후 - 1px 차이 감지 */
}</code></pre>
<p>이게 진짜 유용했던 게, UI 깨짐을 사전에 발견할 수 있었다. 특히 UI가 어떻게 변하는지 한눈에 볼 수 있어서 좋았다.</p>
<h3 id="변경사항-승인거부-워크플로우">변경사항 승인/거부 워크플로우</h3>
<p>실제 워크플로우는 이렇게 진행됐다:</p>
<ol>
<li>변경사항 발견 시 시각적으로 하이라이트 표시</li>
<li>변경 부분을 클릭하면 before/after 비교 가능</li>
<li>승인 또는 거부를 통해 변경사항 관리</li>
<li>거부된 변경사항은 자동으로 PR에 코멘트로 달림</li>
</ol>
<h3 id="2-협업-기능">2. 협업 기능</h3>
<p>** 변경사항 코멘트 **
정말 직관적이었다:</p>
<ul>
<li>변경된 부분에 직접 코멘트를 달 수 있었다</li>
<li>특정 영역을 드래그해서 그 부분에 대한 피드백을 남길 수 있었다</li>
<li>코멘트에 @멘션으로 특정 팀원 호출 가능</li>
</ul>
<p>실제로 이런 대화가 가능했다:</p>
<pre><code>팀원1: &quot;@프론트엔드 버튼의 그림자가 디자인 가이드와 좀 다른 것 같아요&quot;
본인: &quot;네, box-shadow 값을 수정할께요.&quot;</code></pre><p>** 팀 공유 기능**
링크 하나로 모든 정보 공유가 가능했다:</p>
<ul>
<li>브랜치별 스토리북 주소 자동 생성</li>
<li>변경사항 비교 페이지 직접 공유</li>
<li>리뷰 히스토리도 함께 공유</li>
</ul>
<p>특히 디자이너와 협업할 때 정말 편했다. 디자이너가 &quot;이 버튼 좀 이상한데요?&quot;라고 하면, 바로 해당 컴포넌트의 변경사항 페이지를 공유할 수 있었다.</p>
<p>** 버전 히스토리**
커밋 단위로 모든 변경사항을 추적할 수 있었다:</p>
<ul>
<li>언제, 누가, 어떤 변경을 했는지 모두 기록</li>
<li>특정 시점으로 돌아가서 확인 가능</li>
<li>각 변경사항의 승인자도 기록</li>
</ul>
<h3 id="3-cicd-통합">3. CI/CD 통합</h3>
<p><strong>자동화된 테스트</strong>
매 PR마다 자동으로:</p>
<ul>
<li>스토리북 빌드 체크</li>
<li>비주얼 리그레션 테스트 실행</li>
<li>테스트 결과 PR에 코멘트로 추가</li>
</ul>
<p>예를 들어 Button 컴포넌트 변경 후 PR을 올리면:</p>
<ol>
<li>Chromatic이 자동으로 테스트 실행</li>
<li>변경된 스토리들 감지</li>
<li>시각적 차이점 하이라이트</li>
<li>결과를 PR에 코멘트로 추가</li>
</ol>
<p>** PR 상태 관리**
GitHub와 긴밀하게 연동되어:</p>
<ul>
<li>PR 체크 항목으로 자동 추가</li>
<li>리뷰 승인 시 자동으로 체크 통과</li>
<li>PR 머지 가능 여부 자동 제어</li>
</ul>
<p>이런 자동화 덕분에 실수로 버그가 있는 코드가 머지되는 것을 여러 번 방지할 수 있었다.</p>
<p>이렇게 세밀한 관리 기능들이 있어서, 디자인 시스템의 품질을 훨씬 더 높은 수준으로 유지할 수 있었다. 특히 여러 팀원들이 동시에 작업할 때 발생할 수 있는 실수들을 사전에 잡아낼 수 있어서 좋았다.</p>
<h2 id="앞으로의-계획">앞으로의 계획</h2>
<p>현재 구축한 시스템을 기반으로:</p>
<ul>
<li>jest 세팅 </li>
<li>테스트 커버리지 확대</li>
<li>성능 메트릭 모니터링 추가</li>
<li>문서화 자동화 강화</li>
</ul>
<p>이런 자동화 시스템 덕분에 디자인 시스템 관리가 한결 수월해졌고, 특히 팀 간 협업 과정이 많이 개선됐다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인 시스템 배포에 관한 고찰 : Yarn Berry와 Turborepo 도입기]]></title>
            <link>https://velog.io/@k_gu_wae123/design-system-yarn-berry</link>
            <guid>https://velog.io/@k_gu_wae123/design-system-yarn-berry</guid>
            <pubDate>Fri, 13 Dec 2024 13:22:32 GMT</pubDate>
            <description><![CDATA[<p>Main: <a href="https://github.com/zagdang/design-system/pull/3">https://github.com/zagdang/design-system/pull/3</a></p>
<h1 id="새로운-프로젝트-새로운-도전">새로운 프로젝트, 새로운 도전</h1>
<p>최근 새로운 프로젝트를 시작하게 되어 디자인 시스템을 구현하게 되었다. 여기서 프로젝트에 관련된 가장 큰 고민은 서비스들이 배포한 npm을 다운받아 UI를 구성하다가 버그가 났을 때 이슈를 빠르게 수정하고 배포해야 하는 상황이었다. 팀원 또한 이것에 관하여 npm 배포보다는 오히려 서비스 내에 모노레포를 넣는 것이 어떻겠느냐라는 의견이 있었고, 이 과정에서 디자인 시스템을 분리하여 서비스 레포의 번들 사이즈를 줄이고 그 대신 배포 속도를 확실히 높인 Yarn Berry와 Turbo Repo를 택하게 되었다.</p>
<h2 id="왜-yarn-berry와-turborepo를-선택했나">왜 Yarn Berry와 Turborepo를 선택했나?</h2>
<h3 id="node_modules의-구조적-문제점-상세-분석">node_modules의 구조적 문제점 상세 분석**</h3>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/8afd0d68-6681-4802-875f-dd4a2547ce77/image.png" alt=""></p>
<h3 id="1-중첩된-node_modules-구조로-인한-디스크-공간-낭비">1. 중첩된 node_modules 구조로 인한 디스크 공간 낭비**</h3>
<p><strong>실제 구조 예시</strong></p>
<pre><code>node_modules/
├── react/
│   └── node_modules/
│       ├── prop-types/
│       └── loose-envify/
├── react-dom/
│   └── node_modules/
│       ├── prop-types/
│       ├── loose-envify/
│       └── scheduler/
└── next/
    └── node_modules/
        ├── react/
        │   └── node_modules/
        │       └── prop-types/
        └── webpack/</code></pre><p><strong>발생하는 문제</strong></p>
<ol>
<li><p><strong>중복 설치</strong></p>
<ul>
<li><code>prop-types</code>가 react, react-dom, next/react 각각의 node_modules에 설치됨</li>
<li>동일한 패키지가 프로젝트 내에 최소 3번 이상 중복 설치</li>
</ul>
</li>
<li><p><strong>디스크 공간 계산 예시</strong></p>
<ul>
<li>prop-types 패키지 크기: 약 500KB</li>
<li>3번 중복 설치 시: 1.5MB 차지</li>
<li>프로젝트 전체로 보면 수백 MB의 불필요한 공간 낭비</li>
</ul>
</li>
<li><p><strong>설치 시간 증가</strong></p>
<ul>
<li>각 중첩 레벨마다 의존성 검사 필요</li>
<li>동일 패키지 중복 다운로드로 인한 네트워크 부하</li>
</ul>
</li>
</ol>
<p>**2. 동일 패키지의 다양한 버전 중복 설치 현상</p>
<p><strong>실제 사례 예시</strong></p>
<pre><code>node_modules/
├── package-a/
│   └── node_modules/
│       └── lodash@4.17.21/
├── package-b/
│   └── node_modules/
│       └── lodash@4.17.20/
└── package-c/
    └── node_modules/
        └── lodash@4.17.19/</code></pre><p><strong>문제점 상세</strong></p>
<ol>
<li><p><strong>버전 충돌</strong></p>
<ul>
<li>package-a는 <a href="mailto:lodash@4.17.21">lodash@4.17.21</a> 필요</li>
<li>package-b는 <a href="mailto:lodash@4.17.20">lodash@4.17.20</a> 필요</li>
<li>package-c는 <a href="mailto:lodash@4.17.19">lodash@4.17.19</a> 필요</li>
<li>결과: 동일 패키지의 서로 다른 버전이 각각 설치됨</li>
</ul>
</li>
<li><p><strong>메모리 사용량 증가</strong></p>
<ul>
<li>각 버전별로 별도의 인스턴스 생성</li>
<li>런타임에서 불필요한 메모리 차지</li>
</ul>
</li>
<li><p><strong>번들 사이즈 증가</strong></p>
<ul>
<li>웹팩 같은 번들러가 각 버전을 개별적으로 번들링</li>
<li>최종 번들 크기 증가</li>
</ul>
</li>
</ol>
<p><strong>3. 깊은 의존성 트리로 인한 경로 길이 제한 문제</strong></p>
<p><strong>Windows 환경에서의 제한</strong></p>
<pre><code>C:\Project\node_modules\package-a\node_modules\package-b\node_modules\package-c\node_modules\package-d\node_modules\...</code></pre><ol>
<li><p><strong>Windows 경로 길이 제한</strong></p>
<ul>
<li>Windows의 기본 경로 길이 제한: 260자</li>
<li>node_modules 깊이가 깊어질수록 이 제한에 근접</li>
</ul>
</li>
<li><p><strong>실제 발생하는 오류 예시</strong></p>
<pre><code>Error: ENAMETOOLONG: name too long</code></pre><ul>
<li>빌드 실패</li>
<li>패키지 설치 실패</li>
<li>파일 접근 불가</li>
</ul>
</li>
</ol>
<p><strong>현실적인 영향</strong></p>
<ol>
<li><p><strong>개발 환경 문제</strong></p>
<pre><code class="language-javascript">// 이런 긴 경로를 가진 모듈 import 시
import module from &#39;./node_modules/package-a/node_modules/package-b/node_modules/package-c/lib/index.js&#39;</code></pre>
<ul>
<li>IDE에서 파일 열기 실패</li>
<li>소스 맵 생성 실패</li>
<li>디버깅 어려움</li>
</ul>
</li>
<li><p><strong>CI/CD 파이프라인 영향</strong></p>
<ul>
<li>Windows 기반 CI 서버에서 빌드 실패</li>
<li>배포 프로세스 중단</li>
<li>자동화 파이프라인 불안정</li>
</ul>
</li>
<li><p><strong>해결을 위한 임시 방편</strong></p>
<pre><code class="language-json">// package.json
{
  &quot;scripts&quot;: {
    &quot;postinstall&quot;: &quot;node ./scripts/fix-windows-paths.js&quot;
  }
}</code></pre>
<ul>
<li>별도의 경로 처리 스크립트 필요</li>
<li>프로젝트 복잡도 증가</li>
<li>유지보수 부담 가중</li>
</ul>
</li>
</ol>
<p>이러한 문제들은 프로젝트 규모가 커질수록 더욱 심각해지며, 특히 모노레포 환경에서는 그 영향이 배가된다.
디자인 시스템 내에서도 ui, icons, colors로 분리했었기 때문에 npm보다는 다른 패키지를 생각하였다.</p>
<h3 id="yarn-berry-turbo-repo-혁신적인-선택">Yarn Berry, Turbo Repo 혁신적인 선택</h3>
<p>** Yarn Berry를 첫 선택으로 한 이유**</p>
<p>디자인 시스템을 설계할 때 가장 중요하게 생각한 부분은 &#39;빠른 수정과 배포&#39; 였다. 여러 서비스에서 공통으로 사용하는 컴포넌트들이다 보니, 문제가 발생했을 때 얼마나 빨리 수정하고 배포할 수 있는지가 핵심이었다.</p>
<p><strong>1. Plug&#39;n&#39;Play의 강력한 이점</strong></p>
<p>기존 node_modules 방식이 아닌 PnP 방식을 사용하면서 얻은 장점들:</p>
<ul>
<li>zip 아카이브 방식으로 의존성 관리해서 설치 속도가 빨랐다</li>
<li>프로젝트 크기가 확 줄어들어서 레포 관리가 수월했다</li>
<li>TypeScript와의 호환성이 좋아서 개발할 때 편했다</li>
</ul>
<p><strong>2. 모노레포 구성의 편의성</strong></p>
<p>디자인 시스템을 이렇게 구성했다:</p>
<pre><code>packages/
├── ui/          # shadcn 기반 컴포넌트
├── icons/       # 아이콘 컴포넌트
└── colors/      # 디자인 토큰</code></pre><p>이렇게 나눠놓으니:</p>
<ul>
<li>각 패키지별로 독립적인 버전 관리가 가능했다</li>
<li>한 컴포넌트만 수정해도 바로 빌드하고 테스트할 수 있었다</li>
<li>패키지 간 의존성 관리가 깔끔했다</li>
</ul>
<p><strong>3. Zero-Install 시스템</strong></p>
<p>협업할 때 정말 편했던 점:</p>
<ul>
<li>레포 받자마자 바로 개발 시작할 수 있었다</li>
<li>의존성 설치 시간을 기다릴 필요가 없었다</li>
<li>CI/CD 파이프라인이 훨씬 빨라졌다</li>
</ul>
<p><strong>Turbo Repo 추가 도입</strong></p>
<p>Yarn Berry에 Turbo Repo를 더하면서 개발 환경이 더 좋아졌다.</p>
<p><strong>1. 빌드 시스템 최적화</strong></p>
<pre><code class="language-json">// turbo.json
{
  &quot;pipeline&quot;: {
    &quot;build&quot;: {
      &quot;dependsOn&quot;: [&quot;^build&quot;],
      &quot;outputs&quot;: [&quot;dist/**&quot;]
    },
    &quot;dev&quot;: {
      &quot;cache&quot;: false
    }
  }
}</code></pre>
<p>이렇게 설정하니:</p>
<ul>
<li>캐시 덕분에 빌드 시간이 확 줄었다</li>
<li>변경된 패키지만 다시 빌드해서 효율적이었다</li>
<li>다른 팀원들과 캐시를 공유할 수 있었다</li>
</ul>
<p><strong>2. 개발 생산성 향상</strong></p>
<p>실제로 체감한 변화들:</p>
<ul>
<li>코드 수정하고 바로 결과를 볼 수 있었다</li>
<li>테스트 실행 시간이 많이 단축됐다</li>
<li>여러 패키지 동시 작업이 수월해졌다</li>
</ul>
<p><strong>실제로 좋았던 점</strong></p>
<ol>
<li><p><strong>빠른 이슈 대응</strong></p>
<ul>
<li>버그 발견하면 바로 수정해서 적용할 수 있었다</li>
<li>여러 프로젝트에 동시 적용이 가능했다</li>
<li>배포 과정이 단순해졌다</li>
</ul>
</li>
<li><p><strong>개발 환경의 일관성</strong></p>
<ul>
<li>모든 팀원이 동일한 환경에서 개발할 수 있었다</li>
<li>의존성 문제로 고생할 일이 없었다</li>
<li>설정 관리가 편해졌다</li>
</ul>
</li>
<li><p><strong>퍼포먼스 향상</strong></p>
<ul>
<li>전체적인 빌드 시간이 혁신적으로 빨라졌다.<ul>
<li>일반적으로 배포시간이 1분대내에서 전부 해결되었다.</li>
<li>test.yml, pr-check.yml파일 도 전부 40초대, 30초대로 해결되었다.</li>
</ul>
</li>
<li>CI/CD 파이프라인 실행 시간도 크게 줄었다</li>
<li>개발할 때 대기 시간이 거의 없어졌다</li>
</ul>
</li>
</ol>
<h1 id="앞으로의-계획">앞으로의 계획</h1>
<p>지금은 기본적인 구조만 잡아놓은 상태다. 앞으로:</p>
<ul>
<li>테스트 자동화 더 강화할 예정</li>
<li>문서화 시스템 보강</li>
<li>빌드 최적화 더 진행할 계획</li>
</ul>
<p>이런 구조 덕분에 디자인 시스템 운영이 한결 수월해졌다. 특히 현재 진행중인 서비스에서 동시에 사용하는 상황에서 진가를 발휘했다고 팀원들에게 긍정적인 평가를 받았다. 앞으로 더 고도화를 진행해볼 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GDGoC KNU 4기 홈페이지 npm 패키지 내보기 - 서비스편]]></title>
            <link>https://velog.io/@k_gu_wae123/react-lazy-observer</link>
            <guid>https://velog.io/@k_gu_wae123/react-lazy-observer</guid>
            <pubDate>Thu, 05 Dec 2024 06:02:53 GMT</pubDate>
            <description><![CDATA[<p>이번에 제가 GDGoC KNU 홈페이지를 개발하면서 <code>react-lazy-observer</code> 패키지를 NPM에 배포하게 되었는데 이와 관련한 고찰과 패키지 소개를 하려 한다. 
놀랍게도 배포한 지 일주일 만에 150회가 넘는 다운로드를 기록했다.</p>
<h2 id="개발-배경">개발 배경</h2>
<p>블로그 기능에 관련해서 팀원과 개발하면서 고민이 하나 생겼다. 
나중에 블로그 글 목록이나 팀 페이지의 포스트들이 쌓이다 보면 인피니티 스크롤을 적용한다고 하더라도 인기글과 어느정도 블로그 글 포스트때문에 성능 이슈가 생길 것 같았다. </p>
<p>그래서 React에서 제공하는 <code>lazy</code>와 <code>Suspense</code>로 코드 분할은 가능하지만, 여기에 Intersection Observer API를 결합하면 더 스마트한 지연 로딩이 가능하지 않을까? 라는 생각에서 시작했다.</p>
<h2 id="어떻게-동작하는가-초기-계획">어떻게 동작하는가? (초기 계획)</h2>
<p>

<p>  <img src="https://velog.velcdn.com/images/k_gu_wae123/post/639b9687-e508-4623-840f-866943530d44/image.png" alt=""></p>
</p>

<p>

<p>  <img src="https://velog.velcdn.com/images/k_gu_wae123/post/6f605cc6-be67-47cc-bcbb-51cdc84a59ea/image.png" alt=""></p>
</p>

<h3 id="1-초기-마운트">1. 초기 마운트</h3>
<ul>
<li>DOM 요소가 마운트되면 Observer가 관찰을 시작</li>
<li>설정한 threshold(0.3)로 30% 이상 노출을 체크</li>
</ul>
<h3 id="2-스크롤-감지">2. 스크롤 감지</h3>
<ul>
<li>사용자가 스크롤하면서 컴포넌트가 뷰포트로 진입</li>
<li>Observer가 실시간으로 가시성 비율을 계산</li>
</ul>
<h3 id="3-로딩-트리거">3. 로딩 트리거</h3>
<ul>
<li>30% 임계점을 넘으면 상태가 변경되고</li>
<li>React가 이를 감지해서 실제 컴포넌트를 렌더링</li>
<li>임무 완료 후 Observer는 자동으로 정리</li>
</ul>
<h2 id="코드로-보는-동작-방식">코드로 보는 동작 방식</h2>
<h3 id="1-초기-마운트-단계">1. 초기 마운트 단계</h3>
<p>실제 DOM과 React가 어떻게 상호작용하는지 자세히 살펴보자:</p>
<pre><code class="language-tsx">// 1. DOM 요소 참조 설정
const observerRef = React.useRef&lt;HTMLDivElement&gt;(null);
</code></pre>
<p>이 단계에서 일어나는 일:</p>
<ul>
<li>빈 div 엘리먼트가 실제 DOM에 마운트됨</li>
<li>useRef를 통해 이 DOM 노드를 직접 참조</li>
<li>아직 실제 컴포넌트는 로드되지 않은 상태</li>
</ul>
<h3 id="2-observer-설정-단계">2. Observer 설정 단계</h3>
<pre><code class="language-tsx">const observer = new IntersectionObserver(
  (entries) =&gt; {
    entries.forEach((entry) =&gt; {
      if (entry.isIntersecting) {
        setIsVisible(true);
        observer.disconnect();
      }
    });
  },
  {
    threshold: 0.3
  }
);
</code></pre>
<p>여기서 중요한 점:</p>
<ul>
<li>Observer는 실제 DOM 요소를 감시</li>
<li>Virtual DOM이 아닌 실제 브라우저 레벨에서 동작</li>
<li>threshold 0.3은 컴포넌트의 30%가 보여야 활성화</li>
<li>한번 활성화되면 observer는 자동으로 정리됨 (메모리 관리)</li>
</ul>
<h3 id="3-상태-변경과-렌더링-프로세스">3. 상태 변경과 렌더링 프로세스</h3>
<p>그래서 시각적으로 보면 이런 흐름이 된다:</p>
<ol>
<li><p><strong>초기 상태</strong></p>
<ul>
<li>fallback UI만 보이는 상태</li>
<li>실제 컴포넌트는 아직 로드되지 않음</li>
</ul>
</li>
<li><p><strong>스크롤 진행</strong></p>
<ul>
<li>사용자가 페이지를 스크롤</li>
<li>Observer가 실시간으로 가시성 체크</li>
</ul>
</li>
<li><p><strong>30% 임계점 도달</strong></p>
<pre><code class="language-tsx"> setIsVisible(true);  // 상태 업데이트 트리거
</code></pre>
<ul>
<li>isVisible 상태가 true로 변경</li>
<li>React의 리렌더링 사이클 시작</li>
</ul>
</li>
<li><p><strong>컴포넌트 로딩</strong></p>
<pre><code class="language-tsx"> {isVisible ? (
   &lt;React.Suspense fallback={fallback}&gt;
     {children}
   &lt;/React.Suspense&gt;
 ) : (
   fallback
 )}
</code></pre>
<ul>
<li>Suspense를 통해 실제 컴포넌트 로드 시작</li>
<li>로드 중에는 fallback UI 유지</li>
<li>로드 완료 후 실제 컴포넌트로 전환</li>
</ul>
</li>
</ol>
<h3 id="4-메모리-관리">4. 메모리 관리</h3>
<pre><code class="language-tsx">observer.disconnect();  // 임무 완료 후 정리
</code></pre>
<ul>
<li>컴포넌트가 로드되면 Observer 자동 정리</li>
<li>불필요한 메모리 사용 방지</li>
<li>성능 최적화에 기여</li>
</ul>
<h3 id="최적화-포인트">최적화 포인트</h3>
<ol>
<li><p><strong>즉시 정리</strong></p>
<ul>
<li>한 번 로드되면 더 이상 관찰이 필요 없음</li>
<li>observer.disconnect()로 즉시 정리</li>
</ul>
</li>
<li><p><strong>조건부 실행</strong></p>
<pre><code class="language-tsx"> if (isVisible) return;
</code></pre>
<ul>
<li>이미 보이는 상태면 Observer 생성 안 함</li>
<li>불필요한 리소스 사용 방지</li>
</ul>
</li>
<li><p><strong>효율적인 DOM 접근</strong></p>
<ul>
<li>useRef로 직접 DOM 참조</li>
<li>Virtual DOM 렌더링 최소화</li>
</ul>
</li>
</ol>
<p>이런 최적화 덕분에 LazyLoad 컴포넌트는 효율적으로 동작하면서도 브라우저 리소스를 아낄 수 있다.</p>
<h2 id="실제-사용해보니">실제 사용해보니...</h2>
<p>라이트하우스로 성능 측정해보니 몇 가지 재밌는 점을 발견했다:</p>
<ol>
<li>짧은 컨텐츠에선 오히려 역효과<ul>
<li>레이어가 하나 더 생기면서 성능이 살짝 떨어지기도</li>
<li>Observer 설정하고 관리하는 오버헤드가 이득보다 클 수도</li>
</ul>
</li>
<li>적용 위치가 중요함<ul>
<li>화면 하단 컴포넌트에 적용해도 성능 향상이 미미할 때가 있음</li>
<li>최신 브라우저들이 이미 나름의 최적화를 하고 있어서 그런듯</li>
</ul>
</li>
</ol>
<h2 id="이럴-때-쓰면-좋다">이럴 때 쓰면 좋다</h2>
<ol>
<li>무거운 컴포넌트<ul>
<li>이미지/동영상 있는 컴포넌트</li>
<li>복잡한 차트나 시각화</li>
<li>외부 라이브러리 쓰는 무거운 컴포넌트</li>
</ul>
</li>
<li>반복되는 컨텐츠<ul>
<li>블로그 포스트 목록</li>
<li>무한 스크롤 페이지</li>
</ul>
</li>
</ol>
<h2 id="이럴-땐-다시-생각해보자">이럴 땐 다시 생각해보자</h2>
<ol>
<li>간단한 텍스트나 기본 UI</li>
<li>첫 화면에 바로 보이는 컴포넌트</li>
<li>가벼운 컴포넌트</li>
</ol>
<h2 id="결론">결론</h2>
<p>react-lazy-observer는 꽤 괜찮은 도구지만, 모든 상황에 딱 맞는 건 아니다. 적용하기 전에 잘 고민해보자:</p>
<ol>
<li>컴포넌트가 얼마나 무거운지</li>
<li>실제 UX에 얼마나 도움되는지</li>
<li>구현 복잡도 대비 성능 향상이 괜찮은지</li>
</ol>
<p>그래도 앞으로 있을 기능에 대해 기술 부채보단 좋지 않을까라는 생각과 뭔가 재미난 고민에 대한 해결책을 구현할 수 있었던 것 같아서 재밌었다.
더 자세한 내용은 <a href="https://www.npmjs.com/package/react-lazy-observer">npm 패키지 페이지</a>에서 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[좌충우돌 GDG on Campus KNU 4기 준비하기 - 리드편]]></title>
            <link>https://velog.io/@k_gu_wae123/GDGoC-KNU-1</link>
            <guid>https://velog.io/@k_gu_wae123/GDGoC-KNU-1</guid>
            <pubDate>Tue, 15 Oct 2024 11:19:55 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/k_gu_wae123/post/637ab94a-67a4-45b6-8501-55fd57f52d7b/image.jpeg" width=500px/>

<h1 id="리드가-되다">리드가 되다!</h1>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/4bc2f61c-6a6e-46c9-b874-dbd45cf1de5b/image.png" alt=""></p>
<p>이번 3기 리드님의 추천으로 GDSC Korea에 지원을 하게 되었는데 이번 기회에 Lead를 합격하게 되었다!
총 한국에서는 총 37개의 대학이 참여를 하게 되었고, 활동은 24년 8월부터 25년 7월까지 활동을 하게 되었다!</p>
<h1 id="코어-멤버-모으기">코어 멤버 모으기</h1>
<p>코어 멤버 같은 경우는 본인이 마음에 맞는 사람들과 같이 일하고 싶다라는 생각이 들었다. 하고 싶었던 것들도 많았고 뭔가 열정적으로 이어나가고 싶다라는 생각이 들었기에 GDSC KNU 3기가 끝난 후 같이 하고 싶은 사람들에게 직접 접촉하였고 총 9명을 모아 진행하기로 하였다.</p>
<h2 id="gdsc-knu-웹페이지-팀">GDSC KNU 웹페이지 팀</h2>
<p>처음에는 웹 페이지 팀을 모아서 개발을 진행할 때 코어라고 생각하지 않고 진행을 하려고 했었다. 하지만 생각보다 톡방을 따로 나누어서 진행을 하다보니 코어와 서로 같이 얘기해야할 내용, 웹페이지 테크 팀이 얘기해야할 내용들이 겹치는 일들이 많아서 웹페이지 테크 팀도 같이 그냥 코어 멤버에 포함을 시켰다.</p>
<h1 id="멤버-모으기">멤버 모으기</h1>
<p>멤버 같은 경우에는 인스타, 에브리타임을 통해서 홍보를 하고자 했다. 
학교 컴퓨터학부 게시글에 올리는 방법도 있었을 텐데 사실 이건 까먹고 못했다 ㅎㅎ,, 지원 같은 경우에는 자체 웹사이트 내에서 사람들을 모집을 받았었고 모집 기간동안 재미있는 데이터들을 많이 모을 수 있었던 것 같아서 흥미로웠다.</p>
<p><strong>모집 기간 동안 들어온 페이지내 데이터들</strong></p>
<div style="display: flex; justify-content: space-between;">
    <img src="https://velog.velcdn.com/images/k_gu_wae123/post/d4cf0439-c53b-4f39-8459-a829ce33e37a/image.jpg" width='30%'/>
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/572bee7d-88d5-4936-bc56-069e1a4a9e49/image.jpg" width='30%'/>
<img src='https://velog.velcdn.com/images/k_gu_wae123/post/386919e1-43e5-4359-8ad5-ffa3296a2e5a/image.jpg' width='30%'/>
</div>


<p>생각보다 많은 사람들이 보러 와주셨던 것 같고 재밌는 데이터를 많이 받아볼 수 있어서 재밌었다.
User Scroll 경우에는 소개 페이지였는데 생각보다 많이 봐주시러 온거 같아서 나중에 새로 다시 리뉴얼 하기로 했다 ㅎㅎ,,,(요건 일단 나중에)</p>
<h2 id="디자이너-모으기">디자이너 모으기</h2>
<p>사실 제일 힘들었던 부분이긴 하다. 어떻게든 새로운 경험을 모두에게 경험을 시켜주고 싶었고 그러기 위해서는 3기와 다르게 4기부터 정식으로 디자이너 분들을 모집해서 같이 커뮤니티를 즐겼으면 좋겠다라는 생각이 들었었다.</p>
<h3 id="왜-안-모이지">왜 안 모이지?</h3>
<p>처음에는 에브리 타임과 인스타에서 모집을 시작할 때 대충 예상은 했었다. 어떻게보면 디자이너 분들에게는 생소한 커뮤니티이고 무엇을 하는지 잘 모르니까 안 모일 가능성이 있다라고 판단을 했었다. 그래서 디자인학과 사무실에 직접 찾아가서 홍보글과 이미지를 전해드리면서 홍보 요청을 드린다던가 디자인과 학생회 인스타 계정에 직접 찾아가서 홍보 관련 글을 요청하는등 진짜 흔히 말로 발로 뛴다를 좀 직접 실천했던 것 같다.</p>
<h3 id="아니-근데-이래도-안-모인다고">아니 근데 이래도 안 모인다고?</h3>
<p>직접 발로 뛰고 나서 일주일이 지났는데 생각보다 디자이너 분들의 지원율이 많이 뜸했다. 이에 관련해서 이대로 가다가는 진짜 인원수가 부족해서 지원하신 디자이너 분들께 양해를 구하고 안 뽑는 경우까지 생기겠다라는 생각이 들었었다. 그래서 학생회 인스타 쪽에 홍보해주기로 약속하시고 올려주신 디자이너 리크루팅 페이지에 좋아요를 눌러주신 분들께 무작정 DM을 넣어서 커뮤니티 관련 직접 홍보를 진행하였다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/e5f908c6-2dcc-4874-a9b4-34bbf028c8de/image.png" alt=""></p>
<p><strong>왜 굳이 이렇게까지 한걸까?</strong>
남들이 보기를 굳이 이렇게 까지 할 필요가 있을까? 라는 의문점이 드실 분들도 생길 것 같다. 그러나 뭔가 이번계기로 쭉 앞으로 기수제로 운영을 하게 될 때 앞으로 디자이너분들이랑 개발자가 같이 공존하는 서로가 도움되는 커뮤니티를 만들고 싶다라는 생각이 들었었다. 그래서 사람들을 많이 모으고 싶었다. 그런 생각이 드니까 몸이 먼저 움직인 것 같았다.</p>
<h3 id="모으고-보니까">모으고 보니까?</h3>
<p>이번에 디자이너분들은 코어 한명 멤버분들 6명해서 총 7명체제로 진행을 하게 되었다. 원래 목표는 9명이었지만, 이후 추가모집도 생각중이다. 나중에 들어서 알게된 내용이지만 디자인 학과분들이 한 학년이 총 20명이라고 한다. 생각보다 진짜 많은 수의 인원을 모은거 같다라는 생각이 드는 것 같다.</p>
<h1 id="뜻밖의-좋은-사람들-만나기">뜻밖의 좋은 사람들 만나기</h1>
<p>이번에 GDSC 리드들끼리 전체 오프라인 미팅 내에서 Jerry라는 구글 클라우드 직원분을 만났다. 경북대학교 겸임교수를 하시는 분이셨고 종연이라는 친분 덕분에 알게되었다. 그 분과 얘기를 나누면서 앞으로 많은 Workshop을 열어보자고 약속을 받았다.</p>
<h2 id="그러다보니">그러다보니?</h2>
<p>Jerry님과 이후 또 여러 이야기를 나누기도 했었고 어쩌다보니 해커톤을 11월 중에 열기로 했다. 이것도 기획하면서 세상에서 젤 중요한건 돈이란 걸 느껴버렸다.(사회의 빨간약을 많이 먹어버렸다라고 해야되나,,,?) 쨌든 이건 해커톤이 마무리되면 썰을 풀 예정이다. 10월 16일날 해커톤을 모집할 예정이니 많은 관심 가져주셨으면 좋겠다.</p>
<h1 id="첫-onboarding">첫 OnBoarding</h1>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/173a1e05-9e68-4b1f-be19-db5917d496d4/image.jpg" alt=""></p>
<p>Organizer 1명, Team Member 10명, Chapter Member 48명으로 
GDGoC KNU 4기 온보딩을 성공적으로 마무리하였다.
온보딩에서는 GDGoC의 방향성과 진행될 정기 세션들, 또한 Google 코리아에서 내려오는 정기 행사에 대해서 소개하는 자리와 전체분들 one Slide 세션들이 있었기 때문에 모든 것을 내가 다 앞에서 진행하며 소개 했어야했다.
어떻게 보면 처음으로 리드라는 자리에서 코어 운영진들과 같이 연 이벤트였고 단상에서 모든것을 내가 진행을 해야되나보니 조금 긴장되기도 했었지만 잘 진행을 했었던 것 같다. Member분들도 상기된 표정으로 다들 자기소개를 하는데 그 모습을 보니 긴장이 풀렸던 것 같다.</p>
<h1 id="onboarding이-끝나고-근황">OnBoarding이 끝나고 근황</h1>
<p>GDGoC KNU에서 항상 정기적으로 진행되는 커리큘럼이 있다. 이건 참 아무리 생각해도 1기부터 3기까지 정기 커리큘럼은 잘 깎았다고 생각한다. 
그래서 현재 매주 수요일 주별로 진행하는 첫 온라인 세션과 오프라인 세션을 마무리하였다. 또한 스터디 같은 경우에도 각 톡방별로 운영진과 본인이 참여하여 실시간 모니터링을 하며 불편한 점이 없는지 다들 체크하고 있는데 걱정과 달리 정말 탈 없이 열정적으로 진행 중이다. (오히려 개발자의 시점에서 보면 왜 이렇게 잘하지 싶을정도로 불안할 정도이다. 🤔)</p>
<h2 id="session들을-진행하면서-드는-생각">Session들을 진행하면서 드는 생각</h2>
<p>팀 배치 같은 경우에는 홈페이지 어드민 페이지 기능의 팀 배치 기능을 통해서 스터디 팀을 배치해줬었는데 현재 멤버 기능 같은 경우 자신의 배치 팀을 확인하는 기능밖에 제공을 못해줬다. 이걸 보니 12월로 예상 구현 마무리 시점을 잡았지만 빨리 마무리 해야겠다라는 생각이 들더라. </p>
<h1 id="마지막으로-느낀점들">마지막으로 느낀점들</h1>
<h2 id="organizer라는-자리를-처음-받았을-때">Organizer라는 자리를 처음 받았을 때</h2>
<p>처음 GDSC KNU를 들어올 때 이전 리드분들이랑 친분이 있는 사이이다 보니 운좋게 처음에 들어오게 되었던 것 같다. 그땐 정말 개발하면 C언어, Python, Java만 알고 있던 시기고 그때 처음으로 JavaScript, React의 존재에 대해서도 알게 되었던 것 같다. </p>
<p>개발을 공부하게 되면서 GDSC KNU에서 기획하는 여러 행사나 GDG측에서 다양한 행사를 통해 여러 GDG사람들이나 GDSC 챕터분들을 만날 기회가 있었는데 이후 점점 이 커뮤니티에 애정이 쌓이게 되었던 것 같다. 
그러다 보니 당시 개린이였던 나는 다양한 행사 기획이나 이런 네트워킹을 기회를 가질 수 있는 리드들을 동경하게 되었던 것 같다. </p>
<p>이전 리드분들이 엄청난 실력자기도 했었고 다들 괴물이였기 때문에 나도 괴물같은 사람이 되어서 리드가 되어야겠다라는 생각을 가지게 되었고, 항상 내 자신이 부족하다라는 생각을 하면서 쭉 지내다보니 현재 리드가 되었다.</p>
<p>아직도 사실 내가 개발을 잘 하는건 잘 모르겠다. 여전히 목마르고 잘하고 싶다라는 생각이 참 굴뚝같다. 어쩔 땐 괘씸하게도 현업자 분들보다 학생이라는 신분에서 뛰어넘고 싶다라는 생각이 들때도 있다. 그러다 보니까 개발 관련해서는 아직까지는 안 지치고 열심히 뛰어드는게 아닐까? 라는 생각이 들었다.</p>
<h2 id="organizer를-받고-나서부터">Organizer를 받고 나서부터</h2>
<p>초반이긴 하지만 세션들을 진행하다보니까 힘들지만 재밌다.
개발만 하고 운영진만 했던 초보 리드다 보니까 앞에서 멤버분들한테는 태연한 척 항상 웃는 모습 다양한 행사를 보여주는 척을 하지만 뒤로는 아주 여러 고민을 많이 하는 것 같다.</p>
<blockquote>
</blockquote>
<ul>
<li>어떻게 하면 아는 사람들만 어울리지 않고 다양하게 서로 어울렸으면 좋을까?</li>
<li>어떻게 하면 유령 사람들 없이 모두가 다 열정적이였으면 좋을까?</li>
<li>어떻게하면 조금이라도 정기세션말고도 여러 이벤트를 참여하게 할 수 있을까?</li>
<li>좋은 행사나 이벤트를 조금 더 끌어올 수 있는 방법이 어떤 것들이 있을까?</li>
<li>행사의 퀄리티를 높이기 위해서는 어떤것을 더 노력해야될까?</li>
</ul>
<p>물론 이런 고민들은 대부분 동아리의 사람들이 하는 고민이긴 하지만 GDGoC KNU만이 할 수 있는 행사나 세션을 열기 위해 고민을 하다보니 새로운 것들을 하다보니 나름 재밌는 것 같다.</p>
<p> 항상 GDSC KNU가 3기까지 용두사미로 끝나는 경우가 너무 많다보니까 이번 4기에는 
뭔가 끝까지 다 유지되면서 마무리되었으면 하는 바람에 그래서 항상 초반에 본인이 전부 다 기획하고 진행을 하려고 했었다.</p>
<p> 이것 때메 머리를 싸매고는 했었는데 리드라는 직책을 맡고 다양한 일을 벌리다보니까 전부는 할 수 없다는 노릇이더라.</p>
<p> 그래서 요즘은 코어 분들한테도 절대 부담이 안 가는 선에서 맡길려고 최대한 합리적으로 부탁할려고 한다.</p>
<h3 id="결론">결론</h3>
<blockquote>
<p>사람은 글자를 남기고 호랑이는 가죽을 남긴다</p>
</blockquote>
<p>내가 졸업할 때쯤 크게 한바탕하고 떠났으면 좋겠다. 뭔가 뚱딴지 같은 소리일 수도 있지만 우리 커뮤니티를 들어올 수 밖에 없는 그런 메리트를 가질 수 밖에 없는 커뮤니티를 만들고 싶다. </p>
<p>기수별로 커뮤니티 성격이 달라지기야 하지만 4기를 마무리하고 졸업을 했을 때 학교에서 내 이름이 거론되었을 때 알아듣는 사람들이 생기면 그것이야 말로 학교 생활에서 남들이 경험해보지 못한 그런 경험을 한 낭만이 있는 삶을 즐겼던 것이 아닐까?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[좌충우돌 GDSC KNU 4기 준비하기 - 서비스 편 ]]></title>
            <link>https://velog.io/@k_gu_wae123/gdsc-knu-41</link>
            <guid>https://velog.io/@k_gu_wae123/gdsc-knu-41</guid>
            <pubDate>Tue, 27 Aug 2024 11:22:00 GMT</pubDate>
            <description><![CDATA[<h1 id="서론에-앞서">서론에 앞서</h1>
<p>2022년 처음 GDSC KNU 2기 멤버로 활동하게 된 이후로부터 3기 코어를 맡았고 3기 리드님의 추천으로 4기 리드를 맡게 되었다. 현재 리크루팅 중이고 멤버들을 열심히 모집하고 있다!</p>
<h2 id="gdsc-knu-현재-서비스">GDSC KNU 현재 서비스</h2>
<p><a href="https://github.com/GDSC-KNU/GDSC_Official_FE">https://github.com/GDSC-KNU/GDSC_Official_FE</a></p>
<p><a href="https://gdsc-knu.com">https://gdsc-knu.com</a></p>
<p>현재 운영하고 있는 GDSC KNU 서비스이다. 사실 지금 지원되는 기능이야 지원하기 기능밖에 없지만 올해 3월부터 기획을 시작으로 학기 중 바쁘더라도 짬짬이 시간내서 만든 서비스이기에 애증이 남는 서비스이다. 기획은 현재 9개월로 잡고 있어 12월을 기점으로 총 서비스를 마무리할려고 기획 중이다.</p>
<h2 id="왜-이-서비스를-기획하게-됐나요">왜 이 서비스를 기획하게 됐나요?</h2>
<p>GDSC KNU 공식 3기 Core Member로 활동하게 되면서 항상 의문점이 들었던 적이 있다. </p>
<p>회의는 항상 노션에서 진행했었고, 지원폼이나 설문조사는 항상 구글 드라이브로 모든 자료들을 받았으며 음성 관련 화상 회의이나 지세미나는 디스코드에서 진행했었고, 날짜는 항상 구글 캘린더로 회의를 잡았다. 이러다보니 필요한 기능들이 파편화가 되어 회의나 이런 운영관련 활동을 할때 마다 항상 불편하다는 생각이 들었다. 이 모든 것들을 함께 모아놓은 자체 서비스에서 진행을 해보면 좋지 않을까? 라는 생각이 들었고</p>
<p>3월에 차기 4기 운영진을 하고 싶어하는 애들이나 이런 서비스에 대한 욕구가 있는 애들을 모아 팀을 구성하였다.</p>
<p>처음 시작은 프론트엔드 1명(나 혼자), 백엔드 2명, 디자이너 2명, QA 및 회의록 정리담당 1명이었다.</p>
<h3 id="서비스-기획단계">서비스 기획단계</h3>
<p><strong>노션 기록들</strong>
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/2ce5b238-5486-4fbd-8343-e310333333df/image.png" alt="">
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/24fbad32-7405-43e9-9485-4f636685603e/image.png" alt=""></p>
<p>우선적으로 노션에서 많이 정리하면서 기능 관련 명세서 내용을 열심히 정리하고 설계면에서도 엄청 힘들게 구현을 했던 것 같다.
기획 단계에서 과연 사용자들이 필요가 있을까???라는 내용으로 위에서 불편함을 느꼈던 기능에 대해 추려나가기 시작했고 구현을 해야겠다라는 기능들을 다 정리를 하게 되었다.</p>
<h3 id="구현하기로-결정한-내용">구현하기로 결정한 내용</h3>
<ol>
<li><p>우선적으로 디스코드는 WebRTC와 WebSocket을 기능을 사용한 실시간 화상회의 소통 서비스인데 과연 우리가 서버를 감당할 돈이 될까?라는 점에서부터 의문점이 제기되었었다. 또한 관련 트래픽을 경험을 해보지 못하였었고 실제로 운영될 서비스를 감안하였을때 디스코드 만한 퍼포먼스를 낼 수 없다고 판단했기에 디스코드는 따로 냅두었다.</p>
</li>
<li><p>디스코드를 제외하고 지원폼이나 설문조사 같은 경우 서비스는 충분히 자체 서비스내에서 기획할 수 있다고 판단했고 GDSC KNU 같은 경우 항상 지원 마지막 날에 사람 몰리는 것을 감안했을 때 충분히 좋은 경험을 할 수 있다고 판단했기에 구글 드라이브에 자료가 들어가게 되는 것들은 전부 서비스에 담당하기로 하였다. </p>
</li>
<li><p>회의록이나 달력 같은 경우에 초기에 있었던 의문점은 동시편집을 그러면 어떻게 구현하지? 였었다. 이 부분에 관련되어 처음엔 동시편집을 포기하는 방향으로 이 기능들을 포기할까 싶었지만 Yorkie나 Y.js같이 서버에서 부담이 가지않는 자체 라이브러리를 가지고 있었기에 이에 판단하여 회의나 일정을 간단하게 잡을 수 있는 서비스를 넣어야 겠다라고 판단하여 구현하기로 했다.</p>
</li>
<li><p>또한 멤버가 최대한 성장을 이끌어내게 할 수 있는 방법이 무엇이 있을까에 대해서 고민을 해보았었다. 본인 경험 상 개발 후 항상 회고록이나 개발 글을 올렸을 때 좀 힘들었던 점, 억울했던 점, 배웠던 점을 허심탄회하게 글을 썼었기에 이것이 도움된다고 생각했었고 팀원들과 판단했을 때 처음 프로젝트를 경험해보시는 분들이라면 나중에 Tistory나 Velog로 글을 이관한다고 치더라도 처음 글을 쓰는 경험을 만들어 주는 것은 나쁘지 않겠다라고 판단하였고, SEO를 통해 쓴 글들이 GDSC KNU Member들 뿐만 아닌 다른 사람들에게도 노출 시켜주는 경험을 해보는 것도 좋겠다라고 판단하여 개발 블로그도 기능을 넣게 되었다.</p>
</li>
</ol>
<h3 id="넣다보니까-생각보다-기능이-많네">넣다보니까 생각보다 기능이 많네?</h3>
<p>사실 코드에 대한 욕심도 있었고 최대한 개발 코드를 이쁘게 치고 싶다라는 의욕이 매우 강했기에 프론트엔드 개발자는 혼자 자처한다고 개발을 착수하였다. 그러나 수많은 기능들 앞에서는 코드 장사없다라는 점을 깨닫게 되었다. 왜 서비스가 커지면 커질수록 더 많은 사람들을 뽑을려고 하는지도 점차 이해가 되더라.
그래서 디자이너 하시는 분 중 한 명은 프론트엔드 개발을 하는 분이셨고 도움을 요청해 같이 개발하기로 진행을 하였다.</p>
<h1 id="ver-100-기획">ver 1.00 기획</h1>
<p>우선적으로 현재 멤버들을 뽑기 위한 지원서 및 사람들을 관리하기 위한 어드민 페이지를 우선적으로 제작을 하기로 하였다. 어드민 페이지 같은 경우에는 지원 서류 관리, 멤버들 status 관리, 멤버들을 뽑았을때 팀 배치 관리를 개발을 진행하기로 하였다.</p>
<h2 id="ver-100을-구현하면서">ver 1.0.0을 구현하면서</h2>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/b49f3193-7470-4df2-b991-4eb4ad1f3b36/image.png" alt=""></p>
<p>사실 정말 힘들었다. 3월부터 시작하였지만 학업 기간도 걸치면서 같이 개발을 해야되다보니 결국 밀리고 밀려 8월초까지 밀리게 되었었는데 개발을 진행하면서 실제로 운영해야되고 지속적으로 유지보수가 가능한 서비스를 만들어야되다보니 막상 쉽게 코드가 손에 입력이 안 됐었다. 과연 내가 짜는 코드가 맞을까? 성능적으로 괜찮을까? 라는 고민을 항상 진행하다보니 결국 돌아오는 방향은 똑같더라도 코드 속도가 예전만큼 올라오지 않았던 것 같다.</p>
<p>그러나 개발 도중 방학 기간에 카카오 테크 캠퍼스를 활동하면서 강사님이랑 멘토님과 커피챗을 진행했던 적이 있다.
커피챗을 하러 가기 위해 직접 서울로 올라가 강사님 회사 근처에 가서 직접 회사에서 어떤 것을 하는지와 회사에서 추구하는 가치, 내가 고민했던 내용에 대한 답변을 들었다. </p>
<p>이와 관련해서 결국 개발에 목 메이기보다는 어떻게 하면 서비스를 조금 더 사용자에게 빠르게 서비스를 제공할 수 있을까? 어떻게 하면 사용자가 초기에 서비스를 이용하고 지속적으로 찾을 수 있는 서비스를 만들 수 있을까?에 대한 의문점을 가지고 접근을 하면 좋겠다라는 깨달음을 얻을 수 있었다.</p>
<p>이전에 애증이 있는 GDSC KNU 관련해서 조금 더 멋진 서비스를 만들고 싶었기에 코드적으로 더 완벽한 서비스를 만들어야겠다는 압박감을 벗어나게한 계기였다. 
그런 점에서 벗어나게 되다보니 코드 속도도 더 빠르게 오르게 되었고 조금 더 사용자에게 더 편하고 사용자가 발생할 수 있는 문제를 제일 최소화하여 서비스를 운영할 수 있을까에 대한 고민을 하다보니 조금 더 실력적으로 상승하게 될 수 있는 계기였던 것 같다.</p>
<h3 id="carousel">Carousel</h3>
  <img src="https://velog.velcdn.com/images/k_gu_wae123/post/653b3167-1f0c-4e7b-81fe-0f89b4371e8e/image.gif" width='60%'/>

<p>디자이너 분께서 Carousel을 디자인해주셨는데 이와 비슷한 Carousel라이브러리를 직접 찾지 못했었고 직접 구현하기로 했었다.
이 부분에서 각도 계산을 하기 위해 엄청나게 머리를 싸맸던 것 같다. 제일 고민했던 내용은 카드 carousel안에 지구를 넣는 issue였는데</p>
<p>carousel안에서 지구를 넣는 부분이 기술적으로 불가능하다는 생각이 들었다. </p>
<p>카드별로 z-index를 다 적용해보았지만 지구가 카드 carousel 위에 위치하게 되거나 아예 지구가 carousel 뒤로 숨어버리는 문제가 있었다.</p>
<p>이와 관련해서 카드별로 opacity를 따로 적용해보면 어떨까라는 또 다른 해결책으로 진행을 해보았고 위 영상처럼 지구가 카드 carousel 사이에 위치하는 것처럼 보이지만 사실 뒤에 카드를 투명도를 높여 카드 carousel 뒤에 지구가 위치 시켰다.</p>
<p>이 부분에 관련하여 디자이너 분과 1차 개발물 리뷰때 디자이너분이 이 점이 오히려 더 입체적인 것 같다고 좋게 봐주셨던 것 같다!</p>
<p>이렇게 하룻밤을 꼬박 새면서 개발 후 직접 구현된 carousel을 통해 매우 뿌듯함을 느꼈었다.</p>
<h3 id="지원서-폼-및-조회하기-기능">지원서 폼 및 조회하기 기능</h3>
<p>사실 이 부분에 관련해서 제일 신경썼었고 Backend 팀원 분도 이와 관련해서 제일 스트레스를 받았던 부분이다. 항상 학교내에서 사람들이 구현했다고 서비스하는 기능들의 썰을 들으면 항상 어디가 뚫렸다더라~ 이런 기능에 허점이 있더라~라는 썰을 많이 들었기 때문에 기본적인 허점이 뚫리지 않는 서비스를 만들고 싶었다.</p>
<p>이와 관련해서 팀원분과 예외 useCase관련해서 회의를 진행하면서 최대한 기능을 막기 위해 개발을 진행하였다.</p>
<blockquote>
<p><strong>개발적으로 고민했던 내용</strong><br/></p>
</blockquote>
<ol>
<li>최종 제출을 했을 시에 조회가 되어야 할까?</li>
<li>본인이 아닌 다른 사람들이 본인의 학번을 통해 조회를 할려고하면 어떻게 대처해야 될까?</li>
<li>만약 다른 사람들이 본인의 학번을 통해 조회를 할려고 하게 되었을 때 최대한 개인정보를 적게 노출하면서 지원서 폼을 제출하게 할 수 있는 내용들을 어떤 것들을 넣을 수 있을까?</li>
<li>마감일이 되었을때 지원서폼을 지원 못 하게 할 수 있는 방법은 무엇이 있을까?</li>
</ol>
<p>이런 점들을 고려하면서 개발을 최대한 진행을 하려고 하였고 서버, 클라이언트 딴에 두 곳에 전부 검증을 진행하여 최대한 본인의 개인정보를 남들에게 노출되지 않도록 노력하였다.</p>
<p>에러가 발생하게 되면 실시간으로 대비하기 위해 현재 운영을 하고 있는 디스코드 채널에 운영진만 볼 수 있는 홈페이지 에러채널을 따로 파서 진행을 하였고</p>
<p>나름 우려했던 내용들 중 하나가 터지게 되면서 서버 측에서 에러 코드를 제대로 남겨주었다. 나름 성공적으로 막을 수 있었기 때문에 사용자들을 제대로 지켜냈구나라는 뿌듯함이 느껴지는 경험을 할 수 있었다!
 <img src="https://velog.velcdn.com/images/k_gu_wae123/post/9cbf92da-005e-4353-bc9e-ec9ae55a1af2/image.png" alt=""></p>
<h3 id="어드민-페이지-제작기-드래그앤드랍">어드민 페이지 제작기 (드래그앤드랍)</h3>
<p>이 부분을 관련해서 위에서 깨달음을 얻은 상태에서 나 혼자서는 개발을 하는데 서비스시작기간에 못 맞추겠다는 판단을 하였고 팀원 1명과 같이 프론트엔드 개발을 같이 하였다. 표관련해서는 팀원 분께 맡겼었고</p>
<p>나머지 부분 관련해서는 팀배치 기능은 본인이 구현하게 되었다.
팀 배치 기능 관련해서는 이름을 입력해서 넣기보다는 운영진들이 추후 편하게 배치를 하기 위해 드래그앤드랍 기능을 활용하여 배치하게 했으면 좋겠다라고 판단했기 때문에 드래그앤드랍 기능을 사용하여 기능을 구현하였다!</p>
<p><strong>Drag and Drop(드래그앤드랍)</strong>
<a href="https://github.com/atlassian/react-beautiful-dnd">https://github.com/atlassian/react-beautiful-dnd</a></p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/feada36c-3d0b-4c4f-8de2-92506541a073/image.gif" alt=""></p>
<p>react-beautiful-dnd 공식문서에 존재하는 gif 이미지이다. </p>
<p>DragDropContext - 응용 프로그램에서 드래그 앤 드롭을 사용하도록 설정할 부분을 랩합니다
Dropable - 드롭할 수 있는 영역. 
Draggable - 끌고 다닐 수 있는 것</p>
<p><strong>라이브러리를 선택했는 이유</strong>
드래그앤드랍을 구현하기 위해서는 기본적으로 이해를 해야되는 내용들이 많다.</p>
<p>우선 HTML에서 요소가 드래그 이벤트가 발생할 수 있도록 해당 요소의 속성으로 draggable을 통해서 값을 줄 수 있는 걸로 알고 있는데</p>
<blockquote>
<ol>
<li>dragstart: 사용자가 드래그를 시작하려고 할 때 발생함.</li>
<li>drag: 대상 객체를 드래그하면서 마우스를 움직일 때 발생함.</li>
<li>dragenter: 마우스가 대상 위로 처음 진입할 때 발생함.</li>
<li>dragover: 드래그하면서 마우스가 대상 객체의 영역 위레 자리 잡고 있을 때 발생함.</li>
<li>drop: 드래그가 끝나서 드래그하던 객체를 놓는 장소에 위치한 객체에서 발생한다. 드래그된 데이터를 가져와서 드롭 위치에 넣는 역할을 한다.</li>
<li>drogleave: 드래그가 끝나서 마우스가 대상 위를 벗어날 때 발생함.</li>
<li>dragend: 대상 객체를 드래그하다가 마우스 버튼을 놓는 순간 발생한다.</li>
</ol>
</blockquote>
<p>이런 이벤트를 전부 꿰뚫고 있어야 쓰기 편하다.
또한 데이터 전송 기능 같은 경우에도 수많은 기능이 필요하기에</p>
<p>react-beautiful-dnd라는 기능을 사용하게 되었다.</p>
<p><strong>react-beautiful-dnd 기능 중단??!?!</strong>
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/dd8d966e-e70c-4e2d-bd07-9d845872475c/image.png" alt=""></p>
<p>전부 구현 완료 후 콘솔창으로 이동했는데 지원 중단 관련 경고창이 떠서 깃헙을 들어가보니 지원을 중단한다는 이야기가 있었다. 앞으로 버전 관련해서 추후 유지보수를 생각했을때 라이브러리 이동이 필요하다는 생각이 들었고</p>
<p><a href="https://github.com/hello-pangea/dnd">https://github.com/hello-pangea/dnd</a></p>
<p>16버전까지 나온 현재 대체되고 있는 라이브러리로 넘어가게 되었다. 다행히 쓰는 방식은 비슷했기에 코드 자체적으로 수정해야될 일은 거의 없었다!</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/d9f0b559-2953-40c2-b0d5-07f6d578f508/image.gif" alt=""></p>
<p>나름 부드럽게 잘 넘어오는 것을 볼 수 있다!</p>
<h3 id="seo-최적화하기">SEO 최적화하기</h3>
<p>앞으로 지속적인 서비스를 만든다고 생각을 하고 개발을 진행하게 되다보니까 어떻게하면 사용자에게 서비스를 지속적으로 노출시킬 수 있을까하는 고민이 들었었다.</p>
<p>Next.js를 사용하면 RSC를 통해 html 자체를 분리하여 배포되기 때문에 SEO 고민을 할 필요가 없지만 React 자체 같은 경우 SPA라는 특성때문에 index.html파일을 하나만 robot한테 색인을 요청하기때문에 상대적으로 어렵게 된다. </p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/f7dadd9f-44ef-4fb5-aecb-e5c33c69cd38/image.png" alt=""></p>
<p><strong>SEO를 올리기 위한 방안</strong></p>
<p>이 부분에 대해서 고민을 하던 도중 react-async-helmet이라는 기능에 대해서 고민을 하게 되었다.</p>
<p>react-async-helmet는 서버 사이드 렌더링(SSR)을 지원하는 React 애플리케이션에서 HTML <code>head</code> 태그 내의 요소를 동적으로 관리하고 제어하는 데 사용되는 라이브러리이다. react-async-helmet는 react-helmet의 확장된 버전으로, 비동기 데이터 로딩과 같은 시나리오에서 HTML 헤드 관리를 지원한다.</p>
<p>본인은 OG 그래프와 Twitter 카드 쪽 부분까지 고려를 해서 SEO 컴포넌트를 작성하였고</p>
<pre><code>import { Helmet } from &#39;react-helmet-async&#39;;

interface SEOProps {
  title: string;
  description: string;
  url: string;
  image: string;
}

export const SEO = ({ title, description, url, image }: SEOProps) =&gt; {
  return (
    &lt;Helmet&gt;
      &lt;title&gt;{title}&lt;/title&gt;
      &lt;meta name=&#39;description&#39; content={description} /&gt;
      &lt;meta property=&#39;og:title&#39; content={title} /&gt;
      &lt;meta property=&#39;og:description&#39; content={description} /&gt;
      &lt;meta property=&#39;og:image&#39; content={image} /&gt;
      &lt;meta property=&#39;og:url&#39; content={url} /&gt;
      &lt;meta property=&#39;og:locale&#39; content=&#39;ko_KR&#39; /&gt;
      &lt;meta property=&#39;og:image:width&#39; content=&#39;1200&#39; /&gt;
      &lt;meta property=&#39;og:image:height&#39; content=&#39;630&#39; /&gt;
      &lt;meta name=&#39;twitter:card&#39; content=&#39;summary_large_image&#39; /&gt;
      &lt;meta property=&#39;twitter:domain&#39; content=&#39;gdsc-knu.com&#39; /&gt;
      &lt;meta property=&#39;twitter:url&#39; content={url} /&gt;
      &lt;meta name=&#39;twitter:title&#39; content={title} /&gt;
      &lt;meta name=&#39;twitter:description&#39; content={description} /&gt;
      &lt;meta name=&#39;twitter:image&#39; content={image} /&gt;
    &lt;/Helmet&gt;
  );
};
</code></pre><p>공용 컴포넌트를 만든 다음</p>
<pre><code>import { useParams } from &#39;react-router-dom&#39;;

import { SEO } from &#39;@gdsc/router/components/Seo&#39;;

export const MainMetaData = () =&gt; {
  return (
    &lt;SEO
      title=&#39;GDSC KNU&#39;
      description=&#39;GDSC 경북대의 공식 홈페이지에 오신걸 환영합니다. GDSC 활동과 관련된 최신 정보와 이벤트 소식을 확인하세요.&#39;
      url=&#39;https://gdsc-knu.com&#39;
      image=&#39;https://gdsc-knu.com/WhiteLogo.png&#39;
    /&gt;
  );
};

export const SigninMetaData = () =&gt; {
  return (
    &lt;SEO
      title=&#39;GDSC KNU&#39;
      description=&#39;로그인 후 서비스를 이용해보세요.&#39;
      url=&#39;https://gdsc-knu.com/signin&#39;
      image=&#39;https://gdsc-knu.com/Login.png&#39;
    /&gt;
  );
};

export const MypageMetaData = () =&gt; {
  return (
    &lt;SEO
      title=&#39;GDSC KNU&#39;
      description=&#39;마이페이지 정보 확인하기&#39;
      url=&#39;https://gdsc-knu.com/mypage&#39;
      image=&#39;https://gdsc-knu.com/WhiteLogo.png&#39;
    /&gt;
  );
};

export const IntroduceMetaData = () =&gt; {
  return (
    &lt;SEO
      title=&#39;GDSC KNU&#39;
      description=&#39;4기를 앞으로 이어나갈 GDSC KNU의 소개 페이지입니다.&#39;
      url=&#39;https://gdsc-knu.com/introduce&#39;
      image=&#39;https://gdsc-knu.com/Introduce.png&#39;
    /&gt;
  );
};

export const ApplyMetaData = () =&gt; {
  return (
    &lt;SEO
      title=&#39;GDSC KNU&#39;
      description=&#39;GDSC KNU는 모든 경북대 학생들을 환영합니다.&#39;
      url=&#39;https://gdsc-knu.com/apply&#39;
      image=&#39;https://gdsc-knu.com/ApplyNav.png&#39;
    /&gt;
  );
};

export const ApplyFormMetaData = () =&gt; {
  const { tech } = useParams();

  return (
    &lt;SEO
      title=&#39;GDSC KNU&#39;
      description=&#39;GDSC KNU는 모든 경북대 학생들을 환영합니다.&#39;
      url={`https://gdsc-knu.com/apply/${tech}/form`}
      image=&#39;https://gdsc-knu.com/WhiteLogo.png&#39;
    /&gt;
  );
};

export const ApplyExMetaData = () =&gt; {
  const { tech } = useParams();

  return (
    &lt;SEO
      title=&#39;GDSC KNU&#39;
      description=&#39;GDSC KNU는 모든 경북대 학생들을 환영합니다.&#39;
      url={`https://gdsc-knu.com/apply/${tech}`}
      image=&#39;https://gdsc-knu.com/WhiteLogo.png&#39;
    /&gt;
  );
};

export const ApplyInquiryMetaData = () =&gt; {
  return (
    &lt;SEO
      title=&#39;GDSC KNU&#39;
      description=&#39;지원하신 서류를 조회하세요.&#39;
      url={`https://gdsc-knu.com/apply/inquiry`}
      image=&#39;https://gdsc-knu.com/ApplyInquiry.png&#39;
    /&gt;
  );
};

export const TeamBlogMetaData = () =&gt; {
  return (
    &lt;SEO
      title=&#39;GDSC KNU&#39;
      description=&#39;빠른 시일 내에 더 좋은 서비스를 제공할 수 있도록 노력하겠습니다.&#39;
      url={`https://gdsc-knu.com/techblog`}
      image=&#39;https://gdsc-knu.com/CommingSoon.png&#39;
    /&gt;
  );
};

export const TeamMetaData = () =&gt; {
  return (
    &lt;SEO
      title=&#39;GDSC KNU&#39;
      description=&#39;빠른 시일 내에 더 좋은 서비스를 제공할 수 있도록 노력하겠습니다.&#39;
      url={`https://gdsc-knu.com/team`}
      image=&#39;https://gdsc-knu.com/CommingSoon.png&#39;
    /&gt;
  );
};

export const CommunityMetaData = () =&gt; {
  return (
    &lt;SEO
      title=&#39;GDSC KNU&#39;
      description=&#39;빠른 시일 내에 더 좋은 서비스를 제공할 수 있도록 노력하겠습니다.&#39;
      url={`https://gdsc-knu.com/community`}
      image=&#39;https://gdsc-knu.com/CommingSoon.png&#39;
    /&gt;
  );
};</code></pre><p>페이지별 메타 태그를 관리하는 페이지를 만들어 태그를 관리하였다.</p>
<p><strong>태생의 한계점</strong></p>
<p>react-async-helmet는 주로 클라이언트 사이드 렌더링(CSR)과 서버 사이드 렌더링(SSR) 환경에서 <code>head</code> 태그를 동적으로 관리하는 데 유용하지만, SEO의 본질적인 문제를 해결하는 것은 아니다. 클라이언트 사이드 렌더링만 사용하는 경우, 검색 엔진 크롤러가 JavaScript를 완전히 실행할 수 없기 때문에 초기 페이지 로드 시 SEO 최적화에 한계가 있을 수 있다.</p>
<p>또한 서버 사이드 렌더링을 사용하는 경우, 초기 페이지 로드 시 메타 데이터가 포함되지만, 사용자 상호작용 후 페이지 상태 변화에 따라 <code>head</code> 태그가 업데이트되면, 검색 엔진 크롤러가 이러한 변경을 감지하지 못할 수 있다. 검색 엔진은 초기 로드 시점에서의 페이지 상태를 기반으로 인덱싱을 수행하는 경향이 있다.</p>
<p><strong>그래서 여기까지 더 고민쓰,,,? React의 한계점 도달,,,?</strong>
<a href="https://github.com/Tofandel/prerenderer">https://github.com/Tofandel/prerenderer</a></p>
<p>그래서 url기반으로 build 타이밍 이전에 크롤링을 하여 html파일로 프리렌더링을 시켜주는 라이브러리를 사용해서 프리렌더링을 시켜주었지만 어째선지 동적 라우팅 같은 경우 <code>/:pathname</code>은 제대로 빌드가 되지 않는 것 같다. 
(아시는 분 해결책좀,,,)</p>
<p><strong>결론</strong>
SEO를 열심히 처리하면서 느꼈던 점은 참,,,Next.js가 깡패긴 한거 같다. 이제 Next.js를 넘어갈때를 느낀것 같다,,,?라는 생각이 든다.</p>
<h3 id="성능-최적화하기">성능 최적화하기</h3>
<p><strong>처음 배포때 초기 화면 렌더링 속도 및 메인 화면 성능</strong>
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/0d856410-f715-40ad-bd39-e3329934c5fa/image.png" alt=""></p>
<ul>
<li><p>Desktop
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/b57fc327-c2ca-4925-b4d8-2d575c8eaf79/image.png" alt=""></p>
</li>
<li><p>Mobile
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/eb1366f4-dbcf-4022-b3f1-0b9ae10d2129/image.png" alt=""></p>
</li>
</ul>
<p><strong>최적화 후 배포때 초기 화면 렌더링 속도</strong>
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/ee89dbc2-66ea-49b8-ab01-d721477c02fd/image.png" alt=""></p>
<ul>
<li><p>Desktop
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/15f021f3-0458-487c-9ead-52d769601ebf/image.png" alt=""></p>
</li>
<li><p>Mobile
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/b3e8cef6-be50-42d2-8771-efd98c7ef7ab/image.png" alt=""></p>
</li>
</ul>
<p>우선 제일 주목했던 점은 lazy로딩과 Suspense처리를 통해 최대한 초기 렌더링 속도를 줄였었다.</p>
<ul>
<li>lazy loading의 흔적,,,
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/378c425f-5802-4d91-8c86-13d8996c66ec/image.png" alt=""></li>
</ul>
<p>최대한 한 페이지내에서 겪는 컨텐츠가 아닌 것들은 다 lazy loading을 적용시켰고 
이미지 같은 경우에는 처음에는 이미지를 불러오되 블러처리를 해서 불러온뒤 그다음 원본 이미지를 보여주는 이미지 lazy loading을 적용하였다.</p>
<p>최종적으로 8000ms -&gt; 2000ms 4배 정도 성능 향상을 이끌어 낸 성과를 이루어냈다. </p>
<p>페이지가 부드럽게 돌아가는 모습을 보니 정말 기분이 좋더라,,,</p>
<p>메인페이지 같은 경우에는 gif -&gt; video로 교체를 하였다. gif가 그렇게 무거운 용량을 차지하는지 이번에 처음 깨달았기 때문에 gif 교체 하나로 성능이 확실히 올라갔다는 생각이 들었다!</p>
<h3 id="마지막-배포">마지막 배포,,,</h3>
<p>배포 같은 경우에는 s3 + cloudfront + route53을 활용해서 배포를 진행하였다. 실제 서비스가 운영하게 되면 cdn을 활용하는 이야기는 대충 듣고 있었기 때문에 최대한 실제 서비스처럼 배포를 하고 싶었고 pnpm을 통해서 배포를 진행하였다.</p>
<p><strong>pnpm 짱짱 최고</strong>
확실히 느끼는 거지만 왜 toss가 yarn berry를 쓰는지 알 것 같다. 
보통 항상 프로젝트를 진행할 때 npm으로 배포하면 대략 2~3분 정도 걸렸었다. 이 때문에 우리 서비스는 최대한 빠르게 버그나 서비스를 추가하거나 수정하기 위해서 npm 보다는 pnpm, yarn berry라는 선택지가 있었는데 조금 더 안정적으로 배포를 하고 싶다는 의견이 있었기에 pnpm을 선택하였다.</p>
<p>pnpm을 배포를 하면서 현재 서비스 배포 시간은 1분대로 배포가 진행이 되고 나름 빠르다는 생각을 하고 있다. 이 부분은 다른 게시글에서 자세히 다뤄볼려고 진행한다.</p>
<h1 id="정리를-하면서">정리를 하면서</h1>
<p>현재 휴식을 취하면서 요즘 구글 애널리틱스 보는 맛으로 산다. 
사용자들이 늘어나고 사용자가 얼마나 이벤트를 썼을까라는 흥미로 틈만 나면 휴대폰에 애널리틱스를 킨다.</p>
<p>ver2는 현재 프론트엔드 팀 3명 백엔드 2명 QA 및 문서화 1명 디자이너 한명으로 가져갈 계획이다. 기능은 지원이 다 끝나고 멤버가 확정나면 구현을 시작할 생각이다. </p>
<p>버그 제보 같은 경우에는 <a href="mailto:gdsc.knu@gmail.com">gdsc.knu@gmail.com</a>으로 제보해주면 감사하겠습니다!!</p>
<p><a href="https://github.com/GDSC-KNU/GDSC_Official_FE">https://github.com/GDSC-KNU/GDSC_Official_FE</a> -&gt; 요기 스타도 좀 많이 눌러주시면 감사하겠습니다 ㅎㅎ,,</p>
<p>다음 편에는 GDSC-KNU 리드편으로 찾아뵙겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이거 제대로 validation Check안하면 Zod 되겠는데? - (카카오 테크 캠퍼스)]]></title>
            <link>https://velog.io/@k_gu_wae123/zod</link>
            <guid>https://velog.io/@k_gu_wae123/zod</guid>
            <pubDate>Mon, 22 Jul 2024 11:54:52 GMT</pubDate>
            <description><![CDATA[<p>저번주 과제 내용에서
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/bfe748d7-caae-40b3-83b2-3fe57df6a436/image.png" alt=""></p>
<p>zod를 활용해보면 좋겠다라는 과제 내용을 받았다. 그래서 zod에 대해서 한 번 알아보고 현재 개발 진행중인 GDSC KNU 홈페이지에 적용해보기로 하였다.</p>
<h1 id="zod">zod</h1>
<p>우선 zod에 대해서 공식문서에 관련하여 찾아보기로 하였다.</p>
<blockquote>
<p><a href="https://zod.dev/?id=introduction">https://zod.dev/?id=introduction</a></p>
</blockquote>
<p>zod에서 공식문서를 찾아보면 zod에 대한 정의가 나와있다.</p>
<blockquote>
<p>Zod is a TypeScript-first schema declaration and validation library. I&#39;m using the term &quot;schema&quot; to broadly refer to any data type, from a simple string to a complex nested object.
Zod is designed to be as developer-friendly as possible. The goal is to eliminate duplicative type declarations. With Zod, you declare a validator once and Zod will automatically infer the static TypeScript type. It&#39;s easy to compose simpler types into complex data structures.</p>
</blockquote>
<p>요약을 하자면
Zod는 TypeScript를 우선으로 하는 스키마 선언 및 검증 라이브러리로, 여기서 &quot;스키마&quot;는 간단한 문자열부터 복잡한 중첩 객체까지 모든 데이터 유형을 의미한다.
Zod는 개발자 친화적으로 설계되어 중복된 타입 선언을 제거하는 것을 목표로 한다. Zod를 사용하면 한 번 검증기를 선언하면 Zod가 자동으로 정적 TypeScript 타입을 추론한다. 간단한 타입들을 쉽게 조합하여 복잡한 데이터 구조를 만들 수 있다.</p>
<p>zod는 사용하기 위해서 일정 조건이 필요하다</p>
<ul>
<li>타입스크립트 4.5 이상</li>
<li>tsconfig.json에서 strict 는 항상 true여야 한다.</li>
</ul>
<h1 id="zod는-왜-쓸까">zod는 왜 쓸까??</h1>
<h2 id="typescript와의-찰떡궁합">TypeScript와의 찰떡궁합</h2>
<p>우선 TypeScript에는 한계점이 존재한다.</p>
<h3 id="런타임-타입-검증-부족">런타임 타입 검증 부족</h3>
<p>내가 아는 타입스크립트의 한계 중 하나를 뽑으라고 한다면 젤 큰 한계점은 런타입때 타입 검증이 되지 않는다는 점이다.</p>
<p>TypeScript는 정적 타입 검사 도구로, 주로 컴파일 타임에 코드의 타입 오류를 잡는데 사용된다.</p>
<p>하지만 TypeScript는 JavaScript의 상위 집합으로, 컴파일러가 TypeScript 코드를 읽고 타입 검사를 수행한 후, 순수한 JavaScript 코드로 변환하게 되는데, 이때 JavaScript는 브라우저나 Node.js에서 실행하게 된다. 이때 변환된 JavaScript 코드에는 타입 정보가 포함되지 않기 때문에, 런타임에서는 TypeScript의 타입 시스템이 작동하지 않는다. </p>
<p><strong>엥 그게 굳이 필요해?</strong>
보통 런타임 타입 검증은 외부 API 호출이라던가 사용자 입력을 할 때 예측할 수 없는 데이터 타입을 검증할 때 필요하다.</p>
<p>예를 들자면 API 호출같이 API 응답이 예상한 타입과 일치하지 않을 수 있다.</p>
<pre><code>interface User {
  id: number;
  name: string;
}

const fetchUser = async (): Promise&lt;User&gt; =&gt; {
  const response = await fetch(&#39;/api/user&#39;);
  const data = await response.json();
  return data;
}

const displayUser = async () =&gt; {
  const user = await fetchUser();
  console.log(user.id, user.name);
}

displayUser();</code></pre><p>위 코드에서 fetchUser 함수는 외부 API로부터 사용자 데이터를 가져온다. 이때 TypeScript는 User 인터페이스를 통해 반환 타입을 정의하고 있지만, 실제 API 응답 데이터의 구조가 User 타입을 따르는지는 알 수 없다. API 응답이 id가 없는 객체이거나 name이 숫자일 경우, 런타임 오류가 발생할 수 있게 된다.</p>
<p>이때 한 번 zod를 활용해보자.</p>
<pre><code>import { z } from &#39;zod&#39;;

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
});

type User = z.infer&lt;typeof UserSchema&gt;;

const fetchUser = async (): Promise&lt;User&gt; =&gt; {
  const response = await fetch(&#39;/api/user&#39;);
  const data = await response.json();

  // 런타임 타입 검증
  const parsedData = UserSchema.parse(data);

  return parsedData;
}

const displayUser = async () =&gt; {
  const user = await fetchUser();
  console.log(user.id, user.name);
}

displayUser();
</code></pre><p>첫 번째 코드에서 zod를 추가한 내용의 코드이다. 위 예제에서는 <code>UserSchema</code>는 <code>User</code>타입을 정의하고, <code>UserSchema.parse(data)</code>를 통해 런타임에 데이터를 검증시킨다. 이때 API 응답이 예상된 타입과 일치하지 않을 경우 오류가 발생하며, 문제를 조기에 발견할 수 있게 된다. 어떻게 보면 double check라고 생각하면 된다.</p>
<h2 id="react-hook-form과-같이-썼을때의-찰떡궁합">React-hook-form과 같이 썼을때의 찰떡궁합</h2>
<p>우선 React-hook-form 라이브러리는 ref를 통해 uncontrolled Component로 이루어진 라이브러리이다. 이 말인 즉슨, 폼상태가 로컬 상태로 관리되지 않기 때문에, 폼 요소들이 변경될 때마다 전체 컴포넌트가 리렌더링이 되지 않는 장점이 있다. </p>
<p><strong>react-hook-form의 한계점</strong></p>
<p>우선적으로 react hook form은 폼 데이터의 타입 검증을 기본적으로 제공하지 않는다. </p>
<p>개발자가 직접 타입을 관리해야되는데다가 복잡한 폼일 경우, 상태 관리를 효율적으로 수행하는데 어려움이 있을 수 있다. 특히 중첩된 필드나 배열 형태의 데이터 구조를 다룰 때 복잡성이 증가한다. </p>
<p>또한 uncontrolled Component이기 때문에 런타임에서만 폼데이터를 접근할 수 있다. 결과론적으로 TypeScript의 한계점을 고스란히 안고가는 결과가 발생하게 된다. 이 점을 Zod가 타입 안정성을 확보할 수 있게 된다.</p>
<p><strong>zod와 같이 쓰게 된다면?</strong></p>
<p>Zod를 통해 작성하게 된다면 구조화된 에러 메서지를 제공해주기 때문에, 에러 메서지를 일관되게 관리를 할 수 있어 재사용성도 매우 높일 수 있고 유지 보수성도 높일 수 있다.</p>
<h3 id="프로젝트에-적용해보기">프로젝트에 적용해보기</h3>
<pre><code>import { z } from &#39;zod&#39;;

export type SignUpSchemaType = z.infer&lt;typeof SignUpSchema&gt;;

export const SignUpSchema = z.object({
  name: z.string().min(1, { message: &#39;🚨 이름은 필수로 입력해주셔야 합니다.&#39; }),
  age: z
    .number({
      invalid_type_error: &#39;🚨 나이는 숫자형식으로 입력해주셔야 합니다.&#39;,
    })
    .min(1, { message: &#39;🚨 나이는 1살부터 입력이 가능합니다.&#39; }),
  studentNumber: z
    .string()
    .min(10, { message: &#39;🚨 학번을 10자리로 입력해주세요.&#39; })
    .max(10, { message: &#39;🚨 학번을 10자리로 입력해주세요.&#39; }),
  major: z
    .string()
    .min(1, { message: &#39;🚨 전공을 필수로 입력해주셔야 합니다.&#39; }),
  phoneNumber: z.string().regex(/^\d{3}-\d{4}-\d{4}$/, {
    message: &#39;🚨 올바른 전화번호 형식을 입력해주세요. 예: 000-0000-0000&#39;,
  }),
});</code></pre><p>보통 zod를 사용할 경우 Schema를 통해서 Type을 선언 후 여러 에러 메시지를 선언할 수 있게 된다.</p>
<p>이후 react-hook-form을 통해 작성을 할 때</p>
<pre><code>const {
    register,
    formState: { errors },
  } = useForm&lt;SignUpSchemaType&gt;({
    resolver: zodResolver(SignUpSchema),
  });</code></pre><p>zodResolver를 통해 react-hook-form과 연결시켜준다. 이렇게 되면 react-hook-form을 통해서 register를 통해 예외처를 다 작성해줬어야 했지만</p>
<pre><code> &lt;SignupInput
          id=&#39;major&#39;
          title=&#39;전공&#39;
          placeholder=&#39;전공의 정식명칭을 입력해주세요.&#39;
          type=&#39;string&#39;
          register={register(&#39;major&#39;)}
 /&gt;</code></pre><p>간단하게 컴포넌트 데이터를 넘겨줄 명을 써주면 된다.</p>
<pre><code>&lt;ErrorMessage
          errors={errors}
          name=&#39;major&#39;
          render={({ message }) =&gt; &lt;Error role=&#39;alert&#39;&gt;{message}&lt;/Error&gt;}
/&gt;</code></pre><p>이 때 react-hook-form 라이브러리를 이용하여 Error Message라는 컴포넌트를 사용하여 작성해주었다.</p>
<p>이후 추가 정보 입력시 잘 적용되는 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/c8abdc1c-bead-4fc7-89a6-f3fb908d56a0/image.png" alt=""></p>
<h1 id="정리하자면">정리하자면</h1>
<p>react-hook-form이랑 zod를 사용하게 되면 ref를 통해서 얻는 불안정성을 조금 더 높일 수 있게 된다. 물론 그렇다고 해서 꼭 Zod를 써야하나? 이 부분에 관련해서는 생각하기 나름인것 같다.(그렇다면 JS는 진짜 문제가 많기 때문)
그래서 선택에 따라서 한 번 정말 프로젝트 내에서 필요하다라고 생각하면 써보는 것을 강력 추천한다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[야레야레 못 말리는 Context API - (카카오 테크 캠퍼스)]]></title>
            <link>https://velog.io/@k_gu_wae123/contextAPI-ver-2</link>
            <guid>https://velog.io/@k_gu_wae123/contextAPI-ver-2</guid>
            <pubDate>Sat, 13 Jul 2024 02:19:09 GMT</pubDate>
            <description><![CDATA[<h1 id="context-api가-상태관리-도구가-아니라고">Context API가 상태관리 도구가 아니라고?</h1>
<p>최근 카카오 테크 캠퍼스 과제 때문에
<code>질문 2. 리액트 Context 나 Redux는 언제 사용하면 좋을까요? (로그인을 제외한 예시와 이유를 함께 적어주세요.)</code>
에 대해서 답을 한적이 있었다.
이 때문에 이전 포스트 글인 <a href="https://velog.io/@k_gu_wae123/contextapi">https://velog.io/@k_gu_wae123/contextapi</a> 
컨텍스트 API에 대해서 글을 작성하였었다.</p>
<p>하지만 피드백을 통해서
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/c7bd952f-7089-49a3-8717-6e59da5b4dac/image.png" alt=""></p>
<p>Context API에 다시 고민하게 되는 계기를 얻게 되었다.</p>
<h1 id="야레야레-못말려-context-api">야레야레 못말려 Context API</h1>
<p>나는 처음에 Context API가 상태관리 도구 중에 하나로 오해했었다. 왜냐하면 
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/8805cbfe-0257-43d5-9db2-bb6cf352a6b9/image.png" alt="">
React에서는 Context API를 사용하기 위해서는 Context의 Provider와 Consumer를 사용해야한다. 이때 Context에 저장된 데이터를 사용하기 위해서는 공통 부모 컴포넌트에 Context의 Provider를 사용하여 데이터를 제공해야 되고, 데이터를 사용하려는 컴포넌트는 Context 의 Consumer를 사용해서 실제 데이터를 사용하게 되기 때문이다.</p>
<p>그래서 전역적으로 어떻게 보면 상태를 이어주기때문에 관리하는 도구라고 생각을 하였다.</p>
<h2 id="공식-문서를-한-번-볼까">공식 문서를 한 번 볼까?</h2>
<h3 id="context-api-정의">Context API 정의</h3>
<h4 id="react-공식문서">React 공식문서</h4>
<p><a href="https://ko.react.dev/learn/passing-data-deeply-with-context">https://ko.react.dev/learn/passing-data-deeply-with-context</a></p>
<blockquote>
<p>보통의 경우 부모 컴포넌트에서 자식 컴포넌트로 props를 통해 정보를 전달합니다. 그러나 중간에 많은 컴포넌트를 거쳐야 하거나, 애플리케이션의 많은 컴포넌트에서 동일한 정보가 필요한 경우에는 props를 전달하는 것이 번거롭고 불편할 수 있습니다. Context를 사용하면  명시적으로 props를 전달해주지 않아도 부모 컴포넌트가 트리에 있는 어떤 자식 컴포넌트에서나 (얼마나 깊게 있든지 간에) 정보를 사용할 수 있습니다.</p>
</blockquote>
<p>공식문서에 대한 내용을 보면 상태관리라는 말이 따로 언급되어 있지 않고, <code>부모 컴포넌트가 트리에 있는 자식 컴포넌트에 정보를 사용할 수 있다</code>라고 되어있다. </p>
<h3 id="zustand-정의">Zustand 정의</h3>
<h4 id="zustand의-공식-문서">Zustand의 공식 문서</h4>
<p><a href="https://docs.pmnd.rs/zustand/getting-started/introduction">https://docs.pmnd.rs/zustand/getting-started/introduction</a></p>
<blockquote>
<p>작고 빠르며 확장 가능한 베어본 상태 관리 솔루션입니다. Zustand는 Hook기반의 편안한 API를 가지고 있습니다. 틀에 박힌 형식이나 고집이 없지만, 명시적이고 플럭스 같은 규칙이 충분합니다.
귀여워서 무시하지 마세요. 발톱이 있거든요!  zombie child problem, React concurrency,  context loss between mixed renderers. 과 같은 일반적인 함정을 처리하는 데 많은 시간을 들였습니다 . React 공간에서 이 모든 것을 제대로 처리하는 유일한 상태 관리자일 수 있습니다.</p>
</blockquote>
<p>zustand의 공식문서에 대한 내용을 보면 <code>유일한 상태관리자</code>라는 언급이 따로 있다. </p>
<h2 id="context-api-vs-상태관리-라이브러리zustand의-차이">Context API vs 상태관리 라이브러리(Zustand)의 차이</h2>
<p>앞서 공식문서에서 바라봤던 대로 공식 문서내에서 정의 차이가 확실히 나는것을 알 수 있다.</p>
<p>그러면 Context API와 상태관리 라이브러리들의 정확한 차이가 무엇일까?</p>
<h3 id="context-api">Context API</h3>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/6088f5fe-86d6-47be-ba88-bc69f9f025bd/image.png" alt=""></p>
<p>위의 이미지 처럼 결국 Context API는 전역적으로 뿌려주는 것처럼 보이지만 어떻게 보면 통로라고 생각하는게 더 빠를 것 같다.</p>
<p>Context 는 실제로 아무것도 관리하지 않는다. 단순 값을 전달하는 파이프와 같다. 사용하는 주요 목적은 props-drilling 을 피하는 것이다.
props-passing 로직을 작성할 필요가 없기 때문에 코드가 단순해진다.</p>
<p>개념적으로는 종속성 주입의 한 형태이다. 자식 구성 요소에 특정한 상태값이 필요하다는 것은 알고 있지만 값 자체를 생성하거나 설정하려 하지 않는다. 대신 상위 요소가 런타임에 해당 값을 전달한다고 가정한다.</p>
<h3 id="상태관리-라이브러리zustand">상태관리 라이브러리(Zustand)</h3>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/5d973efc-cd08-424a-b1ba-34f2a3e7169b/image.png" alt=""></p>
<p>우선적으로 zustand는 구독의 형태로 상태관리를 한다.</p>
<ol>
<li>스토어 생성</li>
</ol>
<ul>
<li>&#39;create&#39; 함수를 사용하여 스토어를 생성한다.</li>
<li>스토어는 상태와 상태를 변경할 수 있는 액션들을 포함한다.</li>
</ul>
<ol start="2">
<li>상태 관리</li>
</ol>
<ul>
<li>스토어 내부에 상태가 저장된다.</li>
<li>액션을 통해 상태를 변경할 수 있다.</li>
</ul>
<ol start="3">
<li>구독 메커니즘</li>
</ol>
<ul>
<li>컴포넌트가 스토어의 특정 부분을 구독한다.</li>
<li>상태가 변경되면 구독한 컴포넌트만 리렌더링된다.</li>
</ul>
<ol start="4">
<li>셀렉터 사용</li>
</ol>
<ul>
<li>셀렉터를 통해 스토어의 특정 부분만 선택적으로 사용할 수 있다.</li>
<li>불필요한 리렌더링을 방지한다.</li>
</ul>
<ol start="5">
<li>상태 업데이트</li>
</ol>
<ul>
<li>액션을 호출하여 상태를 업데이트한다.</li>
<li>내부적으로 불변성을 유지하며 상태를 업데이트한다.</li>
</ul>
<ol start="6">
<li>리렌더링</li>
</ol>
<ul>
<li>상태가 변경되면 해당 상태를 구독하고 있는 컴포넌트들만 리렌더링된다.</li>
</ul>
<h3 id="정리하자면">정리하자면</h3>
<p>Context가 상태관리가 아닌 이유는</p>
<ul>
<li>초기 값을 저장한다.</li>
<li>현재 값을 읽을 수 있다.</li>
<li>값 업데이트가 가능하다.</li>
</ul>
<p>이 3가지의 경우에 아무것도 해당하지 않는다.</p>
<h1 id="그러면-이제-상태관리를-어떻게-쓰는-게-맞을까">그러면 이제 상태관리를 어떻게 쓰는 게 맞을까?</h1>
<p>사실 이건 정답이 없다. 이 고민을 카테캠 라이브 강의를 통해 여쭤보기도 했다. 그렇지만 그 분도 정답은 없다고 했었다.
생각해보니까 이 과정을 예전에 지인한테 물어본 적이 있었던 것 같다.</p>
<p><code>상태관리를 zustand 이걸로 전체로만 관리를 하는게 맞을까? 아니면 굳이 전역적으로 관리할 필요가 없는 상태관리는 useState를 통해서 일반 React Hook을 섞어서 관리하는게 맞을까?</code></p>
<p>사실 그때의 결론은 zustand만 써서 관리하는게 맞겠다라고 생각했다. 왜냐하면 전역적으로 관리하게 되면 나중에 유지 보수하기도 쉽고 한눈에 코드를 알아보기 쉽기 때문이다.</p>
<p>하지만 이번 계기를 통해 Context API + Zustand 두 개를 이용하여 상태관리를 써볼 계획이다.</p>
<p>강사님께 이런 답변을 들었었다.
<code>사실 정답은 없지만 Zustand에서 전역적으로 관리를 하는것도 좋지만 정말 전역적으로 관리해야되는 정보들을 제외하고는 Context API를 통해서 특정 UI에게 맞는 상태를 이어주는 것이 개발할 때 방향성을 잡기도 좋다고 봐요.</code></p>
<p>이 이야기를 들어보니 확실히 납득이 되어버렸다. 물론 전역적으로 관리를 하게 되면 코드적으로는 유지보수하기 매우 편할 수도 있지만 과연 Render Tree를 그리게 되었을 때 코드 방향적으로 유기적일까? 라는 생각이 들었다.</p>
<p>물론 전역적으로 관리해야되는 상태들 예를 들자면 다크모드, 로그인같이 전역적으로 상태관리해야되는 것들은 무조건 상태관리 라이브러리를 쓰는 것이 좋다. 왜냐하면 Context API에서는 아직 해결하지 못한 리렌더링 이슈도 있고 퍼포먼스를 고려했을 때 상태관리 라이브러리들이 좋다. 그러나 어떻게 보면 큰 창고에서 상태를 뽑아 쓰기 때문에 내가 지정해줘야 되기 때문에 Store라는 큰 상태관리만 두고 봤을때 특정 상태관리가 어떤 UI에 이용되는지 모르는 경우가 있다.</p>
<p>그래서 context API를 사용함으로써 특정 UI에서만 상태관리를 이어주는 것이 개발 방향성이 효율적이지 않을까 라는 생각이 든다. 재렌더링 이슈야 useMemo나 useCallback으로 막아주면 되지않을까?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[반찬 투정 하던 '상남자' 개발자의 최후 - 상태관리(Context API) (카카오 테크 캠퍼스)]]></title>
            <link>https://velog.io/@k_gu_wae123/contextapi</link>
            <guid>https://velog.io/@k_gu_wae123/contextapi</guid>
            <pubDate>Fri, 05 Jul 2024 13:49:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/26d08f44-9256-4a02-b9db-8a508d68e404/image.png" alt=""></p>
<p>난 사실 Zustand 러버이다. 그래서 상태관리를 사용할 때 항상 zustand나 Recoil만 써왔다. 
사실 Redux도 써보고 ContextAPI도 공부해왔었지만 상대적인 난이도와 재렌더링에 대한 이슈때문에 굳이 두 상태관리를 써야될까라는 생각을 하고 있었다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/fbdae2cb-0534-4deb-b962-6e7045d847cf/image.png" alt=""></p>
<p>하지만 이번에 카카오 테크 캠퍼스 과제를 하게 되면서 해당 조건문에 Context API를 사용하여 상태관리를 구현하게 되었다. 그래서 좀 불만이 있었다. 왜 편한 라이브러리 두고 내장 기능을 쓸까? 라는 고민을 했었다.</p>
<p> 그렇지만 최근에 들어간 리액트 오픈 카카오톡방에서도 토스 개발자 한 분께서 저희 팀은 Context API를 사용한다고 해왔었고, 또한 카카오테크캠퍼스에서도 강사분들께서 ContextAPI를 권장을 하고 있어서 왜 React 자체의 상태관리인 Context API에 대해서 쓸 까에 대해서 고민이 되었다.
좀 충격적인 부분은 오픈톡방에서 상태관리를 전역적으로 굳이 관리를 해야되나요? 라는 대답이었었는데 결국 Context API의 재 렌더링 문제로 나오게 된것들이 서드파티의 라이브러리들인 redux, jotai, zustand, recoil 이런 것들이 아닌가에 대해서 의문점이 생기게 되었다.</p>
<h2 id="리액트에서의-props와-state">리액트에서의 Props와 State</h2>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/97661d2d-6921-4bde-a9dd-3d83e3f7df5d/image.png" alt=""></p>
<p>React에서 Props와 State는 부모 컴포넌트와 자식 컴포넌트 또는 한 컴포넌트 안에서 데이터를 다루기 위해서 사용된다. 이때 이 Props와 State를 사용하게 되면 부모 컴포넌트에서 자식 컴포넌트로 데이터가 흐르게 된다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/190c8cb8-90c7-49e0-bc29-29376b4e5ae9/image.png" alt=""></p>
<p>이떄 만약 다른 컴포넌트에서 데이터를 사용하고 싶은 경우 사용하고 싶은 데이터와 이 데이터를 사용할 컴포넌트를 공통 부모 State에 만들고 사용하고자 하는 데이터의 Props를 전달하면 이 문제를 해결할 수 있게 된다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/f9d630db-1972-4911-914d-cfdf9f27e1b8/image.png" alt=""></p>
<p>하지만 이처럼 컴포넌트 사이에 공유되는 데이터를 위해 매번 부모 컴포넌트를 수정하고 하위 모든 컴포넌트에 데이터를 Props로 전달하는 것은 매우 비효율적이다.</p>
<p><a href="https://github.com/KimKyuHoi/Matcher_FE">GitHub - KimKyuHoi/Matcher_FE: 예약과 구인구직을 동시에 한눈에 알아보는 사이트 으라차차입니다.</a></p>
<p>이 부분은 본인이 실제로 겪었던 내용인데 진짜 상태관리 라이브러리에 대해 잘 모르고 막 엄청 잘 활용 못하던 시절 부모 컴포넌트에게 데이터를 전달하기 위해 진짜 힘들어 죽는 줄 알았다. </p>
<p>이 문제를 해결하기 위해 React에서는 Flux 패턴이라는 개념을 도입하기 시작했고 그에 걸맞는 Context API를 제공하기 시작했다.</p>
<h2 id="context-api">Context API</h2>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/672fa4e7-53e4-4407-a394-0bd213a90f86/image.png" alt=""></p>
<p>Context API는 부모 컴포넌트로부터 자식 컴포넌트로 전달되는 데이터의 흐름과는 상관없이 전역적인 데이터를 다룰 때 사용한다. 전역 데이터를 Context에 저장한 뒤에, 데이터가 필요한 컴포넌트에서 해당 데이터를 불러와서 사용할 수 있다. 어찌보면 Redux, Zustand의 Store 폴더에서 전역적으로 관리하는 개념과 뭔가 비슷한 느낌이 든다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/17c0c4a8-bb47-4515-b7fc-e3fa5c010f00/image.png" alt=""></p>
<p>React에서는 Context API를 사용하기 위해서는 Context의 Provider와 Consumer를 사용해야한다. 이때 Context에 저장된 데이터를 사용하기 위해서는 공통 부모 컴포넌트에 Context의 Provider를 사용하여 데이터를 제공해야 되고, 데이터를 사용하려는 컴포넌트는 Context 의 Consumer를 사용해서 실제 데이터를 사용하게 된다.</p>
<hr>
<blockquote>
<p><strong>짤막한 용어 정리</strong></p>
<ul>
<li><strong>Provider:</strong> Context를 생성하고, 이를 하위 컴포넌트들에게 제공하는 컴포넌트를 말한다. 전역적으로 관리할 상태를 value prop으로 넘겨주면, 하위 컴포넌트들이 이 상태를 사용할 수 있게 된다.</li>
<li><strong>Consumer:</strong> Provider에서 제공하는 상태를 사용하는 컴포넌트를 말한다. Provider의 상태를 구독하고, 상태가 변경될 때마다 재렌더링 된다.</li>
</ul>
</blockquote>
<hr>
<p>약간 예시로 들자면 이런 느낌이다.</p>
<ul>
<li>엄마와 형제들이 있는데 Context가 첫째인 느낌이고 나머지 자식 컴포넌트들이 나머지 형제들이라고 비유를 하자면 애들끼리 사고 치는 것을 방지하기 위해서 큰 형이 우선 부모님한테 우리 이거 할꺼에요<del>라고 보고를 한 뒤 나머지 형제들한테 할 내용들을 보내주는 느낌이다. (뭔가 느낌이 참</del> 군대스럽다. 선 보고 후 조치라는 느낌?)</li>
</ul>
<h3 id="context-사용법">Context 사용법</h3>
<pre><code class="language-tsx">import { createContext, useContext, useEffect, useState } from &#39;react&#39;;

interface AuthContextType {
  isLoggedIn: boolean;
  userId: string | null;
  login: (id: string) =&gt; void;
  logout: () =&gt; void;
}

const AuthContext = createContext&lt;AuthContextType | undefined&gt;(undefined);

export const AuthProvider = ({ children }: { children: React.ReactNode }) =&gt; {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [userId, setUserId] = useState&lt;string | null&gt;(null);

  useEffect(() =&gt; {
    const token = sessionStorage.getItem(&#39;authToken&#39;);
    if (token) {
      setIsLoggedIn(true);
      setUserId(token);
    }
  }, []);

  const login = (id: string) =&gt; {
    sessionStorage.setItem(&#39;authToken&#39;, id);
    setIsLoggedIn(true);
    setUserId(id);
  };

  const logout = () =&gt; {
    sessionStorage.removeItem(&#39;authToken&#39;);
    setIsLoggedIn(false);
    setUserId(null);
  };

  return (
    &lt;AuthContext.Provider value={{ isLoggedIn, userId, login, logout }}&gt;
      {children}
    &lt;/AuthContext.Provider&gt;
  );
};

export const useAuth = () =&gt; {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error(&#39;useAuth must be used within an AuthProvider&#39;);
  }
  return context;
};
</code></pre>
<p>이번에 과제를 사용하면서 로그인 관련해서 커스텀 훅 코드를 짜게 되었었다. </p>
<p>이때 보면 </p>
<pre><code class="language-tsx">import { createContext, useContext, useEffect, useState } from &#39;react&#39;;

const AuthContext = createContext&lt;AuthContextType | undefined&gt;(undefined);</code></pre>
<p>createContext라는 함수를 선언하여 사용할 수 있다.</p>
<p>그리고</p>
<pre><code class="language-tsx">export const AuthProvider = ({ children }: { children: React.ReactNode }) =&gt; {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [userId, setUserId] = useState&lt;string | null&gt;(null);

  useEffect(() =&gt; {
    const token = sessionStorage.getItem(&#39;authToken&#39;);
    if (token) {
      setIsLoggedIn(true);
      setUserId(token);
    }
  }, []);

  const login = (id: string) =&gt; {
    sessionStorage.setItem(&#39;authToken&#39;, id);
    setIsLoggedIn(true);
    setUserId(id);
  };

  const logout = () =&gt; {
    sessionStorage.removeItem(&#39;authToken&#39;);
    setIsLoggedIn(false);
    setUserId(null);
  };

  return (
    &lt;AuthContext.Provider value={{ isLoggedIn, userId, login, logout }}&gt;
      {children}
    &lt;/AuthContext.Provider&gt;
  );
};
</code></pre>
<p>로직을 작성한 뒤 AuthContext.Provider를 통해 Children을 감싸준다. 그리고 로직들을 value 즉 상태관리 및 함수들을 넘겨주게 된다.</p>
<p>마지막으로</p>
<pre><code class="language-tsx">export const useAuth = () =&gt; {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error(&#39;useAuth must be used within an AuthProvider&#39;);
  }
  return context;
};
</code></pre>
<p>useAuth 훅을 통해서 컴포넌트 내에서 사용하고 싶은 함수나 State들을 선언을 통해 가지고 와서 사용할 수 있다.</p>
<p>Ex)</p>
<pre><code class="language-tsx">// src/hooks/custom-hooks/useLogin.ts
import { useState } from &#39;react&#39;;
import { useLocation, useNavigate } from &#39;react-router-dom&#39;;

import { useAuth } from &#39;@/contexts/AuthContext&#39;;

const useLogin = () =&gt; {
  const [name, setName] = useState&lt;string&gt;(&#39;&#39;);
  const [password, setPassword] = useState&lt;string&gt;(&#39;&#39;);
  const navigate = useNavigate();
  const location = useLocation();
  const { login } = useAuth();

  const handleNameChange = (event: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setName(event.target.value);
  };

  const handlePasswordChange = (event: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setPassword(event.target.value);
  };

  const handleLoginClick = () =&gt; {
    if (name.length === 0 || password.length === 0) {
      alert(&#39;아이디와 비밀번호를 입력해주세요.&#39;);
    } else {
      login(name);
      const from = location.state?.from?.pathname || &#39;/&#39;;
      navigate(from, { replace: true });
    }
  };

  return {
    name,
    password,
    handleNameChange,
    handlePasswordChange,
    handleLoginClick,
  };
};

export default useLogin;</code></pre>
<p>Login 커스텀 훅인데 따로 Context API를 통해서 그냥 선언을 통해 넘겨주는 모습이다.</p>
<h2 id="context-api의-불편한-진실">Context API의 불편한 진실</h2>
<ul>
<li><a href="https://velog.io/@velopert/react-context-tutorial#%EA%B0%92%EA%B3%BC-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%ED%95%A8%EC%88%98%EB%A5%BC-%EB%91%90%EA%B0%9C%EC%9D%98-context%EB%A1%9C-%EB%B6%84%EB%A6%AC%ED%95%98%EA%B8%B0">https://velog.io/@velopert/react-context-tutorial#값과-업데이트-함수를-두개의-* context로-분리하기</a></li>
</ul>
<p>이 분의 글과 코드를 참고하였고 직접 코드를 쳐보았다.</p>
<p>우선 똑같이 counterContext.tsx 파일을 만들어 준다.</p>
<pre><code class="language-tsx">import { createContext, useState } from &#39;react&#39;;

export const CounterValueContext = createContext(0);
export const CounterActionContext = createContext(() =&gt; {});

export function CounterProvider({ children }: { children: React.ReactNode }) {
  const [counter, setCounter] = useState(0);

  const increase = () =&gt; {
    setCounter((prev) =&gt; prev + 1);
  };

  console.log(&#39;Provider rendered&#39;);

  return (
    &lt;CounterValueContext.Provider value={counter}&gt;
      &lt;CounterActionContext.Provider value={increase}&gt;
        {children}
      &lt;/CounterActionContext.Provider&gt;
    &lt;/CounterValueContext.Provider&gt;
  );
}
</code></pre>
<p>Component.tsx 파일을 따로 만들었고</p>
<pre><code class="language-tsx">import { useContext } from &#39;react&#39;;
import { CounterActionContext, CounterValueContext } from &#39;./counterContext&#39;;

export const MyComponent1 = () =&gt; {
  console.log(&#39;Component 1 rendered&#39;);

  return &lt;div&gt; Component 1 &lt;/div&gt;;
};

export const MyComponent2 = ({ children }: { children: React.ReactNode }) =&gt; {
  console.log(&#39;Component 2 rendered&#39;);

  return &lt;div&gt;Component 2{children}&lt;/div&gt;;
};

export const MyComponent3 = () =&gt; {
  const counter = useContext(CounterValueContext);

  console.log(&#39;Component 3 rendered&#39;);

  return &lt;div&gt; Component 3. Count: {counter} &lt;/div&gt;;
};

export const MyComponent4 = () =&gt; {
  const increase = useContext(CounterActionContext);
  console.log(&#39;Component 4 rendered&#39;);

  return &lt;div&gt; Component 4 &lt;/div&gt;;
};

export const MyButton = () =&gt; {
  const increase = useContext(CounterActionContext);

  console.log(&#39;My Button rendered&#39;);

  return &lt;button onClick={() =&gt; increase()}&gt; + &lt;/button&gt;;
};
</code></pre>
<p>최종적으로 App.tsx 파일을 만들었다.</p>
<pre><code class="language-tsx">import {
  MyButton,
  MyComponent1,
  MyComponent2,
  MyComponent3,
  MyComponent4,
} from &#39;./Component&#39;;
import { CounterProvider } from &#39;./counterContext&#39;;

function App() {
  return (
    &lt;CounterProvider&gt;
      &lt;MyComponent1 /&gt;
      &lt;MyComponent2&gt;
        &lt;MyComponent3 /&gt;
      &lt;/MyComponent2&gt;
      &lt;MyComponent4 /&gt;
      &lt;MyButton /&gt;
    &lt;/CounterProvider&gt;
  );
}

export default App;</code></pre>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/82605b1f-c262-4942-aed8-93dc0cd12194/image.png" alt=""></p>
<p>이 때 컴포넌트 5개는 각각의 조건들이 있다.</p>
<ul>
<li>MyComponent1은 어떤 Context를 사용하지 않았다.</li>
<li>MyComponent2는 어떤 Context도 가져다 사용하지 않았지만, Context를 가져다 사용하는 children 컴포넌트를 렌더링한다.</li>
<li>MyComponent3는 CounterValueContext를 가져다 사용한다.</li>
<li>MyComponent4는 CounterActionContext를 가져오지만 사용하지 않는다.</li>
<li>MyButton은 CounterActionContext를 가져오고 클릭시 increase 함수를 실행한다.</li>
</ul>
<p>과연 여기서 MyButton을 클릭해서 increase 함수를 실행시켜 counter값을 변경했을 때, 다음 중 리렌더링이 되는 컴포넌트는 무엇일까?</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/97480b56-d202-41e5-90cf-31cb25dc2545/image.png" alt=""></p>
<p>우선 CounterProvider가 리렌더링 되는 이유는 setCounter 함수가 실행되기 때문에 어쩔수 없이 발생한다.</p>
<p>또한 MyComponent3도 CounterValueContext를 가져다 사용하여 리렌더링이 되었다. 하지만 MyComponent2는 리렌더링이 되지 않았다. <strong>여기서 Context의 값이 변경된 경우, 해당 Context의 Consumer만 영향을 받는다는 것이다.</strong></p>
<hr>
<p><strong>Q. 왜 MyComponent4와 MyButton은 리렌더링이 될까?</strong></p>
<p>CounterProvider가 리렌더링이 되면서 increase 함수가 새로 생성이 되어버린다.</p>
<p>이때 increase의 참조가 달라지면서 CounterActionContext의 값도 변경이 되어 버리기 때문에 두 컴포넌트도 리렌더링이 되어버린다. 이를 방지하기 위해서는 useCallback함수를 사용하게 되면 increase 함수를 실행시켜도 리렌더링되지 않을 수 있다.</p>
<pre><code class="language-tsx"> export function CounterProvider({children}: {children: React.ReactNode}){
        const [counter, setCounter] = useState(0);

        const increase = useCallback( () =&gt; {
            setCounter((prev) =&gt; prev + 1);
        }, [])

        console.log(&#39;Provider rendered&#39;)

        return (
            &lt;CounterValueContext.Provider value={counter}&gt;
                &lt;CounterActionContext.Provider value={increase}&gt;
                    {children}
                &lt;/CounterActionContext.Provider&gt;
            &lt;/CounterValueContext.Provider&gt;
        )
    }</code></pre>
<h2 id="그러면-진짜-contextapi의-문제점이-무엇일까">그러면 진짜 ContextAPI의 문제점이 무엇일까?</h2>
<pre><code class="language-tsx">import {createContext, useState} from &quot;react&quot;;

export const userContext = createContext({name: &#39;&#39;, email: &#39;&#39;});
export const userActionContext = createContext((value: string) =&gt; {});

export function UserContextProvider({children}: {children: React.ReactNode}){
    const [userName, setUserName] = useState(&#39;John&#39;);
    const [userEmail, setUserEmail] = useState(&#39;john@example.com&#39;);

    return (
        &lt;userContext.Provider value={{name: userName, email: userEmail}}&gt;
            &lt;userActionContext.Provider value={setUserName}&gt;
                {children}
            &lt;/userActionContext.Provider&gt;
        &lt;/userContext.Provider&gt;
    )
}</code></pre>
<pre><code class="language-jsx">function UserNameComponent(){
  const {name} = useContext(userContext);
  console.log(&#39;UserNameComponent rendered&#39;);

  return &lt;div&gt; User Name: {name} &lt;/div&gt;}

function UserEmailComponent(){
  const {email} = useContext(userContext);
  console.log(&#39;UserEmailComponent rendered&#39;);

  return &lt;div&gt; User Email: {email} &lt;/div&gt;}

function UserNameButton(){
  const setName = useContext(userActionContext);
  console.log(&#39;UserNameButton rendered&#39;);

  return &lt;button onClick={() =&gt; setName(&#39;Hello&#39;)}&gt; Set Name &lt;/button&gt;}

export default function App(){
  return (
    &lt;UserContextProvider&gt;
      &lt;UserNameComponent /&gt;
      &lt;UserEmailComponent /&gt;
      &lt;UserNameButton /&gt;
    &lt;/UserContextProvider&gt;)
}</code></pre>
<p>이 두 코드를 보면 Context가 객체일때 객체 일부의 프로퍼티만 업데이트 되더라도 해당 Context를 가져다가 사용하는 모든 Consumer가 리렌더링된다는 것이다.</p>
<p>이떄 userName을 변경하게 되면 </p>
<p>UserNameComponent와, UserEmailComponent가 리렌더링된다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/92a9de90-4897-45d0-9e68-073cb920f37c/image.png" alt=""></p>
<p>만약 이런 문제로 인해서 하나의 value로 provide를 해주게 될 경우 불필요한 리렌더링이 야기된다. 만약 상태가 여러개일 경우에 provider도 그만큼 많아지게 되는데, 코드의 수가 무한방대해지고 많아지게 된 상태에서 provider 개수가 몇 개 인지 모르는 상태에서 새로운 provider가 필요하게 된다면?
새로운 node를 트리 중간에 삽입해야 되는데, 큰 렌더링 이슈가 발생할 것이다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/b81098e2-9a53-4af1-94bb-49972424c410/image.png" alt=""></p>
<p>또한 Provider와 Consumer 내에서 Provider는 무조건 Consumer 상위에 있어야되기 때문에 코드 스플리팅하기가 매우 어려워질 수 있다.</p>
<h2 id="정리">정리</h2>
<p>Context API를 사용할떄 특정 Context의 값이 변경되면, 기본적으로 해당 Context의 Consumer라면 전부 리렌더링이 되어버린다. 하지만 Consumer의 자식 컴포넌트 같은 경우에는 리액트의 리렌더링 매커니즘 때문에 Context를 가져와서 사용하지 않아도 리렌더링이 된다.</p>
<p>그래서 결국에 진짜 문제점은 Context의 값 중 일부만 가져와서 사용해도 다른 값이 업데이트 될 경우 리렌더링이 된다는 점이다. 그렇기 때문에 Context를 분리해서 생성해주는 것이 매우 중요한 것 같다.</p>
<h2 id="결론-및-내-생각">결론 및 내 생각</h2>
<p>리액트를 처음 공부할 떄 리렌더링이라는 문제가 발생하는 경우가 결국 해당 Context의 Consumer라면 전부 리렌더링이 되어버린다는 사실에 대해서 이제서야 깨달음을 알게된 것 같다는 생각이 든다. </p>
<p>결국에는 Context API를 통해 코드를 짜기 위해서는 아주 치밀하게 고려하여 코드를 짜야겠다는 생각이 들었다. Context API의 이상적인 방안에 맞게 코드를 짜게 되면 아주 클린한 코드가 되지 않을까라는 생각과 동시에 매우 끈끈한 코드가 되지 않을까 라는 생각이 들었다.</p>
<p>그러면서 동시에 이렇게 코드를 짤 빠에 굳이 Zustand, Recoil, Redux를 버려가면서 까지 사용할 필요가 있을까,,,?라는 고민과 함께 막 그렇다고 해서 엄청 Context API에 대해서 무조건 나쁘다, 다른 서드파티 라이브러리를 사용하는 것이 좋겠다라는 생각이 들게 되었다. 왜냐하면 리렌더링 부분쪽이야 useCallback이랑 useMemo를 사용해서 리렌더링을 막으면 되니까? 라는 생각이다.
뭐 결과론적으로 얘기하자면 그냥 프로젝트에 맞는 상태관리를 쓰면 될 것 같다.
약간 세상의 나쁜 상태관리는 없는 느낌이랄까?</p>
<h3 id="참고-글">참고 글</h3>
<ul>
<li><a href="https://velog.io/@velopert/react-context-tutorial#%EA%B0%92%EA%B3%BC-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%ED%95%A8%EC%88%98%EB%A5%BC-%EB%91%90%EA%B0%9C%EC%9D%98-context%EB%A1%9C-%EB%B6%84%EB%A6%AC%ED%95%98%EA%B8%B0">https://velog.io/@velopert/react-context-tutorial#값과-업데이트-함수를-두개의-* context로-분리하기</a></li>
<li><a href="https://velog.io/@dahyeon405/Context-API%EC%9D%98-%EB%A6%AC%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%8C%80%ED%95%9C-%EC%98%A4%ED%95%B4">https://velog.io/@dahyeon405/Context-API의-리렌더링-대한-오해</a></li>
<li><a href="https://despiteallthat.tistory.com/184">https://despiteallthat.tistory.com/184</a></li>
<li><a href="https://velog.io/@hoonnn/React-Context-API%EC%99%80-useContext">https://velog.io/@hoonnn/React-Context-API와-useContext</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[빙글빙글 돌아가는 페이지 경로처럼]]></title>
            <link>https://velog.io/@k_gu_wae123/routing</link>
            <guid>https://velog.io/@k_gu_wae123/routing</guid>
            <pubDate>Tue, 02 Jul 2024 05:33:28 GMT</pubDate>
            <description><![CDATA[<p><strong>본 글 내용은 카카오 테크 캠퍼스에서 배웠던 내용을 토대로 정확한 사용법을 공식문서를 참고하여 적은 글입니다.</strong></p>
<h2 id="routing이란">Routing이란?</h2>
<p>특정 경로로 들어오는 요청을 어떻게 처리할 지를 결정하는 방식이다.</p>
<p>전통적으로</p>
<pre><code class="language-html">&lt;nav&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;/&quot;&gt;카카오 테크 월드&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;/jun&quot;&gt;준 월드&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;/poco&quot;&gt;포코 월드&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;/gongwon&quot;&gt;공원 월드&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/nav&gt;
</code></pre>
<p>nav &gt; ul &gt; li &gt; a 태그를 통해서 페이지를 이동하는 방식으로 구현되었었다.</p>
<p>이때 각각의 링크를 클릭하게 되면, 해당 경로로 서버에 요청을 보내고 응답을 받아서 화면을 다시 그리는 식으로 동작하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/26b25769-ff68-4415-8ec5-ccacd6c4e4ba/image.png" alt=""></p>
<h2 id="라우팅을-위해-필요한-것들">라우팅을 위해 필요한 것들</h2>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/f25a09bf-caf2-47c9-b138-181b0b06a33f/image.png" alt=""></p>
<p>라우팅을 하기 위해서는 대표적으로 3가지 작업이 필요하다.</p>
<ol>
<li>실제로 요청 들어오는 URI를 분석하기</li>
<li>어떤 경로로 들어왔을 때  어떤 걸 보여줄 지 연결하기</li>
<li>경로가 변경되는 history를 관리하기</li>
</ol>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/a6118be8-fd9d-41ab-b289-dcfe1471f52b/image.png" alt=""></p>
<h3 id="csr">CSR</h3>
<p>라우팅은 앞서 살펴봤듯이 원래 서버에서 해주던 일이었다. 그러나 점차 컴퓨터 성능도 좋아지고 SPA가 등장하게 되면서 자체적인 라우팅 시스템을 사용하는 방법이 같이 시작되었다. </p>
<p><strong>CSR 동작과정</strong></p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/f3991ad0-c307-4dc0-a78d-fa781b5441f1/image.png" alt=""></p>
<ol>
<li>유저가 웹사이트를 방문하게 되면, 브라우저가 서버에 콘텐츠를 요청한다.</li>
<li>CDN이 HTML파일과 JS로 접근할 수 있는 링크를 클라이언트로 보낸다.<ul>
<li>CDN이란?<ul>
<li>Contend Delivery Network의 줄임말로 데이터 사용량이 많은 애플리케이션의 웹 페이지 로드 속도를 높이는 상호 연결된 서버 네트워크이다.</li>
</ul>
</li>
</ul>
</li>
<li>클라이언트는 HTML과 JS를 다운로드 받는다. (이때 SSR이 아니므로 유저는 볼 수 없다.)</li>
<li>클라이언트는 JS를 다운로드 받는다.</li>
<li>다운이 완료된 JS가 실행하게 된다. 이때 데이터를 위한 API가 호출된다. (이때 User들은 Placeholder를 보게 된다.)</li>
<li>서버가 API로부터 요청에 응답한다.</li>
<li>API로 부터 받아온 data를 placeholder에 자리에 넣어주게된다. 이때 페이지는 상호작용이 가능해 진다.</li>
</ol>
<h3 id="react에서-csr">React에서 CSR?</h3>
<p>react에서는 react-router-dom을 주로 사용하여 라우팅을 진행하게 된다. </p>
<pre><code class="language-html">npm install react-router-dom</code></pre>
<p>보통 react-router-dom 라이브러리를 사용하게 되면</p>
<pre><code class="language-tsx">import {
  Link
} from &quot;react-router-dom&quot;;

&lt;ul&gt;
    &lt;li&gt;
        &lt;Link to=&quot;/&quot;&gt;카테캠 월드&lt;/Link&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;Link to=&quot;/jun&quot;&gt;준 월드&lt;/Link&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;Link to=&quot;/poco&quot;&gt;포코 월드&lt;/Link&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;Link to=&quot;/gongnwon&quot;&gt;공원 월드&lt;/Link&gt;
    &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>Link태그로 작성을 하거나</p>
<p>NavLink로도 코드를 작성할 수 있다.</p>
<pre><code class="language-tsx">import {
  NavLink
} from &quot;react-router-dom&quot;;

&lt;ul&gt;
    &lt;li&gt;
        &lt;NavLink to=&quot;/&quot;&gt;카테캠 월드&lt;/Link&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;NavLink to=&quot;/jun&quot;&gt;준 월드&lt;/Link&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;NavLink to=&quot;/poco&quot;&gt;포코 월드&lt;/Link&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;NavLink to=&quot;/gongnwon&quot;&gt;공원 월드&lt;/Link&gt;
    &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>물론 Navigate 함수를 쓸수 있다.</p>
<pre><code class="language-tsx">import { useNavigate } from &#39;react-router-dom&#39;;

const navigate = useNavigate();
const handleLink = (path) =&gt; {
  navigate.push(path);
};

&lt;button onClick={() =&gt; handleLink(&#39;/jun&#39;)}&gt;준 월드&lt;/button&gt;
&lt;button onClick={() =&gt; handleLink(&#39;/poco&#39;)}&gt;포코 월드&lt;/button&gt;</code></pre>
<h3 id="link">Link</h3>
<p>여기에서 <Link> 같은 경우에 html a 태그와 매우 유사하다. </p>
<Link>는 사용자가 다른 페이지를 클릭하거나 탭하여 다른 페이지로 이동할 수 있게 해주는 요소이다. React-router-dom에서 <Link>는 연결되는 리소스를 가리키는 실제 href를 사용하여 액세스 가능한 a태그 요소를 렌더링한다.

<Link to=’’>는 상위 경로를 기준으로 확인되고 명령줄 cd 함수와 동일하다.

<h3 id="navlink">NavLink</h3>
<p>NavLink 같은 경우는 active, pending, transitioning 같은 특수한 경우에 <Link>대용으로 사용된다.</p>
<p>기본적으로 active 요소가 추가되기때문에 스타일을 지정할 수 있다.</p>
<pre><code class="language-tsx"> &lt;nav id=&#39;sidebar&#39;&gt;
     &lt;NavLink to=&quot;/&quot;&gt;카테캠 월드&lt;/Link&gt;
 &lt;/nav&gt;</code></pre>
<pre><code class="language-css">#sidebar a.active {
  color: red;
}</code></pre>
<h3 id="usenavigate">useNavigate</h3>
<p>useNavigate hooks는 함수로 많이 사용된다.</p>
<p>navigate(-1) 같이 뒤로 갈 수 있는 함수로 사용될 수 도 있다.</p>
<h3 id="기본-라우터-세팅">기본 라우터 세팅</h3>
<ol>
<li><p>create-brower-router</p>
<ol>
<li><p>v6.4 이후로 새롭게 나타나게 된 방식이다.</p>
</li>
<li><p>이 방식이 생기게 되면서 action, loader 라는 개념도 새로 생겨나게 되었다.</p>
<pre><code class="language-css">import { createBrowserRouter, RouterProvider } from &#39;react-router-dom&#39;;

const router = createBrowserRouter([
 {
     path: &#39;/&#39;,
     element: &lt;MainPage /&gt;,
 },
 {
     path: &#39;/jun&#39;,
     element: &lt;JunWorld /&gt;,
 },
 ...
]);

&lt;RouterProvider router={router} /&gt;;
</code></pre>
</li>
</ol>
</li>
<li><p>BrowserRouter 방식</p>
<ol>
<li><p>Routes &gt; Route 형식으로 예전부터 사용되어 오던 방식이다.</p>
<pre><code class="language-css">&lt;BrowserRouter basename=&quot;/app&quot;&gt;
 &lt;Routes&gt;
     &lt;Route path=&quot;/&quot; element={&lt;MainPage /&gt;} /&gt;
     &lt;Route path=&quot;/jun&quot; element={&lt;JunWorld /&gt;} /&gt;
     ...
 &lt;/Routes&gt;
&lt;/BrowserRouter&gt;
</code></pre>
</li>
</ol>
</li>
</ol>
<h3 id="-잠깐-더-뜯어보기">+) 잠깐 더 뜯어보기</h3>
<p>물론 react-router-dom도 js로 구현할 수 있다.</p>
<p>강의에서는 React Context API와 브라우저에 제공하는 history, location API를 조합해 위에서 살펴봤던 라우팅을 위해 필요한 기능들을 구현한 라이브러리라고 할 수 있겠다.</p>
<p>이 부분을 추후에 고민하여 직접 구현해볼 예정이다.</p>
<h2 id="참고한-사이트">참고한 사이트</h2>
<p><strong>CSR vs SSR 특징 및 차이</strong> </p>
<p><a href="https://hahahoho5915.tistory.com/52">https://hahahoho5915.tistory.com/52</a></p>
<p><a href="https://dev-ellachoi.tistory.com/28">https://dev-ellachoi.tistory.com/28</a></p>
<p><strong>공식 문서(react-router-dom)</strong></p>
<p><a href="https://reactrouter.com/en/main/components/nav-link">https://reactrouter.com/en/main/components/nav-link</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[동시편집 그거 실패하면 라이브러리 사용! 성공하면 직접구현 아입니까!]]></title>
            <link>https://velog.io/@k_gu_wae123/Simultaneous-editing</link>
            <guid>https://velog.io/@k_gu_wae123/Simultaneous-editing</guid>
            <pubDate>Tue, 19 Mar 2024 09:35:58 GMT</pubDate>
            <description><![CDATA[<h1 id="마-니-요즘-왜-글-안쓰는데">마 니 요즘 왜 글 안쓰는데?</h1>
<p>오랜만에 글을 쓰는거 같다. 글을 썼는지 2달이 다 되어간다. 1월 중순부터 2월달 동안 스마일게이트 온라인 개발 캠프 때문에 정신이 없었던 것 같다. (스마게 회고록도 써야되는데 언제 쓰지,,,?)
이리저리 바쁘게 치여다니면서 스마일게이트 온라인 개발 캠프도 진행을 했었고, 일본 여행도 다녀왔었다. 
3월이 되면서 학기도 이제 시작이 되었고, 여러가지 일을 맡게 되었다.
멋쟁이 사자 아기사자 면접이라던가,,,공식 홈페이지 개발 TF가 된다던가 (사실 급하게 준비한다고 기대도 하지 않았는데 되어버려서 좀 깜짝 놀랐다). 학기에 진행하는 종합프로젝트 2도 회의때문에 정신이 없는것 같다. 웹마스터, 해달 트랙 등등,,, 일 벌리는 건 참 잘하는 것 같다. (올해도 난항이 예상된다.)</p>
<p>이 와중 GDSC라는 동아리도 개인적인 애정을 갖고 있었기 때문에 경북대 GDSC만의 홈페이지를 만들어 보자는 목표를 간직하고 있었다. 그래서 3기 수료 전까지 마지막으로 진행하게 되는 2차 프로젝트 대신 사람들을 모아 GDSC 홈페이지를 기획하게 되었고, 기획 도중 실시간 편집에 관련된 기능을 구현하기로 하였고 이 기능에 관련하여 고민과 공부한 내용을 적어볼까 한다.</p>
<hr>
<h1 id="동시편집-crdt-ot">동시편집? CRDT? OT?</h1>
<p>다들 동시편집하면 어떤걸 떠오를까? 노션? 피그마? Draw.io?
협업을 조금이라도 해보셨으면 알겠지만 다들 동시편집 도구의 시대에 살고 있다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/90ebbacb-02ec-40ee-befe-bfbad1bc37e7/image.gif" alt=""></p>
<p>동시편집의 도구를 사용하게 되면서 우리는 대면이 아닌 온라인을 통해 하나의 문서를 동시에 편집하고, 실시간으로 여러 사람들의 편집 내용이 각자 편집 화면에 바로 반영되면서 각자 의사소통을 보다 수월하고 쉽게 할 수 있다.
하지만 동시편집은 구현상 분산된 환경에서 복잡한 문제를 가지고 있고, 그런 모호함을 해결하기 위해 여러 기술들이 사용되고 있다.</p>
<h2 id="그래서-여러-기술이-뭔데">그래서 여러 기술이 뭔데?</h2>
<p>동시 편집 기술의 핵심은 동시 협업 시 일어나는 충돌을 처리하는 것이며, 충돌처리 방법 또한 여러 방식이 존재한다.</p>
<h3 id="otoperational-transformation">OT(Operational Transformation)</h3>
<p><strong>주 사용처</strong> : Google Docs, Google Wave, MS Office</p>
<p>오래전부터 사용했었던 기술이다. 
OT(Operational Transformation)이란 입력한 순서에 따라 서버가 이를 적절히 변형하여 전달하는 방식이다. OT는 시간상의 순서를 고려해 우선순위를 부여하고, 앞에서 적용한 변경사항이 다음 순위의 변경사항을 보정하는 정보로 사용된다.</p>
<h4 id="ot-동작원리">OT 동작원리</h4>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/61b42c4d-e54b-4af9-b954-0831b6e90e33/image.jpeg" alt="">
Google Docs를 사용하여 User1과 User2과 동시에 같이 편집한다고 했을때, 한명은 helo안에 l을 넣고, 다른 한명은 !를 추가한다고 가정해보자.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/53efce20-32b6-4f1e-85a9-9acbb2f63c67/image.jpeg" alt="">
이때, 변경사항은 인덱스 위치를 이용해서 l을 인덱스 3에 넣고, !를 인덱스 4에 넣는 일이 발생하게 된다.
이 2가지의 변경사항을 서버에 요청을 하게 되고, 서버가 받아서 통합한다.</p>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/67f2d07e-c80b-46ec-95ae-715d5ae33efb/image.jpeg" alt=""></p>
<p>l을 인덱스 3에 먼저 넣게 될 경우, 원래 !가 인덱스 4에 들어가야되는데, 이미 인덱스 3이 들어가서 위치가 바뀌어 버리게 된다. </p>
<p>그래서, 인덱스 위치가 바뀌어 4가 아닌 5에 넣게 된다. 이렇게 오퍼레이션이 이전 동작에 의해 바뀌게 되어 다음 동작을 변환(transform)을 시켜주는 것을 OT라고 하고 Operational Transform이라고 한다.</p>
<p>결론을 얘기하자면 
<strong>1. 모든 변경 사항 기록 **
**2. 서버에 전부 전송</strong>
<strong>3. 순차적으로 실행</strong>
<strong>4. 클라이언트에 데이터 전송 및 화면 렌더링</strong>
의 순서대로 실행하게 된다.</p>
<h4 id="ot의-문제점">OT의 문제점</h4>
<p>OT는 모든 변경사항을 서버에게 전부 전송하면 변경 사항을 순차적으로 변경해서 화면을 렌더링을 한다. 하지만 이 과정에서 여러 문제점이 있다.</p>
<ul>
<li><p>서버 과부화</p>
<ul>
<li>유저는  모든 변경사항을 서버에 전부 전달할때 다른 유저들의 변화를 알아차릴 수 있는 방법이 없다. 그래서 transformation이라는 주체의 서버에서 변환을 진행한다. 
따라서, OT는 중앙 집중식 서버/DB가 필요하다. 하지만 이런 과정에서 트래픽이 몰리게 되면 과부화가 올 수 있다. 
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/64925855-c28f-4ead-90c7-ae942bb5f29f/image.png" alt=""><blockquote>
<p>가끔씩 과부화로 문서 경고창이 뜰 수 가 있다. </p>
</blockquote>
</li>
<li>또한 중계 서버에서 operation 패킷이 전송된 뒤에 응답을 받을 수 있어서 응답 시간이 매우 길다.</li>
</ul>
</li>
</ul>
<h4 id="ot-오픈소스">OT 오픈소스</h4>
<ul>
<li>SharedDB: JSON 문서 협업을 위한 풀 스택 라이브러리</li>
<li>Rustpad: OT 기반의 소스 협업 텍스트 편집기
(<a href="https://github.com/ekzhang/rustpad">https://github.com/ekzhang/rustpad</a>)<ul>
<li>Rustpad는 완전히 자체 호스팅되며 작은 Docker 이미지에 적합하며 데이터베이스가 필요하지 않는다.</li>
</ul>
</li>
<li>Jot<ul>
<li>node.js 및 브라우저에서 사용하기 위해 JavaScript로 작성된 JSON 데이터 모델에서 OT 구현</li>
<li>다른 오픈소스처럼 문자열 작업은 JSON에서 인코딩할 수 있는 모든 종류의 데이터에 대해 작업 가능</li>
</ul>
</li>
</ul>
<h3 id="crdtconflict-free-replicated-data-types">CRDT(Conflict-Free-Replicated Data Types)</h3>
<p><strong>주사용처</strong> : Figma(UX/UI 프로그램), Redis, Riak, Workie , Apple Notes</p>
<p>동시 편집 기술 초기에는 OT가 주류였으나, 현재에는 CRDT 방식이 인기를 얻고 있다. CRDT는 중앙 서버가 필요 없고, 문서를 편집하는 유저들끼리만 데이터를 교환하면 되므로 속도가 빠르고 서버의 부하를 줄이는 효과가 있다.</p>
<p>OT는 편집 시간과 인덱스를 기반으로 변경사항을 보정하는 것과 다르게, CRDT는 스트림 상의 각 문자에 고유한 id를 부여하고, 이를 기반으로 보정을 정의한다는 차이점이 있다.</p>
<p>한마디로 OT는 순서가 중요하고, CRDT는 순서가 상관 없고 operation만 같으면 어긋나더라도 같은 상태가 된다는 점이다.</p>
<h4 id="crdt-동작원리">CRDT 동작원리</h4>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/5d94ce66-a70d-4df4-8b41-b61070b8fec9/image.png" alt=""></p>
<p>CRDT는 변경사항을 보정할 필요 없이 바로 다른 유저의 문서에 적용된다. 유저1이 &#39;l&#39; 추가하는 경우, 새로운 아이디인 3.5로 부여하고 문자를 id가 4인 문자의 위치에 추가합니다. 그리고 유저2의 환경에서 변경사항을 받아 적용할 수 있습니다. 유저2가 id가 5인 문자를 삭제하는 변경도 마찬가지로 유저1에서 변경사항을 적용할 수 있게됩니다.</p>
<p>동시 편집을 하더라도 합치는 과정에서 unique한 id 값을 새롭게 판별하기 때문에 충돌이 일어나지 않게 된다. CRDT는 이러한 기술을 서버응답을 기다릴 필요 없이 할 수 있는 것이다.
통신은 서버에서 하거나 클라에서 직접 병합하거나 같은 결과를 만들기 때문에 네트워크의 영향을 직접적으로 받지 않을 수 있다.</p>
<h4 id="crdt-문제점-1-interleaving-problem">CRDT 문제점 1 (Interleaving Problem)</h4>
<p><img src="https://velog.velcdn.com/images/k_gu_wae123/post/cda6b5d2-1590-41ac-94a5-dd53e95ca3f0/image.png" alt="">
하지만 CRDT도 완벽한 것은 아니다. 예시처럼 두 글을 합치게 됐을때 이런 충돌이 일어나게 된다.
<img src="https://velog.velcdn.com/images/k_gu_wae123/post/930bd918-77f6-4dd7-8894-12f428e15101/image.png" alt="">
앞서 얘기했던 것처럼 CRDT는 unique한 id값을 가지게 된다. 그래서 우리가 의도하지 않았던 대로 독립적으로 글자들이 랜덤화된 값에 의해 merge가 되는 것이다. 그래서 alice와 charlie의 글자들을 정렬했을 때 이상한 글자로 나오게 된다.</p>
<p>그래서 CRDT의 문제를 보완하기 위해 여러 추가 알고리즘 기법들이 존재한다.</p>
<ul>
<li>Logoot</li>
<li>LSEQ</li>
<li>RGA</li>
<li>Treedoc</li>
<li>WOOT</li>
<li>Astrong</li>
</ul>
<p>이런 다양한 알고리즘들이 있는데 이런 것이 있구나 하고 넘어가면 될것 같다.</p>
<h4 id="crdt-문제점-2">CRDT 문제점 2</h4>
<p>또한 다른 문제점들도 존재한다.</p>
<p>우선 OT보다 많은 메모리를 사용한다. 고유 id 값을 저장하는 메모리와 이를 트리구조로 관리하기 위한 메모리를 필요로 하기 때문에, 일반적인 문서보다 2~3배 더 큰 메모리를 사용하게 된다.</p>
<p>또한 Peer-to-Peer 통신이 항상 가능하지는 않다.
보안이 강화되면서 방화벽 등에 의해 Peer 간의 직접 접속이 힘든 상황이 생길 때, 이를해결하기 위해 결국 패킷 전송을 중계하는 서버를 이용할 수 밖에 없게 된다. 결국 특정 서버에 부하가 증가하게 되는데 이는 CRDT의 중앙 서버가 없다는 핵심 장점이 무색해진다는 점이다.</p>
<h4 id="crdt-오픈소스">CRDT 오픈소스</h4>
<ul>
<li><strong>Automerge</strong> : 현재 문서가 커질수록 내부의 트리 기반 데이터 구조가 커지고 느려진다.
그래서 Immutablejs를 많이 사용하여 메모리 사용량이 증가하고 성능이 저하된다고 한다. 결론적으로 얘기하자면 사용하는데는 비추인 것 같다. Automerge는 현재 성능을 개선한 Rust 버전을 별도로 구현중이라고 한다.</li>
</ul>
<ul>
<li><p><strong>Yjs</strong> : Automerge와 똑같이 트리 구조를 사용하지만 모든 항목을 단일 플랫 목록에 넣어 데이터 구조를 개선하였다. 문서 기반 협업 도구를 만든다면 성능이 좋고, 메모리 사용량이 적은 “Yjs”을 추천한다.</p>
</li>
<li><p><strong>Yorkie</strong> : AutoMerge나 Yjs와 같은 라이브러리와 달리 SDK, Server, DB를 포함하여 공동 편집 기능을 쉽게 구현할 수 있도록 한다. 또한 제일 중요한 점은 한국인 개발자가 이 라이브러리를 만들었다는 점이다!(TMI긴 하지만 개발자 분께서 키우시는 강아지가 요크셔테리어라서 Yorkie라고 지었다고 한다.)</p>
</li>
</ul>
<h2 id="결론">결론</h2>
<p>CRDT도 OT를 해결하기 위한 알고리즘으로 나왔긴 하지만 결국에는 완벽한 알고리즘은 아닌 것 같다. 하지만 이를 보완해주기 위해 여러 알고리즘들이 나오고 있고 중앙 서버가 필요 없고 유저들 끼리 변경사항을 주고 받을 수 있어서 속도가 빠른점에서 무시할 수 없는 알고리즘 인 것같다.</p>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><p>CRDT, OT 관련 링크들</p>
<ul>
<li><a href="https://velog.io/@heelieben/%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%8F%99%EC%8B%9C-%ED%8E%B8%EC%A7%91-OT-%EC%99%80-CRDT">https://velog.io/@heelieben/%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%8F%99%EC%8B%9C-%ED%8E%B8%EC%A7%91-OT-%EC%99%80-CRDT</a></li>
<li><a href="https://velog.io/@ninikim/%EB%8F%99%EC%8B%9C-%ED%98%91%EC%97%85-%EA%B8%B0%EC%88%A0-%EB%A6%AC%EC%84%9C%EC%B9%98">https://velog.io/@ninikim/%EB%8F%99%EC%8B%9C-%ED%98%91%EC%97%85-%EA%B8%B0%EC%88%A0-%EB%A6%AC%EC%84%9C%EC%B9%98</a></li>
<li><a href="https://channel.io/ko/blog/crdt_vs_ot">https://channel.io/ko/blog/crdt_vs_ot</a></li>
<li><a href="https://josephg.com/blog/crdts-are-the-future/">https://josephg.com/blog/crdts-are-the-future/</a></li>
<li><a href="https://news.hada.io/topic?id=4744">https://news.hada.io/topic?id=4744</a></li>
</ul>
</li>
<li><p>참고한 이미지 및 gif 자료들</p>
<ul>
<li>구글 문서 경고창 이미지 : <a href="https://m.blog.naver.com/toruin84/222972642990">https://m.blog.naver.com/toruin84/222972642990</a></li>
<li>노션 사용하는 방법: <a href="https://m.blog.naver.com/freewill_life/221918238701">https://m.blog.naver.com/freewill_life/221918238701</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023년 꿈을 향해 미친듯이 달렸던 프론트엔드 개발자의 회고록]]></title>
            <link>https://velog.io/@k_gu_wae123/2023%EB%85%84-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@k_gu_wae123/2023%EB%85%84-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Sun, 31 Dec 2023 06:11:32 GMT</pubDate>
            <description><![CDATA[<p>2023년이 곧 있으면 다 지나간다. 올해 참 많은 것도 했고 많은 사람들을 만나면서 깨달은 점도 많았던 해인 것 같다. 그만큼 얻어가는 것도 많았고 아쉽게 실패한것도 있었던 것 같다.</p>
<h2 id="202301--202305">2023.01 ~ 2023.05</h2>
<h3 id="coby">Coby.</h3>
<p>내가 리액트 스터디 이후 처음으로 진행했던 스터디이다. 스터디때 리액트 웹 기반 게임을 만들었었는데 이때 처음으로 프론트엔드의 매력을 느꼈었던 것 같다. 이후 프론트엔트 개발 공부를 해봐야겠다는 생각이 들었고 공부에서 적용했던 것들을 전부 적용하면서 진행했던 프로젝트였다. 이때 제대로 된 딥하게 개발을 하고 싶다는 의욕에 1학기 휴학까지 하면서 개발을 하면서 기술 그런거 관련없이 구현에만 집중하면서 개발했던 것 같다. 그때는 구현 후 완전 뿌듯하고 내가 해냈다라는 성취감이 들었지만 이제와서 돌아보면 참 무식하게 기본 배웠던 지식만 가지고 아무런 상태관리 라이브러리라던가 편한 라이브러리 없이 무식하게 구현만 했었구나 라는 생각이 들었다. 
배우면서 공부했었던 내용들은 <a href="https://velog.io/@k_gu_wae123/GDSC-1st-Project-%ED%9B%84%EA%B8%B0-2023.01.25-2023.05.26">https://velog.io/@k_gu_wae123/GDSC-1st-Project-%ED%9B%84%EA%B8%B0-2023.01.25-2023.05.26</a> 에 정리를 했으니 심심하면 한 번 구경해보시는 것도 좋겠다.</p>
<h2 id="20230624--20230907">2023.06.24 ~ 2023.09.07</h2>
<h3 id="picktre">PICKTRE</h3>
<p> 2학기 시작하기 전 1차 프로젝트를 완료 후 내 실력을 시험해보고 싶다는 생각이 들었고 GDSC라는 동아리와 멋쟁이 사자라는 동아리에서 방학동안 사람들을 모아 진행했던 프로젝트이다. 이때 새로운 사람들을 만나 개발을 진행하면서 했다. 새로운 기술들을 적용해보고 새로운 것들이 많이 개발하면서 나름 재밌었다. 1차 프로젝트 때 개발을 하면서 느꼈던 불편한 점들을 경험하면서 그 부분 위주로 보완을 하여 개발을 하였고 리드미도 참 빡세게 적으면서 여러 공모전을 나갔었다. 하지만 모든 공모전을 본선까지만 진출하고 계속 떨어져서 사실 본선따리행만 되었던 것 같다. 사실 아직까진 진짜 내가 부족하구나 라는 생각과 함께 좀 힘들었다는 생각이 들었던 것 같다. 하지만 그만큼 2학기때 또 다른 사람들과 이야기가 통하고 조금 더 성장하게 하는 원동력이 되었던 프로젝트였지 않았을까 싶다.
 배우면서 궁금했었던 내용들은 <a href="https://velog.io/@k_gu_wae123/PICKTRE-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-%EA%B8%B0%ED%9A%8D-%ED%8C%80%EB%B9%8C%EB%94%A9-%EA%B0%9C%EB%B0%9C-%EB%A6%AC%EB%93%9C%EB%AF%B8-%EC%B5%9C%EC%A2%85%EC%9E%91%EC%84%B12023.06.24-2023.09.07%EC%97%90">https://velog.io/@k_gu_wae123/PICKTRE-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-%EA%B8%B0%ED%9A%8D-%ED%8C%80%EB%B9%8C%EB%94%A9-%EA%B0%9C%EB%B0%9C-%EB%A6%AC%EB%93%9C%EB%AF%B8-%EC%B5%9C%EC%A2%85%EC%9E%91%EC%84%B12023.06.24-2023.09.07에</a> 정리를 했으니 심심하면 한 번 구경해보는 것도 좋겠다.</p>
<h2 id="20230728--20231110">2023.07.28 ~ 2023.11.10</h2>
<h3 id="shareit">ShareIT</h3>
<p>멋쟁이 사자라는 동아리내에서 방학때 해커톤을 나갔었는데 팀원들과의 합이 서로 좋지 못했다. 바빴던 사람들도 많았고 결국 프로젝트 자체를 완성해지 못했었다. 하지만 해커톤 이후 다시 작업을 하면서 서비스 관련해서 운영해보고 싶다는 생각이 들었고 프로젝트를 다시 재개하기 위해서 새로운 사람들을 추가적으로 다시 모아 진행했던 프로젝트였다. 나는 프로젝트를 한 번 맡았으면 프로젝트가 죽이 되든 밥이 되든 끝까지 해야된다고 생각하는 사람이기 때문에 결국엔 완성도가 떨어지긴했지만 그래도 나름 깔끔하게 완성시켰던 프로젝트였던것 같다. 그래도 이걸 진행하면서 나름 얻어간것도 많았다고 생각하기때문에 배웠던 내용이나 얻어간 것들은 <a href="https://velog.io/@k_gu_wae123/ShareIT-%ED%9A%8C%EA%B3%A0%EB%A1%9D-%EB%B0%8F-%EB%8A%90%EB%82%80%EC%A0%902023.07.28-2023.11.03%EC%97%90">https://velog.io/@k_gu_wae123/ShareIT-%ED%9A%8C%EA%B3%A0%EB%A1%9D-%EB%B0%8F-%EB%8A%90%EB%82%80%EC%A0%902023.07.28-2023.11.03에</a> 정리한 글이있으므로 궁금하면 방문해보시는 것도 좋을 것 같다. </p>
<h1 id="202309--202312">2023.09 ~ 2023.12</h1>
<p>2학기가 시작하면서 다시 복학을 하였고 운영체제, 데이터 통신, 종합 프로젝트, 데이터베이스(서영균교수님)에 멋쟁이 사자, GDSC 3기 운영진, 해달, 웹마스터 등 참 많은 것들을 했던 학기 였다. 
사실 진짜 몸이 남아 나질 않았고, 걍 기계같이 개발만 하고, 공부만 하고, 개발만 생각했던 학기 였던 것 같다. 여러 사람들도 많이 만나면서 인간관계에 대한 상처도 많이 받았고 실패도 하면서 그만큼 얻어가는 것도 많았던 학기였던 것 같다.</p>
<h2 id="20231106--20231126">2023.11.06 ~ 2023.11.26</h2>
<h3 id="스마일게이트-dev-camp-서류-및-면접">스마일게이트 Dev Camp 서류 및 면접</h3>
<p> 친구랑 얘기를 하면서 스마일게이트 캠프에 대한 얘기를 들으면서 한 번 도전해보고 싶다는 생각이 들었다. 사실 한 층 더 성장하고 싶다는 생각이 들었고, 조금 더 위를 향해 달려나가고 싶다는 생각이 들었다. 그래서 서류를 지원하였고 덕분에 좋은 기회를 얻게 되어 면접과정까지 가게 되었다. 하지만 학기 중이라서 면접을 준비하는 과정이 너무 시간이 부족했고 사실 면접까지 가서는 긴장이 매우 돼서 내가 원하는 만큼의 기량을 못 뽐내고 왔던 것같다.
 좀 아쉬웠다는 생각이 강하게 들더라. 조금만 더 안 자고 면접 준비를 하는데 시간을 투자할껄, 좀 더 이기적으로 내가 맡는 일 말고 면접에 조금 더 시간을 투자할껄 이라는 아쉬움도 들면서 내가 아직은 참 부족하구나 라는 생각이 들었던 것 같다.
 하지만 이대로 끝내기엔 내가 이때까지 해온 것들이 부정당한다는 생각이 들어서 조금 억울했고 이후 서류 탈락자, 면접 탈락자들을 40명만 선착순으로 모여서 특강을 해주는 스마일게이트 Mini Dev Camp를 들으러 서울까지 올라가면서 악착같이 강의해주시는 팀장님들이랑 차장님들께 공부방향과 방법과 면접 준비 방법 및 말하는 방식들을 질문하면서 연락처까지 얻어가면서 보냈던 것 같다. 이 과정속에서 현업자 분들께서 공부 방향이랑 나에게 부족했던 부분들을 피드백 받았었는데 평소에 고민을 하면서 정답을 찾아 고뇌했던 부분들의 답을 듣는 거 같아 가슴이 뻥 뚫린듯한 기분이 들었다.
 다행히 탈락자분들을 상대로 2024년 1월부터 2월까지 진행하는 스마일게이트 온라인 캠프를 연다고 해서 현업 개발자 분들께서 코드 리뷰 및 멘토링을 받고 싶었던 나는 신청을 하였다. 아마 내년에 글을 쓰게 된다면 이때의 느꼈던 회고록을 작성하지 않을까 싶다.</p>
<h3 id="대구를-빛내는-sw-해커톤-1박2일">대구를 빛내는 SW 해커톤 1박2일</h3>
<p>매년 컴퓨터학부에서 진행하는 대회가 있다. 바로 대구를 빛내는 SW 해커톤이다. 이때 1박2일로 개발을 진행했었는데 처음으로 서로 친한 친구들끼리 같이 개발했었다. 같이 개발을 하면서 진짜 재밌던 대회였던 것 같다. 사실 서류 쓰는것과 아이디어가 다 였던 것 같아서 개발에 관련해서는 그렇게 빡세진 않았던 것 같다. 나름 재밌으면서도 덕분에 상도 받고 정말 재밌었던 대회였던 것 같다. </p>
<h2 id="서영균-데이터베이스-팀플">서영균 데이터베이스 팀플</h2>
<h3 id="matcher">Matcher</h3>
<blockquote>
<p><a href="https://github.com/KNU2023/Matcher_FE">https://github.com/KNU2023/Matcher_FE</a></p>
</blockquote>
<p>사실 수업 자체가 진짜 욕나올정도로 힘들었다. 그러면서 물론 얻어가는 것도 있었지만 진짜 힘들었다. 마음 고생도 심했고 신경도 많이 썼다.
나는 프론트엔드 관련 지식에 대해 서로 얘기하는 걸 좋아한다. 그러면서 친구랑 우연히 아토믹 디자인에 대해서 얘기를 나눴었고 아토믹 디자인을 적용한 프로젝트를 해봐야겠다는 생각이 들어 프로젝트를 진행했었다. 이론에 맞춰서 개발을 진행하였고 마지막까지 완성을 하면서 느낀점은
    1. 이론은 이론일 뿐이라는 것.
    2. 이론대로 완벽하게 수행하기에는 무리가 있다는 점.
    3. 결국 이론은 이론이되 이를 활용하여 각자 팀과 프로젝트 성향에 맞게 활용하여 작업을 하면 된다는 점. 
  자세하게 느꼈던 점은 추후에 글을 쓰면서 다뤄볼 예정이다. 
  아토믹 패턴을 공부하면서 참고하면 코드적으로 부족한 면도 있긴 하지만 코드적으로 나름 참고할만한 것 같아서 궁금하시면 깃헙 둘러보셔도 좋을것 같다.</p>
<h2 id="20231126--20231225">2023.11.26 ~ 2023.12.25</h2>
<h3 id="종합-프로젝트">종합 프로젝트</h3>
<p> 우리 팀들이 전부 하나같이 바빴다. 취준생, 막학기 졸업생으로 준비된 팀원들이였고 다들 심컴이 아닌 타과들이나 글솝 편입생들로 모여진 조합이다 보니 서로 서로 협업이 그렇게 이루어지진 않은 것 같다. 마무리는 어찌저찌 했다. 시험이 끝나고 나서 내가 며칠간 고생해서 Sequence 모델을 사용하여 이미지 분류 CNN모델까지 만들어서 나름 마무리는 했지만 완성에만 급급했던 나머지 개인적으로는 참 실패한 프로젝트였던 것같다. 그러면서 개발 외적으로도 많은 것들을 배웠던 것 같다.</p>
<h3 id="해피뉴히어happy-new-here">해피뉴히어(Happy New Here)</h3>
<p>올해 마지막으로 참 뜻깊은 프로젝트로 1년을 마무리했으면 좋겠다라는 생각이 들었고, 해달에서 마음이 맞는 사람들을 찾아서 프로젝트를 진행하였다. 잘하는 사람보다는 의욕이 많은 사람들, 1학기를 휴학하면서 열정이 가득했던 나같은 그런 사람들 위주로 팀을 꾸렸으면 했었다. 다행히 팀을 꾸려 프로젝트를 진행하면서 개발 총괄을 진행하면서 프론트 팀과 백엔드 팀끼리 각자 진행하는 회의를 전부 다 참여하고, Git WorkFlow에 맞춰서 개발을 할 수 있도록 엄격하게 기준을 정하면서 개발을 진행하였다. 이 과정 속에서 시험 끝나고 남들 놀동안 개발을 하면서 느꼈던 스트레스 및 사람간의 스트레스 및 막판 번아웃까지 겹치면서 매우 힘들었지만 프로젝트를 마무리 하였다. 
프로젝트 완료 후 서비스 배포를 하였고 인스타나 카톡 등 여러 매체에 홍보를 하기 시작했고 다행히 현재 12월 31일 기준으로 서비스 가입자가 현재 100명이 넘었다. 뭔가를 기대하지 않고 그냥 순전히 내 욕심에 의해서 사람들이 재밌게 사용할 수 있는 서비스와 깔끔한 서비스를 만들자라는 취지에서 시작한 서비스 였지만 100명을 넘었다는 것에 상징적인 의미가 있다.</p>
<h1 id="마무리">마무리</h1>
<p>올해의 나 자신을 점수로 매긴다면 70점이다. 
남들은 내가 진짜 바쁘게 산다, 진짜 미쳤다, 대단하다 이런 말을 했었지만 항상 내 마음속에는 내 자신이 부족하다는 생각이 엄청 들었기 때문이다. 결국 그 부족하다는 생각은 스마일게이트 면접 이후로 더 확신이 들었다. 
남들은 또 내가 아직 엄청 이르다고 생각한다. 
하지만 난 아슬아슬하게 남들보다 뒤쳐지지 않기 위해 뒤에서 열심히 따라가는 중이라고 생각한다. 남들을 보면 남들보다 더 잘하고 싶고 내가 목표로 하는 남들과의 비슷한 경지에 올랐을 때 더 대단한 남들을 보며 또 그 목표를 향해 올라가는 이기적이고 욕심적인 놈인 그런 놈이기 때문이다.
이런 욕심 때문에 내가 지치지 않고 성장하는 원동력인것 같다. 내년에도 이러한 원동력을 가지고 앞으로 나아갔으면 좋겠다. 더 많은 사람들을 만나고 백과사전같은 프론트엔드 개발자가 되고싶다. 2023년 12월 31일 마지막날을 기리며 이런 열망이 사그라들지 않고 뻗어져 나아갔으면 한다.</p>
<h2 id="ps-내년에-쓸-아직-써야할-글현재-생각나는-것만">PS. 내년에 쓸 아직 써야할 글(현재 생각나는 것만)</h2>
<ol>
<li>Styled-Component vs EmotionJS vs css-module 어떤 것을 쓰는 게 좋을까 고뇌</li>
<li>JS 기본 시리즈</li>
<li>CI/CD  Jenkins, Nginx</li>
<li>Happy New Here 회고록</li>
<li>스마게 온라인 캠프 수료 후 회고록</li>
<li>타입스크립트를 써야만 하는 이유</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>