<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>개발새발🐾</title>
        <link>https://velog.io/</link>
        <description>미술 전공에서 프론트엔드 개발까지</description>
        <lastBuildDate>Wed, 09 Apr 2025 10:49:15 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>개발새발🐾</title>
            <url>https://velog.velcdn.com/images/miiing_gaeng/profile/b20f04c3-d1ef-49f7-8468-4eac55b62753/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 개발새발🐾. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/miiing_gaeng" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[그 시절 우리가 사랑한 <div>는 이제 그만 : 시맨틱 태그와 SEO💔]]></title>
            <link>https://velog.io/@miiing_gaeng/%EA%B7%B8-%EC%8B%9C%EC%A0%88-%EC%9A%B0%EB%A6%AC%EA%B0%80-%EC%82%AC%EB%9E%91%ED%95%9C-div%EB%8A%94-%EC%9D%B4%EC%A0%9C-%EA%B7%B8%EB%A7%8C-%EC%8B%9C%EB%A7%A8%ED%8B%B1-%ED%83%9C%EA%B7%B8%EC%99%80-SEO</link>
            <guid>https://velog.io/@miiing_gaeng/%EA%B7%B8-%EC%8B%9C%EC%A0%88-%EC%9A%B0%EB%A6%AC%EA%B0%80-%EC%82%AC%EB%9E%91%ED%95%9C-div%EB%8A%94-%EC%9D%B4%EC%A0%9C-%EA%B7%B8%EB%A7%8C-%EC%8B%9C%EB%A7%A8%ED%8B%B1-%ED%83%9C%EA%B7%B8%EC%99%80-SEO</guid>
            <pubDate>Wed, 09 Apr 2025 10:49:15 GMT</pubDate>
            <description><![CDATA[<h2 id="1-시맨틱-태그🏛️">1. 시맨틱 태그🏛️</h2>
<h3 id="01-시맨틱-태그semantic-tag란">01. 시맨틱 태그(Semantic Tag)란?</h3>
<p>사람이 이해하기 쉽도록 태그의 이름만 보고도 역할이나 위치를 알 수 있도록(semantic) 만든 태그를 뜻한다. 즉, 포함된 콘텐츠의 특정 의미를 정의하고 목적을 갖는 태그를 의미한다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/143ca07e-32a8-4aaf-99c9-8612fefcac0a/image.jpg" alt=""></p>
<h3 id="02-자주-사용되는-시맨틱-태그">02. 자주 사용되는 시맨틱 태그</h3>
<table>
<thead>
<tr>
<th>태그</th>
<th>의미</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>&lt;header&gt;</code></td>
<td>헤더(머리글)</td>
<td>페이지나 섹션의 머리말(로고, 내비게이션 포함 가능)</td>
</tr>
<tr>
<td><code>&lt;nav&gt;</code></td>
<td>네비게이션</td>
<td>주요 링크(메뉴, 사이드바)를 포함</td>
</tr>
<tr>
<td><code>&lt;main&gt;</code></td>
<td>메인 콘텐츠</td>
<td>문서의 핵심 콘텐츠를 감싸는 태그</td>
</tr>
<tr>
<td><code>&lt;article&gt;</code></td>
<td>독립적인 콘텐츠</td>
<td>블로그 글, 뉴스 기사처럼 독립적으로 의미를 가지는 콘텐츠</td>
</tr>
<tr>
<td><code>&lt;section&gt;</code></td>
<td>콘텐츠 구역</td>
<td>문서 내 논리적인 그룹</td>
</tr>
<tr>
<td><code>&lt;aside&gt;</code></td>
<td>사이드바</td>
<td>부가적인 내용(광고, 추천 글, 관련 링크 등)</td>
</tr>
<tr>
<td><code>&lt;footer&gt;</code></td>
<td>푸터(바닥글)</td>
<td>저작권, 연락처, 사이트 맵 등이 들어가는 하단 부분</td>
</tr>
<tr>
<td><code>&lt;ul&gt;</code>, <code>&lt;ol&gt;</code>, <code>&lt;li&gt;</code></td>
<td>목록</td>
<td>내비게이션 메뉴나 목록을 만들 때</td>
</tr>
<tr>
<td>그 외에 <code>&lt;address&gt;</code>, <code>&lt;figure&gt;</code>, <code>&lt;details&gt;</code> 등 다양한 시맨틱 태그가 있다.</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h2 id="2-시맨틱-태그와-seo🤖">2. 시맨틱 태그와 SEO🤖</h2>
<h3 id="01-시맨틱-태그를-사용해야하는-이유">01. 시맨틱 태그를 사용해야하는 이유</h3>
<ul>
<li><strong>가독성 향상</strong> : 코드만 봐도 구조를 쉽게 이해할 수 있어 협업과 유지보수에 용이하다.</li>
<li><strong>SEO(Search Engine Optimization) 유리</strong> : 검색 엔진이 시맨틱 구조를 기준으로 웹페이지를 분석하기 때문에, 컨텐츠의 핵심을 파악하는데 유리하다.</li>
<li><strong>웹 접근성 향상</strong> : 스크린 리더가 시맨틱 태그를 기반으로 페이지를 해석한다. 이 때문에, 시각 장애인, 키보드 유저들의 컨텐츠 탐색이 더 쉽게 이뤄질 수 있다.</li>
<li><strong>유지보스 편리</strong> : 시맨틱 태그를 활용하여 페이지가 명확한 구조로 이루어져 있다면, 코드리뷰나 리팩토링 시 매우 유리하다.</li>
</ul>
<h2 id="3-고민사항--article-vs-section🤔">3. 고민사항 : <code>&lt;article&gt;</code> vs <code>&lt;section&gt;</code>🤔</h2>
<h3 id="00-article과-section-의-올바른-관계에-대한-고찰">00. <code>&lt;article&gt;</code>과 <code>&lt;section&gt;</code> 의 올바른 관계에 대한 고찰</h3>
<p>시맨틱 태그에 대해 알아보면서 궁금증이 생겼다. 과연 <code>&lt;article&gt;</code>과 <code>&lt;section&gt;</code> 중 어느 태그가 더 상위 태그일까? 시맨틱 태그에는 정답이 아닌 의도와 맥락에 따라 선택이 중요하다. 그래서 리서치를 진행했 때, 이 부분에 대해 개발자마다 다양한 의견을 가지고 있어 따로 정리해보았다.</p>
<h3 id="01-article보다-section이-더-크다">01. <code>&lt;article&gt;</code>보다 <code>&lt;section&gt;</code>이 더 크다!</h3>
<ul>
<li>하나의 독립된 글이나 콘텐츠를 세부적으로 나눌 때, 이 방법을 사용한다.</li>
<li><code>&lt;article&gt;</code> : 독립적인 컨텐츠</li>
<li><code>&lt;section&gt;</code> : 관련된 소주제</li>
<li>예시 : 블로그 글(<code>&lt;article&gt;</code>) 안에 <em>&quot;소개&quot;, &quot;본문&quot;, &quot;결론&quot;</em> 같은 소주제(<code>&lt;section&gt;</code>)<pre><code>&lt;article&gt;
&lt;h2&gt;글 제목&lt;/h2&gt;
&lt;section&gt;
  &lt;h3&gt;소개&lt;/h3&gt;
&lt;/section&gt;
&lt;section&gt;
  &lt;h3&gt;본문&lt;/h3&gt;
&lt;/section&gt;
&lt;/article&gt;</code></pre></li>
</ul>
<h3 id="02-section보다-article이-더-크다">02. <code>&lt;section&gt;</code>보다 <code>&lt;article&gt;</code>이 더 크다!</h3>
<ul>
<li>같은 주제를 가진 여러 개의 독립된 콘텐츠를 모을 때, 이 방법을 사용한다.</li>
<li><code>&lt;section&gt;</code> : 하나의 주제를 묶는 컨테이너</li>
<li><code>&lt;article&gt;</code> : 그 안에 독립적인 여러 개의 컨텐츠</li>
<li>예시 : &quot;최신 뉴스&quot;(<code>section</code>) 안에 각각의 뉴스 기사(<code>article</code>)<pre><code>&lt;section&gt;
&lt;h2&gt;최신 블로그&lt;/h2&gt;
&lt;article&gt;...&lt;/article&gt;
&lt;article&gt;...&lt;/article&gt;
&lt;/section&gt;</code></pre></li>
</ul>
<h3 id="03-그럼-현업에서는">03. 그럼 현업에서는?</h3>
<p>프로젝트의 목적과 팀의 코드 컨벤션에 따라 달라질 수 있다. 즉, 정답은 없고, 맥락에 맞는 구조가 가장 중요하다. 팀에서 컨벤션이 있다면 따르고, 없다면 의미 중심으로 구조를 고민해보는 게 좋다. <code>&lt;article&gt;</code>은 독립성을 중심으로 묶고, <code>&lt;section&gt;</code>은 주제를 중심으로 묶는다는 차이점만 명확하게 구분하자.</p>
<ul>
<li><strong>콘텐츠 중심 프로젝트 (블로그, 미디어 등)</strong> : <code>&lt;article&gt;</code>을 중심으로 내부에 <code>&lt;section&gt;</code>을 나누는 경우가 많다.</li>
<li><strong>서비스형 페이지나 컴포넌트 중심의 구조</strong> : 관련 콘텐츠를 <code>&lt;section&gt;</code>으로 묶고 각각 <code>&lt;article&gt;</code>을 사용하는 방식도 많이 쓴다.</li>
</ul>
<blockquote>
<p><strong>참고 자료</strong>
<a href="https://inpa.tistory.com/entry/HTML-%F0%9F%93%9A-%EC%8B%9C%EB%A7%A8%ED%8B%B1-%ED%83%9C%EA%B7%B8-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83%EC%9D%84-%EC%A7%80%EC%A0%95#section_%EA%B3%BC_article_%EC%9D%98_%EC%B0%A8%EC%9D%B4">시맨틱 태그 참고 사이트 1</a> 
<a href="https://seo.tbwakorea.com/blog/what-is-semantic-tag/">시맨틱 태그 참고 사이트 2</a>
<a href="https://berrywiki.com/webdev/%EC%8B%9C%EB%A7%A8%ED%8B%B1-%ED%83%9C%EA%B7%B8semantic-tag%EC%9D%98-%EC%A2%85%EB%A5%98-%EB%B0%8F-%EC%97%AD%ED%95%A0/">이미지 출처</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[LOL 챔피언 사이트) KPT 회고✍️]]></title>
            <link>https://velog.io/@miiing_gaeng/LOL-%EC%B1%94%ED%94%BC%EC%96%B8-%EC%82%AC%EC%9D%B4%ED%8A%B8-KPT-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@miiing_gaeng/LOL-%EC%B1%94%ED%94%BC%EC%96%B8-%EC%82%AC%EC%9D%B4%ED%8A%B8-KPT-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 01 Apr 2025 12:45:00 GMT</pubDate>
            <description><![CDATA[<h2 id="1-project---lol-챔피언-사이트🔱">1. Project - LOL 챔피언 사이트🔱</h2>
<h3 id="01-완성-사이트-엿보기">01. 완성 사이트 엿보기</h3>
<p><a href="https://lol-champions-site.vercel.app/">LOL 챔피언 사이트 바로가기</a></p>
<ul>
<li>Home</li>
</ul>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/e3482ccf-4f82-44f3-9bff-7f4516f824d1/image.png" alt=""></p>
<ul>
<li>Item</li>
</ul>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/c2b92130-5d25-4e1f-9ced-272f87eb9044/image.png" alt=""></p>
<ul>
<li>Champion</li>
</ul>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/be7e1ba0-3d14-4d4b-8ccc-e9ba9b19c037/image.png" alt=""></p>
<ul>
<li>Champion Detail</li>
</ul>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/5504b6df-2830-47a2-a86f-a32bff36701c/image.png" alt=""></p>
<ul>
<li>Rotation</li>
</ul>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/c86d5071-9dde-4e42-affb-abf4da48a373/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/a872baf1-c38b-43b9-98ec-f8afff80b50e/image.png" alt=""></p>
<ul>
<li>반응형</li>
</ul>
<table>
    <tr>
      <td align="center">
        <img width="215" alt="Screenshot 2025-03-18 at 8 00 32 PM" src="https://github.com/user-attachments/assets/63e23e39-8082-470e-97e9-ab4014c06a14" />
<br>
        <strong>반응형 Home</strong>
      </td>
      <td align="center">
        <img width="215" alt="Screenshot 2025-03-18 at 8 00 42 PM" src="https://github.com/user-attachments/assets/180c8358-e140-48d9-81ce-cfa04955af3a" />
<br>
        <strong>반응형 Champion</strong>
      </td>
    </tr>
    <tr>
      <td align="center">
        <img width="215" alt="Screenshot 2025-03-18 at 8 00 47 PM" src="https://github.com/user-attachments/assets/cb11d524-d6f1-420d-aa8d-c6207feedcb3" />
<br>
        <strong>반응형 Item</strong>
      </td>
      <td align="center">
        <img width="215" alt="Screenshot 2025-03-18 at 8 00 57 PM" src="https://github.com/user-attachments/assets/c908890f-fc8a-430e-8542-1cbbf3f52d16" />
<br>
        <strong>반응형 Rotation</strong>
      </td>
    </tr>
  </table>


<h2 id="2-project-kpt-review🗒️">2. Project KPT Review🗒️</h2>
<h3 id="01-개인-회고">01. 개인 회고</h3>
<ul>
<li>KEEP : 만족하는 부분
```</li>
<li>Next.js의 서버 사이드 / 클라이언트 사이드 개념을 학습했다.</li>
<li>TypeScript를 사용해봄으로써 타입에 대한 개념과 필요성을 학습했다.</li>
<li>README를 열심히 작성했다.
```</li>
<li>PROBLEM : 문제가 발생한 부분
```
[기능 부분]</li>
<li>서버/클라이언트에 대한 개념을 이해하지 못한 채, 프로젝트를 진행하였다.</li>
<li>Next/Image에 대해 리서치를 하지 않고 사용하여 오류를 해결하는데 많은 시간을 소모했다.</li>
</ul>
<p>[기타]</p>
<ul>
<li>도전 기능을 모두 구현하지 못했다.
```</li>
<li>TRY : 개선방안
```
[기능 부분]</li>
<li>서버/클라이언트에 대해 학습하고, 서버 컴포넌트에 대한 장단점을 다시 복습한다.</li>
<li>공식 문서를 꼼꼼히 확인하자.</li>
</ul>
<p>[기타]</p>
<ul>
<li>추후에 도전해본다.<pre><code></code></pre></li>
</ul>
<h3 id="02-튜터님의-피드백">02. 튜터님의 피드백</h3>
<ul>
<li>메타데이터 활용이 아쉽다. 각 페이지별 메타데이터를 적극 활용해서 SEO 최적화, 접근성 개선을 해보자.</li>
<li>챔피언 목록이나 아이템 목록 같은 경우에 ul 태그를 사용해보자.</li>
<li>Route Handlers는 RESTful API를 만들 수 있는 장점이 있다. 그런 점에서 rotation-alluser라는 이름은 조금 적합하지 않다.</li>
<li>Route Handlers에서 두 번째 인자(<code>{ status }</code>)를 활용해보자. 상태코드도 함께 정의를 해주면, 더 다양한 에러 핸들링이 가능하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[여기 좀 보세요! : README 작성법👀]]></title>
            <link>https://velog.io/@miiing_gaeng/%EC%97%AC%EA%B8%B0-%EC%A2%80-%EB%B3%B4%EC%84%B8%EC%9A%94-README-%EC%9E%91%EC%84%B1%EB%B2%95</link>
            <guid>https://velog.io/@miiing_gaeng/%EC%97%AC%EA%B8%B0-%EC%A2%80-%EB%B3%B4%EC%84%B8%EC%9A%94-README-%EC%9E%91%EC%84%B1%EB%B2%95</guid>
            <pubDate>Tue, 18 Mar 2025 13:11:47 GMT</pubDate>
            <description><![CDATA[<h2 id="1-readme📝">1. README📝</h2>
<h3 id="01-readme-란">01. README 란?</h3>
<p><code>README</code>는 Github 프로필 혹은 Repository에 대한 설명을 나타내기 위해 작성하는 문서로, 가이드라인, 안내문 정도로 생각할 수 있다.
즉, GitHub에서 <code>README</code>는 프로젝트의 첫인상이다. 프로젝트를 처음 접하는 사람들에게 어떤 프로젝트인지, 어떻게 사용하는지, 기여할 방법이 있는지를 전달하는 중요한 문서이다.</p>
<h3 id="02-readme를-작성해야-하는-이유">02. README를 작성해야 하는 이유</h3>
<ul>
<li><strong>프로젝트 개요 제공</strong> : 프로젝트가 무엇인지, 어떤 문제를 해결하려는 것인지 간략하게 설명하여, 새로운 사람이나 팀원이 프로젝트에 대해 빠르게 이해할 수 있게 도와준다.</li>
<li><strong>설치 및 사용법 안내</strong> : 프로젝트를 실행하려면 어떻게 해야 하는지, 필요한 환경 설정, 종속성 설치 방법 등을 명시한다.</li>
<li><strong>기여 방법 안내</strong> : 오픈 소스 프로젝트나 협업 프로젝트일 경우, 다른 사람들이 어떻게 기여할 수 있는지 방법을 안내한다.</li>
<li><strong>문제 해결 및 FAQ</strong> : 자주 발생하는 문제나 오류에 대한 해결책을 제시하여 사용자가 문제를 빠르게 해결할 수 있도록 도와준다.</li>
<li><strong>라이센스 정보 제공</strong> : 프로젝트에 적용된 라이센스나 사용 조건을 명확히 하여, 다른 사람들이 프로젝트를 사용하거나 배포할 때 참고할 수 있도록 한다.</li>
<li><strong>프로젝트 유지 관리</strong> : 프로젝트의 최근 업데이트나 버전 정보, 주요 변경 사항 등을 기록하여 사용자가 프로젝트의 상태나 진척 상황을 쉽게 파악할 수 있다.</li>
</ul>
<h2 id="2-readme-작성하기--markdown🔠">2. README 작성하기 : MarkDown🔠</h2>
<p><code>README</code> 파일을 작성하려면 <strong>마크다운 문법</strong>은 필수로 알아야한다. 마크다운 문법은 현재 보고 있는 <em>벨로그</em> 나 <em>노션</em> 등 다양한 곳에서 사용하는 문법으로, 직관적이고 쉬워서 금방 숙지할 수 있다.</p>
<h3 id="01-제목">01. 제목</h3>
<p><code>#</code>로 제목의 크기를 조절할 수 있으며, 개수가 많아질수록 작아진다. 총 6단계까지 조절 가능하다.</p>
<pre><code># 제목 1
## 제목 2
### 제목 3
#### 제목 4
##### 제목 5
###### 제목 6</code></pre><p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/2b10fc12-1d28-444d-93fc-70217cad4ade/image.png" alt=""></p>
<h3 id="02-강조--이탤릭체-볼드체-취소선-밑줄">02. 강조 : 이탤릭체, 볼드체, 취소선, 밑줄</h3>
<ul>
<li><strong>이텔릭체</strong> : <code>* 별 기호</code>나 <code>_ 언더바</code>를 하나 붙여서 사용한다.</li>
<li><strong>볼드체</strong> : <code>** 별 기호</code>나 <code>__ 언더바</code>를 두개씩 붙여서 사용한다.</li>
<li><code>__*이텔릭체*와 볼드체__</code>를 혼용할 수 있다.</li>
<li><strong>취소선</strong> : <code>~~ 물결 기호</code>를 두개씩 붙여서 사용한다.</li>
<li><strong>밑줄</strong> : 마크다운에서 지원하지 않기 때문에, <code>&lt;u&gt;&lt;/u&gt;</code> 태그를 사용한다.
```</li>
<li>이텔릭체 1* &amp; <em>이텔릭체 2</em></li>
<li><em>볼드체 1*</em> &amp; <strong>볼드체 2</strong>
<del>취소선</del>
<u>밑줄</u><pre><code>![](https://velog.velcdn.com/images/miiing_gaeng/post/ba22af27-bbce-4045-9122-cbe3286b3b96/image.png)
</code></pre></li>
</ul>
<h3 id="03-목록--인덱싱불릿">03. 목록 : 인덱싱/불릿</h3>
<ul>
<li><strong>인덱싱</strong> : <code>1.</code> 로 시작하면 순서가 있는 목록으로, 순서대로 인덱싱이 된다.</li>
<li><strong>불릿</strong> : <code>+, -, *</code> 로 시작하면 순서가 없는 목록으로, 불릿으로 인덱싱이 된다. 탭을 누른 후 작성하면 빈 불릿으로 들여쓰기된다.
```</li>
</ul>
<ol>
<li>순서가 있는 항목 1</li>
<li>순서가 있는 항목 2<ol>
<li>순서가 있는 소항목 1</li>
<li>순서가 있는 소항목 2</li>
</ol>
</li>
<li>순서가 있는 항목 3</li>
</ol>
<ul>
<li>순서가 없는 항목 1</li>
<li>순서가 없는 항목 2<ul>
<li>순서가 없는 소항목 1</li>
<li>순서가 없는 소항목 2<pre><code>![](https://velog.velcdn.com/images/miiing_gaeng/post/cf5b4bbb-d56e-4708-bbcb-cbf13d23cde4/image.png)

</code></pre></li>
</ul>
</li>
</ul>
<h3 id="04-인용문">04. 인용문</h3>
<p>참고자료나 다른 사람의 프로젝트 등에서 발췌한 내용을 작성하며, <code>&gt;</code>를 사용하여 작성하며, 중첩해서 사용할 수 있다.</p>
<pre><code>&gt; 인용문
&gt;&gt; 중첩 인용문
&gt;&gt;&gt; 중중첩 인용문 </code></pre><p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/5281b1e1-d697-4222-9967-3ec93abb2eb4/image.png" alt=""></p>
<h3 id="05-하이퍼링크">05. 하이퍼링크</h3>
<ul>
<li><strong>일반 하이퍼링크</strong>: <code>&lt;&gt;</code> 안에 주소를 작성한다.</li>
<li><strong>이름이 지정된 하이퍼링크</strong>: <code>[]</code> 안에 링크 이름을 작성하고, <code>()</code> 안에 링크 주소를 작성한다.</li>
<li><strong>부연 설명이 적용된 하이퍼링크</strong>: <code>()</code> 안에 링크 주소와 부연설명을 콤마로 구분하여 넣는다.</li>
</ul>
<p>*<em>부연 설명이란?</em> : 하이퍼링크에 커서를 가져다 대었을 때 보이는 툴팁</p>
<pre><code>&lt;링크&gt;
[하이퍼링크 1](링크) &lt;br&gt;
[하이퍼링크 2](링크 &quot;설명&quot;) &lt;br&gt;</code></pre><p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/0682d2a5-a89e-4099-bbdb-9128e51cde37/image.png" alt=""></p>
<h3 id="06-이미지">06. 이미지</h3>
<p>이미지를 업로드하기 위해선 이미지의 링크 주소가 필요하다. 이미지 주소를 생성하기 위해서 README 파일을 작성하려는 Repository에서 <code>Upload files</code>를 클릭해 이미지를 업로드한 후, 링크 주소 복사하여 사용한다.</p>
<pre><code>![이미지 1](이미지주소)
![이미지 2](이미지주소 &quot;설명&quot;)</code></pre><p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/76568844-182f-4c12-ae4e-1402e3043288/image.png" alt=""></p>
<h2 id="3-readme-구성요소✍️">3. README 구성요소✍️</h2>
<p>README 구성요소는 개발자마다 다르지만, 보편적으로 들어가는 내용에 대해 간략하게 정리해보겠다.</p>
<h3 id="01-프로젝트-소개">01. 프로젝트 소개</h3>
<ul>
<li>한 줄 요약 및 프로젝트 URL</li>
<li>프로젝트의 목적 및 개요</li>
<li>해결하고자 하는 문제</li>
<li>주요 특징 및 장점</li>
<li>팀프로젝트 멤버 소개</li>
</ul>
<h3 id="02-프로젝트-주요-기능">02. 프로젝트 주요 기능</h3>
<ul>
<li>핵심 기능 설명</li>
<li>스크린샷이나 GIF 추가하여 직관적으로 설명</li>
</ul>
<h3 id="03-프로젝트-기술-스택">03. 프로젝트 기술 스택</h3>
<ul>
<li>프론트엔드, 백엔드, 데이터베이스, 배포 환경 등 사용된 기술 나열</li>
<li>주요 라이브러리 및 프레임워크 설명</li>
</ul>
<h3 id="04-프로젝트-설치-방법">04. 프로젝트 설치 방법</h3>
<ul>
<li>프로젝트를 로컬에서 실행할 수 있도록 단계별로 설명</li>
<li>의존성 설치 및 실행 방법 안내</li>
</ul>
<h3 id="05-기타">05. 기타</h3>
<ul>
<li>프로젝트 구조 및 ERD</li>
<li>프로젝트 과정 중 발생한 트러블 슈팅 정리글</li>
<li>프로젝트 후기</li>
</ul>
<h2 id="4-readme-꾸미기🎨">4. README 꾸미기🎨</h2>
<h3 id="01-뱃지">01. 뱃지</h3>
<p><a href="https://github.com/Envoy-VC/awesome-badges">Shields.io 사이트</a>에서 프로젝트에서 사용한 기술, SNS 등 공유할 때 유용한 뱃지들의 링크를 복사할 수 있다.</p>
<pre><code>&lt;img src=&quot;Shields.io에서 복사한 뱃지 주소&quot;&gt;
![](&quot;Shields.io에서 복사한 뱃지 주소&quot;)

//예시
![](https://img.shields.io/badge/Next.js-000?logo=nextdotjs&amp;logoColor=fff&amp;style=for-the-badge)
&lt;img src=&quot;https://img.shields.io/badge/Next.js-000?logo=nextdotjs&amp;logoColor=fff&amp;style=for-the-badge&quot;&gt; </code></pre><p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/a146fa78-c55c-4854-a782-a28b7001f8ce/image.png" alt=""></p>
<h2 id="5-내가-보려고-만든-템플릿🥠">5. 내가 보려고 만든 템플릿🥠</h2>
<h3 id="01-개인-프로젝트-readme-템플릿">01. 개인 프로젝트 README 템플릿</h3>
<pre><code># 프로젝트 이름

프로젝트 한 줄 소개
[프로젝트 URL 💻](https://프로젝트 URL.com)
&lt;br&gt;&lt;br&gt;

## 프로젝트 미리보기🧚

- 페이지 이름
&lt;img src=&quot;페이지 이미지 주소&quot;&gt;
&lt;br&gt;&lt;br&gt;

## 프로젝트 소개📄

**📆 프로젝트 기간 : 2025.00.00 ~ 2025.00.00**

프로젝트 목적 및 개요, 주요 특징 및 장점
&lt;br&gt;&lt;br&gt;

## 프로젝트 기술 스택💻

#### 📌 프로그래밍 언어 및 프레임워크
&lt;img src=&quot;뱃지&quot;&gt;

#### 🎨 UI 스타일링
&lt;img src=&quot;뱃지&quot;&gt;

#### ✅ 코드 품질 및 포맷팅
&lt;img src=&quot;뱃지&quot;&gt;

#### 🗃️ 상태 관리
&lt;img src=&quot;뱃지&quot;&gt;

#### 🌐 배포
&lt;img src=&quot;뱃지&quot;&gt;
&lt;br&gt;&lt;br&gt;

## 프로젝트 주요 기능⚙️

- 프로젝트 주요 기능 정리
    - 프로젝트 기능 상세 설명
&lt;br&gt;&lt;br&gt;

## 트러블 슈팅🧑‍💻

[TroubleShooting 제목](TroubleShooting 주소)

[요약]&lt;br&gt;
문제 ▶️ TroubleShooting 요약
&lt;br&gt;해결 ▶️ TroubleShooting 요약
&lt;br&gt;교훈 ▶️ TroubleShooting 요약
&lt;br&gt;&lt;br&gt;

## 프로젝트 후기✍️

프로젝트 후기 작성
</code></pre><blockquote>
<p><a href="https://www.heropy.dev/p/B74sNE">마크다운 문법 참고 사이트</a>
<a href="https://velog.io/@gmlstjq123/Readme.md-%ED%8C%8C%EC%9D%BC-%EC%9E%91%EC%84%B1%EB%B2%95#2-markdown-%EB%AC%B8%EB%B2%95">README 작성 참고 사이트 1</a>
<a href="https://velog.io/@noyohanx/GIT-%ED%94%84%EB%A1%9C%ED%95%84-%ED%99%94%EB%A9%B4-%EA%BE%B8%EB%AF%B8%EA%B8%B0#%EB%B1%83%EC%A7%80">README 작성 참고 사이트 2</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[UI 그리기 참 쉽죠? : Next.js의 렌더링👨‍🎨]]></title>
            <link>https://velog.io/@miiing_gaeng/UI-%EA%B7%B8%EB%A6%AC%EA%B8%B0-%EC%B0%B8-%EC%89%BD%EC%A3%A0-Next.js%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81</link>
            <guid>https://velog.io/@miiing_gaeng/UI-%EA%B7%B8%EB%A6%AC%EA%B8%B0-%EC%B0%B8-%EC%89%BD%EC%A3%A0-Next.js%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81</guid>
            <pubDate>Fri, 14 Mar 2025 12:50:14 GMT</pubDate>
            <description><![CDATA[<h2 id="1-렌더링-방법의-변화-mpa와-spa📜">1. 렌더링 방법의 변화: MPA와 SPA📜</h2>
<h3 id="01-mpa-multi-page-application">01. MPA: Multi-Page Application</h3>
<p>MPA(Multi-Page Application)는 전통적인 웹 애플리케이션 방식으로, 사용자가 새로운 페이지로 이동할 때마다 서버에서 HTML 파일을 받아오는 방식이다.</p>
<ul>
<li><p><strong>MPA의 특징</strong></p>
<ul>
<li>각 요청마다 새로운 HTML을 서버에서 렌더링하여 클라이언트에 전달한다.</li>
<li>SEO에 유리하며, 초기 로딩 속도가 빠르다.</li>
<li>페이지 간 전환 시 전체 페이지를 다시 불러와야 하므로 UX가 SPA에 비해 저하될 수 있다.</li>
<li>서버 부하가 크고, 페이지가 많아질수록 유지보수가 어렵다.</li>
</ul>
</li>
<li><p><strong>MPA의 장단점</strong></p>
</li>
</ul>
<table>
<thead>
<tr>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>SEO 친화적</td>
<td>페이지 이동 시 느린 속도</td>
</tr>
<tr>
<td>높은 확장성</td>
<td>복잡한 개발</td>
</tr>
<tr>
<td></td>
<td>보안 및 유지보수에서 불리함</td>
</tr>
</tbody></table>
<h3 id="02-spa-single-page-application">02. SPA: Single-Page Application</h3>
<p>SPA는 단일 HTML 페이지에서 콘텐츠를 동적으로 변경하는 방식으로, 모던 웹의 새로운 패러다임을 제시했다. React와 Angular, Vue.js 등에서 사용한다.</p>
<ul>
<li><p><strong>SPA의 특징</strong></p>
<ul>
<li>최초 로드 시 필요한 JS 파일을 모두 다운로드하여 이후 페이지 이동 시 클라이언트에서 처리한다.</li>
<li>CSR(Client-Side Rendering) 방식을 주로 사용하며, UX 측면에서 좋다.</li>
<li>SEO에서 불리하며, 초기 로딩 속도가 상대적으로 느린 것이 단점이다.</li>
<li>API를 활용한 비동기 데이터 로딩이 가능하여 복잡한 UI 개발이 용이하다.</li>
</ul>
</li>
<li><p><strong>SPA의 장단점</strong></p>
</li>
</ul>
<table>
<thead>
<tr>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>빠른 속도와 짧은 응답시간</td>
<td>느린 초기 구동 속도(TTV)</td>
</tr>
<tr>
<td>모바일 친화적</td>
<td>SEO에 불리함</td>
</tr>
<tr>
<td>개발 간소화</td>
<td>보안 문제</td>
</tr>
<tr>
<td>로컬 스토리지 캐시</td>
<td></td>
</tr>
</tbody></table>
<h2 id="2-nextjs의-렌더링-기법🎨">2. Next.js의 렌더링 기법🎨</h2>
<h3 id="00-nextjs의-렌더링-기법-등장배경">00. Next.js의 렌더링 기법 등장배경</h3>
<p>Next.js는 React 기반의 프레임워크로, React SPA의 장점은 유지하면서 <strong>SEO와 성능 최적화</strong>를 위해 다양한 렌더링 방식을 제공한다.</p>
<h3 id="01-csr-client-side-rendering">01. CSR: Client Side Rendering</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/60ad22db-9be7-4bd3-baa7-56d90d5485a1/image.webp" alt=""></p>
<p><code>CSR</code>은 React의 기본 렌더링 방식으로, 브라우저에서 JavaScript가 실행된 후 데이터를 가져와 UI를 동적으로 구성한다. 렌더링의 주체는 <code>클라이언트</code>이다.</p>
<ul>
<li><strong>장점</strong><ul>
<li>최초 로드 이후, 사용자 상호작용이 빠르고 부드럽다.</li>
<li>서버에게 추가적인 요청을 보낼 필요가 없기 때문에, 사용자 경험이 좋다.</li>
<li>서버 부하가 적다.</li>
</ul>
</li>
<li><strong>단점</strong><ul>
<li>첫 페이지 로딩 시간(TTV)이 길다.</li>
<li>JS가 로딩 되고 실행될 때까지 페이지가 비어있어 SEO에 불리하다.</li>
</ul>
</li>
</ul>
<h3 id="02-ssg-static-site-gerneration">02. SSG: Static Site Gerneration</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/ee03fdbf-2c17-4214-be43-970b4405a55a/image.webp" alt=""></p>
<p><code>SSG</code>은 빌드 타임에 <code>서버</code>에서 HTML을 생성하여 클라이언트에게 정적 파일로 제공하는 방식이다. 최초 빌드 시에만 생성되며, 사전에 미리 정적 페이지를 만들고 클라이언트(브라우저)가 해당 페이지를 요청하면 만들어져있는 페이지를 바로 제공한다.</p>
<ul>
<li><strong>장점</strong><ul>
<li>첫 페이지 로딩 시간(TTV)이 매우 짧아 사용자가 빠르게 페이지를 볼 수 있다.</li>
<li>SEO에 유리하다.</li>
<li>CDN(Content Delivery Network) 캐싱이 가능하다.</li>
</ul>
</li>
<li><strong>단점</strong><ul>
<li>정적인 데이터에만 사용할 수 있다.</li>
<li>사용자와의 상호작용이 서버와의 통신에 의존하므로, CSR보다 상호작용이 느릴 수 있다.</li>
<li>서버 부하가 클 수 있다.</li>
<li><em>마이페이지</em> 처럼 데이터에 의존하여 화면을 그려주는 경우에는 사용이 불가능하다.</li>
</ul>
</li>
</ul>
<h3 id="03-isr-incremental-static-regeneration">03. ISR: Incremental Static Regeneration</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/da564f15-ebc6-423f-beec-a3df6ad38824/image.webp" alt=""></p>
<p><code>ISR</code>은 SSG의 단점을 보완하기 위해 <strong>특정 시간 간격</strong>으로 정적 페이지를 갱신하는 방식으로, 정적 페이지를 먼저 보여주고 필요에 따라 서버에서 페이지를 재생성한다. 즉, SSG처럼 정적 페이지를 제공하지만, 설정한 주기만큼 페이지를 계속 생성해준다.</p>
<ul>
<li><strong>장점</strong><ul>
<li>정적 페이지를 먼저 제공하므로 UX가 좋으며, 콘텐츠가 변경되었을 때 서버에서 페이지를 재생성하므로 최신 상태를 (그나마) 유지할 수 있다.</li>
<li>CDN 캐싱이 가능하다.</li>
</ul>
</li>
<li><strong>단점</strong><ul>
<li>동적인 콘텐츠를 다루기에 한계가 있을 수 있습니다.</li>
<li>마이페이지 처럼 데이터에 의존하여 화면을 그려주는 경우 사용이 불가능하다.</li>
</ul>
</li>
</ul>
<h3 id="04-ssr-server-side-rendering">04. SSR: Server Side Rendering</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/15864463-f001-4c78-b415-4b6b1d7ec9be/image.webp" alt=""></p>
<p><code>SSR</code>은 요청이 들어오면 서버에서 HTML을 완성한 후 클라이언트에 전달하는 방식으로, SSG, ISR처럼 렌더링 주체가 <code>서버</code>이다.</p>
<ul>
<li><strong>장점</strong><ul>
<li>빠른 로딩 속도(TTV)와 높은 보안성을 제공한다.</li>
<li>SEO 최적화 좋다.</li>
<li>실시간 데이터를 사용한다.</li>
<li><em>마이페이지</em> 처럼 데이터에 의존한 페이지 구성 가능하다.</li>
</ul>
</li>
<li><strong>단점</strong><ul>
<li>사이트의 콘텐츠가 변경되면 전체 사이트를 다시 빌드해야 하는데, 이 과정이 시간이 오래 걸릴 수 있습니다.</li>
<li>요청할 때 마다 페이지를 만들어야 한다.</li>
<li>CDN 캐싱이 불가능하다.</li>
</ul>
</li>
</ul>
<h3 id="05-렌더링-기법-비교-및-선택-기준">05. 렌더링 기법 비교 및 선택 기준</h3>
<table>
<thead>
<tr>
<th>렌더링 방식</th>
<th>SEO</th>
<th>초기 로딩 속도</th>
<th>서버 부하</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td>CSR</td>
<td>낮음</td>
<td>느림</td>
<td>낮음</td>
<td>대시보드, 사내 도구</td>
</tr>
<tr>
<td>SSR</td>
<td>높음</td>
<td>보통</td>
<td>높음</td>
<td>뉴스 사이트, 로그인 필요 서비스</td>
</tr>
<tr>
<td>SSG</td>
<td>높음</td>
<td>빠름</td>
<td>없음</td>
<td>블로그, 마케팅 페이지</td>
</tr>
<tr>
<td>ISR</td>
<td>높음</td>
<td>빠름</td>
<td>낮음</td>
<td>상품 페이지, 자주 변경되는 콘텐츠</td>
</tr>
</tbody></table>
<h3 id="06-렌더링-방식-구현하기">06. 렌더링 방식 구현하기</h3>
<ul>
<li><strong>SSG</strong>: <code>cache : force-cache</code><pre><code>//fetch API
</code></pre></li>
</ul>
<p>fetch(&quot;url&quot;, {
   cache: &quot;force-cache&quot;
})</p>
<pre><code>
- **ISR**: `next: {revalidate: (n초) }`</code></pre><p>//fetch API</p>
<p>fetch(&quot;url&quot;, {
    next: {<br>        revalidate: 3,  //3초마다 갱신
    }
})</p>
<pre><code>
- **SSR**: `cache: &quot;no-store&quot;`</code></pre><p>//fetch API</p>
<p>fetch(&quot;url&quot;, {
    cache: &quot;no-store&quot;
})</p>
<pre><code>- **CSR**: `&quot;use client&quot;`</code></pre><ol>
<li>최상단에 &quot;use client&quot; 작성</li>
<li>useState, useEffect 같은 클라이언트 훅은 무조건 클라이언트 컴포넌트 안에서만 사용<pre><code></code></pre></li>
</ol>
<blockquote>
<p><a href="https://velog.io/@yejine2/SPASingle-Page-Application-VS-MPA#%EA%B0%9C%EB%85%90">MPA / SPA 참고 사이트 1</a>
<a href="https://coding-frog.tistory.com/54">Next.js 렌더링 참고 사이트 1</a>
<a href="https://velog.io/@yena1025/Next.js%EC%9D%98-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%B0%A9%EC%8B%9D#%EB%AA%A9%EC%B0%A8">Next.js 렌더링 참고 사이트 2</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[˗ˋˏ와ˎˊ˗  좋아요 : Optimistic Update🖤]]></title>
            <link>https://velog.io/@miiing_gaeng/%EC%99%80-%EC%A2%8B%EC%95%84%EC%9A%94-Optimistic-Update</link>
            <guid>https://velog.io/@miiing_gaeng/%EC%99%80-%EC%A2%8B%EC%95%84%EC%9A%94-Optimistic-Update</guid>
            <pubDate>Thu, 13 Mar 2025 14:02:35 GMT</pubDate>
            <description><![CDATA[<h2 id="1-optimistic-update❤️">1. Optimistic Update❤️</h2>
<h3 id="01-optimistic-update란">01. Optimistic Update란?</h3>
<p>일반적으로 데이터를 업데이트할 때, 서버 응답을 기다린 후 UI를 변경한다. 하지만 <strong>낙관적 업데이트 Optimistic Update</strong>는 서버 응답을 기다리지 않고 먼저 UI를 변경한 후, 요청이 실패하면 롤백하는 방식이다.
낙관적 업데이트는 SNS의 좋아요 기능에서 자주 사용된다. 사용자가 좋아요 버튼을 누르면 즉시 UI의 변경 사항(🤍 ▶️ ❤️)을 먼저 적용하고, 서버에 요청을 보낸다. 만약 요청이 실패하면 원래 데이터(🤍)로 복구된다.</p>
<h3 id="02-optimistic-update가-필요한-이유">02. Optimistic Update가 필요한 이유</h3>
<ul>
<li><strong>UX 개선</strong>: 사용자가 즉각적인 반응을 경험하며, 인터페이스가 더욱 빠르게 느껴진다.</li>
<li><strong>네트워크 지연 완화</strong>: 요청이 완료될 때까지 기다리지 않고 UI를 먼저 갱신하여, 네트워크 지연으로 인한 불편함을 개선할 수 있다.</li>
<li><strong>서버 부하 감소</strong>: 불필요한 refetching을 줄여 서버와의 불필요한 통신을 방지한다.</li>
</ul>
<h3 id="03-낙관적-업데이트-vs-비관적-업데이트-비교">03. 낙관적 업데이트 vs 비관적 업데이트 비교</h3>
<p>*<em>비관적 업데이트란?</em> : 낙관적 업데이트의 반대개념으로, 서버 응답 후 UI 갱신하는 방법을 뜻한다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>낙관적 업데이트</th>
<th>비관적 업데이트</th>
</tr>
</thead>
<tbody><tr>
<td>UI 업데이트 시점</td>
<td>요청 직후 즉시</td>
<td>서버 응답을 받은 후</td>
</tr>
<tr>
<td>응답 실패 시</td>
<td>원래 상태로 롤백</td>
<td>UI 변경 없음</td>
</tr>
<tr>
<td>속도 및 반응성</td>
<td>빠름</td>
<td>상대적으로 느림</td>
</tr>
<tr>
<td>적용 예시</td>
<td>좋아요 버튼, 댓글 작성</td>
<td>데이터 조회 후 렌더링</td>
</tr>
</tbody></table>
<h2 id="2-optimistic-update-with-tq🖤">2. Optimistic Update with. TQ🖤</h2>
<h3 id="00-optimistic-update에-필요한-usemutation의-옵션">00. Optimistic Update에 필요한 useMutation의 옵션</h3>
<ul>
<li><code>onMutate</code> : <code>mutationFn</code>가 실행되기 전에 실행되는 로직</li>
<li><code>onError</code> : <code>mutationFn</code> 실행 중 오류가 발견되면 실행되는 로직</li>
<li><code>onSettled</code> : 성공/실패 여부와 관계없이 <code>mutationFn</code> 요청이 완료된 후 실행되는 로직</li>
</ul>
<h3 id="01-usemutation을-활용한-낙관적-업데이트">01. useMutation을 활용한 낙관적 업데이트</h3>
<ul>
<li><p><code>onMutate</code> : <code>cancelQueries</code>로 중복 방지, <code>getQueryData</code>로 이전 데이터 반환, <code>setQueryData</code>로 낙관적 업데이트 로직 실행</p>
<pre><code>//예시
onMutate: async ({ id, liked }) =&gt; {
    //중복 방지를 위한 요청 취소
    await queryClient.cancelQueries([QUERY_KEY.TODOS, id]);

    //오류 발생 시 되돌려 놓을 이전 데이터 저장
    const prevTodo = queryClient.getQueryData([QUERY_KEY.TODOS, id]);

    //낙관적 업데이트
    const updatedTodo = { ...prevTodo, liked: !liked };
    queryClient.setQueryData([QUERY_KEY.TODOS, id], updatedTodo);

    return { prevTodo };
  }</code></pre></li>
<li><p><code>onError</code> : 요청 실패시, <code>setQueryData</code>를 사용하여 이전 데이터로 재설정</p>
<pre><code>//예시
onError: (error, _, context) =&gt; {
    //요청 실패시 이전 데이터로 재설정
    queryClient.setQueryData(context.prevTodo);
  }</code></pre></li>
<li><p><code>onSettled</code> : 요청 성공시 <code>invalidateQueries</code>로 쿼리 무효화</p>
<pre><code>//예시
onSettled: () =&gt; {
    //쿼리 무효화
    queryClient.invalidateQueries([QUERY_KEY.TODOS]);
  }</code></pre></li>
</ul>
<h3 id="02-tanstack-query---optimistic-update-구현-예제">02. TanStack Query - Optimistic Update 구현 예제</h3>
<ul>
<li><p><code>useTodoMutation.js</code> : Custom Hook으로 제작
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/f329aba9-0354-4b34-a44b-2be93dec37bb/image.png" alt=""></p>
</li>
<li><p><code>TodoList.jsx</code> : <code>mutate</code> 함수 import
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/5cdbbffa-4417-4303-bd40-4016bf0f7161/image.png" alt=""></p>
</li>
</ul>
<ul>
<li><code>TodoList.jsx</code> - JSX : <code>like버튼</code>에 onClick이벤트에 <code>likeMutation</code> 연결
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/c2e0f890-ca40-430b-9059-b773572c882a/image.png" alt=""></li>
</ul>
<blockquote>
<p><a href="https://tanstack.com/query/latest/docs/framework/react/reference/useMutation#usemutation">TanStack Query - useMutation 공식 문서</a>
<a href="https://tanstack.com/query/v4/docs/framework/react/guides/optimistic-updates">TanStack Query - Optimistic Update 공식 문서</a>
<a href="https://velog.io/@jhjung3/Optimistic-Updates-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-with-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC#optimistic-updates-%EA%B0%9C%EB%85%90">Optimistic Update 참고 사이트</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[LOL 챔피언 사이트) TroubleShooting 1 : Next.js의 next/image 컴포넌트🔱]]></title>
            <link>https://velog.io/@miiing_gaeng/LOL-%EC%B1%94%ED%94%BC%EC%96%B8-%EC%82%AC%EC%9D%B4%ED%8A%B8-TroubleShooting-1-Next.js%EC%9D%98-nextimage-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@miiing_gaeng/LOL-%EC%B1%94%ED%94%BC%EC%96%B8-%EC%82%AC%EC%9D%B4%ED%8A%B8-TroubleShooting-1-Next.js%EC%9D%98-nextimage-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Wed, 12 Mar 2025 11:53:49 GMT</pubDate>
            <description><![CDATA[<h2 id="1-문제-발생🤯">1. 문제 발생🤯</h2>
<h3 id="01-nextjs의-nextimage-컴포넌트-사용하기">01. Next.js의 next/image 컴포넌트 사용하기</h3>
<p>이번 프로젝트에서 Next.js와 TypeScript를 사용해보게 되었다. 아직 React도 낯선데... 벌써 Next.js에다 TypeScript까지? 프로젝트 시작과 동시에 또 막막함에 의욕이 사라졌지만, 어쩌겠는가. 해야지!
이번에는 Riot Games API를 활용하여 리그 오브 레전드의 챔피언과 아이템 정보를 제공하는 사이트를 만들어야 한다. 오늘의 할 일은 &quot;아이템 목록 페이지&quot;를 구현하는 것이다. Riot Developer에서 아이템과 챔피온 데이터를 얻기 위해 <a href="https://developer.riotgames.com/docs/lol#data-dragon_items">Data Dragon</a> 문서를 찾아보았고, 해당 데이터를 불러올 수 있는 url을 찾을 수 있었다. 아래는 <code>item</code>의 이미지 경로이다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/0575ba11-3edb-4620-8903-5eec5d4a3aa1/image.png" alt=""></p>
<p>경로를 찾아 <code>items/itemsPage</code>에서 데이터를 fetch하고, UI에 next/image태그와 함께 적용해보았다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/8671de9f-e013-475a-bc69-709d4bf2f801/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/00ba2e1c-999d-4f49-a281-8bde23b85eff/image.png" alt=""></p>
<p>이미지가 잘 불러와졌는지 한번 확인해보자.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/be7ca6f2-0159-4668-89ff-ca86ae32506c/image.png" alt=""></p>
<p>역시나 오류가 없이 성공하면 밍갱의 개발새발이 아니지🫠 아래에서 Next.js의 next/image 컴포넌트에 대해 알아보고, 오류를 고쳐보자!</p>
<h3 id="02-taliwind-css로-반응형-구현하기">02. Taliwind CSS로 반응형 구현하기</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/640353e5-c3f9-4c4f-ac0c-dd9b2d0d4a80/image.png" alt=""></p>
<p>위에서 안된게 바로 아래에서 되는 매직🪄 물론 문제해결을 위해 몇시간을 쏟았지만, 또다른 이슈를 소개하기 위해 시간을 거슬러보았다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/a16b7a55-563c-493e-8a53-f747a8205544/image.png" alt=""></p>
<p>반응형을 해야하는 이유가 이 사진으로 설명된다. 핸드폰 스크린 사이즈로 보게되면 아이템의 이름과 설명 모두 읽기 어려워진다. 그래서 Next.js에 내장된 Taliwind CSS를 활용하여 반응형을 구현해보고자 한다.</p>
<h2 id="2-개념-정리🧐">2. 개념 정리🧐</h2>
<h3 id="01-nextjs의-nextimage-컴포넌트와-이미지-최적화">01. Next.js의 next/image 컴포넌트와 이미지 최적화</h3>
<p>Next.js는 next/image 컴포넌트를 제공하여 자동 이미지 최적화 기능을 지원한다. 이는 브라우저 성능을 향상시키고, 로딩 속도를 최적화하며, 다양한 디바이스에 맞춰 최적화된 이미지를 제공하는 역할을 한다.</p>
<ul>
<li><strong>자동 포맷 변환 (WebP 지원)</strong> : Next.js는 지원하는 브라우저에 따라 이미지를 WebP 등의 고효율 포맷으로 자동 변환해준다.</li>
<li><strong>지연 로딩 (Lazy Loading)</strong> : 기본적으로 next/image 컴포넌트는 지연 로딩이 활성화되어 있어서, 화면에 보일 때만 이미지가 로드된다. 이를 통해 불필요한 리소스 로딩을 방지할 수 있다.</li>
<li><strong>반응형 이미지</strong> : Next.js는 뷰포트 크기에 맞춰 최적화된 이미지를 자동으로 제공한다. 즉, 모바일, 태블릿, 데스크톱에서 각각 적절한 크기의 이미지가 로드되도록 도와준다.</li>
<li><strong>CDN을 통한 이미지 최적화</strong> : Next.js는 next/image 컴포넌트를 사용할 때 Vercel의 이미지 최적화 서비스를 기본적으로 활용하기 때문에, 별도의 서버 설정 없이도 최적화된 이미지를 빠르게 제공할 수 있다.</li>
</ul>
<p>*<em>WebP란?</em> : WebP는 기존 JPG, PNG보다 용량이 작은 이미지 압축 포맷. 용량이 작아 페이지 로딩 속도를 향상시킨다.</p>
<h3 id="02-nextimage-컴포넌트-vs-img-태그">02. next/image 컴포넌트 vs img 태그</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th><code>&lt;img&gt;</code> 태그</th>
<th><code>next/image</code></th>
</tr>
</thead>
<tbody><tr>
<td>이미지 최적화</td>
<td>없음</td>
<td>자동 최적화</td>
</tr>
<tr>
<td>WebP 변환</td>
<td>수동 처리 필요</td>
<td>지원</td>
</tr>
<tr>
<td>Lazy Loading</td>
<td>수동 처리 필요</td>
<td>기본 적용</td>
</tr>
<tr>
<td>반응형 지원</td>
<td>CSS 설정 필요</td>
<td>자동 크기 조정</td>
</tr>
<tr>
<td>CDN 최적화</td>
<td>없음</td>
<td>Vercel 최적화</td>
</tr>
</tbody></table>
<h3 id="03-nextimage-컴포넌트-사용법">03. next/image 컴포넌트 사용법</h3>
<ul>
<li><strong>로컬 이미지 사용</strong> : <code>public</code> 폴더에 이미지를 저장하고 절대경로로 불러와야한다. 외부 URL보다 빠르고 안정적인 최적화 가능하기 때문에 가능하면 로컬 이미지를 사용하는 것이 좋다.<pre><code>import Image from &quot;next/image&quot;;
</code></pre></li>
</ul>
<p>&lt;Image
  src=&quot;/images/sample.jpg&quot;  // 로컬 이미지 (public 폴더 내부)
  alt=&quot;Sample Image&quot;
  width={500}  // 고정 너비
  height={300} // 고정 높이
/&gt;</p>
<pre><code>
- **외부 url 사용** : 외부 URL 이미지를 사용하려면 `next.config.js`에서 도메인 허용 설정이 필요하다.</code></pre><p>// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: &quot;https&quot;,
        hostname: &quot;example.com&quot;,
      },
    ],
  },
};</p>
<p>// 해당 컴포넌트.tsx
<Image
  src="https://example.com/image.jpg"
  alt="External Image"
  width={600}
  height={400}
/></p>
<pre><code>
### 04. next/image 컴포넌트의 프로퍼티
- **필수 프로퍼티**
    - `src` : 이미지 경로
    - `alt` : 대체 텍스트, 스크린 리더 및 검색 엔진을 위해 이미지를 설명하는 데 사용
    - `width` : 픽셀 단위의 고유 이미지 너비
    - `heigth` : 픽셀 단위의 고유 이미지 높이
- **선택 프로퍼티**
    - `fill` : 이미지가 부모 요소를 채우도록 하는 boolean값. `width`와 `height`를 알 수 없을 때 사용(이 때, width, heigth 생략 가능)

 기타 프로퍼티들은 아래 Next.js 공식 문서 확인하기

&gt; [Next.js의 next/image 컴포넌트 공식 문서](https://nextjs.org/docs/app/api-reference/components/image)
&gt; [Next.js의 next/image 컴포넌트 공식 문서 (한국어판)](https://nextjs-ko.org/docs/app/api-reference/components/image#fill)

## 3. 해결 방안😇
### 01. Next/Image 컴포넌트에 width, heigth 프로퍼티 추가
![](https://velog.velcdn.com/images/miiing_gaeng/post/99ead432-2f8a-4d50-8588-d9e671d97a05/image.png)

공식 문서를 확인해보니 `src`, `alt`, `width`, `height`는 필수값인데, 나는 `src`와 `alt`만 줘서 생긴 오류였던 것이다. 바로 `width`와 `height`에 임의로 50px을 넣어주었다.

### 01-1. 또 다른 오류발생. url 문제인가?
![](https://velog.velcdn.com/images/miiing_gaeng/post/60cd9546-a097-4833-8c70-9d197f69704a/image.png)

이건 또 뭐람? 해결된 줄 알고 설레는 마음으로 새로고침했더니, 이젠 또다른 오류가 나를 맞이했다. &quot;대충&quot; 읽어보니 `src` 프로퍼티에서 생긴 오류인 것 같다.
![](https://velog.velcdn.com/images/miiing_gaeng/post/1b13ba70-0592-4bfc-9988-f28e1e325402/image.png)

처음에는 이미지 url이 잘못 되었나 싶은 생각에 무작정 Riot developer 문서에 있는 url을 냅다 넣어주었다.
당연히 해결이 되지 않았다. 왜냐하면 url 문제가 아니었기 때문이다. 도대체 뭐가 문제인가 싶은 마음에 다시 오류를 꼼꼼히 읽어보았고, 이 오류에 대한 [공식 문서 링크](https://nextjs.org/docs/messages/next-image-unconfigured-host)가 있어서 한번 들어가 보았다.
![](https://velog.velcdn.com/images/miiing_gaeng/post/68df2c89-bee4-4334-b193-a6ab54e4fb94/image.png)

알고보니 Next/Image 컴포넌트에서 `src`에 외부 URL을 사용할 때, 해당 호스트 네임이 `next.config.js`의 `images.remotePatterns`에 등록되지 않아서 발생한 오류였다. 이래서 공식 문서를 꼼꼼히 읽어야한다는 것이다!

### 02. next.config.js 수정
![](https://velog.velcdn.com/images/miiing_gaeng/post/ecf25ba1-dbc1-48f5-b72a-bc5c860a483c/image.png)

Next/Image 컴포넌트 공식 문서에서 [remotePatterns](https://nextjs.org/docs/app/api-reference/components/image#remotepatterns)를 참고하여 `next.config.js`파일을 수정해주었다.
![](https://velog.velcdn.com/images/miiing_gaeng/post/f5e84947-60e6-4841-9dcf-c6ad44d3a0de/image.png)

오 이미지가 페이지에 잘 불러와졌다!

### 02-1. grid는 어려워
![](https://velog.velcdn.com/images/miiing_gaeng/post/308c17a6-8b89-4b5d-afa4-d6b4a411a4ab/image.png)

![](https://velog.velcdn.com/images/miiing_gaeng/post/ad5629c7-6cf5-4209-b770-7e100f8e8174/image.png)

개발 초기 단계이기 때문에 CSS는 최대한 간소하게 만져주었다. 동시에 반응형을 위해 `grid`를 사용하여 간단하게 구현해보고자 했다. 신나게 `grid`로 적용해주고 확인해보았더니...

![](https://velog.velcdn.com/images/miiing_gaeng/post/c1de0950-52ca-4080-bc63-22a9ab0e8aad/image.png)

왜 1줄이 되었죠?

### 03. grid로 반응형 구현
나는 바보 멍청이였다. Tailwind CSS 반응형 클래스를 제대로 이해하지 않고 작성하여 생긴 문제였다. `max- ~`로 작성된 클래스를 전부 다 수정해주었다.
![](https://velog.velcdn.com/images/miiing_gaeng/post/65254c79-5252-4a50-8f6a-fede1e3e5c09/image.png)

그리고나서 확인해보니...
![](https://velog.velcdn.com/images/miiing_gaeng/post/d0134028-363f-4e85-9ff3-5afeb30c2352/image.png)

![](https://velog.velcdn.com/images/miiing_gaeng/post/df587bad-b923-40bf-a5c7-ffc3310d315a/image.png)

반응형이 잘 적용 되었다.


### 03-1. 근데 이미지 크기가 왜 다 달라요?
![](https://velog.velcdn.com/images/miiing_gaeng/post/7af09962-caf1-4c12-940f-eaebe3592652/image.png)

반응형까지 어찌저찌 해냈는데, 왜 이미지 크기는 제멋대로일까? 이 문제를 해결하고자 next/image 컴포넌트의 프로퍼티 중 `fill`을 활용해보았다.
![](https://velog.velcdn.com/images/miiing_gaeng/post/50beb500-4c2b-423f-88ec-618598e44cff/image.png)

next/image 컴포넌트를 `&lt;div&gt;`로 감싸줘 크기를 고정시킨 다음, `fill`프로퍼티를 추가해주었다.

![](https://velog.velcdn.com/images/miiing_gaeng/post/a8e2dfc1-faf2-42fa-8c88-62da39f8f941/image.png)

예? 이 아름다운 오로라는 또 뭐고...

### 04. 제발 공식 문서를 꼼꼼히 읽어보자
이 문제 또한 공식 문서를 꼼꼼히 확인하지 않은 데에서 발생한 문제였다. 이정도면 난독증이 있는게 아닐까...🤔
![](https://velog.velcdn.com/images/miiing_gaeng/post/053f0cf3-e347-4009-b8f9-eca11c77e6b0/image.png)

알고보니 `fill` 프로퍼티를 사용할 때는 부모 요소에 `position: absolute / relative / fixed`를 주어야한다. 왜냐하면 `fill` 프로퍼티로 인해서 next/image 컴포넌트가 자동적으로 `position: absolute`가 적용되기 때문이다.
![](https://velog.velcdn.com/images/miiing_gaeng/post/54b9192e-7277-4b3d-a2dd-89cbee03eb83/image.png)

`&lt;div&gt;`에 `position: relative`를 추가해주고 다시 확인해보았다.

## 4. 결과😎
### 01. 해결!
![](https://velog.velcdn.com/images/miiing_gaeng/post/b57b44c1-d186-450f-a014-a8bdd5383a80/image.png)

![](https://velog.velcdn.com/images/miiing_gaeng/post/c37e2dc4-8b4b-44c5-be26-b583424b8eac/image.png)

### 02. 최종 코드
- 📁items / page.tsx
![](https://velog.velcdn.com/images/miiing_gaeng/post/91f0fe41-41a9-4836-a95f-309c728fa2af/image.png)

- 📁components / ItemCard.tsx
![](https://velog.velcdn.com/images/miiing_gaeng/post/82c23746-d749-463d-a317-acc05cf2b0ad/image.png)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[상태, 서버 딸이에요 : TanStack Query🧃]]></title>
            <link>https://velog.io/@miiing_gaeng/%EC%83%81%ED%83%9C-%EC%84%9C%EB%B2%84-%EB%94%B8%EC%9D%B4%EC%97%90%EC%9A%94-TanStack-Query</link>
            <guid>https://velog.io/@miiing_gaeng/%EC%83%81%ED%83%9C-%EC%84%9C%EB%B2%84-%EB%94%B8%EC%9D%B4%EC%97%90%EC%9A%94-TanStack-Query</guid>
            <pubDate>Tue, 11 Mar 2025 12:23:05 GMT</pubDate>
            <description><![CDATA[<h2 id="1-상태-관리🧑💼">1. 상태 관리🧑‍💼</h2>
<h3 id="00-react의-상태란">00. React의 상태란?</h3>
<p><strong>React state 관련 게시글</strong> ▶️ <a href="https://velog.io/@miiing_gaeng/%EB%B0%94%EB%80%90-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%8A%94-%EA%B3%A0%EA%B0%9C%EB%A5%BC-%EB%93%A4%EC%96%B4%EC%A3%BC%EC%84%B8%EC%9A%94-React-state">이전 정리글 참고</a></p>
<p>React 컴포넌트는 로직을 처리하기 위해 저장하는 특정한 값을 state라고 한다. state 값이 변화했을 때, 컴포넌트는 리렌더링을 한다.</p>
<p>상태의 종류는 크게 3가지이다.</p>
<ul>
<li><strong>전역 상태</strong> : 프로젝트 전체에 영향을 끼치는 상태 (예시 - 로그인 여부, 테마)</li>
<li><strong>컴포넌트 간 상태</strong> : 여러 컴포넌트에서 관리되는 상태 (예시 - 데이터)</li>
<li><strong>지역 상태</strong> : 특정 컴포넌트 안에서만 관리되는 상태 (예시 - 카운트)</li>
</ul>
<h3 id="01-상태-관리란">01. 상태 관리란?</h3>
<p>여러 컴포넌트에서 같은 데이터(상태)가 필요할 때, 부모자식 관계가 아니라면 직접적인 데이터 전달은 불가능하다. 전달을 하기 위해선 부모 컴포넌트로 데이터를 보내고, 그 데이터를 필요한 컴포넌트로 전달하는 복잡한 과정(prop drilling)이 필요하다. 프로젝트의 규모가 커져 필요한 상태가 많아지고, 어플리케이션이 복잡해질수록 props를 추적하는 것은 더 어려워진다.
이런 문제점을 해결하기 위해, 개발자는 알맞은 상태관리 툴을 선택해 <strong>상태관리</strong>를 해야한다. 즉, 상태관리는 데이터(상태)를 추적하고, 여러 컴포넌트 간의 데이터 공유를 용이하게 한다. 또한, 프로그램이 어떻게 구동 될지 예측 가능하도록 하여 유지보수 측면에서 유리해지며, 코드의 재사용성 증가와 성능 최적화에도 도움이 된다.</p>
<p>요약하자면...</p>
<ul>
<li>상태 추적 ▶️ 여러 컴포넌트 간의 데이터 공유를 용이하도록 만듦</li>
<li>프로그램이 어떻게 구동 될지 예측 가능 ▶️ 유지보수 측면에서 유리</li>
<li>코드의 재사용성 증가와 성능 최적화에도 도움</li>
</ul>
<h3 id="02-클라이언트-상태란">02. 클라이언트 상태란?</h3>
<p>웹 애플리케이션의 프론트엔드 에서 관리되는 데이터로, 사용자의 브라우저나 애플리케이션 내에서 유지된다. 각 클라이언트별로 독립적인 상태를 가지며, UI 상태, 사용자 입력값, 임시 데이터 등을 포함한다. 클라이언트 상태는 서버 요청 없이도 즉시 반영되며, UX 개선을 위해 미리 가져온 데이터를 활용할 수 있다. 하지만 다른 클라이언트와 공유되지 않으며, 페이지를 새로고침하면 사라질 수 있다는 점이 특징이다.</p>
<h3 id="03-서버-상태란">03. 서버 상태란?</h3>
<p>웹 애플리케이션의 백엔드 에 저장되는 데이터로, 데이터베이스, 파일 시스템, 캐시 등에 보관된다. 서버 상태는 모든 클라이언트가 동일하게 공유하는 데이터이며, 주로 사용자 정보, 게시물, 상품 정보 등 영속적인 데이터를 포함한다. 클라이언트에서 변경이 발생하면 비동기 요청을 통해 서버와 데이터를 동기화해야 하며, 응답을 기다려야 한다. 또한 보안과 데이터 무결성을 유지해야 하므로 중앙 집중식 데이터 소스에서 관리된다.</p>
<p>*<em>중앙 집중식 데이터 소스란 ?</em> : 다양한 소스의 데이터를 한 곳에 모아놓은 저장소</p>
<h3 id="04-클라이언트-상태-vs-서버-상태">04. 클라이언트 상태 vs 서버 상태</h3>
<table>
<thead>
<tr>
<th>특징</th>
<th>클라이언트 상태</th>
<th>서버 상태</th>
</tr>
</thead>
<tbody><tr>
<td>관리 주체</td>
<td>프론트엔드<br> (사용자 브라우저, 애플리케이션)</td>
<td>백엔드<br> (서버, 데이터베이스)</td>
</tr>
<tr>
<td>저장 위치</td>
<td>브라우저 메모리, 상태 관리 라이브러리</td>
<td>데이터베이스, 파일 시스템, 캐시</td>
</tr>
<tr>
<td>클라이언트 간 데이터 공유</td>
<td>X (각 클라이언트별 개별 상태)</td>
<td>O (동일한 데이터 제공)</td>
</tr>
<tr>
<td>주요 데이터</td>
<td>UI 상태, 사용자 입력값 등 일시적 데이터</td>
<td>게시물, 상품 정보 등 영속적 데이터</td>
</tr>
<tr>
<td>상태 변경 시</td>
<td>즉시 반영, 미리 가져온 데이터 활용</td>
<td>비동기 요청▶️ 서버데이터 요청/수정</td>
</tr>
</tbody></table>
<h3 id="05-상태-관리-라이브러리">05. 상태 관리 라이브러리</h3>
<ul>
<li><strong>useState</strong>
state를 관리하여 UI를 업데이트하는 React 내장 hook이다.</li>
</ul>
<p><strong>useState 관련 게시글</strong> ▶️ <a href="https://velog.io/@miiing_gaeng/Hook-series-1-useState">이전 정리글 참고</a></p>
<ul>
<li><strong>Context API</strong>
Context API는 불필요한 prop-drilling을 하지 않아도 타겟 컴포넌트에게 필요한 데이터/상태를 쉽게 공유할 수 있게 해준다. 하지만 불필요한 리렌더링이 발생한다는 한계점이 있다.</li>
</ul>
<p><strong>Context API 관련 게시글</strong> ▶️ <a href="https://velog.io/@miiing_gaeng/Hook-Series-4-useContext%EC%99%80-%EC%A0%84%EC%97%AD-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC">이전 정리글 참고</a></p>
<ul>
<li><p><strong>Redux</strong>
Redux는 전역 상태관리를 위한 도구(라이브러리)로, 어플리케이션 전체에 대한 중앙 저장소 역할을 한다. <code>store</code>를 활용하여 중앙 상태 관리 방식으로 동작한다. <code>action</code>을 통해 <code>store</code> 내부의 상태 변경에 대한 이벤트를 정의하고, <code>reducer</code>로 액션에 대한 이벤트를 처리하여 전역 상태를 업데이트한다.</p>
</li>
<li><p><strong>TanStack Query</strong> (구 React Query)
서버상태를 관리하는 라이브러리로, 서버와 클라이언트 간 비동기 작업을 쉽게 다룰 수 있게 도와준다.</p>
</li>
</ul>
<h2 id="2-tanstack-query🏝️">2. TanStack Query🏝️</h2>
<h3 id="01-tanstack-query란">01. TanStack Query란?</h3>
<p>TanStack Query는 서버로부터 데이터를 가져오고(fetching), 캐싱(caching)하고, 캐시를 제어하는 등 서버 상태를 쉽고 효율적으로 관리할 수 있는 라이브러리이다. 즉, 웹 애플리케이션에서 서버 상태의 페칭, 캐싱, 동기화, 업데이트 작업을 간편하게 수행할 수 있도록 도와준다.
TanStack Query를 사용하면 데이터 관련 코드의 양을 줄이고, 항상 최신 데이터를 유지할 수 있다. 네트워크가 재연결되거나 요청이 실패할 경우 자동으로 데이터를 갱신하여 애플리케이션의 안정성을 높인다. 또한, 낙관적 업데이트(Optimistic Updates), 무한 스크롤(Infinite Scroll), 페이지네이션(Pagination) 등의 기능을 손쉽게 구현할 수 있으며, 이를 통해 메모리 사용과 성능을 최적화할 수 있다.</p>
<h3 id="02-tanstack-query의-주요-개념">02. TanStack Query의 주요 개념</h3>
<ul>
<li><p><strong>Queries</strong> : 쿼리는 비동기 데이터 소스에 대한 선언적 의존성을 가지며, 고유한 키를 통해 식별되며, 서버에서 데이터를 가져올 때 활용된다. <code>new QueryClient</code>로 QueryClient를 생성하고 <code>useQueryClient</code>로 QueryClient를 호출한다. 그리고, <code>useQuery</code>로 데이터를 캐싱한다.</p>
</li>
<li><p><strong>Mutations</strong> : Mutation은 데이터를 생성(Create), 수정(Update), 삭제(Delete)하거나 서버에 부수적인 영향을 주는 작업에 사용된다. <code>useMutation</code>을 사용한다.</p>
</li>
<li><p><strong>Query Invalidation</strong> : 쿼리의 stale(오래됨) 상태나 사용자 행동으로 인해 데이터가 변경되는 경우 새로운 데이터를 가져와야한다. 이럴 때, QueryClient의 <code>invalidateQueries</code>를 사용하여 특정 쿼리의 상태를 변경하고 데이터를 다시 가져올 수 있다.</p>
</li>
</ul>
<h3 id="03-queryclient--쿼리-생성-및-호출">03. QueryClient : 쿼리 생성 및 호출</h3>
<ul>
<li><strong><code>new QueryClient</code></strong> : 쿼리 생성<pre><code>// 쿼리 생성
const queryClient = new QueryClient()
</code></pre></li>
</ul>
<p>export default function App() {
  return (
      //Provider로 래핑
    <QueryClientProvider client={queryClient}>
      <TodoList />
    </QueryClientProvider>
  )
}</p>
<pre><code>
- **`useQueryClient`** : 쿼리 인스턴스 연결</code></pre><p>export default function TodoList(){
    //쿼리 인스턴스 연결
    const queryClient = useQueryClient()</p>
<pre><code>...생략</code></pre><p>}</p>
<pre><code>- `invalidateQueries()` : 쿼리 무효화</code></pre><p>queryClient.invalidateQueries() // 모든 쿼리 무효화
queryClient.invalidateQueries([&quot;queryKey&quot;]) // 특정 쿼리 무효화</p>
<pre><code>
### 04. useQuery : 데이터 fetch
- **`useQuery`** : 컴포넌트에서 데이터를 가져올 때 사용</code></pre><p>//jsx
const { 반환값 구조 분해 } = useQuery(옵션)</p>
<p>//tsx
const { 반환값 구조 분해 } = useQuery&lt;데이터타입&gt;(옵션)</p>
<pre><code>- **useQuery의 옵션**
    - `queryKey` : _필수_ ! 해당 쿼리를 식별하는 고유한 키(TanStack Query는 쿼리 키에 따라 쿼리 캐싱을 관리) 배열 형태로 설정한다.
    - `queryFn` :  _필수_ ! 데이터를 가져오는 비동기 함수
    - `select` : 가져온 데이터를 변형(선택)하는 로직
 기타 옵션들은 [TanStack Query useQuery 문서](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery#usequery) 확인하기

- **useQuery의 반환값**
    - `data` : 성공적으로 가져온 데이터
    - `isPending` : 쿼리가 실행 중이며, 아직 데이터가 로딩되지 않은 상태 (첫 데이터 fetch 시 사용)
    (`isLoading`과 비슷하지만 `isLoading`은 v5 이후부터 잘 사용하지 않음)
    - `isFetching` : 쿼리 함수가 실행 중인 상태 (첫 fetch + refetch 모두 포함)
    - `isError` : 쿼리 함수에서의 오류 발생 여부
    - `error` : 오류가 발생했을 때의 오류 객체, 오류가 없으면 null
 기타 반환값들은 [TanStack Query useQuery 문서](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery#usequery) 확인하기

- **예시**</code></pre><p>//sample in jsx</p>
<p>const { data, isPending, isError } = useQuery({
    queryKey: [&quot;todos&quot;],
    queryFn: fetchTodoData,
    select: data =&gt; data.map(user =&gt; user.name)
})</p>
<pre><code>
### 05. useMutation : 데이터 변경 작업
- **`useMutation`** : 데이터 변경 작업(생성, 수정, 삭제 등)을 처리</code></pre><p>const 반환 = useMutation(옵션)</p>
<pre><code>
- **useMutation의 옵션**
    - `mutationFn` : 실행할 비동기 변이 함수
    - `onSuccess` : 변이가 성공할 때 호출되는 함수
    - `onMutate` : 변이 함수가 실행되기 전에 호출되는 함수
    - `onSettled` : 변이가 성공하거나 실패해도 항상 호출되는 함수
    - `onError` : 변이 중 오류가 발생할 때 호출되는 함수
 기타 옵션들은 [TanStack Query useMutation 문서](https://tanstack.com/query/latest/docs/framework/react/reference/useMutation#usemutation) 확인하기

- **useMutation의 반환값**
    - `mutate` : 변이 실행 함수
    - `data` : 성공적으로 가져온 데이터
    - `isPending` : 변이가 실행 중인지 여부 (이전 버전에서는 `isLoading`)
 기타 반환값들은 [TanStack Query useMutation 문서](https://tanstack.com/query/latest/docs/framework/react/reference/useMutation#usemutation) 확인하기

- **예시**</code></pre><p>//sample in jsx</p>
<p>const { mutate : addMutation } = useMutation({
    mutationFn: addTodo,
    onSuccess: () =&gt; {
      alert(&quot;새로운 할일이 추가되었습니다.&quot;);
      //쿼리 무효화
      queryClient.invalidateQueries([&quot;todos&quot;]);
    }
  });</p>
<p>```</p>
<blockquote>
<p><a href="https://tanstack.com/query/latest">TanStack Query 공식 문서</a>
<a href="https://velog.io/@eunbeann/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90%EC%84%9C-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%9C-%EC%9D%B4%EC%9C%A0#%EC%83%81%ED%83%9Cstate%EB%9E%80-">상태관리 참고 사이트 1</a>
<a href="https://mingule.tistory.com/74">상태관리 참고 사이트 2</a>
<a href="https://velog.io/@okko8522/%EC%84%9C%EB%B2%84-%EC%83%81%ED%83%9C-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%83%81%ED%83%9C#%EC%84%9C%EB%B2%84-%EC%83%81%ED%83%9C-vs-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%83%81%ED%83%9C">서버상태/클라이언트상태 참고 사이트</a>
<a href="https://www.heropy.dev/p/HZaKIE">TanStack Query 참고 사이트</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[지금까지 이런 탐색법은 없었다 : 델타 배열💣]]></title>
            <link>https://velog.io/@miiing_gaeng/%EB%8D%B8%ED%83%80-%EB%B0%B0%EC%97%B4</link>
            <guid>https://velog.io/@miiing_gaeng/%EB%8D%B8%ED%83%80-%EB%B0%B0%EC%97%B4</guid>
            <pubDate>Mon, 10 Mar 2025 11:40:31 GMT</pubDate>
            <description><![CDATA[<h2 id="1-주제-선정-이유📖">1. 주제 선정 이유📖</h2>
<h3 id="01-프로그래머스-입문---안전지대">01. 프로그래머스 입문 - 안전지대</h3>
<p>오랜만에 프로그래머스 입문 문제를 풀었다. 확실히 입문 문제 90% 이상 넘어가니 난이도가 확 올라가더라(그냥 내가 못하는거임)
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/405f3699-22d4-4e85-aa28-ed75d6d24e16/image.png" alt=""></p>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/120866">프로그래머스 코딩테스트 입문 - 안전지대</a></p>
<h3 id="02-그래서-여기서-뭐-어떻게-하라구요">02. 그래서 여기서 뭐 어떻게 하라구요...?</h3>
<p>일단 지뢰의 위치들을 mines 배열에 넣어주었다. 그리고 이제 그 좌표를 중심으로 주변 8칸에 표시만 해주면 되는데, 여기서 뇌정지가 왔다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/9bcce0ed-5250-48ee-a0e8-29e243e8ad84/image.png" alt=""></p>
<p>&#39;어떻게 주변 8칸을 구하지?🤔&#39;라는 막막함에 프로그래머스 질문 목록도 찾아보고, 구글링도 해본 결과... <code>델타 배열</code>이라는 유용한 계산법이 있는게 아닌가! 앞으로 알고리즘에서 2차원 배열과 관련된 문제가 많이 나올 것 같은데, 이참에 한번 정리해보고자 한다. (나중에 두고두고 봐야지)</p>
<h2 id="2-델타-배열🏁">2. 델타 배열🏁</h2>
<h3 id="01-델타-배열이란">01. 델타 배열이란?</h3>
<p>델타 배열은 2차원 배열(<code>[ [],[] ... ]</code>)에서 특정 위치의 이동을 쉽게 처리하기 위한 배열로, 특히 8방향(상하좌우+대각선)을 탐색할 때 유용하다. 즉, 2차원 배열에서 특정 위치에서 여러 방향을 탐색할 때, if문을 여러 개 쓰는 것보다 반복문 하나로 해결할 수 있도록 해주는 것이 델타 배열이다.</p>
<h3 id="02-델타-배열의-구성요소">02. 델타 배열의 구성요소</h3>
<ul>
<li><p><strong>델타 배열</strong> : 이동할 방향을 정의한 배열</p>
<pre><code>const directions = [
  [-1, 0], [1, 0],  // 상, 하
  [0, -1], [0, 1],  // 좌, 우
  [-1, -1], [-1, 1], [1, -1], [1, 1] // 대각선 (좌상, 우상, 좌하, 우하)
];</code></pre></li>
<li><p><strong>델타 배열을 활용한 이동 코드</strong> : 반복문 하나로 여러 방향을 탐색할 수 있다. 기본적인 사용법은 <em>현재 좌표</em> <code>x, y</code>에서 <em>이동 거리</em> <code>dx, dy</code>를 더해서 이동</p>
<pre><code>//8방향 탐색
for (let [dx, dy] of directions) {
  let nx = x + dx;  // 새로운 행 위치
  let ny = y + dy;  // 새로운 열 위치

  // 범위 체크 (배열 인덱스 벗어나지 않도록)
  if (nx &gt;= 0 &amp;&amp; nx &lt; board.length &amp;&amp; ny &gt;= 0 &amp;&amp; ny &lt; board.length) {
      console.log(`이동 가능: (${nx}, ${ny})`);
  }
}
</code></pre></li>
</ul>
<pre><code>
- **범위 체크** : 배열을 탐색할 때 범위를 벗어나면 오류가 발생할 수 있으므로 범위를 체크해줘야한다.</code></pre><p>if (nx &gt;= 0 &amp;&amp; nx &lt; board.length &amp;&amp; ny &gt;= 0 &amp;&amp; ny &lt; board.length) {
    // 유효한 좌표일 때만 작업 수행
}</p>
<pre><code>
### 03. 직접 구현 vs 델타 배열
- **8방향 탐색 직접 구현**
각 방향을 하나씩 조건문으로 처리해야 하기 때문에 실수할 가능성 증가하며, 중복된 코드가 많아서 유지보수가 어렵다. 그리고 방향을 추가하면 코드 길이가 늘어난다.</code></pre><p>for (let x = 0; x &lt; n; x++) {
    for (let y = 0; y &lt; n; y++) {
        if (board[x][y] === 1) {  // 지뢰 발견
            // 상
            if (x &gt; 0) board[x - 1][y] = -1;
            // 하
            if (x &lt; n - 1) board[x + 1][y] = -1;
            // 좌
            if (y &gt; 0) board[x][y - 1] = -1;
            // 우
            if (y &lt; n - 1) board[x][y + 1] = -1;
            // 좌상
            if (x &gt; 0 &amp;&amp; y &gt; 0) board[x - 1][y - 1] = -1;
            // 우상
            if (x &gt; 0 &amp;&amp; y &lt; n - 1) board[x - 1][y + 1] = -1;
            // 좌하
            if (x &lt; n - 1 &amp;&amp; y &gt; 0) board[x + 1][y - 1] = -1;
            // 우하
            if (x &lt; n - 1 &amp;&amp; y &lt; n - 1) board[x + 1][y + 1] = -1;
        }
    }
}</p>
<pre><code>- **델타 배열을 활용한 8방향 탐색**
모든 방향을 for문 하나로 탐색 가능하기 때문에, 코드가 훨씬 짧고 간결해진다. 또한 방향을 추가하거나 수정할 때 쉽게 수정할 수 있다.</code></pre><p>//델타 배열 정의
const directions = [
    [-1, 0], [1, 0], [0, -1], [0, 1],  // 상, 하, 좌, 우
    [-1, -1], [-1, 1], [1, -1], [1, 1] // 좌상, 우상, 좌하, 우하 (대각선)
];</p>
<p>for (let x = 0; x &lt; n; x++) {
    for (let y = 0; y &lt; n; y++) {
        if (board[x][y] === 1) {  // 지뢰 발견
            for (let [dx, dy] of directions) {  // 8방향 탐색
                let nx = x + dx;
                let ny = y + dy;
                if (nx &gt;= 0 &amp;&amp; nx &lt; n &amp;&amp; ny &gt;= 0 &amp;&amp; ny &lt; n &amp;&amp; board[nx][ny] === 0) {
                    board[nx][ny] = -1;  // 위험지역 표시
                }
            }
        }
    }
}</p>
<p>```</p>
<ul>
<li><strong>정리</strong></li>
</ul>
<table>
<thead>
<tr>
<th>방식</th>
<th>코드 길이</th>
<th>유지보수 용이성</th>
<th>실수 가능성</th>
<th>성능</th>
</tr>
</thead>
<tbody><tr>
<td>직접 구현</td>
<td>길다</td>
<td>어렵다</td>
<td>높다</td>
<td>보통</td>
</tr>
<tr>
<td>델타 배열</td>
<td>짧다</td>
<td>쉽다</td>
<td>낮다</td>
<td>빠르다</td>
</tr>
</tbody></table>
<h2 id="3-정답-코드✅">3. 정답 코드✅</h2>
<h3 id="01-나의-정답">01. 나의 정답</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/a7d65a85-bd31-44d9-8af7-c16e159d1f9c/image.png" alt=""></p>
<blockquote>
<p><a href="https://grit0913.tistory.com/124">델타 배열/탐색 참고 사이트 1</a>
<a href="https://gyyeom.tistory.com/100">델타 배열/탐색 참고 사이트 2</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS, 내 안에 타입있다 2 : TypeScript 심화개념🗼]]></title>
            <link>https://velog.io/@miiing_gaeng/WOW-Sexy-Type-2-TypeScript-%EC%8B%AC%ED%99%94%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@miiing_gaeng/WOW-Sexy-Type-2-TypeScript-%EC%8B%AC%ED%99%94%EA%B0%9C%EB%85%90</guid>
            <pubDate>Fri, 07 Mar 2025 12:28:16 GMT</pubDate>
            <description><![CDATA[<h2 id="1-타입-어노테이션-type-annotation과-타입-추론🕵">1. 타입 어노테이션 Type annotation과 타입 추론🕵</h2>
<h3 id="01-타입-명시-type-annotation이란">01. 타입 명시 Type annotation이란?</h3>
<p><em>annotation</em> 은 &quot;주석&quot;이라는 뜻으로, TypeScript에서 변수나 함수 등에 데이터 타입을 지정하기 위해 주석을 다는 것을 <strong>타입 명시 type annotation</strong>이라고 한다. 즉, 어떤 값이 어떤 타입을 참조하고 있는지 개발자가 직접 타입을 명시하여 TypeScript에게 알려주는 것이다. 한번 타입을 명시하면 해당 타입만 사용할 수 있으며, 다른 타입 사용시 TypeScript 컴파일러가 에러를 던진다.</p>
<ul>
<li><strong>사용법</strong><pre><code>//변수
let name: string = &#39;Owen&#39;;
let age: number = 30;
let isMarried: boolean = false;
</code></pre></li>
</ul>
<p>//배열
let animals: string[] = [&#39;cat&#39;, &#39;dog&#39;, &#39;cow&#39;];</p>
<p>//객체 리터럴
let point: {x: number, y: number} = {
    x: 10,
    y: 20
}</p>
<p>//함수
const logNumber: (i: number) =&gt; void = (i: number) =&gt; {
    console.log(i);
}</p>
<p>//null과 undefined
let haveNothing: null = null;
let nothing: undefined = undefined;</p>
<pre><code>
### 02. 타입 추론이란?
개발자가 타입을 명시적으로 지정하지 않아도, TypeScript 컴파일러가 코드 문맥을 통해 타입을 자동으로 추론해주는 것을 **타입 추론**이라고 한다. 타입 추론은 TypeScirpt의 중요한 기능 중 하나로, 타입 안정성을 유지할 수 있게 한다. 또한, 여러 타입을 동시에 사용할 경우 최적 공통 타입을 알아서 계산해준다.

- **기본 타입 추론**</code></pre><p>//변수 타입 추론
let num = 10; // number로 추론
let str = &quot;Hello&quot;; // string으로 추론
let bool = true; // boolean으로 추론</p>
<p>//배열 타입 추론
let numbers = [1, 2, 3, 4, 5]; // number[]로 추론
let strings = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]; // string[]로 추론</p>
<p>//객체 리터럴 타입 추론
let person = {
    name: &quot;Alice&quot;,
    age: 25
}; // { name: string; age: number }로 추론</p>
<p>person.name = &quot;Bob&quot;; // 정상
person.age = 30; // 정상
person.age = &quot;thirty&quot;; // 오류: &#39;string&#39; 형식은 &#39;number&#39; 형식에 할당할 수 없습니다.</p>
<p>//함수 return값 타입 추론
function add(a: number, b: number) {
    return a + b; // number로 추론
}</p>
<p>const result = add(5, 3); // result는 number로 추론</p>
<pre><code>
- **컨텍스트를 통한 타입 추론**</code></pre><p>//함수의 매개변수를 통한 return값 타입 추론
let add = (a: number, b: number) =&gt; a + b; // (a: number, b: number) =&gt; number로 추론</p>
<p>//콜백함수의 타입 추론
let numbers = [1, 2, 3, 4, 5];
numbers.forEach(num =&gt; {
    console.log(num); // num은 number로 추론
});</p>
<pre><code>
### 03. Type annotation이 필요한 경우
타입 추론에 의존하게 되면 TypeScript를 쓰는 이유인 &quot;안전한 코드 작성&quot;과 멀어질 수 있는 위험이 있다. 그래서 처음 TypeScript를 접했거나 익숙치 않은 상황이라면 타입 어노테이션을 꼼꼼히 해주는 습관이 필요하다.
또한, 타입 추론이 되지 않은 경우도 발생할 수 있다. 그럴 때는 꼭 타입 어노테이션을 작성해주어야 한다.
- 함수가 `any`타입을 return하고, 이 값을 명확하게 해야하는 경우
- 어떤 변수를 선언한 이후에 다른 라인에서 초기화 하는 경우
- 변수가 추론할 수 없는 타입(대입될 값이 일정하지 않거나 여러 타입이 지정)을 가지는 경우

## 2. 제네릭 Generics
### 01. 제네릭 Generics이란?
기본 타입은 항상 고정되어 변하지 않는다. 여기에 약간의 유연성을 더한 유니온 타입이 있지만, 이마저도 프로그래밍 환경에서 발생하는 모든 변수를 대처할 수 없다. 그래서 타입을 고정된 값으로 명시하지 않고, _변수_ 를 통해 유연하게 코딩할 수 있도록 하는 것이 바로 **제네릭 Generics**이다. 즉, 타입을 변수화하여 마치 클래스나 함수 등에서 파라미터처럼 사용하는 것을 뜻한다.

### 02. 제네릭 사용방법 : 기본 문법 &lt; T &gt;
기본적인 문법은 `&lt;&gt;` 안에 제네릭으로 받을 타입의 이름을 넣어주면 된다. 대표적으로 `T`, `U` 등을 많이 사용한다.</code></pre><p>//일반 함수
function toArray<T>(a: T, b: T): T[] {
   return [a, b];
}</p>
<p>//화살표 함수
const toArray2 = <T>(a: T, b: T): T[] =&gt; { ... }</p>
<p>toArray<number>(1, 2); // 숫자형 배열
toArray<string>(&#39;1&#39;, &#39;2&#39;); // 문자형 배열
toArray&lt;string | number&gt;(1, &#39;2&#39;); // 혼합 배열</p>
<p>//TypeScript 타입 추론 기능으로 인해 제네릭을 안써줘도 타입 추론이 가능하다.
toArray(1, 2);
toArray(&#39;1&#39;, &#39;2&#39;);
// 하지만 가끔 자동 타입 추론이 잘안되는 경우가 있기 때문에 직접 제네릭을 선언해야한다.
toArray&lt;string | number&gt;(1, &#39;2&#39;);</p>
<pre><code>
### 03. 제네릭 사용방법 : 제네릭 함수
제네릭 함수를 활용하면 여러 타입을 처리할 수 있다.</code></pre><p>function printAnything<T>(arr: T[]): void {
    for (let i = 0; i &lt; arr.length; i++) {
        console.log(arr[i]);
    }
}</p>
<p>printAnything([&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]); // <string>을 써주지 않아도 타입 추론이 가능
printAnything([1, 2, 3]); // <number>를 써주지 않아도 타입 추론이 가능</p>
<p>```</p>
<h2 id="3-유틸리티-타입-utility-type">3. 유틸리티 타입 Utility type</h2>
<h3 id="01-유틸리티-타입-utility-type-이란">01. 유틸리티 타입 Utility type 이란?</h3>
<p><strong>유틸리티 타입</strong>이란 타입 변환을 용이하게 하기 위한 문법으로, 타입을 통해 간단한 계산을 수행해주는 타입이다. 유틸리티 타입을 쓰면 훨씬 더 간결한 문법으로 타입을 정의할 수 있으며, 특히 이미 정의해 놓은 타입을 변환할 때 유용하다.</p>
<h3 id="02-유틸리티-타입-종류">02. 유틸리티 타입 종류</h3>
<table>
<thead>
<tr>
<th>타입명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Pick&lt;T, K&gt;</td>
<td>T에서 프로퍼티 K의 집합을 선택해 타입을 구성</td>
</tr>
<tr>
<td>Omit&lt;T, K&gt;</td>
<td>T의 모든 프로퍼티에서 K를 제거한 타입을 구성</td>
</tr>
<tr>
<td>Exclude&lt;T, U&gt;</td>
<td>T에서 U에 할당할 수 있는 모든 속성을 제외한 타입을 구성</td>
</tr>
<tr>
<td>Partial&lt; T &gt;</td>
<td>T의 모든 프로퍼티를 선택적으로 만드는 타입을 구성</td>
</tr>
<tr>
<td>ReadOnly&lt; T &gt;</td>
<td>T의 모든 프로퍼티를 읽기 전용(재할당 X)으로 설정한 타입을 구성</td>
</tr>
<tr>
<td>Record&lt;K, T&gt;</td>
<td>타입 T의 프로퍼티의 집합 K로 타입을 구성 <br> 주로 타입의 프로퍼티들을 다른 타입에 매핑 시키는 데 사용</td>
</tr>
<tr>
<td>Extract&lt;T, U&gt;</td>
<td>T에서 U에 할당 할 수 있는 모든 속성을 추출하여 타입을 구성</td>
</tr>
<tr>
<td>ReturnType&lt; T &gt;</td>
<td>T 함수 타입의 반환 타입을 구성, 함수의 반환 타입을 추론하는 데 유용</td>
</tr>
<tr>
<td>Parameters&lt; T &gt;</td>
<td>T 함수 타입의 매개변수 타입을 튜플로 구성</td>
</tr>
<tr>
<td>Awaited&lt; T &gt;</td>
<td>Promise의 결과 타입을 추론<br> 비동기 함수의 반환 타입을 처리하거나 Promise로 감싸진 값을 추출할 때 유용</td>
</tr>
</tbody></table>
<blockquote>
<p><a href="https://www.typescriptlang.org/docs/handbook/typescript-tooling-in-5-minutes.html#type-annotations">타입 어노테이션 TypeScript 공식 문서</a>
<a href="https://woohyun-king.tistory.com/210">타입 어노테이션/타입 추론 참고 사이트</a>
<a href="https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Generic-%ED%83%80%EC%9E%85-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0">제네릭 참고 사이트</a>
<a href="https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0-%ED%83%80%EC%9E%85-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC">유틸리티 타입 참고 사이트</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS, 내 안에 타입있다 1 : TypeScript 기본개념👚]]></title>
            <link>https://velog.io/@miiing_gaeng/WOW-Sexy-Type-1-TypeScript</link>
            <guid>https://velog.io/@miiing_gaeng/WOW-Sexy-Type-1-TypeScript</guid>
            <pubDate>Thu, 06 Mar 2025 15:13:20 GMT</pubDate>
            <description><![CDATA[<h2 id="1-typescript📃">1. TypeScript📃</h2>
<h3 id="01-typescript란">01. TypeScript란?</h3>
<p>TypeScript는 JavaScript의 상위 언어로, JS의 모든 기능과 정적 타입 시스템을 제공한다. 즉, JS처럼 작동하지만, 코드에서 예상치 못한 동작을 감지하여 버그 가능성을 낮출 수 있다. 모던 개발 환경에서 안전한 프로그래밍을 위해 TypeScript는 중요한 언어이다.</p>
<p>*<em>정적 타입 시스템이란?</em> : 프로그램이 실행되기 전에 모든 변수와 표현식의 타입을 확인하고 고정하는 방식</p>
<h3 id="02-javascript-vs-typescript">02. JavaScript vs TypeScript</h3>
<table>
<thead>
<tr>
<th></th>
<th>JavaScript</th>
<th>TypeScript</th>
</tr>
</thead>
<tbody><tr>
<td>타입 시스템</td>
<td>동적</td>
<td>정적</td>
</tr>
<tr>
<td>컴파일</td>
<td>필요X</td>
<td>JavaScript로 컴파일 필요</td>
</tr>
<tr>
<td>개발 경험</td>
<td>유연함, 빠름</td>
<td>코드 완성, 타입 검사 등으로 향상된 개발 경험</td>
</tr>
<tr>
<td>프로젝트 규모</td>
<td>작은 규모에 적합</td>
<td>대규모 프로젝트에 적합</td>
</tr>
<tr>
<td>런타임 오류</td>
<td>런타임에 타입 오류 발견 가능</td>
<td>컴파일 타임에 타입 오류 발견 가능</td>
</tr>
<tr>
<td>학습 곡선</td>
<td>낮음</td>
<td>높음</td>
</tr>
<tr>
<td>도구 지원</td>
<td>광범위한 도구와 라이브러리 지원</td>
<td>JavaScript 생태계의 모든 도구와 라이브러리 지원</td>
</tr>
<tr>
<td>브라우저 지원</td>
<td>모든 브라우저에서 기본적으로 지원</td>
<td>트랜스 파일 된 JavaScript가 모든 브라우저에서 지원</td>
</tr>
</tbody></table>
<p>*<em>컴파일(Compile)이란?</em> : 소스 코드(TypeScript)에서 기계가 이해할 수 있는 코드(JavaScript)로 변환하는 과정</p>
<p>*<em>런타임이란?</em> : 코드가 실행되는 순간. 즉, JS로 변환된 코드가 실제로 동작하는 시점.</p>
<p>*<em>컴파일 타임이란?</em> : 코드를 실행하기 전에 발생하는 시간. 즉, 소스 코드를 JavaScript로 변환하는 시점</p>
<h3 id="03-typescript-동작원리">03. TypeScript 동작원리</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/204b31cd-8eaf-48fc-a83c-9adc4a7c0e61/image.webp" alt=""></p>
<ul>
<li>TypeScript 컴파일러가 코드를 분석 (= 컴파일 타임에 분석)</li>
<li>분석 이후 오류가 없다면 JavaScript 코드로 &quot;모두&quot; 변경</li>
<li>런타임에는 JavaScript와 동일하게 동작</li>
</ul>
<h2 id="2-타입-선언하기📢">2. 타입 선언하기📢</h2>
<h3 id="01-타입이란">01. 타입이란?</h3>
<p>타입이란 값이 가진 프로퍼티와 메서드 등을 참조하는 방법으로, 즉 변수,함수 등이 어떤 속성을 가지고 있는지 표현하는 방법이다.</p>
<h3 id="02-타입-기본-형태와-종류">02. 타입 기본 형태와 종류</h3>
<ul>
<li>기본 형태<pre><code>function someFunc(a: TYPE_A, b: TYPE_B): TYPE_RETURN {
  return a + b;
}</code></pre></li>
</ul>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/63fbbce9-2858-45bb-a010-91e7424381c0/image.webp" alt=""></p>
<ul>
<li>자주 사용하는 타입</li>
</ul>
<table>
<thead>
<tr>
<th>타입명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>boolean</td>
<td>불리언(true 또는 false)</td>
</tr>
<tr>
<td>string</td>
<td>문자열</td>
</tr>
<tr>
<td>number</td>
<td>숫자</td>
</tr>
<tr>
<td>string[] / Array&lt; string &gt;</td>
<td>요소가 문자열인 배열</td>
</tr>
<tr>
<td>number[] / Array&lt; number &gt;</td>
<td>요소가 숫자인 배열</td>
</tr>
<tr>
<td>any[]</td>
<td>요소의 타입이 모두 가능한 배열</td>
</tr>
<tr>
<td>interface</td>
<td>객체 구조</td>
</tr>
<tr>
<td>type (타입 별칭)</td>
<td>객체 구조</td>
</tr>
<tr>
<td>null</td>
<td>null</td>
</tr>
<tr>
<td>undefined</td>
<td>undefined</td>
</tr>
<tr>
<td>any</td>
<td>모든 타입</td>
</tr>
<tr>
<td>void</td>
<td>return값이 없는 함수</td>
</tr>
</tbody></table>
<ul>
<li>여러 타입을 동시에 정의</li>
</ul>
<table>
<thead>
<tr>
<th>타입명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>│</td>
<td>union: OR 연산자, A이거나 B</td>
</tr>
<tr>
<td>&amp;</td>
<td>intersection: AND 연산자, A이면서 동시에 B</td>
</tr>
</tbody></table>
<ul>
<li>기타 타입</li>
</ul>
<table>
<thead>
<tr>
<th>타입명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>readonly</td>
<td>읽기 전용 (수정X)</td>
</tr>
<tr>
<td>tuple</td>
<td>튜플: 길이와 각 요소의 타입이 고정된 배열</td>
</tr>
<tr>
<td>enum</td>
<td>열거형: 관련된 값들의 집합을 정의 (기본: 숫자 0부터 증가 / 그 외는 =로 할당)</td>
</tr>
<tr>
<td>unknown</td>
<td>알 수 없는 타입</td>
</tr>
</tbody></table>
<h3 id="03-interface-vs-type-alias">03. interface vs type alias</h3>
<p><code>interface</code>와 <code>type alias</code>는 매우 유사하다. 그래서 대부분의 경우 자유롭게 선택하여 사용할 수 있다. 하지만 약간의 차이점이 있으니 참고해야한다.</p>
<ul>
<li><strong>확장성</strong> : <code>interface</code>는 항상 확장 가능하지만, <code>type alias</code>는 한 번 정의되면 다시 열어 새로운 속성을 추가할 수 없다.</li>
<li><strong>복잡한 타입 표현</strong> : <code>interface</code>는 객체 형태의 타입을 정의하는 데에 주로 사용하며, <code>type alias</code>는 객체 형태 뿐만 아니라, 유니온 타입, 튜플, 매핑된 타입 등 복잡한 타입 표현에 유리하다.</li>
</ul>
<p><a href="https://stackoverflow.com/questions/37233735/interfaces-vs-types-in-typescript/52682220#52682220">interface vs type alias 관련 사이트</a></p>
<h3 id="04-구조적-타입">04. 구조적 타입</h3>
<p>TypeScript의 타입 시스템은 구조적 타입 시스템으로, 값의 형태와 구조에 따라 타입이 결정된다. 즉, 타입의 실제 선언보다 실질적으로 어떤 프로퍼티와 메서드를 가지고 있는지가 더 중요하다. 구조적 타입 시스템에서는 두 개체가 동일한 구조를 가지면 동일한 타입으로 간주된다.</p>
<pre><code>//예시

interface Person {
    name: string;
    age: number;
}

let p1: Person = { name: &quot;Alice&quot;, age: 25 };
let p2 = { name: &quot;Bob&quot;, age: 30 };

p1 = p2; //true : p2는 Person과 동일한 구조를 가짐</code></pre><p><a href="https://www.typescriptlang.org/">TypeScript 공식 사이트</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[아웃소싱 프로젝트 PINGER) KPT 회고✍️]]></title>
            <link>https://velog.io/@miiing_gaeng/%EC%95%84%EC%9B%83%EC%86%8C%EC%8B%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-PINGER-KPT-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@miiing_gaeng/%EC%95%84%EC%9B%83%EC%86%8C%EC%8B%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-PINGER-KPT-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 05 Mar 2025 09:52:43 GMT</pubDate>
            <description><![CDATA[<h2 id="1-team-project-아웃소싱-프로젝트-pinger📍">1. Team Project-아웃소싱 프로젝트 PINGER📍</h2>
<h3 id="01-프로젝트-컨셉">01. 프로젝트 컨셉</h3>
<p>PINGER(핑거)는 지도를 활용한 혁신적인 구직 플랫폼으로, 공공기관 취업을 준비하는 구직자들을 위해 기획재정부 공공기관 채용정보 API와 카카오 맵 API를 활용하여 실시간 채용 리스트와 위치 정보를 제공합니다.
단순한 채용 정보 제공을 넘어, 구직자는 해당 공고에 맞춰 자기소개서를 작성하고, 멘토의 검토와 피드백을 받을 수 있어 더욱 체계적인 취업 준비가 가능합니다.</p>
<ul>
<li><strong>PING</strong> : 지도 API를 활용해 사용자의 근무 희망지역에 위치한 공고를 한눈에 제공합니다.</li>
<li>work<strong>ER</strong> : 공공기관 채용 데이터를 통해 채용공고 정보를 제공하고, 해당 공고에 자기소개서 작성 및 첨삭이 가능합니다.</li>
</ul>
<h3 id="02-프로젝트-엿보기">02. 프로젝트 엿보기</h3>
<p><a href="https://pinger-one.vercel.app/">PINGER 사이트 보러가기</a>
<a href="https://github.com/lje00220/PINGER">PINGER Github 보러가기</a></p>
<ul>
<li><p>Home
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/c44ba888-d35b-44d8-a96a-2be22178018f/image.png" alt=""></p>
</li>
<li><p>Home / 검색 및 마커 기능
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/aee33b76-2f60-4713-ab41-c02e0238121a/image.png" alt=""></p>
</li>
<li><p>Signup
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/bbdd21a8-5772-42db-9aa8-a7c9cbf37bd5/image.png" alt=""></p>
</li>
<li><p>Signup / 예외처리
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/a30cf978-9eb3-43c2-b1c3-b88accfca7f2/image.gif" alt=""></p>
</li>
<li><p>Login
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/cd383106-eb50-4764-98ff-a5f49d326a9d/image.png" alt=""></p>
</li>
<li><p>Mypage / <code>구직자</code> 버전
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/bf30576f-3c60-47e1-9dd5-3d9c014db24a/image.png" alt=""></p>
</li>
<li><p>Mypage / <code>멘토</code> 버전
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/1cdeca8a-2c3d-4f5a-a433-7ae81b588186/image.png" alt=""></p>
</li>
<li><p>JobList
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/17082b3a-221d-4ae8-9004-5534022ec32b/image.png" alt=""></p>
</li>
<li><p>JobDetail
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/10fc556f-1d5c-42c3-9d74-18bfeee280a5/image.png" alt=""></p>
</li>
<li><p>JobDetail / 댓글 기능
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/411f9cc4-b2eb-4051-bfe1-9f452ce79114/image.png" alt=""></p>
</li>
</ul>
<ul>
<li>ResumeList / <code>구직자</code> 버전
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/043ee445-6082-4319-aa85-b5539365930b/image.png" alt=""></li>
</ul>
<ul>
<li><p>ResumeList / <code>멘토</code> 버전
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/c220df80-27c3-4ad5-a7be-008cde05237e/image.png" alt=""></p>
</li>
<li><p>자기소개서 CRUD 기능 / <code>구직자</code> 버전
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/239ca91c-ad3a-4ddd-9aac-2dfee9011b7e/image.gif" alt=""></p>
</li>
<li><p>자기소개서 검토 기능 / <code>멘토</code> 버전
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/3cc97b4e-5315-418c-8fe5-c80bf1908466/image.gif" alt=""></p>
</li>
</ul>
<h2 id="2-project-kpt-review🗒️">2. Project KPT Review🗒️</h2>
<h3 id="01-개인-회고">01. 개인 회고</h3>
<ul>
<li><p>KEEP : 만족하는 부분
```</p>
</li>
<li><p>supabase auth와 OAuth 기능(트리거 함수 등)을 시도해보았다.</p>
</li>
<li><p>다른 팀원분들의 코드를 통해 깔끔하게 작성하는 방법을 배울 수 있었다.</p>
</li>
<li><p>Zustand를 활용해 auth 전역 상태관리를 구현하였다.</p>
<pre><code></code></pre></li>
<li><p>PROBLEM : 문제가 발생한 부분
```</p>
</li>
<li><p>담당기능 : Signup/Login 페이지</p>
<pre><code>     supabase auth 기능을 활용한 회원가입/로그인 로직 구현
     supabase OAuth 기능을 활용한 소셜 로그인(구글, 카카오)구현</code></pre></li>
</ul>
<p>[기능 부분]</p>
<ul>
<li>소셜 로그인시 meta_data 수정/삽입을 구현하지 못해 오류가 발생했다.</li>
<li>담당 기능에서 필요가 없어 TanStack Query를 활용해보지 못했다.</li>
</ul>
<p>[소통 부분]</p>
<ul>
<li><p>담당한 기능의 마감기한을 잘 지키지 못했다.</p>
<pre><code></code></pre></li>
<li><p>TRY : 개선방안
```
[기능 부분]</p>
</li>
<li><p>소셜 로그인을 구현할 때, 2단계 회원가입을 통해 추가 데이터를 받도록 한다.</p>
</li>
<li><p>이번 프로젝트에서 다른 사람이 짠 코드를 통해 복습하고, 다음 프로젝트때 적극 활용해본다.</p>
</li>
</ul>
<p>[소통 부분]</p>
<ul>
<li>나의 기술적 위치를 파악하고, 소요시간을 미리 예상하여 팀원에게 공유한다.<pre><code></code></pre></li>
</ul>
<h3 id="02-팀-회고">02. 팀 회고</h3>
<ul>
<li><p>KEEP : 만족하는 부분
```</p>
</li>
<li><p>Github ISSUE &amp; PR을 적극적으로 활용하여 협업을 원활하게 진행했다.</p>
</li>
<li><p>공통 컴포넌트(Button, InputBar)를 제작하여 재사용성을 높였다.</p>
</li>
<li><p>기획 단계에서 와이어프레임을 상세하게 설계하여 실제 개발 과정에서 큰 차이가 없었다.</p>
</li>
<li><p>상수를 적극 활용하여 하드코딩을 줄이고 유지보수성을 높였다.</p>
</li>
<li><p>커스텀 훅을 효과적으로 활용하여 코드의 가독성과 재사용성을 향상시켰다.</p>
</li>
<li><p>컨벤션을 철저히 준수하고, JSDoc을 활용하여 코드 가독성을 높였다.</p>
</li>
<li><p>팀원들이 적극적으로 참여하며 원활한 협업을 진행했다.</p>
<pre><code></code></pre></li>
<li><p>PROBLEM : 문제가 발생한 부분
```</p>
</li>
<li><p>도전 기능을 마감하지 못함 : 소셜 로그인 및 맵 API의 다양한 기능을 적용하지 못했다.</p>
</li>
<li><p>마감 기한을 명확하게 설정하지 않음 : 일정이 명확하지 않아 우선순위 조정이 어려웠다.</p>
</li>
<li><p>배포 전략 브랜치 없이 <code>main</code>에서 직접 배포를 진행했다.</p>
</li>
<li><p>배포 전 오류 확인 과정이 부족하여 예상치 못한 이슈 발생했다.</p>
</li>
<li><p>중복되는 fetch 함수가 많았음 : 공통화하지 못해 유지보수성이 낮아졌다.</p>
</li>
<li><p>UX 개선 필요 : 정렬, 무한스크롤 등의 기능 필요, 멘토와 구직자의 구분이 명확하지 않았다.</p>
</li>
<li><p>프로젝트 진행 중 새롭게 생기는 기획 이슈에 대한 역할 분담이 제대로 이뤄지지 않았다.</p>
<pre><code></code></pre></li>
<li><p>TRY : 개선방안
```</p>
</li>
<li><p>기능별 마감 기한을 설정하여 일정 관리의 명확성을 높인다.</p>
</li>
<li><p>중간 배포 테스트를 진행하여 배포 과정에서 발생할 수 있는 오류를 사전에 점검한다.</p>
</li>
<li><p>테스트 기간을 설정하여 UX 피드백을 반영하고, 정렬 및 무한스크롤 등의 기능을 보완한다.</p>
</li>
<li><p>팀 스크럼에서 세부적인 역할과 진척도를 공유하여 기획 변경에도 유연하게 대응한다.</p>
</li>
<li><p>API 명세서를 작성하여 데이터 활용 로직을 통일하고, fetch 함수의 공통화를 진행한다.</p>
<pre><code></code></pre></li>
</ul>
<h3 id="03-튜터님의-피드백">03. 튜터님의 피드백</h3>
<ul>
<li>활발한 commit, ISSUE &amp; PR 활용이 좋았다. 팀 협업의 증거!</li>
<li>ISSUE과 PR에 <code>#아이디</code> 를 적극 활용해보자.</li>
<li>채용리스트는 infiniteQuery를 사용해서 무한 스크롤 구현을 했지만 자기소개서는 구현이 안되어 아쉽다. 나중에 리팩토링 해보자.</li>
<li>constant를 활용한 부분이 좋았다. 지역명도 여러군데 사용되는데, 이것도 상수로 리팩토링 해보자.</li>
<li>폴더 구조와 주석 활용 좋다.</li>
<li>추후에 채용 정보 API를 구해서 프로젝트 발전시켜보자.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[아웃소싱 프로젝트 PINGER) TroubleShooting 2 : OAuth 로그인과 meta_data📍]]></title>
            <link>https://velog.io/@miiing_gaeng/%EC%95%84%EC%9B%83%EC%86%8C%EC%8B%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-PINGER-TroubleShooting-2-OAuth-%EB%A1%9C%EA%B7%B8%EC%9D%B8%EA%B3%BC-metadata</link>
            <guid>https://velog.io/@miiing_gaeng/%EC%95%84%EC%9B%83%EC%86%8C%EC%8B%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-PINGER-TroubleShooting-2-OAuth-%EB%A1%9C%EA%B7%B8%EC%9D%B8%EA%B3%BC-metadata</guid>
            <pubDate>Tue, 04 Mar 2025 15:59:02 GMT</pubDate>
            <description><![CDATA[<h2 id="1-문제-발생🤯">1. 문제 발생🤯</h2>
<h3 id="01-나-구글로-로그인할래">01. 나 구글로 로그인할래</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/923acda8-dbea-4695-96d2-02a4ff188384/image.png" alt=""></p>
<p>이번 팀프로젝트에서 로그인/회원가입 기능을 담당하면서, 꼭 구현해보고 싶었던 부분이 바로 <strong>소셜 로그인</strong>이었다. 구글이나 카카오를 통해 로그인이 되면 사용자 수가 폭발적으로 증가한다는 기사가 어렴풋이 기억이 났고, 최종 프로젝트때 무조건 구현해야 할 것 같다는 느낌에 미리 예습하는 기분으로 도전해보았다.
다행히 정리가 잘 된 글들이 많아서 차근차근 따라해보았고, 기본 설정까지 막힘없이 해낼 수 있었다. Login 페이지에서 사용할 소셜 로그인 함수를 만들고, 소셜 버튼에 props로 내려주어 테스트를 해보았다. 우선 어떤 data가 찍힐지 알아보고자 console에 찍어보았다.</p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/2cc07f36-40d4-408c-91c2-61ef62c0c7b5/image.png" alt=""></p>
<p>응? 왜 console이 깨끗하죠? 분명 console.log로 data를 찍어주었는데, 콘솔창이 깨끗하다. 오류도 아니고, 아무것도 안찍힌 적은 처음이라 당황스러웠다.</p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/1a2ced43-95f0-4a9b-a616-bf97ccc7eabd/image.gif" alt=""></p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/877f7593-5151-454e-88a3-2d0f3e599399/image.png" alt=""></p>
<p>빛났다 사라지는 data... 도대체 왜 이러는걸까...</p>
<h3 id="02-meta_data가-뭐에요">02. meta_data가 뭐에요?</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/b68b3e61-7456-4b9b-8e17-ce67ef4db4f0/image.png" alt=""></p>
<p>지난번의 교훈 덕분에 로그를 들여다보면서 문제점을 찾는 방법을 잘 활용하고 있다. 이번에도 안될 때, 무조건 supabase의 로그로 찾아가 문제점이 뭔지 확인했다. public.users에 값이 제대로 들어가지 않았던 것이었다.
trigger 함수도 변경해보고, column 설정도 변경해보면서 여차저차 로그인까지는 구현이 되었다. 하지만, nickname, address, role의 값이 다 null로 들어가지는 문제가 또 발생했다.</p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/e18dd5b7-f619-4f44-9eb4-cfa73c4d59e0/image.png" alt=""></p>
<p>supabase의 <code>auth</code> schema에서 확인을 해보니, <code>raw_user_meta_data</code>에 nickname, address, role이 아예 들어가지 않았다. 그래서 단순하게 &#39;다시 추가해주지 뭐!&#39; 라고 생각했는데, 이때는 몰랐다... 이게 기나긴 여정의 시작이 될지🥲</p>
<h2 id="2-개념-정리🧐">2. 개념 정리🧐</h2>
<h3 id="00-supabase-공식문서">00. supabase 공식문서</h3>
<blockquote>
<p><a href="https://supabase.com/docs/guides/auth/social-login/auth-google?queryGroups=platform&amp;platform=web&amp;queryGroups=environment&amp;environment=client&amp;queryGroups=framework&amp;framework=nextjs">supabase 구글 로그인 공식문서</a></p>
</blockquote>
<h3 id="01-supabase-oauth--구글-로그인-구현하기">01. supabase OAuth : 구글 로그인 구현하기</h3>
<ul>
<li>** <a href="https://console.cloud.google.com/welcome/new?project=final-project-410604&amp;inv=1&amp;invt=AbrI9A">Google Cloud</a> 및 supabae 설정**</li>
</ul>
<ol>
<li><p>프로젝트 생성
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/8afb1919-49d2-4949-8396-cf559ef1d2be/image.png" alt=""></p>
</li>
<li><p><code>사용자 인증 정보</code> 탭에서 클라이언트 ID 만들기
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/2428e97f-7a55-4b2a-be9b-620fcd379bfd/image.png" alt=""></p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/e6fdb124-f6c2-448b-aa6d-b16af6393bba/image.png" alt=""></p>
<ol start="3">
<li><p>웹 애플리케이션 선택
<code>승인된 자바스크립트 원본</code> : 현재 쓰고 있는 port ( <code>http://localhost:5173</code>)
<code>승인된 리디렉션 URI</code> : supabase google provider의 <code>Callback URL</code>
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/60651ac7-c8d7-4272-a961-5b904eb3e607/image.png" alt=""></p>
</li>
<li><p>supabase provider 설정
<code>Client IDs</code> : 구글 클라이언트 ID
<code>Client Secret</code> : 구글 클라이언트 보안 비밀번호
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/b11ba80e-4fab-427b-bc0e-d5ceaf79370e/image.png" alt=""></p>
</li>
</ol>
<ul>
<li><p><strong>코드</strong></p>
<pre><code>//google 소셜 로그인
export const googleLogin = async () =&gt; {
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: &#39;google&#39;,
  options: {
    persistSession: true,
    queryParams: {
      access_type: &#39;offline&#39;,
      prompt: &#39;consent&#39;,
    },
    redirectTo: PATH.SOCIAL_LOGIN_REDIRECT_URL,
  },
});

if (error) {
  toast.error(AUTH_ERROR_MESSAGES.LOGIN.FAIL);
  console.error(&#39;구글 로그인 error : &#39;, error);
  return;
}

if (data) {
  console.log(data)
}
};</code></pre></li>
</ul>
<h3 id="02-supabase-oauth--카카오-로그인-구현하기">02. supabase OAuth : 카카오 로그인 구현하기</h3>
<ul>
<li><a href="https://developers.kakao.com/">Kakao Developer</a> 및 supabase 설정</li>
</ul>
<ol>
<li><p>카카오 개발자센터 앱 등록
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/15e1418d-9936-48d4-b52a-e47cb779b3d7/image.png" alt=""></p>
</li>
<li><p>카카오 로그인 <code>활성화</code>, <code>OpenId Connect 활성화</code> 모두 ON
<code>Redirect URI</code> : supabase kakao provider의 <code>Callback URL</code>
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/4d2a63cb-177e-48ba-99fe-7b5b1dd0e190/image.png" alt=""></p>
</li>
<li><p>동의항목 설정하기 전, 개인정보 동의항목 심사신청 (사업자 정보 등록 안해도 가능)</p>
</li>
<li><p>비즈앱 전환 - 앱 대표 이미지 등록 - 개인 개발자 비즈 앱 전환 항목 선택(이메일 인증 필요) - 동의항목 설정
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/9e485c2b-bac0-42da-86b0-73e743604398/image.png" alt=""></p>
</li>
<li><p><code>Client Secret</code>와 <code>REST API 키</code> 복사
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/4e22cc64-1c50-4213-8061-b35c7b9eb1a1/image.png" alt=""></p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/2f4f5197-c7ef-429c-9000-6557be4542d0/image.png" alt=""></p>
<ol start="6">
<li>supabase provider 설정
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/08c6d0c9-97bf-4a12-929f-4e266b0d3828/image.png" alt=""></li>
</ol>
<ul>
<li><p>코드</p>
<pre><code>//구글 로그인과 로직이 같다. provider만 변경해주면 된다.
//kakao 소셜 로그인
export const googleLogin = async () =&gt; {
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: &#39;kakao&#39;,
  options: {
    persistSession: true,
    queryParams: {
      access_type: &#39;offline&#39;,
      prompt: &#39;consent&#39;,
    },
    redirectTo: PATH.SOCIAL_LOGIN_REDIRECT_URL,
  },
});

if (error) {
  toast.error(AUTH_ERROR_MESSAGES.LOGIN.FAIL);
  console.error(&#39;카카오 로그인 error : &#39;, error);
  return;
}

if (data) {
  console.log(data)
}
};</code></pre></li>
</ul>
<p><a href="https://velog.io/@yubnee/%EC%B5%9C%EC%A2%85%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-3%EC%9D%BC%EC%B0%A8">소셜 로그인 참고 사이트 1</a>
<a href="https://mingos-habitat.tistory.com/108#%ED%95%A8%EC%88%98%EC%99%80-%ED%8A%B8%EB%A6%AC%EA%B1%B0-%EC%84%A4%EC%A0%95-%F0%9F%93%96">소셜 로그인 참고 사이트 2</a></p>
<h2 id="3-해결-방안😇">3. 해결 방안😇</h2>
<h3 id="01-publicusers의-column-수정">01. public.users의 column 수정</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/408f84cd-c156-4b7b-a3e7-d7f00e90b701/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/9baf5106-c628-4e34-9700-a5d73c854d6b/image.png" alt=""></p>
<p>우선 data.user에 nickname, address, role이라는 키값이 존재하지 않기 때문에 테이블에는 null로 들어간다는 사실을 알았다. 그래서 찜찜하지만 Allow Nullable을 on해주었다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/804394d2-e2f4-48a4-9bfc-70d75f1d2152/image.png" alt=""></p>
<p>그랬더니 갑자기 작동 되는 소셜로그인! 하지만 왜 이름이 undefined?</p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/ecd9be59-d5c7-4bdf-afca-c645b4395d4e/image.png" alt=""></p>
<p>localStorage를 확인해보니 이메일과 id만 zustand로 넘어왔네...</p>
<h3 id="02-소셜-로그인-로직에서-데이터-upsertupdate">02. 소셜 로그인 로직에서 데이터 upsert/update</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/d9c89ded-5e3e-45ac-a808-1ca38188b8ce/image.png" alt=""></p>
<p>그래도 다행히 소셜 로그인까지는 된다! 그럼 로그인이 완료되는 시점에서 public.users에 nickname, address, role의 값을 upsert/update해주는 로직을 넣어 null값이 들어가지 않도록 구현해보고자 했다.</p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/9ed5b32e-9e7f-40b4-9597-4ab5f0ba5397/image.png" alt=""></p>
<p>응 어림도 없지~ 로직을 분리도 해봤다가, useEffect도 써봤다가 별짓을 다했지만 public.users의 <code>NULL</code>은 꿈쩍도 하지 않았다. 그래서 튜터님께 도움을 요청했는데, 결국 다른 방법으로 해결해야할 것 같다는 슬픈 피드백을 듣게 되었다.</p>
<h3 id="03-2단계-회원가입">03. 2단계 회원가입</h3>
<p>튜터님께서 추천해주신 방법은 2단계 회원가입이었다. 우선 소셜로그인으로 로그인까지 구현이 되면 리디렉션을 2단계 회원가입 페이지로 연결해주고, 그 페이지에서 nickname, address, role 값을 받아 처리하도록 하는 것이었다.
그래도 방법을 찾았으니 해결을 하면 된다. 다른 팀원분들이 중간에 합류해서 함께 해결해보고자 했지만....</p>
<h2 id="4-결과😎">4. 결과😎</h2>
<h3 id="01-미해결">01. 미해결!</h3>
<p>개발 블로그 사상 첫 미해결 트러블 슈팅! 다른 팀원분들까지 문제 해결에 나섰지만, 대공사가 필요하다고 판단되었다. 내일이 마감이라 결국 해결하지 못한 채로 우선 마무리지었고, 추후에 마지막 해결방안으로 구현해볼 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MBTI 테스트 사이트) KPT 회고✍️]]></title>
            <link>https://velog.io/@miiing_gaeng/MBTI-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%82%AC%EC%9D%B4%ED%8A%B8-KPT-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@miiing_gaeng/MBTI-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%82%AC%EC%9D%B4%ED%8A%B8-KPT-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 28 Feb 2025 17:11:17 GMT</pubDate>
            <description><![CDATA[<h2 id="1-project---mbti-테스트-사이트📇">1. Project - MBTI 테스트 사이트📇</h2>
<h3 id="01-완성-사이트-엿보기">01. 완성 사이트 엿보기</h3>
<p><a href="https://mbti-test-site.vercel.app/">MBTI 사이트 바로가기</a></p>
<ul>
<li><p>Home
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/41eb0927-b6a5-450e-bc04-0d727d93880b/image.png" alt=""></p>
</li>
<li><p>Test
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/63be793a-f262-459b-9e69-83f6942f0c85/image.png" alt=""></p>
</li>
<li><p>MyPage
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/37c4f8aa-748a-41ae-a129-defc00f89c5a/image.png" alt=""></p>
</li>
<li><p>Results
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/5ab4d639-d0b6-4cab-966c-4b31f89b5161/image.png" alt=""></p>
</li>
<li><p>MyResult
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/67ffc399-65fb-452f-95d6-c79cdd610bb7/image.png" alt="">
⬆️ 본인 계정이면 공유하기 버튼 표시</p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/0ca72241-69d9-42a1-b58a-f50cbe367dc2/image.png" alt="">
⬆️ 로그아웃/타 계정이면 테스트 버튼 표시</p>
</li>
<li><p>SignUp
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/c8c32be5-fcb5-4d9c-b810-ed8e18681606/image.png" alt=""></p>
</li>
<li><p>Login
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/f8b8e68e-3559-4568-b437-f853713464e8/image.png" alt=""></p>
</li>
<li><p>반응형</p>
</li>
</ul>
<table>
<thead>
<tr>
<th>제목</th>
<th>이미지</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/miiing_gaeng/post/cffc58d9-686c-4c43-83db-af610520a40f/image.png" width="200"> Home</td>
<td><img src="https://velog.velcdn.com/images/miiing_gaeng/post/4569b67a-5950-48f1-aafd-edd3f43850ff/image.png" width="200"> Test</td>
</tr>
<tr>
<td><img src="https://velog.velcdn.com/images/miiing_gaeng/post/2918a287-fc65-4f91-8c69-7eaceee8c5c3/image.png" width="200">  Results</td>
<td><img src="https://velog.velcdn.com/images/miiing_gaeng/post/a3ec41b1-53cb-44df-94ae-7e34e21146d1/image.png" width="200"> MyPage</td>
</tr>
</tbody></table>
<h2 id="2-project-kpt-review🗒️">2. Project KPT Review🗒️</h2>
<h3 id="01-개인-회고">01. 개인 회고</h3>
<ul>
<li>KEEP : 만족하는 부분
```</li>
<li>클라이언트 상태관리와 서버상태 관리에 대한 개념을 배웠다.</li>
<li>zustand, tanstack query 등 새로운 라이브러리를 많이 접할 수 있었다.</li>
<li>README를 열심히 작성하였다.
```</li>
<li>PROBLEM : 문제가 발생한 부분
```
[기능부분]</li>
<li>라이브러리에 대한 이해도가 많이 부족하였다.</li>
</ul>
<p>[기타]</p>
<ul>
<li>강의 내용 등에서 참고한 코드를 그대로 따라치려고 했다.</li>
<li>과제가 너무 하기 싫었다...
```</li>
<li>TRY : 개선방안
```
[기능부분]</li>
<li>라이브러리 공식문서를 많이 보자!</li>
</ul>
<p>[기타]</p>
<ul>
<li>라이브러리를 체득할 때까지 연습하자!</li>
<li>&quot;뭐 어쩌겠어. 그래도 해야지&quot; 마인드 함양!<pre><code></code></pre></li>
</ul>
<h3 id="02-튜터님의-피드백">02. 튜터님의 피드백</h3>
<ul>
<li>대부분의 라이브러리는 공식 문서를 제공하며, 가장 신뢰할 수 있는 정보이다.</li>
<li>라이브러리 공식 문서의 Quick Start 가이드나 API Reference 섹션을 자주 참고하면서 기능을 확인하며 시작해보자.</li>
<li>작은 단위의 예제부터 따라하면서, 하나씩 응용해가며 이해도를 높이자.</li>
<li>라이브러리가 어떤 문제를 해결하기 위해 만들어졌는지 파악하며, 라이브러리의 필요성을 직접 체감해보자.</li>
<li>TanStack Query를 사용하면 클라이언트 상태/서버 상태를 명확하게 구분할 수 있다. 기존 <code>useEffect</code>에서 데이터를 가져오고 <code>useState</code>로 관리하던 부분을 TanStackQuery의 <code>useQuery</code>로 리팩토링해보자.</li>
<li>데이터 조회 로직(queries)와 데이터 변경 로직(mutations)를 분리하여 폴더를 구성하면 좋다.</li>
<li>개발용 서버와 배포용 서버의 API URL이 다른 점을 Vite의 내장 상수인 <code>import.meta.env.PROD</code>를 활용해보자. <code>import.meta.env.PROD</code>는 프로덕션 모드(배포한 사이트)에서 true를 반환하므로, 삼항 연산자를 사용하여 API URL을 동적으로 관리할 수 있다. </li>
<li>프로필 페이지에서 닉네임을 변경할 때, 기존 닉네임을 미리 채워 넣으면 UX를 개선할 수 있다. 또한, 닉네임 변경사항이 없을 경우 클라이언트에서 검증을 해줘도 좋다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[아웃소싱 프로젝트 PINGER) TroubleShooting 1 : supabase auth와 트리거 함수📍]]></title>
            <link>https://velog.io/@miiing_gaeng/%EC%95%84%EC%9B%83%EC%86%8C%EC%8B%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-PINGER-TroubleShooting-1-supabase-auth%EC%99%80-%ED%8A%B8%EB%A6%AC%EA%B1%B0-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@miiing_gaeng/%EC%95%84%EC%9B%83%EC%86%8C%EC%8B%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-PINGER-TroubleShooting-1-supabase-auth%EC%99%80-%ED%8A%B8%EB%A6%AC%EA%B1%B0-%ED%95%A8%EC%88%98</guid>
            <pubDate>Thu, 27 Feb 2025 16:20:49 GMT</pubDate>
            <description><![CDATA[<h2 id="1-문제-발생🤯">1. 문제 발생🤯</h2>
<h3 id="01-onchange-작동이-왜-안될까">01. onChange 작동이 왜 안될까</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/7caa44dd-1620-4412-9e27-c1eea6f3e7a8/image.gif" alt=""></p>
<p>이번 프로젝트에서 담당한 부분은 로그인/회원가입이다. 지난번 게시글, 댓글 CRUD를 구현했기 때문에, 이번에는 로그인/회원가입을 담당했다. 사실 오늘 회원가입/로그인 로직을 모두 구현하고, 내일까지 zustand를 활용해서 AuthContext 구현하는 것이 목표였는데, 회원가입 로직에서부터 막혀서 계획이 전부 틀어졌다.
처음 맞닥뜨린 문제는 input의 값이 formData로 set되지 않은 것이다. 위의 이미지에서 보이다시피 input에 값을 넣고 있지만 formData는 꿈쩍도 하지 않았다.</p>
<h3 id="02-500-error--database-error-saving-new-user">02. 500 error : Database error saving new user</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/37c4ae7f-0d4f-4548-9cca-c1c8cae70ed0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/46ee01d8-7a44-45c9-8616-95b4d67f7e14/image.png" alt=""></p>
<p>어찌저찌 input 값을 formData로 불러오는 것에 성공했다. 하지만 더 큰 시련이 다가왔으니... <code>supabase.auth.signUp()</code>으로 데이터를 넣어보려고 했지만 500 에러와 함께 null로 가득한 data 객체가 돌아왔다. 내가 데이터를 잘못 넣었거나 트리거 설정이 잘못된 것 같은데, 어찌 할 수가 없었다. React, JS도 겨우 찌끄리는 내가 SQL, 트리거를 잘 알리가 없지 않은가... 겨우겨우 해결한 방법을 더 까먹기 전에 정리해보고자 한다.</p>
<h2 id="2-개념-정리🧐">2. 개념 정리🧐</h2>
<p>우선 개념을 정리하기 전에 정말 많이 도움 받았던 <a href="https://ramincoding.tistory.com/entry/React-Supabase-%EC%9D%B4%EB%A9%94%EC%9D%BC-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EB%B0%8F-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-%ED%8A%B8%EB%A6%AC%EA%B1%B0-%EC%84%A4%EC%A0%95">참고 사이트</a>가 있어 미리 공유한다. SQL문에 대한 지식이 1도 없는 나에게 트리거 설정에 있어 한줄기 빛이 되어주었다. 이 자리를 빌어 감사의 인사를 드린다.</p>
<h3 id="01-supabase-로그인회원가입-로직-구현하기">01. supabase 로그인/회원가입 로직 구현하기</h3>
<p>supabase에서 회원가입 로직을 구현하면, 유저의 정보는 기본적으로 <code>Auth</code> schema의 <code>users</code> 테이블에 저장된다. auth.users는 이메일, 비밀번호, 전화번호, 사용자의 UUID 등이 저장되는데, 이외의 추가 정보(닉네임, 프로필 이미지, 주소 등)을 넣기 위해선 <code>Public</code> schema에 별도 테이블을 추가해주어야 한다. 그리고 테이블간의 관계를 형성하기 위해 특정 column값을 FK로 연결해주고, 한쪽 테이블에 정보가 추가되면 다른 테이블도 자동 추가되도록 trigger를 설정해야한다.</p>
<ul>
<li><em>trigger란?</em> : 특정 테이블에 INSERT, DELETE, UPDATE 같은 DML 문이 수행되었을 때, 데이터베이스에서 자동으로 동작하도록 작성된 프로그램</li>
</ul>
<p>사실 SQL에 대한 기본 지식조차 없었기에, 구글링과 chatGPT에게 도움을 많이 받아 아래의 코드를 작성하였다. 그래서 이 코드가 어떻게 사용되는지 전혀 설명할 수 없다. 추후에 기회가 된다면 SQL에 대해 공부해보도록 하겠다.</p>
<ul>
<li>trigger<pre><code>DO $$ 
BEGIN 
IF NOT EXISTS (
  SELECT 1 FROM information_schema.triggers WHERE trigger_name = &#39;on_auth_user_created&#39;
) THEN
  create trigger on_auth_user_created
after insert on auth.users
for each row execute function public.handle_create_public_user();
END IF;
END $$;
</code></pre></li>
</ul>
<pre><code>
- trigger function : `handle_create_public_user`</code></pre><p>BEGIN
  INSERT INTO public.users(user_id, created_at, nickname, address, email, role)
  VALUES (
  NEW.id,
  NEW.created_at,
  NEW.raw_user_meta_data-&gt;&gt;&#39;nickname&#39;,
  NEW.raw_user_meta_data-&gt;&gt;&#39;address&#39;,
  NEW.email,
  NEW.raw_user_meta_data-&gt;&gt;&#39;role&#39;
)
ON CONFLICT (user_id) DO NOTHING;
RETURN NEW;
END;</p>
<pre><code>
## 3. 해결 방안😇
### 01. Input 컴포넌트 props 설정
사실 첫번째 문제는 나의 멍청한 실수였다. InputBar는 재사용성이 있다고 판단하여 공통 컴포넌트로 빼놓았는데, `name`과 `value`, `onChange`를 props로 내려주지 않아서 발생한 문제였다.
![](https://velog.velcdn.com/images/miiing_gaeng/post/bd9ec3fb-50e2-4a43-8277-7b4227d7b50f/image.png)

보이는가, 저 조촐한 props가... `type`과 `placeholder`만 내려주고 중요한 props는 전혀 전달이 되고 있지 않았다.
![](https://velog.velcdn.com/images/miiing_gaeng/post/3069c71c-7023-4e68-85ea-4bd64f9f651d/image.png)

props를 다시 적용해주고, 확인해보았다.
![](https://velog.velcdn.com/images/miiing_gaeng/post/ce7838d0-05db-4c6f-b4da-07c3987a1fd7/image.gif)

input값이 formData로 잘 들어가고, 유효성 검사까지도 잘 구현되었다.

### 02. trigger 함수 수정
500에러는 튜터님의 도움으로 겨우 해결할 수 있었다. 우선 supabase의 로그를 꼼꼼히 확인해보았다. console.log에 찍히는 error 메세지는 한계가 있어서 어디가 문제인지 스스로 판단이 안되었다. 다행히 supabase에서 Auth에 대한 로그를 세세하게 제공하고 있었다. 이점을 튜터님께서 알려주셨고, 뒤늦게나마 로그를 천천히 다시 살펴볼 수 있었다.

*_로그 보는 법_ : 우측 메뉴바의 `Logs` - COLLECTIONS에 다양한 로그를 확인할 수 있다. 이번 문제는 회원가입 로직에서 발생한 문제였기 때문에 `Auth`의 로그를 확인하였다.
![](https://velog.velcdn.com/images/miiing_gaeng/post/6f3e11ca-439c-4ecd-859f-b4a4783201b1/image.png)

public.users의 role 타입에 대한 문제였다. 테이블을 세팅할 때, user의 role은 &#39;구직자&#39;와 &#39;채용담당자&#39; 뿐이서 두개 값만 존재하는 타입을 생성하여 넣어주었다. 하지만 여기서 뭔가 값에 충돌이 발생한 것으로 추측되었다.
![](https://velog.velcdn.com/images/miiing_gaeng/post/236fab56-a0ec-4d18-9497-8a9b5ad53474/image.png)

그래서 다시 text 타입으로 변환해준 뒤, 다시 시도해보았다. 하지만 실패!

![](https://velog.velcdn.com/images/miiing_gaeng/post/f619fb7e-4ce2-4cfd-b303-13579ee03cef/image.png)

다시 로그를 확인해보니 트리거 함수에서 문제가 있었다. auth.users의 `id`와 public.users의 `user_id`가 FK로 묶여있는 상황이었는데, 이 사이에서 어떤 값을 넣고 어떤 값을 설정하는지 혼동이 있었던 것이다.
![](https://velog.velcdn.com/images/miiing_gaeng/post/26360a66-a659-40ed-ab58-b4a5b18d1941/image.png)

`handle_create_public_user` 트리거 함수를 위와 같이 수정해주었다.

## 4. 결과😎
### 01. 해결!
![](https://velog.velcdn.com/images/miiing_gaeng/post/dbb03dc3-7401-4b01-90c7-1d380efa8a5e/image.png)

![](https://velog.velcdn.com/images/miiing_gaeng/post/be40cbe0-d5c7-4a7d-b0d9-a3415aef6ab1/image.png)

auth.users와 public.users가 잘 연동되는 것을 확인할 수 있었다!

### 02. 최종코드
- InputBar.jsx
![](https://velog.velcdn.com/images/miiing_gaeng/post/d1d08ae9-fc29-4461-8dcd-ac6bbe1f94b4/image.png)


- supabase trigger 함수
![](https://velog.velcdn.com/images/miiing_gaeng/post/f98bea74-56e2-41c5-93c4-2ca0f1e7da6d/image.png)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[포켓몬 도감 사이트) KPT 회고✍️]]></title>
            <link>https://velog.io/@miiing_gaeng/%ED%8F%AC%EC%BC%93%EB%AA%AC-%EB%8F%84%EA%B0%90-%EC%82%AC%EC%9D%B4%ED%8A%B8-KPT-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@miiing_gaeng/%ED%8F%AC%EC%BC%93%EB%AA%AC-%EB%8F%84%EA%B0%90-%EC%82%AC%EC%9D%B4%ED%8A%B8-KPT-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 26 Feb 2025 14:07:18 GMT</pubDate>
            <description><![CDATA[<h2 id="1-project---포켓몬-도감-사이트😼">1. Project - 포켓몬 도감 사이트😼</h2>
<h3 id="01-완성-사이트-엿보기">01. 완성 사이트 엿보기</h3>
<p><a href="https://pokemon-five-swart.vercel.app/">포켓몬 사이트 바로가기</a></p>
<ul>
<li><p>Home
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/f373e16c-184f-4dc1-9416-3fc57aaf86a3/image.png" alt=""></p>
</li>
<li><p>Dex
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/eb98d862-d247-40eb-b207-bd80fafa0d18/image.png" alt=""></p>
</li>
<li><p>Detail
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/58aa6d4c-2094-44eb-9c8b-5a8fb98fa504/image.png" alt=""></p>
</li>
<li><p>Card
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/5eaa3aa7-affd-435c-b048-be664eec5d46/image.gif" alt=""></p>
</li>
<li><p>Toast
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/5fb81fd3-6bd0-4df9-a845-44a430af9695/image.gif" alt=""></p>
</li>
</ul>
<h2 id="2-project-kpt-review🗒️">2. Project KPT Review🗒️</h2>
<h3 id="01-개인-회고">01. 개인 회고</h3>
<ul>
<li><p>KEEP : 만족하는 부분
```</p>
</li>
<li><p>도전 기능(RTK 리팩토링, 디테일 페이지 버튼 구현 등)을 모두 구현해냈다.</p>
</li>
<li><p>애니메이션 등 다양한 CSS 기능과 styled-components 라이브러리를 시도해보았다.</p>
</li>
<li><p>트러블슈팅과 README를 열심히 작성하였다.</p>
<pre><code></code></pre></li>
<li><p>PROBLEM : 문제가 발생한 부분
```
[기능부분]</p>
</li>
<li><p>애니메이션 구현시 약간 어색하게 작동했다. (카드 전체가 회전되지 않고, 내부만 회전됨)</p>
</li>
<li><p>&quot;포켓몬 수&quot;에서 하드코딩된 부분이 있었다.</p>
</li>
</ul>
<p>[기타]</p>
<ul>
<li><p>새로운 문법, 특히 RTK 리팩토링을 적용하기 전에 지레 겁을 먹고 할 일을 미뤘다.</p>
<pre><code></code></pre></li>
<li><p>TRY : 개선방안
```
[기능부분]</p>
</li>
<li><p>CSS 애니메이션에 대한 학습을 필히 해보자!</p>
</li>
<li><p>하드코딩을 줄이기 위해 constants 등을 활용해보자!</p>
</li>
</ul>
<p>[기타]</p>
<ul>
<li>뭐든 처음이다. 너무 겁먹지 말고, &quot;막상 해보니 별거 아니네!&quot; 마인드를 잊지 말자!<pre><code></code></pre></li>
</ul>
<h3 id="02-튜터님의-피드백">02. 튜터님의 피드백</h3>
<ul>
<li>React 17버전 이상부터는 컴포넌트 모듈에 React를 import 하지 않아도 된다. 즉, 불필요한 코드이므로 제거하자!</li>
<li>Redux의 리듀서에서는 상태를 변경하는 로직만 있는 것이 좋다. (선호도 차이 O)</li>
<li>유효성 검사는 이벤트가 발생하는 UI에서 작성되는 것이 가독성 측면에서 좋다. (선호도 차이 O)</li>
<li>스타일드 컴포넌트를 따로 관리하고, 메인 컬러와 포인트 컬러를 정한 것은 좋다.</li>
<li>DashBoard 컴포넌트와 dexListSlice에서 <code>최대 포켓몬의 수</code>를 매직 넘버로 사용하고 있다. 해당 숫자에 <code>maxPokemon</code> 또는 <code>MAX_POKEMON</code>과 같이 이름을 지어주고, 여러 곳에서 사용이 된다면 export 시켜서 여러 모듈에서 공유하는 것이 좋다.</li>
</ul>
<p>*<em>매직넘버란?</em> : 정확한 뜻을 밝히지 않고 소스 코드 안에 직접 숫자 값을 넣는 것. </p>
<ul>
<li>PokemonList 컴포넌트에서 MOCK_DATA에 대한 이름을 지어주기 위해 변수에 할당을 해주었는데, MOCK_DATA는 export default로 내보냈기 때문에, import 할 때 바로 pokemonList로 불러와도 된다.
(export default로 내보낸 것은 import할 때 이름을 변경할 수 있다!)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[MBTI 테스트 사이트) TroubleShooting 1 : Tailwind CSS📇]]></title>
            <link>https://velog.io/@miiing_gaeng/MBTI-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%8E%98%EC%9D%B4%EC%A7%80-TroubleShooting-1-Tailwind-CSS</link>
            <guid>https://velog.io/@miiing_gaeng/MBTI-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%8E%98%EC%9D%B4%EC%A7%80-TroubleShooting-1-Tailwind-CSS</guid>
            <pubDate>Tue, 25 Feb 2025 06:02:49 GMT</pubDate>
            <description><![CDATA[<h2 id="1-문제-발생🤯">1. 문제 발생🤯</h2>
<h3 id="01-tailwind-css를-사용한-컴포넌트-디자인">01. tailwind CSS를 사용한 컴포넌트 디자인</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/3c88a9bd-69cb-4d50-97f1-04d2e10355da/image.png" alt=""></p>
<p>이번 프로젝트에서는 styled-components가 아닌 심화주차에서 배운 Tailwind CSS로 UI 스타일링을 해야한다. 다행히 강의 내용과 발제 문서에 Tailwind CSS를 사용하는 방법에 대해 친절하게 설명되어있어, 그대로 따라해보았다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/618de4b2-08bb-4d0c-870b-1a9d1d583a13/image.png" alt=""></p>
<p><a href="https://tailwindcss.com/">Tailwind CSS 공식 문서</a>에서 문법들을 확인하고 한번 적용해보았다. 따로 컴포넌트를 만들어서 import하고... 이름 바꿔서 넣어주고... prefix 넣어주고... 등등 복잡한 과정 없이도 간편하게 CSS를 적용할 수 있다니! 완전 럭키비키잖아🥹</p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/79b4300c-566a-4247-902b-6660dd44cb48/image.png" alt=""></p>
<p>럭키비키가 아니라 언럭키비키였네...🤬 신나서 적용 다 하고 확인해보니 CSS가 하나도 적용되지 않았다. 어디서부터 문제였을까...</p>
<h2 id="2-개념-정리🧐">2. 개념 정리🧐</h2>
<h3 id="01-tailwind-css란">01. Tailwind CSS란?</h3>
<p>Tailwind CSS는 유틸리티 클래스 기반의 CSS 프레임워크로, class 속성을 통해 HTML에 직접 스타일을 적용한다. 즉, 추가적인 CSS 파일이나 코드가 필요 없다. Tailwind CSS는 불필요한 스타일이 제거되어 속도나 성능 측면에서 좋고, React의 JSX 문법과 함께 사용하기 편리하다. 또한, 기본설정을 커스터마이징할 수 있어, 프로젝트에 맞는 디자인 시스템을 구축할 수 있다.</p>
<p>*<em>유틸리티 클래스란?</em> : 주로 특정한 목적을 위해 메서드를 제공하는 클래스</p>
<h3 id="02-tailwind-css-설치방법">02. tailwind CSS 설치방법</h3>
<pre><code>yarn add tailwindcss@3.3.5 postcss autoprefixer
npx tailwindcss init -p</code></pre><p>npx를 실행하고 나서 <code>tailwind.config.js</code>파일과 <code>postcss.config.js</code> 파일이 생성되는지 확인해야한다. 그리고나서, <code>tailwind.config.js</code>, <code>index.css</code>를 아래와 같이 수정한다.</p>
<pre><code>//tailwind.config.js

/** @type {import(&#39;tailwindcss&#39;).Config} */
export default {
  content: [&quot;./index.html&quot;, &quot;./src/**/*.{js,ts,jsx,tsx}&quot;],
  theme: {
    extend: {}
  },
  plugins: []
};

----------------------
//index.css
@tailwind base;
@tailwind components;
@tailwind utilities;</code></pre><h3 id="03-tailwind-css-적용방법">03. Tailwind CSS 적용방법</h3>
<p>Tailwind css는 HTML 태그의 클래스 속성에 직접 적용하는 것이 기본적이다.</p>
<pre><code>&lt;div className=&quot;w-12 h-12 text-white bg-gray-800&quot;&gt;&lt;/div&gt;</code></pre><ul>
<li><p><strong>크기</strong>
<code>w-*</code>,<code>h-*</code> : Tailwind에서 미리 정해준 값을 적용. 기본적으로 4px 단위로 증가한다.
<code>w-1/2</code>,<code>h-full</code> : 비율 설정
<code>w-[10px]</code>,<code>h-[2rem]</code> : 단위 높이를 지정
<code>text-lg, xl, 2xl...</code> : 텍스트 크기</p>
</li>
<li><p><strong>색상</strong>
<code>text-{color name}-number</code> : 텍스트 색상
<code>bg-{color name}-number</code> : 배경 색상</p>
<pre><code>&lt;div className=&quot;w-10 h-10 bg-indigo-100&quot;&gt;
  &lt;p className=&quot;text-sm text-indigo-800&quot;&gt;테스트입니다&lt;/p&gt;
&lt;/div&gt;</code></pre></li>
<li><p><strong>반응형</strong>
<code>sm:{...}</code> : @media (min-width: 640px) { ... }
<code>md:{...}</code> : @media (min-width: 768px) { ... }
<code>lg:{...}</code> : @media (min-width: 1024px) { ... }
<code>xl:{...}</code> : @media (min-width: 1280px) { ... }
<code>2xl:{...}</code> : @media (min-width: 1536px) { ... }</p>
<pre><code>&lt;div className=&quot;w-screen flex flex-col md:flex-row md:justify-center pt-10&quot;&gt;
//기본적으로 세로방향(flex-col)이지만, md(768px)이상부터 가로방향(md:flex-row)</code></pre></li>
<li><p><strong>hover(마우스오버) 이벤트</strong>
<code>hover:{...}</code> : 마우스를 올렸을 때 상태 변화</p>
<pre><code>&lt;button className=&quot;bg-indigo-100 text-white hover:bg-indigo-800&quot;&gt;Click me!&lt;/button&gt;</code></pre></li>
</ul>
<p>이 외의 속성들은 공식 문서에서 찾을 수 있다. 공식 문서를 꼭 확인하자.</p>
<h3 id="04-vscode-익스텐션---tailwind-css-intellisense">04. VSCode 익스텐션 - Tailwind CSS IntelliSense</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/619701ed-e066-45ee-93c5-7cd6e2fedb22/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/06a82a8f-c7f8-4a43-834f-10e5cdfc182d/image.png" alt=""></p>
<p>Tailwind CSS IntelliSense 확장 프로그램을 설치하면 유틸리티 클래스가 자동완성된다. 너무 편리하니, Tailwind CSS를 사용한다면 설치할 것을 권장한다!</p>
<p><a href="https://tailwindcss.com/">Tailwind CSS 공식 문서</a>
<a href="https://www.daleseo.com/tailwind/">Tailwind CSS 참고 사이트 1</a>
<a href="https://velog.io/@ldlldl/Tailwind-CSS-%EC%A0%95%EB%A6%AC#1-%EA%B8%B0%EB%B3%B8-%EC%82%AC%EC%9A%A9%EB%B2%95">Tailwind CSS 참고 사이트 2</a></p>
<h2 id="3-해결-방안😇">3. 해결 방안😇</h2>
<h3 id="01-chatgpt-너-아웃">01. chatGPT, 너 아웃!</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/8415f60b-45c0-408c-9eff-69d9890f1754/image.png" alt=""></p>
<p>튜터님께 도움을 요청하기 전에 혼자서 해결해보기 위해 chatGPT의 도움을 받았다. node_modules를 전부 삭제한 다음 다시 설치도 해보고, import가 제대로 되었는지, 오타는 없는지 꼼꼼히 확인했는데... 계속 되는 시도에도 CSS는 꿈쩍도 하지 않아서 chatGPT에게 거듭 물어보았고, 그 중 위의 방법대로 <code>vite.config.js</code>을 수정한 다음 계속해서 삭제-재설치를 반복해보았다. 그래도 요지부동이어서 결국 튜터님께 도움을 받기로...
튜터님께서도 Tailwind CSS 설정은 이상이 없는데, 속성이 적용 안되는 것에 의문을 가지셨다. 그래서 튜터님의 코드와 비교해보았더니 다른 점이 딱 1군데에 있었다. 바로 chatGPT가 수정하라고 알려준 <code>vite.config.js</code> 파일이었다. postcss 어쩌구저쩌구를 삭제해보고 다시 확인해보았다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/1042c35f-1fa7-4714-80ba-82cd1666d526/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/ef550416-0953-411c-96d7-5dca0c83c8f1/image.png" alt=""></p>
<p>이게 왜 되지..? node_modules를 삭제하고 재설치하는 과정에서 이미 문제는 해결되었는데, <code>vite.config.js</code> 파일 설정 때문에 오히려 안되고 있었던 것이었다. 너만 믿고 있었는데 이렇게 배신을 하다니... chatGPT, 너 아웃.</p>
<h3 id="01-1-tailwind-css는-못말려">01-1. Tailwind CSS는 못말려</h3>
<p>드디어 Tailwind CSS가 정상적으로 적용이 되었다. 그럼 이제 세심한 디자인을 만져볼 차례다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/4011e627-dc12-4dcb-915b-dd09141ac83e/image.png" alt=""></p>
<p>Home 중앙에 위치한 아이템의 height 값을 늘려주고 싶어서 <code>h-150</code>이란 값을 주고, 얼마나 커졌는지 확인해보았다.</p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/a32bd6ac-8cc5-4210-9eb9-d50082e45bc0/image.png" alt=""></p>
<p>응..? 왜 오히려 작아졌을까?😨 상식적으로 72보다 150이 큰데, 2배만큼 길어진 것도 아니고 오히려 작아졌다.</p>
<h3 id="02-tailwind-css의-클래스-범위">02. Tailwind CSS의 클래스 범위</h3>
<p>Tailwind CSS는 클래스의 범위가 정해져있다. 높이 클래스는 <code>h-0</code>부터 <code>h-96</code>까지만 제공되고, 그 이상의 크기는 <code>h-screen</code>, <code>h-full</code> 유틸리티를 사용하거나 <code>h-[100px]</code>와 같이 직접 높이를 지정해야한다.
나의 경우에는 <code>h-150</code>이라는 값 자체가 존재하지 않기 때문에, 적용이 안된 것이었다. <code>h-*</code>은 기본적으로 4px 단위로 증가하기 때문에, 나는 대략 384px 정도 되도록 <code>h-96</code>으로 지정해주었다.</p>
<ul>
<li>자주 쓰이는 <code>h-*</code> 값 변환표</li>
</ul>
<table>
<thead>
<tr>
<th>Tailwind 클래스</th>
<th>px 값</th>
</tr>
</thead>
<tbody><tr>
<td><code>h-1</code></td>
<td>4px</td>
</tr>
<tr>
<td><code>h-2</code></td>
<td>8px</td>
</tr>
<tr>
<td><code>h-10</code></td>
<td>40px</td>
</tr>
<tr>
<td><code>h-20</code></td>
<td>80px</td>
</tr>
<tr>
<td><code>h-40</code></td>
<td>160px</td>
</tr>
<tr>
<td><code>h-72</code></td>
<td>288px</td>
</tr>
<tr>
<td><code>h-96</code></td>
<td>384px</td>
</tr>
</tbody></table>
<h2 id="4-결과😎">4. 결과😎</h2>
<h3 id="01-해결">01. 해결!</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/ef80268a-1270-4df0-ab77-4c2bc2c423d9/image.png" alt=""></p>
<h3 id="02-최종-코드">02. 최종 코드</h3>
<ul>
<li>Home.jsx
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/fe08e13d-85b7-46ac-affc-b6bef96c325e/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터 fetch 싹싹김치 part. 2 : fetch와 axios🥬]]></title>
            <link>https://velog.io/@miiing_gaeng/%EB%8D%B0%EC%9D%B4%ED%84%B0-fetch-%EC%8B%B9%EC%8B%B9%EA%B9%80%EC%B9%98-part.-2-fetch%EC%99%80-axios</link>
            <guid>https://velog.io/@miiing_gaeng/%EB%8D%B0%EC%9D%B4%ED%84%B0-fetch-%EC%8B%B9%EC%8B%B9%EA%B9%80%EC%B9%98-part.-2-fetch%EC%99%80-axios</guid>
            <pubDate>Mon, 24 Feb 2025 15:41:24 GMT</pubDate>
            <description><![CDATA[<h2 id="1-fetch-api📬">1. fetch API📬</h2>
<h3 id="01-fetch-api란">01. fetch API란?</h3>
<p>Fetch API는 HTTP 파이프라인을 구성하는 요청과 응답 등 요소를 JavaScript에서 접근/조작할 수 있도록 한다. 즉, 전역 <code>fetch()</code> 메서드를 활용해 네트워크의 리소스를 비동기적으로 가져올 수 있다. <code>fetch()</code>는 브라우저 window 객체에 소속되어 있기 때문에 <code>window.fetch()</code>로 사용되기도 하며, 구식 브라우저에서 지원하지 않는다.(폴리필을 쓰면 사용이 가능하다.)</p>
<h3 id="02-사용방법">02. 사용방법</h3>
<p><code>fetch()</code>에는 HTTP 요청을 전송할 URL, HTTP 요청 메서드, HTTP 요청 헤더, 페이로드 등을 설정한 객체를 전달한다.<code>fetch()</code>는 2개의 인자를 받는다.</p>
<ul>
<li><code>url</code> : 접근하고자 하는 URL</li>
<li><code>options</code> : 선택 매개변수, method나 headers 등을 지정
<code>options</code>이 없다면 GET 요청으로 진행된다.<pre><code>const promise = fetch(url, options)</code></pre></li>
</ul>
<h3 id="03fetch-api로-http-요청하기">03.Fetch API로 HTTP 요청하기</h3>
<ul>
<li><p><strong>GET</strong> : 존재하는 데이터 요청 <code>read</code>
단순히 원격 API에 존재하는 데이터를 가져올 때 사용한다. <code>fetch()</code>는 기본적으로 GET 방식으로 작동하고, <code>options</code>가 필요 없다.</p>
<pre><code>fetch(&quot;https://jsonplaceholder.typicode.com/posts/1&quot;)
.then((response) =&gt; response.json())
.then((data) =&gt; console.log(data))</code></pre></li>
<li><p><strong>POST</strong> : 새로운 데이터 생성 요청 <code>create</code>
form/input 태그 등을 사용해 데이터를 생성하거나 비밀번호 등 개인정보를 보낼 때 사용한다. <code>options</code>의 method를 POST로 지정하고, headers로 JSON 포맷을 사용한다고 명시해야한다. 그리고, body에 요청 전문을 JSON 포맷으로 넣어준다.</p>
<pre><code>fetch(&quot;https://jsonplaceholder.typicode.com/posts&quot;, {
method: &quot;POST&quot;,
headers: {
  &quot;Content-Type&quot;: &quot;application/json&quot;,
},
body: JSON.stringify({
  title: &quot;Test&quot;,
  body: &quot;I am testing!&quot;,
  userId: 1,
}),
})
.then((response) =&gt; response.json())
.then((data) =&gt; console.log(data))</code></pre></li>
<li><p><strong>PUT</strong> : 존재하는 데이터 변경 요청 <code>update</code>
원격 API의 데이터를 수정하기 위해 사용한다. <code>options</code>의 method를 PUT으로 설정하고, 나머지 포맷은 POST와 비슷하다.</p>
<pre><code>fetch(&quot;https://jsonplaceholder.typicode.com/posts&quot;, {
method: &quot;PUT&quot;,
headers: {
  &quot;Content-Type&quot;: &quot;application/json&quot;,
},
body: JSON.stringify({
  title: &quot;Test&quot;,
  body: &quot;I am testing!&quot;,
  userId: 1,
}),
})
.then((response) =&gt; response.json())
.then((data) =&gt; console.log(data))</code></pre></li>
<li><p><strong>DELETE</strong> : 존재하는 데이터 삭제 요청 <code>delete</code>
원격 API의 데이터를 삭제하기 위해 사용한다. 보낼 데이터가 없기 때문에 headers, body가 따로 필요없으며, method만 DELETE로 설정하면 된다.</p>
<pre><code>fetch(&quot;https://jsonplaceholder.typicode.com/posts/1&quot;, {
method: &quot;DELETE&quot;,
})
.then((response) =&gt; response.json())
.then((data) =&gt; console.log(data))</code></pre></li>
</ul>
<h2 id="2-axios">2. axios</h2>
<h3 id="01-axios란">01. axios란?</h3>
<p>axios는 Promise API를 활용하는 HTTP 비동기 통신 라이브러리로, 백엔드와 프론트엔드가 통신을 쉽게하기 위해 Ajax와 더불어 사용한다.</p>
<p><strong>axios의 장점</strong></p>
<ul>
<li>요청 응답을 자동으로 JSON 형태로 만든다. 즉, <code>res.json()</code>의 과정이 필요없다.</li>
<li>fetch API는 네트워크 오류만 catch한다면, axios는 대부분의 오류를 catch할 수 있다.</li>
<li>기본 설정(custom instance)나 intercepter 등 다양한 기능을 제공한다.</li>
</ul>
<h3 id="02-설치방법사용방법">02. 설치방법/사용방법</h3>
<ul>
<li><strong>설치방법</strong>
axios는 써드파티 라이브러리로 설치가 필요하다.<pre><code>npm install axios   // npm 설치
yarn add axios      // yarn 설치</code></pre></li>
<li><strong>사용방법</strong><pre><code>axios({
url: &#39;접근할 주소&#39;,
method: &#39;메소드 종류(get, post 등)&#39;,
data: {
  데이터 제목: &#39;데이터 내용&#39;
}
});</code></pre></li>
<li><strong>단축 메서드</strong><pre><code>axios.get(url[, config])
</code></pre></li>
</ul>
<p>axios.delete(url[, config])</p>
<p>axios.post(url[, data[, config]])</p>
<p>axios.put(url[, data[, config]])</p>
<p>axios.patch(url[, data[, config]])</p>
<pre><code>
### 03. axios로 HTTP 요청하기
- **GET** : 존재하는 데이터 요청 `read`
위에서 언급했다시피, 요청 응답을 자동으로 JSON 형태로 만들기 때문에 구조분해 할당으로 data를 사용할 수 있다.</code></pre><p>const getList = async () =&gt; {
    const { data } = await axios.get(&quot;요청할 주소&quot;)
    console.log(data)
}</p>
<pre><code>
- **POST** : 새로운 데이터 생성 요청 `create`</code></pre><p>const postList = async () =&gt; {
   try {
       const { data } = await axios.post(&quot;요청할 주소&quot;, {
         headers: {
           &#39;Content-type&#39;: &#39;application/json&#39;,
           &#39;Accept&#39;: &#39;application/json&#39;
         }
       })
       console.log(data);
   } catch (error) {
       console.log(error);
   }
}</p>
<pre><code>
- **PUT/PATCH** : 존재하는 데이터 변경 요청 `update`
PUT은 보낸 내용으로 데이터 전체를 덮어쓰지만, PATCH는 보낸 내용으로 데이터의 일부를 변경한다. 일반적으로 데이터의 안정성을 위해 PATCH를 사용한다.</code></pre><p>axios.put(&quot;요청할 주소&quot;, &quot;덮어쓸 값&quot;, config)
axios.patch(&quot;요청할 주소&quot;, &quot;일부만 바꿀 값&quot;, config)</p>
<p>const updateList = async (newData) =&gt; {
    const response = await axios.patch(&quot;요청할 주소&quot;, newData, {
        headers: {
              &quot;Content-Type&quot;: &quot;multipart/form-data&quot;
        }
    });</p>
<pre><code>return response</code></pre><p>};</p>
<pre><code>
- **DELETE** : 존재하는 데이터 삭제 요청 `delete`</code></pre><p>const deleteList = async (listId) =&gt; {
    const { data } = await axios.delete(<code>요청할 주소/${listId}</code>)
    console.log(data);
}</p>
<p>```</p>
<h3 id="04-axios-vs-fetch-api">04. axios vs Fetch API</h3>
<table>
<thead>
<tr>
<th>특징</th>
<th>axios</th>
<th>fetch</th>
</tr>
</thead>
<tbody><tr>
<td>설치 필요</td>
<td>써드파티 라이브러리로 설치 필요</td>
<td>모던 브라우저 빌트인이라 설치 필요 없음</td>
</tr>
<tr>
<td>XSRF 보호</td>
<td>해준다</td>
<td>별도 보호 없음</td>
</tr>
<tr>
<td>요청 시 사용 속성</td>
<td>data 속성 사용</td>
<td>body 속성 사용</td>
</tr>
<tr>
<td>데이터 처리 방식</td>
<td>data는 object를 포함</td>
<td>body는 문자열화 되어있다</td>
</tr>
<tr>
<td>성공 응답 판단</td>
<td>status가 200이고 statusText가 &#39;OK&#39;이면 성공</td>
<td>응답객체가 ok 속성을 포함하면 성공</td>
</tr>
<tr>
<td>데이터 형식 변환</td>
<td>자동으로 JSON 데이터 형식으로 변환됨</td>
<td>.json() 메서드를 사용해야 함</td>
</tr>
<tr>
<td>요청 취소 및 타임아웃 설정</td>
<td>요청을 취소할 수 있고 타임아웃 설정 가능</td>
<td>해당 기능 존재하지 않음</td>
</tr>
<tr>
<td>HTTP 요청 가로챔</td>
<td>가능</td>
<td>기본적으로 제공하지 않음</td>
</tr>
<tr>
<td>다운로드 지원</td>
<td>기본적인 지원 있음</td>
<td>지원하지 않음</td>
</tr>
<tr>
<td>브라우저 지원</td>
<td>더 많은 브라우저에 지원됨</td>
<td>Chrome 42+, Firefox 39+, Edge 14+, Safari 10.1+ 이상에 지원</td>
</tr>
</tbody></table>
<p><a href="https://developer.mozilla.org/ko/docs/Web/API/Fetch_API/Using_Fetch">fetch API Mdn</a>
<a href="https://ko.javascript.info/fetch">fetch API 모던 자바스크립트</a>
<a href="https://www.daleseo.com/js-window-fetch/">fetch API 참고 사이트 1</a>
<a href="https://velog.io/@eunjin/JavaScript-fetch-%ED%95%A8%EC%88%98-%EC%93%B0%EB%8A%94-%EB%B2%95-fetch-%ED%95%A8%EC%88%98%EB%A1%9C-HTTP-%EC%9A%94%EC%B2%AD%ED%95%98%EB%8A%94-%EB%B2%95">fetch API 참고 사이트 2</a>
<a href="https://axios-http.com/kr/docs/intro">axios 공식 문서</a>
<a href="https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-%EC%84%A4%EC%B9%98-%EC%82%AC%EC%9A%A9">axios 참고 사이트 1</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터 fetch 싹싹김치 part. 1 : 동기와 비동기🥬]]></title>
            <link>https://velog.io/@miiing_gaeng/%EB%8D%B0%EC%9D%B4%ED%84%B0-fetch-%EC%8B%B9%EC%8B%B9%EA%B9%80%EC%B9%98-%EB%8F%99%EA%B8%B0%EC%99%80-%EB%B9%84%EB%8F%99%EA%B8%B0</link>
            <guid>https://velog.io/@miiing_gaeng/%EB%8D%B0%EC%9D%B4%ED%84%B0-fetch-%EC%8B%B9%EC%8B%B9%EA%B9%80%EC%B9%98-%EB%8F%99%EA%B8%B0%EC%99%80-%EB%B9%84%EB%8F%99%EA%B8%B0</guid>
            <pubDate>Fri, 21 Feb 2025 15:11:57 GMT</pubDate>
            <description><![CDATA[<h2 id="1-동기-vs-비동기☕️">1. 동기 vs 비동기☕️</h2>
<h3 id="01-동기synchronous란">01. 동기(Synchronous)란?</h3>
<ul>
<li>직렬적 수행 : 요청과 응답이 순차적으로 이뤄지는 방식이다.</li>
<li>시스템 효율의 저하 : 응답을 기다리는 동안 다음 작업은 대기해야 한다.</li>
<li>일의 순서가 중요한 경우 동기 처리를 한다. (for 루프 등)</li>
</ul>
<h3 id="02-비동기asynchronous란">02. 비동기(Asynchronous)란?</h3>
<ul>
<li>병렬적 수행 : 요청을 보내고 응답의 여부와 상관없이 다음 작업이 이뤄진다.</li>
<li>효율적인 처리 : 먼저 요청한 응답을 기다리지 않고 바로 다음 작업을 진행한다.</li>
<li>일의 순서가 중요하지 않은 경우 비동기 처리를 한다. (대부분의 JS 로직)</li>
</ul>
<h3 id="03-비동기-처리--콜백-함수와-콜백-지옥">03. 비동기 처리 : 콜백 함수와 콜백 지옥</h3>
<ul>
<li><strong>비동기 처리와 콜백 함수(Callback Function)</strong>
JavaScript에서 비동기 처리를 구현하는 기본적인 방법으로, <code>콜백 함수</code>가 있다. <code>콜백 함수</code>란, 함수의 인자로 전달되는 함수로 특정 작업이 완료된 후 호출된다. 즉, 함수의 비동기 작업이 완료된 후 콜백 함수가 실행되어 비동기 작업의 결과를 처리한다.</li>
<li><strong>콜백 지옥</strong>
비동기 처리를 위해 콜백 함수를 여러개 중첩하여 사용하면, 오히려 코드가 복잡해지고 가독성이 떨어진다. 이를 콜백 지옥(Callback Hell)이라고 한다. 콜백 지옥은 유지보수가 어려워지고, 순차적 처리나 병렬 처리를 구현하기 복잡하다는 단점이 있다.
이를 보완하고자 ES6의 Promise가 등장하였다.</li>
</ul>
<h2 id="2-promise🤙">2. Promise🤙</h2>
<h3 id="01-promise란">01. Promise란?</h3>
<p><code>Promise</code>는 &#39;약속&#39;이란 의미처럼, 비동기 작업의 진행 상태를 관리하는 &quot;<em>객체</em> &quot;이다. <code>Promise</code>는 비동기 작업의 상태를 관리하고, 체이닝을 통한 비동기 작업 처리를 하기 때문에, 콜백 함수의 단점을 보완한다.</p>
<ul>
<li><strong><code>Promise()</code> 생성자</strong>
Promise를 지원하지 않는 함수를 감쌀 때 사용한다. 인자로 <code>resolve</code>와 <code>reject</code>를 받아, 성공 혹은 실패에 대한 로직을 수행한다.<pre><code>const 이름 = new Promise((resolve, reject) =&gt; {콜백 함수 로직})
</code></pre></li>
</ul>
<p>//예시
const promise1 = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; {
    resolve(&quot;비동기로 처리해봅시다&quot;);
  }, 3000);
});</p>
<pre><code>
### 02. Promise의 상태
- `pending` : 대기, 비동기 처리 로직이 아직 완료되지 않은 상태
- `fulfilled` : 이행, 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
- `rejected` : 실패, 비동기 처리가 실패하거나 오류가 발생한 상태

### 03. Promise의 메서드
Promise는 후속 처리 메소드를 체이닝(chaining)하여 여러 Promise를 연결한다. 이를 통해 콜백 지옥을 해결한다.
- `then()` : Promise가 이행(fulfilled) 되었을 때 실행되는 로직. 이전 Promise의 결과를 받아 새로운 값을 반환하거나, 새로운 Promise를 반환할 수 있다.
- `catch()` : Promise가 실패(rejected) 되었을 때 실행되는 로직. 이전 then()에서 발생한 오류를 잡아 처리할 수 있다.
- `finally()` : Promise의 성공/실패 여부와 관계없이 무조건 실행되는 로직</code></pre><p>fetch(&quot;<a href="https://jsonplaceholder.typicode.com/users/1&quot;">https://jsonplaceholder.typicode.com/users/1&quot;</a>)
  .then((response) =&gt; response.json()) // ✅응답을 JSON으로 변환
  .then((data) =&gt; console.log(&quot;사용자 데이터:&quot;, data)) // ✅JSON 데이터 출력
  .catch((error) =&gt; console.error(&quot;오류 발생:&quot;, error)) // ❌오류 처리
  .finally(() =&gt; console.log(&quot;요청 완료!&quot;)); // ❓성공/실패와 관계없이 실행</p>
<pre><code>
### 04. 비동기 작업의 병렬 처리 : Promise.all()
`Promise.all()`은 여러 개의 Promise들을 병렬적으로 실행하여 처리하는 메서드로, 다음 로직을 계속 실행하기 전에 서로 연관된 비동기 작업 여러 개가 모두 이행되어야할 때 사용한다. 즉, 내부 Promise들은 실행 순서와 상관없이 각각 진행되지만, `Promise.all()`은 모든 Promise가 완료될 때까지 기다린다.
인자로 여러 Promise를 담은 배열을 받으며, 인자로 들어온 Promise 중 하나라도 rejected가 되면 `Promise.all()`은 즉시 거부된다.
- **사용 방법**</code></pre><p>const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) =&gt; {
  setTimeout(resolve, 100, &quot;foo&quot;);
});</p>
<p>Promise.all([promise1, promise2, promise3]).then((values) =&gt; {
  console.log(values);
});
// [3, 42, &quot;foo&quot;]</p>
<pre><code>
### 05. async/await
- **async/await란?**
`async/await`는 JavaScript 비동기 처리 문법 중 가장 최근에 나온 문법으로, 기존 콜백 함수와 Promise의 단점을 보완하고, 개발자 친화적인 문법을 제공한다.

- **async**
`async`는 함수(function) 앞에 사용하는 키워드로, `async` 함수는 항상 Promise를 반환한다. 즉, 해당 함수의 return 값이 Promise가 아니어도 Promise로 감싸진 값이 반환된다.

- **await**
`await`는 `async 함수` 안에서만 동작하며, Promise가 처리될 때까지 함수 실행을 기다리게 하는 키워드이다. `await`가 붙은 Promise가 처리되면 그 결과값과 함께 다음 로직이 실행된다.
기존 Promise의 `then`과 `catch`를 체이닝하여 사용하는 문법보다 `await`를 사용한 문법이 가독성이 좋다.

- **try...catch**
`try...catch`는 `await`가 던진 에러를 잡는 문법이다. `async/await`를 사용하면 `await`가 대기를 처리해주기 때문에 `then`이 필요하지 않고, `catch` 대신 `try..catch`를 사용할 수 있다.

- **사용 방법**</code></pre><p>async function 함수명() {
    await 비동기_처리_메서드_명();
};</p>
<p>//예시
async function fetchData () {
    const response = await fetch(&quot;<a href="https://jsonplaceholder.typicode.com/&quot;">https://jsonplaceholder.typicode.com/&quot;</a>)
    const data = await response.json()</p>
<pre><code>return data</code></pre><p>}</p>
<pre><code>- **async/await의 한계**
`async/await`는 Promise 체이닝보다 가독성이 좋지만, `Promise.all()`처럼 병렬적으로 처리해야 하는 경우에는 await를 연속으로 사용하면 오히려 성능이 저하될 수 있다.</code></pre><p>// 병렬 실행이 아님 (비효율적)
async function fetchUsers() {
  const user = await fetch(&quot;/user&quot;);
  const posts = await fetch(&quot;/posts&quot;);<br>  return { user, posts };
}</p>
<p>// Promise.all()을 사용하여 병렬 실행 (더 효율적)
async function fetchUsersParallel() {
  const [user, posts] = await Promise.all([
    fetch(&quot;/user&quot;),
    fetch(&quot;/posts&quot;)
  ]);
  return { user, posts };
}</p>
<pre><code>
[동기/비동기 참고 사이트 1](https://f-lab.kr/insight/javascript-async-handling-20240724?gad_source=1&amp;gclid=CjwKCAiA5eC9BhAuEiwA3CKwQviRXBrK9cgMrKaYaArUqWjCQzkGxbezRHvnhKyxTsV_xLmISrWtZRoCV38QAvD_BwE)
[동기/비동기 참고 사이트 2](https://velog.io/@khy226/%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0%EB%9E%80-Promise-asyncawait-%EA%B0%9C%EB%85%90#promise-%EC%B2%B4%EC%9D%B4%EB%8B%9D)
[Promise 참고 사이트 1](https://ko.javascript.info/promise-chaining)
[Promise 참고 사이트 2](https://joshua1988.github.io/web-development/javascript/promise-for-beginners/#%ED%94%84%EB%A1%9C%EB%AF%B8%EC%8A%A4%EC%9D%98-3%EA%B0%80%EC%A7%80-%EC%83%81%ED%83%9Cstates)
[async/await 참고 사이트 1](https://javascript.info/async-await)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Hook Series 5 : useMemo와 memoization📝]]></title>
            <link>https://velog.io/@miiing_gaeng/Hook-Series-5-useMemo%EC%99%80-memoization</link>
            <guid>https://velog.io/@miiing_gaeng/Hook-Series-5-useMemo%EC%99%80-memoization</guid>
            <pubDate>Thu, 20 Feb 2025 12:56:39 GMT</pubDate>
            <description><![CDATA[<h2 id="1-memoization🗃️">1. memoization🗃️</h2>
<h3 id="01-memoization이란">01. memoization이란?</h3>
<p>memoization은 React 컴포넌트의 렌더링 성능을 최적화하는 기술로, 이전에 계산한 값을 메모리에 저장(캐싱 caching)하여 동일한 계산을 반복하거나 컴포넌트의 불필요한 리렌더링을 방지한다. 즉, 동일한 계산을 요구할 때, 계산을 다시 하는 것이 아닌 이전 결과를 재사용하여 성능을 높인다.</p>
<p>*<em>캐시 Cache란?</em> : 데이터나 값을 미리 복사해 놓는 임시 장소. 데이터에 접근하는 시간이 오래 걸리는 경우나 값을 다시 계산하는 시간을 절약하고 싶은 경우에 사용하는 임시 저장 공간으로, 이를 사용해 값을 임시로 저장해두는 것을 &quot;<em>캐싱 caching</em>&quot;이라고 한다.</p>
<h3 id="02-memoization을-적용할-때">02. memoization을 적용할 때</h3>
<ul>
<li>대규모 데이터를 처리하는 테이블</li>
<li>복잡한 계산을 수행하는 대시보드</li>
<li>다수의 자식 컴포넌트를 포함하는 리스트</li>
</ul>
<p>그 외에도 쓰로틀링, 디바운싱 등 컴포넌트가 리렌더링 될 때 동일한 함수를 재생성을 방지해야하거나 자식 컴포넌트의 불필요한 렌더링을 방지하여 성능을 최적화해야할 때 사용한다.</p>
<h2 id="2-usememo--react의-memoization-api🔖">2. useMemo : React의 memoization API🔖</h2>
<h3 id="01-reactmemo">01. React.memo</h3>
<p><code>React.memo</code>는 <strong>함수형 컴포넌트</strong>의 렌더링 결과를 메모이징하는데 사용한다. 즉, props의 변경이 없다면 이전 렌더링의 결과를 재사용한다. 이는 다수의 자식 컴포넌트가 있는 경우, 자식 컴포넌트까지 리렌더링을 하지 않아도 되어 성능을 향상시킬 수 있다. props가 변경되지 않으면 리렌더링을 방지하지만, 내부 state가 변경되면 리렌더링이 된다.</p>
<ul>
<li>사용 방법 : <code>React.</code>는 생략이 가능하다!<pre><code>const App = (props) =&gt; {
  // 렌더링 로직...
};
</code></pre></li>
</ul>
<p>export default React.memo(App);</p>
<p>const App = memo((props) =&gt; {
    //렌더링 로직...
});</p>
<pre><code>
### 02. useMemo
`useMemo`는 **특정 계산의 결과(함수의 return 값)** 을 메모이징하는데 사용한다. 이는 복잡하거나 무거운 작업을 반복하지 않도록 하여 성능을 최적화한다. 즉, `useMemo`는 렌더링될 때마다 실행되지만, 의존성 배열 안의 값이 변경되지 않으면 이전 값을 반환한다.
`useMemo`는 콜백함수와 의존성 배열을 인자로 받는데, 의존성 배열 중 하나라도 값이 변경되면 콜백함수를 재실행한다.

- 사용 방법</code></pre><p>const 이름 = useMemo(콜백함수, 의존성 배열)</p>
<p>const memoizedValue = useMemo(() =&gt; computeExpensiveValue(a, b), [a, b]);</p>
<pre><code>
### 03. useCallback
`useCallback`은 특정 **함수**를 메모이징하는데 사용한다. React 컴포넌트가 리렌더링 될 때 마다 순차적으로 렌더링을 하는데, 이때마다 동일한 함수가 새롭게 생성된다. 이는 메모리 성능을 저하시키며 자식 컴포넌트의 불필요한 리렌더링까지 야기하게 되는데, `useCallback`으로 함수를 메모이징하여 해결할 수 있다.
`useCallback`도 콜백함수와 의존성 배열을 인자로 받으며, 의존성 배열의 값이 변경되면 콜백함수를 재실행한다.

- 사용 방법</code></pre><p>const 이름 = useCallback(콜백함수, 의존성 배열)</p>
<p>const [name, setName] = useState(&#39;&#39;);
const [age, setAge] = useState(0);
const onSave = useCallback(() =&gt; saveUserInfo(name, age), [name, age]);</p>
<p>```</p>
<h3 id="04-정리">04. 정리</h3>
<table>
<thead>
<tr>
<th>API</th>
<th>메모이징 대상</th>
<th>인자</th>
</tr>
</thead>
<tbody><tr>
<td>React.memo</td>
<td>컴포넌트</td>
<td>함수형 컴포넌트</td>
</tr>
<tr>
<td>useMemo</td>
<td>계산 값(함수 return 값)</td>
<td>콜백함수, 의존성 배열</td>
</tr>
<tr>
<td>useCallback</td>
<td>함수</td>
<td>콜백함수, 의존성 배열</td>
</tr>
</tbody></table>
<p><a href="https://f-lab.kr/insight/utilizing-memoization-for-performance-optimization-in-react?gad_source=1&amp;gclid=Cj0KCQiAwtu9BhC8ARIsAI9JHakBzsS9PJbV31GC2kFmID1wohIg3X6lgdazrxiOp08ilZqnrW11sk8aAsdEEALw_wcB">memoization 참고 사이트 1</a>
<a href="https://f-lab.kr/insight/react-usememo-usecallback-20241207?gad_source=1&amp;gclid=Cj0KCQiAwtu9BhC8ARIsAI9JHalR2t27GMiQcAEIpgqzCPNB_G1-tP9wb4lowgePd-PQbcriVSnQXdIaAgAIEALw_wcB">memoization 참고 사이트 2</a>
<a href="https://velog.io/@hsk10271/TIL-30#1-usememo">memoization 참고 사이트 3</a>
<a href="https://readinggeneral.tistory.com/entry/%EC%BA%90%EC%8B%9CCache%EC%99%80-%EC%BA%90%EC%8B%B1Caching-%EC%A0%95%EB%A6%AC-from-10%EB%B6%84-%ED%85%8C%ED%81%AC%ED%86%A1">캐시/캐싱 참고 사이트</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[뉴스피드 프로젝트 TEA) TroubleShooting 1 : supabase와 RLS👂]]></title>
            <link>https://velog.io/@miiing_gaeng/%EB%89%B4%EC%8A%A4%ED%94%BC%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-TEA-TroubleShooting-1-supabase%EC%99%80-RLS</link>
            <guid>https://velog.io/@miiing_gaeng/%EB%89%B4%EC%8A%A4%ED%94%BC%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-TEA-TroubleShooting-1-supabase%EC%99%80-RLS</guid>
            <pubDate>Wed, 19 Feb 2025 14:27:56 GMT</pubDate>
            <description><![CDATA[<h2 id="1-문제-발생🤯">1. 문제 발생🤯</h2>
<p>프로젝트가 끝나고 KPT 회고까지 작성했는데, 이제서야 트러블 슈팅을 쓰는 사람이 있다? 그건 바로 나! 팀프로젝트 일정 때문에 미뤄두었던 트러블 슈팅 내용을 다 잊어버리기 전에 미리 작성하고자한다.</p>
<h3 id="01-supabase에서-데이터-fetch-시도">01. supabase에서 데이터 fetch 시도</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/bff57634-6c20-433c-a429-5574fbade191/image.png" alt=""></p>
<p>supabase에서 데이터를 가져올 때 사용할 함수는 재사용성이 있다고 판단하여, 여러 곳에서 사용할 수 있도록 유틸리티 함수로 만들고자 했다.
supabase의 메서드를 처음 써본 프로젝트였기에, 제대로 작동하는지 확인하고자 <code>feeds</code> 테이블의 데이터를 console로 찍어보았다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/55803ba0-797b-49ef-a443-f5699bebfa69/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/c96f0a1b-42ae-42b4-bb4e-1617601df9b4/image.png" alt="">
어라? 분명 <code>feeds</code> 테이블에 테스트 데이터를 넣어 놓았는데, console에 빈 배열이 찍혔다. 
console에 에러가 아닌 빈배열이 찍힌 것은 테이블 접근 방법이 잘못된 것이 아닐텐데...라는 생각에 어디가 문제일지 구글링도 해보고, 여러 값을 바꿔보기도 해보았다. 그러다 갑자기 머릿속을 스쳐지나간 특강 속 &quot; <em>RLS</em> &quot;가 기억나서 급하게 구글링을 해보았다.</p>
<h2 id="2-개념-정리🧐">2. 개념 정리🧐</h2>
<h3 id="01-supabase-rls란">01. supabase RLS란?</h3>
<p>Row-Level Security로, 데이터베이스 보안 기능 중 하나이다. 각 사용자가 자신의 데이터(row)만 접근할 수 있도록 제한하는 정책이다. 즉, 한 사용자는 본인의 데이터만 추가/조회/수정/삭제가 가능해야하며, 다른 사용자의 데이터에 접근하지 못하도록 한다.</p>
<h3 id="02-rls를-설정하는-이유">02. RLS를 설정하는 이유</h3>
<ul>
<li>유저의 개인정보를 보호하고, 유저의 데이터가 유출되는 것을 방지한다.</li>
<li>역할에 기반(관리자, 사용자 등)하여 데이터에 접근을 제어할 수 있다.</li>
<li>데이터 무결성을 보호한다.</li>
<li>보안을 강화한다.</li>
</ul>
<h3 id="03-supabase-rls를-설정하는-방법">03. supabase RLS를 설정하는 방법</h3>
<p>단순한 SQL 로직을 사용하여 각 테이블에 원하는 정책을 설정할 수 있다.</p>
<pre><code>CREATE POLICY &quot;정책을 설명하는 이름&quot;
 ON 테이블명
 FOR 동작 (SELECT(선택)/UPSERT(없으면 추가, 있으면 수정)/DELETE(삭제))
 USING 조건 지정</code></pre><p><a href="https://supabase.com/docs/guides/database/postgres/row-level-security">supabase RLS 공식 문서</a>
<a href="https://ramincoding.tistory.com/entry/React-Supabase-%EC%82%AC%EC%9A%A9%EC%9E%90%EB%B3%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC-%EB%B0%8F-%EC%A0%9C%EC%96%B4%ED%95%98%EA%B8%B0-RLS-Policy">supabase 참고 사이트</a></p>
<h2 id="3-해결-방안😇">3. 해결 방안😇</h2>
<h3 id="01-supabase에서-rls-설정하기">01. supabase에서 RLS 설정하기</h3>
<p>문제 해결에 대해 작성하기 전, 내 상황에 대해 간단하게 설명하자면... 이전에 코딩을 전혀 경험해보지 못한 비전공자로서, SQL이나 백엔드 언어에 대해 저어어언혀 아는 것이 없었다. 보통 프론트엔드는 데이터베이스를 직접 구축하기보다는, 구축된 데이터 서버에 접근해 데이터를 받아와 화면 UI에 표시하는 역할이다. 그래서 백엔드 언어에 대해 깊게 알 수도, 알 필요도 없다고 생각했다. 이번 프로젝트가 막막하게 느껴졌던 이유도 BaaS인 supabase를 활용하는 부분이었다.</p>
<p>*<em>BaaS란?</em> : Backend as a Service. 웹 또는 모바일 애플리케이션의 백그라운드 측면을 아웃소싱하는 클라우드 서비스 모델로, 프론트엔드 개발자가 애플리케이션 코드 작성에 집중할 수 있음</p>
<p>다행히 supabase는 테이블 생성 뿐만 아니라 RLS 등 다양한 기능에 대해 GUI와 템플릿을 제공한다. 나는 이 템플릿에 큰 도움을 받아 테이블 생성부터 RLS 설정까지 손쉽게 해낼 수 있었다. 이것이 BaaS의 장점이다. BaaS를 활용하여 백엔드 언어를 전혀 알지 못해도 데이터 베이스를 쉽게 구축하고 연결할 수 있다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/c36ab22a-1d5a-481c-bf55-ebf8db10dc7f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/a25d4f66-6d75-43f3-a91a-12bed4f38498/image.png" alt=""></p>
<p><code>feeds</code> 테이블에 데이터 접근/추가/삭제/수정에 대한 각 RLS를 추가해주었다. 데이터 조회는 화면 UI 구성과 데이터 CRUD를 위해 <code>all users</code>로 정책을 설정해주었고, 데이터 추가/수정/삭제는 회원가입한 회원만 접근 가능하도록 <code>user_id</code>가 필요하도록 설정해주었다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/72dc8daf-806d-4b67-a05b-78cf9fc78241/image.png" alt=""></p>
<p>auth 스키마의 user id는 <code>feeds</code> 테이블의 writer_id와 FK로 연결되어 있기 때문에 USING문을 위 사진처럼 작성해주었다.</p>
<p>*<em>FK란?</em> : Foreign Key. 외래키 혹은 참조키로, 테이블 간 관계를 유지하고, 데이터의 일관성을 유지한다.</p>
<h2 id="4-결과😎">4. 결과😎</h2>
<h3 id="01-해결">01. 해결!</h3>
<p><img src="https://velog.velcdn.com/images/miiing_gaeng/post/8a8831b3-51a6-4f17-b73a-e1b394935ce8/image.png" alt=""></p>
<p>데이터가 console에 잘 찍히는 것을 확인할 수 있었다!</p>
<h3 id="02-최종코드">02. 최종코드</h3>
<p>재사용성과 활용성(table join 등)을 더 높이기 위해 함수의 인자를 추가하여 최종 코드를 작성하였다.
<img src="https://velog.velcdn.com/images/miiing_gaeng/post/ddd1c685-dde8-4b6f-99c2-bec470278a6f/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>