<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>5o_hyun.log</title>
        <link>https://velog.io/</link>
        <description>학생 점심 좀 차려</description>
        <lastBuildDate>Thu, 04 Sep 2025 03:52:30 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>5o_hyun.log</title>
            <url>https://images.velog.io/images/5o_hyun/profile/cfd64a98-dd3d-4cb9-98e2-e49e44673a5b/프사2.gif</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 5o_hyun.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/5o_hyun" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[로컬에서 프로덕션버전 확인하기 nextjs standalone]]></title>
            <link>https://velog.io/@5o_hyun/%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C-%ED%94%84%EB%A1%9C%EB%8D%95%EC%85%98%EB%B2%84%EC%A0%84-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0-nextjs-standalone</link>
            <guid>https://velog.io/@5o_hyun/%EB%A1%9C%EC%BB%AC%EC%97%90%EC%84%9C-%ED%94%84%EB%A1%9C%EB%8D%95%EC%85%98%EB%B2%84%EC%A0%84-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0-nextjs-standalone</guid>
            <pubDate>Thu, 04 Sep 2025 03:52:30 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p>나는 서버컴포넌트에서 next캐시를 사용하고있었는데 캐시가 적용되지않고 계속 불러와지는 일이 발생했다. 
찾아보니 next에서 개발모드(dev)에서는 계속 확인해야하니 캐시를 무시하고 보여지고, 실제 배포를하면(production) 캐시가 적용된다고 나와있었다.
그러나 배포후까지 기다릴수도 없고, 배포를 계속 하면서 캐시여부를 확인하기에는 너무 리소스가 많이들었다.
그래서 로컬에서도 production환경일때 확인할수있는 방법을 찾아보았다.</p>
<pre><code class="language-tsx">  const eng_name = await getCenterName();
  const response = await fetch(`${process.env.SERVER_API_HOST}/config?eng_name=${eng_name}`, {
    next: { revalidate: 3600 }, // 1시간
    cache: &#39;force-cache&#39;,
  });
  const cachedConfig = (await response.json()) as Config;</code></pre>
<h2 id="방법">방법</h2>
<ol>
<li><p>nextjs를 로컬버전에서 빌드한다. <code>npn run build</code> 
빌드하면 자체적으로 배포할 수 있는 폴더가 생성된다. standalone</p>
</li>
<li><p>다음 명령어를 수행하여, 폴더에 수동으로 복사 한다.</p>
<pre><code class="language-ts">cp -r public .next/standalone/ &amp;&amp; cp -r .next/static .next/standalone/.next/</code></pre>
</li>
<li><p>최소파일을 로컬에서 시작하려면 server.js 다음명령어를 실행한다.</p>
<pre><code class="language-ts">node .next/standalone/server.js</code></pre>
</li>
<li><p>이렇게 다 치고나면 localhost:3000에서 수행되고있다고 터미널에 뜰것이다.
로컬호스트 3000으로 접속하면 production환경에서 확인할 수 있다.</p>
</li>
</ol>
<h2 id="참고">참고</h2>
<p><a href="https://nextjs.org/docs/pages/api-reference/config/next-config-js/output">https://nextjs.org/docs/pages/api-reference/config/next-config-js/output</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[임시이미지 (Placeholder Image) url 모음 ]]></title>
            <link>https://velog.io/@5o_hyun/%EC%9E%84%EC%8B%9C%EC%9D%B4%EB%AF%B8%EC%A7%80-Placeholder-Image-url-%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@5o_hyun/%EC%9E%84%EC%8B%9C%EC%9D%B4%EB%AF%B8%EC%A7%80-Placeholder-Image-url-%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Fri, 04 Jul 2025 08:30:58 GMT</pubDate>
            <description><![CDATA[<p>via.placeholder.com을 잘 사용하고 있었는데 최근 갑자기 안되기 시작했다.
홈페이지도 몇년전부터 사라지고 url 경로만 잘 사용하고있었는데 이제 이거도 막힌듯하다.
그래서 새로 가져온 임시이미지 url 모음</p>
<h2 id="1-placehold">1. placehold</h2>
<h3 id="사용방법">사용방법</h3>
<pre><code class="language-js">https://placehold.co/600x400</code></pre>
<pre><code class="language-js">https://placehold.co/400</code></pre>
<h3 id="공식-홈페이지-주소">공식 홈페이지 주소</h3>
<p>공식 홈페이지에 들어가면 더욱 자세한 내용(사이즈조정,텍스트조정 등 )을 볼 수 있다.
<a href="https://placehold.co/">https://placehold.co/</a></p>
<h3 id="이미지-예시">이미지 예시</h3>
<img src="https://placehold.co/600x400"/>


<h2 id="2-loremflickrcom">2. loremflickr.com</h2>
<p>전보다 자세한 이미지가 필요하다면, 이걸 사용해보자. 
이미지 사이즈만 설정하면 기본이미지가 나온다. (기본은 고양이 이미지들이 뜬다.) 🐱🐱🐱
키워드 설정 등으로 다른 이미지도 호출할 수 있다. </p>
<h3 id="사용방법-1">사용방법</h3>
<pre><code>&lt;img src=&quot;https://loremflickr.com/600/400&quot; /&gt;</code></pre><h3 id="공식-홈페이지-주소-1">공식 홈페이지 주소</h3>
<p><a href="https://loremflickr.com/">https://loremflickr.com/</a></p>
<h3 id="이미지-예시-1">이미지 예시</h3>
<img src="https://loremflickr.com/600/400" />]]></description>
        </item>
        <item>
            <title><![CDATA[결제하기기능 next에서 빌게이츠 연동하기]]></title>
            <link>https://velog.io/@5o_hyun/%EA%B2%B0%EC%A0%9C%ED%95%98%EA%B8%B0%EA%B8%B0%EB%8A%A5-next%EC%97%90%EC%84%9C-%EB%B9%8C%EA%B2%8C%EC%9D%B4%EC%B8%A0-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@5o_hyun/%EA%B2%B0%EC%A0%9C%ED%95%98%EA%B8%B0%EA%B8%B0%EB%8A%A5-next%EC%97%90%EC%84%9C-%EB%B9%8C%EA%B2%8C%EC%9D%B4%EC%B8%A0-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 22 Apr 2025 08:17:45 GMT</pubDate>
            <description><![CDATA[<p>결제하기기능도 본인인증과 비슷한 플로우다.</p>
<h2 id="결제-과정">결제 과정</h2>
<blockquote>
<p>결제버튼클릭으로 결제창열기 -&gt; 결제하고 완료되면, 결과값과함께 return url로 이동 -&gt; return url에서 부모창으로 결과값전달 -&gt; 부모창에서 로직처리</p>
</blockquote>
<h2 id="1-결제창열기">1. 결제창열기</h2>
<p>우선 Head에 script를 붙여준다. 
<img src="https://velog.velcdn.com/images/5o_hyun/post/3356e0cf-374f-4b0c-acfd-693b513e0867/image.png" alt=""></p>
<p>결제를 완료하려면 다음과같은 함수를 호출해야하는것같다. 알아만두고 ㅇㅇ
<img src="https://velog.velcdn.com/images/5o_hyun/post/f7b84cd7-9c70-4c28-9be0-7f6eee113092/image.png" alt=""></p>
<p>결제버튼이 있는 페이지로 간다.
결제창을 만들어준다. </p>
<pre><code class="language-tsx"> // action이 테스트action이고 실결제는 문서를참고 
      &lt;form name=&quot;payment&quot; id=&quot;payment&quot; method=&quot;POST&quot; action=&quot;https://tpay.billgate.net&quot;&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;SERVICE_ID&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;SERVICE_CODE&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;SERVICE_TYPE&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;ORDER_ID&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;ORDER_DATE&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;AMOUNT&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;RETURN_URL&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;ITEM_CODE&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;ITEM_NAME&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;USER_ID&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;USER_NAME&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;USER_EMAIL&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;RESERVED1&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;RESERVED2&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;RESERVED3&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;CANCEL_FLAG&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;WEBAPI_FLAG&quot; /&gt;
        &lt;input type=&quot;hidden&quot; name=&quot;LOGO&quot; /&gt;
        {/* 추가 파라미터는 빌게이트 가이드 참조 */}
        &lt;button type=&quot;button&quot;&gt;결제하기&lt;/button&gt;
      &lt;/form&gt;</code></pre>
<p>결제창을 열려면 form에 많은 값을 넣어줘야하는데, 이는 백엔드의 api에 요청하여 받아야한다.
나는 백엔드에 <code>상품id</code> , <code>return url</code> 을 보내어 form요청시 필요한 값을 받았다.
이후 form을 요청했다. </p>
<pre><code class="language-tsx">  // 결제하기 버튼클릭 
  const onClickPay = () =&gt; {
    getPGInfoMutation.mutate(rentalId);
  };
 // 결제하기 함수 
  const getPGInfoMutation = useMutation({
    mutationKey: [&#39;getPGInfo&#39;],
    mutationFn: (rental_id: number) =&gt;
      client.Payments_PgInfo_ON_CENTER_CODE_POST(config.centerInfo.center_code, {
        rental_id,
        return_url: `${process.env.NEXT_PUBLIC_API_URL}/pay`,
      }),
    onSuccess: (response) =&gt; {
      const form = document.getElementById(&#39;payment&#39;) as HTMLFormElement;
      const data = response.data as PayInfo;

      if (form &amp;&amp; data) {
        form.SERVICE_ID.value = data.SERVICE_ID;
        form.SERVICE_CODE.value = data.SERVICE_CODE;
        form.SERVICE_TYPE.value = data.SERVICE_TYPE;
        form.ORDER_ID.value = data.ORDER_ID;
        form.ORDER_DATE.value = data.ORDER_DATE;
        form.AMOUNT.value = data.AMOUNT;
        form.RETURN_URL.value = data.RETURN_URL;
        form.ITEM_CODE.value = data.ITEM_CODE;
        form.ITEM_NAME.value = data.ITEM_NAME;
        form.USER_ID.value = data.USER_ID;
        form.USER_NAME.value = data.USER_NAME;
        form.USER_EMAIL.value = data.USER_EMAIL;
        form.RESERVED1.value = data.RESERVED1;
        form.RESERVED2.value = data.RESERVED2;
        form.RESERVED3.value = data.RESERVED3;
        // form.CANCEL_FALG.value = data.CANCEL_FALG;
        form.CANCEL_FLAG.value = &#39;Y&#39;;
        form.WEBAPI_FLAG.value = data.WEBAPI_FLAG;
        form.LOGO.value = data.LOGO;

        GX_pay(&#39;payment&#39;, &#39;popup&#39;, &#39;https_tpay&#39;);
      }
    },
  });</code></pre>
<p>결제창이 열린다.</p>
<h2 id="2-결제완료후-결과값받기">2. 결제완료후 결과값받기</h2>
<p>결제창이 뜨면 팝업창에서 결제를 완료한다.
결제완료를 하면 내가 설정해놨던 return url로 팝업창의 주소가 바뀐다. <code>${process.env.NEXT_PUBLIC_API_URL}/pay</code></p>
<p>/pay 에 가도록 설정했으니 해당경로에 페이지를 만들어주고, 
여기서는 <strong>부모에게 결과값을 전달해주고 팝업을 닫는 역할만</strong> 한다. </p>
<pre><code class="language-tsx">// /pay
&#39;use client&#39;;

import { useEffect } from &#39;react&#39;;

const PayClient = () =&gt; {
  const urlParams = new URLSearchParams(window.location.search);
  const status = urlParams.get(&#39;status&#39;);

  useEffect(() =&gt; {
    if (status === &#39;success&#39;) {
      window.opener?.postMessage({ type: &#39;PAY_SUCCESS&#39; }, &#39;*&#39;);
    } else {
      window.opener?.postMessage({ type: &#39;PAY_FAIL&#39; }, &#39;*&#39;);
    }

    window.close();
  }, [status]);

  return null;
};

export default PayClient;
</code></pre>
<h2 id="3-부모창에서-결과값으로-로직처리">3. 부모창에서 결과값으로 로직처리</h2>
<pre><code class="language-tsx">  // 결제완료후 처리로직
  useEffect(() =&gt; {
    const handleMessage = async (event: MessageEvent) =&gt; {
      const { type } = event.data || {};

      if (type === &#39;PAY_SUCCESS&#39;) {
        Alert(&#39;결제가 정상 처리되었습니다.&#39;);
        GX_payClose();
        router.push(&#39;/my/rental&#39;);
      } else if (type === &#39;PAY_FAIL&#39;) {
        Alert(&#39;결제가 완료되지 않았습니다.&#39;);
        GX_payClose();
      }
    };

    window.addEventListener(&#39;message&#39;, handleMessage);
    return () =&gt; window.removeEventListener(&#39;message&#39;, handleMessage);
  }, []);</code></pre>
<hr>
<p>최종적으로 api는 폼에 필요한값을 받을때 1번만 요청하고, pg사에서 제공한 팝업안에서 결제완료가되면, 나는 팝업을 닫고 refetch만 해주었다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[카카오톡 공유하기]]></title>
            <link>https://velog.io/@5o_hyun/%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%86%A1-%EA%B3%B5%EC%9C%A0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@5o_hyun/%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%86%A1-%EA%B3%B5%EC%9C%A0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 16 Apr 2025 08:53:32 GMT</pubDate>
            <description><![CDATA[<h2 id="1-카카오-디벨로퍼스-설정">1. 카카오 디벨로퍼스 설정</h2>
<p>카카오 디벨로퍼스에서 애플리케이션을 추가하고,로컬주소와 배포주소 도메인을 넣는다. 
<img src="https://velog.velcdn.com/images/5o_hyun/post/7043d843-9f91-4a3d-89ca-051a011de173/image.png" alt=""></p>
<h2 id="2-카카오sdk추가">2. 카카오sdk추가</h2>
<p>head안에 스크립트로 카카오sdk를 추가해준다.</p>
<pre><code> &lt;html lang=&quot;ko&quot; className={`${pretendard.variable}`}&gt;
      {/* 카카오공유 */}
      &lt;Script defer src=&quot;https://developers.kakao.com/sdk/js/kakao.min.js&quot;&gt;&lt;/Script&gt; // 여기추가
      {/* 네이버지도 */}
      &lt;Script
        strategy=&quot;afterInteractive&quot;
        src={`https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.NEXT_PUBLIC_MAP_KEY}`}
      &gt;&lt;/Script&gt;
      &lt;body className={`${pretendard.className} antialiased`}&gt;
        &lt;ClientRoot&gt;{children}&lt;/ClientRoot&gt;
      &lt;/body&gt;
    &lt;/html&gt;</code></pre><h2 id="3-env에-자바스크립트앱키-넣기">3. env에 자바스크립트앱키 넣기</h2>
<pre><code>// .env
NEXT_PUBLIC_KAKAO_API_KEY=djsdbajkbjdkbakjbwjkabjkdwbakjd</code></pre><p><img src="https://velog.velcdn.com/images/5o_hyun/post/e871ff5e-fe81-43fa-a261-024a4029c1aa/image.png" alt=""></p>
<h2 id="4-initialize">4. initialize</h2>
<p>이제 JavaScript 키를 이용해서 initialize를 해주어야한다.
클라이언트단의 상위컴포넌트에 다음과같이 입력해준다 (레이아웃등)</p>
<pre><code class="language-tsx">  useEffect(() =&gt; {
    window.Kakao.init(process.env.NEXT_PUBLIC_KAKAO_API_KEY);
  }, []);</code></pre>
<p>그러면 window가 kakao를 인식하지못하는 문제가있다.
따라서 이 위의 provider가 있는 컴포넌트에 전역으로 명시해준다</p>
<pre><code class="language-tsx">declare global {
  interface Window {
    Kakao: any;
  }
}</code></pre>
<h2 id="5-카카오톡-공유하기-버튼-생성">5. 카카오톡 공유하기 버튼 생성</h2>
<p>카카오톡 공유하기 버튼을 따로 컴포넌트로 생성해줬다.</p>
<h3 id="종류1-스크랩하여-자동으로-공유">종류1) 스크랩하여 자동으로 공유</h3>
<p>자동으로 스크랩하여 공유하는방식인 <code>sendScrap</code>을 사용하여 구현하였다.</p>
<pre><code class="language-tsx">// /components/KakaoShareBtn.tsx
import React from &#39;react&#39;;

const KakaoShareBtn = () =&gt; {
  const onClick = () =&gt; {
    const { Kakao, location } = window;
    Kakao.Share.sendScrap({
      requestUrl: location.href,
    });
  };

  return (
    &lt;button className=&quot;w-[60px] h-[60px] rounded-full bg-[#D5D5D5] flex items-center justify-center&quot; onClick={onClick}&gt;
      &lt;img src=&quot;/images/kakao.png&quot;&gt;
    &lt;/button&gt;
  );
};

export default KakaoShareBtn;</code></pre>
<p>카카오디벨로퍼스에 친절하게 나와있다. <a href="https://developers.kakao.com/tool/demo/message/kakaolink?message_type=scrap">https://developers.kakao.com/tool/demo/message/kakaolink?message_type=scrap</a>
<img src="https://velog.velcdn.com/images/5o_hyun/post/6a01cd51-6e26-4843-be8e-af804b1798df/image.png" alt="">
이방식으로 해보니 seo로 넣어준 logo, title, description이 나오고 , url만 해당경로로 가게 구현이 되었다. 
해당 방식도 괜찮긴한데 마음에안들어서 커스텀을 해보기로했다.</p>
<h3 id="종류2-커스텀방식">종류2) 커스텀방식</h3>
<pre><code class="language-tsx">import React from &#39;react&#39;;

type Props = {
  title: string;
  description: string;
  imageUrl?: string;
};

const KakaoShareBtn = ({ title, description, imageUrl }: Props) =&gt; {
  const onClick = () =&gt; {
    const { Kakao, location } = window;

    Kakao.Link.sendDefault({
      objectType: &#39;feed&#39;,
      content: {
        title,
        description,
        imageUrl: imageUrl ? imageUrl : &#39;&#39;,
        link: {
          mobileWebUrl: location.href,
          webUrl: location.href,
        },
      },
      buttons: [
        {
          title: &#39;자세히 보기&#39;,
          link: {
            mobileWebUrl: location.href,
            webUrl: location.href,
          },
        },
      ],
    });
  };

  return (
    &lt;button className=&quot;w-[60px] h-[60px] rounded-full bg-[#D5D5D5] flex items-center justify-center&quot; onClick={onClick}&gt;
      &lt;img src=&quot;/images/kakao.png&quot;&gt;
    &lt;/button&gt;
  );
};

export default KakaoShareBtn;
</code></pre>
<p>카카오톡 공유하기의 템플릿은 많으니, 아래주소에서 종류를 확인하여 맞는 개발을 하면된다. 
<a href="https://developers.kakao.com/docs/latest/ko/kakaotalk-share/js-link">https://developers.kakao.com/docs/latest/ko/kakaotalk-share/js-link</a></p>
<hr>
<p>참고사이트
<a href="https://yong-nyong.tistory.com/16">https://yong-nyong.tistory.com/16</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NICE 본인인증 휴대폰본인인증 ]]></title>
            <link>https://velog.io/@5o_hyun/NICE-%EB%B3%B8%EC%9D%B8%EC%9D%B8%EC%A6%9D-%ED%9C%B4%EB%8C%80%ED%8F%B0%EB%B3%B8%EC%9D%B8%EC%9D%B8%EC%A6%9D</link>
            <guid>https://velog.io/@5o_hyun/NICE-%EB%B3%B8%EC%9D%B8%EC%9D%B8%EC%A6%9D-%ED%9C%B4%EB%8C%80%ED%8F%B0%EB%B3%B8%EC%9D%B8%EC%9D%B8%EC%A6%9D</guid>
            <pubDate>Tue, 15 Apr 2025 08:00:29 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-전체-흐름-요약">✅ 전체 흐름 요약</h2>
<p>프론트에서 버튼 클릭 → 백엔드로 요청 (enc_data, integrity_value 받기)</p>
<p>프론트가 받은 값으로 form submit → NICE 인증창 열림</p>
<p>사용자가 인증을 마치면 → NICE가 우리 백엔드에 결과를 POST로 전송</p>
<p>백엔드가 결과 디코딩 → 프론트에 인증 결과 보여줌</p>
<h2 id="✅-목표">✅ 목표</h2>
<blockquote>
<p>휴대폰 인증 버튼 누르면 → NICE 인증 창이 열리고 → 인증 후 돌아오는 흐름을 만들고 싶다</p>
</blockquote>
<hr>
<p>  nice인증창은 단순 url로 열리는게 아니기때문에 <code>enc_data</code> <code>integrity_value</code> <code>token_version_id</code> 가 필요하다
  이는 보안상 백엔드에서 만들어줘야한다. (token_version_id는 나이스가입할떄 준다고함 )
  따라서 프론트에서 특정api에 요청하면 백엔드에서 해당값(<code>enc_data</code> <code>integrity_value</code> <code>token_version_id</code>)들을 반환해줘야한다. </p>
<h3 id="왜-백엔드가-꼭-필요한가">왜 백엔드가 꼭 필요한가?</h3>
<p>NICE 인증은 보안이 중요한 서비스라서,
암호화된 요청 데이터를 <strong>NICE에서 제공한 암호화 모듈</strong>로 만들어야 한다.
그런데 이 암호화 모듈은 <strong>프론트엔드(브라우저)에서는 사용할 수 없다</strong>.</p>
<p><strong>그래서 반드시 백엔드에서 <code>enc_data</code>를 만들고 → 프론트에 넘겨줘야 한다.</strong></p>
<h2 id="✅-과정">✅ 과정</h2>
<h3 id="1-나이스-인증창-폼버튼-만들기">1) 나이스 인증창 폼,버튼 만들기</h3>
<pre><code>{/* 나이스인증창 */}
        &lt;form name=&quot;form&quot; id=&quot;form&quot; action=&quot;https://nice.checkplus.co.kr/CheckPlusSafeModel/service.cb&quot;&gt;
          &lt;input type=&quot;hidden&quot; id=&quot;m&quot; name=&quot;m&quot; value=&quot;service&quot; /&gt;
          &lt;input type=&quot;hidden&quot; id=&quot;token_version_id&quot; name=&quot;token_version_id&quot; value=&quot;&quot; /&gt;
          &lt;input type=&quot;hidden&quot; id=&quot;enc_data&quot; name=&quot;enc_data&quot; /&gt;
          &lt;input type=&quot;hidden&quot; id=&quot;integrity_value&quot; name=&quot;integrity_value&quot; /&gt;
        &lt;/form&gt;

  {/* 누를버튼 */}
  &lt;Button onClick={openNiceWindow}&gt;인증창열기&lt;/Button&gt;</code></pre><h3 id="2-백엔드-api-호출함수암호화토큰을-발급-만들기">2) 백엔드 api 호출함수(암호화토큰을 발급) 만들기</h3>
<p>  나는 <code>/lib/hooks</code> 폴더에 <code>useNiceAuthParams</code> 라는 커스텀훅을 따로 만들었다. </p>
<pre><code class="language-ts">// lib/hooks/useNiceAuthParams.ts

  import client from &#39;@lib/api/ClientApi&#39;;

import { useQuery } from &#39;@tanstack/react-query&#39;;

const useNiceAuthParams = () =&gt;
  useQuery&lt;any&gt;({
    queryKey: [&#39;nice-auth&#39;],
    queryFn: () =&gt;
      client.NiceAuth_CryptoToken_GET({
        returnurl: `${process.env.NEXT_PUBLIC_API_URL}/join/auth/nice-result`, // 가상의 페이지
      }),
    enabled: false,
  });

export default useNiceAuthParams;
</code></pre>
<p>return url은 본인인증한후 이후 로직을 사용할 페이지로 보내주는건데, 이때 가상의 페이지<code>/join/auth/nice-result</code> 를 만들어서 가상의페이지에서 인증완료된값을 받고, 그대로 부모의 창에 다시 넣어줄것이다.
이렇게 안하면 <strong>nice팝업창 안에서 다음로직의 창이 열리기때문</strong>이다. (nice인증창안에서 회원정보가입창이열림)</p>
<h3 id="3--프론트에서-나이스-인증창-열기">3)  프론트에서 나이스 인증창 열기</h3>
<p>  원래 코드로 돌아와서 <code>openNiceWindow</code> 함수가 실행되면 
  백엔드에서 발급받은 <code>enc_data</code>, <code>integrity_value</code>, <code>token_version_id</code> 을 넣어 인증창을 열어준다</p>
<pre><code class="language-tsx">  // 나이스 인증창
  const { refetch } = useNiceAuthParams();

  const openNiceWindow = async () =&gt; {
    const form = document.getElementById(&#39;form&#39;) as HTMLFormElement;
    const left = screen.width / 2 - 500 / 2;
    const top = screen.height / 2 - 800 / 2;
    const option = `status=no, menubar=no, toolbar=no, resizable=no, width=500, height=600, left=${left}, top=${top}`;

    const { data } = await refetch();

    if (form &amp;&amp; data) {
      const { enc_data, integrity_value, token_version_id } = data;
      window.open(&#39;/join/auth/nice-result&#39;, &#39;nicePopup&#39;, option);
      // 본인인증결과페이지경로 /join/auth/nice-result

      form.target = &#39;nicePopup&#39;;
      form.enc_data.value = enc_data;
      form.token_version_id.value = token_version_id;
      form.integrity_value.value = integrity_value;

      form.submit();
    }
  };</code></pre>
<h3 id="4-인증완료후-가상의페이지로-redirect">4) 인증완료후 가상의페이지로 redirect</h3>
<p>지금까지 한 로직을 실행하면 redirect url을 가상의페이지인 <code>/join/auth/nice-result</code> 에 인증된값이 도착할것이다.
<code>/join/auth/nice-result</code>페이지를 생성해준다. 이 페이지에서는 인증된값을 원래있던창<code>/join/auth</code>에주고 팝업닫음</p>
<pre><code class="language-tsx">&#39;use client&#39;;

import { useEffect } from &#39;react&#39;;

export default function NiceResultClient() {
  useEffect(() =&gt; {
    const urlParams = new URLSearchParams(window.location.search);

    const enc_data = urlParams.get(&#39;enc_data&#39;) ?? &#39;&#39;;
    const integrity_value = urlParams.get(&#39;integrity_value&#39;) ?? &#39;&#39;;
    const token_version_id = urlParams.get(&#39;token_version_id&#39;) ?? &#39;&#39;;

    if (window.opener &amp;&amp; enc_data &amp;&amp; integrity_value &amp;&amp; token_version_id) {
      window.opener.postMessage({ niceAuthSuccess: true, enc_data, integrity_value, token_version_id }, &#39;*&#39;);
      window.close();
    }
  }, []);

  return &lt;p&gt;본인인증 결과를 처리 중입니다...&lt;/p&gt;;
}</code></pre>
<h3 id="5-원래창에서-인증완료후-로직-실행">5) 원래창에서 인증완료후 로직 실행</h3>
<p>이제 인증된값이 원래페이지에 제대로 도착했을것이다. 그럼 인증완료후 하고싶었던 로직을 처리해준다.
<code>// 인증완료후 하고싶은 로직</code> 부분에, 반환된인증값 <code>enc_data</code>, <code>integrity_value</code>, <code>token_version_id</code>를 가지고 원하는 로직을 실행해준다.</p>
<pre><code class="language-tsx">  // 인증완료후,
  useEffect(() =&gt; {
    const handleMessage = async (event: MessageEvent) =&gt; {
      const { niceAuthSuccess, enc_data, integrity_value, token_version_id } = event.data || {};

      if (niceAuthSuccess &amp;&amp; enc_data &amp;&amp; integrity_value &amp;&amp; token_version_id) {
        try {
          // 인증완료후 하고싶은 로직 
          verifyMutation.mutate({
            enc_data,
            integrity_value,
            token_version_id,
          });
        } catch (error) {
          console.log(&#39;인증 확인 실패:&#39;, error);
          Alert(&#39;인증 확인 실패&#39;);
        }
      }
    };

    window.addEventListener(&#39;message&#39;, handleMessage);
    return () =&gt; window.removeEventListener(&#39;message&#39;, handleMessage);
  }, []);

</code></pre>
<h2 id="결론">결론</h2>
<blockquote>
<p>➊ nice 인증폼 생성
➋ 암호화토큰을 요청할 api를 커스텀훅으로 뺀다. 
➌ &#39;인증하기&#39; 버튼을누르면 위의 api를 호출하여 받은 <code>enc_data</code>, <code>integrity_value</code>, <code>token_version_id</code> 값으로 팝업창을 연다. 
(이렇게하면 나이스인증팝업창은 열린다.)
➍ 팝업창에서 인증완료후 가상페이지로 값을 받는다 (팝업창안에서 다음 로직창이 열리기떄문)
➎ 가상페이지에서 부모페이지(원래페이지)으로 인증값 <code>enc_data</code>, <code>integrity_value</code>, <code>token_version_id</code> 을 전달해주고, 팝업을 닫는다.
➏ 부모페이지(원래페이지)에서 인증된값이 주어진다.
➐ 이후 해당 인증값으로 원하는 로직을 처리한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스위프 8기 후기] 붕마카세 회고록 ]]></title>
            <link>https://velog.io/@5o_hyun/%EC%8A%A4%EC%9C%84%ED%94%848%EA%B8%B0%ED%9B%84%EA%B8%B0-%EB%B6%95%EB%A7%88%EC%B9%B4%EC%84%B8-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@5o_hyun/%EC%8A%A4%EC%9C%84%ED%94%848%EA%B8%B0%ED%9B%84%EA%B8%B0-%EB%B6%95%EB%A7%88%EC%B9%B4%EC%84%B8-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Thu, 27 Mar 2025 00:31:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/5o_hyun/post/0327873a-ea0d-4507-9518-d5be1db6cad8/image.png" alt=""></p>
<p>안녕하세요 프론트엔드 개발자 김소현입니다 :)
스위프(SWYP) 8기에 참여하여 서비스를 출시하고, 오늘은 그 6주간의 회고를 진행해보려고 합니다!</p>
<h2 id="✍-프로젝트-소개">✍ 프로젝트 소개</h2>
<blockquote>
<p>기간 : 2025.01~2025.03 
인원 : PM 1명, Design 1명, Front 2명, Backend 2명
주제 : 나만의 붕어빵 아카이브, 붕마카세
프론트 스택 : nextjs, typescript, tanstack-query, tailwindcss, shadcn/ui, storybook, vercel, pnpm
백엔드 스택 : java, spring boot, mysql, jwt, oauth2.0, junit, swagger, aws, pnpm
사이트 :  <a href="https://bungmakase.vercel.app">https://bungmakase.vercel.app</a>
깃허브 레포지토리 : <a href="https://github.com/bungmakase">https://github.com/bungmakase</a></p>
</blockquote>
<h2 id="🔥-성장한-경험">🔥 성장한 경험</h2>
<blockquote>
<p>*<em>❶ 스토리북 사용경험 *</em></p>
</blockquote>
<p>테스트도구를 이론으로 배워도 실무에서 사용하지않으면 혼자서는 사용해볼 일이 없기도하고, 혼자하더라도 맞는 방향인지 궁금했던게 많은 부분이 있었습니다.
그래서 실무에서 테스트도구를 사용해봤던 사람의 코드를 보며 배울수 있으면 좋겠다고 생각했습니다.
팀원을 구할때 테스트도구를 사용해 본 경험이 있는분을 구하려고했고, 스토리북을 사용해보신분이 있어서 이번 기회에 실제로 사용해볼 수 있어서 좋았습니다.</p>
<blockquote>
<p>*<em>➋ 사람에 치이지않아도 잘만된다  *</em></p>
</blockquote>
<p>이번 프로젝트는 독특하게 같이 협업하는 프엔개발자를 비롯한 모든 팀원들과 개인적인 톡이나 회의를 단 한번도 하지않았다는것입니다.
말로 표현하기는 힘든데 서로 일이 생기면 하려고하고, 각자 자연스럽게 분야를 맡고, 자연스럽게 완성했다(?) 고 해야하나ㅋㅋ... 
저도 이런경험은 처음이였는데 어딜가나 사람떄문에 스트레스를 받기마련인데 오히려 전체회의 딱 한번씩만 주마다하니까 사람에 치일일도없고 일도 더 잘되었던거같은 신기한 경험이였습니다.
이번 경험으로 느낀점은 꼭 굳이 회의를 자주하고 자주소통하는게 바람직하지만은 않다는것과, 소통없이도 알잘딱깔센으로 서로 일을 하면 편안하게 일할수도있다는것을 알았습니다. </p>
<h2 id="🩹-어려웠던-점과-극복한-방법">🩹 어려웠던 점과 극복한 방법</h2>
<blockquote>
<p>시간이 없다</p>
</blockquote>
<p>이번 프로젝트는 정말정말 시간이 없었습니다. 항상 어떤 사이드 프로젝트를 하던 api가 늦게나와서가 주였는데 이번에는 그게 문제가아니라, 정말 제 시간이 없었습니다.
면접 + 회사 + 사이드플젝 + 다른플젝 + 외주가 혼합된 시기여서 제 시간 자체가 너무 없었달까
그래서 제가 생각한 방법은 일단 시간날때 틈틈히라도 조금씩 해두자! 였습니다.</p>
<ol>
<li>디자인 나오기전에 공통컴포넌트만 디자인 해달라고 해서 공통컴포넌트제작과 스토리북을 먼저 개발</li>
<li>디자인 완성안되도 UI개발을하고 변경되는건 추후 수정 </li>
<li>백엔드가 완성될때까지 기다렸다가 하루에 몰아서 연동</li>
</ol>
<p>안될거같지만 다 되는게 신기했다는거...역시 안될건없다ㅋㅋㅋㅋ
극복했다고 볼 수는 없지만 다음에 또 이런 급박한 일정이 생긴다면, 지금처럼 우선순위와 순서를 두는게 중요하다고 생각했습니다. </p>
<h2 id="🥰-개선-및-추가할점">🥰 개선 및 추가할점</h2>
<blockquote>
<p>MSW(Mock Service Worker) 이용 </p>
</blockquote>
<p>스토리북을 사용하면서 저는 아직 미숙하여 단순 컴포넌트만 만들었는데, 같이 협업한 프론트개발자분을 보니 msw라고 API Mocking 라이브러리를 사용하는것을 볼 수 있었습니다.
현재 하고 있는게 많아서 아직 사용해보지는 못했는데, 이것을 이용하면 네비게이션등 실제 데이터가 있는거처럼 스토리북에 구현하는것이 가능해보여서 추후 배워서 적용해보고 싶다는 생각을 했습니다. </p>
<h2 id="✅-네트워킹행사-및-수료증">✅ 네트워킹행사 및 수료증</h2>
<p>네트워킹 행사에 참여하면 열심히 개발했던 저의 서비스를 소개시켜줄 수 있어 뿌듯함을 챙길 수 있고, 또 다른 서비스를 구경하는 재미도 있습니다. 
스위프에서 다양한 혜택도 제공하니 국비지원 후 프로젝트가 필요한사람이나 대학생에게 강추합니다!</p>
<ul>
<li>다양한 업계 IT 직군과 네트워킹</li>
<li>리뷰데이로 개발일정조율</li>
<li>웹호스팅제공, 광고부착, 서버비용지원</li>
</ul>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/43bfefd7-9ca8-4185-88e7-371f000ccc21/image.JPG%25E1%2584%258B%25E1%2585%25B4%2520%25E1%2584%2589%25E1%2585%25A1%25E1%2584%2587%25E1%2585%25A9%25E1%2586%25AB" alt=""><img src="https://velog.velcdn.com/images/5o_hyun/post/89ec27b2-c06f-43a7-91b8-2c259d209459/image.JPG%25E1%2584%258B%25E1%2585%25B4%2520%25E1%2584%2589%25E1%2585%25A1%25E1%2584%2587%25E1%2585%25A9%25E1%2586%25AB" alt=""></p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/2ae2a96a-1efc-46ed-8696-b20425541850/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/946df9b6-ad71-41c1-b5e0-9becc13e8e76/image.png" alt=""></p>
<hr>
<p>끝으로, 저희 서비스를 사용해보고 싶다면 아래 링크로 접속해주세요!
소개 페이지 : <a href="https://slashpage.com/bungmakase">https://slashpage.com/bungmakase</a>
서비스 바로가기 :  <a href="https://bungmakase.vercel.app">https://bungmakase.vercel.app</a>
깃허브 저장소 : <a href="https://github.com/bungmakase">https://github.com/bungmakase</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Suspense와 useSearchParams]]></title>
            <link>https://velog.io/@5o_hyun/Suspense%EC%99%80-useSearchParams</link>
            <guid>https://velog.io/@5o_hyun/Suspense%EC%99%80-useSearchParams</guid>
            <pubDate>Wed, 26 Mar 2025 00:08:09 GMT</pubDate>
            <description><![CDATA[<h2 id="문제발생-및-원인">문제발생 및 원인</h2>
<p><code>useQueryParams</code> 를 사용해 제품검색을 하는 기능을 만들었는데, useSearchParams() should be wrapped in a suspense boundary at page 라는 에러가 발생했다.
해당 에러는 <code>useQueryParams</code>을 사용하여 컴포넌트를 렌더링하면 검색하는도중 빈값일때 렌더링이 안되는 문제가 있으니 <code>suspense</code>로 감싸라는 에러메세지이다. </p>
<h2 id="보통의-해결방법">보통의 해결방법</h2>
<p>보통 구글링을하면 <code>useQueryParams</code>을 사용한 컴포넌트 밖에 <code>suspense</code>만 감싸라고 할것이다.
하지만 나의 경우 이렇게 해도 해결이되지않았다. </p>
<pre><code class="language-tsx">const Component = () =&gt; {
  ...
};

const ComponentWrapper = () =&gt; {
    &lt;Suspense fullback={null}&gt;
          &lt;Component/&gt;
    &lt;Suspense&gt;
};

export default ComponentWrapper;</code></pre>
<ul>
<li><code>Suspense</code> : useQueryParams를 사용한 컴포넌트를 감싼다.</li>
<li><code>fullback</code> : 값이 렌더링되지않을때 어떻게 표시할것인지</li>
<li><code>children</code> : 렌더링할 컴포넌트 </li>
</ul>
<h2 id="suspense-적용-위치가-잘못되었을-가능성">Suspense 적용 위치가 잘못되었을 가능성</h2>
<p>Suspense가 useSearchParams를 사용하는 페이지에서만 감싸져 있다면, 다른 페이지나 컴포넌트에서 useSearchParams를 사용할 때 제대로 처리되지 않아 오류가 발생할 수 있다. 
예를 들어, useSearchParams를 사용하는 컴포넌트가 여러 곳에서 렌더링되고 있을 수 있다.</p>
<p>해결 방법:
Suspense를 전체 페이지 혹은 QueryClientProvider와 같은 상위 레벨에서 감싸주는 방법도 고려해볼 수 있다. 이렇게 하면 useSearchParams를 사용하는 컴포넌트가 다른 곳에서 렌더링될 때 오류가 발생하지 않았다.</p>
<p>해결 이유:
404페이지나 에러페이지가 있었으면 전체를 안감쌌을수도있을거같은데, 나의경우 에러가발생했을떄 보낼수있는 에러페이지가 없기때문에 발생했던 경우였다. </p>
<pre><code class="language-tsx">import { Suspense } from &#39;react&#39;;
import { QueryClientProvider } from &#39;@tanstack/react-query&#39;;
import { QueryClient } from &#39;@tanstack/react-query&#39;;

const queryClient = new QueryClient();

export default function App() {
  return (
    &lt;Suspense fallback={&lt;div&gt;Loading...&lt;/div&gt;}&gt;
      &lt;QueryClientProvider client={queryClient}&gt;
        &lt;YourComponent /&gt;
      &lt;/QueryClientProvider&gt;
    &lt;/Suspense&gt;
  );
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTML특수문자리스트 ＆nbsp /＆amp /＆lt /＆gt /＆quot]]></title>
            <link>https://velog.io/@5o_hyun/HTML%ED%8A%B9%EC%88%98%EB%AC%B8%EC%9E%90%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EF%BC%86nbsp-%EF%BC%86amp-%EF%BC%86lt-%EF%BC%86gt-%EF%BC%86quot</link>
            <guid>https://velog.io/@5o_hyun/HTML%ED%8A%B9%EC%88%98%EB%AC%B8%EC%9E%90%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EF%BC%86nbsp-%EF%BC%86amp-%EF%BC%86lt-%EF%BC%86gt-%EF%BC%86quot</guid>
            <pubDate>Tue, 25 Mar 2025 05:45:55 GMT</pubDate>
            <description><![CDATA[<p>내가 보려고 만든 html특수문자리스트 정리 </p>
<p><code>&amp;nbsp;</code> 
 &#39; &#39; 공백(스페이스 한 칸)을 의미</p>
<p><code>&amp;lt;</code> 
부등호(&lt;)</p>
<p><code>&amp;gt;</code> 
부등호(&gt;)</p>
<p><code>&amp;amp;</code> 
&amp; </p>
<p><code>&amp;quot;</code> 
쌍따옴표(“)</p>
<p><code>&amp;#035;</code> 
sharp(#)</p>
<p><code>&amp;#039;</code> 
따옴표(‘)</p>
<p><code>&amp;#40;</code> 
왼쪽 괄호 ( </p>
<p><code>&amp;#41;</code> 
오른쪽 괄호 )</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[드래그 대표라이브러리 react-beautiful-dnd 의 변화 : Unable to find draggable with id: 1]]></title>
            <link>https://velog.io/@5o_hyun/%EB%93%9C%EB%9E%98%EA%B7%B8-%EB%8C%80%ED%91%9C%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-react-beautiful-dnd-%EC%9D%98-%EB%B3%80%ED%99%94-Unable-to-find-draggable-with-id-1</link>
            <guid>https://velog.io/@5o_hyun/%EB%93%9C%EB%9E%98%EA%B7%B8-%EB%8C%80%ED%91%9C%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-react-beautiful-dnd-%EC%9D%98-%EB%B3%80%ED%99%94-Unable-to-find-draggable-with-id-1</guid>
            <pubDate>Sun, 09 Feb 2025 08:26:52 GMT</pubDate>
            <description><![CDATA[<p>프론트에서 드래그 라이브러리로 아마 대부분 <code>react-beautiful-dnd</code>를 사용하고 있었을것이다.
나또한그랬다.
그런데 어느순간부터 Unable to find draggable with id: x 의 문구가 뜨기 시작했다. </p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/5b2472ce-a720-4761-b2f0-4564fd48e004/image.png" alt=""></p>
<p>검색해보니 dnd 라이브러리에서 자주 일어나는 문제였다. 
구글에 해결방법을 검색해보면 모두 strick mode 를 false로 돌려라 라는말만할텐데 아니었다..!
아마 내 블로그까지 왔으면 이미 해볼거 다해본사람이기 떄문에 본론만 말하겠다.</p>
<h2 id="원인">원인</h2>
<p>&quot;Unable to find draggable with id&quot; 오류는 일반적으로 react-dnd와 같은 드래그 앤 드롭 라이브러리에서 드래그 가능한 요소의 ID가 일치하지 않거나, 해당 요소가 드래그 가능한 상태로 제대로 등록되지 않았을 때 발생한다.</p>
<h2 id="체크할사항">체크할사항</h2>
<h3 id="1--draggable-요소에-id가-설정되어있는지-확인한다">1.  <code>draggable</code> 요소에 id가 설정되어있는지 확인한다.</h3>
<pre><code class="language-ts">
  &lt;Draggable
      draggableId={item.id} // 아이디가 있는지 확인! 
      index={index}
      key={item.id}
  &gt;
    {(provided) =&gt; (
        ...
    )}
  &lt;/Draggable&gt;</code></pre>
<h3 id="2-strick-mode가-false로-되어있는지-확인한다">2. strick mode가 false로 되어있는지 확인한다.</h3>
<p>솔직히 이부분은 내 입장에서 똑같다고 생각했다.. 뭐가 달라진진 모르겠는데 구글링하면 많은 검색결과에서 strick mode 를 false로 돌리면 해결된다~ 라고 하니 일단 해본다. </p>
<p><code>next.config</code> 파일에서 <code>strick mode</code> 를 <code>false</code>로 돌려봤다. </p>
<pre><code class="language-ts">await import(&#39;./src/env.js&#39;);

/** @type {import(&quot;next&quot;).NextConfig} */
const config = {
  reactStrictMode: false, // &lt;-----------------추가 
  webpack: (config) =&gt; {
    config.module.rules.push({
      test: /\.svg$/,
      use: [&#39;@svgr/webpack&#39;],
    });
    return config;
  },
  images: {
    remotePatterns: [],
  },
};

export default config;
</code></pre>
<p>아마 이렇게했을떄 마찬가지로 안되었을거다</p>
<h2 id="해결방법--hello-pangeadnd-로-변경">해결방법 : @hello-pangea/dnd 로 변경</h2>
<p><strong>찾아보니 react-beautiful-dnd 는 리액트 18버전부터는 지원하지않는다!</strong>
그래서 해결방법으로 다른 라이브러리를 찾다보니 이름만 변경된거같은 라이브러리를 발견했다. <code>@hello-pangea/dnd</code></p>
<p><code>npm i @hello-pangea/dnd</code> 로 설치해준다음 import된 부분만 해당 라이브러리로 바꿔주면 바로작동된다 ㄷㄷ;;</p>
<pre><code class="language-tsx">import {
  DragDropContext,
  Draggable,
  Droppable,
  DropResult,
} from &#39;@hello-pangea/dnd&#39;; &lt;--from뒤에 import 된 라이브러리명만 변경 </code></pre>
<p>react18버전이상으로 새로나온건지, 아니면 해당 라이브러리 제작자가 새 이름으로 다시 판건지는 모르겠지만, 기존 dnd와 똑같이 작동하는거로 봐선 후자가 아닐까 싶었다. </p>
<p>공홈은 여기! 
<a href="https://www.npmjs.com/package/@hello-pangea/dnd">https://www.npmjs.com/package/@hello-pangea/dnd</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CRA vs Vite ]]></title>
            <link>https://velog.io/@5o_hyun/CRA-vs-Vite</link>
            <guid>https://velog.io/@5o_hyun/CRA-vs-Vite</guid>
            <pubDate>Tue, 07 Jan 2025 11:32:35 GMT</pubDate>
            <description><![CDATA[<h2 id="cra종료-">CRA종료 ?</h2>
<p>이때까지 react 프로젝트를 간단하게 설치하기위해 CRA를 사용해왔었다.
그런데 최근 사용하려고보니 <code>testing-library/react</code>와 <code>react 19</code>버전이 불일치하는것 외에도 <code>reportvitals.ts</code>인식이 안되는 등 많은 오류가 있었다.
고치면 되긴했으나 근본적으로 왜 안되는지 알아보니, react19버전이랑 안맞아서 바꿔야한다도 있었지만 근본적으로 cra가 종료되었다는 소식을 들었다.
그래서 이때까지 애써 외면해왔던 Vite를 사용하기로했다. </p>
<h2 id="vite-설치">Vite 설치</h2>
<p><code>npm create vite@latest</code>  설치</p>
<p>1) 설치할거냐 -&gt; y
2) 어떻게 설치할거냐 -&gt; 싹밀고다시깐다(2번째클릭)
3) 프레임워크선택 -&gt; React
4) 환경 -&gt; Typescript
<img src="https://velog.velcdn.com/images/5o_hyun/post/e0c00f73-ae7c-4a1d-8876-7b5fdf757f5d/image.png" alt=""></p>
<p><code>npm i</code> 설치
<code>npm run dev</code> 개발모드 실행 </p>
<h2 id="로컬주소-변경하기">로컬주소 변경하기</h2>
<p>vite의 로컬주소는 <code>http://localhost:5173/</code>이다.
근데 아무래도 이때까지 로컬주소를 <code>3000</code>으로 썼었다보니 <code>5173</code>이 너무 어색했다. 또한 백엔드에서 보통 <code>3000</code>을 열어주기때문에 주소를 맞춰주기에도 좋다. 
그래서 5173 -&gt; 3000으로 바꾸는 방법을 알아보았다. </p>
<p><code>vite.config.ts</code> 폴더에서 서버 포트 설정 추가하기</p>
<pre><code class="language-ts">import { defineConfig } from &quot;vite&quot;;
import react from &quot;@vitejs/plugin-react&quot;;

export default defineConfig({
  plugins: [react()],
  server: { port: 3000 }, // 이부분추가
});
</code></pre>
<p>이렇게 하면 기본 포트주소를 변경할 수 있다! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[미마(meet mate) 회고록]]></title>
            <link>https://velog.io/@5o_hyun/%EB%AF%B8%EB%A7%88meet-mate-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@5o_hyun/%EB%AF%B8%EB%A7%88meet-mate-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Mon, 06 Jan 2025 13:41:42 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요 프론트엔드 개발자 김소현입니다 :)
스위프(SWYP) 7기에 참여하여 서비스를 출시하고, 오늘은 그 6주간의 회고를 진행해보려고 합니다!</p>
<h2 id="👉-참여동기">👉 참여동기</h2>
<p>저번기수에 이어 이번에도 참여한 계기는 사실 저번 기수가 너무 재밌었습니다 ㅎㅎ
경험을 떠나서 좋은 동료를 얻어간게 너무좋았달까 아직도 넘넘 친하게 지내고있습니다.
따라서 이번 프로젝트에서 얻어가고 싶었던건 저와 비슷한 년차를 가진분과 서로 도우면서 할 수 있었으면 좋겠고, 저번에는 전반적인 기능을 했기때문에 이번에는 핵심기능을 집중적으로 맡고 싶었습니다.</p>
<blockquote>
<ol>
<li>나보다 비슷한 연차나 경험을 가진 프엔개발자와 협업하기</li>
<li>핵심기능 집중적으로 개발하기 </li>
</ol>
</blockquote>
<h2 id="✍-프로젝트-소개">✍ 프로젝트 소개</h2>
<blockquote>
<p>기간 : 2024.11~2024.12 
인원 : PM 1명, Design 1명, Front 2명, Backend 2명
주제 : 모임을 더 쉽게, meet mate (MEMA)
프론트 스택 : nextjs, typescript, tanstack-query, styled-component, jest, cypress, storybook, velcel
백엔드 스택 : java, spring boot, gradle, jpa, jwt, oauth2.0, mysql, swagger,  chat gpt
사이트 :  <a href="https://meet-mate-mema.vercel.app">https://meet-mate-mema.vercel.app</a>
깃허브 레포지토리 : <a href="https://github.com/swyp-mema">https://github.com/swyp-mema</a></p>
</blockquote>
<h2 id="🔥-성장한-경험">🔥 성장한 경험</h2>
<blockquote>
<p>*<em>❶ 네이버지도 기반에 대한 API 로직처리 *</em></p>
</blockquote>
<p>회사에서 지도기반을 해보긴했지만 혼자 모든 로직을 처리한게 아니였고, 요즘 모든 서비스가 지도기반인게 많다보니 혼자 구현해보고싶었던 갈망이 있었습니다.
네이버지도는 처음 써봤지만 카카오지도랑 다른점은 딱히 못느꼈고, 네이버 클라우드 크레딧을 제공받을 수 있는 기회와 네이버 클로바 AI를 통해 추천기능을 구현해서 지도도 네이버로 사용했습니다.</p>
<blockquote>
<p>*<em>➋ AI 기반 기능 구현 *</em></p>
</blockquote>
<p>물론 AI기능은 서버에서 처리해서 제가 구현한 부분은 없었지만 최근 해커톤과 이번 프로젝트에서 AI기반으로 사용하다보니 어떻게 돌아가는지 과정을 이해하게 되었습니다.
AI기능을 구현한다하면 엄청 어려운거라 생각했는데, 우리가 chat gpt에 ai로 검색하는거랑 똑같아서 생각보다 간단해서 혹시 저처럼 도전을 두려워하고있다면 쉽다고 말해주고 싶었습니다! 
다만 경험의 차이는 있는 것 같았습니다. (예를 들면 이번 미마에서는 처음써보시는거였고, 전에 해커톤했었을때는 개발자출신 기획자분이 있으셔서 프롬프트를 얼마나 잘짜느냐, 그리고 얼마나 단순하게쓰고 나머지는 db에서 갖다쓰냐 등이 다르다고 느꼈음)</p>
<blockquote>
<p>*<em>➌ 코드리뷰 *</em></p>
</blockquote>
<p>사실 지금까지 해오면서 타 프론트엔드 개발자와 이렇다할 코드리뷰를 쌓은적이 없었습니다. 그래서 저보다 더 경험이 많은 개발자와의 협업을 해보고싶은 마음이 들었습니다.
이번에 같이 개발하게된 프론트엔드 개발자분은 전공자에 3년차 개발자라 그런가 웹으로는 많은 경험이 없으셔도 확실히 이해도와 cs지식이 풍부한것같았고 전회사에서 코드리뷰를 열심히 하는 회사에서 나오셨다고 했습니다.
그래서 이번에 최적의 코드를 찾으려고 노력했고 한번 pr을 올리면 10개이상의 코드리뷰가 달렸습니다. (물론 나중에 바빠질땐 그냥 했다...)
우리둘다 부족하기때문에 솔직히 지금이 최적의 코드는 아니라고 생각하지만, 막코드가아니라 한번이라도 더 생각해보고 올리게 되기때문에 이 경험 자체는 좋았던 것 같습니다.</p>
<h2 id="🩹-어려웠던-점과-극복한-방법">🩹 어려웠던 점과 극복한 방법</h2>
<blockquote>
<p>팀의 사기 </p>
</blockquote>
<p>이번 프로젝트는 팀의 사기가 확실이 줄어든 느낌이였습니다.
이유는 정확히는 모르겠지만 1주일마다 리뷰데이를 가지면서 다른팀보다 우리팀이 늦는다고 생각해서 속도도 느리고 분위기 자체도 쳐졌던것같고...
주변사람들 들어보면 원래 사이드플젝이 그렇다고들하는데 저는 사람에 의해서 많은 영향을 받기때문에 영향을 좀 받은것도 있는 것 같습니다.
사실 최근 많은 외주프로젝트와 해커톤에 사이드프로젝트까지 시기가 바쁘기도해서 제가 팀의 분위기를 많이 못살핀 탓도 있었습니다.
이번에 경험했던건 제가 다른사람에게 영향을 받는게아니라 나자신이 팀의 사기를 북돋을 수 있는 사람이 되어야겠다고 생각했고, 이렇게 생각하니 끝나고나니 팀원들에게 미안했습니다.
앞으로는 분위기를 타는사람이아니라 분위기를 만들 수 있는 사람이 되어야겠다고 생각했습니다!  </p>
<h2 id="🥰-개선-및-추가할점">🥰 개선 및 추가할점</h2>
<blockquote>
<p>테스트코드 작성 </p>
</blockquote>
<p>이번 프로젝트를 하며 같이 개발하고 있는 프론트엔드 개발자와 이야기를 처음 나눴을때 우리가 도입하고 싶었던건 테스트코드를 작성하는것 이였습니다.
아무래도 요즘 테스트경험을 요구하는데가 많은데 회사에 다니고있더라도 처음 도입하는게 어렵기때문에 많은 경험이 없으면 힘든 부분이여서 경험해보기로 했었습니다.
그래서 우리는 처음 테스트코드 이론공부를 했고 이번 프로젝트에 도입해보기로했는데 아무래도 6주프로젝트에 개발기간은 1주일반정도 였다보니 도입이 잘 안됬습니다.</p>
<p>일단 mvp기능은 마무리했고, 같이 협업하는 프론트개발자분도 여행갔다가 이제 복귀했기때문에 슬슬 테스트코드 작성을 같이 해보면서 협업을 해보면 좋을 것 같습니다.</p>
<p>기능구현만 하면 됐지! 라고 생각하는 사람도 있을거같은데, 저와 같은 생각을 가지고 있는 동료를 만나게되어 정말 감사한 부분인것같습니다.</p>
<h2 id="✅-네트워킹행사-및-수료증">✅ 네트워킹행사 및 수료증</h2>
<p>네트워킹 행사에 참여하면 열심히 개발했던 저의 서비스를 소개시켜줄 수 있어 뿌듯함을 챙길 수 있고, 또 다른 서비스를 구경하는 재미도 있습니다. </p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/c59b0f8c-15b3-4ba0-80f2-c97a19b12dce/image.png" alt="">
<img src="https://velog.velcdn.com/images/5o_hyun/post/2ed7e9e7-9f45-4d52-90a8-5f8379f9b663/image.png" alt="">
<img src="https://velog.velcdn.com/images/5o_hyun/post/c2081fc3-29ee-4df3-a952-31e531e9a918/image.png" alt=""></p>
<hr>
<p>끝으로, 저희 서비스를 사용해보고 싶다면 아래 링크로 접속해주세요!
서비스 바로가기 : <a href="https://meet-mate-mema.vercel.app">https://meet-mate-mema.vercel.app</a>
깃허브 저장소 : <a href="https://github.com/swyp-mema">https://github.com/swyp-mema</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[react-native 알아두면 좋을것들 (웹으로띄우기)]]></title>
            <link>https://velog.io/@5o_hyun/react-native-%EC%95%8C%EC%95%84%EB%91%90%EB%A9%B4-%EC%A2%8B%EC%9D%84%EA%B2%83%EB%93%A4</link>
            <guid>https://velog.io/@5o_hyun/react-native-%EC%95%8C%EC%95%84%EB%91%90%EB%A9%B4-%EC%A2%8B%EC%9D%84%EA%B2%83%EB%93%A4</guid>
            <pubDate>Mon, 06 Jan 2025 12:35:34 GMT</pubDate>
            <description><![CDATA[<h2 id="웹으로-테스트">웹으로 테스트</h2>
<p>계속 앱으로 테스트를 하니 바로바로 변경되는걸 보기 힘들어졌다. 
개발은 컴퓨터로하는데 보는건 앱으로 봐야하니,, 그래서 웹으로 보는방법을 알아보았다. </p>
<p><code>npm start</code> 를 입력해 앱이 켜져있는 상태에서, <code>w</code> 를 터미널에 입력해보면 다음과같이 뜬다.
그럼 ctrl+c로 종료해주고  해당 나온부분을 설치해준다.</p>
<p><code>npx expo install react-dom react-native-web @expo/metro-runtime</code></p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/3399e8a3-ae51-463d-9119-9a95df85d88d/image.png" alt=""></p>
<p>그리고 다시 <code>npm start</code>로 켜준다음, <code>w</code>를 누르면 웹에서 켜지게된다! </p>
<h2 id="이외에-알아두면-좋을것들">이외에 알아두면 좋을것들</h2>
<p>웹개발에서는 코드가 변경되면 자동으로 반영이 되었는데 앱은 안된다는걸알았다.!
계속 터미널에서 종료하고 새로열고 하려니 힘들어서 바로바로 새로고침 하는방법을 알아보았는데 생각보다 넘나 간단했다.</p>
<p>앱이 켜져있는상태로 터미널에서 <code>r</code> 누르면 웹,앱 둘다 바로 새로고침 반영되었다.</p>
<p>이렇게 기타 등등 알 수 있는건 일일히 찾아보는게아니라 실행시 터미널에 적여져있는 이 부분을 보면 알 수있으니 참고하자! </p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/5214cfc1-1d59-42ac-8db1-f2f065715753/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[react-native 하이브리드앱 생성]]></title>
            <link>https://velog.io/@5o_hyun/react-native-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C%EC%95%B1-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@5o_hyun/react-native-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C%EC%95%B1-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Sat, 04 Jan 2025 05:12:38 GMT</pubDate>
            <description><![CDATA[<h2 id="1-typescript설치">1. Typescript설치</h2>
<pre><code>npm i -g typescript ts-node</code></pre><h2 id="2-expo-cli설치">2. EXPO CLI설치</h2>
<p>다음 명령어를 터미널에 입력하여 Expo App 개발에 필요한 패키지를 설치한다.
<code>npm i -g expo-cli</code></p>
<p>이후 설치가 잘되었는지 확인하자.</p>
<pre><code>npm show expo version // 50.0.14
npm show expo-cli version // 6.3.10</code></pre><p>이후 expo에 로그인을 해준다.
계정이 없는 경우 <a href="https://expo.dev">Expo Web Site</a>에서 가입하거나, expo register 해주면 된다.</p>
<pre><code>npx expo register // 계정이 없는 경우에만
npx expo login // expo에 로그인</code></pre><p>로그인이 잘된것을 확인해보자. 계정정보가 나온다</p>
<pre><code>npx expo whoami</code></pre><p>expo-cli 및 expo 로그인 후 터미널에서 아래 명령어를 실행하면 typescript 사용 환경이 초기화 된 Expo App 프로젝트가 생성된다.</p>
<pre><code>// 새폴더를만들어서생성할경우
npx create-expo-app 앱이름 -t expo-template-blank-typescript

// 현재폴더에생성할경우
npx create-expo-app . -t expo-template-blank-typescript</code></pre><h2 id="3-초기세팅">3. 초기세팅</h2>
<h3 id="1-app구조">1. APP구조</h3>
<p>기본 폴더 구조는 아래와같다. react-native cli에 비해 간단한 구조라고 한다. </p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/b38460b2-ebcc-4559-bd9e-4fbbc8c1f02b/image.png" alt=""></p>
<p><code>assets</code> 폴더는 기존 react와는 똑같이 이미지등의 정적파일들이 담겨있다. (icon, favicon, splash)
로컬에서 개발할 때 이 <code>asset</code> 파일들은 <code>app.json</code>파일에 연결하여 사용하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/84a5a966-23aa-4d3e-9385-09f99c4f5c32/image.png" alt=""></p>
<p><code>app.json</code>에는 필요한 파일들이 초기화되어있다. 앞서말한 <code>assets</code>의 favicon, icon, splash등도 여기있고 앱을 개발하다가 필요한 부분을 바꾸면된다. 
또한 <code>jsx</code>가 아닌 <code>App.tsx</code>로 초기화했기때문에 변경되어있는것을 볼 수 있다. </p>
<h3 id="2-prettier설정">2. prettier설정</h3>
<p>프리티어 설정하는 부분은 기존 react와 다른게 없다. </p>
<p>프리티어 설치를 한다.
<code>npm i -D prettier</code></p>
<p>루트에 <code>.prettierrc</code> 생성후 설정값설정 </p>
<pre><code>{
  &quot;singleQuote&quot;: true,
  &quot;semi&quot;: true,
  &quot;useTabs&quot;: false,
  &quot;tabWidth&quot;: 2,
  &quot;printWidth&quot;: 80,
  &quot;sortImports&quot;: true,
  &quot;importOrderSeparation&quot;: true,
  &quot;importOrderSortSpecifiers&quot;: true
}
</code></pre><h3 id="3-typescript만-사용하게-설정">3. Typescript만 사용하게 설정</h3>
<p><code>tsconfig.json</code> 파일에서 <code>tsx</code> <code>ts</code>만 사용한다고 명시해준다.</p>
<pre><code>{
  &quot;extends&quot;: &quot;expo/tsconfig.base&quot;,
  &quot;compilerOptions&quot;: {
    &quot;strict&quot;: true
  },
  &quot;include&quot;: [&quot;**/*.ts&quot;, &quot;**/*.tsx&quot;] // 추가 
}
</code></pre><h2 id="4-실행하기">4. 실행하기</h2>
<p>package.json에 start 보면 기본적으로 <code>npm start</code> 하면 실행되게 설정되어있다. 
실행해보면 다음과같이 QR코드로 뜨고 실행해볼 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/d603f09f-657f-4434-8a94-f9abae6272dd/image.png" alt=""></p>
<p>Happy Coding! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kakao X Goorm] 카카오구름톤12기 프론트엔드 후기]]></title>
            <link>https://velog.io/@5o_hyun/Kakao-X-Goorm-%EC%B9%B4%EC%B9%B4%EC%98%A4%EA%B5%AC%EB%A6%84%ED%86%A412%EA%B8%B0-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@5o_hyun/Kakao-X-Goorm-%EC%B9%B4%EC%B9%B4%EC%98%A4%EA%B5%AC%EB%A6%84%ED%86%A412%EA%B8%B0-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Mon, 16 Dec 2024 14:41:01 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/5o_hyun/post/14f6b3c1-aa5a-4aeb-a329-e751104979a2/image.jpg" alt=""></p>
<h2 id="지원-동기">지원 동기</h2>
<p>외주만 돌리는 프리랜서로 산지 어언 1년,, 이제는 다시 취업이 하고싶어졌습니다.
항상 실무위주로 돌려서 몰랐었는데 개발자들은 보통 깃허브 주소를 포트폴리오로 내니까 한번 둘러보게되었는데 다른분들 깃헙을 보니 본업말고도 사이드플젝이 넘넘 많은것임...
저는 왜 본업만 했는가....에대한 회의감이 들었고 외주를하면서 동시에 여러 사이드프로젝트를 하게 되었고
그러다가 이 카카오 구름톤을 발견하게 되었습니다.
사실 비전공자라 바로 실무투입된 케이스이기도해서 해커톤에 참여할 기회도 없었고 인맥도 없었기때문에.. 이번에 한번 참여 해보고 싶었습니다. 
회사들어가면 언제 이런거 나가보겠냐 싶었고 다들 한번씩은 나가는데 나도 나가보자! 하는 마음에 신청기간을 기다리다가 신청했습니다.</p>
<h2 id="지원-과정">지원 과정</h2>
<h3 id="지원서-작성">지원서 작성</h3>
<p>지원서에 있던 항목을 가물가물 떠올려보자면</p>
<ol>
<li>지원하게 된 동기 : 현재 프리랜서고 곧 취업을 하고싶어 올해 마지막 기회기도 하고 이때까지 기회가없었다. 이거쓰려고 한달기다렸다 </li>
<li>제주도가 직면한 사회문제 : 제주도는 관광산업이 유명한데도 불구하고 저품질의 관광산업이 주를 이루고있다.</li>
<li>이 문제를 해결하기위해 어떤서비스를 개발하고싶은지 : AI 기반 선호도를 조사해 친환경 코스를 제공하고, 탄소를 줄이는 방법? 이런걸로 친환경 제주 산업을 촉진하고, 데이터를 쌓아 나중에 제주도에 고품질의 관광산업을 할수있을거다.</li>
</ol>
<p>4부터는 생각이 안나는데 그냥인성질문이였던듯</p>
<p>사실 다른 프로젝트가 너무 많이 밀려있어서 1시간만에 얼레벌레 제출한것같습니다. 하지만 해커톤을 해보고싶은 제 마음은 간절했기에 1번을 진짜 진심을 다해 작성했고 합격했습니당당🎉</p>
<h3 id="합격🎉">합격🎉</h3>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/2325ff30-21f9-49dd-8218-b930b0a386ce/image.png" alt=""></p>
<p>아는 동료와 같이 지원했는데 저만 붙었습니다....ㅠㅠ (근데 나중에 들어보니 경쟁률이 2X:1 이라고 합디다.. 같이될확률은 거의없는거였음) </p>
<p>암튼 비행기와 1일차 숙소를 미리예약하시는거같던데, 저는 너무 바빠서 전날에 비행기와 숙소를 끊었습니다.
나중에 보니 사람들이 보통 제주시청 위주로 숙소를 잡았습니다. (보통 시청주변게스트하우스나 라마다호텔 잡는듯)
결국 시청주변에서 모이게되니 유앤아이게스트 추천합니다. 그곳이 가장 번화가와 모든 숙소의 중간지점이라서 밤에 같이 모이게되도 숙소들어가기좋음.
저는 운좋게 거기를 잡았는데 다 제 숙소근처로 모여서 좋았습니다 ^ㅠ^</p>
<h2 id="1일차-활동">1일차 활동</h2>
<p>1일차에 10시까지 구름스퀘어로 모이라하는데 10시&#39;에&#39; 열어줍니다. 그래서 미리가면 줄서있어야함..
10시 넘어서 가는것을 추천드립니다.
좌석도 선착순이아니라 들어가면 기념품 나눠주는데 그 기념품에 랜덤으로 앉게될 테이블번호가 쓰여져있으니 혹시라도 미리가지않는것을 추천드립니다</p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/2c4e4d35-cb73-4f4f-a41d-1ce0741a0b62/image.jpg" alt=""></p>
<p>1일차는 아이스브레이킹과같은 간단한게임과 본인PR 그리고 대부분이 강의를 듣습니다
저는 일을 다 끝내고오느라 밤을새고와서 죽을뻔했습니다.................</p>
<p>그리고 극E 가 아니면 살아남기 힘든 분위기.
저도 ENFP인데 진짜 여기는 사람들이 다 같이 친해지려는 분위기도있고 네트워킹을 24시간 한다고 생각하면 되서 기가 빨리지만 좋습니다(?)
암튼 진짜 극E가 아니면 좀 힘듭니다 기빨려서 나중에 3-4시될때는 졸려서 죽을뻔했습니다...😵</p>
<p>이날 키워드를 키워드를 주는데 다음날까지 PPT 제출을 해야합니당.
이번 12기 기수는 #제주도 #클라우드 #고령화 였는데 다들 고령화는 예상치못해서 힘들어했습니다ㅋㅋㅋ</p>
<p>저는 11시까지 끝내고 12시에 잠들었는데 다들 새벽제출을 하셨더라구요,,, 저는 다음과 같이 제출했습니다.
처음 지원서에 냈던거처럼 선호도 조사 기반의 AI서비스를 만들고 싶었습니다.
<img src="https://velog.velcdn.com/images/5o_hyun/post/091077db-ae15-40fe-8807-0095f99785b5/image.png" alt=""></p>
<h2 id="2일차-활동">2일차 활동</h2>
<p>2일차에는 어제 모였던 구름스퀘어에 모여 PPT 발표를 하고, 팀빌딩을 합니다.
많은 후기에서 팀빌딩이 전쟁이다 라는말이있듯이 전쟁 그 자체이긴합니다. 적극적인 사람들이 위주로 팀들을 찾아다니지만 걱정할필요없습니다.
제 다년간의 짬밥으로 인하면 나중에 팀빌딩이 될수록 실력자들이 많습니다. 이때 최대한 뻘쭘하지 않은척 기다립니다.
다행히 저를 찾아주시는 팀이있어 같이 하자고 했지만 프론트가 1명이 부족한 상태에서 거의 마지막 두팀이 남게되어 찢어졌는데, 결국 이 마지막 두팀 둘다 수상했습니다 ㅎㅎ</p>
<p>암튼 이렇게 팀빌딩을 하고 성산일출봉 플레이스캠프로 넘어갑니다. 
숙소에 짐풀시간도없이 가방만 던져놓고 나와서 팀끼리 모여앉아 무한강의를 듣습니다.
이때 틈틈히 아이디에이션 디벨롭을 했습니다.
강의를 다 들으면 비어파티를 하는데 비어파티 챰 넘 좋았습니다 ✋ 
저도 처음에 이거 먹고 다들 개발하러모인다해서 눈치보면서 안마셨는데 나중에 우리 팀 개발자들 다 술 많이먹는것을 확인후 맘놓고 먹었습니다😊😊</p>
<p>멘토들이랑 애기도할수있고, 다른사람들과 이야기도할수있지만 이게다가아닙니다. 
비어파티 때는 뭐랄까
그냥 제주도까지와서 코딩만하다가는게아니라 이렇게 낭만있게 술도한잔하고 코딩도하고가니 너무너무 이 일정자체가 재밌겠다~~ 라는 생각이들어 좋았습니다.</p>
<p>비어파티가끝나고 다른팀은 아이디에이션을 했는데 저희팀은 아이디어는 이미나온후여서 무리하지않고 다들 숙소들어가서 푹 쉬어줬습니다.</p>
<h2 id="3일차-4일차-활동">3일차-4일차 활동</h2>
<p>이제 본격적인 해커톤시작입니다.
아침에 일어나서 오자마자 코딩을 시작합니다. 우리팀은 뭐랄까 그냥 모든게 순조로웠습니다.
아이디어부터 디벨롭 디자인 코딩 까지 순탄하게 흘러갔고, 팀원과의 마찰도 없었습니다. 그래서 집중해서 빠르게 끝낼 수 있었던것같습니다.
크램폴린IDE환경에서 배포하면 가산점이있기때문에 그곳에 시간을 많이들였는데 다른 개발자분들도 다들 처음써보는것이기때문에 너무 머리아파할 필요없습니다.</p>
<p>빨리 끝낼 수 있을줄 알았지만 4일차 아침 8시에 배포 및 시연영상까지 촬영을 끝냈습니다.
이후 숙소가서 40분정도 잠을자고 짐을싸서 나온것같습니다.
근데 놀라운점은 우리팀이 제일 빨리제출했다는사실... ㅋㅋㅋㅋㅋ
다들 10시제출이면 10시까지 끝까지 잡고있었습니다.</p>
<h2 id="발표">발표</h2>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/df07bc5a-a58c-4858-a38e-1c0506156006/image.jpg" alt=""></p>
<p>발표를 듣는데 심사위원분들은 확실히 기술적인것보단 아이디어를 중요시여기는것같았습니다.
그리고 그 아이디어가 그저 신박한것이아닌, #제주도 #클라우드 #고령화 이 키워드에 맞춰 신박하냐 를 보는것같았습니다.
우리는 제주도에 이주하고 싶은 사람들과, 이미 정착하고있는 사람들을 AI로 매칭해주는 서비스를 개발했습니다.
웹크롤링으로 제주 이주 정보를 모아 초기유저를 모아 
어르신들의 사용성을 고려해 줌링크로 간단하게 미팅을 열수있게 제작했고, 이후 피드백을 통해 황금향을 키우는 재미있는 요소도 곁들였습니다. 
다른팀이지만 어르신이 음성으로 이야기를 들려주면 아이가 어르신의 이야기를 듣고 미니홈피처럼 물건을 받아 꾸미는 서비스를 기획한팀이있는데 이 팀의 아이디어도 정말 좋았다고 생각했습니다. </p>
<p>아무튼 이렇게 각자 발표를 하고 수상을 진행했는데 저희 서비스가 우수상을 받았습니다🎉
다음 회고록으로 서비스에 대한 간단한 소개를 올리면 좋을것같네용.
해커톤은 대학생과 전공자만할수있는 특권이라고 생각했는데 저도 이번에 참여하게 되어서 너무 좋았습니다.
이 글이 다음 지원하는 지원자에게 참고가 되길 바라며, 모두 수고하셨습니다💞</p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/3aa76512-f09d-46c3-8a3e-983af3871ba3/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[구름 디자인시스템 GDS]]></title>
            <link>https://velog.io/@5o_hyun/GDS</link>
            <guid>https://velog.io/@5o_hyun/GDS</guid>
            <pubDate>Mon, 16 Dec 2024 10:03:44 GMT</pubDate>
            <description><![CDATA[<p>왜 디자인 시스템이 필요할까? </p>
<p>디자이너가 바뀌거나 하면서 <strong>디자인 파편화</strong>가 일어나므로 이를 방지하기위함이다. (각기다른 디자인과 색상등)</p>
<p>1.타겟설정
회사, 사용자, 만드는사람(개발자,디자이너)
2.타겟이 겪는 문제발굴
3.액션 아이템 설정
4.각 타겟에 맞는 목표설정</p>
<p><strong>규모가 커짐에 따라 유연한 디자인시스템이 요구됨.</strong></p>
<h2 id="1-최소한의-기능과-형태-정의">1. 최소한의 기능과 형태 정의</h2>
<h3 id="기능-정의">기능 정의</h3>
<p>W3C의 ARIA Authoring Practices Guide (APG)를 기준으로 기능을 정의한다.</p>
<h3 id="형태-정의">형태 정의</h3>
<p>디자인토큰으로 표시할 수 있는 형태(색상,폰트사이즈,굵기 등)와 애니메이션을 형태로 최소한으로 둔다. </p>
<h3 id="현재-유명한-디자인시스템">현재 유명한 디자인시스템</h3>
<ul>
<li>완성형 UI : Material UI, Antd</li>
<li>커스터마이징 UI : shadcn/ui , daisyUI</li>
<li>필수 기능만 정의 : Radix, React Aria (형태는 정의하지않아 유연하게 적용할수있음)</li>
</ul>
<p>현재 구름에서는 필수기능만 정의하고 최소한의 형태만 정의한 디자인시스템을 구축하고있다. </p>
<h2 id="2-속성-통일">2. 속성 통일</h2>
<p>디자이너와 개발자가 컴포넌트의 동일한 속성을 정의해 불일치 없앤다.
디자인 피그마에서는 variants, 개발에서는 props로 사용하는데 단어가 다르므로, 이를 공통의 용어 Component Property로 통일하여 100%일치하도록 한다.</p>
<p>예를들면 피그마에서 showIcon 으로했으면 개발에서 props로 ShowIcon으로 하지말고 동일하네 showIcon으로 통일해야한다.</p>
<h2 id="3-조합형-컴포넌트-설계">3. 조합형 컴포넌트 설계</h2>
<p>Antd 등의 환성된 컴포넌트의 한계가 있다. 모두이해/복잡성증가/높은진입장벽에 어렵고, property로 미리정의된 형태만 가능하므로 자유롭게 변경하기 힘들다. </p>
<ul>
<li><p>프로퍼티의 분산과 단순화
DatePicker의 컴포넌트가있다고치면 DatePicker.Header DatePickerBody DatePickerSideBar 등으로 하나의 컴포넌트를 따로따로 나눈다. </p>
</li>
<li><p>일관성있는관리
DatePicker.Header DatePickerBody 등등 각각 서브컴포넌트별로 disabled등으로 일관성있게 관리가 가능하다. </p>
</li>
<li><p>유연한 조합</p>
</li>
</ul>
<p>=&gt; 이는 *<em>Compound Pattern *</em>이라고 한다. </p>
<blockquote>
<p>이렇게 조합형컴포넌트를 사용하다보니, UI의 일관성이 깨지고 , 자주쓰이는데도 매번 직접 정의해야하는 문제점이 생겼다고한다.
그래서 무조건 필수기능만 정의한 조합형 컴포넌트만 사용하는게 아니라, 자주 사용하는것은 완성형 컴포넌트 등으로 유도리있게 만들며 조율하고있다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[카카오 기획자의 발표잘하는법 강의 -에디]]></title>
            <link>https://velog.io/@5o_hyun/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EA%B8%B0%ED%9A%8D%EC%9E%90%EC%9D%98-%EB%B0%9C%ED%91%9C%EC%9E%98%ED%95%98%EB%8A%94%EB%B2%95-%EA%B0%95%EC%9D%98-%EC%97%90%EB%94%94</link>
            <guid>https://velog.io/@5o_hyun/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EA%B8%B0%ED%9A%8D%EC%9E%90%EC%9D%98-%EB%B0%9C%ED%91%9C%EC%9E%98%ED%95%98%EB%8A%94%EB%B2%95-%EA%B0%95%EC%9D%98-%EC%97%90%EB%94%94</guid>
            <pubDate>Mon, 16 Dec 2024 10:02:41 GMT</pubDate>
            <description><![CDATA[<h2 id="빠져드는-기획의-필수요소">빠져드는 기획의 필수요소</h2>
<h4 id="1-어려운-이야기는-개념정리부터-하고-시작">1. 어려운 이야기는 개념정리부터 하고 시작</h4>
<p>비개발자가 보아도 이해가 쉬운것 </p>
<h4 id="2-우리가-마주한-문제를-이미지와-그림으로-표시하자">2. 우리가 마주한 문제를 이미지와 그림으로 표시하자.</h4>
<p>이미지와 그림은 <strong>누구나 만들기 쉽고 이해하기 쉬운 수준</strong>으로 제작하자. </p>
<h4 id="3-문제점-인지-후-해결방안으로-바로-연결하기">3. 문제점 인지 후 해결방안으로 바로 연결하기</h4>
<h4 id="4-신뢰도를-향상시킬-수-있는-말을-인용하기">4. 신뢰도를 향상시킬 수 있는 말을 인용하기</h4>
<p>전문가의 말을 이용하면 신뢰도를 극대화할수있다.
내가 이런말을 하고싶다 이거보다는 스티브잡스가 강조하던 이야기다~ 이런식으로 말하면 신뢰가 가기때문에 설득력이 높아진다.</p>
<h2 id="잘-준비된-기획서가-빛을-발하는-발표">잘 준비된 기획서가 빛을 발하는 발표</h2>
<h4 id="발표시작-떄-관심을-끌-수있는-어그로로-시작하기">발표시작 떄 관심을 끌 수있는 어그로(?)로 시작하기</h4>
<p>스티브잡스도 애플 발표시 &quot;2년반동안 제가 손꼽아서 기다려왔던날입니다&quot; 라고 발표했다.
모두가 집중할 수 밖에없는 시작말이다. 
위인이나, 성공한 사람들을 예로 들면 아주아주 좋을것같다. </p>
<h2 id="프레젠테이션에서-초반-관심도를-올리는-방법">프레젠테이션에서 초반 관심도를 올리는 방법</h2>
<h4 id="1발표에-의미를-부여">1.발표에 의미를 부여</h4>
<h4 id="2과거의-성과나-숫자를-언급">2.과거의 성과나 숫자를 언급</h4>
<p>과거의 성과나 숫자를 높여 내 이야기의 객관성을 확보하고 신뢰를 높이자</p>
<h4 id="3유머러스한-이야기">3.유머러스한 이야기</h4>
<p>윤여정이 수상발표시 &quot;아들들이 일하러 나가라고 잔소리햇서 엄마가 이 상을 받았다&quot; 라고 말했다.</p>
<h3 id="이-세가지중-하나만해도-확실히-발표에-대한-관심도를-올릴-수-있다">이 세가지중 하나만해도 확실히 발표에 대한 관심도를 올릴 수 있다.</h3>
<h2 id="🤟-강의-후기-및-느낀점">🤟 강의 후기 및 느낀점</h2>
<p>내가 가장 취약한 부분이라면 부분인점이 발표다.
개발 일을 하게되면서 발표할 일이 거의 없긴하지만 아예없는것은 아니기때문에,,
요즘 사람들은 나이대신 MBTI를 물어보는 시대가 되었는데, 나같은경우 ENFP라하면 보통 발표도 잘한다 생각하고 친화력도 좋다 생각하는데 아니다.
나랑 친한 사람들 앞에서만 엄청 쾌활할뿐,, 일에 있어서는 I가 되가는것같다.
회사에서도 말을 안할수록 더 좋은 사람이라는 말을 듣기때문에 나도모르게 일에 있어서는 항상 조용히 하고있는것이 버릇이 되어서 사람들 앞에서 발표는 더더욱 잘 안되는것같다.</p>
<p>에디님 강의를 들으면서 발표하는법에대한 알짜배기만 공부한 느낌이 들었다.
간략하게 정리했지만 정말 핵심이 쏙쏙...</p>
<p>*<em>나중에 어디가서 발표할떄 이 자료만 보고 발표자료만들거나 연습해야겠다. *</em></p>
<p><del>_역시 카카오기획자는 다른건가... 대기업의.... 실력..
발표도 알짜배기 ppt도 알짜배기로 그냥 쏙쏙 머리속에 넣어주셨다. _</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[카카오 DevRel 헌터님 강의]]></title>
            <link>https://velog.io/@5o_hyun/%EA%B5%AC%EB%A6%84%ED%86%A4-%ED%97%8C%ED%84%B0%EB%8B%98-%EA%B0%95%EC%9D%98</link>
            <guid>https://velog.io/@5o_hyun/%EA%B5%AC%EB%A6%84%ED%86%A4-%ED%97%8C%ED%84%B0%EB%8B%98-%EA%B0%95%EC%9D%98</guid>
            <pubDate>Mon, 16 Dec 2024 09:54:04 GMT</pubDate>
            <description><![CDATA[<h2 id="바퀴를-재발명-하지말자">바퀴를 재발명 하지말자.</h2>
<p><strong>이미 선배들이 오랜시간 노력해서 만든 성과들이있다면 거인의 어깨에 올라타 적절히 사용하는것이 좋다.</strong></p>
<ul>
<li>오픈소스</li>
<li>깃허브</li>
<li>오픈api </li>
</ul>
<p>디자인으로치면 중요한 BI 로고 정도만 직접디자인하고, 아이콘 등 중요하지않은건 이미 만들어진것을 가져다쓰자.
개발로치면 지도 등을 직접 만들생각하지말고(어차피못만들지만) 이미 구현되어있는것을 가져다쓰자.</p>
<h2 id="아이디어를-구상할땐">아이디어를 구상할땐</h2>
<p>기존 서비스와의 어떤 측면에서 경쟁이있는지를 생각하자 (이용자의 어떤 가려운 부분을 긁어줄 수 있는지에 집중하자.)</p>
<p>또한, 아이디어도구를 사용하면 아이디어를 조정하는데 도움이된다 (구름에서는 아이디어개발도구로 lean canvas 를 사용한다.)</p>
<h2 id="mvp-프로세스">MVP 프로세스</h2>
<ul>
<li>완벽함보다 <strong>속도중시</strong></li>
<li>폭넓은 관심보다는 <strong>하나의 집중</strong></li>
<li>과정과 절차보다는 <strong>분업과협업으로 속도를 우선</strong>으로</li>
<li>기능추가보다는 <strong>실용성</strong> </li>
</ul>
<h2 id="발표-팁">발표 팁</h2>
<ul>
<li>발표는 무조건 잘하는사람이 해라 (ㅋㅋㅋㅋ이거진짜공감)</li>
<li>순서: 가설 -&gt; 검증 -&gt; 구현 </li>
</ul>
<ol>
<li>가설 : (나-우리) 경험, 기성품의 한계, 새로운 접근/기능</li>
<li>검증 : 주요고객 페르소나, 선택과집중</li>
<li>구현 : 프로토타입, mvp</li>
</ol>
<ul>
<li>핵심에 집중 -&gt;** 와우포인트만들기**</li>
<li>해당 행사 목표와 주최/후원사 요구사항을 확인
: <strong>기능구현이 목적이아닌 모두가 공감할 수 있는것을 만들어야한다.</strong></li>
</ul>
<h2 id="🤟-강의-후기-및-느낀점">🤟 강의 후기 및 느낀점</h2>
<p>뻔하다고도 할 수 있지만, 어쩌면 <strong>중요한것을 놓치고 지금까지 개발한느낌</strong>이였다.
가장 마음에 와닿았던건 MVP범위 설정이다. 
사실 최근에 여러 사이드 프로젝트를 하면서 느낀것 중 하나가 MVP범위에 대한 사람들간의 다른점이였다.
나같은경우 조금 더 확장된 정도의 범위를 원했는데, 다른사람들은 엄청엄청 최소한의 범위만 원했다. 
그로인해 조율하는점이 힘들었었는데, 
이강의를 보고 느낀점은 내가 잘못생각을 했다는점이다.
MVP범위라 함은 정말정말 최소한의 핵심기능을 말하는것이였는데, 나는 지금까지 어느정도 서비스가 그려지는 범위를 생각했던것같아서 많은 반성이되었다.
앞으로는 MVP범위를 잘 생각해서 구현해야할것같다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next14에서 네이버지도 API 구현]]></title>
            <link>https://velog.io/@5o_hyun/Next14%EC%97%90%EC%84%9C-%EB%84%A4%EC%9D%B4%EB%B2%84%EC%A7%80%EB%8F%84-API-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@5o_hyun/Next14%EC%97%90%EC%84%9C-%EB%84%A4%EC%9D%B4%EB%B2%84%EC%A7%80%EB%8F%84-API-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Fri, 06 Dec 2024 17:47:15 GMT</pubDate>
            <description><![CDATA[<p>Next14버전으로 위치 기반 서비스를 개발하고있는도중 네이버크레딧이 나온다해서 네이버지도를 사용하기로 했습니다.
크레딧이 안나와도 대표계정 1개는 무료니 걱정하지마세요! ( 저도 일단 개인계정으로 개발중 )
나중에 까먹을까봐 적어놓는 일기라고 생각하면되고 저처럼 완전 네이버맵 처음써보는 사람들을 위한 구현방법입니다. </p>
<h2 id="1-네이버-인증키-발급">1. 네이버 인증키 발급</h2>
<p>네이버클라우드에 접속해 서비스 -&gt; Application Services -&gt; map -&gt; 이용신청 -&gt; 애플리케이션 등록</p>
<p><a href="https://www.ncloud.com/">https://www.ncloud.com/</a>
<img src="https://velog.velcdn.com/images/5o_hyun/post/743c97c9-45c4-4931-8adc-5ca75c3be9e4/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/1f1af73c-d5af-4ab6-8491-2bbcc0862243/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/192bbc24-8de2-4aee-954f-8e7b8b1c58a5/image.png" alt=""></p>
<p>애플리케이션 등록에서 이름과 map에체크, 서비스URL을 작성해줍니다. 
저는 로컬서버와 배포서버를 다 입력했습니다. </p>
<blockquote>
<p>(등록누르면 대표계정아니라고 유료로 진행된다고 뜨는데 내가 1시간동안 찾아본결과 일단 등록하고 &#39;대표계정확인&#39;으로 확인하면 대표계정이 자동으로 등록되어 무료로 되는거같다 
난 한번도 이용한적없는데 무료크레딧아니라길래 먼가했다ㅠㅠ )</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/b4960fdb-faba-4ab6-b75b-6de71ac394d7/image.png" alt=""></p>
<h2 id="2-프로젝트에-설정">2. 프로젝트에 설정</h2>
<p><code>npm add -D @types/navermaps</code> 설치</p>
<p>전역에서 네이버맵을 사용할수있도록 루트레이아웃에 다음과같은 스크립트를 추가해줍니다</p>
<pre><code class="language-tsx">import ThemeProviderWrapper from &#39;@/components/common/ThemeProviderWrapper&#39;;
import GlobalStyle from &#39;@/styles/global&#39;;
import Script from &#39;next/script&#39;; // 추가

export const metadata = {
  title: &#39;Next.js&#39;,
  description: &#39;Generated by Next.js&#39;,
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    &lt;html lang=&quot;ko&quot;&gt;
      // 추가
      &lt;Script
        strategy=&quot;afterInteractive&quot;
        src={`https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.NEXT_PUBLIC_MAP_KEY}`}
      &gt;&lt;/Script&gt;
      &lt;body&gt;
        &lt;ThemeProviderWrapper&gt;
          &lt;GlobalStyle /&gt;
          {children}
        &lt;/ThemeProviderWrapper&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre>
<h2 id="3-프로젝트에서-보여주기">3. 프로젝트에서 보여주기</h2>
<p>이제 설정이 끝났으니 페이지에서 보여주는걸 구현해보자.
아직 코드소스분석은 힘들긴 하지만 우선 긁어와서 한번 해봤더니 실행됬다!! ㅋㅋㅋㅋ</p>
<pre><code class="language-tsx">const PlacePage = () =&gt; {
  useEffect(() =&gt; {
    const initMap = () =&gt; {
      const mapOptions = {
        center: new naver.maps.LatLng(37.3595704, 127.105399),
        zoom: 10,
      };

      new naver.maps.Map(&#39;map&#39;, mapOptions);
    };

    if (window.naver &amp;&amp; window.naver.maps) {
      initMap();
    } else {
      const mapScript = document.createElement(&#39;script&#39;);
      mapScript.onload = () =&gt; initMap();
      mapScript.src = `https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=발급받은 클라이언트 아이디`;
      document.head.appendChild(mapScript);
    }
  }, []);

  return (
    &lt;Container&gt;
      &lt;div id=&quot;map&quot; style={{ width: &#39;100%&#39;, height: &#39;100vh&#39; }}&gt;&lt;/div&gt; 
    &lt;/Container&gt;
  );
};</code></pre>
<p>이제 잘 실행됬는지 살펴봤는데 잘됬다!
<img src="https://velog.velcdn.com/images/5o_hyun/post/e7688c5b-c0d4-48fd-8957-2b5acbe15be9/image.png" alt=""></p>
<h2 id="4-마커-표시">4. 마커 표시</h2>
<h3 id="마커-1개만-표시">마커 1개만 표시</h3>
<pre><code class="language-tsx">const initMap = () =&gt; {
      const mapOptions = {
        center: new naver.maps.LatLng(37.3595704, 127.105399),
        zoom: 10,
      };

  // 이 하단 부분 수정
      const map = new naver.maps.Map(&#39;map&#39;, mapOptions);
      //   new naver.maps.Map(&#39;map&#39;, mapOptions);
      new naver.maps.Marker({
        position: new naver.maps.LatLng(37.3595704, 127.105399),
        map: map,
        title: &#39;네이버본사&#39;,
      });
    };</code></pre>
<h3 id="마커-여러개-표시">마커 여러개 표시</h3>
<pre><code class="language-tsx">const initMap = () =&gt; {
      const mapOptions = {
        center: new naver.maps.LatLng(37.3595704, 127.105399),
        zoom: 10,
      };

  // datas를 map을 돌려서 찍으면 된다. 
      const map = new naver.maps.Map(&#39;map&#39;, mapOptions);
      //   new naver.maps.Map(&#39;map&#39;, mapOptions);
   datas.forEach(data =&gt; {
        new naver.maps.Marker({
          position: new naver.maps.LatLng(data.lat, data.lng),
          map: map,
          title: hospital.name,
        });
      });
    };</code></pre>
<h3 id="마커-커스텀">마커 커스텀</h3>
<pre><code class="language-tsx">new naver.maps.Marker({
        position: new naver.maps.LatLng(37.3595704, 127.105399),
        map: map,
        title: &#39;마커표시&#39;,
        icon: {
          url: &#39;/svgs/place/marker.svg&#39;, // SVG 파일 경로
          size: new naver.maps.Size(36, 39), // 마커 크기 설정
          origin: new naver.maps.Point(0, 0), // SVG의 시작점
          anchor: new naver.maps.Point(16, 16), // 기준점 설정
        },
      });</code></pre>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/e2de9bae-40de-4808-a811-9fa69f12d32e/image.png" alt=""></p>
<h2 id="5-현재-위치-조회">5. 현재 위치 조회</h2>
<p>사용자의 현재 위치를 조회하고 마커도 표시할 수 있다.
나는 현재 위치에 마커는 필요없어서 안했고, 대신 특정 버튼을 누르면 현위치로 가도록 구현했다! 
<img src="https://velog.velcdn.com/images/5o_hyun/post/8eeb8f36-0d75-437b-a55a-ee0fdf8bc8da/image.png" alt="">
다음은 처음 로드시 사용자의 현재 위치로 기본설정한 코드이다. (마커도 구현은했지만 표시는안함)</p>
<pre><code class="language-tsx"> useEffect(() =&gt; {
    const initMap = () =&gt; {
      const mapOptions = {
        center: new naver.maps.LatLng(37.3595704, 127.105399),
        zoom: 10,
      };

      const map = new naver.maps.Map(&#39;map&#39;, mapOptions);

      new naver.maps.Marker({
        position: new naver.maps.LatLng(37.3595704, 127.105399),
        map: map,
        title: &#39;aa&#39;,
      });

      // 사용자의 현재 위치 표시
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition((position) =&gt; {
          const currentLocation = new naver.maps.LatLng(
            position.coords.latitude,
            position.coords.longitude,
          );
          //   현위치마커표시
          //   new naver.maps.Marker({
          //     position: currentLocation,
          //     map: map,
          //     title: &#39;Your Location&#39;,
          //   });

          // 지도 첫 접속 시 사용자의 현 위치로 중심이 오도록 추가!
          map.setCenter(currentLocation);
        });
      }
    };</code></pre>
<p>또 특정 버튼을 누르면 현재 위치로 되돌아오게 구현을해봤다.
추가! 붙은 줄만 보면된다! </p>
<pre><code class="language-tsx">const PlacePage = () =&gt; {
  const mapRef = useRef&lt;naver.maps.Map | null&gt;(null);  // 맵객체를 저장할 ref 추가!*****

  useEffect(() =&gt; {
    const initMap = () =&gt; {
      const mapOptions = {
        center: new naver.maps.LatLng(37.3595704, 127.105399),
        zoom: 10,
      };

      const map = new naver.maps.Map(&#39;map&#39;, mapOptions);
      mapRef.current = map; // 현재 맵을 ref에 추가!*******

      new naver.maps.Marker({
        position: new naver.maps.LatLng(37.3595704, 127.105399),
        map: map,
        title: &#39;aa&#39;,
      });

      // 사용자의 현재 위치 표시
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition((position) =&gt; {
          const currentLocation = new naver.maps.LatLng(
            position.coords.latitude,
            position.coords.longitude,
          );
          //   현위치마커표시
          //   new naver.maps.Marker({
          //     position: currentLocation,
          //     map: map,
          //     title: &#39;Your Location&#39;,
          //   });

          // 지도 첫 접속 시 사용자의 현 위치로 중심이 오도록 추가!
          map.setCenter(currentLocation);
        });
      }
    };

    if (window.naver &amp;&amp; window.naver.maps) {
      initMap();
    } else {
      const mapScript = document.createElement(&#39;script&#39;);
      mapScript.onload = () =&gt; initMap();
      mapScript.src = `https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.REACT_APP_MAP_KEY}`;
      document.head.appendChild(mapScript);
    }
  }, []);

  // 클릭함수구현 추가!
  const onClickUserLocation = () =&gt; {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition((position) =&gt; {
        const currentLocation = new naver.maps.LatLng(
          position.coords.latitude,
          position.coords.longitude,
        );
        if (mapRef.current) {
          console.log(&#39;click&#39;);
          mapRef.current.setCenter(currentLocation); // 사용자의 현재 위치로 맵 이동****
        }
      });
    }
  };

  return (
    &lt;Container&gt;
      &lt;div id=&quot;map&quot; style={{ width: &#39;100%&#39;, height: &#39;100vh&#39; }}&gt;
        &lt;PlaceUserLocation onClick={onClickUserLocation} /&gt; // 해당버튼클릭하면 현위치로 돌아오게 추가!***
      &lt;/div&gt;
    &lt;/Container&gt;
  );
};


export default PlacePage;
</code></pre>
<p>이제 하단의 버튼을 누르면 현위치로 돌아간다!</p>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/bfb8fab7-4538-4c85-922a-a39393276a56/image.png" alt=""></p>
<hr>
<p>참고
<a href="https://velog.io/@osohyun0224/Next.js-14%EB%A1%9C-%EB%84%A4%EC%9D%B4%EB%B2%84-%EC%A7%80%EB%8F%84-API%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EC%A7%80%EB%8F%84-%EA%B8%B0%EB%8A%A5-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0">https://velog.io/@osohyun0224/Next.js-14%EB%A1%9C-%EB%84%A4%EC%9D%B4%EB%B2%84-%EC%A7%80%EB%8F%84-API%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EC%A7%80%EB%8F%84-%EA%B8%B0%EB%8A%A5-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[두더지 (Do Does Did) 회고록]]></title>
            <link>https://velog.io/@5o_hyun/%EB%91%90%EB%8D%94%EC%A7%80-Do-Does-Did-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@5o_hyun/%EB%91%90%EB%8D%94%EC%A7%80-Do-Does-Did-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Fri, 06 Dec 2024 14:20:33 GMT</pubDate>
            <description><![CDATA[<h2 id="✍-프로젝트-소개">✍ 프로젝트 소개</h2>
<blockquote>
<p>기간 : 2024.09~2024.10 
인원 : PM 1명, Design 1명, Front 2명, Backend 1명
주제 : 친구들과 함께하는 1일1다짐 두더지(Do Does Did)
프론트 스택 : react, typescript, tanstack-query, tailwind, velcel, prettier, craco
백엔드 스택 : nextjs, typescript, postgresql, prisma, aws, nginx
사이트 :  <a href="https://www.dodoesdid.site">https://www.dodoesdid.site</a>
깃허브 레포지토리 : <a href="https://github.com/dodoesdid-project">https://github.com/dodoesdid-project</a></p>
</blockquote>
<h2 id="🔥-성장한-경험">🔥 성장한 경험</h2>
<blockquote>
<p>*<em>❶ 로그인을 혼자 구현해봤다! *</em></p>
</blockquote>
<p>회사나 단체에있으면 보통 이미 만들어져있는 플젝에 유지보수를 하게되니 로그인이 가장 궁금했던 부분이였는데 이번엔 처음부터 끝까지 혼자 해냈다 
그냥 로그인이아니라 인증메일을 보내기, 메일에서 링크뒤에 토큰값을 붙여 비밀번호 재설정 하기 등 로그인에서 구현할 수 있는 모든 플로우를 경험해본것같아서 좋았다. 
또한 유효성검사를 진행하면서 react-hook-form을 알게되었는데, 이때까지 input에 대한 state값을 일일히 지정하고 onChange가 끝없이 늘어났었는데 이 라이브러리를 사용하니 정말 쉽게 유효성검사를 할 수 있어서 좋았다 추천! </p>
<blockquote>
<p>*<em>❶ 소셜로그인을 구현해봤다 *</em></p>
</blockquote>
<p>구글링했을때는 이것저것 프론트에서 처리하는 로직이 많이 떴었는데 나같은경우 백엔드분이 구현을 다 해주셔서 나는 링크만 갖다붙이는식으로 진행했다.
그래도 소셜로그인을 구현하려면 보안상 https로 해야하는데 로컬은 http이기때문에 https로컬세팅하는방법에 대해서도 공부하게되었고 왜 이렇게 세팅을 해야하는지도 알수있게되어서 좋았다.
아직잘모르지만,, 다음에는 앞단에서 뭔가 처리하는게 있는것같은데 구현해봐야겠다</p>
<blockquote>
<p>*<em>❶ 유저플로우를 이해하게 되었다! *</em></p>
</blockquote>
<p>로그인뿐만아니라 유저에 관한 모든 로직과 페이지를 내가 처리했다.
그래서인지 유저플로우는 먼나라 이야기인줄 알았는데 리프레쉬토큰과 엑세스토큰에 대해서도 알게되고 큰 틀에 있는 유저플로우를 이해하게 되었다! </p>
<h2 id="🩹-어려웠던-점과-극복한-방법">🩹 어려웠던 점과 극복한 방법</h2>
<blockquote>
<p>타인의 의지를 내가 어떻게 할수가 없는것</p>
</blockquote>
<p>저는 실력이 부족한것이 문제가 아니라, 본인이 미숙해도 밤을새면서 끝까지 하고자하는 열정이 중요하다고 생각하는 사람이라서 크게 실력에대해 신경은 안썼습니다.</p>
<p>일정이 빡빡한 편이다보니 개발이 늦어지는건 어쩔수없는일이기도하지만, API가 안나와서 백엔드개발자분들에게 빨리 달라고 했으나..... 힘들다힘들다 하시면서 계속 늦어졌습니다.</p>
<p><span style="color:gray">배포 4일전인데 저희 서비스의 대부분의 기능들이 제대로 동작을 하지않았고, 신규개발건도 남아있어서 새로운 백엔드개발자를 데려와서 신규개발건이라도 맡기면 어떻겠느냐 물어봤습니다.
아무래도 저희 백엔드팀도 이때까지 해온게있는데 갑자기 다른사람 데려온다하면 기분나쁠까봐 최대한 조심스럽게 물어봤는데,,, 갑자기 취업이 되셨다며 어차피 다음주부터는 잘 못할거같다고 다 맡기면 안되냐하시며</span></p>
<p>백엔드2명이였는데 2명모두 팀을 나갔습니다. 새로운 백엔드개발자분이 4일만에 새로 레포지토리를 파서 개발을 해주셨고, 다른 팀원분들도 다같이 고생하자 하면서 1분대기조로 피드백을 해주며 다행히 배포날짜에 서비스를 출시할 수 있었습니다.
탈주사건이 있었지만 끝까지 함께해준 정말 팀원들에게 고맙고, 마음이 잘 맞아 저희는 이 이후에도 따로 프로젝트를 같이 하고있습니다.</p>
<hr>
<p>결국 어떻게 극복하냐, 타인이 팀을 나간다고 하는 의지는 내가 어떻게 할 수 있는 방안이 아니기때문에 다른곳으로 시선을 분산시키는 방법을 썼습니다.</p>
<p>할수있다 할수있다 하며 <strong>팀원들의 의지가 처지지 않게 하는것</strong>.
개발기간이 얼마 남지않았을 때 팀원이 새로왔다면, 난 기획이니까 난 디자인이니까 난 프론트니까 라는 <strong>개인적인 생각보다는 팀 모두가 함께 사기를 북돋으며 좋은 분위기를 만들면 안될것도 되게할수있다</strong>는것.</p>
<p>우리가 다른팀에 비해 기능이 많아서 사실 4일만에 개발을 완료해서 버그까지 잡는건 어려운일이지만 해냈으니까! 안될것도됬다 라고 생각합니다
사실 탈주해서 화가 났다기보다는, 나중에 다른 사이드프로젝트를 해도 똑같은 상황이 있을 수 있으니 &quot;경험했다&quot; 하고 떠올리는 요즘입니다</p>
<h2 id="🥰-개선-및-추가할점">🥰 개선 및 추가할점</h2>
<h3 id="개인적으로-개선-하고-싶은-부분">개인적으로 개선 하고 싶은 부분</h3>
<p>아무래도 빠른 시간내에 개발하다보니 아쉬운 부분이 많은건 사실입니다.</p>
<ol>
<li>웹앱인데 웹에서 모바일사이즈로보여 작아서 아쉽다. 웹버전을 조금 더 크게해서 개선하기</li>
<li>앱출시시 윈도우를 사용하고있어 안드로이드만 배포할 수 있었는데, 맥으로 ios도 양쪽으로 배포하기</li>
<li>클린코드 작성이 되어있지않아 리팩토링을 해야하는데, 테스트코드를 작성하여 리팩토링을 진행하기</li>
</ol>
<h3 id="팀원들이-말한-추가기능개발-하고-싶은-부분">팀원들이 말한 추가기능개발 하고 싶은 부분</h3>
<ol>
<li>앱스토어에 배포하고싶다.</li>
<li>구글 애널리틱스로 유입자양을 보면서 유지보수하고싶다. =&gt; 해결 </li>
<li>웹버전 제작을 하고싶다.</li>
</ol>
<p>이외에도 팀원들이 원하는 기능들은 노션에 자세히 기술되어있습니다
생각보다 최종배포 이후에도 원하는 사항이 많아서 최대한 다 반영할 예정이고, 현재 최종 배포 한 후 지났지만 팀원들과 계속 디벨롭중입니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next14 폴더구조 FSD(Feature Sliced Design)]]></title>
            <link>https://velog.io/@5o_hyun/Next14-%ED%8F%B4%EB%8D%94%EA%B5%AC%EC%A1%B0-FSDFeature-Sliced-Design</link>
            <guid>https://velog.io/@5o_hyun/Next14-%ED%8F%B4%EB%8D%94%EA%B5%AC%EC%A1%B0-FSDFeature-Sliced-Design</guid>
            <pubDate>Mon, 18 Nov 2024 10:17:19 GMT</pubDate>
            <description><![CDATA[<h2 id="feature-sliced-design이란">Feature Sliced Design이란?</h2>
<p>프론트엔드 아키텍처 중 하나.
FSD는 앱을 <strong>기능단위로 분할</strong>하고, <strong>각 기능을 독립적으로 개발,테스트,유지보수 할 수 있도록</strong> 하는것이 목표이다. </p>
<ul>
<li>기능중심설계 : 기능단위로 구성</li>
<li>계층화 : 코드를 여러계층으로 구분하여관리 (통일성) </li>
<li>단방향의존성 : 상위계층은 하위계층에만 의존가능 (변경, 리팩토링시 안정성)</li>
<li>명시적공개인터페이스 : 각 모듈은 명확한 공개 API를 통해 상호작용</li>
<li>구성가능성 : 작은 단위의 기능을 조합하여 더 큰 기능을 만듬 </li>
</ul>
<p><img src="https://velog.velcdn.com/images/5o_hyun/post/832f17bb-6ace-4ce8-a991-738f38f37b74/image.jfif" alt="">
FSD 는 3가지의 개념이있다. <code>layer</code> <code>slice</code> <code>segment</code> </p>
<h3 id="layer">Layer</h3>
<p>레이어는 각각의 특정 역할을 맡고있으며 구조를 더 명확히 만든다. </p>
<ul>
<li><p><code>app</code> : app로직 초기화. provider, router, 전역스타일 등이 정의 
ex : src/app/index.tsx, src/app/store.ts, src/app/routes.ts</p>
</li>
<li><p><del>_<code>processes</code> : 현재사용하지않음 _</del></p>
</li>
<li><p><code>pages</code> : 라우팅 가능한 화면 정의
ex : src/pages/HomePage.tsx, src/pages/ProfilePage.tsx</p>
</li>
<li><p><code>widgets</code> : 재사용 가능한 UI컴포넌트
ex : src/widgets/Header, src/widgets/Sidebar, src/widgets/ProductList</p>
</li>
<li><p><code>features</code> (optional) : 비즈니스 로직관련 사용자 시나리오와 기능
ex: src/features/auth/LoginForm, src/features/cart/AddToCartButton</p>
</li>
<li><p><code>entities</code> (optional) : 비즈니스 엔티티관련 사용자 리뷰, 댓글등이 포함 </p>
</li>
<li><p><code>shared</code> : 비즈니스 로직과 무관한 재사용 가능한 코드 
ex: src/shared/ui/Button, src/shared/lib/api, src/shared/config</p>
</li>
</ul>
<h2 id="slice">Slice</h2>
<p>코드를 목적에 맞게 그룹화 하는것이 목적 (layer 아래 두번째 그룹핑하는게 목적) </p>
<pre><code>.
└── src/
    ├── app/
    │   ├── provders/
    │   ├── styles
    │   └── index.tsx
    ├── pages/
    │   ├── home/
    │   ├── profile/
    │   └── about/
    ├── widgets/
    │   ├── newsfeed/
    │   ├── catalog/
    │   ├── header/
    │   └── footer/
    ├── features/
    │   ├── user/
    │   ├── auth/
    │   ├── favorites/
    │   └── filter-users/
    ├── entities/
    │   ├── user/
    │   └── session/
    └── shared/
        ├── ui/
        ├── utils/
        └── ...</code></pre><h2 id="segments">Segments</h2>
<p>slices안의 모듈들을 기술적 목적에 따라 분리</p>
<p><code>api</code>: 서버 요청과 관련된 파일
<code>ui</code>: 슬라이스의 UI 컴포넌트 파일
<code>model</code>: 비즈니스 로직 및 상태의 인터랙션 파일
<code>lib</code>: 슬라이스 내에서 사용되는 보조 기능 파일
<code>config</code>: 슬라이스에서 필요한 설정 파일
<code>consts</code>: 슬라이스 내에서 필요한 상수 파일</p>
<h2 id="사용-예시-구조">사용 예시 구조</h2>
<p>  분석해보니 기능별로 분리하는거같아요 !!! </p>
<pre><code class="language-jsx">src - app 
    - features -feature1- index.ts
                         - api
                        - components
                        - composables
                        - stores
                        - types ...

                -feature2- index.ts
                        - api 
                        - components
                        - composables
                        - stores
                        - types ...
      - pages // 벨로그에서 pages 없음 
      - components -component1 -index.tsx // path : 단일의존성
                               -component.module.css // ui 
                               -component.tsx // structure 
                               -component.test.tsx //test
      - lib
      - stores
      - test
      - types
      - styles
</code></pre>
<p>벨로그에서 <code>pages</code>가 없는 이유는 <code>app</code>폴더에서 파일의 형태로 관리되기때문에 폴더구조가 따로 필요없다라고 판단한듯</p>
<p><code>feature</code> 에서 <code>index.ts</code>  가 존재하는이유는,
<code>feature</code> 폴더안의 components,hooks,store…들은 다른 <code>feature</code>안에 공유되어선안된다.
다만 꼭 필요하다면 진입점인 <code>index.ts</code> 파일을 통해서만 이루어져야한다.</p>
<pre><code class="language-tsx">  import { UserProfile} from &#39;@/features/profile/components/UserProfile.tsx&#39;; x 
import { UserProfile} from &#39;@/features/UserProfile&#39;; o </code></pre>
<hr>
<p> 레퍼런스
 <a href="https://velog.io/@koreanthuglife/%EA%B7%B8%EB%8C%80-Next.js-14-%ED%8F%B4%EB%8D%94-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%A0%EA%B2%83%EC%9D%B8%EA%B0%80-feat.-medium">https://velog.io/@koreanthuglife/%EA%B7%B8%EB%8C%80-Next.js-14-%ED%8F%B4%EB%8D%94-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%A0%EA%B2%83%EC%9D%B8%EA%B0%80-feat.-medium</a></p>
]]></description>
        </item>
    </channel>
</rss>