<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>lyla-bae 님의 블로그</title>
    <link>https://lyla-bae.tistory.com/</link>
    <description>lyla-bae 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Wed, 8 Apr 2026 17:20:02 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>lyla-bae</managingEditor>
    <image>
      <title>lyla-bae 님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/8159728/attach/f5b51433a6bc4126b4d43f65c72eab3b</url>
      <link>https://lyla-bae.tistory.com</link>
    </image>
    <item>
      <title>서버 vs 클라이언트 혼재된 개념 정리</title>
      <link>https://lyla-bae.tistory.com/46</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;요 4주동안 프로젝트 현직 멘토님과 주1회 멘토링을 진행하면서 Next 관련 공부했던 내용들을 정리해보았다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본기가 너무 부족한거같다 공부좀 하자 ^^&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSR / CSR vs 서버 컴포넌트 / 클라이언트 컴포넌트 &lt;u&gt;부터 일단 헷갈림&lt;/u&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;SSR = 서버컴포넌트&lt;/s&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;CSR = 클라이언트컴포넌트&lt;/s&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;라고 이해하고 있었던 나....(진짜 공부 안한거 티남) 너무 부끄러웠다&lt;/s&gt;&lt;/span&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;SSR / CSR 이란 &amp;rarr; &lt;b&gt;Redering&lt;/b&gt; 시점을 말하는것이다.&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;SSR: 서버에서 HTML 만들어서 보냄 (언제 그리냐)&lt;/li&gt;
&lt;li&gt;CSR: 브라우저에서 JS로 그림&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;&lt;b&gt;서버 컴포넌트(RSC) / 클라이언트 컴포넌트(RCC)는? &amp;rarr; 실행되는 위치&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;서버컴포: 서버에서 실행됨&lt;/li&gt;
&lt;li&gt;클라컴포: 브라우저에서 실행됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSR vs CSR &lt;b&gt;차이&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SSR&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버단에서 렌더링이 빠르게 진행됨. 때문에 FCP 시간을 단축가능한 장점이 있다. &lt;br /&gt;그러나 무언가 인터렉션할수있는 상태는 아니기때문에 그상태로 JS번들 &amp;gt; Hydration을 기다려야하는 단점이 있다.&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;CSR&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트단에서 렌더링이 진행된다. 빈 HTML 파일을 가장먼저 불러오고 JS번들을 기다려야한다는 단점이 있지만, &lt;br /&gt;Hydration 이후에는 바로 사용자 인터렉션 가능하다는 장점&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;b&gt;Hydration&lt;/b&gt;은 또 뭐고&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bguS2O/dJMcacikH4Z/HZw3qaNHobPN78kUqP05eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bguS2O/dJMcacikH4Z/HZw3qaNHobPN78kUqP05eK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bguS2O/dJMcacikH4Z/HZw3qaNHobPN78kUqP05eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbguS2O%2FdJMcacikH4Z%2FHZw3qaNHobPN78kUqP05eK%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;1920&quot; height=&quot;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사전렌더링 이후 인터렉션 되지않는 목마른 JS번들이 빈 HTML에게 물을 뿌려주는것과 같은..! 즉 &lt;u&gt;인터렉션되게 만들어주는 과정을 말한다&lt;/u&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;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;SSR이면 바로 인터랙션 가능하다?&lt;/s&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;CSR만 인터랙션 가능하다?&lt;/s&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;No.... SSR도 처음엔 인터랙션 안된다. &lt;b&gt;반드시 Hydration 이후에&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;&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;서버 컴포넌트 vs 클라이언트 컴포넌트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;서버컴포 = 그냥 SSR용&lt;/s&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;클라컴포 = 그냥 CSR용&lt;/s&gt;&lt;/span&gt;&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;&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;b&gt;서버 컴포넌트&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;데이터 fetching&lt;/li&gt;
&lt;li&gt;인증 처리&lt;/li&gt;
&lt;li&gt;초반에 빠르게 UI 구성&lt;/li&gt;
&lt;li&gt;JS 번들에 포함 안됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&amp;rarr; &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 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;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태 관리 (useState)&lt;/li&gt;
&lt;li&gt;이벤트 (onClick)&lt;/li&gt;
&lt;li&gt;애니메이션&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;&amp;rarr; 사용자와 상호작용을 해야하는&amp;nbsp;&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next에서는 기본적으로 서버컴포를 쓰는것을 권장. 그래야 JS번들 용량을 줄일수 있기 때문이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;but 상호작용 필요한 페이지들은 클라컴포를 써야한다&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;size14&quot;&gt;이정도 적어두면 이제 정말 헷갈릴일은 없겠지...? ^^.....제발&lt;/p&gt;</description>
      <category>NEXT</category>
      <category>CSR</category>
      <category>Hydration</category>
      <category>next.js</category>
      <category>RCC</category>
      <category>RSC</category>
      <category>SSR</category>
      <author>lyla-bae</author>
      <guid isPermaLink="true">https://lyla-bae.tistory.com/46</guid>
      <comments>https://lyla-bae.tistory.com/46#entry46comment</comments>
      <pubDate>Fri, 3 Apr 2026 22:35:48 +0900</pubDate>
    </item>
    <item>
      <title>스와이프 탭으로 UX까지 배우는 캐러셀 유랑기</title>
      <link>https://lyla-bae.tistory.com/45</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;오랜만에 돌아온 블로그,, 요즘 근황은 또 다른 팀플을 하고있느라 정신이 없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;문제 해결을 해갈때마다 블로그에 꼭 회고해야지라고 마음속으로 다짐했지만 실제로 행동하기까진 쉽지 않은것같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RagNd/dJMcaaLsWUE/9nkcSKKKX7c3qgL7LS7zx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RagNd/dJMcaaLsWUE/9nkcSKKKX7c3qgL7LS7zx0/img.png&quot; data-alt=&quot;디자인시안&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RagNd/dJMcaaLsWUE/9nkcSKKKX7c3qgL7LS7zx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRagNd%2FdJMcaaLsWUE%2F9nkcSKKKX7c3qgL7LS7zx0%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;500&quot; height=&quot;291&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;디자인시안&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 팀플에서 본격적으로 게시판 CRUD 개발을 맡게되었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 &lt;u&gt;스와이프 되는 모양의 카테고리 탭을 만들어야 하는 상황&lt;/u&gt;이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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 data-ke-size=&quot;size16&quot;&gt;문제가 되는 영역이 이 컴포넌트 하나뿐이었기 때문에, JS 이벤트로 가볍게 처리하는 방식도 충분히 가능해 보였다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그래서 실제로 직접 구현을 1차 시도해봤다.&lt;/h4&gt;
&lt;pre id=&quot;code_1774485448514&quot; class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;div
  className=&quot;tab-wrap flex gap-2 overflow-x-auto mt-5 ml-6 [scrollbar-width:none] [-ms-overflow-style:none] [&amp;amp;::-webkit-scrollbar]:hidden&quot;
  onWheel={(event) =&amp;gt; {
    event.currentTarget.scrollLeft += event.deltaY;
  }}
&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤 y는 숨기고 x축만 허용했고 pc에서는 가로스크롤이 보기싫어서 머리를 굴리다가 &lt;br /&gt;&lt;b&gt;onWheel 이벤트로 마우스휠을 굴리면 옆으로 굴러가는&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;UT결과는 불편함  &lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KMSP6/dJMcadahYA0/AyQs2PZUkiOrn0n6ueOO90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KMSP6/dJMcadahYA0/AyQs2PZUkiOrn0n6ueOO90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KMSP6/dJMcadahYA0/AyQs2PZUkiOrn0n6ueOO90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKMSP6%2FdJMcadahYA0%2FAyQs2PZUkiOrn0n6ueOO90%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;500&quot; height=&quot;321&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;936&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;꼭 &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 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;shadcn 내장 캐러셀&lt;/li&gt;
&lt;li&gt;slick&lt;/li&gt;
&lt;li&gt;swiper.js&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 프로젝트에서 shadcn을 사용하고 있었기 때문에 내장 캐러셀을 그대로 쓰는 것도 고려했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 shadcn 캐러셀이 &lt;b&gt;Embla 기반으로 동작한다는 걸 알게 됐다&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Embla 을 선택한 이유&lt;b&gt;&lt;/b&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;u&gt;벼우면서도 기본적인 캐러셀 기능을 하는&lt;/u&gt; 라이브러리가 필요했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2733&quot; data-origin-height=&quot;1625&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmc9DI/dJMcacoYKnK/pAS0DeEdqYLhsnh1ZdKkKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmc9DI/dJMcacoYKnK/pAS0DeEdqYLhsnh1ZdKkKk/img.png&quot; data-alt=&quot;npm trends 조사결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmc9DI/dJMcacoYKnK/pAS0DeEdqYLhsnh1ZdKkKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcmc9DI%2FdJMcacoYKnK%2FpAS0DeEdqYLhsnh1ZdKkKk%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;2733&quot; height=&quot;1625&quot; data-origin-width=&quot;2733&quot; data-origin-height=&quot;1625&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;npm trends 조사결과&lt;/figcaption&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;u&gt;shadcn이 대중화되서인지 embla가 압도적&lt;/u&gt;었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 shadcn 캐러셀 컴포넌트를 굳이 설치해서까지는 필요없었다고 판단.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어짜피 shadcn을 썼어도 Embla 의존성은 설치했어야했기때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;shadcn은 쓰지않고 Embla 하나만으로 구현이 충분했다&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-origin-width=&quot;1398&quot; data-origin-height=&quot;1860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PNCtU/dJMcaduAuKo/PG2m4iZOekJ9ZDcQZDjXZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PNCtU/dJMcaduAuKo/PG2m4iZOekJ9ZDcQZDjXZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PNCtU/dJMcaduAuKo/PG2m4iZOekJ9ZDcQZDjXZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPNCtU%2FdJMcaduAuKo%2FPG2m4iZOekJ9ZDcQZDjXZK%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;500&quot; height=&quot;665&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;1860&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;사용법은 다른라이브러리와 비슷하게 useEmblaCarousel 전용훅을 이용했고, 각 속성값을 이용해 간단하게 구현가능!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디테일한건 &lt;u&gt;공식문서&lt;/u&gt;에서 확인~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.embla-carousel.com/&quot;&gt;https://www.embla-carousel.com/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774488259058&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;Home&quot; data-og-description=&quot;An API designed with flexibility and extensibility in mind.&quot; data-og-host=&quot;www.embla-carousel.com&quot; data-og-source-url=&quot;https://www.embla-carousel.com/&quot; data-og-url=&quot;https://www.embla-carousel.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.embla-carousel.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.embla-carousel.com/&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;Home&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An API designed with flexibility and extensibility in mind.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.embla-carousel.com&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;그리고 내가 선택한 탭 index를 useMemo에 저장시켜 탭을 선택한후에 자연스럽게 애니메이션되면서 활성화될수있도록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect와 scrollTo를 활용했다.&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;h4 data-ke-size=&quot;size20&quot;&gt;그런데 또 문제발견 -&amp;nbsp; touch action&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1544&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRve9U/dJMcaf63Jhj/SGLMAq5gB5ZxJ2JkvuOF30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRve9U/dJMcaf63Jhj/SGLMAq5gB5ZxJ2JkvuOF30/img.png&quot; data-alt=&quot;코드래빗의 리뷰&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRve9U/dJMcaf63Jhj/SGLMAq5gB5ZxJ2JkvuOF30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRve9U%2FdJMcaf63Jhj%2FSGLMAq5gB5ZxJ2JkvuOF30%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;500&quot; height=&quot;235&quot; data-origin-width=&quot;1544&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;코드래빗의 리뷰&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PR올리고 코드리뷰 받았더니&amp;nbsp;touch action이란것을 추가하라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런게 있는지도 몰랐는데 알아보니 모바일에서 x 스크롤과 y스크롤 제스처가 혼용되는것을 방지하는 속성이라고 하더라&lt;/p&gt;
&lt;pre id=&quot;code_1774487401636&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div ref={emblaRef} className=&quot;overflow-hidden [touch-action:pan-y_pinch-zoom]&quot;&amp;gt;&lt;/code&gt;&lt;/pre&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-crop (1).gif&quot; data-origin-width=&quot;247&quot; data-origin-height=&quot;75&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkJ7Ed/dJMcacCwyVD/WL8FBEa0Epr8O7lhTpRyJ0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkJ7Ed/dJMcacCwyVD/WL8FBEa0Epr8O7lhTpRyJ0/img.gif&quot; data-alt=&quot;완성결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkJ7Ed/dJMcacCwyVD/WL8FBEa0Epr8O7lhTpRyJ0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bkJ7Ed/dJMcacCwyVD/WL8FBEa0Epr8O7lhTpRyJ0/img.gif&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;247&quot; height=&quot;75&quot; data-filename=&quot;ezgif.com-crop (1).gif&quot; data-origin-width=&quot;247&quot; data-origin-height=&quot;75&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;완성결과&lt;/figcaption&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;&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;처음에는 &amp;ldquo;포커스가 잡히기만 하면 충분하다&amp;rdquo;고 생각했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;실제로는 &lt;/span&gt;&lt;b&gt;포커스의 &amp;lsquo;모양&amp;rsquo;도 사용자 경험에 큰 영향을 준다는 것&lt;/b&gt;&lt;span&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&gt;버튼에 적용된 &lt;/span&gt;border-radius&lt;span&gt;와&lt;/span&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컴포넌트의 형태와 일관되게 인식되도록 border-radius를 함께 조정했다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;154&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwVhCX/dJMcabQ7QKz/d1Cm0cDoHTOn9YqFEuq5w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwVhCX/dJMcabQ7QKz/d1Cm0cDoHTOn9YqFEuq5w1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwVhCX/dJMcabQ7QKz/d1Cm0cDoHTOn9YqFEuq5w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwVhCX%2FdJMcabQ7QKz%2Fd1Cm0cDoHTOn9YqFEuq5w1%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;154&quot; height=&quot;198&quot; data-origin-width=&quot;154&quot; data-origin-height=&quot;198&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;h4 data-ke-size=&quot;size20&quot;&gt;마무리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 라이브러리 캐러셀이라고 생각했지만 실제로는 사용성, 접근성, UX까지 챙기느라 생각보다 쉽지않았던 과정이었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 직접 부딪혀보는것만 해도 배울게 정말 많다는것을 깨달은거같다. 팀플 쵝오 &lt;/p&gt;</description>
      <category>Trouble shooting</category>
      <category>embla</category>
      <category>캐러셀</category>
      <category>트러블슈팅</category>
      <category>팀프로젝트</category>
      <author>lyla-bae</author>
      <guid isPermaLink="true">https://lyla-bae.tistory.com/45</guid>
      <comments>https://lyla-bae.tistory.com/45#entry45comment</comments>
      <pubDate>Thu, 26 Mar 2026 10:28:35 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트 주제와 관련된 UX 논문 찾기</title>
      <link>https://lyla-bae.tistory.com/44</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1bFk6/dJMcafTiJMd/tTywBVPnnTO9BPsTnIDUi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1bFk6/dJMcafTiJMd/tTywBVPnnTO9BPsTnIDUi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1bFk6/dJMcafTiJMd/tTywBVPnnTO9BPsTnIDUi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1bFk6%2FdJMcafTiJMd%2FtTywBVPnnTO9BPsTnIDUi1%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;300&quot; height=&quot;300&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;720&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;size14&quot;&gt;24년 11월 10월에 진행한 스터디 주제는 &lt;u&gt;&lt;b&gt;UX 관련 논문 찾기&lt;/b&gt;&lt;/u&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;프로젝트를 진행할 때 논문을 참고하는 습관을 들이면 좋은데, &lt;b&gt;각 팀의 프로젝트 주제와 관련된 논문&lt;/b&gt;을 찾아보시거나,&amp;nbsp;&lt;b&gt;UX 관련 논문&lt;/b&gt;을 찾아보자. 보통 논문을 볼 땐 abstract과 가장 뒤의 결론에 모든게 나와 있긴 하지만 첨부한 논문과 같이 사례 분석을 한 논문은 이론 배경에서 많은 지식을 얻을 수 있다. 논문 결과를 간단히 정리해도 좋고, 인상 깊거나 새롭게 알게 된 내용만 발췌해도 좋다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추천 키워드: 사용자 경험 UX, User experience, 데이터 UX, UX 방법론, UI 디자인&lt;/li&gt;
&lt;li&gt;국내 무료 논문 사이트 RISS:&amp;nbsp;&lt;a href=&quot;https://www.riss.kr/index.do&quot;&gt;https://www.riss.kr/index.do&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;논문 중 KCI 등재된 논문은 인정받은 논문이므로 질이 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;br /&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;u&gt;서비스컨셉&lt;/u&gt; 중 하나로 사용자가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;헬스장에 출입하면서 바코드를 통해 출석한 데이터로 운동하고 있다는 것을 아바타 화하여 보여준다.&lt;/b&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;그리고 프로토타입까지 만들어 UT까지 진행되었는데 라이트모드보다 &lt;b&gt;다크모드 선호도가 높았다&lt;/b&gt;는 테스트결과가 나왔고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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 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;다른 이용자들이 현재 운동하고 있다는 것을 보여주는 것이 motivation을 만듦&lt;/li&gt;
&lt;li&gt;다크모드가 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에 힌트를 얻어 기다려짐을 만드는데 큰 도움이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;(아쉽게도 현재 논문링크는 없어진상태 )&lt;/p&gt;</description>
      <category>UXUI</category>
      <category>UX스터디</category>
      <category>논문찾기</category>
      <category>사용자인터랙션</category>
      <category>헬스장서비스</category>
      <author>lyla-bae</author>
      <guid isPermaLink="true">https://lyla-bae.tistory.com/44</guid>
      <comments>https://lyla-bae.tistory.com/44#entry44comment</comments>
      <pubDate>Sat, 14 Mar 2026 10:21:25 +0900</pubDate>
    </item>
    <item>
      <title>자신이 행하는 일은 엄격하게, 남의 것을 받아들일땐 너그럽게,</title>
      <link>https://lyla-bae.tistory.com/43</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;design_thumb.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tFWrq/dJMcachOsJd/SWup4OO0CKOyI4fpH5gZIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tFWrq/dJMcachOsJd/SWup4OO0CKOyI4fpH5gZIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tFWrq/dJMcachOsJd/SWup4OO0CKOyI4fpH5gZIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtFWrq%2FdJMcachOsJd%2FSWup4OO0CKOyI4fpH5gZIK%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;300&quot; height=&quot;300&quot; data-filename=&quot;design_thumb.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;720&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;h3 data-ke-size=&quot;size23&quot;&gt;다른 곳으로부터 받는 정보는 최대한 많은 변수에 대비해야하고, &lt;br /&gt;상대에게 전달해야할 피드백은 분명하고 명백하게 전달해야한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;포스텔의 법칙을 간단하게 요약해 보았다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&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;size14&quot;&gt;ex) 반응형 웹디자인은 다양한 기기의 크기나 유형에 맞게 웹사이트가 유동적으로 반응하고 이용자에게 제공되는것을 알수있습니다&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&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;size14&quot;&gt;ex) 회원가입 폼을 구성할때 중복된 정보를 요청하고있지 않은지 확인하고, 꼭 필요한 폼만 채울수있도록 구성해야 이용자의 피로를 줄일수있습니다&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;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;참고링크&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://m.blog.naver.com/moongteagray/222302964051&quot;&gt;https://m.blog.naver.com/moongteagray/222302964051&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771584636455&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;[UX/UI의 10가지 심리학 법칙] 5장. Postel&amp;rsquo;s Law 포스텔의 법칙&quot; data-og-description=&quot;자신이 행하는 일은 엄격하게, 남의 것을 받아들일 때는 너그럽게 Be conservative in what you do be lib...&quot; data-og-host=&quot;blog.naver.com&quot; data-og-source-url=&quot;https://m.blog.naver.com/moongteagray/222302964051&quot; data-og-url=&quot;https://blog.naver.com/moongteagray/222302964051&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4UDQV/dJMb8YXH7in/uhAl0tiKJ64Hvi1FGJKJfk/img.png?width=743&amp;amp;height=433&amp;amp;face=0_0_743_433&quot;&gt;&lt;a href=&quot;https://m.blog.naver.com/moongteagray/222302964051&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://m.blog.naver.com/moongteagray/222302964051&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4UDQV/dJMb8YXH7in/uhAl0tiKJ64Hvi1FGJKJfk/img.png?width=743&amp;amp;height=433&amp;amp;face=0_0_743_433');&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;[UX/UI의 10가지 심리학 법칙] 5장. Postel&amp;rsquo;s Law 포스텔의 법칙&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자신이 행하는 일은 엄격하게, 남의 것을 받아들일 때는 너그럽게 Be conservative in what you do be lib...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.naver.com&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>UXUI</category>
      <category>명백하게전달</category>
      <category>변수에대비</category>
      <category>포스텔의법칙</category>
      <author>lyla-bae</author>
      <guid isPermaLink="true">https://lyla-bae.tistory.com/43</guid>
      <comments>https://lyla-bae.tistory.com/43#entry43comment</comments>
      <pubDate>Mon, 23 Feb 2026 09:55:06 +0900</pubDate>
    </item>
    <item>
      <title>Lighthouse로 만난 접근성 이슈 하나씩 뽀개기</title>
      <link>https://lyla-bae.tistory.com/42</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;00.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1620&quot;&gt;&lt;a href=&quot;https://github.com/WaitGYM/FE&quot; target=&quot;_blank&quot; title=&quot;기다려짐 FE&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2qheX/dJMcacPEaCH/R1kbQDEjb01qZosHogA7nK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2qheX%2FdJMcacPEaCH%2FR1kbQDEjb01qZosHogA7nK%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;2880&quot; height=&quot;1620&quot; data-filename=&quot;00.png&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1620&quot;/&gt;&lt;/a&gt;&lt;figcaption&gt;이미지를 누르면 기다려짐 github로 이동합니다 (틈새홍보)&lt;/figcaption&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;size14&quot;&gt;드디어 끝이보이는 기다려짐...! &lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;(그러나 끝나지않은...)&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;요즘은 리팩토링, 최적화 등 마무리작업을 진행중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;나는 접근성과 SEO을 개선하기 위해 Lighthouse를 요 몇일사이 열심히 돌려보았고&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;h4 data-ke-size=&quot;size20&quot;&gt;접근성 구조와 Landmark&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 시맨틱하게 마크업을 해야한다는것은 너무 잘알고있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 Landmark라는 개념은 초면이였다. 그래서 또 찾아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;페이지의 주요 영역을 시맨틱태그 or Aria role로 구분해 &lt;br /&gt;스크린단위가 영역단위로 탐색할수있게 해주는 것&lt;/u&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;주요 랜드마크 종류&lt;/b&gt;는 HTML 태그로 이렇게 표현한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lt;header&amp;gt; ⭐️⭐️⭐️ 페이지 상단 헤더&lt;/li&gt;
&lt;li&gt;&amp;lt;nav&amp;gt; ⭐️⭐️⭐️ 내비게이션 메뉴&lt;/li&gt;
&lt;li&gt;&amp;lt;main&amp;gt; ⭐️⭐️⭐️ 주요 콘텐츠 영역&lt;/li&gt;
&lt;li&gt;&amp;lt;aside&amp;gt; 사이드바&lt;/li&gt;
&lt;li&gt;&amp;lt;footer&amp;gt; ⭐️⭐️ 하단 푸터&lt;/li&gt;
&lt;li&gt;&amp;lt;section&amp;gt;, &amp;lt;article&amp;gt; 콘텐츠 구역&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@sejinkim/HTML-랜드마크-역할을-사용하여-접근성-향상시키기&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@sejinkim/HTML-랜드마크-역할을-사용하여-접근성-향상시키기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771567491789&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;[번역] HTML 랜드마크 역할을 사용하여 접근성 향상시키기&quot; data-og-description=&quot;웹 접근성을 향상시키기 위한 HTML 랜드마크 규칙(ARIA landmark roles)에 대해 설명합니다.&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@sejinkim/HTML-랜드마크-역할을-사용하여-접근성-향상시키기&quot; data-og-url=&quot;https://velog.io/@sejinkim/HTML-랜드마크-역할을-사용하여-접근성-향상시키기&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uL4UV/dJMb8SXupZ5/lfclnjstPXOs9RlUWkJqz0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dUXJoO/dJMb8Zvx3BK/3UCiH4HkEkKFVBsO45rOp1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/nABmF/dJMb8SXupZ6/Va9qHN4uR2zeJkopNaXknK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://velog.io/@sejinkim/HTML-랜드마크-역할을-사용하여-접근성-향상시키기&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@sejinkim/HTML-랜드마크-역할을-사용하여-접근성-향상시키기&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uL4UV/dJMb8SXupZ5/lfclnjstPXOs9RlUWkJqz0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dUXJoO/dJMb8Zvx3BK/3UCiH4HkEkKFVBsO45rOp1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/nABmF/dJMb8SXupZ6/Va9qHN4uR2zeJkopNaXknK/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;[번역] HTML 랜드마크 역할을 사용하여 접근성 향상시키기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;웹 접근성을 향상시키기 위한 HTML 랜드마크 규칙(ARIA landmark roles)에 대해 설명합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;브랜드컬러도 좋지만 4.5:1 명암비는 한번쯤은 고려해보자&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;디자인 의사결정을 혼자서 하다보니 접근성에 맞는지도 확인하지 않고 작업을 했던것이였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUDeiD/dJMcadgHUB1/52Jo4WoVCsYvP7dXSRBZyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUDeiD/dJMcadgHUB1/52Jo4WoVCsYvP7dXSRBZyK/img.png&quot; data-alt=&quot;기다려짐의 홈화면 비교&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUDeiD/dJMcadgHUB1/52Jo4WoVCsYvP7dXSRBZyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUDeiD%2FdJMcadgHUB1%2F52Jo4WoVCsYvP7dXSRBZyK%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;500&quot; height=&quot;395&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;852&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기다려짐의 홈화면 비교&lt;/figcaption&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;위 화면은 서비스 홈화면인데 하단 오렌지색상 버튼이 WCAG가 정한 4.5:1 명암비에 맞지않아&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;br /&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;u&gt;피그마 플러그인 Stark&lt;/u&gt;도 알게되었고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 명암비에 맞지않는다하더라도 &lt;u&gt;비슷한 색상을 추천&lt;/u&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;a href=&quot;https://www.getstark.co/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.getstark.co/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771569748523&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;Stark: The suite of integrated accessibility tools for your product design and development team&quot; data-og-description=&quot;The suite of integrated accessibility tools for your product design and development team &amp;bull; Making the world&amp;rsquo;s products accessible.&quot; data-og-host=&quot;www.getstark.co&quot; data-og-source-url=&quot;https://www.getstark.co/&quot; data-og-url=&quot;https://www.getstark.co/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c7VIXx/dJMb81GTPkh/E6GRhxkd6Mm9NOgknI0kDk/img.png?width=2880&amp;amp;height=1600&amp;amp;face=0_0_2880_1600,https://scrap.kakaocdn.net/dn/u7HHP/dJMb86OYrZZ/4PGt17pioG9ldq5Xnz7Hlk/img.png?width=2880&amp;amp;height=1600&amp;amp;face=0_0_2880_1600,https://scrap.kakaocdn.net/dn/k01l6/dJMb84p5mF0/YS9MceZLfFvpS85qjd0rok/img.png?width=2384&amp;amp;height=1382&amp;amp;face=0_0_2384_1382&quot;&gt;&lt;a href=&quot;https://www.getstark.co/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.getstark.co/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c7VIXx/dJMb81GTPkh/E6GRhxkd6Mm9NOgknI0kDk/img.png?width=2880&amp;amp;height=1600&amp;amp;face=0_0_2880_1600,https://scrap.kakaocdn.net/dn/u7HHP/dJMb86OYrZZ/4PGt17pioG9ldq5Xnz7Hlk/img.png?width=2880&amp;amp;height=1600&amp;amp;face=0_0_2880_1600,https://scrap.kakaocdn.net/dn/k01l6/dJMb84p5mF0/YS9MceZLfFvpS85qjd0rok/img.png?width=2384&amp;amp;height=1382&amp;amp;face=0_0_2384_1382');&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;Stark: The suite of integrated accessibility tools for your product design and development team&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The suite of integrated accessibility tools for your product design and development team &amp;bull; Making the world&amp;rsquo;s products accessible.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.getstark.co&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;a href=&quot;https://app.contrast-finder.org/?lang=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://app.contrast-finder.org/?lang=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771569855529&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;Contrast Finder, 웹 접근성 기준(WCAG)에 적합하도록 명암이 충분히 대비되는 색의 조합 검색&quot; data-og-description=&quot;Contrast-Finder는 웹 접근성 기준(WCAG)에 적합하도록 명암이 충분히 대비되는 색의 조합을 찾아줍니다. 그래서 색의 명암비와 관련된 웹 접근성(a11y) 테스트를 충족시키는 데 도움을 드립니다. Contras&quot; data-og-host=&quot;app.contrast-finder.org&quot; data-og-source-url=&quot;https://app.contrast-finder.org/?lang=ko&quot; data-og-url=&quot;https://app.contrast-finder.org/?lang=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oZGEB/dJMb85WPUcb/MlKfpjvIkOeqm4qS1tMJQk/img.png?width=1051&amp;amp;height=1053&amp;amp;face=0_0_1051_1053&quot;&gt;&lt;a href=&quot;https://app.contrast-finder.org/?lang=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://app.contrast-finder.org/?lang=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oZGEB/dJMb85WPUcb/MlKfpjvIkOeqm4qS1tMJQk/img.png?width=1051&amp;amp;height=1053&amp;amp;face=0_0_1051_1053');&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;Contrast Finder, 웹 접근성 기준(WCAG)에 적합하도록 명암이 충분히 대비되는 색의 조합 검색&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contrast-Finder는 웹 접근성 기준(WCAG)에 적합하도록 명암이 충분히 대비되는 색의 조합을 찾아줍니다. 그래서 색의 명암비와 관련된 웹 접근성(a11y) 테스트를 충족시키는 데 도움을 드립니다. Contras&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;app.contrast-finder.org&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;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;접근성이슈는 아니지만 pc에서 모바일화면을 작업할때는 체감되지않던 아이콘,폰트들이 &lt;br /&gt;실제 디바이스에서 실행해보면 사이즈가 많이 작다는것을 알게되었다. &lt;br /&gt;이럴땐 그냥 사람들이 많이 이용하고 잘운영되고 있는 사이트들을 벤치마킹 하는것이 짱인거같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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 data-ke-size=&quot;size16&quot;&gt;또한 &lt;u&gt;아이콘은 터치영역까지 고려&lt;/u&gt;해서 작업해야한다는점..! (최소 48px) 이건 WCAG에서 권고하는 부분이다!&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;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;영원히 해결하지 못할것같았던 MUI Modal의 Focus 접근성 이슈&lt;/h4&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 id=&quot;code_1771570441667&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Blocked aria-hidden on an element because its descendant retained focus.
Element with focus: &amp;lt;button&amp;gt;
Ancestor with aria-hidden: &amp;lt;div#root&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;MUI Modal은 열릴 때 배경 콘텐츠를 비활성화하기 위해 #root에 aria-hidden=&quot;true&quot;를 자동으로 붙인다. &lt;br /&gt;문제는 버튼을 클릭하면 그 버튼에 포커스가 생기는데, 모달이 열리면서 aria-hidden이 적용되는 시점에 &lt;br /&gt;&lt;b&gt;포커스된 버튼이 아직 #root 안에 남아 있다&lt;/b&gt;는 거다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;버튼 클릭 &amp;rarr; 버튼에 포커스 생성 &amp;rarr; 모달 열림 &amp;rarr; #root에 aria-hidden 적용 &amp;rarr; &lt;br /&gt;포커스된 버튼이 aria-hidden 영역 안에 남아있음 &amp;rarr; 에러&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI는 멀쩡하게 동작하지만 스크린리더 기준으로는 포커스 탐색이 깨진 상태였던것...&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;이거 하나를 해결하기 위해 온갖 방법(구글링,gpt,claude 등) 다 동원해봐도 계속 해결되지않아&lt;br /&gt;접근성 모든이슈를 포기하고 있다가 요몇일 다시 해야겠단 생각이 들어 &lt;br /&gt;claude와 함께 다시 머리를 맡대어 문제해결을 드디어 해버렸다...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771573313638&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;button
onClick={(e) =&amp;gt; {
  e.currentTarget.blur(); // 포커스 제거
  setTimeout(() =&amp;gt; {
    setIsOpenDialog(true);  // 모달 오픈
  }, 0);
}}
&amp;gt;
  삭제
&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모달을 열기전에 버튼 포커스를&lt;b&gt; blur()로 포커스를 명시적으로 제거&lt;/b&gt;시키면 되는 간단한 문제였던것이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 간헐적으로 이슈가 일어나고 있어서 &lt;u&gt;setTimeout으로&amp;nbsp;모달오픈을 타이밍을 맞추어 &lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 task로 미루면 안정적으로 문제 해결을 할수 있었다. (고마워 클로드 )&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eswdJr/dJMcaih0IMc/8A8XqWUJkULY6zHkl1e1Ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eswdJr/dJMcaih0IMc/8A8XqWUJkULY6zHkl1e1Ak/img.png&quot; data-alt=&quot;처음에는 Lighthouse 접근성 점수가 70점대가 나온 게 이제는 90점대가 나온다..!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eswdJr/dJMcaih0IMc/8A8XqWUJkULY6zHkl1e1Ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeswdJr%2FdJMcaih0IMc%2F8A8XqWUJkULY6zHkl1e1Ak%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;450&quot; height=&quot;206&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;처음에는 Lighthouse 접근성 점수가 70점대가 나온 게 이제는 90점대가 나온다..!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;접근성 이슈들을 하나씩 해결해 나가다 보니, 단순히 점수를 맞추는 작업이 아니라&lt;br /&gt;사용자가 어떤 흐름으로 화면을 탐색하고, 어디에서 멈추고, &lt;br /&gt;어떻게 인터랙션하는지를 확인하는 과정에 가깝다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;랜드마크 구조, 명암비, 터치 영역, 포커스 흐름까지 결국은 모두 UIUX를 안정적으로 만들기 위한 요소들이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업을 통해 접근성은 UI를 구현하면서 자연스럽게 함께 고려해야 하는 기준이라는 것을 조금은 체감하게 된 것 같다.&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>
      <category>Trouble shooting</category>
      <category>기다려짐</category>
      <category>웹접근성</category>
      <category>트러블슈팅</category>
      <author>lyla-bae</author>
      <guid isPermaLink="true">https://lyla-bae.tistory.com/42</guid>
      <comments>https://lyla-bae.tistory.com/42#entry42comment</comments>
      <pubDate>Fri, 20 Feb 2026 17:11:42 +0900</pubDate>
    </item>
    <item>
      <title>미들웨어, 알고나니 다 설명된다!</title>
      <link>https://lyla-bae.tistory.com/41</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 디깅미션이 돌아왔다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이정환 Next강의를 이미 완강했었기때문에 수업을 잘이해하지않을까 생각했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미들웨어는 설명이없어서&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(있었는데 기억을 못하는걸수도;;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;나의 영원한 친구&lt;/s&gt; 클로드와 지피티와 함께 미들웨어에 대해 공부하는 시간을 가졌다&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;건물입구의 경비원 같은 존재라 생각하면 된다. &lt;b&gt;페이지 보여주기 전에 한번 거르는 관문&lt;/b&gt;인것이다&lt;/p&gt;
&lt;pre id=&quot;code_1770099768437&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// middleware.ts
import { NextResponse } from 'next/server'

export function middleware(request) {
  const token = request.cookies.get('token')

  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  return NextResponse.next()
}

export const config = {
  matcher: ['/dashboard/:path*']
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 /dashboard 접속하면 미들웨어가 로그인여부를 따진다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안했으면 /login으로 보내고 했으면 그대로 통과시켜주는 것이다&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;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;pre id=&quot;code_1770099912627&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function middleware() {
  console.log('실행')
} //큰일난다!

export const config = {
  matcher: ['/dashboard/:path*']
} //matcher로 쓸곳만 명시하자&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;matcher&lt;/b&gt; 안쓰면 이 콘솔메시지가 페이지, 이미지, css, api, 심지어 favicon까지 전부거쳐 실행될것이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 &lt;u&gt;성능저하가&lt;/u&gt; 된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770100241040&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;matcher: '/:path*'//모든경로 다포함

export const config = {
  matcher: [
    '/((?!login|signup|api|_next|favicon.ico).*)' //login, signup, api, 정적 파일 빼고 전부
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;matcher에 모든경로를 포함해서 체크하면 &lt;b&gt;리다이렉트된 페이지가 또다시 미들웨어를 타기때문에 무한루프&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;하나더 말하자면 미들웨어는 서버에서 실행된다.&lt;/h4&gt;
&lt;pre id=&quot;code_1770100448908&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;localStorage.getItem('user') // 응안됨.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 localStorage같은 &lt;b&gt;브라우저 api들은 먹히지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할수 있는것은 쿠키,헤더,url 정보이니 명심하라~!!!!!!!&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;h2 data-ke-size=&quot;size26&quot;&gt;middleware의 보안이슈&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RSC 취약점이 발견되엇다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년 12월 React Server Components의 취약점때문에 보안 공격이슈가 있어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은이들의 react,next 프로젝트가 코인 채굴기로 변질되어있었다라는 후기를 꽤나 볼수있었다.&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; next16이 나오면서 middleware.ts는 proxy.ts로 바꾸라고 권장&lt;/b&gt;하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 파일명 변경이 아니라 proxy에서는 전달만하고 인증처리는 서버컴포넌트에서 안전하게 인증하라는 뜻이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&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;아직까진 middleware라고 적어도 동작은 하지만 언제 또 없어질지 모르니 걍 proxy로 쓰도록하자&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;a href=&quot;https://nextjs.org/docs/messages/middleware-to-proxy&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nextjs.org/docs/messages/middleware-to-proxy&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770101446344&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;Renaming Middleware to Proxy&quot; data-og-description=&quot;Using App Router Features available in /app&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/messages/middleware-to-proxy&quot; data-og-url=&quot;https://nextjs.org/docs/messages/middleware-to-proxy&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/messages/middleware-to-proxy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs/messages/middleware-to-proxy&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;Renaming Middleware to Proxy&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Using App Router Features available in /app&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&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;a href=&quot;https://www.dailysecu.com/news/articleView.html?idxno=203063&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.dailysecu.com/news/articleView.html?idxno=203063&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770100899598&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;[긴급] React&amp;middot;Next.js RSC서 인증 없는 원격 코드 실행 취약점&amp;hellip;&amp;ldquo;패치 미적용 시 즉각 침해 가능&amp;rdquo; - &quot; data-og-description=&quot;리액트 서버 컴포넌트(React Server Components, RSC)에서 인증 없이 원격 코드 실행이 가능한 중대한 보안 취약점이 공개됐다. 전 세계 웹 애&quot; data-og-host=&quot;www.dailysecu.com&quot; data-og-source-url=&quot;https://www.dailysecu.com/news/articleView.html?idxno=203063&quot; data-og-url=&quot;https://www.dailysecu.com/news/articleView.html?idxno=203063&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cj77o1/dJMb8YXGGuP/yBd6JPYEqkD5fvGs4eHWYK/img.jpg?width=960&amp;amp;height=803&amp;amp;face=0_0_960_803,https://scrap.kakaocdn.net/dn/blf2w9/dJMb8WMkMqz/nlilf1X7fOZhoMe8iSmc5K/img.jpg?width=960&amp;amp;height=803&amp;amp;face=0_0_960_803,https://scrap.kakaocdn.net/dn/0MOWd/dJMb8PGrnsD/Ti95qjefv74JWmVuWlVwR0/img.jpg?width=960&amp;amp;height=803&amp;amp;face=0_0_960_803&quot;&gt;&lt;a href=&quot;https://www.dailysecu.com/news/articleView.html?idxno=203063&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.dailysecu.com/news/articleView.html?idxno=203063&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cj77o1/dJMb8YXGGuP/yBd6JPYEqkD5fvGs4eHWYK/img.jpg?width=960&amp;amp;height=803&amp;amp;face=0_0_960_803,https://scrap.kakaocdn.net/dn/blf2w9/dJMb8WMkMqz/nlilf1X7fOZhoMe8iSmc5K/img.jpg?width=960&amp;amp;height=803&amp;amp;face=0_0_960_803,https://scrap.kakaocdn.net/dn/0MOWd/dJMb8PGrnsD/Ti95qjefv74JWmVuWlVwR0/img.jpg?width=960&amp;amp;height=803&amp;amp;face=0_0_960_803');&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;[긴급] React&amp;middot;Next.js RSC서 인증 없는 원격 코드 실행 취약점&amp;hellip;&amp;ldquo;패치 미적용 시 즉각 침해 가능&amp;rdquo; -&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;리액트 서버 컴포넌트(React Server Components, RSC)에서 인증 없이 원격 코드 실행이 가능한 중대한 보안 취약점이 공개됐다. 전 세계 웹 애&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.dailysecu.com&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그래서 결론은 &lt;u&gt;2단계 인증패턴&lt;/u&gt;을 쓰면 좋다!&lt;/h4&gt;
&lt;pre id=&quot;code_1770101557320&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// middleware or proxy
export function middleware(request) {
  const token = request.cookies.get('token')

  if (!token) {
    return NextResponse.redirect('/login')
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;u&gt;미들웨어에서 토큰 존재&lt;/u&gt;만 체크한뒤&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770101586236&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// app/dashboard/page.tsx
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'

export default async function Page() {
  const token = (await cookies()).get('token')
  if (!token) redirect('/login')

  const session = await db.session.findUnique({
    where: { token: token.value }
  })

  if (!session) redirect('/login')

  return &amp;lt;&amp;gt;대시보드&amp;lt;/&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버컴포넌트에서&lt;u&gt; DB까지 확인하면 그때가서 진짜 인증하는 구조&lt;/u&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0BT9d/dJMcajgKiRB/vWhPW52l3rK1Ajyz2oEKRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0BT9d/dJMcajgKiRB/vWhPW52l3rK1Ajyz2oEKRk/img.png&quot; data-alt=&quot;대충 이런짤로 마무리 (미들웨어 이해완했다는 뜻)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0BT9d/dJMcajgKiRB/vWhPW52l3rK1Ajyz2oEKRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0BT9d%2FdJMcajgKiRB%2FvWhPW52l3rK1Ajyz2oEKRk%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;300&quot; height=&quot;451&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대충 이런짤로 마무리 (미들웨어 이해완했다는 뜻)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>NEXT</category>
      <category>middleware</category>
      <category>next</category>
      <category>Proxy</category>
      <author>lyla-bae</author>
      <guid isPermaLink="true">https://lyla-bae.tistory.com/41</guid>
      <comments>https://lyla-bae.tistory.com/41#entry41comment</comments>
      <pubDate>Tue, 3 Feb 2026 16:18:15 +0900</pubDate>
    </item>
    <item>
      <title>Page Router에서 App Router로, 왜 바뀌었을까?</title>
      <link>https://lyla-bae.tistory.com/40</link>
      <description>&lt;h3 data-end=&quot;235&quot; data-start=&quot;217&quot; data-ke-size=&quot;size23&quot;&gt;CSR vs SSR&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Router 차이를 설명하기 전에 먼저 SSR의 등장부터 이해할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSR는 초기접속이 느리고 SEO가 불리하다는 치명적인 단점이 있었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR의 사전렌더링으로 CSR의 단점들을 해결할수 있다.&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;아래는 SSR의 사전렌더링 과정을 한눈에 볼수있어서 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSR은 빈껍데기를 화면에 먼저 렌더링시켜버리는 반면 SSR은 서버에서 미리 렌더링시켜 유저에게 뿌리기때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FCP(First Contentful Paint) : 사용자가 페이지 접속후 첫콘테츠를 만나는 시간을 나타내는 지표&lt;/b&gt; 가 굉장히 유리해지게된다.&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cH0Ekr/dJMcagj1WDS/pZOanMCW04n6RzhdPxgJ61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cH0Ekr/dJMcagj1WDS/pZOanMCW04n6RzhdPxgJ61/img.png&quot; data-alt=&quot;출처 - 이정환님의 Next.js 강의&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cH0Ekr/dJMcagj1WDS/pZOanMCW04n6RzhdPxgJ61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcH0Ekr%2FdJMcagj1WDS%2FpZOanMCW04n6RzhdPxgJ61%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;1920&quot; height=&quot;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 - 이정환님의 Next.js 강의&lt;/figcaption&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;Next12까지 page Router로 SSR을 도입하면서 데이터 패칭과 렌더링을 분리했는데 이 방법도 곧 한계를 보이고만다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 전체가 SSR되면서 굉장히 비효율적이며,&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;그래서 이문제를 또다시 해결하기위해 Next13 버전부터 app Router가 도입이 되었고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재기준 Next16까지 지금까지도 많은 풀스택 기능들을 쓸수있게 업데이트 되고 있다!&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; 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;뭐든 공식문서가 짱이니 Next가 궁금한 사람이라면 한번씩 들러보길 츄라이츄라이&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2400&quot; data-origin-height=&quot;1256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mEICs/dJMcac9Isdl/3w1cWqVmJ2E5hXVKPukDK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mEICs/dJMcac9Isdl/3w1cWqVmJ2E5hXVKPukDK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mEICs/dJMcac9Isdl/3w1cWqVmJ2E5hXVKPukDK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmEICs%2FdJMcac9Isdl%2F3w1cWqVmJ2E5hXVKPukDK1%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;400&quot; height=&quot;209&quot; data-origin-width=&quot;2400&quot; data-origin-height=&quot;1256&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://nextjs.org/docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nextjs.org/docs&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769577714624&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;Next.js Docs | Next.js&quot; data-og-description=&quot;Welcome to the Next.js Documentation.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs&quot; data-og-url=&quot;https://nextjs.org/docs&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bDgFTD/dJMb8Zvv19V/u3RSiAN8pyqXYfoAPi7L20/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/yI10X/dJMb8PGqNpH/3JMfOECyPUYr8NnLALFZH1/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bDgFTD/dJMb8Zvv19V/u3RSiAN8pyqXYfoAPi7L20/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/yI10X/dJMb8PGqNpH/3JMfOECyPUYr8NnLALFZH1/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628');&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;Next.js Docs | Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Welcome to the Next.js Documentation.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&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;p data-ke-size=&quot;size16&quot;&gt;이제부터 본격 Page Router와 App Router 차이를 본격 비교해보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;499&quot; data-start=&quot;482&quot; 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;color: #9d9d9d;&quot;&gt;(나도 이해했는데말야 ^^)&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;517&quot; data-start=&quot;501&quot; data-ke-size=&quot;size20&quot;&gt;Page Router&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1769578093560&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pages/
 └─ index.tsx        &amp;rarr; /
 └─ about.tsx        &amp;rarr; /about
 └─ posts/[id].tsx   &amp;rarr; /posts/1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;669&quot; data-start=&quot;624&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;639&quot; data-start=&quot;624&quot;&gt;pages 폴더 기준&lt;/li&gt;
&lt;li data-end=&quot;650&quot; data-start=&quot;640&quot;&gt;파일 = 라우트&lt;/li&gt;
&lt;li data-end=&quot;669&quot; data-start=&quot;651&quot;&gt;구조가 직관적이라 배우기 쉬움&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;686&quot; data-start=&quot;671&quot; data-ke-size=&quot;size20&quot;&gt;App Router&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1769578113978&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;app/
 └─ page.tsx         &amp;rarr; /
 └─ about/page.tsx   &amp;rarr; /about
 └─ posts/[id]/page.tsx &amp;rarr; /posts/1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;864&quot; data-start=&quot;794&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;809&quot; data-start=&quot;794&quot;&gt;&lt;b&gt;폴더 기반 라우팅&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;844&quot; data-start=&quot;810&quot;&gt;page.tsx, layout.tsx 역할이 분리됨&lt;/li&gt;
&lt;li data-end=&quot;864&quot; data-start=&quot;845&quot;&gt;처음엔 낯설지만 구조화가 잘 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;889&quot; data-start=&quot;871&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;889&quot; data-start=&quot;871&quot; data-ke-size=&quot;size23&quot;&gt;레이아웃 처리 방식&lt;/h3&gt;
&lt;h4 data-end=&quot;907&quot; data-start=&quot;891&quot; data-ke-size=&quot;size20&quot;&gt;Page Router&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;968&quot; data-start=&quot;908&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;940&quot; data-start=&quot;908&quot;&gt;공통 레이아웃을 바깥 _app.tsx에서 직접 감싸야 함&lt;/li&gt;
&lt;li data-end=&quot;968&quot; data-start=&quot;941&quot;&gt;페이지 이동 시 &lt;b&gt;레이아웃도 다시 렌더링&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;985&quot; data-start=&quot;970&quot; data-ke-size=&quot;size20&quot;&gt;App Router&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1769578315405&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;app/
 └─ layout.tsx
 └─ page.tsx&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1104&quot; data-start=&quot;1031&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1061&quot; data-start=&quot;1031&quot;&gt;layout.tsx로 &lt;b&gt;중첩 레이아웃&lt;/b&gt; 가능&lt;/li&gt;
&lt;li data-end=&quot;1084&quot; data-start=&quot;1062&quot;&gt;페이지 이동 시 &lt;b&gt;레이아웃 유지&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1104&quot; data-start=&quot;1085&quot;&gt;대시보드, 탭 구조에 매우 유리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1135&quot; data-start=&quot;1106&quot; data-ke-size=&quot;size16&quot;&gt;페이지단위로 끊어서 레이아웃을 커스텀할수있기에 이건 App Router가 압승인거같다&lt;/p&gt;
&lt;h3 data-end=&quot;1159&quot; data-start=&quot;1142&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;1159&quot; data-start=&quot;1142&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;데이터 패칭 방식&lt;/h3&gt;
&lt;h4 data-end=&quot;1177&quot; data-start=&quot;1161&quot; data-ke-size=&quot;size20&quot;&gt;Page Router&lt;/h4&gt;
&lt;pre id=&quot;code_1769578487008&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;export async function getServerSideProps() {
  return { props: {} }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1238&quot; data-start=&quot;1178&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1200&quot; data-start=&quot;1178&quot;&gt;getServerSideProps&lt;/li&gt;
&lt;li data-end=&quot;1219&quot; data-start=&quot;1201&quot;&gt;getStaticProps&lt;/li&gt;
&lt;li data-end=&quot;1238&quot; data-start=&quot;1220&quot;&gt;getStaticPaths&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요런 전용 API로 비동기처리를 해야해서 기본적으로 암기해야할것들이 좀 있는 반면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;1336&quot; data-start=&quot;1321&quot; data-ke-size=&quot;size20&quot;&gt;App Router&lt;/h4&gt;
&lt;pre id=&quot;code_1769578494355&quot; class=&quot;qml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const data = await fetch(url, { cache: 'no-store' })&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1402&quot; data-start=&quot;1337&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1358&quot; data-start=&quot;1337&quot;&gt;&lt;b&gt;컴포넌트에서 직접 fetch&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1385&quot; data-start=&quot;1359&quot;&gt;기본이 &lt;b&gt;Server Component&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1402&quot; data-start=&quot;1386&quot;&gt;캐싱 전략을 옵션으로 제어&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;무려 비동기처리를 컴포넌트 내에서 실행시킬수있으며 &lt;u&gt;캐싱도 옵션&lt;/u&gt;으로 온오프가 가능하다..!!&lt;/div&gt;
&lt;h3 data-end=&quot;1533&quot; data-start=&quot;1502&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;1533&quot; data-start=&quot;1502&quot; data-ke-size=&quot;size23&quot;&gt;Server / Client 컴포넌트 차이&lt;/h3&gt;
&lt;h4 data-end=&quot;1551&quot; data-start=&quot;1535&quot; data-ke-size=&quot;size20&quot;&gt;Page Router&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1591&quot; data-start=&quot;1552&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1575&quot; data-start=&quot;1552&quot;&gt;모든 컴포넌트가 &lt;u&gt;기본적으로 Client&lt;/u&gt;&lt;/li&gt;
&lt;li data-end=&quot;1591&quot; data-start=&quot;1576&quot;&gt;SSR은 &amp;ldquo;페이지 단위&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;1608&quot; data-start=&quot;1593&quot; data-ke-size=&quot;size20&quot;&gt;App Router&lt;/h4&gt;
&lt;pre id=&quot;code_1769578699160&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'use client'&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1651&quot; data-start=&quot;1609&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1635&quot; data-start=&quot;1609&quot;&gt;기본은 &lt;b&gt;Server Component&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1651&quot; data-start=&quot;1636&quot;&gt;Client 필요할 때만 위 코드를 상단에 적어주면 끝 (대박)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-end=&quot;1695&quot; data-start=&quot;1676&quot; data-ke-size=&quot;size16&quot;&gt;필요할때만 클라이언트 컴포넌트를 쓸수있기때문에 JS번들 용량도 줄이고 성능도 올릴수있다.&lt;/p&gt;
&lt;h3 data-end=&quot;1722&quot; data-start=&quot;1702&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;1722&quot; data-start=&quot;1702&quot; data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&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;u&gt;러닝커브가 낮은 Page Router&lt;/u&gt;도 충분하지만..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;성능이 중요&lt;/u&gt;한 프로젝트에선 &lt;b&gt;App Router&lt;/b&gt;를 적극 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 Next도 App Router를 중심으로 지원업뎃이 될것이기때문에 이왕이면 최신스택을 고르는게 좋겠다!&lt;/p&gt;</description>
      <category>NEXT</category>
      <author>lyla-bae</author>
      <guid isPermaLink="true">https://lyla-bae.tistory.com/40</guid>
      <comments>https://lyla-bae.tistory.com/40#entry40comment</comments>
      <pubDate>Wed, 28 Jan 2026 14:51:30 +0900</pubDate>
    </item>
    <item>
      <title>쉬워도 이렇게 쉬울수없는 Zustand</title>
      <link>https://lyla-bae.tistory.com/39</link>
      <description>&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;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UshDC/dJMcaaKPbsi/SCE974QKI4hazkG0aJCY80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UshDC/dJMcaaKPbsi/SCE974QKI4hazkG0aJCY80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UshDC/dJMcaaKPbsi/SCE974QKI4hazkG0aJCY80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUshDC%2FdJMcaaKPbsi%2FSCE974QKI4hazkG0aJCY80%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;400&quot; height=&quot;400&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;store와 create만 기억하면 됨&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://zustand-demo.pmnd.rs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://zustand-demo.pmnd.rs/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768801683924&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;Zustand&quot; data-og-description=&quot;&quot; data-og-host=&quot;zustand-demo.pmnd.rs&quot; data-og-source-url=&quot;https://zustand-demo.pmnd.rs/&quot; data-og-url=&quot;https://zustand-demo.pmnd.rs/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://zustand-demo.pmnd.rs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://zustand-demo.pmnd.rs/&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;Zustand&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;zustand-demo.pmnd.rs&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zustand는 크기가 작아서 가볍고 빠르며 확장성이 좋은 전역 상태 관리 라이브러리다. &lt;br /&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;pre id=&quot;code_1768801863121&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { create } from 'zustand'
export const use이름Store = create((set, get) =&amp;gt; { //set은 상태 업데이트할 때, get은 현재 상태 가져올 때 씀
  return {
    상태: 초깃값,
    액션: 함수
  }
})&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;Zustand에서는 &lt;b&gt;create&lt;/b&gt;라는 함수로 store를 만든다. 그리고 이&amp;nbsp;&lt;b&gt;store를 기반&lt;/b&gt;으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store가 뭐냐면, 간단히 말해서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;애플리케이션의 상태를 저장하는 중앙 데이터 저장소&lt;/u&gt;라고 보면 된다.&lt;br /&gt;쇼핑몰로 치면 창고 같은 개념이랄까. 필요한 물건들을 한곳에 모아두고 필요할 때 꺼내 쓰는 거지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Zustand 쓸 때는&lt;u&gt; 무조건 create 함수로 store 생성하는 것부터 시작&lt;/u&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: #9d9d9d;&quot;&gt;&lt;b&gt;참고자료&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yunicornlab.tistory.com/100&quot;&gt;https://yunicornlab.tistory.com/100&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768802843493&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;zustand가 뭐야 어떻게 써&quot; data-og-description=&quot;1. Zustand란?크기가 작아서 가볍고 빠르며 확장성이 좋은 전역 상태 관리 라이브러리다.&amp;nbsp;Zustand는 독일어로 &amp;quot;상태&amp;quot;라는 뜻이다. (독일어다보니 발음은 쭈스탄트 정도로 읽으면 된다.)zustand는 store를&quot; data-og-host=&quot;yunicornlab.tistory.com&quot; data-og-source-url=&quot;https://yunicornlab.tistory.com/100&quot; data-og-url=&quot;https://yunicornlab.tistory.com/100&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zXg2P/dJMb8UHIXU7/GDH8ZwuyzcQVrNN35YkTlk/img.png?width=800&amp;amp;height=468&amp;amp;face=0_0_800_468,https://scrap.kakaocdn.net/dn/iaCjV/dJMb8WMjlXD/jVKjG60zVGc01eqHYHNyu1/img.png?width=800&amp;amp;height=468&amp;amp;face=0_0_800_468,https://scrap.kakaocdn.net/dn/KmpG9/dJMb8SpBIFl/i5JoeMW25bGHggi412LwAK/img.png?width=3170&amp;amp;height=1856&amp;amp;face=0_0_3170_1856&quot;&gt;&lt;a href=&quot;https://yunicornlab.tistory.com/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yunicornlab.tistory.com/100&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zXg2P/dJMb8UHIXU7/GDH8ZwuyzcQVrNN35YkTlk/img.png?width=800&amp;amp;height=468&amp;amp;face=0_0_800_468,https://scrap.kakaocdn.net/dn/iaCjV/dJMb8WMjlXD/jVKjG60zVGc01eqHYHNyu1/img.png?width=800&amp;amp;height=468&amp;amp;face=0_0_800_468,https://scrap.kakaocdn.net/dn/KmpG9/dJMb8SpBIFl/i5JoeMW25bGHggi412LwAK/img.png?width=3170&amp;amp;height=1856&amp;amp;face=0_0_3170_1856');&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;zustand가 뭐야 어떻게 써&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. Zustand란?크기가 작아서 가볍고 빠르며 확장성이 좋은 전역 상태 관리 라이브러리다.&amp;nbsp;Zustand는 독일어로 &quot;상태&quot;라는 뜻이다. (독일어다보니 발음은 쭈스탄트 정도로 읽으면 된다.)zustand는 store를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yunicornlab.tistory.com&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;h2 data-ke-size=&quot;size26&quot;&gt;진짜 강점은 미들웨어&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;&lt;u&gt;미들웨어는 store에 추가 기능을 끼워넣을 수 있는 방법&lt;/u&gt;이다. &lt;br /&gt;Redux를 써봤다면 Redux 미들웨어랑 비슷한 개념이라고 보면 되는데, Zustand는 이게 훨씬 간단하고 직관적이다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NR5A3/dJMcabiFub0/4W9RJwrhRgrpZliMTy5Cok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NR5A3/dJMcabiFub0/4W9RJwrhRgrpZliMTy5Cok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NR5A3/dJMcabiFub0/4W9RJwrhRgrpZliMTy5Cok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNR5A3%2FdJMcabiFub0%2F4W9RJwrhRgrpZliMTy5Cok%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;800&quot; height=&quot;450&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&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;Zustand에서 제공하는 주요 미들웨어들을 간단히 소개하면 이렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;combine&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;br /&gt;&lt;/span&gt;: 여러 개의 store를 합쳐서 하나로 만들어준다. 상태를 모듈별로 나눠서 관리하다가 나중에 합칠 때 쓴다.&lt;b&gt;&lt;br /&gt;immer&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;: 불변성 관리를 편하게 해준다. 복잡한 객체나 배열 상태를 업데이트할 때 spread 연산자 남발 안 해도 되고, 그냥 일반 객체 다루듯이 코드 짜면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;subscribeWithSelector&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;: 특정 상태 값만 선택적으로 구독할 수 있게 해준다. 전체 store가 아니라 필요한 부분만 감시하고 싶을 때 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;persist&lt;/b&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;b&gt;devtools&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: Redux DevTools를 연동해준다. 크롬 확장 프로그램으로 상태 변화를 추적하고 디버깅할 수 있어서 개발할 때 엄청 편하다.&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;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;pre id=&quot;code_1768801408883&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

const useStore = create(
  devtools(
    persist(
      immer((set) =&amp;gt; ({
        count: 0,
        user: { name: '', age: 0 },
        increase: () =&amp;gt; set((state) =&amp;gt; { state.count += 1 }),
        setUser: (name, age) =&amp;gt; set((state) =&amp;gt; {
          state.user.name = name;
          state.user.age = age;
        }),
      })),
      { name: 'my-store' }
    )
  )
);&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;b&gt;immer&lt;/b&gt;가 제일 안쪽에 있고, &lt;b&gt;persist&lt;/b&gt;로 감싸고, 다시 &lt;b&gt;devtools&lt;/b&gt;로 감싼 구조다. &lt;br /&gt;실행 순서로 보면 immer가 먼저 적용되고, 그 다음 persist, 마지막으로 devtools가 적용된다고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;persist 같은 경우는 두 번째 인자로 옵션 객체를 받는데, 여기서 name은 로컬스토리지에 저장될 키 이름이다.&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;h4 data-ke-size=&quot;size20&quot;&gt;마무리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zustand의 미들웨어는 복잡한 설정 없이 괄호 몇 개로 강력한 기능들을 추가할 수 있다는 게 진짜 장점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redux처럼 복잡한&amp;nbsp;&lt;u&gt;보일러플레이트&lt;/u&gt;를 만날 필요도 없고, 그냥 필요한 미들웨어 import해서 감싸주기만 하면 된다.&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;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 785px; top: 4112.02px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>REACT</category>
      <category>zustand</category>
      <category>상태관리라이브러리</category>
      <author>lyla-bae</author>
      <guid isPermaLink="true">https://lyla-bae.tistory.com/39</guid>
      <comments>https://lyla-bae.tistory.com/39#entry39comment</comments>
      <pubDate>Mon, 19 Jan 2026 14:59:21 +0900</pubDate>
    </item>
    <item>
      <title>더 나은 UX를 위해 개발자가 할수있는 방법은?</title>
      <link>https://lyla-bae.tistory.com/38</link>
      <description>&lt;blockquote data-ke-size=&quot;size23&quot; data-ke-style=&quot;style1&quot;&gt;&amp;ldquo;Make waiting times tolerable.&lt;br /&gt;Provide feedback during delays.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;1198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4y47x/dJMcagxs0WV/ignRMG7jUAXJCYkTSW3Ek0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4y47x/dJMcagxs0WV/ignRMG7jUAXJCYkTSW3Ek0/img.png&quot; data-alt=&quot;도널드 노먼의 UX 원칙 중 하나는 
모든 행동에는 적절한 피드백이 뒤따라야 한다는 것이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4y47x/dJMcagxs0WV/ignRMG7jUAXJCYkTSW3Ek0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4y47x%2FdJMcagxs0WV%2FignRMG7jUAXJCYkTSW3Ek0%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;300&quot; height=&quot;265&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;1198&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;도널드 노먼의 UX 원칙 중 하나는 
모든 행동에는 적절한 피드백이 뒤따라야 한다는 것이다.&lt;/figcaption&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;UX에서 중요한 것은 기다림을 없애는 것이 아니라,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;u&gt;기다림을 경험하게 만드는 방식&lt;/u&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;디자인을 공부할때 UX는 기획자와 디자이너만의 영역이라 생각했는데 &lt;br /&gt;생각해보면 개발자도 충분히 기여할수있는 방법이 많다 &lt;span style=&quot;color: #9d9d9d;&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;b&gt;성능최적화&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;코드스플리팅&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하나의 거대한 JS 번들을 여러 조각으로 나누고, 필요한 코드만 로드하는 전략&lt;/b&gt;을 말한다.&lt;/p&gt;
&lt;p data-end=&quot;331&quot; data-start=&quot;217&quot; data-ke-size=&quot;size16&quot;&gt;React와 같은 SPA 구조 라이브러리/프레임워크에서는&lt;/p&gt;
&lt;p data-end=&quot;331&quot; data-start=&quot;217&quot; data-ke-size=&quot;size16&quot;&gt;보통 모든 페이지와 기능을 하나의 번들로 묶어 초기 로딩 시점에 한 번에 내려받는다.&lt;br /&gt;이 방식은 구현은 단순하지만, 사용자가 아직 사용하지도 않을 코드까지 함께 다운하게 만든다.&lt;/p&gt;
&lt;p data-end=&quot;331&quot; data-start=&quot;217&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;857&quot; data-start=&quot;824&quot; data-ke-size=&quot;size23&quot;&gt;React.lazy 와 Suspense 함께쓰기&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768377448098&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { lazy, Suspense } from 'react'

const ChartPage = lazy(() =&amp;gt; import('./ChartPage'))

function App() {
  return (
    &amp;lt;Suspense fallback={&amp;lt;Loading /&amp;gt;}&amp;gt;
      &amp;lt;ChartPage /&amp;gt;
    &amp;lt;/Suspense&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChartPage는 당장 필요한 코드가 아니기 때문에&lt;b&gt; lazy로 초기 번들에 포함되지 않게&lt;/b&gt; 만들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 렌더링 시점에 JS 파일을 네트워크로 가져오는 동안 &lt;b&gt;Suspense로 fallback (대체 UI)로 상태를 전달&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;여기서의 성능을 최적화는 lazy 뿐이지만 노먼 선생님이 말한대로 기다리는 동안 아무 피드백이 없으면 사용자는 답답해질것이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 UX개선과 lazy의 비동기 처리를 위해 Suspense를 함께 쓰는 전략을 고르는것이다.&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;pre id=&quot;code_1768377827451&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Home = lazy(() =&amp;gt; import('./pages/Home'))
const Detail = lazy(() =&amp;gt; import('./pages/Detail'))&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;요렇게 lazy를 통해 &lt;b&gt;페이지 단위로 import&lt;/b&gt; 시켜주면 초기로딩 비용도 줄이고 &lt;br /&gt;사용자도 한번에 한페이지만 사용할수있으니 일석이조!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&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;리액트에선 state props가 바뀌면 컴포넌트가 리렌더링 되기때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이과정에서 state가 바뀌지않았는데 비용이 큰 계산이 로직에 의해 매번 실행된다거나 과한 &lt;b&gt;props drilling&lt;/b&gt;이 있다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;React.memo, useMemo, useCallback&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;a href=&quot;https://lyla-bae.tistory.com/31&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.12.29 - [REACT] - Virtual DOM부터 Memoization까지&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768378252834&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;Virtual DOM부터 Memoization까지&quot; data-og-description=&quot;기나긴 2주간의 방학이 끝나고 다시 수업이 재개되었다.. 그리고 오랜만의 디깅타임~!!!오늘은 가상돔과 메모이제이션으로 최적화할 수 있는 방법들을 두루 배웠다.역시나 면접 단골 질문으로 &quot; data-og-host=&quot;lyla-bae.tistory.com&quot; data-og-source-url=&quot;https://lyla-bae.tistory.com/31&quot; data-og-url=&quot;https://lyla-bae.tistory.com/31&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/czkYtg/dJMb86nQTyK/6T1UGzONHnB52yVtgAKruk/img.jpg?width=480&amp;amp;height=362&amp;amp;face=0_0_480_362,https://scrap.kakaocdn.net/dn/hN3oB/dJMb9c9rjJx/4jVV76hy1kjGj4IWCbFpI1/img.jpg?width=480&amp;amp;height=362&amp;amp;face=0_0_480_362,https://scrap.kakaocdn.net/dn/mNuNa/dJMb83kmjEg/mGIZRHOYaG7o2lFCrKxoS0/img.jpg?width=480&amp;amp;height=362&amp;amp;face=0_0_480_362&quot;&gt;&lt;a href=&quot;https://lyla-bae.tistory.com/31&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://lyla-bae.tistory.com/31&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/czkYtg/dJMb86nQTyK/6T1UGzONHnB52yVtgAKruk/img.jpg?width=480&amp;amp;height=362&amp;amp;face=0_0_480_362,https://scrap.kakaocdn.net/dn/hN3oB/dJMb9c9rjJx/4jVV76hy1kjGj4IWCbFpI1/img.jpg?width=480&amp;amp;height=362&amp;amp;face=0_0_480_362,https://scrap.kakaocdn.net/dn/mNuNa/dJMb83kmjEg/mGIZRHOYaG7o2lFCrKxoS0/img.jpg?width=480&amp;amp;height=362&amp;amp;face=0_0_480_362');&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;Virtual DOM부터 Memoization까지&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;기나긴 2주간의 방학이 끝나고 다시 수업이 재개되었다.. 그리고 오랜만의 디깅타임~!!!오늘은 가상돔과 메모이제이션으로 최적화할 수 있는 방법들을 두루 배웠다.역시나 면접 단골 질문으로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;lyla-bae.tistory.com&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;h3 data-ke-size=&quot;size23&quot;&gt;메모제이션 실습 문제로 마무리!&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실전 같이 성능 최적화하기 try-first&lt;/h4&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;React.memo, useMemo, useCallback을 &lt;b&gt;조합해서 최적화하는 고급 실습입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;실무에서 자주 맞닥뜨리는 패턴이고, 특히 리스트 &amp;amp; 자식 컴포넌트가 있을 때 성능 최적화가 중요해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;현재 우리가 개발 중인 프로젝트에서 &lt;b&gt;게시글 리스트를 렌더링하는 부모 컴포넌트&lt;/b&gt;가 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;각 게시글(자식 컴포넌트)은 props에 따라 무거운 연산을 하거나 버튼 클릭 이벤트&lt;/b&gt;를 가지고 있는 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768371143841&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// PostItem.tsx
const PostItem = ({ title, content, onClick }) =&amp;gt; {
  console.log(`  Rendering: ${title}`);

  // 무거운 연산 (예시)
  const wordCount = content
    .split(&quot; &quot;)
    .reduce((acc, word) =&amp;gt; acc + word.length, 0);

  return (
    &amp;lt;div style={{ border: &quot;1px solid gray&quot;, marginBottom: &quot;10px&quot; }}&amp;gt;
      &amp;lt;h4&amp;gt;{title}&amp;lt;/h4&amp;gt;
      &amp;lt;p&amp;gt;{content}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;
        &amp;lt;b&amp;gt;Word Count:&amp;lt;/b&amp;gt; {wordCount}
      &amp;lt;/p&amp;gt;
      &amp;lt;button onClick={onClick}&amp;gt;Like&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

// App.tsx
import { useState } from &quot;react&quot;;

const DUMMY_POSTS = [
  { id: 1, title: &quot;First Post&quot;, content: &quot;This is the first post content.&quot; },
  {
    id: 2,
    title: &quot;Second Post&quot;,
    content: &quot;Another post with more words to count.&quot;,
  },
  { id: 3, title: &quot;Third Post&quot;, content: &quot;Short one.&quot; },
];

export default function App() {
  const [likes, setLikes] = useState(0);
  const [dummy, setDummy] = useState(false);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Total Likes: {likes}&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setDummy(!dummy)}&amp;gt;Re-render Parent&amp;lt;/button&amp;gt;

      {DUMMY_POSTS.map((post) =&amp;gt; (
        &amp;lt;PostItem
          key={post.id}
          title={post.title}
          content={post.content}
          onClick={() =&amp;gt; setLikes(likes + 1)}
        /&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;위 코드를 실행시켜보면 모든 버튼을 누르면 모든 자식 컴포넌트들이 리렌더링됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&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;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;div id=&quot;code_1768375929696&quot; data-ke-type=&quot;html&quot; data-source=&quot;&amp;lt;iframe src=&amp;quot;https://codesandbox.io/embed/fnzlsh?view=preview&amp;amp;module=%2Fsrc%2FPostItem.jsx&amp;quot;
     style=&amp;quot;width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;&amp;quot;
     title=&amp;quot;cool-phoebe&amp;quot;
     allow=&amp;quot;accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking&amp;quot;
     sandbox=&amp;quot;allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts&amp;quot;
   &amp;gt;&amp;lt;/iframe&amp;gt;&quot;&gt;&lt;iframe src=&quot;https://codesandbox.io/embed/fnzlsh?view=preview&amp;amp;module=%2Fsrc%2FPostItem.jsx&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;풀이&lt;/h3&gt;
&lt;pre id=&quot;code_1768378455424&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// PostItem.jsx
import { memo, useMemo } from &quot;react&quot;;

export const PostItem = memo(({ title, content, onClick }) =&amp;gt; {
  // props 비교해 메모제이션
  console.log(`  Rendering: ${title}`);

  // 무거운 연산이 반복되므로 useMemo로 content,title이 바뀔때만 다시계산
  const wordCount = useMemo(() =&amp;gt; {
    return content.split(&quot; &quot;).reduce((acc, word) =&amp;gt; acc + word.length, 0);
  }, [content, title]);

  return (
    &amp;lt;div style={{ border: &quot;1px solid gray&quot;, marginBottom: &quot;10px&quot; }}&amp;gt;
      &amp;lt;h4&amp;gt;{title}&amp;lt;/h4&amp;gt;
      &amp;lt;p&amp;gt;{content}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;
        &amp;lt;b&amp;gt;Word Count:&amp;lt;/b&amp;gt; {wordCount}
      &amp;lt;/p&amp;gt;
      &amp;lt;button onClick={onClick}&amp;gt;Like&amp;lt;/button&amp;gt;
    &amp;lt;/div&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;p data-ke-size=&quot;size16&quot;&gt;1. PostItem 컴포넌트의 props들부터 불필요하게 리렌더링 되고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 React.memo로 props를 비교해 메모제이션 시켜주고&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;2. wordCount 란 무거운 연산 또한 content와 title이 바뀔때만 다시 계산되도록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useMemo로 메모제이션 시켜주었다.&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;pre id=&quot;code_1768374753632&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.tsx
import { useCallback, useState } from &quot;react&quot;;
import { PostItem } from &quot;./PostItem&quot;;
const DUMMY_POSTS = [
  { id: 1, title: &quot;First Post&quot;, content: &quot;This is the first post content.&quot; },
  {
    id: 2,
    title: &quot;Second Post&quot;,
    content: &quot;Another post with more words to count.&quot;,
  },
  { id: 3, title: &quot;Third Post&quot;, content: &quot;Short one.&quot; },
];

export default function App() {
  const [likes, setLikes] = useState(0);
  const [dummy, setDummy] = useState(false);

  //매번 실행하던 함수를 useCallback 빈배열(의존성배열)로 한번만 생성할수있게
  const handleLike = useCallback(() =&amp;gt; {
    setLikes((prev) =&amp;gt; prev + 1);
  }, []);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Total Likes: {likes}&amp;lt;/h2&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setDummy(!dummy)}&amp;gt;Re-render Parent&amp;lt;/button&amp;gt;

      {DUMMY_POSTS.map((post) =&amp;gt; (
        &amp;lt;PostItem
          key={post.id}
          title={post.title}
          content={post.content}
          onClick={handleLike}
        /&amp;gt;
      ))}
    &amp;lt;/div&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;3. 버튼의 이벤트 핸들러도 이전코드엔 App 컴포넌트가 실행될때마다 내용이 같아도 &lt;u&gt;매번 생성&lt;/u&gt;되기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요거또한 &lt;b&gt;useCallback의 의존성배열&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;</description>
      <category>REACT</category>
      <category>메모제이션</category>
      <category>성능최적화</category>
      <category>코드스플리팅</category>
      <author>lyla-bae</author>
      <guid isPermaLink="true">https://lyla-bae.tistory.com/38</guid>
      <comments>https://lyla-bae.tistory.com/38#entry38comment</comments>
      <pubDate>Wed, 14 Jan 2026 16:06:24 +0900</pubDate>
    </item>
    <item>
      <title>테스트를 먼저하고 개발을 한다고...? TDD와 Jest</title>
      <link>https://lyla-bae.tistory.com/37</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;오랜만에 돌아온 아티클 미션!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD와 Jest에 대해 배우는 시간이였다.&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;TDD란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;테스트를 먼저 작성 후 그 테스트를 통과하도록 개발을 하는 개발 프로세스를 말한다.&lt;/u&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;정말 TDD를 하는 회사가 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진짜 궁금해서 당장 사람인,잡코리아에 들어가 JD를 찾아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mnmrr/dJMcahQGe34/JEK3hp1yUGyOxgdDBkd6a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mnmrr/dJMcahQGe34/JEK3hp1yUGyOxgdDBkd6a1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;400&quot; data-filename=&quot;스크린샷 2026-01-13 오후 3.04.43.png&quot; style=&quot;width: 40.9951%; margin-right: 10px;&quot; data-widthpercent=&quot;41.48&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mnmrr/dJMcahQGe34/JEK3hp1yUGyOxgdDBkd6a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmnmrr%2FdJMcahQGe34%2FJEK3hp1yUGyOxgdDBkd6a1%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;858&quot; height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyIuhz/dJMcafk1NOq/hd2dDlMYKqg8ZblquyP8c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyIuhz/dJMcafk1NOq/hd2dDlMYKqg8ZblquyP8c1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;302&quot; data-filename=&quot;스크린샷 2026-01-13 오후 3.05.05.png&quot; style=&quot;width: 57.8421%;&quot; data-widthpercent=&quot;58.52&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyIuhz/dJMcafk1NOq/hd2dDlMYKqg8ZblquyP8c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyIuhz%2FdJMcafk1NOq%2Fhd2dDlMYKqg8ZblquyP8c1%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;914&quot; height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-13 오후 3.07.01.png&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;1178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kYDgg/dJMcaajIv0k/ij8sTx0zjTW116N0GNcFDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kYDgg/dJMcaajIv0k/ij8sTx0zjTW116N0GNcFDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kYDgg/dJMcaajIv0k/ij8sTx0zjTW116N0GNcFDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYDgg%2FdJMcaajIv0k%2Fij8sTx0zjTW116N0GNcFDK%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;1342&quot; height=&quot;1178&quot; data-filename=&quot;스크린샷 2026-01-13 오후 3.07.01.png&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;1178&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;정말 개발 쉽지않구나,,아직은 테스트를 먼저 설계하는 게 익숙하지 않아서 어렵게 느껴졌다.&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;a href=&quot;https://youtu.be/W-fyyAHAqSU?si=DDKTGgBUBDXcwhto&quot;&gt;https://youtu.be/W-fyyAHAqSU?si=DDKTGgBUBDXcwhto&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=W-fyyAHAqSU&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/6M5Ga/dJMb84p11j8/4Q0gFI8BIhRH60i7YttTGK/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360,https://scrap.kakaocdn.net/dn/M9lzQ/dJMb87fZzDq/HbAPYFqLXPOGkhz6mwLy0K/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;테스트 귀찮은데 꼭 해야할까? [Jest(자바스크립트 테스트) 강의 맛보기]&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/W-fyyAHAqSU&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;진짜 TDD가 필요한건지 궁금해서 보았는데 한번씩 시청해보시길&lt;/figcaption&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TDD의 종류&lt;/h4&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;1. Unit Test : 단위테스트 (Jest)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Integration Test : 통합테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 컴포넌트가 함께 잘 동작하는지 테스트한다. Jest + React Testing Library로 가능하다&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;3. E2E Test (End to End):&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 사용자 플로우를 테스트한다. 실제 사용자처럼 브라우저에서 클릭하고 입력하는 &lt;u&gt;UI Test도 포함&lt;/u&gt;되어있는 테스트이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첨에 UI Test를 보고 내가 아는 그 UT(Usability&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Test : 사용성 테스트)인가 했는데 전혀 다른개념이다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;여기서의 UI Test는 테스트를 자동화시켜 &lt;b&gt;기능이 제대로 작동하는지에 목적&lt;/b&gt;을 둔다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Usability Test가 기획/디자이너들 포폴에 쓴다면 개발자 포폴에 쓰는 테스트는 UI Test가 적절하다고 볼수있겠따,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;(이것도 모르고 나는 지난 사이드프로젝트때 Usability Test를 하자고 고집피웠다,, 결론적으론 못했지만)&lt;/s&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yvdeV/dJMcaiaYZLT/Hl3M6PKTkKHa25hOTulzik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yvdeV/dJMcaiaYZLT/Hl3M6PKTkKHa25hOTulzik/img.png&quot; data-alt=&quot;출처 : https://velog.io/@nueob/%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%94%BC%EB%9D%BC%EB%AF%B8%EB%93%9C%EB%9E%80&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yvdeV/dJMcaiaYZLT/Hl3M6PKTkKHa25hOTulzik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyvdeV%2FdJMcaiaYZLT%2FHl3M6PKTkKHa25hOTulzik%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;500&quot; height=&quot;291&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://velog.io/@nueob/%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%94%BC%EB%9D%BC%EB%AF%B8%EB%93%9C%EB%9E%80&lt;/figcaption&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그래서 Jest가 뭔데..?&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnjUlB/dJMcaaYkdEP/KdmHV57SdIRjKmygXGIW51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnjUlB/dJMcaaYkdEP/KdmHV57SdIRjKmygXGIW51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnjUlB/dJMcaaYkdEP/KdmHV57SdIRjKmygXGIW51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnjUlB%2FdJMcaaYkdEP%2FKdmHV57SdIRjKmygXGIW51%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;1280&quot; height=&quot;525&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;525&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;설명이 좀 늦었지만 Jest란 자바스크립트 환경에서 테스트를 할수있는 &lt;b&gt;테스트 프레임워크&lt;/b&gt;이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크이기 때문에 당연 node 설치가 필요하다능&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7ihzr/dJMcahC71Jr/83rkJFz1U0FM9zYkKpsVMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7ihzr/dJMcahC71Jr/83rkJFz1U0FM9zYkKpsVMK/img.png&quot; data-alt=&quot;출처 : https://velog.io/@bami/Javascript-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-Jest&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7ihzr/dJMcahC71Jr/83rkJFz1U0FM9zYkKpsVMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7ihzr%2FdJMcahC71Jr%2F83rkJFz1U0FM9zYkKpsVMK%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;1280&quot; height=&quot;500&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://velog.io/@bami/Javascript-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-Jest&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(자스기준) 테스트 프레임워크도 종류가 몇있는거 같은데 현재로선 Jest가 가장 점유율이 높고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그외 Jasmine, mocha, (강사님 피셜) Vitest 등 다양한 프레임워크들이 나오는거같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jest는 기술적으로 특별해서라기보다는, 프론트엔드 생태계에서 사실상 표준처럼 사용되고 있어 자연스럽게 선택되는 도구라고 느꼈다&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;그리고 리액트 컴포넌트를 테스트하기위해 Testing Library 같은것도 존재한다,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 사용자 관점에서 컴포넌트를 다루기 쉽게 도와주는 도구이기 때문에&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;npm 설치를 알아서 잘 하도록 하자&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;어쨋든 Jest도 프레임워크이기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;그리고 당연히 문법이 따로 존재한다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 기능, 그리고 다양한 메소드들이 있다 ^^...(wow)&lt;/p&gt;
&lt;pre id=&quot;code_1768286488968&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;expect(실제값).toBe(예상값);&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;Jest의 기본문법인데 저&lt;b&gt; 예상값앞에 쓰는 메소드들을 Matcher&lt;/b&gt;라고 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jest를 사용하려면 Matcher에 대한 이해가 꼭 필요하다!&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;h4 data-ke-size=&quot;size20&quot;&gt;JavaScript Top3&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. toBe()&lt;/b&gt;&lt;br /&gt;-&amp;nbsp;===&amp;nbsp;비교&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- 숫자, 문자열, boolean 같은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Primitive(원시,기본형)한 것만 비교&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;* toEqual와 혼돈 주의&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2.&amp;nbsp;toEqual()&lt;/b&gt;&lt;br /&gt;-&amp;nbsp;객체,&amp;nbsp;배열&amp;nbsp;같은&amp;nbsp;참조&amp;nbsp;타입&amp;nbsp;비교&lt;br /&gt;-&amp;nbsp;내용이&amp;nbsp;같은지&amp;nbsp;체크&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. toBeNull() / toBeUndefined() / toBeTruthy() / toBeFalsy()&lt;/b&gt;&lt;br /&gt;null,&amp;nbsp;undefined,&amp;nbsp;참/거짓&amp;nbsp;체크&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;React Top3&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. toBeInTheDocument()&lt;/b&gt;&lt;br /&gt;DOM에&amp;nbsp;요소가&amp;nbsp;존재하는지&amp;nbsp;확인&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. toHaveTextContent()&lt;/b&gt;&lt;br /&gt;요소의&amp;nbsp;텍스트&amp;nbsp;내용&amp;nbsp;확인&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. toHaveValue()&lt;/b&gt;&lt;br /&gt;input,&amp;nbsp;textarea의&amp;nbsp;값&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 style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;...정도가 있겠다. 당장은 못외우겠지만 일단 그렇다&lt;/span&gt;&lt;/p&gt;
&lt;p 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;a href=&quot;https://think0wise.tistory.com/100&quot;&gt;https://think0wise.tistory.com/100&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768289994907&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;Jest - Matcher란?&quot; data-og-description=&quot;여러분들은 Jest로 테스트 코드를 작성할 때 다양한 Matcher를 사용하고 계시나요? 오늘은 Jest에서 테스트 코드를 작성할 때 자주 사용하는 Matcher에 대해서 알아보겠습니다. 1. toBe( value ) 기본형을 &quot; data-og-host=&quot;think0wise.tistory.com&quot; data-og-source-url=&quot;https://think0wise.tistory.com/100&quot; data-og-url=&quot;https://think0wise.tistory.com/100&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gLfa6/dJMb87fZzCH/IraXsXIDqiJVHTWs6sm4K0/img.png?width=800&amp;amp;height=292&amp;amp;face=0_0_800_292,https://scrap.kakaocdn.net/dn/dAMebU/dJMb9bvVz1w/UiiVsyWBKKKlXKciKTwuwk/img.png?width=800&amp;amp;height=292&amp;amp;face=0_0_800_292,https://scrap.kakaocdn.net/dn/ckI0xo/dJMb84XR0iS/Cjqkdog5q5rxLKKYJ8OpVk/img.jpg?width=1333&amp;amp;height=2000&amp;amp;face=587_268_733_427&quot;&gt;&lt;a href=&quot;https://think0wise.tistory.com/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://think0wise.tistory.com/100&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gLfa6/dJMb87fZzCH/IraXsXIDqiJVHTWs6sm4K0/img.png?width=800&amp;amp;height=292&amp;amp;face=0_0_800_292,https://scrap.kakaocdn.net/dn/dAMebU/dJMb9bvVz1w/UiiVsyWBKKKlXKciKTwuwk/img.png?width=800&amp;amp;height=292&amp;amp;face=0_0_800_292,https://scrap.kakaocdn.net/dn/ckI0xo/dJMb84XR0iS/Cjqkdog5q5rxLKKYJ8OpVk/img.jpg?width=1333&amp;amp;height=2000&amp;amp;face=587_268_733_427');&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;Jest - Matcher란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;여러분들은 Jest로 테스트 코드를 작성할 때 다양한 Matcher를 사용하고 계시나요? 오늘은 Jest에서 테스트 코드를 작성할 때 자주 사용하는 Matcher에 대해서 알아보겠습니다. 1. toBe( value ) 기본형을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;think0wise.tistory.com&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;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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000; font-size: 1.25em; letter-spacing: -1px;&quot;&gt;오늘의 실습문제&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000; text-align: start;&quot;&gt;문제 1. 입력 필드와 제출 (NameForm 컴포넌트)&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;&lt;b&gt;요구사항&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 이름을 입력하고 제출하면, 화면에 &amp;ldquo;안녕하세요, [이름]님!&amp;rdquo; 이 표시된다.&lt;/li&gt;
&lt;li&gt;초기에는 아무 텍스트도 보이지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1768287434194&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//NameForm.jsx
import React from &quot;react&quot;;
export default function NameForm() {
  const [name, setName] = React.useState(&quot;&quot;);
  const [greeting, setGreeting] = React.useState(&quot;&quot;);

  const handleSubmit = (e) =&amp;gt; {
    e.preventDefault();
    setGreeting(`안녕하세요, ${name}님!`);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;form onSubmit={handleSubmit}&amp;gt;
        &amp;lt;input
          type=&quot;text&quot;
          value={name}
          onChange={(e) =&amp;gt; setName(e.target.value)}
          placeholder=&quot;이름을 입력하세요&quot;
        /&amp;gt;
        &amp;lt;button type=&quot;submit&quot;&amp;gt;제출&amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
      {greeting &amp;amp;&amp;amp; &amp;lt;p&amp;gt;{greeting}&amp;lt;/p&amp;gt;}
    &amp;lt;/div&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&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;이름을 입력하고 제출 시 인사말이 표시된다&lt;/li&gt;
&lt;li&gt;hint) toBeNull, toBeInTheDocument&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1768287459428&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// NameForm.test.jsx
import React from &quot;react&quot;;
import { render, screen, fireEvent } from &quot;@testing-library/react&quot;;
import NameForm from &quot;./NameForm&quot;;

test(&quot;초기에는 인사말이 보이지 않는다&quot;, () =&amp;gt; {
  render(&amp;lt;NameForm /&amp;gt;);

  expect(screen.queryByText(/안녕하세요,/)).toBeNull();
});

test(&quot;이름을 입력하고 제출 시 인사말이 표시된다&quot;, () =&amp;gt; {
  render(&amp;lt;NameForm /&amp;gt;);
  const input = screen.getByPlaceholderText(&quot;이름을 입력하세요&quot;);
  fireEvent.change(input, { target: { value: &quot;철수&quot; } });
  fireEvent.click(screen.getByText(&quot;제출&quot;));

  expect(screen.getByText(&quot;안녕하세요, 철수님!&quot;)).toBeInTheDocument();
});&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;문제2. 조건부 렌더링 리스트 (TodoList 컴포넌트)&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;&lt;b&gt;요구사항&lt;/b&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;초기 할 일 2개를 렌더링한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 할 일 : [&quot;React 공부하기&quot;, &quot;테스트 배우기&quot;]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;ldquo;할 일 추가&amp;rdquo; 버튼을 누르면 새 할 일이 추가된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 할 일 : 새 할 일 ${prev.length + 1}&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1768287583874&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TodoList.jsx
import React from &quot;react&quot;;
export default function TodoList() {
  const [todos, setTodos] = React.useState([&quot;React 공부하기&quot;, &quot;테스트 배우기&quot;]);

  const addTodo = () =&amp;gt; {
    setTodos((prev) =&amp;gt; [...prev, `새 할 일 ${prev.length + 1}`]);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;ul&amp;gt;
        {todos.map((todo, index) =&amp;gt; (
          &amp;lt;li key={index}&amp;gt;{todo}&amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
      &amp;lt;button onClick={addTodo}&amp;gt;할 일 추가&amp;lt;/button&amp;gt;
    &amp;lt;/div&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 요구사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 할 일이 2개 표시된다&lt;/li&gt;
&lt;li&gt;할 일 추가 버튼을 누르면 새 항목이 추가된다&lt;/li&gt;
&lt;li&gt;hint) toHaveTextContent, toBeInTheDocument&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1768287602326&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TodoList.test.jsx
import React from &quot;react&quot;;
import { render, screen, fireEvent } from &quot;@testing-library/react&quot;;
import TodoList from &quot;./TodoList&quot;;

test(&quot;초기 할 일이 2개 표시된다&quot;, () =&amp;gt; {
  render(&amp;lt;TodoList /&amp;gt;);

  expect(screen.getByText(&quot;React 공부하기&quot;)).toBeInTheDocument();
  expect(screen.getByText(&quot;테스트 배우기&quot;)).toBeInTheDocument();
});

test(&quot;할 일 추가 버튼을 누르면 새 항목이 추가된다&quot;, () =&amp;gt; {
  render(&amp;lt;TodoList /&amp;gt;);
  const button = screen.getByText(&quot;할 일 추가&quot;);

  fireEvent.click(button);

  expect(screen.getByText(&quot;새 할 일 3&quot;)).toBeInTheDocument();
});&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고자료&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; href=&quot;https://velog.io/@skyu_dev/Jest-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-JS%EC%9D%98-%EA%B8%B0%EB%8A%A5-%EC%A0%90%EA%B2%80%ED%95%98%EA%B8%B0#21-%EC%9C%A0%EC%9A%A9%ED%95%9C-matchers&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@skyu_dev/Jest-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-JS%EC%9D%98-%EA%B8%B0%EB%8A%A5-%EC%A0%90%EA%B2%80%ED%95%98%EA%B8%B0#21-%EC%9C%A0%EC%9A%A9%ED%95%9C-matchers&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768287788711&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;[Jest] 테스트 코드로 JS 의 기능 및 로직 점검하기&quot; data-og-description=&quot;Jest를 사용해서 JavaScript 코드를 테스트하고 버그를 줄여보자&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@skyu_dev/Jest-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-JS%EC%9D%98-%EA%B8%B0%EB%8A%A5-%EC%A0%90%EA%B2%80%ED%95%98%EA%B8%B0#21-%EC%9C%A0%EC%9A%A9%ED%95%9C-matchers&quot; data-og-url=&quot;https://velog.io/@skyu_dev/Jest-테스트-코드를-사용하여-JS의-기능-점검하기&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eHQwo/dJMb8U8MULU/uPHZ44wNICWN0RsQS1Jul0/img.png?width=796&amp;amp;height=416&amp;amp;face=0_0_796_416,https://scrap.kakaocdn.net/dn/k0mte/dJMb8UHIp4Q/e2s0rhjjSK6CsuxfZWnMWK/img.png?width=796&amp;amp;height=416&amp;amp;face=0_0_796_416,https://scrap.kakaocdn.net/dn/yrdTb/dJMb8T9SGIL/GlpYKc9C8IFx5H8INp5Kjk/img.png?width=796&amp;amp;height=416&amp;amp;face=0_0_796_416&quot;&gt;&lt;a href=&quot;https://velog.io/@skyu_dev/Jest-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-JS%EC%9D%98-%EA%B8%B0%EB%8A%A5-%EC%A0%90%EA%B2%80%ED%95%98%EA%B8%B0#21-%EC%9C%A0%EC%9A%A9%ED%95%9C-matchers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@skyu_dev/Jest-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-JS%EC%9D%98-%EA%B8%B0%EB%8A%A5-%EC%A0%90%EA%B2%80%ED%95%98%EA%B8%B0#21-%EC%9C%A0%EC%9A%A9%ED%95%9C-matchers&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eHQwo/dJMb8U8MULU/uPHZ44wNICWN0RsQS1Jul0/img.png?width=796&amp;amp;height=416&amp;amp;face=0_0_796_416,https://scrap.kakaocdn.net/dn/k0mte/dJMb8UHIp4Q/e2s0rhjjSK6CsuxfZWnMWK/img.png?width=796&amp;amp;height=416&amp;amp;face=0_0_796_416,https://scrap.kakaocdn.net/dn/yrdTb/dJMb8T9SGIL/GlpYKc9C8IFx5H8INp5Kjk/img.png?width=796&amp;amp;height=416&amp;amp;face=0_0_796_416');&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;[Jest] 테스트 코드로 JS 의 기능 및 로직 점검하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Jest를 사용해서 JavaScript 코드를 테스트하고 버그를 줄여보자&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@nueob/%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%94%BC%EB%9D%BC%EB%AF%B8%EB%93%9C%EB%9E%80&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@nueob/%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%94%BC%EB%9D%BC%EB%AF%B8%EB%93%9C%EB%9E%80&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768287768332&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;테스트피라미드란 ?&quot; data-og-description=&quot;(https://velog.velcdn.com/images/nueob/post/81558da3-f7f8-4e37-9ac2-a8f906aaa00f/image.png)unit Testing대상: 단일 기능 혹은 작은 단위의 함수/객체 등가벼운 비용으로 새로운 기능&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@nueob/%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%94%BC%EB%9D%BC%EB%AF%B8%EB%93%9C%EB%9E%80&quot; data-og-url=&quot;https://velog.io/@nueob/테스트피라미드란&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c5oCWr/dJMb9jOggKZ/MuAOQ7n0dKcsfNS7OiDS80/img.png?width=1186&amp;amp;height=690&amp;amp;face=0_0_1186_690,https://scrap.kakaocdn.net/dn/mX2sJ/dJMb8Xj8Gxh/xRNldALv0K37T0tQM2ksP1/img.png?width=1186&amp;amp;height=690&amp;amp;face=0_0_1186_690,https://scrap.kakaocdn.net/dn/Uvtdy/dJMb85vH6UI/c8efFkcQCVaQlPvubqVpkk/img.png?width=1186&amp;amp;height=690&amp;amp;face=0_0_1186_690&quot;&gt;&lt;a href=&quot;https://velog.io/@nueob/%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%94%BC%EB%9D%BC%EB%AF%B8%EB%93%9C%EB%9E%80&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@nueob/%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%94%BC%EB%9D%BC%EB%AF%B8%EB%93%9C%EB%9E%80&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c5oCWr/dJMb9jOggKZ/MuAOQ7n0dKcsfNS7OiDS80/img.png?width=1186&amp;amp;height=690&amp;amp;face=0_0_1186_690,https://scrap.kakaocdn.net/dn/mX2sJ/dJMb8Xj8Gxh/xRNldALv0K37T0tQM2ksP1/img.png?width=1186&amp;amp;height=690&amp;amp;face=0_0_1186_690,https://scrap.kakaocdn.net/dn/Uvtdy/dJMb85vH6UI/c8efFkcQCVaQlPvubqVpkk/img.png?width=1186&amp;amp;height=690&amp;amp;face=0_0_1186_690');&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;(https://velog.velcdn.com/images/nueob/post/81558da3-f7f8-4e37-9ac2-a8f906aaa00f/image.png)unit Testing대상: 단일 기능 혹은 작은 단위의 함수/객체 등가벼운 비용으로 새로운 기능&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.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;&lt;a href=&quot;https://velog.io/@bami/Javascript-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-Jest&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@bami/Javascript-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-Jest&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768287753624&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;[Javascript] 테스트 프레임워크 Jest&quot; data-og-description=&quot;오늘은 자바스크립트의 또 다른 테스트 프레임 워크인 Jest를 소개해보려고 합니다. 예전에 Jasmine을 소개해드렸었는데요. 사용법이 거의 비슷합니다. 그 이유는 Meta에서 Jasmine을 기반으로 Jest 프&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@bami/Javascript-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-Jest&quot; data-og-url=&quot;https://velog.io/@bami/Javascript-테스트-프레임워크-Jest&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ePDRc/dJMb8SXq45p/HxsBOoAyCNpjofZNAmJgG1/img.png?width=700&amp;amp;height=350&amp;amp;face=0_0_700_350,https://scrap.kakaocdn.net/dn/GX4Eg/dJMb8Z3kuSU/BdajjkqhXFaIWkqV3SlEd0/img.png?width=700&amp;amp;height=350&amp;amp;face=0_0_700_350,https://scrap.kakaocdn.net/dn/5aSCc/dJMb8T9SGIB/9dDg4wph0I63ka9H1WQ9qK/img.png?width=1296&amp;amp;height=507&amp;amp;face=0_0_1296_507&quot;&gt;&lt;a href=&quot;https://velog.io/@bami/Javascript-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-Jest&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@bami/Javascript-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-Jest&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ePDRc/dJMb8SXq45p/HxsBOoAyCNpjofZNAmJgG1/img.png?width=700&amp;amp;height=350&amp;amp;face=0_0_700_350,https://scrap.kakaocdn.net/dn/GX4Eg/dJMb8Z3kuSU/BdajjkqhXFaIWkqV3SlEd0/img.png?width=700&amp;amp;height=350&amp;amp;face=0_0_700_350,https://scrap.kakaocdn.net/dn/5aSCc/dJMb8T9SGIB/9dDg4wph0I63ka9H1WQ9qK/img.png?width=1296&amp;amp;height=507&amp;amp;face=0_0_1296_507');&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;[Javascript] 테스트 프레임워크 Jest&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 자바스크립트의 또 다른 테스트 프레임 워크인 Jest를 소개해보려고 합니다. 예전에 Jasmine을 소개해드렸었는데요. 사용법이 거의 비슷합니다. 그 이유는 Meta에서 Jasmine을 기반으로 Jest 프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.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;</description>
      <category>JS</category>
      <category>JEST</category>
      <category>TDD</category>
      <author>lyla-bae</author>
      <guid isPermaLink="true">https://lyla-bae.tistory.com/37</guid>
      <comments>https://lyla-bae.tistory.com/37#entry37comment</comments>
      <pubDate>Tue, 13 Jan 2026 16:12:49 +0900</pubDate>
    </item>
  </channel>
</rss>