<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>modori@</title>
    <link>https://berrymotto.tistory.com/</link>
    <description>웹, 인터넷, 프론트엔드에 대해 배운 것을 나누는 개발 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Mon, 10 Aug 2026 12:08:59 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>modori@</managingEditor>
    <image>
      <title>modori@</title>
      <url>https://tistory1.daumcdn.net/tistory/6328703/attach/feb4abafffe84d818bbdbe14ccf30672</url>
      <link>https://berrymotto.tistory.com</link>
    </image>
    <item>
      <title>만다라트로 갓생 살아보기</title>
      <link>https://berrymotto.tistory.com/10</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;열심히 직장생활을 하다보니 벌써 2026년의 50%나 지나갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한게 머가 있다고 이렇게 시간이 흐른지 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매일 똑같이 직장 -&amp;gt; 집 -&amp;gt; 직장 -&amp;gt; 집 으로만 흘러가니 흘러가는대로 살고있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소랑 똑같이 릴스를 넘기다가 우연히 보게된게 오타니의 만다라트 계획표였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;593&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxZMA/dJMcahSJTQc/elDk2eMYlBXmKrKSrcx6E0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxZMA/dJMcahSJTQc/elDk2eMYlBXmKrKSrcx6E0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxZMA/dJMcahSJTQc/elDk2eMYlBXmKrKSrcx6E0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkxZMA%2FdJMcahSJTQc%2FelDk2eMYlBXmKrKSrcx6E0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;593&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;593&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;만다라트 계획표는 주요목표를 중심으로 8개의 세부목표와 해당 세부목표의 실행방법을 브레인스토밍처럼 확산해나가는 계획표다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남은 5개월을 알차게 보내기위해 만다라트 계획표를 작성하는 김에 사이드 프로젝트로 만들어보기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 기획&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 생각하고있는 기능은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 나만의 만다라트 만들기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 공유하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 엔비디아에서 무료로 ai api를 배포중이기 때문에 해당 ai를 사용해서 세부목표를 작성해주는걸 도와줄려고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나같은 의지력 없는 사람들은 계획도 구체화 하기 힘들어한다.&lt;br /&gt;ai를 통해서 계획 작성을 도와준다면 여러가지 내가 생각하지 못한 부분들을 ai가 생각해주며 더욱 도움이 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;엔비디아 AI API 연결하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략 반년전에 엔비디아에서 ai api를 무료배포했는데, 돈이없는 나는 엔비디아의 ai의 힘을 빌려볼려고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 엔비디아 NIM 에서 api키를 먼저 발급받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.nvidia.com 회원가입 후 아무 모델 페이지나 들어가면 우측에 &lt;b&gt;Get API Key&lt;/b&gt; 버튼이 있다. nvapi- 로 시작하는 키를 던져준다. 계정 단위 공통 키라 한 번만 받으면 다른 모델도 그대로 쓸 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-08-04 오후 8.44.05.png&quot; data-origin-width=&quot;1902&quot; data-origin-height=&quot;909&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mISD8/dJMcabrCHqo/piawsZHfDSYQn6147ETTL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mISD8/dJMcabrCHqo/piawsZHfDSYQn6147ETTL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mISD8/dJMcabrCHqo/piawsZHfDSYQn6147ETTL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmISD8%2FdJMcabrCHqo%2FpiawsZHfDSYQn6147ETTL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1902&quot; height=&quot;909&quot; data-filename=&quot;스크린샷 2026-08-04 오후 8.44.05.png&quot; data-origin-width=&quot;1902&quot; data-origin-height=&quot;909&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 제작하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트는 클로드 코드로 개발을 진행할거다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-08-04 오후 10.28.00.png&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CXH11/dJMcadiJn2B/n2DuW2MsVQfGT1aCHgMCPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CXH11/dJMcadiJn2B/n2DuW2MsVQfGT1aCHgMCPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CXH11/dJMcadiJn2B/n2DuW2MsVQfGT1aCHgMCPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCXH11%2FdJMcadiJn2B%2Fn2DuW2MsVQfGT1aCHgMCPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;779&quot; height=&quot;683&quot; data-filename=&quot;스크린샷 2026-08-04 오후 10.28.00.png&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 인터뷰 스킬을 통해서 사용 기능을 구체화를 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체화한 기획서는 가독성을 위해 html로 추출하게 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-08-04 오후 10.29.47.png&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rQyU0/dJMcajwrYQe/vT6YG7kjjH433KpAPzyesk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rQyU0/dJMcajwrYQe/vT6YG7kjjH433KpAPzyesk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rQyU0/dJMcajwrYQe/vT6YG7kjjH433KpAPzyesk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrQyU0%2FdJMcajwrYQe%2FvT6YG7kjjH433KpAPzyesk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1022&quot; height=&quot;750&quot; data-filename=&quot;스크린샷 2026-08-04 오후 10.29.47.png&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;해당 기획서: &lt;a href=&quot;https://claude.ai/code/artifact/e286c11d-85ba-459b-a2ad-f6d0f91b4906&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;&amp;nbsp;https://claude.ai/code/artifact/e286c11d-85ba-459b-a2ad-f6d0f91b4906&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1785850209885&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;만다라트 플래너 &amp;mdash; 설계 명세&quot; data-og-description=&quot;인터뷰로 합의한 만다라트 플래너 MVP의 흐름&amp;middot;데이터 구조&amp;middot;규칙 정리&quot; data-og-host=&quot;claude.ai&quot; data-og-source-url=&quot;https://claude.ai/code/artifact/e286c11d-85ba-459b-a2ad-f6d0f91b4906&quot; data-og-url=&quot;https://claude.ai/code/artifact/e286c11d-85ba-459b-a2ad-f6d0f91b4906&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cdJZ8J/dJMb9eT4jjV/RYNpoeAPBKYpcURCqqE7s1/img.png?width=1138&amp;amp;height=640&amp;amp;face=0_0_1138_640,https://scrap.kakaocdn.net/dn/EqzeM/dJMb9iIVEBI/2KyQ4k1lK77KBCiKopLKXk/img.png?width=1138&amp;amp;height=640&amp;amp;face=0_0_1138_640&quot;&gt;&lt;a href=&quot;https://claude.ai/code/artifact/e286c11d-85ba-459b-a2ad-f6d0f91b4906&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://claude.ai/code/artifact/e286c11d-85ba-459b-a2ad-f6d0f91b4906&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cdJZ8J/dJMb9eT4jjV/RYNpoeAPBKYpcURCqqE7s1/img.png?width=1138&amp;amp;height=640&amp;amp;face=0_0_1138_640,https://scrap.kakaocdn.net/dn/EqzeM/dJMb9iIVEBI/2KyQ4k1lK77KBCiKopLKXk/img.png?width=1138&amp;amp;height=640&amp;amp;face=0_0_1138_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;만다라트 플래너 &amp;mdash; 설계 명세&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인터뷰로 합의한 만다라트 플래너 MVP의 흐름&amp;middot;데이터 구조&amp;middot;규칙 정리&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;claude.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 해당 기획을 토대로 제작을 진행해준다.&lt;br /&gt;만들 최소기능과 사용기술이 정해졌으니 agents에게 제작을 지시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 qa도 Claude in Chrome 연결을 통해서 사용검증을 진행했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-08-04 오후 10.34.36.png&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MXL1A/dJMcahelGci/IncnopRSze5kyMDmOr32sK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MXL1A/dJMcahelGci/IncnopRSze5kyMDmOr32sK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MXL1A/dJMcahelGci/IncnopRSze5kyMDmOr32sK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMXL1A%2FdJMcahelGci%2FIncnopRSze5kyMDmOr32sK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;748&quot; height=&quot;418&quot; data-filename=&quot;스크린샷 2026-08-04 오후 10.34.36.png&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://claude.ai/chrome/installed&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://claude.ai/chrome/installed&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 직접적으로 크롬에서 커서를 통해 조작하니 명확한 사용자 검증이 가능했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ai 반환 형태 지정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완성된 프로젝트를 확인중 에러가 발생했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-08-04 오후 10.57.17.png&quot; data-origin-width=&quot;415&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JjQyM/dJMcahelGKo/jaqfjuvVkY1Z3kzQhnszK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JjQyM/dJMcahelGKo/jaqfjuvVkY1Z3kzQhnszK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JjQyM/dJMcahelGKo/jaqfjuvVkY1Z3kzQhnszK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJjQyM%2FdJMcahelGKo%2FjaqfjuvVkY1Z3kzQhnszK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;415&quot; height=&quot;159&quot; data-filename=&quot;스크린샷 2026-08-04 오후 10.57.17.png&quot; data-origin-width=&quot;415&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NVIDIA API 자체 문제인지 확인하기위해서 서버 로그를 확인해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 앱과 동일한 프롬프트로는 29초 만에 응답이 왔다. 그런데 응답 내용을 확인해보니 JSON이 끝까지 닫히지 않은 상태였다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&quot;tasks&quot;:[&quot;피드백수용&quot;, &quot;피드백분석&quot;, ...]&lt;br /&gt;}]&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NVIDIA NIM이 OpenAI와 동일하게 response_format: {&quot;type&quot;: &quot;json_object&quot;} 옵션(강제 JSON 모드)을 지원하는 걸 확인하고 요청에 추가했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 추가로 &amp;nbsp;무료티어 요청은 응답시간이 불규칙해 명확한 에러로 끝나도록 타임아웃을 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;완성&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-08-04 오후 10.55.03.png&quot; data-origin-width=&quot;1201&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vOc8a/dJMcajwrZtM/0JrGpSQH3ITXIFSbZdpxr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vOc8a/dJMcajwrZtM/0JrGpSQH3ITXIFSbZdpxr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vOc8a/dJMcajwrZtM/0JrGpSQH3ITXIFSbZdpxr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvOc8a%2FdJMcajwrZtM%2F0JrGpSQH3ITXIFSbZdpxr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1201&quot; height=&quot;921&quot; data-filename=&quot;스크린샷 2026-08-04 오후 10.55.03.png&quot; data-origin-width=&quot;1201&quot; data-origin-height=&quot;921&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-08-04 오후 10.54.49.png&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;883&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvjXIN/dJMcaaM2CDj/aUV7Tj2TxbM77SR4ngjcSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvjXIN/dJMcaaM2CDj/aUV7Tj2TxbM77SR4ngjcSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvjXIN/dJMcaaM2CDj/aUV7Tj2TxbM77SR4ngjcSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvjXIN%2FdJMcaaM2CDj%2FaUV7Tj2TxbM77SR4ngjcSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1196&quot; height=&quot;883&quot; data-filename=&quot;스크린샷 2026-08-04 오후 10.54.49.png&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;883&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 완성했다!멋지다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 만다라트 플래너로 남은 5개월도 야무지게 살아가길 바란다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mandalart-planner-chi.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mandalart-planner-chi.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1785852819860&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;만다라트 플래너&quot; data-og-description=&quot;&quot; data-og-host=&quot;mandalart-planner-chi.vercel.app&quot; data-og-source-url=&quot;https://mandalart-planner-chi.vercel.app/&quot; data-og-url=&quot;https://mandalart-planner-chi.vercel.app/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://mandalart-planner-chi.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mandalart-planner-chi.vercel.app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;만다라트 플래너&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mandalart-planner-chi.vercel.app&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI</category>
      <category>IT</category>
      <category>만다라트</category>
      <category>만다라트 계획서</category>
      <category>클로드</category>
      <author>modori@</author>
      <guid isPermaLink="true">https://berrymotto.tistory.com/10</guid>
      <comments>https://berrymotto.tistory.com/10#entry10comment</comments>
      <pubDate>Tue, 4 Aug 2026 23:15:14 +0900</pubDate>
    </item>
    <item>
      <title>zustand 로 새로고침해도 안변하는 데이터관리하기</title>
      <link>https://berrymotto.tistory.com/9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;새로고침을 하면 상태가 초기화되는 건 당연한 일입니다. React 앱에서 전역 상태를 zustand로 관리하더라도, 브라우저를 새로고침하면 메모리에 있던 값은 사라지니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 실제로 서비스를 운영하다보면 &amp;ldquo;새로고침해도 유지돼야 하는 데이터&amp;rdquo;가 꽤 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 &lt;b&gt;Zustand로 새로고침해도 안 변하는 데이터를 관리하는 방법&lt;/b&gt;을 사용 방법과 작동원리에 대해서 알아보려고 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) 왜 새로고침하면 상태가 날아갈까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zustand 스토어는 기본적으로 &lt;b&gt;메모리&lt;/b&gt;에 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 새로고침(= JS 런타임 재시작)이 일어나면, 스토어는 다시 생성되고 초기값으로 돌아갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결하려면 &amp;ldquo;어딘가에 저장&amp;rdquo;이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 보편적으로 사용하게 되는게 local storage죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 유동적으로 변하는 데이터를 저장하며 사용하게되면 생각보다 고려할 게 많아집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 탭에서 값이 엇갈리는 &lt;b&gt;동기화 문제&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;저장/복원 타이밍이 어긋나서 &lt;b&gt;이전 값으로 덮어씌워지는 문제&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;JSON.stringify/parse 과정에서 생기는 &lt;b&gt;타입 손실/파싱 에러 처리&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;getItem/setItem 반복으로 인한 호출부&amp;nbsp;&lt;b&gt;보일러플레이트 증가&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 이유로 &amp;ldquo;저장/복원&amp;rdquo;을 직접 구현하기보다는, Zustand에서 제공하는 미들웨어로 이 책임을 위임하는 편이 훨씬 깔끔합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) persist란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;persist는 Zustand 스토어의 상태를 &lt;b&gt;외부 스토리지에 자동으로 저장하고 불러올 수 있게&amp;nbsp;&lt;/b&gt;해주는 미들웨어입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태 변경 시 &amp;rarr; 스토리지에 저장&lt;/li&gt;
&lt;li&gt;앱 시작 시 &amp;rarr; 스토리지에서 읽어와 복원&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) 기본 사용법 (sessionStorage 예시)&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

export const useBearStore = create(
  persist(
    (set, get) =&amp;gt; ({
      bears: 0,
      addABear: () =&amp;gt; set({ bears: get().bears + 1 }),
    }),
    {
      name: 'food-storage',
      storage: createJSONStorage(() =&amp;gt; sessionStorage),
    }
  )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 흐름은 다음처럼 이해하면 편합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스토어 생성 시점에 persist가 set을 감쌈&lt;/li&gt;
&lt;li&gt;이후 set이 호출되면:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 상태 업데이트&lt;/li&gt;
&lt;li&gt;스토리지에 직렬화(JSON)해서 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;앱 시작 시점에는:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스토리지에서 값을 읽고&lt;/li&gt;
&lt;li&gt;스토어 상태를 덮어써서 복원(hydration)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4) createJSONStorage는 왜 필요할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createJSONStorage는 스토리지(localStorage/sessionStorage 등)를 persist가 기대하는 형태로 바꿔주는 &lt;b&gt;어댑터&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) JSON 직렬화/역직렬화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태는 객체이므로 문자열로 바꿔서 저장해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const createJSONStorage = (getStorage, options?) =&amp;gt; {
  return {
    getItem: (name) =&amp;gt; {
      const value = getStorage().getItem(name)
      return JSON.parse(value, options?.reviver)
    },
    setItem: (name, value) =&amp;gt; {
      const str = JSON.stringify(value, options?.replacer)
      getStorage().setItem(name, str)
    },
    removeItem: (name) =&amp;gt; {
      getStorage().removeItem(name)
    },
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) SSR에서 안전하게 접근하기 (함수로 감싸는 이유)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR은 서버(Node.js)에서 실행되기 때문에 &lt;b&gt;브라우저 전용 API&lt;/b&gt;를 사용할 수 없습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// ❌ SSR에서 즉시 에러, 해당 storage 접근 불가
storage: createJSONStorage(localStorage)

// ✅ 실제 접근 시점까지 지연
storage: createJSONStorage(() =&amp;gt; localStorage)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5) hydration 타이밍 이슈 (깜빡임/조건 분기 버그)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;persist는 기본적으로 아래와 같이 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;스토어 생성 (기본값으로 렌더) -&amp;gt; 스토리지에서 값을 읽음 -&amp;gt; 읽은 값으로 상태를 덮어씀&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 기본값에서 동기화 되는 과정에 아래 현상들이 발생합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인 상태가 잠깐 풀린 것처럼 보임 (user: null &amp;rarr; hydration 후 user)&lt;/li&gt;
&lt;li&gt;UI 깜빡임&lt;/li&gt;
&lt;li&gt;라우팅/초기 fetch가 잘못된 상태 기준으로 실행됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결: hasHydrated 플래그로 &amp;ldquo;복원 완료 후&amp;rdquo; 렌더/로직 실행&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

type Store = {
  hasHydrated: boolean
  user: { id: string } | null
  setHasHydrated: (v: boolean) =&amp;gt; void
}

export const useStore = create&amp;lt;Store&amp;gt;()(
  persist(
    (set) =&amp;gt; ({
      hasHydrated: false,
      user: null,
      setHasHydrated: (v) =&amp;gt; set({ hasHydrated: v }),
    }),
    {
      name: 'app-storage',
      storage: createJSONStorage(() =&amp;gt; sessionStorage),
      onRehydrateStorage: () =&amp;gt; (state) =&amp;gt; {
        if (state) state.setHasHydrated(true)
      },
    }
  )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트에서는:&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const hasHydrated = useStore((s) =&amp;gt; s.hasHydrated)
if (!hasHydrated) return null // 또는 스켈레톤/로딩
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6) partialize: &amp;ldquo;전부 저장하지 말고, 일부만 저장&amp;rdquo;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;persist는 기본적으로 스토어 전체를 저장하려고 합니다. 하지만 실무에서는 &amp;ldquo;저장하면 안 되는 값&amp;rdquo;이 섞이기 쉽습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토큰/민감정보(보안)&lt;/li&gt;
&lt;li&gt;대용량 캐시(성능/용량)&lt;/li&gt;
&lt;li&gt;서버에서 다시 받아야 하는 값(데이터 품질)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 partialize로 &lt;b&gt;저장할 필드만 선택&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

type AuthState = {
  user: { id: string; nickname: string } | null
  accessToken: string | null
  refreshToken: string | null
  tempCache: Record&amp;lt;string, unknown&amp;gt;
  setUser: (user: AuthState['user']) =&amp;gt; void
  setTokens: (a: string | null, r: string | null) =&amp;gt; void
  clear: () =&amp;gt; void
}

export const useAuthStore = create&amp;lt;AuthState&amp;gt;()(
  persist(
    (set) =&amp;gt; ({
      user: null,
      accessToken: null,
      refreshToken: null,
      tempCache: {},
      setUser: (user) =&amp;gt; set({ user }),
      setTokens: (accessToken, refreshToken) =&amp;gt; set({ accessToken, refreshToken }),
      clear: () =&amp;gt; set({ user: null, accessToken: null, refreshToken: null, tempCache: {} }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() =&amp;gt; sessionStorage),
      partialize: (state) =&amp;gt; ({
        user: state.user,
        // accessToken/refreshToken/tempCache는 저장하지 않음
      }),
    }
  )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7) 버전/마이그레이션: 구조가 바뀌었을 때 기존 데이터 살리기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 구조가 바뀌면 기존에 저장된 값이 그대로 hydrate되면서 버그가 생길 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 version + migrate로 &amp;ldquo;옛 데이터 &amp;rarr; 새 데이터&amp;rdquo; 변환을 해줍니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

type StateV2 = {
  profile: { id: string; nickname: string } | null
  theme: 'light' | 'dark'
}

type Actions = {
  setTheme: (t: StateV2['theme']) =&amp;gt; void
}

export const useSettingsStore = create&amp;lt;StateV2 &amp;amp; Actions&amp;gt;()(
  persist(
    (set) =&amp;gt; ({
      profile: null,
      theme: 'light',
      setTheme: (theme) =&amp;gt; set({ theme }),
    }),
    {
      name: 'settings-storage',
      storage: createJSONStorage(() =&amp;gt; localStorage),
      version: 2,
      migrate: (persistedState: any, version) =&amp;gt; {
        // v1: { user, theme } -&amp;gt; v2: { profile, theme }
        if (version === 1) {
          return {
            profile: persistedState?.user ?? null,
            theme: persistedState?.theme ?? 'light',
          }
        }

        return {
          profile: persistedState?.profile ?? null,
          theme: persistedState?.theme ?? 'light',
        }
      },
    }
  )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zustand에서 새로고침해도 유지되는 데이터를 만들려면 결국 &amp;ldquo;저장 + 불러오기&amp;rdquo;가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;zustand middleware를 통해 좀 더 간결하게 관리할 수 있게 되면 좋겠습니다.&lt;/p&gt;</description>
      <author>modori@</author>
      <guid isPermaLink="true">https://berrymotto.tistory.com/9</guid>
      <comments>https://berrymotto.tistory.com/9#entry9comment</comments>
      <pubDate>Wed, 8 Apr 2026 21:18:19 +0900</pubDate>
    </item>
    <item>
      <title>Tauri로 앱 만들기 - 개발환경 세팅</title>
      <link>https://berrymotto.tistory.com/8</link>
      <description>&lt;h1&gt;오늘은 Tauri 프로젝트 개발 환경을 처음부터 세팅해보려고 합니다&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tauri란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tauri.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tauri.app/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774267072544&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Tauri 2.0&quot; data-og-description=&quot;The cross-platform app building toolkit&quot; data-og-host=&quot;v2.tauri.app&quot; data-og-source-url=&quot;https://tauri.app/&quot; data-og-url=&quot;https://v2.tauri.app/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cJE3He/dJMb8RRRjuq/hC0LROVyKSVRrbY7lZpPZ0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b0AzHF/dJMb8Wey4nN/1WsQsESE3RqelDEpEValE0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://tauri.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tauri.app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cJE3He/dJMb8RRRjuq/hC0LROVyKSVRrbY7lZpPZ0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b0AzHF/dJMb8Wey4nN/1WsQsESE3RqelDEpEValE0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Tauri 2.0&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The cross-platform app building toolkit&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;v2.tauri.app&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tauri는 Electron과 같이 크로스 플렛폼 앱을 만들기 위해 개발된 프레임워크입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Electron과 다른 점은 Electron은 웹을 그대로 데스크톱 앱으로 옮기기 쉬운 쪽에 가깝고 Tauri는 더 작고 가벼운 앱을 만들기 좋은 쪽에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 빠른 개발과 익숙한 Node.js 생태계가 중요하면 Electron이 유리하고, 배포 용량이나 메모리 효율이 중요하면 Tauri가 더 적합합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 66.5117%; height: 102px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 13.8009%; height: 17px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 29.4255%; height: 17px;&quot;&gt;Electron&lt;/td&gt;
&lt;td style=&quot;width: 26.9565%; height: 17px;&quot;&gt;Tauri&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 13.8009%; height: 17px;&quot;&gt;뷰&lt;/td&gt;
&lt;td style=&quot;width: 29.4255%; height: 17px;&quot;&gt;Chromium(독자 엔진)사용&lt;/td&gt;
&lt;td style=&quot;width: 26.9565%; height: 17px;&quot;&gt;시스템에 설치되어있는 웹뷰 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 13.8009%; height: 17px;&quot;&gt;번들 사이즈&lt;/td&gt;
&lt;td style=&quot;width: 29.4255%; height: 17px;&quot;&gt;약 350MB&lt;/td&gt;
&lt;td style=&quot;width: 26.9565%; height: 17px;&quot;&gt;약 30MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 13.8009%; height: 17px;&quot;&gt;백엔드 환경&lt;/td&gt;
&lt;td style=&quot;width: 29.4255%; height: 17px;&quot;&gt;Node.js&lt;/td&gt;
&lt;td style=&quot;width: 26.9565%; height: 17px;&quot;&gt;Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 13.8009%; height: 17px;&quot;&gt;개발 경험&lt;/td&gt;
&lt;td style=&quot;width: 29.4255%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9893 0.003 48.72); color: oklch(0.2642 0.013 93.9); text-align: start;&quot;&gt;생태계가 크고 JavaScript 친화적&lt;br /&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 26.9565%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9893 0.003 48.72); color: oklch(0.2642 0.013 93.9); text-align: start;&quot;&gt;경량성과 보안에 강점&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 &lt;b&gt;새 Tauri 프로젝트를 만들고 개발 서버를&amp;nbsp;띄우는 것&lt;/b&gt;까지 다루겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Node.js부터 깔아보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트 템플릿을 돌리거나 &lt;code&gt;create-tauri-app&lt;/code&gt; 스크립트를 실행할 때 &lt;b&gt;Node.js&lt;/b&gt;가 필요합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://nodejs.org/&quot;&gt;Node.js 공식 사이트&lt;/a&gt;에서 &lt;b&gt;LTS&lt;/b&gt;를 받아 설치합니다.&lt;/li&gt;
&lt;li&gt;설치가 끝났으면 터미널을 &lt;b&gt;새로 연 다음&lt;/b&gt; 아래로 확인합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;node -v
npm -v&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전 숫자가 찍히면 여기까지는 성공입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Rust 다운로드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tauri의 네이티브 쪽은 Rust로 빌드되기 때문에, &lt;b&gt;rustup&lt;/b&gt;으로 툴체인을 깔아줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 터미널을 열고 다음 명령을 사용해 설치를 진행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774268621092&quot; class=&quot;vim&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 역시 터미널을 &lt;b&gt;새로 연 뒤&lt;/b&gt; 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;rustc -V
cargo -V&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;새 프로젝트 만들기 &amp;mdash; create-tauri-app&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하는 패키지 매니저에 맞게 프로젝트를 만드시면&amp;nbsp;됩니다.&lt;/p&gt;
&lt;table style=&quot;height: 74px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;th style=&quot;height: 20px;&quot;&gt;환경&lt;/th&gt;
&lt;th style=&quot;height: 20px;&quot;&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;npm&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;code&gt;npm create tauri-app@latest&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;pnpm&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;code&gt;pnpm create tauri-app&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;Yarn&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;code&gt;yarn create tauri-app&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-23 오후 9.30.55.png&quot; data-origin-width=&quot;1576&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvbiKe/dJMcadnN63U/iwD3FXRJ9u6qhtDXniM211/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvbiKe/dJMcadnN63U/iwD3FXRJ9u6qhtDXniM211/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvbiKe/dJMcadnN63U/iwD3FXRJ9u6qhtDXniM211/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvbiKe%2FdJMcadnN63U%2FiwD3FXRJ9u6qhtDXniM211%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1576&quot; height=&quot;622&quot; data-filename=&quot;스크린샷 2026-03-23 오후 9.30.55.png&quot; data-origin-width=&quot;1576&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어를 실행하면&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774269505191&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;프로젝트 이름 -&amp;gt; 고유식별자(pakageName) -&amp;gt; 프론트 개발 언어 -&amp;gt; 패키지 매니저 -&amp;gt; 프로젝트 기본 프레임워크 -&amp;gt; 내부 사용 언어&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순으로 프로젝트를 세팅하게&amp;nbsp; 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;Tauri 프로젝트 구조&lt;/h2&gt;
&lt;pre id=&quot;code_1774270477285&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;├── package.json
├── index.html
├── src/        ## 프론트엔드 프로젝트
│   ├── main.js 	## 초기 진입점
├── src-tauri/  ## 백엔드 프로젝트
│   ├── Cargo.toml
│   ├── Cargo.lock
│   ├── build.rs
│   ├── tauri.conf.json   ## Tauri의 기본 설정 파일
│   ├── src/
│   │   ├── main.rs
│   │   └── lib.rs
│   ├── icons/
│   │   ├── icon.png
│   │   ├── icon.icns
│   │   └── icon.ico
│   └── capabilities/
│       └── default.json&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 구조도는 처음 프로젝트를 생성했을 때 생기는 기본 템플릿입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src는 프론트엔드 프로젝트, src-tauri는 rust 프로젝트가 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발 서버 띄우기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 폴더로 들어가서 의존성을 깔고, Tauri 개발 모드를 켭니다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;cd tauri-init-app
pnpm install
pnpm tauri dev&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데스크톱 창이 뜨면 성공입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;처음&lt;/b&gt; &lt;code&gt;tauri dev&lt;/code&gt;나 &lt;code&gt;tauri build&lt;/code&gt;를 돌릴 때는 Rust 크레이트를 받아 오느라 &lt;b&gt;시간이 꽤 걸릴 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배포용으로 한 번 빌드해보기&lt;/h2&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run tauri build&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pnpm이면 &lt;code&gt;pnpm tauri build&lt;/code&gt;처럼, 사용하시는 패키지 매니저에 맞춰 주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과물은&amp;nbsp; &lt;code&gt;src-tauri/target/release/bundle&lt;/code&gt; 에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-23 오후 9.41.55.png&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6F6Nm/dJMcagSpPne/vyAYGsf1pI8bqU2nDZFp21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6F6Nm/dJMcagSpPne/vyAYGsf1pI8bqU2nDZFp21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6F6Nm/dJMcagSpPne/vyAYGsf1pI8bqU2nDZFp21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6F6Nm%2FdJMcagSpPne%2FvyAYGsf1pI8bqU2nDZFp21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;531&quot; height=&quot;71&quot; data-filename=&quot;스크린샷 2026-03-23 오후 9.41.55.png&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>modori@</author>
      <guid isPermaLink="true">https://berrymotto.tistory.com/8</guid>
      <comments>https://berrymotto.tistory.com/8#entry8comment</comments>
      <pubDate>Mon, 23 Mar 2026 22:01:10 +0900</pubDate>
    </item>
    <item>
      <title>node.js 로 서버 만들기</title>
      <link>https://berrymotto.tistory.com/7</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;node먼저 알아보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js는 크로스 플랫폼 오픈소스 자바스크립트 런타임 환경으로, 구글 크롬의 V8 자바스크립트 엔진을 기반으로 만들어졌습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를통해서 웹 브라우저 내에서만 작동하던 자바스크립트를&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt; 브라우저 밖, 즉 서버나 다양한 환경에서 실행할 수 있게 되었습니다.&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;V8 엔진은 자바스크립트 코드를 매우 빠른 기계어 코드로 컴파일하여 실행하며, 인터프리터를 사용하지 않고 JIT(Just-In-Time) 컴파일 방식을 통해 최적화된 성능을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;오늘은 이 node.js로 서버를 구성하기전 단계인 프로젝트 세팅을 진행해볼 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;JIT: 프로그램을 실행할 때 필요한 부분을 즉석에서 컴파일하는 방식&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 프로젝트를 생성해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;Node.js 프로젝트를 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;package.json&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일을 먼저 생성하도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762708048061&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# bash
# 프로젝트 디렉토리 생성
mkdir node-server

# 프로젝트 디렉터리 이동
cd node-server

# npm 초기화 (package.json 생성)
# -y는 빠른 생성 ( 대화형 질문 스킵 )
npm init -y&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 생성된 해당 pakage.json파일을 확인하면 잘 나온 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762708218863&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// pakage.json
{
  &quot;name&quot;: &quot;server&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;amp;&amp;amp; exit 1&quot;
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;description&quot;: &quot;&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라이브러리 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 node.js 로 서버를 구축할 때, 사용하는 라이브러리를 설치해줄겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;express&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fffbff; color: #22191b; text-align: left;&quot;&gt;Node.js가 JavaScript를 서버에서 실행할 수 있게 해주는 런타임 환경이라면, Express.js는 그 위에서 웹 서버를 쉽게 구축할 수 있게 해주는 프레임워크입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100.116%; height: 51px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 46.3652%; height: 17px;&quot;&gt;http&lt;/td&gt;
&lt;td style=&quot;width: 132.82%; height: 17px;&quot;&gt;express&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 46.3652%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 132.82%;&quot;&gt;간결하고 직관적인 코드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 46.3652%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;서버를 설정하고 라우팅을 직접 구현해야 함&lt;br /&gt;&lt;/span&gt;요쳥별 분기처리 증가시 복잡도 향상&lt;/td&gt;
&lt;td style=&quot;width: 132.82%; height: 17px;&quot;&gt;라우팅기능 내장&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 46.3652%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;미들웨어 기능이 없으며, 필요한 기능을 직접 구현&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 132.82%; height: 17px;&quot;&gt;&lt;span style=&quot;background-color: #fffbff; color: #22191b; text-align: left;&quot;&gt;요청 처리 과정에 여러 기능을 쉽게 추가할 수 있는 미들웨어 시스템을 제공&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 46.3652%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 132.82%;&quot;&gt;&lt;span style=&quot;background-color: #fffbff; color: #22191b; text-align: left;&quot;&gt;동적 HTML 페이지 생성을 위한 다양한 템플릿 엔진을 지원&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;dotenv&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;dotenv는 환경변수를 .env파일에 저장하고 process.env로 로드하는 라이브러리입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;dotenv를 사용하는 이유는 개발 도중 사용하게되는 서버주소, 고유 API KEY등 오픈소스에 노출되면 보안적인 위험이 생기는 값들을 환경변수 파일로 따로 관리하기 위해서입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;body-parser&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;Express에서 HTTP 요청의 본문(body)을 쉽게 파싱할 수 있게 도와주는 미들웨어입니다. JSON, URL-encoded 형식 등 다양한 포맷의 요청 데이터를 처리해, 서버에서 클라이언트가 보낸 데이터를 편리하게 사용할 수 있게 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;DB 세팅&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;모든 라이브러리를 정상적으로 다운로드 했다면 이제 데이터를 실제로 저장하기위해 DB를 세팅해야합니다.&lt;br /&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;저는 mac 환경이기 때문에&lt;/span&gt;&lt;/span&gt;&amp;nbsp;brew로 mysql를 다운해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762784087333&quot; class=&quot;mipsasm&quot; style=&quot;background-color: #f6f7f8; color: #555555; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt; brew install mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;span&gt;dbeaver를 다운로드해줍니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dbeaver란 &lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;&lt;span style=&quot;color: oklch(0.3039 0.04 213.68);&quot;&gt;&lt;span&gt;데이터베이스 관리 및 개발 도구&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;로, 다양한 데이터베이스를 한 곳에서 편리하게 관리할 수 있도록 도와주는 소프트웨어입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관적인 GUI로 누구나 쉽게 &lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;SQL 쿼리 작성, 실행, 데이터 편집, ERD 생성 등 다양한 작업을 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;오픈소스로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;무료&lt;/span&gt;이기 때문에 사용하기도 편합니다.&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;&lt;a href=&quot;https://dbeaver.io/download/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dbeaver.io/download/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1762785440921&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Download | DBeaver Community&quot; data-og-description=&quot;Download DBeaver Community 25.2.4 Released on November 2nd 2025 (Milestones). It is free and open source (license). Also you can get it from the GitHub mirror. System requirements. DBeaver PRO 25.2 Released on September 8th, 2025 PRO version website: dbeav&quot; data-og-host=&quot;dbeaver.io&quot; data-og-source-url=&quot;https://dbeaver.io/download/&quot; data-og-url=&quot;https://dbeaver.io/download/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b6h4H5/hyZNrC5nNt/8bLiRMnusKwyrg7UNKCqR0/img.png?width=1083&amp;amp;height=222&amp;amp;face=0_0_1083_222&quot;&gt;&lt;a href=&quot;https://dbeaver.io/download/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dbeaver.io/download/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b6h4H5/hyZNrC5nNt/8bLiRMnusKwyrg7UNKCqR0/img.png?width=1083&amp;amp;height=222&amp;amp;face=0_0_1083_222');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Download | DBeaver Community&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Download DBeaver Community 25.2.4 Released on November 2nd 2025 (Milestones). It is free and open source (license). Also you can get it from the GitHub mirror. System requirements. DBeaver PRO 25.2 Released on September 8th, 2025 PRO version website: dbeav&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dbeaver.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;mysql 실행하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 모든 설정을 마쳤다면 아래 명령어로 아까 설치한 mysql의 상태를 확인해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762785481056&quot; class=&quot;bash&quot; style=&quot;background-color: #f6f7f8; color: #555555; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;brew services list

// Homebrew로 설치한 백그라운드 서비스들의 상태를 보여주는 명령어&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행되고있지 않는다면 mysql을 실행시켜줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762785577036&quot; class=&quot;armasm&quot; style=&quot;background-color: #f6f7f8; color: #555555; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt; brew services start mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-10 오후 11.39.56.png&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lGxyV/dJMcajN6ldv/bZ9lnTbHqHVkdU8oMbmgF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lGxyV/dJMcajN6ldv/bZ9lnTbHqHVkdU8oMbmgF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lGxyV/dJMcajN6ldv/bZ9lnTbHqHVkdU8oMbmgF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlGxyV%2FdJMcajN6ldv%2FbZ9lnTbHqHVkdU8oMbmgF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1214&quot; height=&quot;48&quot; data-filename=&quot;스크린샷 2025-11-10 오후 11.39.56.png&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 정상적으로 실행되고있는 모습을 보실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;초기 비밀번호 세팅&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 mysql의 초기 보안 세팅을 진행해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1762785738345&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql_secure_installation&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1762785992242&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
Enter current password for root (enter for none):  ## 처음 설정하는 경우라면 ENTER 로 넘어간다

...
Change the root password? [Y/n] y  ## root 계정의 패스워드 교체유무
New password: 
Re-enter new password:

...
Remove anonymous users? [Y/n] y  ## anonymous 계정 삭제유무

...
Disallow root login remotely? [Y/n] n ## 외부에서 root 계정의 로그인 허용유무
 
...
Remove test database and access to it? [Y/n] y  ## test 데이터베이스 삭제 유무

...
Reload privilege tables now? [Y/n] y  ## privileges 테이블 재시작 유무&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 보안 설정을 마쳤다면 mysql을 root권한으로 접근해줍니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;mysql -u root -p&lt;br /&gt;// -u 는 user name을 root로, -p는 명령어 실행 후, 비밀번호를 입력하겠다는 내용&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터베이스 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 데이터를 저장할 새 데이터베이스를 만들어야겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 이름으로 새로운 데이터베이스를 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;mysql&amp;gt; create database &lt;span style=&quot;background-color: #f3c000;&quot;&gt;원하는이름&lt;/span&gt;;&lt;br /&gt;// 데이터베이스 생성&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성까지 마친 후 데이터베이스가 정상적으로 생성되었는지 확인해줍니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;mysql&amp;gt; show databases&lt;br /&gt;// 모든 데이터베이스 조회&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-10 오후 11.52.03.png&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0Lei4/dJMcagw4zTO/kYxleFHFyX43DPfKau31wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0Lei4/dJMcagw4zTO/kYxleFHFyX43DPfKau31wK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0Lei4/dJMcagw4zTO/kYxleFHFyX43DPfKau31wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0Lei4%2FdJMcagw4zTO%2FkYxleFHFyX43DPfKau31wK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;88&quot; data-filename=&quot;스크린샷 2025-11-10 오후 11.52.03.png&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 성공적으로 생성된 걸 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>node.js</category>
      <author>modori@</author>
      <guid isPermaLink="true">https://berrymotto.tistory.com/7</guid>
      <comments>https://berrymotto.tistory.com/7#entry7comment</comments>
      <pubDate>Tue, 11 Nov 2025 00:15:51 +0900</pubDate>
    </item>
    <item>
      <title>[ios] 하이브리드앱 자동로그인 구현하기.</title>
      <link>https://berrymotto.tistory.com/6</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 하이브리드 앱에 자동로그인을 구현해볼려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;하이브리드 앱은 웹 기술을 기반으로 네이티브 앱의 기능을 함께 활용하는 구조이기 때문에, 자동로그인 기능을 구현할 때도 네이티브와 웹뷰(WebView) 사이의 인증 상태 동기화가 핵심입니다.&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹의 인증 상태를 네이티브에 저장하기위해 &lt;span style=&quot;color: #555555; text-align: left;&quot;&gt;userdefault에 토큰을 저장, 조회하는 함수를 만들어줍니다.&lt;/span&gt;&lt;span style=&quot;color: #555555; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;UserDefaults 는 IOS 앱에서 데이터를 영구적으로 저장하고 불러오는 데 사용하는 간단한 로컬 저장소입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1760958327936&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserDataUtils {
    class func setLoginToken(_ token: String) {
        UserDefaults.standard.set(token, forKey: &quot;loginToken&quot;)
    }

    class func getLoginToken() -&amp;gt; String {
        return UserDefaults.standard.string(forKey: &quot;loginToken&quot;) ?? &quot;&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 페이지에서 token을 인증하게되면&lt;span style=&quot;background-color: #f6e199;&quot;&gt; 앱 실행&amp;nbsp; -&amp;gt; 로그인페이지 -&amp;gt; 웹뷰&lt;/span&gt; 로 이동해 사용자에게 &quot;자동로그인이 되긴 한건가?&quot; 라는 인식을 심어줄 수 있기 때문에 splash screen에서 token 인증 후, 인증에 성공하면 token set 후 웹뷰로 이동하는 로직으로 구현하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;splash screen controller&lt;/p&gt;
&lt;pre id=&quot;code_1760960764447&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
        let savedToken = UserDataUtils.getLoginToken()
        if !savedToken.isEmpty {
            self.tokenLogin()
        } else {
            self.redirectLoginPage()
        }
    }
}

private func tokenLogin() {
    let token = UserDataUtils.getLoginToken()
    AuthAPI.validateToken(token) { isLogin, userInfo in
        DispatchQueue.main.async {
            if isLogin {
                self.redirectMainPage(with: token)
            } else {
                self.redirectLoginPage()
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.6898 0.027 109.55 / 0.16); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;viewWillAppear&lt;/span&gt;는 해당 뷰(view)가 화면에 완전히 나타난 후에 호출되는 생명주기 메서드중 하나입니다.&lt;br /&gt;해당 메서드를 통해 splash iamge를 먼저 화면에 띄운 후, &lt;span style=&quot;background-color: oklch(0.6898 0.027 109.55 / 0.16); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;tokenLogin()&lt;/span&gt;함수를 통해 해당 token이 유효한지를 검사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이브리드 앱이기 때문에 &lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: oklch(0.3039 0.04 213.68);&quot;&gt;&lt;span style=&quot;background-color: oklch(0.6898 0.027 109.55 / 0.16);&quot;&gt;redirectMainPage()&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 통해서 받은 token을 웹에서 사용하기 위해 저희가 사용할 webview의 cookies에 set해주어야합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1760961232398&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func redirectMainPage(with token: String) {
    guard let webVC = UIStoryboard(name: &quot;Main&quot;, bundle: nil)
        .instantiateViewController(withIdentifier: &quot;WebViewController&quot;) as? WebViewController else { return }
    
    webVC.token = token
    webVC.url_Str = targetURL(&quot;https://example.com&quot;)

    navigationController?.pushViewController(webVC, animated: true)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webView controller&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 웹뷰에 진입했을 때, 해당 토큰을 사용해야겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;WKWebView는 기본적으로 쿠키를 공유하지 않기 때문에, 토큰을 웹뷰 내 쿠키로 설정해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;WKHTTPCookieStore를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1761227811722&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override func viewDidLoad() {
    super.viewDidLoad()
    setupCookieAndLoad()
}

private func setupCookieAndLoad() {
    guard let token = token,
          let url = URL(string: url_Str ?? &quot;&quot;) else { return }

    let cookie = HTTPCookie(properties: [
        .domain: url.host ?? &quot;&quot;,
        .path: &quot;/&quot;,
        .name: &quot;loginToken&quot;,
        .value: token,
        .secure: &quot;TRUE&quot;,
        .expires: NSDate(timeIntervalSinceNow: 31536000)
    ])
    
    if let cookie = cookie {
        WKWebsiteDataStore.default().httpCookieStore.setCookie(cookie) { [weak self] in
            self?.webView.load(URLRequest(url: url))
        }
    } else {
        webView.load(URLRequest(url: url))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 과정을 통해 네이티브에서 인증된 토큰이 웹뷰 내의 쿠키로 동기화되어, 동일한 로그인 세션을 유지할 수 있습니다.&lt;br /&gt;즉, 사용자는 웹 로그인 페이지를 거치지 않고도 바로 서비스 페이지로 접근 가능해집니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;이후에는 웹뷰 내부에서 토큰 변경이나 만료 검증이 발생했을 때 다시 네이티브 측에 반영해야 합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;이를 위해 웹뷰 내에서 쿠키를 읽어와&lt;/span&gt;&lt;span style=&quot;color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;UserDefaults의 토큰과 비교하는 로직을 유지합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;이렇게 웹뷰 하이브리드앱의 자동로그인을 구현해보았습니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;회고&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;구현 후, 알게된 것이지만 &lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;UserDefaults는 i&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;OS 내에서 간편하게 데이터를&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;저장하고 불러오는 대신&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;, 데이터를&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;평문으로 저장하므로 OS&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;탈취나 탈옥 환경에서는&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;민감한 정보가 쉽게 노&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;출될 위험이 크다고합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;따라서 로그인 토큰과 같은&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;중요한 인증 정보 저장&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;에 사용하기에는 적합하지&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;않음을 알게 되었습니다&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;.&lt;br /&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;키체인은 iOS가 제공하는 암호화된 보안 저장소로, AES-256 암호화와 디바이스 잠금 상태와 연동해 강력한 보호 기능을 제공하기때문에&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;물리적 탈취 및 기기 비밀번호 탈취 시에도 어느 정도 보안성을 지킬 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: left;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklch(0.9902 0.004 106.47); color: oklch(0.3039 0.04 213.68); text-align: start;&quot;&gt;이후 리랙토링을 통해 키체인으로 변경을 진행할 예정입니다.!&lt;/span&gt;&lt;/p&gt;</description>
      <author>modori@</author>
      <guid isPermaLink="true">https://berrymotto.tistory.com/6</guid>
      <comments>https://berrymotto.tistory.com/6#entry6comment</comments>
      <pubDate>Thu, 23 Oct 2025 23:44:03 +0900</pubDate>
    </item>
    <item>
      <title>리팩토링에 대해서(Refactoring)</title>
      <link>https://berrymotto.tistory.com/5</link>
      <description>&lt;p&gt;오랜만에 서비스중인 프로젝트의 코드를 보고있었는데, 정말 코드가 심각하게 더러워서 리팩토링을 진행하게 되었다.&lt;br&gt;분명 예전에 보았을 때는 괜찮다고 생각했는데, 오랜만에 코드를 확인해서 그런지 예전엔 생각하지 못했던 부분들이 보였다.&lt;br&gt;이번 글에서는 개발자들에게 필수적으로 필요한 리팩토링에 대해서 이야기해볼려한다.&lt;/p&gt;
&lt;h1&gt;refactoring&lt;/h1&gt;
&lt;p&gt;오늘 이야기할려는 리팩토링 (refactoring) 이라는 단어는 영어사전에는 존재하지않는 단어다.&lt;br&gt;그럼 언제부터 이 단어가 생기게 되었을까?&lt;br&gt;이 refactoring 이라는 단어는 사실 언제 생겨났는지 명확하지 않다. 하지만 널리 퍼지기 시작한 계기중 하나를 이 사람이라고 이야기 할 수 있다.&lt;br&gt;1990년 마틴 파울러라는 사람이 Refactoring: Improving the Design of Existing Code라는 책을 통해 이 개념에 대해 널리 알리기 시작했다.&lt;br&gt;이 마틴 아저씨가 이야기하는 리팩토링의 정의를 한 단어로 이야기하자면..&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;사용자 관점에서 변하는게 없지만, 내부적으로 함수의 성능이나 구조가 변하는 것&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;..라고 할 수 있다.&lt;br&gt;겉보기론 똑같은 동작을 하지만, 더 이해하기 쉬워지고, 더 수정하기 간편해지며 더욱 좋은 성능을 내게 만드는 것이라고 할 수 있다.&lt;br&gt;물론 여기서 중요한 것은 &lt;strong&gt;&lt;em&gt;사용자 관점에서 보았을 때 이전과 같은 동작을 해야한다는 것&lt;/em&gt;&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h2&gt;리팩토링을 해야하는 이유&lt;/h2&gt;
&lt;p&gt;리팩토링을 하지 않게 되면 소프트웨어의 아키텍쳐가 무너져가기 쉽다. 이 내부 설계가 무너지게 된다면 코드를 통해 이 내부 설계를 파악하기가 어려워진다.&lt;br&gt;코드만으로 설계를 파악하기가 어려워질수록 이 설계를 유지하기가 어려워지고, 점차 녹이들게 된다.&lt;/p&gt;
&lt;h2&gt;코드의 의도를 잘 생각하자&lt;/h2&gt;
&lt;p&gt;리팩토링을 이야기할 때, 성능을 향상시키기 위해 코드를 수정하는 것에 중점을 맞추는 경우가 많다.&lt;br&gt;하지만 그건 잘못된 방향성이라고 생각한다. 코드 자체의 성능에 포커스를 맞추게 되면 길고 이해하기 힘든 구조의 코드가 발생하는 경우가 부지기수다.&lt;br&gt;코딩이란 컴퓨터에게 명령을 내리는 것 만이 중요한 것이 아니다.&lt;br&gt;내가 작성한 코드를 컴퓨터만 보는 것이 아니라, 이후에 다른 개발자가 수정을 위해 읽게 될지도 모른다.&lt;br&gt;잘 작동하지만 좋지않은 구조를 가지고있는 코드는 다른 개발자가 수정을 위해 코드를 살펴볼 때, 1시간이면 해결할 수 있는 문제를 2일, 5일, 일주일.. 이나 걸리게 할 수 있다.&lt;br&gt;이렇게 프로그램의 동작에 대해서만 집중하게 된다면 나중에 이 코드를 살펴볼 다른 개발자를 배려하지 못하게 된다는 데 있다.&lt;br&gt;프로그램의 동작에 집중하기보다는 코드에 목적이 잘 드러나게, 내 의도가 잘 전달 뒬 수 있는 코드를 작성하는 것이 중요하다.&lt;/p&gt;
&lt;h2&gt;지구력 가설&lt;/h2&gt;
&lt;p&gt;리팩토링을 통해 코드의 품질을 높일 수 있다.&lt;br&gt;하지만 그러면 시간이 너무 오래걸려 전체 개발 속도가 느려지는 것이 아닌가?&lt;/p&gt;
&lt;p&gt;한 시스템을 오랫동안 개발한 개발자들을 보면 초기에는 빠르게 개발했지만 나중엔 기능 하나를 추가하는 데 훨씬 오래걸린다고 이야기한다. 새로운 기능을 하나 추가할 때 마다 기존 코드에 녹여내는 시간이 늘어나고, 기능을 추가할 때 버그가 발생하는 일이 잦아지기도 한다.&lt;br&gt;또 심한 경우에는 차라리 처음부터 다시 개발하는게 더 낫겠다고 생각하는 일까지 생기는 경우도 있다.&lt;/p&gt;
&lt;p&gt;그래서 코드의 품질이 중요하다.&lt;br&gt;좋은 설계를 통해 코드를 작성하게 된다면 현재 코드베이스를 이용해 더 빠르게 신규 기능을 추가할 수 있고, 또한 명확한 코드를 통해 버그를 쉽게 디버깅할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/aronia/post/b7995cc9-6c96-497b-9147-0c68cf634c47/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;리팩토링에 대해&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;리팩토링은 단순히 코드의 성능을 개선하는 것이 아니라, 코드의 가독성과 유지보수성을 높여주는 핵심 작업이라고 할 수 있다.&lt;br&gt;코드는 더 명확해지고, 수정이 쉬워지며, 성능이 개선된다.&lt;br&gt;결국 리팩토링은 사용자를 위한 수정이 아니라, 사용자와 개발자 모두에게 더 나은 경험을 제공해줄 수 있는 작업이다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;</description>
      <author>modori@</author>
      <guid isPermaLink="true">https://berrymotto.tistory.com/5</guid>
      <comments>https://berrymotto.tistory.com/5#entry5comment</comments>
      <pubDate>Wed, 21 Aug 2024 16:19:36 +0900</pubDate>
    </item>
    <item>
      <title>Context is not a State Manager ❌</title>
      <link>https://berrymotto.tistory.com/4</link>
      <description>&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;이 글은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://blog.isquaredsoftware.com/2021/01/context-redux-differences/&quot;&gt;Why React Context is Not a &quot;State Management&quot; Tool&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 읽고 정리한 글 입니다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;소개&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모두 상태관리 라이브러리를 사용해 보셨나요?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모두 프로젝트를 생성할 때 Context API와 Redux중에 어떤 상태관리 라이브러리를 선택해서 사용할지 고민했던 기억이 있으실겁니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;( 저도 예전에 상태관리 라이브러리를 선택하기위해 여러 라이브러리들 중에서 고민했던 기억이 있네요.  )&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L3rZ8/btsG4jqSWLa/BV3OYBa9d2AKWTY1TLbIqk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L3rZ8/btsG4jqSWLa/BV3OYBa9d2AKWTY1TLbIqk/img.jpg&quot; data-alt=&quot;Redux vs Context API&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L3rZ8/btsG4jqSWLa/BV3OYBa9d2AKWTY1TLbIqk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL3rZ8%2FbtsG4jqSWLa%2FBV3OYBa9d2AKWTY1TLbIqk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Redux vs Context API&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아실수도 있으시겠지만 Context vs Redux라는 전에 리엑트 커뮤니티에서 가장 많이 논의되어온 주제 중 하나입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 안타깝게도 이 논쟁은 도구의 목적과 사용 사례에 대한 혼동에서 생겨나게 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오늘 이 글을 통해 Context 와 Redux가 명확하게 어떻게 다른지 이야기해보도록 하겠습니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Context와 Redux&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 이 도구들의 원래 목적과 방향에 맞게 사용할려면 3가지를 이해하는 것이 중요합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;nbsp;이 도구의 목적이 무엇인지&lt;/li&gt;
&lt;li&gt;&amp;nbsp;어떤 문제를 해결할려고 하는지&lt;/li&gt;
&lt;li&gt;&amp;nbsp;만들어진 시기와 이유가 무엇인지&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한 현재 해결하고자 하는 문제가 무엇인지 정확히 파악한 후 이 문제를 가장 잘 풀어낼 것 같은 도구를 선택하면 됩니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;이 도구를 선택한 이유는 누군가 써보라고 해서, 또는 인기가 많다고 따라 쓰는 것이 아니라 이 상황에서 가장 적합한 도구를 찾아 사용하는 것이 중요합니다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Context?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;먼저 리엑트 공식문서를 살펴보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;컨텍스트를 사용하면 구성요소가 정보를 props으로 전달하지 않고 멀리있는 부모로부터 정보를 받을 수 있습니다.&lt;span style=&quot;color: #333333; background-color: #99cefa;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;값을&quot;&lt;b&gt;관리&lt;/b&gt;&quot;하는 것은 언급하지 않고 값을 &quot;&lt;b&gt;전달&lt;/b&gt;&quot; 하는 것만을 언급하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존에 레거시 컨텍스트 API는 주로 'sholdComponentUpdate' 함수를 사용해 성능 최적화을 진행했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 기존 방식은 심각한 문제가 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;부모 컴포넌트가 업데이트되었지만 자식 컴포넌트가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;shouldComponentUpdate&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;로 인해 업데이트를 건너 뛸 경우,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;레거시 컨텍스트 API를 통해 전달된 값의 업데이트가 차단되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: start;&quot;&gt;이후 React Context API는 React 16.3에서 새 버전으로 출시되고, 이 문제들이 해결되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;새로운 Context Api는 중간에 있는 구성 요소가 렌더링을 건너 뛰더라도 값의 업데이트를 하위 구성요소에 즉시 표현합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 렌더링을 건너뛰는 경우에도 Context를 통해 전달된 값의 업데이트가 문제 없이 적용됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;여기서 잠깐!&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;sholdComponentUpdate&lt;/span&gt;란?&lt;/b&gt;&lt;br /&gt;간단하게 말하면 바뀌어야 하는 부분만 다시 렌더링하도록 해주는 성능 최적화 Lifecycle API다.&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Redux?&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 redux를 살펴보도록 합시다. 아까와 마찬가지로 공식문서를 먼저 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&quot;Redux is a JS library for predictable and maintainable global state management.&quot;&lt;br /&gt;&amp;nbsp; Redux는 예측 가능하고 유지 관리 가능한 전역 상태 관리를 위한 JS 라이브러리입니다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;자, 확실하게 &quot;global state management&quot; 라고 명시하고 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Context가 상태관리가 아닌 이유&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;상태는 애플리케이션의 동작들을 설명하는 모든 데이터를 말합니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;상태는 서버상태, 커뮤니케이션 상태, 로케이션 상태로 분류할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가장 중요한 점은 이 데이터들이&amp;nbsp;저장되고(stored), 읽고(read), 업데이트되고(updated), 사용된다(used)는 것이죠.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런 의미에서 상태 관리는 아래 3가지로 이야기 할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 값 저장&lt;/li&gt;
&lt;li&gt;현재 값 읽기&lt;/li&gt;
&lt;li&gt;값 업데이트&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;좋은 상태관리의 예시로 React의 useStatedhk useReducer를 들 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;훅을 호출하며 초기값 저장&lt;/li&gt;
&lt;li&gt;훅을 호출하면서 현재 값 읽기&lt;/li&gt;
&lt;li&gt;setState나 disfetch 함수를 호출해 값 업데이트&lt;/li&gt;
&lt;li&gt;컴포넌트 리렌더링 시 값 업데이트&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 맥락에서 볼 수 있듯이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Redux또한 확실한 상태 관리 툴&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;root reducer를 호출하며 초기값을 저장하고, store.getState()로 현재 값을 읽고, store.dispatch(action)으로 값을 업에이트 할 수 있게 하며, store.subscribe (listener)를 통해 스토어의 업데이트를 알립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 React Context는 이 조건에 맞지 않으므로 상태관리 툴이 아닙니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Context는 그 자체로는 아무 것도 &quot;저장&quot;하지 못합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;lt;MyContext.povider&amp;gt;를 렌더하는 상위컴포넌트는 Context에 어떤 값을 넣어 줄지만 결정하는 것에만 관여합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실질적인 '상태관리'는 useState,usePeducer에서 훅으로 이루어지는 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결론적으로 컨텍스트는 이미 어딘가에 존재하고 있는 상태를 다른 커포넌트와 공유하는 방법일 뿐입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;context와-redux의-비교&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Context와 Redux의 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 context와 redux의 기능을 비교해 보면서 살펴보도록 합시다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Context&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 것도 저장하거나 &quot;관리&quot;하지 않는다.&lt;/li&gt;
&lt;li&gt;React Componenet 에서만 작동한다.&lt;/li&gt;
&lt;li&gt;타입과 관계 없는 단일 값들을 전달한다. (원시, 객체, 클래스 등등)&lt;/li&gt;
&lt;li&gt;단일 값을 읽을 수 있도록 한다.&lt;/li&gt;
&lt;li&gt;prop-drilling을 피하기 위해 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;Context가 전달하는 값이 업데이트 되면 Consumer컴포넌트도 자동으로 업데이트 되며, 스킵할 방법이 없다.&lt;/li&gt;
&lt;li&gt;사이드 이펙트를 처리하는 메커니즘이 전무하고, 오로지 렌더링에만 관여한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;React + Redux&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 값을 저장하고 관리한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;react component 가 아닌 다른 UI와 함께 사용할 수 있다. (&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;React, Vue, Angular, 바닐라 JS 등)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;단일 값을 읽을 수 있도록 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;prop-drilling을 피하기 위해 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;action을 dispatching하고 reducer를 작동시킴으로 값을 업데이트 한다.&lt;/li&gt;
&lt;li&gt;사이드 이펙트를 발생시키기 위한 미들웨어를 사용할 수 있다,&lt;/li&gt;
&lt;li&gt;컴포넌트가 store의 업데이트를 구독하여 해당값이 바뀌었을 때만 컴포넌트가 리렌더링 되도록 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보다시피 이 둘은 매우 다른 도구들 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 공통점을 찾으라면 prop-driling을 피하게 해준다는 것 정도겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Context와 useReducer&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 이야기했던 Context vs Redux 에서 가장 큰 문제점은, &quot;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;사람들이 &lt;b&gt;useReducer&lt;/b&gt;로 상태관리를 하고, &lt;b&gt;Context&lt;/b&gt;로 값을 전달해준다.&lt;/span&gt;&quot;라고 말하는 대신 &quot;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;나 Context&lt;/span&gt;&lt;/i&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt; &lt;/span&gt;&lt;i&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;사용했어.&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&quot; 라고 말하며 발생합니다. 이때문에 많은 사람들이 Context로 상태를 &quot;관리&quot; 한다는 이야기를 한다고 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 한번 Context + useReducer의 조합을 살펴봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조합은 Redux + React-Redux와 정말 상당한 부분을 닮아 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공통점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저장(보관)되는 값&lt;/li&gt;
&lt;li&gt;리듀서 함수&lt;/li&gt;
&lt;li&gt;액션 디스패칭&lt;/li&gt;
&lt;li&gt;중첩 컴포넌트 내에서 값을 전달하고 읽는 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;차이점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Context +&lt;span&gt;&amp;nbsp;&lt;/span&gt;useReducer는 리액트의 기능이므로 리액트에서만 사용할 수 있다. Redux 스토어는 모든 UI에 독립적이므로 별개로 사용 가능하다.&lt;/li&gt;
&lt;li&gt;리액트 DevTools로 현재 컨텍스트 값을 볼 수는 있지만, 시간 순으로 지난 기록을 볼 수는 없다.&lt;br /&gt;리덕스 DevTools로는 디스패치된 모든 액션, 모든 액션의 컨텐츠, 액션 실행 후의 상태, 시간에 따른 상태의 차이를 모두 볼 수 있다.&lt;/li&gt;
&lt;li&gt;useReducer에는 미들웨어가 없다.&lt;br /&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;useReducer와&lt;span&gt;&amp;nbsp;&lt;/span&gt;useEffect를 함께 사용하여 사이드 이펙트를 낼 수 있고, 미들웨어와 비슷한 대안을 사용하려는 시도도 있어왔으나 기능과 효과면에서 리덕스 미들웨어에 비해서는 한계를 지닌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 얼핏 보기에는 두 조합이 비슷해보이지만, 절대 똑같지 않습니다. (실제로 Redux를 대체할 수 없습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>modori@</author>
      <guid isPermaLink="true">https://berrymotto.tistory.com/4</guid>
      <comments>https://berrymotto.tistory.com/4#entry4comment</comments>
      <pubDate>Wed, 1 May 2024 14:10:33 +0900</pubDate>
    </item>
    <item>
      <title>[ JS ] 클로저에 대해 알아보자</title>
      <link>https://berrymotto.tistory.com/3</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 분야를 공부하다보면 &quot;클로저&quot;라는 단어를 들어보셨을 겁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 이 한번쯤은 들어본 closure에 대해 정리 해보겠습니다.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클로저(Closure)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript에서 클로저(Closure)는 이렇게 정의할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;함수와 그 함수가 선언될 당시의 렉시컬 환경(Lexical Environment)과의 조합&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 정의만 보아선 이해하기 어려울 수 있습니다.&lt;br /&gt;조금더 간단히 설명해 보도록 하겠습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부함수가 외부함수의 context에 접근할 수 있는 함수&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 이해하기 쉬우신가요?&lt;br /&gt;이 개념을 기억하면서 클로저에 대해 차근차근 알아보도록 합시다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 컨텍스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 클로저를 이해하기 전 함수의 실행 컨텍스트에 대해 간단히 알아야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수는 호출 될 때 함수의 실행 컨텍스트를 생성했다가, 실행이 끝나면 실행컨텍스트가 종료됩니다.&lt;br /&gt;( 이 실행 컨텍스트의 lexical environment엔 함수의 지역변수의 정보, 이 함수의 상위 스코프에 대한 정보가 들어있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 예제를 하나 봐보도록 합시다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const HelloWorld = () =&amp;gt; {
    const hello = &quot;Helloooo&quot;;
      return hello;
}

console.log(HelloWorld());
console.log(hello)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/aronia/post/9117565e-83e7-43f7-862d-42bb9db62ed0/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보이는대로 위의 함수 호출이 끝난후 실행 컨텍스트가 종료되며, 안의 &lt;code&gt;hello&lt;/code&gt;라는 변수에 아무도 접근할 수 없게 되었습니다.&lt;br /&gt;어떻게 보면 당연한 결과죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 &lt;code&gt;hello&lt;/code&gt;라는 변수에 접근할 수 없는 것은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 주제인 &lt;code&gt;closure&lt;/code&gt;를 사용한다면 실행 컨텍스트가 끝난 이후에도 외부에서 이 &lt;code&gt;hello&lt;/code&gt;라는 변수에 대해 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const HelloWorld = () =&amp;gt; {
    const hello = &quot;Helloooo&quot;;
    console.log(hello)
    const sayHello = () =&amp;gt; {
        return hello;
    }
    return sayHello;
}

const helloFunction = HelloWorld();
console.log(helloFunction()); 
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;HelloWorld&lt;/code&gt;가 마찬가지로 실행 후 종료되었습니다.&lt;br /&gt;마찬가지로 실행 컨텍스트가 종료되었으니&lt;code&gt;hello&lt;/code&gt;라는 변수에는 접근할 수가 없겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 &lt;code&gt;sayHello&lt;/code&gt;에서는 접근할 수가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/aronia/post/6e09b39b-c9ea-4884-842d-a7830bec4fdd/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 클로접니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로져의 특성상&lt;code&gt;sayHello&lt;/code&gt;함수가 선언될 때 그 주변의 lexical enviroment와 함께 번들로 묶였기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 왜 기존의 규칙을 바꾸면서까지 이 클로저라는 것을 만들었을까요?&lt;/p&gt;
&lt;h1&gt;유용한 클로저&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전역변수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역변수는 어디서든 접근이 가능하기 때문에 되도록이면 적은게 좋습니다.&lt;br /&gt;하지만 단 1곳에 사용하는데도 전역변수가 필요한 경우가 있는데요, 바로 이런 순간에 사용하는 것이 클로저 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 하나 살펴보도록 합시다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const btn = document.querySelector('button')

btn.addEventListener('click',handleClick)

let count = 0
function handleCilck(){
  count++
  return count
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제처럼 클릭할 때마다 count를 세주는 함수가 있습니다.&lt;br /&gt;count를 전역변수로 작성해 줘야지만 값을 추가하는게 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 클로저를 사용하면 전역변수가 아니더라도 counter를 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const btn = document.querySelector('button')

btn.addEventListener('click',handleClick())

function handleCilck(){
  let count = 0
  return function (){
    count++
    return count
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부함수(handleClick)의 lexical environment를 참조하는 함수를 btn의 콜백함수로 이용해 구현할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드 재사용성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저를 사용하게 되면 비슷한 형태의 코드를 재사용률을 높일 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const newTag = function(open, close) {
    return function(content) {
        return open + content + close
    }
}

const bold = newTag('&amp;lt;b&amp;gt;', '&amp;lt;/b&amp;gt;')
const italic = newTag('&amp;lt;i&amp;gt;', '&amp;lt;/i&amp;gt;')

console.log(bold(italic(&quot;This is my content!&quot;)))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 bold, italic같이 여러 태그를 새로운 태그를 만들 수 있는 newTag를 클로저를 이용해 간단하게 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자로 open, close, content를 한번에 다 받으면 코드의 가독성이 떨어질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 클로저를 통해 구현하면 코드의 가독성, 재사용성까지 챙긴 편한 코드가 됩니다.&lt;/p&gt;
&lt;h1&gt;착각하기 쉬운 클로저&lt;/h1&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function outer() {
  let name = 'kyle'
  if (true) {
    let city = 'seoul'
    return function inner() {
      console.log(city)
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드가 클로저를 사용한 코드일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저를 정확하게 파악하지 않았을 때, 마치 함수를 리턴하는 것 자체가 클로저라고 오해하는 경우가 행길 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 내부에 선언된 함수가 외부함수의 지역변수를 사용해 줬을 때만 클로저라고 선언됩니다.&lt;br /&gt;&lt;code&gt;inner&lt;/code&gt;함수에도 클로저를 사용하고 싶다면 &lt;code&gt;name&lt;/code&gt;변수를 사용해주면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function outer() {
  let name = 'kyle'
  if (true) {
    let city = 'seoul'
    return function inner() {
      console.log(city)
     console.log(name)
    };
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 클로저를 다시한번 정리해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저란 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;내부함수에서 외부함수의 지역변수를 사용할 때 외부함수의 lexcial environment와 함께 짝을 이루는 것&lt;/span&gt;이라고 생각하면 될 것 같습니다.&lt;/p&gt;</description>
      <author>modori@</author>
      <guid isPermaLink="true">https://berrymotto.tistory.com/3</guid>
      <comments>https://berrymotto.tistory.com/3#entry3comment</comments>
      <pubDate>Sat, 27 Apr 2024 15:05:23 +0900</pubDate>
    </item>
    <item>
      <title>SEO에 대해 알아보자</title>
      <link>https://berrymotto.tistory.com/2</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 프론트엔드 개발자 이아론입니다. 평소에 Next의 ssr을 통해 SEO를 향상시킨다~ 만 알고있다가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말로 Next의 ssr이 SEO에 엄청나게 큰 영향을 끼칠까? 라는 생각을 가지게 되어 SEO에 대해 알아보게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 한번 SEO에 대해 알아보도록 합시다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; SEO가 멀까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버와 구글같은 검색엔진의 상위에 컨텐츠를 노출시킬 수 있는 방법은 2가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 광고를 통해 컨텐츠를 상단에 위치시키는 것과 검색엔진에 친화적인 사이트를 구축하여 자연검색결과의 상단에 위치시키는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SEO란 Search Engine Optimization의 약자로 구글이나 네이버같은 검색엔진에 광고가 아닌 무료 검색 방식을 통해 상위에 노출될 수 있도록 최적화하는 것을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; 왜 SEO를 써야할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 검색엔진의 상위에 컨텐츠를 노출시킬 수 있는 방법은 2가지라고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연스럽게 이런생각이 들 수 있죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&quot;그럼 검색 광고를 통해 최상단에 위치시키면 되지 않나?&quot;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번 봅시다. 혹시 광고로 올라온 컨텐츠를 확인하나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 광고를 보게되면 거부감이 들게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 많은 돈을 써서 검색엔진의 상단에 페이지를 노출시켜도 광고인 이상 해당 컨텐츠의 신뢰는 한계가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;SEO&lt;/b&gt;를 하면 자연적으로 검색 순위가 높아져 &lt;b&gt;오가닉 트래픽&lt;/b&gt;이 급진적으로 늘어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;google-organic-traffic.webp&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P8ws2/btsGbB5WJha/AFlAVfKMgJkrx6iTZklKb1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P8ws2/btsGbB5WJha/AFlAVfKMgJkrx6iTZklKb1/img.webp&quot; data-alt=&quot;(출처: Backlinko)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P8ws2/btsGbB5WJha/AFlAVfKMgJkrx6iTZklKb1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP8ws2%2FbtsGbB5WJha%2FAFlAVfKMgJkrx6iTZklKb1%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;406&quot; data-filename=&quot;google-organic-traffic.webp&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(출처: Backlinko)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 자료처럼 3%이하론 클릭률이 현저히 떨어지고, 최상단에 노출된 컨텐츠가 대부분의 클릭률을 가지고 갑니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; SEO의 범위&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 SEO가 얼마나 중요한지 알았으니 SEO를 하는 방법을 알아야봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SEO는 크게 온페이지 SEO, 온서프 SEO, 마지막으로 오프페이지 SEO 3가지 작업으로 분류됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;what-is-seo-1024x380.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/701bL/btsF9G1wqTr/rFLCJuKSxNFbuTn2GsD6W0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/701bL/btsF9G1wqTr/rFLCJuKSxNFbuTn2GsD6W0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/701bL/btsF9G1wqTr/rFLCJuKSxNFbuTn2GsD6W0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F701bL%2FbtsF9G1wqTr%2FrFLCJuKSxNFbuTn2GsD6W0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;380&quot; data-filename=&quot;what-is-seo-1024x380.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;온페이지 SEO&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;온페이지 SEO는 웹사이트 내에서 하는 최적화 작업을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주기적으로 양질의 컨텐츠를 만드는것, 해당 컨텐츠를 검색엔진에게 잘 전달하는것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next의 SSR을 통한 SEO향상을 통해 페이지 내의 컨텐츠를 검색엔진이 더 잘 읽어내게 하는 부분을 예로 들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;온서프 SEO&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;온서프 SEO란 검색 결과 페이지에서의 최적화 작업을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;온서프 SEO의 목적은 차별화된 방식으로 더 노출되어 더 많은 클릭을 얻는 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 만개의 레시피 콘텐츠에 재료, 평점, 리뷰, 작성시간등이 나오는 것처럼 구조화된 데이터를 페이지에 삽입하여 검색결과에 노출되게 하는 작업이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1263&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4EmCX/btsF9I56SFv/B0kU2i2KJinNKVGqQe6X00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4EmCX/btsF9I56SFv/B0kU2i2KJinNKVGqQe6X00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4EmCX/btsF9I56SFv/B0kU2i2KJinNKVGqQe6X00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4EmCX%2FbtsF9I56SFv%2FB0kU2i2KJinNKVGqQe6X00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1263&quot; height=&quot;754&quot; data-origin-width=&quot;1263&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오프페이지 SEO&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오프페이지 SEO란 외부 웹사이트에서 이뤄지는 최적화 작업을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오프페이지SEO의 목표는 우리 컨텐츠와 비슷한 웹사이트에서 많은 백링크를 얻는 것 입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;백링크&lt;/b&gt;란, 한 웹사이트가 다른 웹사이트를 언급해 그 사이트로 연결해 주는 링크를 말합니다.&lt;br /&gt;한마디로 A사이트에서 B사이트로 가는 링크를 넣었다면 그 링크는 &lt;b&gt;백링크&lt;/b&gt;가 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백링크가 중요한 이유는&amp;nbsp; 만약 A사이트의 링크가 여러 사이트에서 백링크를 얻게 된다면 &quot;아, 이 사이트는 많은 컨텐츠를 통해 많은 사용자들에게 신뢰를 주는 웹사이트구나?!&quot; 라고 인식합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 사람들이 &quot;와 저 식당 맛있더라!&quot; 하면 맛집이 되듯이 웹사이트도 같은 양식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번글은 웹사이트를 최상단에 노출시키는 SEO에 대해 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;온페이지 SEO는 개발을 통해 향상시켜 본적이 있었지만, 온서프 SEO와 오프페이지 SEO는 처음 알게되었던 부분이였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>modori@</author>
      <guid isPermaLink="true">https://berrymotto.tistory.com/2</guid>
      <comments>https://berrymotto.tistory.com/2#entry2comment</comments>
      <pubDate>Thu, 28 Mar 2024 15:21:17 +0900</pubDate>
    </item>
  </channel>
</rss>