<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jiwon_96.log</title>
        <link>https://velog.io/</link>
        <description>hi there~!</description>
        <lastBuildDate>Tue, 24 Feb 2026 07:30:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jiwon_96.log</title>
            <url>https://velog.velcdn.com/images/jiwon_96/profile/e7820981-2884-4758-ad5d-4ae0ee2c808e/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jiwon_96.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jiwon_96" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[사내 홈페이지 프롬프트 엔지니어링 도입 일지 기록]]></title>
            <link>https://velog.io/@jiwon_96/%EC%82%AC%EB%82%B4-%ED%99%88%ED%8E%98%EC%9D%B4%EC%A7%80-%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-%EB%8F%84%EC%9E%85-%EC%9D%BC%EC%A7%80-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@jiwon_96/%EC%82%AC%EB%82%B4-%ED%99%88%ED%8E%98%EC%9D%B4%EC%A7%80-%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-%EB%8F%84%EC%9E%85-%EC%9D%BC%EC%A7%80-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Tue, 24 Feb 2026 07:30:43 GMT</pubDate>
            <description><![CDATA[<h3 id="1-취지-및-도입-배경"><strong>1. 취지 및 도입 배경</strong></h3>
<p>사내 홈페이지 운영에 AI를 도입하자는 의견으로 시작되었다.
사내에 디자이너 혹은 개발자 부재 상황이 SI회사 특성상 빈번하게 발생하므로, 
사내 홈페이지는 방치되는 상황이 다수였다.
따라서, 개발자 및 디자이너의 부재 상황에서도 회사 연혁 및 사소한 디자인을 변경하고자 AI를 도입했다.</p>
<h3 id="2-진행-과정"><strong>2. 진행 과정</strong></h3>
<p>1️⃣ 우선, figma MCP 연동을 진행하였다. 
   Figma MCP 와 claude desktop 을 이용해  figma 를 직접 작업하지 않고, claude에서 figma 화면을 그려주는 것을 첫 목표로 하였다. </p>
<blockquote>
<p>*<em>Figma MCP 연동과정  *</em>
          1. claude desktop app 설치
          2. bunx 설치
          3. claude desktop app &gt; 설정 &gt; 개발자모드에서 플러그인 설정
          4. figma 유료 버전 구독 및 플러그인 설치</p>
</blockquote>
<p>위와 같은 과정을 거쳐 피그마에 직접 손대지 않아도 claude를 이용해 피그마에 원하는 디자인을 그릴 수 있다.</p>
<br/>

<p>2️⃣ 두번째로, vscode 상에서 figma-watcher.js 파일을 생성하여 피그마의 디자인을 반영하는 프롬프트를 작성해야한다. 
해당 내용에서는 스타일링 및 커밋 자동화까지 가능하며, 
컴포넌트의 형태등을 프로젝트 성격에 맞게 커스텀할 수 있다.</p>
<br/>

<p>3️⃣ 위의 두가지를 진행 후에 node 로 실행하여 직접 피그마 디자인이 vscode 상에 파일이 생성 및 자동 커밋푸시 되는것까지 테스트를 진행해보았다.</p>
<br/>


<p>4️⃣ 간단한 연동 테스트를 진행했으니, 본격적으로 회사 홈페이지의 구조 명세서를 작성해보기로 했다.
 프롬프트를 위한 효과적인 엑셀 파일 작성을 위해서는 홈페이지 구조 명세서가 명확해야한다. 따라서 문서를 먼저 작성하고자 홈페이지 구조명세서를 작성했다. 
 물론 홈페이지 구조명세서도 클로드를 이용해 작성했다.</p>
<p><img src="https://velog.velcdn.com/images/jiwon_96/post/f313d3e4-5990-4605-a9a9-fe33f8272ab8/image.png" alt=""></p>
<br/>


<p>5️⃣ 홈페이지 구조 명세서를 이용해 엑셀 템플릿을 만들었다.
비개발자가 정해진 양식에 값만 입력하면 홈페이지에 반영되는 구조를 목표로 했다.</p>
<br/>


<p>6️⃣ 현재까지는 로컬에 있는 엑셀 파일을 수정하여 연동 테스트를 진행해왔다. 
   하지만, 궁극적으로 원하는건 접근 권한을 가진 담당자가 수정하는 것이므로, 
   ** Google 공유 스프레드 시트 ** 로 수정하는 것을 최종 목표로 하였다.  </p>
<br/>

<p>   ** 📌 Google 공유 스프레드 시트 선택 이유 **</p>
<table>
<thead>
<tr>
<th>도구</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>Notion DB</td>
<td>UI 친숙, API 지원</td>
<td>구조 복잡, 비개발자 러닝커브</td>
</tr>
<tr>
<td>별도 CMS (Contentful 등)</td>
<td>전문성 높음</td>
<td>유료, 추가 학습 필요</td>
</tr>
<tr>
<td><strong>Google Sheets</strong></td>
<td><strong>즉시 사용 가능, 실시간 공유, 엑셀과 동일한 UX</strong></td>
<td>전용 CMS 대비 기능 제한</td>
</tr>
</tbody></table>
<p>많은 툴 중에서 결정 이유는 단순했다. <strong>비개발자가 이미 알고 있는 도구</strong>여야 했다.
Google Sheets는 엑셀과 동일한 UX에 실시간 공유 기능까지 갖추고 있어,
별도 교육 없이 바로 투입 가능했다.</p>
<hr>
<h4 id="🚨-figma-api-한계">🚨 Figma API 한계</h4>
<p><strong>비개발자도 엑셀만 수정하면 홈페이지가 자동으로 변경되는 시스템</strong>을 목표로
아래와 같은 구조를 설계하였다.</p>
<blockquote>
<p>Excel 수정 → Figma 반영 → Homepage 배포</p>
</blockquote>
<p>그러나 실제 구현 과정에서 테스트를 진행해보니, 로컬 파일의 엑셀을 수정해 수동으로 배포하면, 코드상의 스타일(피그마에서 수정한)이 초기화되는 이슈가 있었다.</p>
<p>그 이유는 Figma API의 구조적인 제약사항 때문이었다.</p>
<blockquote>
<p>❗ 문제 발생 : Figma API는 Read Only</p>
</blockquote>
<p>Figma API를 활용하여
엑셀에 입력된 데이터를 디자인 시안에 자동 반영하고자 하였으나,</p>
<p>Figma의 공식 REST API는 파일 및 노드 정보를 조회(Read)만 가능하고 수정(Write)은 지원하지 않는다.</p>
<p>결과적으로, 엑셀 변경사항을 디자인 시안에 반영하기 위해서는 피그마 내부에서 수동 수정 작업이 필요하다는 한계가 존재했다.</p>
<hr>
<h3 id="3--자동화-구조-재설계-architecture-pivot"><strong>3.  자동화 구조 재설계 (Architecture Pivot)</strong></h3>
<p>이에 따라 기존 자동화 흐름인</p>
<blockquote>
<p>Excel 수정 → Figma 반영 → Homepage 배포</p>
</blockquote>
<p>는 실제 운영 환경에 적합하지 않다고 판단하였고,</p>
<p>디자인 수정 단계를 제외한 콘텐츠 중심 자동화 구조로 아키텍처를 재설계하였다.</p>
<blockquote>
</blockquote>
<p>Google Sheets 수정
        ↓
GitHub Actions 트리거
        ↓
정적 데이터(JSON) 변환
        ↓
Vite 빌드 수행
        ↓
Vercel 자동 배포
        ↓
홈페이지 실시간 반영</p>
<hr>
<h3 id="3-2-google-drive-api-연동-방식">3-2. Google Drive API 연동 방식</h3>
<p>Google Sheets에 저장된 데이터를 코드에서 가져오기 위해
<strong>Google Drive API + Service Account 인증</strong> 방식을 사용했다.</p>
<h3 id="oauth-vs-service-account">OAuth vs Service Account</h3>
<p>처음에는 OAuth 2.0을 고려했지만, 자동화 파이프라인에는 적합하지 않았다.
OAuth는 사용자가 매번 브라우저에서 직접 로그인해야 하기 때문이다.</p>
<p><strong>Service Account</strong>는 사람 없이 서버끼리 인증하는 방식이다.
Google Cloud Console에서 서비스 계정을 생성하고,
해당 계정의 이메일 주소를 Google Sheets의 공유 권한에 추가하면
코드에서 직접 파일에 접근할 수 있다.</p>
<p>Google Cloud Console → 서비스 계정 생성
JSON 키 발급 → service-account.json
Google Sheets → 공유 → 서비스 계정 이메일 추가 (뷰어 권한)
GitHub Secrets에 JSON 키 내용 저장</p>
<p>GitHub Actions에서 사용할 때는 <code>GOOGLE_SERVICE_ACCOUNT_KEY</code> 시크릿에
JSON 키 내용을 저장하고, 워크플로우 실행 시 파일로 복원한다.</p>
<hr>
<h3 id="3-3-스프레드시트-구조-설계">3-3. 스프레드시트 구조 설계</h3>
<p>단순히 데이터를 한 시트에 모두 작성하면 유지보수가 어렵다.
페이지별로 시트를 분리하고, <strong>필드ID</strong> 체계를 도입했다.</p>
<p>  <img src="https://velog.velcdn.com/images/jiwon_96/post/93c7a2cf-ec11-4e6b-a019-3cbee7e4d37c/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>시트명</th>
<th>관리 항목</th>
</tr>
</thead>
<tbody><tr>
<td><code>1.설정</code></td>
<td>메인 컬러, 폰트 등 디자인 시스템</td>
</tr>
<tr>
<td><code>2.홈페이지</code></td>
<td>히어로 문구, 서비스 소개, 주요 고객사</td>
</tr>
<tr>
<td><code>3.About</code></td>
<td>회사 연혁 타임라인, 핵심 가치</td>
</tr>
<tr>
<td><code>4.Product</code></td>
<td>제품명, 설명, 기능, 데모 링크</td>
</tr>
<tr>
<td><code>5.Recruit</code></td>
<td>복리후생, 채용 절차, 연락처</td>
</tr>
<tr>
<td><code>6.Contact</code></td>
<td>회사 주소, 지도 링크</td>
</tr>
<tr>
<td><code>7.Business</code></td>
<td>사업 문의 솔루션 카드</td>
</tr>
<tr>
<td><code>8.네비게이션</code></td>
<td>메뉴 항목, 로고, CTA 버튼</td>
</tr>
<tr>
<td><code>9.Footer</code></td>
<td>회사 정보, 저작권 문구</td>
</tr>
</tbody></table>
<p>각 행은 <code>필드ID / 현재값 / 타입 / 설명</code> 컬럼으로 구성했다.
비개발자는 <strong>현재값</strong> 컬럼만 수정하면 된다. 필드ID는 코드에서 키값으로 사용한다.</p>
<hr>
<h3 id="3-4-excel-→-json-변환-파이프라인">3-4. Excel → JSON 변환 파이프라인</h3>
<p>Google Drive에서 Sheets 파일을 다운로드하면 <code>.xlsx</code> 포맷으로 저장된다.
<code>xlsx</code> 라이브러리로 파싱하여 구조화된 JSON으로 변환하는 스크립트를 작성했다.</p>
<pre><code>Google Sheets
↓ (Google Drive API로 xlsx 다운로드)
제타소프트_CMS.xlsx
↓ (excel-to-prompt.ts 실행)
outputs/cms_data.json
↓ (React 컴포넌트에서 import)
홈페이지에 렌더링</code></pre><p>변환 스크립트의 핵심 로직은 시트별로 <code>{ [fieldId]: value }</code> 형태의 맵을 만드는 것이다.
컴포넌트에서는 <code>cms_data.json</code>을 import하여 기본값과 병합(<code>{...defaultData, ...cmsData}</code>)하는
방식으로 사용하기 때문에, CMS 데이터가 없는 필드는 기본값으로 fallback된다.</p>
<hr>
<h3 id="3-5-github-actions-트리거-이중-구조">3-5. GitHub Actions 트리거 이중 구조</h3>
<p>자동 배포 파이프라인의 트리거는 두 가지를 병행한다.</p>
<blockquote>
<h4 id="트리거-1-크론-스케줄-polling">트리거 1: 크론 스케줄 (Polling)</h4>
</blockquote>
<p>평일 업무시간(09:00~18:00 KST) 동안 <strong>10분 간격</strong>으로 GitHub Actions가 실행된다.
Google Drive에서 파일을 다운로드하고, 기존 <code>cms_data.json</code>과 비교하여 <strong>변경이 있을 때만</strong> 커밋 &amp; 배포한다.</p>
<pre><code class="language-yaml">on:
  schedule:
    # 평일 09:00~18:00 KST (UTC 기준 00:00~09:00)
    - cron: &#39;*/10 0-9 * * 1-5&#39;</code></pre>
<blockquote>
<h4 id="트리거-2-google-apps-script-event-driven">트리거 2: Google Apps Script (Event-Driven)</h4>
</blockquote>
<p>Sheets를 수정하는 즉시 배포가 트리거되도록 Google Apps Script로 repository_dispatch 이벤트를 발송한다.</p>
<pre><code class="language-yaml">// Google Sheets → Apps Script → GitHub Actions
function onEdit(e) {
  // 2분 디바운스 (중복 트리거 방지)
  UrlFetchApp.fetch(&#39;https://api.github.com/repos/{owner}/{repo}/dispatches&#39;, {
    method: &#39;post&#39;,
    headers: { Authorization: &#39;token {PAT}&#39; },
    payload: JSON.stringify({ event_type: &#39;cms-update&#39; })
  });
}
</code></pre>
<p>두 트리거를 병행하는 이유는 신뢰성 때문이다.
Apps Script는 즉각적이지만 설정이 없거나 오류가 나면 반영이 안 될 수 있다.
크론은 느리지만(최대 10분 지연) 어떤 상황에서도 주기적으로 반영을 보장한다.</p>
<h3 id="4-마치며"><strong>4. 마치며</strong></h3>
<p>이번 작업을 통해 비개발자도 Google Sheets만으로 홈페이지를 관리할 수 있는 구조를 완성했다.
Figma API 한계라는 예상치 못한 장벽을 만났지만, 오히려 더 단순하고 안정적인 아키텍처로 
방향을 전환할 수 있었다. 
현재 구조는 <strong>콘텐츠(텍스트, 링크)</strong> 변경은 완전 자동화되어 있지만,
<strong>디자인(레이아웃, 색상, 컴포넌트 구조)</strong> 변경은 여전히 개발자가 코드를 직접 수정해야한다.</p>
<p>궁극적인 목표는 이 한계도 없애는 것이다.
Figma의 Write API가 공식 지원된다면, 또는 Figma Plugin 방식으로 우회할 수 있다면
아래와 같은 완전 자동화 파이프라인이 가능해진다.</p>
<pre><code>Google Sheets 수정 (콘텐츠)
       +
Figma 수정 (디자인)
       ↓
GitHub Actions 트리거
       ↓
콘텐츠 JSON + 디자인 토큰 변환
       ↓
Vite 빌드 수행
       ↓
Vercel 자동 배포
       ↓
홈페이지 실시간 반영</code></pre><p>추후로 진행하게 된다면 이부분을 보완해보도록 하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GNB, LNB, SNB, FNB 용어 정리]]></title>
            <link>https://velog.io/@jiwon_96/GNB-LNB-SNB-FNB-%EC%9A%A9%EC%96%B4-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jiwon_96/GNB-LNB-SNB-FNB-%EC%9A%A9%EC%96%B4-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 11 Mar 2024 00:54:00 GMT</pubDate>
            <description><![CDATA[<h3 id="gnb-global-navigation-bar">GNB (global navigation bar)</h3>
<p> : 최상위 메뉴로 보통 상단에 위치하고 있다. 메인메뉴로도 부름.
<img src="https://velog.velcdn.com/images/jiwon_96/post/8ae6fcf7-de1f-402b-a2e7-fad620a9c12e/image.PNG" alt=""></p>
<h3 id="lnb">LNB</h3>
<h4 id="1-local-navigation-bar">1. Local Navigation Bar</h4>
<p> GNB를 누를 경우 소제목 형식으로 나오는 메뉴를 가르킨다.
 네이게이션을 통해 특정 지역으로 가능 네비게이션 바이다.
 <img src="https://velog.velcdn.com/images/jiwon_96/post/edc9421b-19ba-4080-b0d6-f5a610d9d9fc/image.PNG" alt=""></p>
<h4 id="2-left-navigation-bar">2. Left Navigation Bar</h4>
<p>SNB가 Left에 위치해 있을 때, LNB 라고도 부른다.</p>
<h3 id="snb-side-navigation-bar">SNB (Side navigation bar)</h3>
<p>메인 메뉴, 서브 메뉴를 제외한 나머지 사이드 메뉴.
보편적으로 왼쪽에 많이 있어 LNB라고도 함.
기타메뉴바 라고도 한다.
<img src="https://velog.velcdn.com/images/jiwon_96/post/1f07fd80-1c50-4fea-8448-04c0b5feda2b/image.png" alt=""></p>
<h3 id="fnb-footer-navigation-bar">FNB (Footer navigation bar)</h3>
<p>하단 메뉴, 하단 로고, 주소, 카피라이팅 영역을 말한다.
<img src="https://velog.velcdn.com/images/jiwon_96/post/2efa1985-1544-48b7-9f91-7af71c5b5116/image.PNG" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[children에게 Props 전달하기]]></title>
            <link>https://velog.io/@jiwon_96/%EA%B3%B5%ED%86%B5-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%97%90%EC%84%9C-Props-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jiwon_96/%EA%B3%B5%ED%86%B5-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%97%90%EC%84%9C-Props-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 08 Feb 2024 12:52:40 GMT</pubDate>
            <description><![CDATA[<p>여러 곳에서 사용하는 공통된 컴포넌트의 모든 자식 요소에 props를 전달하고 싶었다.</p>
<pre><code class="language-javascript">export const CommonComponent = () =&gt; {
   const [state, dispatch] = useReducer(reducer, {
    status: status || &#39;enable&#39;,
  });
    return (
      &lt;div&gt;
          {children} // status를 children에게 모두 전달하고 싶다!
      &lt;/div&gt;
    )
}
function reducer(state: any, action: any) {
  switch (action) {
    case &#39;mouse_enter&#39;:
      return {
        ...state,
        status: &#39;hover&#39;,
      };

    case &#39;mouse_leave&#39;:
      return {
        ...state,
        status: &#39;enable&#39;,
      };
  }

  return state;
}
</code></pre>
<p>굳이 Provider를 만들고 싶지 않았는데, 찾다가 새로운 걸 발견했다.</p>
<p><a href="https://react.dev/reference/react/cloneElement#cloneelement">https://react.dev/reference/react/cloneElement#cloneelement</a></p>
<p>cloneElement를 이용해서 아래와 같이 전달하고 싶은 props를 지정할 수 있었다.</p>
<pre><code class="language-javascript">export const CommonComponent = () =&gt; {
   const [state, dispatch] = useReducer(reducer, {
    status: status || &#39;enable&#39;,
  });
    return (
      &lt;div&gt;
      {cloneElement(children, { status: state.status })}
      &lt;/div&gt;
    )
}
function reducer(state: any, action: any) {
  switch (action) {
    case &#39;mouse_enter&#39;:
      return {
        ...state,
        status: &#39;hover&#39;,
      };

    case &#39;mouse_leave&#39;:
      return {
        ...state,
        status: &#39;enable&#39;,
      };
  }

  return state;
}
</code></pre>
<p>하지만 기본적으로 cloneElement는 추천되지는 않는다.
왜냐하면, props가 전달되고 있는지 상위 컴포넌트에서는 코드만 보고 알 수 없기 때문이다.
따라서, 좋은 코드가 아니다.
이러한 <a href="https://react.dev/reference/react/cloneElement#alternatives">대안</a>을 이용해서 아래처럼 다시 수정해보았다.</p>
<pre><code class="language-javascript">interface Props {
  status: &#39;enable&#39; | &#39;active&#39; | &#39;hover&#39;;
  children: (status: &#39;enable&#39; | &#39;active&#39; | &#39;hover&#39;) =&gt; JSX.Element;
}
export const CommonComponent = ({ status, icon }: Props) =&gt; {
   const [state, dispatch] = useReducer(reducer, {
    status: status || &#39;enable&#39;,
  });
    return (
      &lt;div&gt;
          {children(state.status)}
      &lt;/div&gt;
    )
}
function reducer(state: any, action: any) {
  switch (action) {
    case &#39;mouse_enter&#39;:
      return {
        ...state,
        status: &#39;hover&#39;,
      };

    case &#39;mouse_leave&#39;:
      return {
        ...state,
        status: &#39;enable&#39;,
      };
  }

  return state;
}

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[SEO(검색 엔진 최적화) open graph 설정하기]]></title>
            <link>https://velog.io/@jiwon_96/SEO%EA%B2%80%EC%83%89-%EC%97%94%EC%A7%84-%EC%B5%9C%EC%A0%81%ED%99%94-open-graph-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jiwon_96/SEO%EA%B2%80%EC%83%89-%EC%97%94%EC%A7%84-%EC%B5%9C%EC%A0%81%ED%99%94-open-graph-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 23 Jan 2024 01:57:19 GMT</pubDate>
            <description><![CDATA[<p>SEO(검색 엔진 최적화)에서 Open Graph는 웹 페이지의 메타데이터를 정의하는 데 사용되는 프로토콜 중 하나입니다. 주로 소셜 미디어 플랫폼에서 링크를 공유할 때 사용되며, 웹 페이지가 어떻게 보이고 공유되는지에 대한 정보를 제공합니다. Open Graph 태그는 페이스북에서 시작되었지만, 지금은 다른 소셜 미디어 플랫폼에서도 지원되고 있습니다.</p>
<p>일반적으로 Open Graph 메타데이터는 웹 페이지의 <code>&lt;head&gt;</code> 섹션에 추가됩니다. 아래는 몇 가지 주요 Open Graph 태그의 예시입니다:</p>
<pre><code>og:title: 페이지의 제목을 나타냅니다.
&lt;meta property=&quot;og:title&quot; content=&quot;페이지 제목&quot; /&gt;

og:type: 페이지의 유형을 지정합니다. 예를 들어, &#39;website&#39;, &#39;article&#39;, &#39;video&#39; 등이 있습니다.
&lt;meta property=&quot;og:type&quot; content=&quot;website&quot; /&gt;

og:description: 페이지에 대한 간단한 설명을 제공합니다.
&lt;meta property=&quot;og:description&quot; content=&quot;페이지에 대한 간략한 설명&quot; /&gt;

og:image: 페이지를 대표하는 이미지의 URL을 지정합니다.
&lt;meta property=&quot;og:image&quot; content=&quot;이미지 URL&quot; /&gt;

og:url: 페이지의 URL을 지정합니다.
&lt;meta property=&quot;og:url&quot; content=&quot;페이지 URL&quot; /&gt;</code></pre><p>이러한 Open Graph 태그는 소셜 미디어 플랫폼에서 링크를 공유할 때, 해당 페이지의 적절한 제목, 설명, 이미지 등을 플랫폼에 제공하여 공유 시에 눈에 띄도록 하고 사용자에게 더 많은 정보를 제공할 수 있습니다. 이는 페이지의 가시성과 클릭률을 높이는 데 도움이 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js13 dynamic route]]></title>
            <link>https://velog.io/@jiwon_96/Next.js13-dynamic-route</link>
            <guid>https://velog.io/@jiwon_96/Next.js13-dynamic-route</guid>
            <pubDate>Thu, 14 Dec 2023 19:14:19 GMT</pubDate>
            <description><![CDATA[<h2 id="상황">상황</h2>
<p>Next.js 13 버전을 이용해서 프로젝트 진행 중 dynamic route를 사용할 일이 있었다.
리스트에서 사진을 클릭 시 사진 디테일 페이지로 넘어가게 해야했다.</p>
<p><img src="https://velog.velcdn.com/images/jiwon_96/post/a17c8c89-33e4-44cd-abbb-7cafcf33f4c9/image.png" alt=""></p>
<p>파일 구조는 아래와 같이 설계했다.
<img src="https://velog.velcdn.com/images/jiwon_96/post/eb08fe90-9b4b-4ef8-902b-0c7c3992e088/image.png" alt=""></p>
<pre><code class="language-javascript">// layout.tsx
&#39;use client&#39;;

export default async function PhotoDetailPageLayout() {
    console.log(&#39;detail page!&#39;);
    return (
      &lt;div&gt;photo layout&lt;/div&gt;
      )
}</code></pre>
<pre><code class="language-javascript">// page.tsx
import PhotoDetailPageLayout from &quot;./layout&quot;;

export default function PostDetailPage() {
  console.log(&quot;post detail page&quot;);
  return (
    &lt;div&gt;
      &lt;PhotoDetailPageLayout /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>일단 확인을 위해 콘솔을 찍어봤다.
확인해보니, 터미널에 콘솔은 찍히지만, 브라우저에서 콘솔은 안찍혔다.
서버컴포넌트인 page.tsx는 잘 작동되지만, 
클라이언트 컴포넌트인 레이아웃파일은 콘솔도 안찍히고, 
html도 바뀌지 않는걸로 보아 build가 안된거 같았다.</p>
<p>그래서 yarn build로 빌드 트리를 확인해보니 아래와 같았다.
<img src="https://velog.velcdn.com/images/jiwon_96/post/9224d57c-b415-41e9-898c-d31a13a3c38e/image.png" alt="">
구글링 해보니 다들 O표시로 나오는데 나는 html이 생성이 안되었다고 판단했다.</p>
<p>원래는 레이아웃 페이지를 client component로 설정하고, useMutation을 이용해서 사진 디테일 api요청을 하려고 했다.</p>
<p>근데, 아예 html이 안생기는걸 보아 
서버 컴포넌트에서 fetch 해서 사용해야겠다고 판단하고 아래와 같이 코드를 변경했다.</p>
<pre><code class="language-javascript">import { PhotoDetailProps } from &quot;@/apis/axios/photos/getPhotoDetail&quot;;
import { GetPhotosProps } from &quot;@/apis/axios/photos/getPhotos&quot;;
import PhotoDetail from &quot;./(components)/PhotoDetail&quot;;
import axiosInstance from &quot;@/apis/axios/instance&quot;;

export async function generateStaticParams() {
  const response = axiosInstance.get&lt;{
    photo_sections: GetPhotosProps[];
  }&gt;(&quot;/photo-sections&quot;);

  if (!response) return [];

  return (await response).data.photo_sections.map(photo =&gt;
    photo.photo_cards.map(photo_card =&gt; {
      id: photo_card.id.toString();
    })
  );
}

export default async function PhotoDetailPageLayout({ params }: any) {
  const { id } = params;
  console.log(&quot;params&quot;, params);
  const res = await axiosInstance.get&lt;PhotoDetailProps&gt;(`/photos/${id}`);
  const data: PhotoDetailProps = res.data;
  console.log(&quot;data&quot;, data);
  return (
    &lt;div className=&quot;w-full h-full flex flex-col justify-center items-center gap-1&quot;&gt;
      &lt;PhotoDetail data={data} /&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>이렇게 변경 후 빌드 트리를 다시 확인해봤더니, O표시로 잘바뀌었다.
근데,, 아직도 각 id 별 html은 없는거 같다.
<img src="https://velog.velcdn.com/images/jiwon_96/post/dadc7ace-5e6f-4648-a30f-44d7dee7ce77/image.png" alt=""></p>
<p>터미널을 확인하니, 아래와 같이 콘솔이 잘찍힌다.
<img src="https://velog.velcdn.com/images/jiwon_96/post/0ad8daaa-aee9-4202-905b-a9e0f41a10a7/image.png" alt="">
바꾸기 전과 동일하게 브라우저에서는 url만 변경될 뿐, 디테일 페이지가 아예 렌더링되지 않았다.
<img src="https://velog.velcdn.com/images/jiwon_96/post/892fd4d4-7c0d-4afb-a225-b23e502dad38/image.png" alt=""></p>
<h2 id="원인">원인</h2>
<p>[stackoverflow] (<a href="https://stackoverflow.com/questions/76468015/dynamic-routes-in-nextjs-13-not-working-as-expected-how-to-make-it-work">https://stackoverflow.com/questions/76468015/dynamic-routes-in-nextjs-13-not-working-as-expected-how-to-make-it-work</a>) 를 열심히 찾아본 결과</p>
<p>layout.tsx가 app/photo/layout.tsx 와 app/photo/[id]/layout.tsx 총 두 곳에 위치해서 그랬던 거였다....!
<del>원인을 찾는데 일주일 이상 걸렸다....</del></p>
<h2 id="해결">해결</h2>
<p>폴더 구조를 아래처럼 
app/photo/layout.tsx &gt; app/photo/(components)/PhotoPageLayout.tsx
으로 변경해 상위 layout의 위치를 변경해주었다.</p>
<p><img src="https://velog.velcdn.com/images/jiwon_96/post/514e5bf1-b63e-46cc-9a4c-9f5cc6690901/image.png" alt=""></p>
<p>이 문제로 꽤나 오랜 기간 고민했는데 드디어 해결..
스택오버플로우 최고.......🥹</p>
<p>코딩하면서 항상 느끼는 것 : 안될때는 정말 너무 안되는데 항상 알고보면 가까운데에 원인이 있었다. </p>
<p>📝 너무 오랫동안 닫혀있어서 벽인줄 알았는데, 알고보니 문이었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js .env 환경변수 설정]]></title>
            <link>https://velog.io/@jiwon_96/Next.js-.env-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@jiwon_96/Next.js-.env-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Mon, 06 Nov 2023 15:13:01 GMT</pubDate>
            <description><![CDATA[<h2 id="상황">상황</h2>
<p>카카오 api 사용해 로그인 기능 구현 중 .env 파일을 아래처럼 작성했다.</p>
<pre><code class="language-javascript">REACT_APP_API_KEY = 내 api 키
REACT_APP_REST_API_KEY = 내 rest api 키
REACT_APP_REDIRECT_URI = 내 redirect uri </code></pre>
<p>이렇게 설정하고 </p>
<pre><code class="language-javascript">const KAKAO_SDK_URL = `//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.REACT_APP_API_KEY}&amp;autoload=false`;
</code></pre>
<p>요렇게 가져다 썼는데, 
<img src="https://velog.velcdn.com/images/jiwon_96/post/ba0e7f5a-5854-4196-a0cd-4ae2394bab80/image.png" alt=""></p>
<p>에러가 났다.
자세히 보니, undefined로 들어가있었다</p>
<h2 id="원인">원인</h2>
<p>.env 는 서버에서만 접근가능하기 때문에, 클라이언트 환경에서는 undefined로 나타나는 거였다.
나는 Next.js에서 실행중이었는데, <a href="https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables#loading-environment-variables">공식문서</a>를 보니, 
브라우저에서 환경변수에 접근하려면 접두사로 <code>NEXT_PUBLIC</code> 를 써줘야 한다.
한참 삽질중에 터미널에서는 콘솔이 잘 출력되고, 브라우저에서는 undefined였던 것도 
Node.js 환경에서 일시적으로 실행된 콘솔이 터미널에 나타난 것이고,
브라우저의 콘솔창에 나타난 것은 클라이언트 단에서 실행되었기 때문에 undefined였던 것이다.</p>
<h2 id="해결">해결</h2>
<p>.env 파일을 아래처럼 변경해주고 </p>
<pre><code class="language-javascript">NEXT_PUBLIC_API_KEY = 내 api 키
NEXT_PUBLIC_REST_API_KEY = 내 rest api 키
NEXT_PUBLIC_REDIRECT_URI = 내 redirect uri </code></pre>
<pre><code class="language-javascript">const kakaoURL = `https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_REST_API_KEY}&amp;redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URI}&amp;response_type=code`;</code></pre>
<p>이렇게 가져다 쓰니 잘 작동된다 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SHA-256 해시 알고리즘을 이용해 비밀번호 암호화하기 에러]]></title>
            <link>https://velog.io/@jiwon_96/SHA-256-%ED%95%B4%EC%8B%9C-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EB%B9%84%EB%B0%80%EB%B2%88%ED%98%B8-%EC%95%94%ED%98%B8%ED%99%94%ED%95%98%EA%B8%B0-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@jiwon_96/SHA-256-%ED%95%B4%EC%8B%9C-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EB%B9%84%EB%B0%80%EB%B2%88%ED%98%B8-%EC%95%94%ED%98%B8%ED%99%94%ED%95%98%EA%B8%B0-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Tue, 31 Oct 2023 20:42:23 GMT</pubDate>
            <description><![CDATA[<h2 id="intro">INTRO</h2>
<p>회원가입 구현 시 비밀번호 해싱을 해야했다.
SHA-256 해시 알고리즘을 이용해서 비밀번호를 암호화하기로 했다.
우선, 나는 한번도 암호화를 해본적이 없어서 개념부터 찾아보았다.</p>
<p>유저의 비밀번호는 절대 그대로 DB에 저장해서는 안된다.(법률적 강제)
DB가 해킹 당하거나 담당자 또한 유저의 비밀번호를 알게 해서는 안되기 때문이다.
따라서, 비밀번호는 반드시 암호화해서 저장해야 한다.
일반적으로 비밀번호 암호화에는 단방향 해시 함수를 사용한다.</p>
<blockquote>
<p><strong>단방향 해시 함수(one-way hash function)</strong>
DataBase에 저장 시 난수 처리를 통하여 해싱하여 복원할 수 없도록 처리하는 방법이다.
원본 메시지를 통해 암호화된 메시지는 쉽게 유추할 수 있으나, 
역으로 암호화된 메세지를 통해서는 원본 메세지를 유추할 수 없어 단방향성이라고 한다.
SHA-256 이상 암호화 알고리즘을 활용하여 진행하며 단방향 해시 함수는 원본 메시지를 변환하여 암호화된 메시지인 다이제스트(digest)를 생성한다.</p>
</blockquote>
<h2 id="1-상황">1. 상황</h2>
<p>구글링을 통해 해야하는 이유와 개념을 파악했다.
이제, 회원가입 폼 제출 시 폼데이터의 비밀번호를 아래와 같이 해싱했다.</p>
<pre><code class="language-javascript">&lt;form
    noValidate
    onSubmit={handleSubmit(data =&gt; {
        data.password = SHA256(data.password).toString(); // 해싱
        if (!isEmailDuplicate) {
          axiosSignUpPost(data).then(navigateLoginPage);
        }
      })}
    &gt;</code></pre>
<p>회원가입이 잘 처리가 되었다 !
이제 로그인을 하러 가보자.
<img src="https://velog.velcdn.com/images/jiwon_96/post/a2c678cb-1380-4fbe-8f82-c0064c89e7d9/image.png" alt=""></p>
<p>잉? 
조회결과가 없다니..
회원가입 응답 200으로 잘 받았는데.. 왤까</p>
<p>다시 구글링으로 돌아갔다.
잘 모르겠다.
지피티한테 물어봤다.</p>
<h2 id="2-원인">2. 원인</h2>
<p>👇🏻 지피티의 답변 중 일부</p>
<blockquote>
<p>비밀번호를 SHA-256 해시로 암호화하는 것은 보안적으로 좋은 아이디어일 수 있지만, 로그인이 실패하는 이유가 여러 가지일 수 있습니다. 다음과 같은 몇 가지 가능성을 고려해 보실 수 있습니다:</p>
</blockquote>
<ol>
<li>비밀번호 해시의 비교: 사용자가 로그인할 때, 입력한 비밀번호를 해싱하여 저장된 해시와 비교해야 합니다. 회원가입 시에만 SHA-256로 해싱하는 것은 로그인 시에는 비밀번호를 원본 상태로 비교할 수 없게 만듭니다. 로그인 시에도 사용자가 입력한 비밀번호를 SHA-256으로 해싱한 후, 저장된 해시와 비교해야 합니다.
로그인 로직에서는 비밀번호 검증을 수행하는 부분이 필요합니다. 입력한 비밀번호를 SHA-256으로 해싱한 후, 서버에서 저장된 해시와 비교하여 로그인을 승인하거나 거부해야 합니다.</li>
</ol>
<p>젤 첫번째 가능성이 내 이유였다 ..🫠
로그인 시에도 해싱을 해야하는구나...🥲
진짜.. 바본가</p>
<h2 id="3-해결">3. 해결</h2>
<p>로그인폼 제출 시에도 해싱을 추가해줬다.</p>
<pre><code class="language-javascript">&lt;form
      noValidate
      onSubmit={handleSubmit(data =&gt; {
        data.password = SHA256(data.password).toString(); // 해싱 추가
        axiosLoginPost(data)
          .then(res =&gt; dispatch(loginUser(res)))
          .then(navigateMainPage);
      })}
    &gt;</code></pre>
<p>다시 로그인을 해보자.</p>
<p><img src="https://velog.velcdn.com/images/jiwon_96/post/4f58af4e-d9ad-49e4-8bfb-626290df1a6f/image.png" alt=""></p>
<p>로그인이 성공해서 스토리지에 유저 정보가 잘 들어갔다 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[A non-serializable value was detected in an action 에러]]></title>
            <link>https://velog.io/@jiwon_96/A-non-serializable-value-was-detected-in-an-action-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@jiwon_96/A-non-serializable-value-was-detected-in-an-action-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Mon, 30 Oct 2023 19:03:11 GMT</pubDate>
            <description><![CDATA[<h2 id="1-상황">1. 상황</h2>
<p>리덕스로 유저정보(name, company, email)을 저장하는데 
아래와 같은 에러가 떴다.</p>
<p><img src="https://velog.velcdn.com/images/jiwon_96/post/992181df-1871-45c2-a7c5-e4e51be8d92e/image.png" alt=""></p>
<p>오류 내용을 보면 직렬화할 수 없는 값을 action에 전달했다는 뜻이다.</p>
<blockquote>
<p>직렬화(serialization) : object를 string으로 변환하는 것
역직렬화(deserialization) / 파싱(parsing) : object를 string으로 변환하는 것</p>
</blockquote>
<h2 id="2-원인">2. 원인</h2>
<p>redux는 state, action에 직렬화가 불가능한 값을 넣을 수 없기에 에러가 발생한 것이다. 
redux의 middleware중 serializability check middleware가 있다.
이는 직렬화가 불가능한 것들 (함수, promise, 등의 non-plain-js-data values) 에 대해 state tree(상태 트리)와 action을 검사한다.
직렬화할 수 없는 값이 감지되면 콘솔에 오류를 띄워준다.</p>
<h2 id="3-해결">3. 해결</h2>
<p>해결 방법으로는 toString을 사용해도 되지만, 
이는 middleware를 사용해 해결할 수 있다.
아래와 같이 serializableCheck 를 false 로 설정하면 오류를 없앨 수 있다.
<img src="https://velog.velcdn.com/images/jiwon_96/post/d3951e5f-955e-4fd9-8aea-0719d49843ea/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[axios 응답 200인데 ..? 에러같다]]></title>
            <link>https://velog.io/@jiwon_96/axios-%EC%9D%91%EB%8B%B5-200%EC%9D%B8%EB%8D%B0-..-%EC%97%90%EB%9F%AC%EA%B0%99%EB%8B%A4</link>
            <guid>https://velog.io/@jiwon_96/axios-%EC%9D%91%EB%8B%B5-200%EC%9D%B8%EB%8D%B0-..-%EC%97%90%EB%9F%AC%EA%B0%99%EB%8B%A4</guid>
            <pubDate>Sat, 28 Oct 2023 20:07:28 GMT</pubDate>
            <description><![CDATA[<h2 id="1-상황">1. 상황</h2>
<p><img src="https://velog.velcdn.com/images/jiwon_96/post/363c13a6-a32c-4acc-b2b1-a2cfa80e3ec9/image.png" alt=""></p>
<p>응답 200 인데..? 
data가 이상하다.
명세서를 자세히 보니 0이어야 정상이라고 한다.
근데 아무리 해도 error: 2 가 나온다</p>
<p><img src="https://velog.velcdn.com/images/jiwon_96/post/d05e58c2-758a-4ba4-bcc0-af63b3135a9b/image.png" alt=""></p>
<h3 id="코드">코드</h3>
<pre><code class="language-javascript">import { LoginFormValue } from &quot;@Types/types&quot;;
import axios from &quot;axios&quot;;
import { BASE_URL } from &quot;utils/baseUrl&quot;;
export const axiosLoginPost = async (data: LoginFormValue) =&gt; {
  try {
    const res = await axios.post(`${BASE_URL}/api/v1/auth/login`, {
      email: data.email,
      password: data.password,
    });
    console.log(res);
    console.log(res.data);
  } catch (error) {
    alert(error);
  }
}; </code></pre>
<p>BASE_URL 도 맞고, 경로도 맞고, 
똑같은 형식으로 회원가입에서는 error: 0 으로 응답이 잘만 됐었다.
뭐지..
200이면 다 성공아니었나..</p>
<h2 id="2-원인">2. 원인</h2>
<p>내가 만났던 api들은 모두 200이면 성공이었는데, 
이 api는 응답이 200이어도 res.data.error를 통해 구분하고 있었다.
그래서 안됐던 것.</p>
<ul>
<li>res.data.error 가 0 이면 정상처리</li>
<li>res.data.error 가 2 이면 로그인 실패
로 이해하고 로직을 수정했다.</li>
</ul>
<h2 id="3-해결">3. 해결</h2>
<p>코드를 아래과 같이 변경해주니 잘 처리가 되었다.</p>
<pre><code class="language-javascript">export const axiosLoginPost = async (data: LoginFormValue) =&gt; {
  try {
    const res = await axios.post(`${BASE_URL}/api/v1/auth/login`, {
      email: data.email,
      password: data.password,
    });
    if (res.data.error === 0) {
      return res.data.payload.user;
    } else if (res.data.error === 2) {
      alert(res.data.message);
    }
  } catch (error) {
    alert(error);
  }
};</code></pre>
<h3 id="콘솔-결과">콘솔 결과</h3>
<p><img src="https://velog.velcdn.com/images/jiwon_96/post/4a4cac86-3419-4876-a903-68265028b77e/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 스터디 1주차_ 230624 백준 2606 바이러스]]></title>
            <link>https://velog.io/@jiwon_96/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8-230624-%EB%B0%B1%EC%A4%80-2606-%EB%B0%94%EC%9D%B4%EB%9F%AC%EC%8A%A4</link>
            <guid>https://velog.io/@jiwon_96/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8-230624-%EB%B0%B1%EC%A4%80-2606-%EB%B0%94%EC%9D%B4%EB%9F%AC%EC%8A%A4</guid>
            <pubDate>Sat, 24 Jun 2023 07:42:44 GMT</pubDate>
            <description><![CDATA[<h2 id="백준-2606-바이러스">백준 2606 바이러스</h2>
<hr>
<h4 id="회고-">회고 :</h4>
<p>알고리즘 3달만에 해서 가물가물했다..
DFS로 구현했고, stdin으로 실행하면 런타임에러난다..🫠
코드가 잘못된줄 알고 한참 삽질했다.
다들 readline으로 작성하길 권장한다 !</p>
<h4 id="코드">코드</h4>
<pre><code>const readline = require(&quot;readline&quot;);
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

let input = [];

rl.on(&quot;line&quot;, function (line) {
  input.push(line);
}).on(&quot;close&quot;, function () {
  console.log(input);

  let n = parseInt(input[0]);
  input.splice(0, 2);
  input = input.map(x =&gt; x.split(&quot; &quot;)).map(x =&gt; x.map(v =&gt; parseInt(v)));

  let graph = Array.from(Array(n + 1), () =&gt; Array()); // 컴퓨터 연결 관계 그래프
  let check = [1]; // 경유한 컴퓨터 번호 리스트
  let answer = 0;

  for (let x of input) {
    graph[x[0]].push(x[1]);
    graph[x[1]].push(x[0]);
  }

  function DFS(x) {
    for (let i = 0; i &lt; graph[x].length; i++) {
      if (!check.includes(graph[x][i])) {
        check.push(graph[x][i]);
        answer++;
        DFS(graph[x][i]);
      }
    }
  }
  // check 배열에 해당 컴퓨터가 들어있지 않을 경우에 if 문 안을 실행한다.
  // check 배열에 해당 컴퓨터를 들렸다고 표시
  // &gt; 바이러스에 감염된 컴퓨터 숫자 + 1
  // 감염된 컴퓨터를 기준으로 다시 출발시킨다.

  DFS(1);
  console.log(&#39;answer&#39; ,answer);
  console.log(&#39;check&#39;, check);
  process.exit();
});</code></pre><blockquote>
<p><a href="https://www.acmicpc.net/problem/2606">백준 2606번 바로가기</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 스터디 1주차_ 230623 백준 2875 대회 or 인턴]]></title>
            <link>https://velog.io/@jiwon_96/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8-230623-%EB%B0%B1%EC%A4%80-2875-%EB%8C%80%ED%9A%8C-or-%EC%9D%B8%ED%84%B4</link>
            <guid>https://velog.io/@jiwon_96/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94-1%EC%A3%BC%EC%B0%A8-230623-%EB%B0%B1%EC%A4%80-2875-%EB%8C%80%ED%9A%8C-or-%EC%9D%B8%ED%84%B4</guid>
            <pubDate>Sat, 24 Jun 2023 07:33:14 GMT</pubDate>
            <description><![CDATA[<h2 id="백준-2875-대회-or-인턴">백준 2875 대회 or 인턴</h2>
<hr>
<p>유형 : 그리디</p>
<h4 id="코드">코드</h4>
<pre><code>let input = require(&quot;fs&quot;)
  .readFileSync(&quot;example.txt&quot;)
  .toString()
  .trim()
  .split(&quot; &quot;)
  .map(x =&gt; parseInt(x));
let [n, m, k] = input;
let answer = 0;

while (k !== 0) {
  if (n / 2 &gt; m) {
    n -= 1;
    k -= 1;
  } else {
    m -= 1;
    k -= 1;
  }
}

while (m !== 0) {
  if (m * 2 &lt;= n) {
    answer = m;
    break;
  } else {
    m -= 1;
  }
}

console.log(answer);</code></pre><blockquote>
<p><a href="https://www.acmicpc.net/problem/2875">백준 2875번 바로가기</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 2주차 - 과제 중 에러..🚫]]></title>
            <link>https://velog.io/@jiwon_96/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-2%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-%EC%A4%91-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@jiwon_96/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-2%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C-%EC%A4%91-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Thu, 22 Jun 2023 16:36:24 GMT</pubDate>
            <description><![CDATA[<h2 id="과제-목록">과제 목록</h2>
<p>vanilla js로 배경이미지 랜덤 변경
vanilla js로 인사 만들기
vanilla js로 시계 만들기
vanilla js로 할 일 목록 만들기
vanilla js로 내 위치의 날씨 만들기</p>
<hr>
<p>과제 중 내 위치 날씨를 가져오는 중에 Weather API를 사용했다.
내 api key를 utill로 빼서 gitignore 처리 하려고 코드를 분리했는데, 
<img src="https://velog.velcdn.com/images/jiwon_96/post/c43b91e7-861e-4ba1-b203-b45e83a27b48/image.png" alt=""></p>
<p>위와 같은 cors 에러가 떴다.</p>
<p>해결 방법 : </p>
<ol>
<li>터미널에서 <pre><code> npm install http-server -g</code></pre>  를 입력해서 http-server를 전역에 설치한다.</li>
</ol>
<br/>
2. 그 다음 http-server를 실행시켜 해당 폴더를 서버에 올린다.

<pre><code>    ```
    npx http-server
    ```



 를 입력해서 http-server를 전역에 설치한다.</code></pre><blockquote>
<p>참고 링크 : <a href="https://velog.io/@takeknowledge/%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C-CORS-policy-%EA%B4%80%EB%A0%A8-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-3gk4gyhreu">https://velog.io/@takeknowledge/%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C-CORS-policy-%EA%B4%80%EB%A0%A8-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-3gk4gyhreu</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 2주차 - 개인 과제 후기]]></title>
            <link>https://velog.io/@jiwon_96/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-2%EC%A3%BC%EC%B0%A8-%EA%B0%9C%EC%9D%B8-%EA%B3%BC%EC%A0%9C-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@jiwon_96/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-2%EC%A3%BC%EC%B0%A8-%EA%B0%9C%EC%9D%B8-%EA%B3%BC%EC%A0%9C-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Mon, 19 Jun 2023 02:02:33 GMT</pubDate>
            <description><![CDATA[<h3 id="개인-과제--세이프홈즈-홈페이지구-media-query-로-반응형-구현하기퍼블리싱">개인 과제 : 세이프홈즈 홈페이지(구) Media Query 로 반응형 구현하기(퍼블리싱)</h3>
<hr>
<p>기능 구현 없이 퍼블리싱만 진행하였다. 
디테일을 신경 쓰다가 초기에 레이아웃을 좀 깊게 고민하지 않았던 거 같아서 마무리 단계에서 살짝 후회했다. 
나도 클래스명 찾다가 헷갈림..🫠</p>
<p>네이밍 이라던가, 스타일링을 좀 더 가독성 좋게 하면 추후에 더 보기 좋을 것 같다. 
또 미디어 쿼리 쓸 때 어디까지 지정해줘야 하나 코드가 넘 지저분하지 않나 고민이 됐다. 최대한 flex 먹이면서 지정하긴 했는데 코드 리뷰를 받으면 좋겠다~ 생각했다 🤔</p>
<p>📌 <a href="https://github.com/energy1-914/Udemy_SF/tree/main/safehomes">깃헙 링크</a></p>
<p>📌 <a href="https://www.figma.com/file/s5XMTwfBmWDmDJC0B6gAww/%E1%84%89%E1%85%A6%E1%84%8B%E1%85%B5%E1%84%91%E1%85%B3%E1%84%92%E1%85%A9%E1%86%B7%E1%84%8C%E1%85%B3-%E1%84%85%E1%85%A2%E1%86%AB%E1%84%83%E1%85%B5%E1%86%BC%E1%84%91%E1%85%A6%E1%84%8B%E1%85%B5%E1%84%8C%E1%85%B5?type=design&amp;node-id=0-1&amp;t=ojgXXXqKISjmWTDX-0">피그마 링크</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 2주차 - 실습 2]]></title>
            <link>https://velog.io/@jiwon_96/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-2%EC%A3%BC%EC%B0%A8-%EC%8B%A4%EC%8A%B5-2</link>
            <guid>https://velog.io/@jiwon_96/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-2%EC%A3%BC%EC%B0%A8-%EC%8B%A4%EC%8A%B5-2</guid>
            <pubDate>Mon, 12 Jun 2023 13:02:20 GMT</pubDate>
            <description><![CDATA[<h3 id="주제--팀별로-웅진씽크빅-홈페이지와-웅진it-홈페이지-html-태그-분석하고-시맨틱-태그를-사용해야-하는-이유를-만들어보세요">주제 : 팀별로 &quot;웅진씽크빅&quot; 홈페이지와 &quot;웅진IT&quot; 홈페이지 HTML 태그 분석하고 시맨틱 태그를 사용해야 하는 이유를 만들어보세요.</h3>
<h4 id="시맨틱-태그-사용-이유">시맨틱 태그 사용 이유</h4>
<ol>
<li><p>HTML 문서의 가독성이 좋다. </p>
</li>
<li><p>웹 접근성이 좋아진다.(SEO 최적화 / 시각장애인 등)</p>
</li>
<li><p>코드의 유지보수가 쉬워진다.</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/jiwon_96/post/45d7e7a0-284e-4e2d-aa1c-00995bf1d586/image.png" alt=""></p>
<p>해당 홈페이지는 시맨틱 태그를 사용하지 않고, 모두 div 태그를 사용했고, id를 부여했다. 
따라서 id 명을 일일이 확인해야하는 번거로움이 생겼다. 비효율적이고 가독성이 좋지 않다!
<img src="https://velog.velcdn.com/images/jiwon_96/post/4c438430-9fb8-4c9f-bee7-1970df5755f3/image.png" alt="">
이와 반대로 웅진IT 홈페이지는 시맨틱 태그를 활용했고, 코드 가독성이 더 좋은것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 서비스기획 2차 강의  - UML
]]></title>
            <link>https://velog.io/@jiwon_96/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-%EC%84%9C%EB%B9%84%EC%8A%A4%EA%B8%B0%ED%9A%8D-2%EC%B0%A8-%EA%B0%95%EC%9D%98-%ED%9B%84%EA%B8%B0-UML</link>
            <guid>https://velog.io/@jiwon_96/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-10%EC%A3%BC-%EC%99%84%EC%84%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-%EC%84%9C%EB%B9%84%EC%8A%A4%EA%B8%B0%ED%9A%8D-2%EC%B0%A8-%EA%B0%95%EC%9D%98-%ED%9B%84%EA%B8%B0-UML</guid>
            <pubDate>Thu, 08 Jun 2023 15:43:29 GMT</pubDate>
            <description><![CDATA[<h3 id="umlunified-modeling-language-이란-">UML(Unified Modeling Language) 이란 ?</h3>
<p>통합 모델링 언어를 사용해 시스템 상호작용, 업무흐름, 시스템 구조 등을 그린 도면이다.
시스템 자체의 산출물의 역할을 시각화한다.
이 과정을 통해 반드시 필요한 행위를 기반으로 한 객체지향 모델링이 가능해진다.
이해 당사자가 서로 커뮤니케이션 할 수 있는 소프트웨어 시스템의 구조와 행위를 정의하고 기술함으로써 보다 빠르고 정확한 플랫폼을 구현할 수 있다. 이런 방식을 MDD(Model-Driven Development) 즉 모델 주도적 개발이라 한다. </p>
<h4 id="-uml-모델링의-이점">* UML 모델링의 이점</h4>
<ol>
<li>시각화</li>
<li>이해도</li>
<li>정확성</li>
<li>의사전달</li>
<li>일관성</li>
</ol>
<p>➡️ 이는 모두 성공적 플랫폼을 구축하기 위함이다.</p>
<h4 id="-uml의-종류">* UML의 종류</h4>
<p><img src="https://velog.velcdn.com/images/jiwon_96/post/e23a8b0d-9a3b-437b-a884-aff7cda60d1a/image.png" alt=""></p>
<h4 id="-usecase-diagram">* Usecase diagram</h4>
<p>시스템의 행위를 모델링 하며 시스템의 요구 사항을 표현하는데 사용됩니다. 
시스템의 상위 레벨 기능과 범위를 기술하며, 시스템이 기능을 어떻게(HOW) 구현할 것인지 기술하지 않는다.
<strong>시스템이 어떤 “WHAT”기능이 필요한지</strong>에 대해서만 표현한다.
<img src="https://velog.velcdn.com/images/jiwon_96/post/cba16d8a-237a-4b58-bbcf-f028b8f22f8d/image.png" alt=""></p>
<ul>
<li>구성 요소 </li>
</ul>
<pre><code>1. 시스템 : 현재 개발할 프로그램.
          프라이머리 액터(시스템을 사용하는 주체)와 세컨더리 엑터(프라이머리 액터로 시작되어 시스템과 상호작용하는 다른 시스템) 사이의 사각형으로 표시.

2. 액터 : 시스템 외부에 존재하며 시스템과 상호작용하는 모든 것.
         대게 시스템을 사용하는 유저와, 이 때 상호작용되는 다른 시스템이 액터가 될 수 있다.

3. 유즈케이스 : 사용자의 입장에서 바라본 시스템의 추상적인 기능.
             시스템의 요구사항을 나타내며 타원형으로 표기. 

4. 연관관계 : 액터와 유즈케이스 간의 상호작용을 실선으로 표현.

5. 포함관계 : 포함하는 유스케이스에서 포함 되는 유스케이스 방향으로 화살표를 점선으로 연결하고 &lt;&lt; include&gt;&gt;라고 표기한다.

6. 확장관계 : 해당 유즈케이스에 부가적인 유즈케이스를 실행 할 수 있을 때의 관계.
            확장하는 유스케이스에서 대상 유스케이스 방향을 점선으로 연결하고 &lt;&lt; extend&gt;&gt;로 표기한다.</code></pre><h4 id="-activity-diagram-활동-다이어그램">* Activity diagram (활동 다이어그램)</h4>
<p>활동 다이어그램은 시스템의 실행과 행위의 흐름을 표현. 
비즈니스 프로세스 또는 작업에서 이용하는 고객의 흐름을 표현하는데 적합하다.
<img src="https://velog.velcdn.com/images/jiwon_96/post/9598269c-7023-4af3-af4e-a559ce0d57fa/image.png" alt="">
<img src="https://velog.velcdn.com/images/jiwon_96/post/83ff848c-37c2-4c6b-82b4-375315f40095/image.png" alt=""></p>
<ul>
<li>과제 : Activity diagram 만들기
<img src="https://velog.velcdn.com/images/jiwon_96/post/f799e7ac-f7fe-4fac-92a1-5d198adcda28/image.png" alt=""></li>
</ul>
<ul>
<li>후기
사실 기획자에게 전달 받은 기획안대로 프로그래밍하면 내 역할을 다한 거라고 생각했는데, 직접 다이어그램을 만들고 생각해보니 고려해야할 부분이 더 많았다.
아직 draw.io에 익숙치 않아서 그냥 손으로 작성해보는게 편하긴 하지만, 직접 마인드맵, 다이어그램으로 만들어보는 것과 무지성으로 코딩하는 것이 퀄이 다른 결과를 만들 것이라는 걸 확실히 깨닫게 되었다 !
혼자서 프로젝트를 진행할 때에도 다이어그램을 만들어보며 가시화하는 습관을 가져야겠다..! </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[아키텍쳐 마인드맵 (1주차)]]></title>
            <link>https://velog.io/@jiwon_96/%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%EB%A7%88%EC%9D%B8%EB%93%9C%EB%A7%B5</link>
            <guid>https://velog.io/@jiwon_96/%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%EB%A7%88%EC%9D%B8%EB%93%9C%EB%A7%B5</guid>
            <pubDate>Tue, 06 Jun 2023 23:38:22 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jiwon_96/post/cefaa8ff-263e-4c11-afd1-21f33a618654/image.png" alt=""></p>
<h3 id="첫-과제--drawio-를-이용한-내가-만들고-싶은-아키텍쳐에-대한-마인드맵-만들기">첫 과제 : draw.io 를 이용한 내가 만들고 싶은 아키텍쳐에 대한 마인드맵 만들기</h3>
<h4 id="1-아이디어">1. 아이디어</h4>
<p>아이있는 집은 흔치않은데, 애완동물 있는 집은 점점 늘어나는 추세이다. 
        동물에 대한 웹을 만들면 사용자에게 흥미를 끌 수 있을 것이라고 생각했다. 
        또한, 친한 지인 중 앵무새를 키우는 걸 보고, 강아지나 고양이에 국한되지 않고 여러 동물을 대상으로 서비스를 만들면 더욱 수요가 늘어날 것이라고 판단했다. </p>
<h4 id="2-마인드맵">2. 마인드맵</h4>
<p><img src="https://velog.velcdn.com/images/jiwon_96/post/6d849143-05e4-451f-ac42-f2c4d184c005/image.png" alt=""></p>
<h4 id="3-후기">3. 후기</h4>
<p> 해당 아이디어로 간략히 마인드맵을 만들어보았다. 사실 만들면서 생각한건데, 내가 프론트만 공부하다보니 너무 백단에 대한 이해가 부족하다는 것을 또다시 느꼈다. 
    또한, 이 서비스의 관건은 동물 말을 얼마나 정확히, 설득력있게 해석할 수 있느냐이다. 이를 위해서는 동물 말 분석 시스템의 정확도가 가장 중요하다. 그런데, 나는 이 부분을 깊게 고려하지 않은 것 같다. 아직은 무료로 사용할 수 있는 데이터가 많은 해석 시스템이 없다....(낭패)
  다른 아이디어를 생각해서 다시 마인드맵을 그려봐야겠다..!</p>
<hr>
<pre><code>본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.</code></pre>]]></description>
        </item>
    </channel>
</rss>