<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev_dan.log</title>
        <link>https://velog.io/</link>
        <description>단단한 개발자</description>
        <lastBuildDate>Thu, 31 Jul 2025 00:33:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev_dan.log</title>
            <url>https://velog.velcdn.com/images/dev_dan/profile/a5236233-e0ce-4901-9e47-b461ef4c0459/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev_dan.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_dan" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[깃 컨벤션 (보기전용)]]></title>
            <link>https://velog.io/@dev_dan/%EA%B9%83-%EC%BB%A8%EB%B2%A4%EC%85%98-%EB%B3%B4%EA%B8%B0%EC%A0%84%EC%9A%A9</link>
            <guid>https://velog.io/@dev_dan/%EA%B9%83-%EC%BB%A8%EB%B2%A4%EC%85%98-%EB%B3%B4%EA%B8%B0%EC%A0%84%EC%9A%A9</guid>
            <pubDate>Thu, 31 Jul 2025 00:33:39 GMT</pubDate>
            <description><![CDATA[<h3 id="커밋-메세지-규칙">커밋 메세지 규칙</h3>
<ul>
<li>타입과 제목은 필수로 작성합니다.</li>
<li>타입은 소문자를 기본으로 사용합니다. (!BREAKING과 !HOTFIX의 경우 대문자로 작성합니다.)</li>
<li>타입과 제목 사이에는 콜론(:)을 사용합니다.</li>
<li>콜론은 타입에 붙여쓰며, 제목과는 한 칸 띄어 사용합니다.</li>
<li>필요할 경우 본문에 자세한 내용을 기입합니다. (option)</li>
</ul>
<p>feat: 로그인 기능 구현</p>
<p>Type    Description
feat    새로운 기능을 추가한 경우
fix    버그를 수정한 경우
design    CSS 등 UI 디자인을 변경한 경우
style    코드 포맷 변경을 하거나 세미 콜론 누락하여 추가하면서 코드 수정이 없는 경우
refactor    코드를 리팩토링한 경우
comment    주석을 추가하거나 변경한 경우
docs    문서를 수정한 경우
test    테스트 코드를 추가, 변경, 리팩토링한 경우
chore    기타 변경사항 (빌드 스크립트 수정, 패키지 매니징 설정 등)
rename    파일 or 폴더명 수정하거나 옮기는 경우
remove    파일을 삭제하는 작업만 수행한 경우
!BREAKING (대문자)    CHANGE 중대한 API를 변경한 경우
!HOTFIX (대문자)    급하게 치명적인 에러를 고친 경우</p>
<h2 id="🍒-작업-중-conflict-최소화하기-위한-cherry-pick-방법">🍒 작업 중 Conflict 최소화하기 위한 cherry-pick 방법</h2>
<ol>
<li>[현재 브랜치: <code>old_branch</code>]: 작업하던 사항을 커밋한다. (푸시 ❌)</li>
<li>[현재 브랜치: <code>old_branch</code>]: 커밋 번호를 조회하고 기록해둔다 (git log --oneline  으로 확인)</li>
<li>[현재 브랜치: <code>old_branch</code>]:  <code>develop</code> 브랜치로 체크아웃 한다. (git checkout develop)</li>
<li>[현재 브랜치: <code>develop</code>]:  최신화된 내용을 반영한다. (git pull)</li>
<li>[현재 브랜치: <code>develop</code>]: 새롭게 브랜치를 생성하고 이동한다. (git checkout -b new_branch)</li>
<li>[현재 브랜치: <code>new_branch</code>]: <code>old_branch</code> 에서 작업하던 커밋을 옮겨온다 (git cherry-pick 000000)</li>
<li><ul>
<li>문제 없이 옮겨오면 마저 작업 시작
그러나 <code>conflict</code>, <code>accept</code>, <code>현재 작업내용 수락</code> 등과 같은 불길한 메시지가 뜬다면 Slack 메시지 남겨주세요</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[🌐 React Context Provider]]></title>
            <link>https://velog.io/@dev_dan/React-Context-Provider</link>
            <guid>https://velog.io/@dev_dan/React-Context-Provider</guid>
            <pubDate>Tue, 29 Jul 2025 08:19:43 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-개념-정리">📌 개념 정리</h2>
<h3 id="✅-context란">✅ Context란?</h3>
<blockquote>
<p><strong>컴포넌트 간에 데이터를 전역으로 공유할 수 있는 리액트 기능</strong></p>
</blockquote>
<ul>
<li><strong>props drilling 없이</strong> 데이터를 여러 컴포넌트에 전달할 수 있음</li>
<li>예: 로그인 정보, 테마 설정, 필터 조건, API 데이터 등</li>
</ul>
<h3 id="✅-왜-provider를-쓰는가">✅ 왜 Provider를 쓰는가?</h3>
<table>
<thead>
<tr>
<th>기존 방식</th>
<th>문제점</th>
</tr>
</thead>
<tbody><tr>
<td>props를 부모 → 자식 → 손자에게 계속 전달</td>
<td>유지보수 어렵고, 중간 컴포넌트가 불필요한 책임을 짐</td>
</tr>
<tr>
<td>useState로만 관리</td>
<td>여러 파일에서 중복되거나 비효율적일 수 있음</td>
</tr>
</tbody></table>
<p>✔ Provider를 사용하면 <strong>상태를 하나의 상위 컴포넌트에서 전역 관리</strong> 가능함<br>→ 컴포넌트 간 응집도 높이고, 상태 흐름을 명확하게 관리</p>
<hr>
<h3 id="✅-query란">✅ Query란?</h3>
<blockquote>
<p>서버에서 데이터를 가져오는 행위 (ex. API 호출)</p>
</blockquote>
<ul>
<li>React Query 같은 라이브러리를 사용하면:<ul>
<li><code>useQuery()</code> / <code>useSuspenseQuery()</code> 등으로 비동기 데이터 요청</li>
<li>캐시 기능이 있어 같은 쿼리는 다시 호출하지 않음</li>
<li>상태: <code>isLoading</code>, <code>isFetching</code>, <code>error</code>, <code>data</code> 등 자동 관리</li>
</ul>
</li>
</ul>
<hr>
<h3 id="✅-쿼리와-provider의-관계">✅ 쿼리와 Provider의 관계?</h3>
<table>
<thead>
<tr>
<th>역할</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>쿼리(Query)</strong></td>
<td>실제 API 호출 담당 (<code>GET</code>, <code>POST</code> 등)</td>
</tr>
<tr>
<td><strong>Provider</strong></td>
<td>쿼리에서 가져온 데이터 + 기타 상태들을 전역에서 쓸 수 있게 공급</td>
</tr>
</tbody></table>
<p>→ 쿼리로 데이터를 가져오고, 그걸 Context로 감싸서 <strong>여러 컴포넌트에서 재사용</strong> 가능하게 만듦</p>
<hr>
<h3 id="✅-언제-provider를-쓰면-좋은가">✅ 언제 Provider를 쓰면 좋은가?</h3>
<table>
<thead>
<tr>
<th>상황</th>
<th>쓰면 좋음?</th>
</tr>
</thead>
<tbody><tr>
<td>작은 컴포넌트끼리만 공유</td>
<td>❌ props로 충분</td>
</tr>
<tr>
<td>페이지 전체에서 공유 (ex. 필터 조건, 검색어, 리스트 데이터 등)</td>
<td>✅ Context Provider 추천</td>
</tr>
<tr>
<td>쿼리 응답값 + 필터 상태 + 페이징 같이 관리</td>
<td>✅ Provider로 묶으면 최적화와 응집도 상승</td>
</tr>
</tbody></table>
<hr>
<h2 id="✍️-작성-규칙-itp-ccc">✍️ 작성 규칙 (ITP-CCC)</h2>
<p>Context Provider를 작성할 땐 다음 규칙을 지켜주세요:</p>
<h3 id="📦-1-import">📦 1. <code>**I**mport</code></h3>
<ul>
<li>필요한 훅, 타입, 쿼리 등 불러오기</li>
</ul>
<h3 id="✍️-2-type-정의">✍️ 2. <code>**T**ype 정의</code></h3>
<ul>
<li>Context 내부에서 다룰 value 타입 정의 (e.g., <code>SomeContextType</code>)</li>
</ul>
<h3 id="⚙️-3-provider-생성">⚙️ 3. <code>**P**rovider 생성</code></h3>
<ul>
<li><code>createContext</code>로 context 객체 만들기 + 기본값 세팅</li>
</ul>
<h3 id="📬-4-child-props-정의">📬 4. <code>**C**hild Props 정의</code></h3>
<ul>
<li><code>children: React.ReactNode</code>를 받는 props 타입</li>
</ul>
<h3 id="🧠-5-component-구현">🧠 5. <code>**C**omponent 구현</code></h3>
<ul>
<li>내부 상태 관리 (<code>useState</code>, <code>useQuery</code>, <code>useEffect</code> 등)</li>
<li><code>value={{ ... }}</code>에 정확하게 타입 맞춰서 전달</li>
</ul>
<h3 id="🚀-6-context-hook-만들기">🚀 6. <code>**C**ontext Hook 만들기</code></h3>
<ul>
<li><code>useXXXProvider()</code> 이름으로 context 리턴하는 훅 정의</li>
</ul>
<h3 id="📤-7-default-export">📤 7. <code>default export</code></h3>
<ul>
<li>Provider 컴포넌트를 export</li>
</ul>
<hr>
<h2 id="🧠-전체-기억하기-📦-itp-ccc-구조">🧠 전체 기억하기: <code>📦 ITP-CCC</code> 구조</h2>
<table>
<thead>
<tr>
<th>단계</th>
<th>이름</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>1️⃣</td>
<td><strong>I</strong>mport</td>
<td>필요한 모듈/타입 import</td>
</tr>
<tr>
<td>2️⃣</td>
<td><strong>T</strong>ype 정의</td>
<td>Context에서 사용할 타입 정의</td>
</tr>
<tr>
<td>3️⃣</td>
<td><strong>P</strong>rovider 객체 생성</td>
<td><code>createContext</code>로 context 객체 만들기</td>
</tr>
<tr>
<td>4️⃣</td>
<td><strong>C</strong>hild Props</td>
<td>Provider Props 타입 정의</td>
</tr>
<tr>
<td>5️⃣</td>
<td><strong>C</strong>omponent</td>
<td>내부 상태 + 쿼리 + <code>value={}</code> 설정</td>
</tr>
<tr>
<td>6️⃣</td>
<td><strong>C</strong>ontext Hook</td>
<td><code>useXXXProvider()</code>로 접근성 향상</td>
</tr>
<tr>
<td>7️⃣</td>
<td><strong>Export</strong></td>
<td><code>default export</code> 꼭 추가</td>
</tr>
</tbody></table>
<hr>
<h2 id="✅-예시-전체-코드">✅ 예시 전체 코드</h2>
<pre><code class="language-tsx">import { createContext, useContext, useState, Dispatch, SetStateAction } from &#39;react&#39;;
import useXYZ from &#39;@/hooks/useXYZ&#39;;
import { Item } from &#39;@/types/model/item&#39;;

type XYZContextType = {
  itemList: Item[];
  count: number;
  setCount: Dispatch&lt;SetStateAction&lt;number&gt;&gt;;
};

const XYZContext = createContext&lt;XYZContextType&gt;({
  itemList: [],
  count: 0,
  setCount: () =&gt; {},
});

type XYZProviderProps = {
  children: React.ReactNode;
};

const XYZProvider = ({ children }: XYZProviderProps) =&gt; {
  const [count, setCount] = useState(0);
  const { data: itemList = [] } = useXYZ();

  return (
    &lt;XYZContext.Provider value={{ itemList, count, setCount }}&gt;
      {children}
    &lt;/XYZContext.Provider&gt;
  );
};

export const useXYZProvider = () =&gt; useContext(XYZContext);
export default XYZProvider;</code></pre>
<hr>
<h2 id="🔚-마무리-팁">🔚 마무리 팁</h2>
<ul>
<li>Context는 너무 많이 남발하면 오히려 복잡해질 수 있으니 <strong>공통으로 사용하는 큰 상태</strong>에만 적용</li>
<li>쿼리와 결합해서 사용하면 더욱 깔끔한 <strong>데이터 흐름</strong> 설계 가능</li>
<li><code>value={{ ... }}</code>에 들어가는 key들은 <code>ContextType</code>과 100% 일치하도록 유지하세요.</li>
</ul>
<hr>
<p>📌 이 구조대로 작성하면 규모가 커져도 유지보수/협업이 편리해집니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[신입개발일기 / Loading page & Provider]]></title>
            <link>https://velog.io/@dev_dan/%EC%8B%A0%EC%9E%85%EA%B0%9C%EB%B0%9C%EC%9D%BC%EA%B8%B0-%EB%A9%94%EC%8B%9C%EC%A7%80%ED%8E%98%EC%9D%B4%EC%A7%80</link>
            <guid>https://velog.io/@dev_dan/%EC%8B%A0%EC%9E%85%EA%B0%9C%EB%B0%9C%EC%9D%BC%EA%B8%B0-%EB%A9%94%EC%8B%9C%EC%A7%80%ED%8E%98%EC%9D%B4%EC%A7%80</guid>
            <pubDate>Mon, 28 Jul 2025 04:30:43 GMT</pubDate>
            <description><![CDATA[<ol>
<li>과제 라우터에서  ...withErrorAndSuspenseBoundary  사용하지 않기.</li>
</ol>
<p>router.tsx
<img src="https://velog.velcdn.com/images/dev_dan/post/6ac8bd25-a403-4130-930c-8b264404e703/image.png" alt=""></p>
<pre><code>const withErrorAndSuspenseBoundary = (element: React.ReactNode) =&gt; ({
    element: (
        &lt;ErrorBoundary fallback={&lt;ErrorArea /&gt;}&gt;
            &lt;Suspense fallback={&lt;LoadingArea /&gt;}&gt;{element}&lt;/Suspense&gt;
        &lt;/ErrorBoundary&gt;
    ),
});
</code></pre><ol start="2">
<li>과제 프로바이더 구조 이해하기
<img src="https://velog.velcdn.com/images/dev_dan/post/ae050ddb-2511-4e9e-ae7b-9745a6be149c/image.png" alt=""></li>
</ol>
<h1 id="💡-react-provider-제대로-이해하기-내-프로젝트에선-이렇게-동작">💡 React Provider 제대로 이해하기: 내 프로젝트에선 이렇게 동작!</h1>
<h2 id="✅-provider란">✅ Provider란?</h2>
<p>React에서 <code>Provider</code>는 <strong>공통으로 사용되는 데이터를 전역 상태처럼 관리</strong>하고, <strong>필요한 컴포넌트에 쉽게 공급</strong>하는 역할을 한다.</p>
<p>쉽게 말하면, <strong>React에서의 물류센터</strong> 같은 존재입니다. 어떤 데이터든 이 센터에 넣어두면, 아래에 있는 어떤 컴포넌트든 필요할 때 가져다 쓸 수 있다.</p>
<hr>
<h2 id="🔄-왜-provider가-필요할까-feat-택배-기사">🔄 왜 Provider가 필요할까? (feat. 택배 기사)</h2>
<p>여러 컴포넌트가 같은 데이터를 필요로 하는 상황을 상상해볼게요. 예를 들어, 사용자 정보가 있다면 이건 <strong>헤더</strong>, <strong>사이드바</strong>, <strong>댓글</strong>, <strong>대시보드</strong>에서도 필요하겠지?</p>
<h3 id="❌-provider-없이-prop-drilling">❌ Provider 없이: Prop Drilling</h3>
<pre><code class="language-tsx">&lt;App user={user}&gt;
  &lt;Header user={user}&gt;
    &lt;Nav user={user}&gt;
      &lt;Profile user={user} /&gt;
    &lt;/Nav&gt;
  &lt;/Header&gt;
&lt;/App&gt;</code></pre>
<p>이렇게 props를 계속 넘겨야 해요. 이것을 <strong>Prop Drilling</strong>이라고 부릅니다. 택배 기사가 1층에서 출발해 꼭대기까지 계단으로 올라가서 직접 배달하는 상황이라고 보면 돼요.</p>
<h3 id="✅-provider-사용">✅ Provider 사용</h3>
<pre><code class="language-tsx">&lt;UserContext.Provider value={user}&gt;
  &lt;App /&gt;
&lt;/UserContext.Provider&gt;</code></pre>
<p>이제 <code>Header</code>, <code>Nav</code>, <code>Profile</code> 어디서든 <code>useContext(UserContext)</code>만 쓰면 데이터를 바로 쓸 수 있어요. 마치 <strong>1층에 물류센터</strong>를 두고 필요한 사람이 와서 직접 가져가는 구조죠.</p>
<hr>
<h2 id="🗂-프로젝트-구조-속-provider-예시">🗂 프로젝트 구조 속 Provider 예시</h2>
<pre><code>src/
└── pages/
    └── inquiries/
        └── pages/
            └── indexes/
                └── pages/
                    └── MessagePage/
                        ├── MessagePage.tsx
                        └── components/
                            ├── MessageAllGetProvider/
                            │   └── MessageAllGetProvider.tsx
                            ├── MessageLocalValueProvider/
                            │   └── MessageLocalValueProvider.tsx</code></pre><hr>
<h2 id="🧩-실제-연결-구조">🧩 실제 연결 구조</h2>
<h3 id="1️⃣-messagepagetsx">1️⃣ MessagePage.tsx</h3>
<pre><code class="language-tsx">const MessagePage = () =&gt; {
  return (
    &lt;MessageAllGetProvider&gt;
      &lt;MessageLocalValueProvider&gt;
        &lt;MessageLayout
          MessageTable={&lt;MessageTableBox /&gt;}
          MessageDatePicker={&lt;MessageDatePicker /&gt;}
          // ...
        /&gt;
      &lt;/MessageLocalValueProvider&gt;
    &lt;/MessageAllGetProvider&gt;
  );
};</code></pre>
<ul>
<li><code>MessageAllGetProvider</code>: 서버에서 데이터 조회</li>
<li><code>MessageLocalValueProvider</code>: 로컬 상태 관리 (ex. 날짜 선택)</li>
</ul>
<hr>
<h3 id="2️⃣-messageallgetprovidertsx">2️⃣ MessageAllGetProvider.tsx</h3>
<pre><code class="language-tsx">const MessageAllGetProvider = ({ children }) =&gt; {
  const { company, site } = useCompanySelectProvider();
  const [pagination, setPagination] = useState({ page: 1, size: 20, total: 0 });
  const [dateRange, setDateRange] = useState();
  const [selectedSite, setSelectedSite] = useState(company.siteUrls[0]);

  const { isFetching, data } = useInquiryAllGetQuery({
    path: { companyId: company?.uuid },
    query: { pagination, siteUrl: selectedSite, dateRange },
    body: {},
  });

  const changePagination = (newPage) =&gt; setPagination(newPage);
  const changeSite = (site) =&gt; setSelectedSite(site);

  return (
    &lt;MessageAllGetContext.Provider
      value={{
        changePagination,
        pagination,
        selectedSite,
        changeSite,
        dateRange,
        setDateRange,
        inquiries: data?.inquiries || [],
      }}
    &gt;
      {children}
      {isFetching &amp;&amp; &lt;LoadingOverlay /&gt;}
    &lt;/MessageAllGetContext.Provider&gt;
  );
};</code></pre>
<hr>
<h3 id="3️⃣-하위-컴포넌트에서-사용하는-방법">3️⃣ 하위 컴포넌트에서 사용하는 방법</h3>
<pre><code class="language-tsx">const MessageTableBox = () =&gt; {
  const { inquiries, changePagination } = useMessageAllGetProvider();

  return (
    &lt;Table&gt;
      {inquiries.map((inquiry) =&gt; (
        &lt;Row key={inquiry.id}&gt;{inquiry.title}&lt;/Row&gt;
      ))}
    &lt;/Table&gt;
  );
};</code></pre>
<hr>
<h3 id="4️⃣-customapisettingupdateprovider-예시">4️⃣ CustomApiSettingUpdateProvider 예시</h3>
<pre><code class="language-tsx">const CustomApiSettingUpdateContext = createContext({
  uuid: &#39;&#39;,
  setUuid: () =&gt; {},
  name: &#39;&#39;,
  status: &#39;&#39;,
  media: &#39;&#39;,
  apiMedias: [],
  handleNameInputChange: () =&gt; {},
  handleStatusItemClick: () =&gt; {},
  handleMediaItemClick: () =&gt; {},
  handleSubmitButtonClick: () =&gt; {},
  handleCancelButtonClick: () =&gt; {},
});</code></pre>
<p>이걸 사용하는 컴포넌트는 다음과 같아요:</p>
<pre><code class="language-tsx">const {
  name,
  handleNameInputChange,
  handleSubmitButtonClick
} = useCustomApiSettingUpdateProvider();

return (
  &lt;&gt;
    &lt;Input value={name} onChange={handleNameInputChange} /&gt;
    &lt;Button onClick={handleSubmitButtonClick}&gt;등록&lt;/Button&gt;
  &lt;/&gt;
);</code></pre>
<hr>
<h2 id="🧠-요약-provider-패턴의-핵심-정리">🧠 요약: Provider 패턴의 핵심 정리</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>createContext()</code></td>
<td>데이터를 전역처럼 사용할 수 있게 ‘컨텍스트’를 만듦</td>
</tr>
<tr>
<td><code>Context.Provider</code></td>
<td>데이터를 하위 컴포넌트에 전달하는 ‘공급자 역할’</td>
</tr>
<tr>
<td><code>useContext()</code></td>
<td>하위 컴포넌트에서 Context에 접근하는 훅</td>
</tr>
<tr>
<td>Provider 컴포넌트</td>
<td>실제로 값을 정의하고 상태를 관리하는 컴포넌트</td>
</tr>
<tr>
<td>장점</td>
<td>Prop drilling 방지, 유지보수 쉬움, 관심사 분리, 재사용성 ↑</td>
</tr>
</tbody></table>
<hr>
<h2 id="💬-마무리">💬 마무리</h2>
<p>Provider는 React에서 <strong>공통 데이터나 상태를 효율적으로 관리</strong>할 수 있게 도와주는 핵심 개념이다.<br>특히 복잡한 페이지일수록, 여러 상태와 데이터를 여러 컴포넌트에 전달할 일이 많을수록 Provider 패턴은 필수라고 할 수 있다.</p>
<p>이 구조를 이해하고 나면, 협업이나 유지보수에서도 훨씬 수월해질 거다.<br>복잡한 상태 로직, 중첩된 데이터 전달로 고생하고 있다면, 꼭 Provider 패턴을 적극적으로 활용해보자! 💪</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[신입개발일기 / Sentry   에러 로깅 연동]]></title>
            <link>https://velog.io/@dev_dan/%EC%8B%A0%EC%9E%85%EA%B0%9C%EB%B0%9C%EC%9D%BC%EA%B8%B0-Sentry-%EC%97%90%EB%9F%AC-%EB%A1%9C%EA%B9%85-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@dev_dan/%EC%8B%A0%EC%9E%85%EA%B0%9C%EB%B0%9C%EC%9D%BC%EA%B8%B0-Sentry-%EC%97%90%EB%9F%AC-%EB%A1%9C%EA%B9%85-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Thu, 24 Jul 2025 03:26:22 GMT</pubDate>
            <description><![CDATA[<h1 id="🛠️-sentry-에러-로깅-연동이란">🛠️ Sentry 에러 로깅 연동이란?</h1>
<p>오늘 개발팀 데일리 스크럼에서 맡게 된 업무 중 하나가 <strong>Sentry 에러 로깅 연동</strong>이었는데, 이게 어떤 작업인지 정리해봤다.</p>
<hr>
<h2 id="🔍-sentry란">🔍 Sentry란?</h2>
<p><strong>Sentry</strong>는 애플리케이션에서 발생하는 에러(버그, 예외 등)를 <strong>실시간으로 추적하고 수집하는 모니터링 도구</strong>다.</p>
<ul>
<li>에러 발생 시 <strong>스택 트레이스, 사용자 정보, 환경 등</strong> 자동 수집</li>
<li>다양한 플랫폼 지원 (웹, 모바일, 백엔드 등)</li>
<li>Slack, 이메일 등과 연동 가능</li>
</ul>
<p>👉 <a href="https://sentry.io">https://sentry.io</a></p>
<hr>
<h2 id="⚙️-sentry-에러-로깅-연동이란">⚙️ Sentry 에러 로깅 연동이란?</h2>
<blockquote>
<p>앱 또는 서버에 Sentry SDK를 연동해서, 예외 발생 시 자동으로 Sentry에 보고하도록 설정하는 작업</p>
</blockquote>
<hr>
<h2 id="📦-일반적인-연동-절차">📦 일반적인 연동 절차</h2>
<h3 id="1-sentry-프로젝트-생성">1. Sentry 프로젝트 생성</h3>
<ul>
<li>sentry.io에 로그인하고, 사용할 플랫폼(React, Node, Django 등) 선택해 프로젝트 생성</li>
</ul>
<h3 id="2-sdk-설치-예-react-기준">2. SDK 설치 (예: React 기준)</h3>
<pre><code class="language-bash">npm install @sentry/react @sentry/tracing</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[React-Query + Gemini]]></title>
            <link>https://velog.io/@dev_dan/React-Query-Gemini</link>
            <guid>https://velog.io/@dev_dan/React-Query-Gemini</guid>
            <pubDate>Wed, 23 Jul 2025 10:17:29 GMT</pubDate>
            <description><![CDATA[<p>프론트엔드 입사 3일차,
React-Query를 사용하여 프로필 설정 부분 생성된 API를 (아직은 미완성이지만 된 부분만)
가져와서 UI에 뿌려주는 과제를 받게 되었다.</p>
<h3 id="해결과정">해결과정</h3>
<p><img src="https://velog.velcdn.com/images/dev_dan/post/4c0e25fa-b4cb-474b-9f2b-f5c8a1f25e3c/image.png" alt=""></p>
<ol>
<li><p>실제 백엔드 API와 통신하고 데이터를 화면에 표시하는 부분 찾기
-폴더 구조 파악도 안 된 상태에서 실제로 통신이 이루어진 부분을 찾기란 쉽지않았다.
-GeminiCLI를 사용해서 useQuery가 사용된 파일을 검색해달라고 부탁해서 코드를 확인했다.</p>
<p>-useQuery를 사용하는 파일이 많아서 <strong>src&gt;hooks&gt;server&gt;useInquiryAllGetQuery.tsx</strong>파일을 예시로 데이터 호출 및 처리 과정을 확인하라고 함
-먼저 useInquiryAllGetQuery.tsx 커스텀 훅이 어떻게 useQuery를 사용하여 API를 호출하고 있는지 살펴봄
useInquiryAllGetQuery.tsx</p>
<pre><code class="language-ts">import { InquireType, InquiryAllGetReq } from &#39;@/types/model/inquiry&#39;;
import { useQuery, useQueryClient, useSuspenseQuery } from &#39;@tanstack/react-query&#39;;
import queryKey from &#39;constants/queryKeys&#39;;
import { useEffect } from &#39;react&#39;;
import { getInquiryAll } from &#39;services/inquiry&#39;;
import { Pagination } from &#39;types/pagination&#39;;
import { Request } from &#39;types/request&#39;;
</code></pre>
</li>
</ol>
<p>const useInquiryAllGetQuery = (request: InquiryAllGetReq) =&gt; {
    const queryClient = useQueryClient();
    const {
        path: { companyId },
    } = request;
    const result = useSuspenseQuery({
        queryKey: [queryKey.getInquiryAll, JSON.stringify(request)],</p>
<pre><code>    queryFn: async () =&gt; {
        if (!companyId) {
            return { body: [], page: 1, size: 20, total: 0 };
        }
        const response = await getInquiryAll(request);
        return response;
    },
    // throwOnError: true,
    // placeholderData: (previousData) =&gt; previousData,
});

useEffect(() =&gt; {
    if (!result.data) return;

    const { page, size, total } = result.data;
    const nextPage = page + 1;

    if (nextPage * size - size &lt; total) {
        const nextRequest: InquiryAllGetReq = {
            path: { companyId: request.path.companyId },
            query: {
                ...request.query,
                pagination: {
                    page: nextPage,
                    size,
                    total,
                },
            },
            body: {}, // 유지
        };

        queryClient.prefetchQuery({
            queryKey: [queryKey.getVisitHistoryAll, JSON.stringify(nextRequest)],
            queryFn: () =&gt; getInquiryAll(nextRequest),
        });
    }
}, [result.data, queryClient, request]);

const safeData = result.data
    ? {
          inquiries: result.data.body,
          pagination: {
              page: result.data.page,
              size: result.data.size,
              total: result.data.total,
          },
      }
    : {
          inquiries: [],
          pagination: { page: 1, size: 20, total: 0 },
      };

return {
    ...result,
    data: safeData,
};</code></pre><p>};</p>
<p>export default useInquiryAllGetQuery;</p>
<pre><code>
-그런데 useInquiryAllGetQuery를 사용하는 컴포넌트가 많아서
그 중에서 src&gt;pages&gt;inquiries&gt;pages&gt;indexes&gt;pages&gt;InquiryPage&gt;components&gt;InquityTableBox&gt;components&gt;UserInquiryTableBox&gt;UserInquiryTableBox.tsx파일을 예시로 봄

```ts
import BasicTable from &#39;headful/Table/BasicTable&#39;;
import React from &#39;react&#39;;
import EmptyBoundary from &#39;@/components/EmptyBoundary/EmptyBoundary&#39;;
import EmptyTable from &#39;headful/EmptyTable/EmptyTable&#39;;
import useInquiryAllGetQuery from &#39;@/hooks/server/useInquiryAllGetQuery&#39;;
import { useCompanySelectProvider } from &#39;@/components/CompanySelectProvider/CompanySelectProvider&#39;;
**import useCompanyGetQuery from &#39;@/hooks/server/useCompanyGetQuery&#39;;**
import { useInquiryAllGetProvider } from &#39;../../../InquiryAllGetProvider/InquiryAllGetProvider&#39;;
import InquiryColumns from &#39;../InquiryColumns/InquiryColumns&#39;;
import ContentBox from &#39;@/headful/ContentBox/ContentBox&#39;;

type UserInquiryTableBoxProps = {};

const UserInquiryTableBox = ({}: UserInquiryTableBoxProps) =&gt; {
    const { pagination: p, selectedSite, dateRange } = useInquiryAllGetProvider();
    const { company, site } = useCompanySelectProvider();

    const {
        data: { inquiries },
    } = useInquiryAllGetQuery({
        path: { companyId: company?.uuid as string },
        query: { pagination: p, siteUrl: site as string, dateRange },
        body: {},
    });

    const {
        data: { company: companyData },
    } = useCompanyGetQuery({ path: { companyId: company?.uuid as string }, query: {}, body: {} });

    const companyColumns = companyData.columns?.split(&#39;,&#39;) ?? [];

    const inquiryColumns = InquiryColumns({ inquiries });
    const desiredColumnKeys = [&#39;seq&#39;, &#39;createdDate&#39;, &#39;createdTime&#39;, &#39;clientPhone&#39;, &#39;device&#39;, &#39;ip&#39;, ...companyColumns];
    const filteredColumns = inquiryColumns.filter((col) =&gt; desiredColumnKeys.includes(col.key));

    return (
        &lt;ContentBox title={&#39;문의내역&#39;}&gt;
            &lt;EmptyBoundary data={inquiries} fallback={&lt;EmptyTable text=&quot;문의 내역이 없습니다.&quot; /&gt;}&gt;
                &lt;BasicTable&gt;
                    &lt;BasicTable.Header&gt;
                        {filteredColumns.map(({ key, label }) =&gt; (
                            &lt;BasicTable.Cell asHeader align=&quot;left&quot; key={key}&gt;
                                {label}
                            &lt;/BasicTable.Cell&gt;
                        ))}
                    &lt;/BasicTable.Header&gt;
                    &lt;BasicTable.Body&gt;
                        {inquiries?.map((item: any, rowIndex: number) =&gt; (
                            &lt;BasicTable.Row key={item.uuid}&gt;
                                {filteredColumns.map((col) =&gt;
                                    // render 함수에서 생성된 JSX에 key를 부여합니다.
                                    React.cloneElement(col.render(item, rowIndex), { key: col.key + col.label })
                                )}
                            &lt;/BasicTable.Row&gt;
                        ))}
                    &lt;/BasicTable.Body&gt;
                &lt;/BasicTable&gt;
            &lt;/EmptyBoundary&gt;
        &lt;/ContentBox&gt;
    );
};

export default UserInquiryTableBox;
</code></pre><p>-이파일에서 useInquiryAllGetQuery 훅을 호출하여 얻은 데이터를 테이블 형태로 화면에 표시한다.
-즉, 훅을 호출하여 문의 데이터를 가져오고, BasicTable 컴포넌트를 사용해서 화면에 테이블 형태로 데이터를 뿌려줌</p>
<blockquote>
<p>데이터 호출 :
src/hooks/server/useInquiryAllGetQuery.tsx 파일에서 useSuspenseQuery(react-query의 useQuery와 유사)를 사용하여 getinquiryAll API 함수를 호출하고 데이터를 가져옴.</p>
</blockquote>
<blockquote>
<p>데이터 표시 :
src/pages/inquiries/pages/indexes/pages/InquiryPage/components/InquiryTableBox/components/UserInquiryTableBox/UserInquiryTableBox.tsx 컴포넌트에서 useInquiryAllGetQuery 훅을 사용해서 데이터를 받아온 후 BasicTable 컴포넌트를 이용해 해당 데이터를 화면에 렌더링 함.</p>
</blockquote>
<p>그럼 이를 참고해서 데이터를 표시해야되는 UserInfo.tsx(프로필설정) 의 Query.ts파일도 만들어보자
<img src="https://velog.velcdn.com/images/dev_dan/post/48afdfcb-24c8-48c5-9ca3-66a2e91795d7/image.png" alt=""></p>
<ol>
<li>먼저 사용자 정보를 가져오는 API함수가 어디있는지 확인해야 한다.</li>
</ol>
<p>-services 디렉터리에서 관련 함수를 찾아봄
-getUserAll은 모든 유저를 가져오는 함수로 파악됨. 현재 로그인된 특정 유저의 정보를 가져오는 함수가 필요하다. user.ts파일을 직접 읽어서 확인하자
-getUserAll 함수만으로는 현재 로그인한 사용자의 정보를 가져올 수 없다. auth.ts파일에 현재 정보를 가져오는 함수가 있는지 확인하자</p>
<blockquote>
<p>getContext : 현재 로그인한 사용자의 정보를 가져오는 함수임</p>
</blockquote>
<ol start="2">
<li>useUserContextGetQuery.ts 파일 생성하기<pre><code class="language-ts"></code></pre>
</li>
</ol>
<p>import { useQuery } from &#39;@tanstack/react-query&#39;;
import { getContext } from &#39;@/services/auth&#39;;
import queryKey from &#39;@/constants/queryKeys&#39;;</p>
<p>const useUserContextGetQuery = () =&gt; {
    const result = useQuery({
        queryKey: [queryKey.getContext],
        queryFn: getContext,
    });</p>
<pre><code>return result;</code></pre><p>};</p>
<p>export default useUserContextGetQuery;</p>
<pre><code>
3. UserInfo.tsx파일 수정
-useUserContextGetQuery 훅을 사용하고, 가져온 데이터를 상태에 반영하기
```ts
import React, { useEffect, useState } from &#39;react&#39;;
import styles from &#39;./UserInfo.module.scss&#39;;
import InputA from &#39;@/ui-kit/src/components/contents/Input/A/InputA&#39;;
import ColorChip from &#39;@/headful/ColorChip/ColorChip&#39;;
import BasicModal from &#39;@/headful/BasicModal/BasicModal&#39;;
import ButtonB from &#39;@/ui-kit/src/components/contents/Button/B/ButtonB&#39;;
import UserPasswordChangeForm from &#39;../UserPasswordChangeForm/UserPasswordChangeForm&#39;;
import useUserContextGetQuery from &#39;@/hooks/server/users/useUserContextGetQuery&#39;;

const UserInfo = () =&gt; {
    const { data: userData } = useUserContextGetQuery();
    const [userInfo, setUserInfo] = useState({
        name: &#39;&#39;,
        role: &#39;&#39;,
        account: &#39;&#39;,
        phone: &#39;&#39;,
    });

    useEffect(() =&gt; {
        if (userData) {
            setUserInfo({
                name: userData.body.name,
                role: userData.body.role,
                account: &#39;&#39;, // API에 없음
                phone: &#39;&#39;, // API에 없음
            });
        }
    }, [userData]);

    const handleSave = () =&gt; {
        alert(JSON.stringify(userInfo));
    };

    return (
        &lt;div className={styles.UserInfo}&gt;
            &lt;div className={styles.FormItem}&gt;
                &lt;div className={styles.Label}&gt;
                    &lt;span&gt;이름&lt;/span&gt;
                &lt;/div&gt;
                &lt;div className={styles.Input}&gt;
                    &lt;UserInfoInput
                        value={userInfo.name}
                        onChange={(e) =&gt; setUserInfo({ ...userInfo, name: e.target.value })}
                    /&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div className={styles.FormItem}&gt;
                &lt;div className={styles.Label}&gt;
                    &lt;span&gt;직급&lt;/span&gt;
                &lt;/div&gt;
                &lt;div className={styles.Input}&gt;
                    &lt;UserInfoInput
                        value={userInfo.role}
                        onChange={(e) =&gt; setUserInfo({ ...userInfo, role: e.target.value })}
                    /&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div className={styles.FormItem}&gt;
                &lt;div className={styles.Label}&gt;
                    &lt;span&gt;권한&lt;/span&gt;
                &lt;/div&gt;
                &lt;div className={styles.Input}&gt;
                    &lt;ColorChip bgColor=&quot;#D8F5DA&quot; color=&quot;#1D5C21&quot; text=&quot;관리자&quot; /&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div className={styles.FormItem}&gt;
                &lt;div className={styles.Label}&gt;
                    &lt;span&gt;계정&lt;/span&gt;
                &lt;/div&gt;
                &lt;div className={styles.Input}&gt;
                    &lt;UserInfoInput
                        value={userInfo.account}
                        onChange={(e) =&gt; setUserInfo({ ...userInfo, account: e.target.value })}
                    /&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;div className={styles.FormItem}&gt;
                &lt;div className={styles.Label}&gt;
                    &lt;span&gt;비밀번호&lt;/span&gt;
                &lt;/div&gt;
                &lt;div className={styles.Input}&gt;
                    &lt;UserInfoInput type=&quot;password&quot; placeholder=&quot;비밀번호&quot; /&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;BasicModal&gt;
                &lt;BasicModal.Trigger&gt;
                    &lt;ButtonB
                        bgColor=&quot;#F8F8F8&quot;
                        color=&quot;#333333&quot;
                        width=&quot;124px&quot;
                        height=&quot;45px&quot;
                        style={{ border: &#39;1px solid #E2E2E2&#39;, marginBottom: &#39;8px&#39; }}
                    &gt;
                        비밀번호 변경
                    &lt;/ButtonB&gt;
                &lt;/BasicModal.Trigger&gt;
                &lt;BasicModal.Backdrop /&gt;
                &lt;BasicModal.Content title=&quot;비밀번호 변경&quot; width=&quot;550px&quot; height=&quot;460px&quot;&gt;
                    &lt;UserPasswordChangeForm /&gt;
                &lt;/BasicModal.Content&gt;
            &lt;/BasicModal&gt;
            &lt;div className={styles.FormItem}&gt;
                &lt;div className={styles.Label}&gt;
                    &lt;span&gt;휴대폰 번호&lt;/span&gt;
                &lt;/div&gt;
                &lt;div className={styles.Input} style={{ display: &#39;flex&#39;, alignItems: &#39;center&#39;, gap: &#39;10px&#39; }}&gt;
                    &lt;UserInfoInput
                        width={84}
                        maxLength={3}
                        value={userInfo.phone.slice(0, 3)}
                        onChange={(e) =&gt;
                            setUserInfo({
                                ...userInfo,
                                phone: e.target.value + userInfo.phone.slice(3),
                            })
                        }
                    /&gt;
                    &lt;span&gt;-&lt;/span&gt;
                    &lt;UserInfoInput
                        width={84}
                        maxLength={4}
                        value={userInfo.phone.slice(3, 7)}
                        onChange={(e) =&gt;
                            setUserInfo({
                                ...userInfo,
                                phone:
                                    userInfo.phone.slice(0, 3) +
                                    e.target.value +
                                    userInfo.phone.slice(7),
                            })
                        }
                    /&gt;
                    &lt;span&gt;-&lt;/span&gt;
                    &lt;UserInfoInput
                        width={84}
                        maxLength={4}
                        value={userInfo.phone.slice(7)}
                        onChange={(e) =&gt;
                            setUserInfo({
                                ...userInfo,
                                phone: userInfo.phone.slice(0, 7) + e.target.value,
                            })
                        }
                    /&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;ButtonB
                bgColor=&quot;#007AFF&quot;
                color=&quot;#FFFFFF&quot;
                width=&quot;124px&quot;
                height=&quot;45px&quot;
                onClick={handleSave}
            &gt;
                저장
            &lt;/ButtonB&gt;
        &lt;/div&gt;
    );
};

export default UserInfo;

const UserInfoInput = ({ ...props }: React.ComponentProps&lt;typeof InputA&gt;) =&gt; (
    &lt;InputA s padding={{ y: 15, x: 20 }} height={45} width={props.width || &#39;100%&#39;} {...props} /&gt;
);</code></pre><p>-UserInfo.tsx 파일에 useUserContextGetQuery훅을 사용하여 사용자 정보를 가져오고, useEffect로 상태를 업데이트하며, 저장 버튼 클릭 시 alert 창으로 현재 상태를 보여주는 기능을 추가함.
-&gt; 이제 프로필 설정 페이지에서 실제 사용자 데이터를 불러와 수정하고, 저장 버튼읠 눌러 변경된 데이터를 확인할 수 있다!</p>
<h2 id="but-콘솔에러-발생">But 콘솔에러 발생</h2>
<p>내용 &gt;  컴파일 오류 발생 관련,, 어쩌구 저쩌구 
이유 &gt; rank, email, phone 속성이 없다는 메시지
User 타입 정의를 확인해보자 </p>
<blockquote>
<p>User타입에는 uid, name, role, companyUuid만 있고, UserAllGetItem rank가 아닌 position과 email, phone이 포함되어 있어서 그런 것
getContext API가 User타입을 반환하므로, Userinfo.tsx에서 User타입의 속성을 사용하도록 수정해야 한다.
또한, getContext API 응답에는 position, email, phone 정보가 포함되어 있지 않기 때문에 이런 오류가 발생한 것.</p>
</blockquote>
<p>getContext 함수를 다시 확인해서 어떤 정보를 반환하는지 파악하자
-&gt; 확인결과 : getContext는 User타입을 반환하므로 name과 role만 사용할 수 있음
UserInfo.tsx를 다시 수정해서 User타입에 맞게 name과 role만 사용하고 나머지 정보는 빈값으로 두기로 함!</p>
<p>또한 rank대신 role을 사용해보자</p>
<blockquote>
<p>잼미니 응답 내용 이렇게 나옴 
<img src="https://velog.velcdn.com/images/dev_dan/post/2cd86f6e-6f6e-429f-a8e0-4d1ff262524f/image.png" alt=""></p>
</blockquote>
<h2 id="but-콘솔에러-발생-1">But 콘솔에러 발생</h2>
<p>내용 &gt;  이번에는 key prop을 지정하지 않아서 발생하는 경고 warring!!!!
이유 &gt; React는 key prop을 사용하여 리스트의 항목들을 효율적으로 업데이트하고 식별한다.</p>
<p>메시지에서 DynamicMenu.tsx:15라고 명시되어 있으므로, 해당 파일의 15번째 줄 근처에서 리스트를 렌더링하는 부분 (예:map함수)을 찾아 각 리스트 항목에 고유한 key prop을 추가해야 함</p>
<p>DynamicMenu.tsx파일확인 =&gt; compaines.map부분에 key prop이 누락되어 있음
=&gt; company.uuid를 key로 사용하도록 수정함</p>
<ul>
<li>menuTree.map부분에 key prop도 누락되어 잇어서 menu.id 또는 menu.name과 같이 고유한 값을 key로 사용하도록 수정함
하지만 여기에서는 menu.id가 없으므로 menu.name을 사용함. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[잼미니 사용법 GOOGLE GEMINI ]]></title>
            <link>https://velog.io/@dev_dan/%EC%9E%BC%EB%AF%B8%EB%8B%88-%EC%82%AC%EC%9A%A9%EB%B2%95-GOOGLE-GEMINI</link>
            <guid>https://velog.io/@dev_dan/%EC%9E%BC%EB%AF%B8%EB%8B%88-%EC%82%AC%EC%9A%A9%EB%B2%95-GOOGLE-GEMINI</guid>
            <pubDate>Mon, 14 Jul 2025 10:05:10 GMT</pubDate>
            <description><![CDATA[<h2 id="✨-gemini-cli-잼미니-cli-장점-및-주요-기능">✨ Gemini CLI (잼미니 CLI) 장점 및 주요 기능</h2>
<blockquote>
<p>Google의 Gemini 모델을 로컬 터미널에서 사용할 수 있는 강력한 CLI 도구로, 개발자와 AI가 더 가까워지는 경험을 제공한다.</p>
</blockquote>
<hr>
<h3 id="✅-1-mcp-기능-multi-code-path">✅ 1. MCP 기능 (Multi Code Path)</h3>
<ul>
<li>여러 파일의 흐름을 동시에 인식하고 이해한다.</li>
<li>프로젝트 단위 문맥 파악이 가능해, 복잡한 코드 구조도 AI가 함께 추적하고 설명해준다.</li>
</ul>
<hr>
<h3 id="✅-2-코드-파일-수정-및-생성">✅ 2. 코드 파일 수정 및 생성</h3>
<ul>
<li><code>@경로/파일명</code> 방식으로 파일을 직접 불러오거나 수정할 수 있다.</li>
<li>AI가 직접 코드 일부를 수정하거나, 새 파일을 만들어주는 작업도 가능하다.</li>
<li>예: <code>@src/App.tsx</code> → <code>&quot;이 부분의 성능을 개선해줘&quot;</code></li>
</ul>
<hr>
<h3 id="✅-3-cli에서-스크립트-실행">✅ 3. CLI에서 스크립트 실행</h3>
<ul>
<li><code>run</code> 명령어를 통해 테스트, 빌드, 실행 등 다양한 명령어를 터미널 내에서 바로 실행할 수 있다.</li>
<li>AI에게 &quot;이 테스트를 통과시키려면 어떻게 수정해야 할까?&quot; 같은 질문도 자연스럽게 할 수 있다.</li>
</ul>
<hr>
<h3 id="✅-4-일상적인-질문도-가능-웹-검색-및-일반-qa">✅ 4. 일상적인 질문도 가능 (웹 검색 및 일반 Q&amp;A)</h3>
<ul>
<li>&quot;오늘 날씨 알려줘&quot;, &quot;React에서 useEffect 언제 써?&quot; 같은 질문도 가능!</li>
<li>일반적인 ChatGPT처럼도 쓸 수 있어서, 문법 설명, 알고리즘 풀이 등도 질문할 수 있다.</li>
</ul>
<hr>
<h3 id="✅-5-파일-기반-작업-맥락-인식">✅ 5. 파일 기반 작업 맥락 인식</h3>
<ul>
<li>현재 열려 있는 파일뿐 아니라, 주변의 다른 파일들도 함께 고려해 답변을 준다.</li>
<li>실제 프로젝트 상황처럼 맥락 있는 도움을 받을 수 있다.</li>
</ul>
<hr>
<h3 id="✅-6-체크포인트-기능-checkpointing">✅ 6. 체크포인트 기능 (Checkpointing)</h3>
<ul>
<li>코드 수정 내역을 체크포인트로 저장하여, 변경 전후 비교가 가능하고 되돌리기도 쉽다.</li>
</ul>
<hr>
<h3 id="✅-7-확장성">✅ 7. 확장성</h3>
<ul>
<li>다양한 확장(extensions)을 설치하거나 선택적으로 활성화해서 원하는 기능을 더할 수 있다.</li>
<li>예: <code>--extensions</code>, <code>--list-extensions</code> 옵션 활용이 가능하다.</li>
</ul>
<hr>
<h3 id="📝-사용-예시">📝 사용 예시</h3>
<pre><code class="language-bash">gemini -p &quot;초등학생용 수학 퀴즈 게임을 React로 만들어줘. 덧셈 문제와 점수 시스템 포함해서.&quot;</code></pre>
<h1 id="본격-gemini-셋팅-방법">본격 GEMINI 셋팅 방법</h1>
<h2 id="💻-gemini-cli-잼미니-설치-방법-mac--windows">💻 Gemini CLI (잼미니) 설치 방법 (Mac &amp; Windows)</h2>
<p>Google의 Gemini 모델을 로컬 터미널에서 쓸 수 있는 Gemini CLI는 개발자의 코드 생산성을 높여주는 강력한 AI 도구입니다. 아래는 운영체제별 설치 방법입니다.</p>
<hr>
<h3 id="🍎-macos에서-설치하기">🍎 macOS에서 설치하기</h3>
<h2 id="1-nodejs-20181-이상-설치">1. Node.js 20.18.1 이상 설치</h2>
<ul>
<li>Node.js 설치
<a href="https://nodejs.org/ko/download">https://nodejs.org/ko/download</a>
<img src="https://velog.velcdn.com/images/dev_dan/post/12b3c0c9-2817-4b61-a837-8cf513bf2312/image.png" alt=""></li>
<li>로컬 환경(맥 or 윈도우)에 맞는 버전 설치</li>
<li>여기에서 npm 설치 꼭 하기  (설치는 기초 셋팅 건들이지 않고 다음 &gt; 다음 넘기면 됨)
<code>nvm</code>(Node Version Manager)이 설치되어 있다면 아래 명령어로 Node.js를 설치한다:</li>
</ul>
<pre><code class="language-bash">nvm install 20.18.1
nvm use 20.18.1</code></pre>
<h2 id="2-로컬-컴퓨터-cli터미널로-설치하기">2. 로컬 컴퓨터 CLI(터미널)로 설치하기</h2>
<p>윈도우 : powershell 파워쉘
맥 : Terminal 터미널</p>
<pre><code class="language-bash">npm install -g @google/gemini-cli</code></pre>
<p>만약 실행이 되지 않는다면?</p>
<ul>
<li><p>윈도우 : 관리자 모드로 실행
<img src="https://velog.velcdn.com/images/dev_dan/post/4356b1e0-f46e-470a-84b1-20369f29c5af/image.png" alt=""></p>
<pre><code class="language-bash">Set-ExecutionPolicy RemoteSigned</code></pre>
</li>
<li><p>입력후 다시 설치 명령어 실행하면 된다.</p>
</li>
<li><p>맥 : sudo 붙이기</p>
</li>
</ul>
<pre><code class="language-bash">sudo npm install -g @google/gemini-cli</code></pre>
<p>또는 </p>
<ul>
<li>에러 메시지를 ctrl + c 한뒤 , AI 에게 물어보기</li>
</ul>
<h2 id="4-설치-완료-확인">4. 설치 완료 확인</h2>
<p><img src="https://velog.velcdn.com/images/dev_dan/post/07a0fb95-4108-4156-83bd-ee9dc022a681/image.png" alt=""></p>
<p>이런 메시지가 떴으면 설치 완료!
<img src="https://velog.velcdn.com/images/dev_dan/post/c44bc295-963b-47bb-a10e-47788de6dc0c/image.png" alt=""></p>
<p>원하는 테마 (색상) 선택하고, 구글 계정을 연결한다.</p>
<h2 id="5구글-계정-연결하기">5.구글 계정 연결하기</h2>
<p><img src="https://velog.velcdn.com/images/dev_dan/post/58783266-d943-40b8-94fc-266fc20c6502/image.png" alt=""></p>
<ul>
<li>구글 계정 연동 후 보이는 정상 메시지</li>
</ul>
<h2 id="6-잼미니-사용하기">6. 잼미니 사용하기</h2>
<pre><code class="language-bash">gemini</code></pre>
<p>입력하면 네모난 칸이 뜨는데 이곳에 AI에게 질문하면된다.
예를 들면, useState와 useEffect의 차이점을 한글로 물을 수 있다.
<img src="https://velog.velcdn.com/images/dev_dan/post/7098c227-1df2-4c90-bd35-75963591c17d/image.png" alt=""></p>
<p>참고 : 코딩애플 유튜브
<a href="https://youtu.be/f-Izv0ZIeQs?si=cBEBmCmFlyFXIAun">https://youtu.be/f-Izv0ZIeQs?si=cBEBmCmFlyFXIAun</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트 셋팅 Tailwindcss + VITE(ts) npx tailwindcss init -p 못 찾을 때,, GPT 금지]]></title>
            <link>https://velog.io/@dev_dan/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%85%8B%ED%8C%85-Tailwindcss-VITEts</link>
            <guid>https://velog.io/@dev_dan/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%85%8B%ED%8C%85-Tailwindcss-VITEts</guid>
            <pubDate>Wed, 28 May 2025 03:46:26 GMT</pubDate>
            <description><![CDATA[<p><span style="background-color: yellow;">## ** 1. node.js 설치 **</span>
✅ Node.js 최신 설치</p>
<ul>
<li>nvm 설치하고<pre><code class="language-bash"> nvm install node</code></pre>
</li>
<li>또는 Node.js 공식 설치파일 다운로드
 <a href="https://nodejs.org/ko/download">https://nodejs.org/ko/download</a></li>
</ul>
<p>✅ npm 최신 업데이트</p>
<ul>
<li><p>Node.js 설치하면 기본 업데이트</p>
</li>
<li><p>필요하면 </p>
<pre><code class="language-bash"> npm install -g npm@latest</code></pre>
<p><span style="color: red;">node가 설치되어있다면 </span></p>
<pre><code class="language-bash"> node -v</code></pre>
</li>
</ul>
<p><span style="color: red;">최신 버전이 아니라면  ** nvm 설치**</span></p>
<pre><code class="language-bash">curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash</code></pre>
<p><span style="background-color: yellow;">## <strong>2. 리엑트 프로젝트 생성 (Vite + TypeScript)</strong></span></p>
<pre><code class="language-bash">npm create vite@latest 프로젝트이름 --template react-swc</code></pre>
<ul>
<li>React -&gt; Typescript 선택<img src="https://velog.velcdn.com/images/dev_dan/post/8441aad7-96a7-47d0-a383-9aed5e665c86/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev_dan/post/16ec19d7-593a-4124-b5f6-db931c88c602/image.png" alt=""></li>
</ul>
<p><span style="background-color: yellow;">## *<em>3. 프로젝트 폴더로 이동 *</em></span></p>
<pre><code class="language-bash">cd my-app</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_dan/post/9e2987d8-71cc-4163-b2cd-48ada87a8c5b/image.png" alt=""></p>
<p><span style="background-color: yellow;">## *<em>4. TailwindCSS 설치 *</em></span></p>
<pre><code class="language-bash">npm install -D tailwindcss@3 postcss autoprefixer</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_dan/post/b458cb33-8cdd-4618-81e6-a462320c90a3/image.png" alt=""></p>
<p><span style="background-color: yellow;">## *<em>5. Tailwind 설정 파일 생성 *</em></span></p>
<pre><code class="language-bash">npx tailwindcss init -p</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_dan/post/15dd2f1c-66c7-4ae8-a04d-7b8d3e8510d4/image.png" alt="">
생성이 되면 이렇게 뜬다
<img src="https://velog.velcdn.com/images/dev_dan/post/e91ae449-f792-4142-a8d8-d9b63abbba9b/image.png" alt=""></p>
<p>만약 생성이 안된다면? </p>
<ul>
<li>package.json이 있는 경로에 tailwind.config.js 파일 직접 만들기</li>
<li>기본파일 이렇게 되어있음<pre><code class="language-js">/** @type {import(&#39;tailwindcss&#39;).Config} */
export default {
content: [],
theme: {
  extend: {},
},
plugins: [],
}
</code></pre>
</li>
</ul>
<pre><code>- 주의할 점은 여기에 content가 비어있으면 안된다.
-  Tailwind가 아무 파일도 검사하지 않아서 스타일을 생성하지 않게된다.

**content에 프로젝트의 src 폴더 내 파일들을 지정**
``` ts
export default {
  content: [&quot;./index.html&quot;, &quot;./src/**/*.{js,ts,jsx,tsx}&quot;],
  theme: {
    extend: {},
  },
  plugins: [],
};</code></pre><p><span style="background-color: yellow;">## *<em>6. CSS 파일에 Tailwindimport *</em></span>
src/index.css 또는 App.css에 아래와 같이 import</p>
<pre><code class="language-css">@tailwind base;
@tailwind components;
@tailwind utilities;</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_dan/post/5c7d9ca6-9db7-4497-bbc6-0476a2303260/image.png" alt=""></p>
<p>노란줄이 생긴더라도 문제 없다.
<img src="https://velog.velcdn.com/images/dev_dan/post/c8391846-b38d-40ad-b9f8-e54ea1720c6c/image.png" alt=""></p>
<p><span style="background-color: yellow;">## ** 7. 메인 파일에서 CSS 연결 (main.ts) **</span></p>
<ul>
<li>이미 연결되어 있으므로 패스해도 됨<pre><code class="language-ts">import { createRoot } from &quot;react-dom/client&quot;;
import &quot;./index.css&quot;;
import App from &quot;./App&quot;;
</code></pre>
</li>
</ul>
<p>createRoot(document.getElementById(&quot;root&quot;)!).render(<App />);</p>
<pre><code>
&lt;span style=&quot;background-color: yellow;&quot;&gt;## ** 8. 노드 모듈스 설치 **&lt;/span&gt;
```bash
npm i</code></pre><h4 id="-하지만-나는-tsconfigappjson파일에서-빨간-줄-에러-발생-">** 하지만 나는 tsconfig.app.json파일에서 빨간 줄 에러 발생 **</h4>
<p><img src="https://velog.velcdn.com/images/dev_dan/post/59548480-dcda-4950-9985-96b6d8090519/image.png" alt=""></p>
<pre><code class="language-ts">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES2020&quot;,
    &quot;useDefineForClassFields&quot;: true,
    &quot;lib&quot;: [&quot;ES2020&quot;, &quot;DOM&quot;, &quot;DOM.Iterable&quot;],
    &quot;module&quot;: &quot;ESNext&quot;,
    &quot;skipLibCheck&quot;: true,

    /* Bundler mode */
    &quot;moduleResolution&quot;: &quot;bundler&quot;,
    &quot;allowImportingTsExtensions&quot;: true,
    &quot;verbatimModuleSyntax&quot;: true,
    &quot;moduleDetection&quot;: &quot;force&quot;,
    &quot;noEmit&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;,

    /* Linting */
    &quot;strict&quot;: true,
    &quot;noUnusedLocals&quot;: true,
    &quot;noUnusedParameters&quot;: true,
    &quot;noFallthroughCasesInSwitch&quot;: true
  },
  &quot;include&quot;: [&quot;src&quot;]
}</code></pre>
<p>해당 에러 부분을 과감하게 지워준다. 
<img src="https://velog.velcdn.com/images/dev_dan/post/d4796a0d-aa92-4555-aa83-85a9af53a9ff/image.png" alt=""></p>
<p>마찬가지로 tsconfig.node.json에도 에러 발생;;
<img src="https://velog.velcdn.com/images/dev_dan/post/efed05f4-79a5-4dbc-9e0b-23d8fee31ca2/image.png" alt=""></p>
<p>이경우엔 정상 작동되는 코드로 변경해준다.</p>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es5&quot;,
    &quot;lib&quot;: [&quot;dom&quot;, &quot;dom.iterable&quot;, &quot;esnext&quot;],
    &quot;allowJs&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;strict&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;noFallthroughCasesInSwitch&quot;: true,
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;resolveJsonModule&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;
  },
  &quot;include&quot;: [&quot;src&quot;]
}
</code></pre>
<p><span style="background-color: yellow;">## ** 9. Tailwind 반영 확인용 UI 생성(test 파일) **</span></p>
<ul>
<li>App.tsx<pre><code class="language-ts">import &quot;./App.css&quot;;
</code></pre>
</li>
</ul>
<p>function App() {
  return (
    <div className="w-full h-full bg-gray-100 flex items-center justify-center">
      <h1 className="text-10xl font-bold text-blue-500">
        🎨 TailwindCSS 반영 확인 🎨
      </h1>
    </div>
  );
}</p>
<p>export default App;</p>
<pre><code>
변경을 했는데 에러처럼 빨간색으로 보인다면
당황하지 않고 프로그램을 종료하였다가 다시 키면 된다.

&lt;span style=&quot;background-color: yellow;&quot;&gt;## ** 9. 개발 서버 실행 **&lt;/span&gt;
```bash
npm run dev</code></pre><p><span style="background-color: yellow;">## ** 10. Tailwind UI 반영 확인 **</span></p>
<p><img src="https://velog.velcdn.com/images/dev_dan/post/2ab138ce-02fa-4863-acb2-89927952223a/image.png" alt=""></p>
<p>이런 화면이 나와야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[비로그 Velog 사용 방법]]></title>
            <link>https://velog.io/@dev_dan/%EB%B9%84%EB%A1%9C%EA%B7%B8-Velog-%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@dev_dan/%EB%B9%84%EB%A1%9C%EA%B7%B8-Velog-%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Tue, 29 Apr 2025 01:12:45 GMT</pubDate>
            <description><![CDATA[<h2 id="🖼️-velog-내-페이지-꾸미는-기본-방법">🖼️ Velog 내 페이지 꾸미는 기본 방법</h2>
<h4 id="1-프로필아바타-이미지-변경-방법">1. 프로필(아바타) 이미지 변경 방법</h4>
<ul>
<li><p>Velog 로그인 → 오른쪽 상단 프로필 사진 클릭 → 설정(Settings) 이동</p>
</li>
<li><p>&quot;프로필&quot; 항목에서 👉 <strong>이미지 업로드</strong> 가능 (JPG, PNG 파일 사용)</p>
</li>
</ul>
<h3 id="✅-팁-깔끔한-본인-로고나-심플한-아이콘-추천">✅ 팁: 깔끔한 본인 로고나 심플한 아이콘 추천.</h3>
<h4 id="2-소개글-about-작성-방법">2. 소개글 (About) 작성 방법</h4>
<ul>
<li>설정(Settings) → &quot;자기소개&quot; 입력란에 <strong>간단한 소개</strong> 작성. (예: &quot;자바스크립트를 공부하는 신입 개발자입니다.&quot;)</li>
</ul>
<h3 id="✅-팁-짧고-인상적인-문장-23줄이면-충분하다">✅ 팁: 짧고 인상적인 문장 2~3줄이면 충분하다.</h3>
<h4 id="3-커버-이미지-설정-페이지-상단-배경-방법">3. 커버 이미지 설정 (페이지 상단 배경) 방법</h4>
<ul>
<li>설정(Settings) → &quot;커버 이미지 업로드&quot; 배경 이미지를 추가하면, 내 Velog <strong>메인 페이지</strong> 상단에 크게 나온다.</li>
</ul>
<h3 id="✅-팁-배경은-너무-화려하면-가독성-떨어짐">✅ 팁: 배경은 너무 화려하면 가독성 떨어짐.</h3>
<p>심플하거나 연한 그라데이션 배경 추천.</p>
<h4 id="4-소셜-링크-추가-github-blog-website-등-방법">4. 소셜 링크 추가 (GitHub, Blog, Website 등) 방법</h4>
<ul>
<li>설정(Settings) → &quot;SNS 링크&quot; 추가 (GitHub, Instagram, <strong>개인 웹사이트 URL</strong> 입력 가능)</li>
</ul>
<h3 id="✅-팁">✅ 팁:</h3>
<ul>
<li><p><strong>GitHub 프로필 주소</strong>는 꼭 넣는 걸 추천한다.</p>
</li>
<li><p><strong>포트폴리오 사이트</strong> 있으면 추가하면 좋다.</p>
</li>
</ul>
<h4 id="5-태그카테고리-관리-방법">5. 태그/카테고리 관리 방법</h4>
<ul>
<li><p>글 작성할 때 <strong>&quot;태그&quot; 추가</strong> 가능</p>
</li>
<li><p>&quot;카테고리&quot; 지정해서 블로그 <strong>주제별로 나누기</strong></p>
</li>
</ul>
<h3 id="✅-팁-1">✅ 팁:</h3>
<ul>
<li><p>ex) 자바스크립트 공부글은 Javascript <strong>카테고리로 묶기</strong></p>
</li>
<li><p>글 목록 정리가 깔끔해진다.</p>
</li>
</ul>
<h3 id="🎨-velog-고급-꾸미기-디자인-직접-커스터마이징">🎨 Velog 고급 꾸미기 (디자인 직접 커스터마이징)</h3>
<ul>
<li>Velog는 직접 디자인 편집은 못 하지만, 특정 설정으로 &quot;내 블로그 느낌&quot;을 낼 수 있다.</li>
</ul>
<h4 id="팁들">팁들</h4>
<ul>
<li><p>썸네일 이미지를 <strong>통일된 스타일</strong>로 만든다.</p>
</li>
<li><p>글 제목 스타일을 통일한다. ([Javascript] var/let/const 정리 같은 형식)</p>
</li>
<li><p>본문에도 <strong>이모지(✅, 🚀, 🧠) 활용하면 가독성 좋아진다.</strong></p>
</li>
</ul>
<h2 id="🧹-정리-한-줄">🧹 정리 한 줄</h2>
<p>➔ Velog 내 페이지 꾸미려면 설정(Settings) 가서 프로필, 커버, 소개글, 소셜 링크를 설정하고, 글 쓸 때는 태그/카테고리 잘 나누면 된다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[변수]]></title>
            <link>https://velog.io/@dev_dan/%EB%B3%80%EC%88%98</link>
            <guid>https://velog.io/@dev_dan/%EB%B3%80%EC%88%98</guid>
            <pubDate>Tue, 29 Apr 2025 00:40:11 GMT</pubDate>
            <description><![CDATA[<h1 id="📝-javascript-변수---var-let-const-정리">📝 JavaScript 변수 - var, let, const 정리</h1>
<h2 id="1-var">1. var</h2>
<ul>
<li>ES5까지 사용된 변수 선언 방법</li>
<li><strong>중복 선언 가능</strong></li>
<li><strong>호이스팅 발생</strong> (선언만 위로 끌어올려짐, 초기화는 안 됨)</li>
<li><strong>함수 스코프</strong>를 가진다. ({} 중괄호를 무시하고 함수 단위로 스코프를 가짐)</li>
</ul>
<h3 id="📌-예시">📌 예시</h3>
<pre><code class="language-javascript">console.log(a); // undefined (호이스팅)
var a = 5;
console.log(a); // 5

function test() {
  if (true) {
    var x = 10;
  }
  console.log(x); // 10 (if문 블록 무시하고 접근 가능)
}
test();                                </code></pre>
<h2 id="2-let">2. let</h2>
<ul>
<li><p>ES6(2015)부터 등장</p>
</li>
<li><p><strong>중복 선언 불가</strong></p>
</li>
<li><p><strong>호이스팅은 발생하지만 초기화 전에 접근하면 에러 (Temporal Dead Zone)</strong></p>
</li>
<li><p><strong>블록 스코프</strong>를 가진다. ({} 안에서만 유효)</p>
<h3 id="📌-예시-1">📌 예시</h3>
<pre><code class="language-javascript">let b = 10;
b = 20; // 값 변경 가능
</code></pre>
</li>
</ul>
<p>function testLet() {
  if (true) {
    let y = 30;
    console.log(y); // 30
  }
  console.log(y); // ReferenceError (블록 스코프)
}
testLet();</p>
<pre><code>
## 3. const
- ES6부터 등장

- **값 재할당 불가 (초기화 이후 값 변경 금지)**

- **호이스팅은 발생하지만 접근 시 에러**

- **블록 스코프**를 가진다.

### 📌 예시
```javascript
const c = 100;
c = 200; // TypeError: Assignment to constant variable.

const arr = [1, 2, 3];
arr.push(4); // 가능 (참조 주소는 그대로, 내부 데이터 변경)

const obj = { name: &#39;John&#39; };
obj.name = &#39;Jane&#39;; // 가능</code></pre><h3 id="📚-var-let-const-비교표">📚 var, let, const 비교표</h3>
<table>
<thead>
<tr>
<th align="left">특징</th>
<th align="left">var</th>
<th align="left">let</th>
<th align="left">const</th>
</tr>
</thead>
<tbody><tr>
<td align="left">선언 시점</td>
<td align="left">끌어올림(hoisting)</td>
<td align="left">끌어올림, TDZ 발생</td>
<td align="left">끌어올림, TDZ 발생</td>
</tr>
<tr>
<td align="left">스코프</td>
<td align="left">함수 스코프</td>
<td align="left">블록 스코프</td>
<td align="left">블록 스코프</td>
</tr>
<tr>
<td align="left">재선언</td>
<td align="left">가능</td>
<td align="left">불가</td>
<td align="left">불가</td>
</tr>
<tr>
<td align="left">재할당</td>
<td align="left">가능</td>
<td align="left">가능</td>
<td align="left">불가 (단, 객체/배열 내부 변경은 가능)</td>
</tr>
</tbody></table>
<h3 id="✏️-정리">✏️ 정리</h3>
<ul>
<li><p>var는 이제 사용 지양!</p>
</li>
<li><p>변할 수 있는 값이면 <strong>let</strong></p>
</li>
<li><p>변하지 않는 값이면 <strong>const</strong>를 사용하는 것이 기본.</p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>