<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>joooii</title>
    <link>https://joooii.tistory.com/</link>
    <description>joooii 님의 블로그 입니다</description>
    <language>ko</language>
    <pubDate>Sun, 5 Jul 2026 23:08:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>joooii</managingEditor>
    <image>
      <title>joooii</title>
      <url>https://tistory1.daumcdn.net/tistory/8182006/attach/d8f9f6c5bc6649f29e84c7540672815d</url>
      <link>https://joooii.tistory.com</link>
    </image>
    <item>
      <title>[TIL] useEffect와 useLayoutEffect 차이</title>
      <link>https://joooii.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;2004&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp3V1A/dJMcai4pWBR/fcWn8CsmEg4mfKlK1fi4l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp3V1A/dJMcai4pWBR/fcWn8CsmEg4mfKlK1fi4l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp3V1A/dJMcai4pWBR/fcWn8CsmEg4mfKlK1fi4l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp3V1A%2FdJMcai4pWBR%2FfcWn8CsmEg4mfKlK1fi4l0%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;2004&quot; height=&quot;1020&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1020&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❓ 궁금한 내용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React를 공부하다가 `useLayoutEffect`에 대해 알게 되었고, `useLayoutEffect`를 사용하면 `useEffect`를 사용했을 때와는 달리 화면에 렌더링될 때 값이 0을 출력했다가 값이 채워지는 깜빡임 현상(Flash of Unstyled Content, FOUC)이 발생하지 않는다는 것을 알게 되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;`useLayoutEffect`를 사용하는 것이 더 좋은 것이 아닌가?&lt;/b&gt;하는 생각이 들었다. 일단 정답은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;'No'&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  학습 내용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 useEffect와 useLayoutEffect에 대해서 알아보자.&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;1. useEffect&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`useEffect`는 리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 hook이고, &lt;i&gt;&lt;b&gt;비동기적&lt;/b&gt;&lt;/i&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;1.1. useEffect 실행 과정&lt;/h3&gt;
&lt;pre id=&quot;code_1779349891678&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;리액트 컴포넌트 렌더링 -&amp;gt; 브라우저가 화면을 그림 (paint) -&amp;gt; useEffect 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 순서로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;화면이 이미 그려진 후에 실행&lt;/b&gt;되기 때문에, 훅 내부에서 상태(state)를 바꾸면 화면이 한 번 더 그려지면서 &lt;i&gt;깜빡임이 발생&lt;/i&gt;하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zmXRl/dJMcabYCcNi/vsJ6BotqbT2vHWHKkrDm6k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zmXRl/dJMcabYCcNi/vsJ6BotqbT2vHWHKkrDm6k/img.gif&quot; data-alt=&quot;useEffect를 사용했을 때 깜빡임 현상&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zmXRl/dJMcabYCcNi/vsJ6BotqbT2vHWHKkrDm6k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/zmXRl/dJMcabYCcNi/vsJ6BotqbT2vHWHKkrDm6k/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;576&quot; height=&quot;156&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;useEffect를 사용했을 때 깜빡임 현상&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;h3 data-ke-size=&quot;size23&quot;&gt;2. useLayoutEffect&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`useLayoutEffect`는 브라우저가 화면에 DOM을 그리기 전에 이펙트를 수행하는 hook이고, &lt;b&gt;&lt;i&gt;동기적&lt;/i&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;h3 data-ke-size=&quot;size23&quot;&gt;2.1. useLayoutEffect 실행 과정&lt;/h3&gt;
&lt;pre id=&quot;code_1779350042683&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;리액트 컴포넌트 렌더링 -&amp;gt; useLayoutEffect 실행 -&amp;gt; 브라우저가 화면 그림 (paint)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 순서로 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면을 &lt;b&gt;그리기 바로 직전에 실행&lt;/b&gt;되기 때문에, 이 내부에서 상태를 바꾸면 리액트는 그 바뀐 것까지 계산을 끝낸 뒤에 화면을 딱 한 번만 그리면서 &lt;i&gt;깜빡임이 전혀 발생하지 않는다.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GqLnl/dJMcaaZFGEQ/3FifaOHCP2pgzYVSioTde0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GqLnl/dJMcaaZFGEQ/3FifaOHCP2pgzYVSioTde0/img.gif&quot; data-alt=&quot;useLayoutEffect를 사용했을 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GqLnl/dJMcaaZFGEQ/3FifaOHCP2pgzYVSioTde0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/GqLnl/dJMcaaZFGEQ/3FifaOHCP2pgzYVSioTde0/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;576&quot; height=&quot;156&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;useLayoutEffect를 사용했을 때&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;h3 data-ke-size=&quot;size23&quot;&gt;3. 결정적인 차이점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`useEffect`는 비동기, `useLayoutEffect`는 동기적으로 실행된다.&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;4. 그럼 왜 `useLayoutEffect`를 남발하면 안될까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`useLayoutEffect` 내부의 코드가 실행되는 동안 브라우저는 화면을 그리지 못하고 완전히 멈춰있다.&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;만약 `useLayoutEffect` 안에서 시간이 오래걸리는 무거운 작업을 하거나 서버에서 데이터를 받아오는 처리를 한다면, 사용자는 그 작업이 끝날 때까지 하얀 화면이나 멈춘 화면을 보며 기다려야 하기 때문에 웹 초기 로딩 속도와 반응성이 떨어진다.&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;결론적으로 `useLayoutEffect`는 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;시각적인 깜빡임을 해결&lt;/span&gt;해주는 대신, &lt;span style=&quot;color: #ee2323;&quot;&gt;성능이 떨어지는&lt;/span&gt; 트레이드오프&lt;/b&gt;가 있기 때문에 필요할 때만 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. `useLayoutEffect`를 언제 사용하는 것이 좋을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM 조작(화면에 그려질 요소의 크기나 위치를 측정)으로 UI를 동적으로 변경해야 할 때 주로 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 예시1: 팝업창이나 툴팁을 띄울 때, 버튼의 위치를 계산해서 즉각 띄워야 하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 예시2: 스크롤 위치를 특정 지점으로 강제 이동시켜야 하는 경우&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;위와 같은 상황을 제외하고는 `useEffect`를 사용하는 것을 지향한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-21 17.18.11.png&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/slcQJ/dJMcafmpFq5/JQndGD06lB6XSfUdhLg4kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/slcQJ/dJMcafmpFq5/JQndGD06lB6XSfUdhLg4kk/img.png&quot; data-alt=&quot;react 공식문서 왈왈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/slcQJ/dJMcafmpFq5/JQndGD06lB6XSfUdhLg4kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FslcQJ%2FdJMcafmpFq5%2FJQndGD06lB6XSfUdhLg4kk%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;907&quot; height=&quot;171&quot; data-filename=&quot;스크린샷 2026-05-21 17.18.11.png&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;react 공식문서 왈왈&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  느낀점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useLayoutEffect가 생소한 개념이지만, 적절하게 사용하면 매우 유용할 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 크롬 브라우저에서는 useLayoutEffect를 사용할 때 크롬 DevTools에서는 layout과 paint 성능을 측정할 수 있는데, 이러한 성능 모니터링 기능을 사용할 때는 유용하다고 한다. (출처: &lt;a href=&quot;https://junghyeonsu.com/posts/useeffect-vs-uselayouteffect/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://junghyeonsu.com/posts/useeffect-vs-uselayouteffect/&lt;/a&gt; )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  참고자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prepare-frontend-interview.vercel.app/react#uselayouteffect%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%8B%A0-%EC%A0%81-%EC%9E%88%EB%82%98%EC%9A%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://prepare-frontend-interview.vercel.app/react#uselayouteffect%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%8B%A0-%EC%A0%81-%EC%9E%88%EB%82%98%EC%9A%94&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1779351597732&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;prepare_frontend_interview | 프론트엔드 면접 준비 핸드북&quot; data-og-description=&quot;&quot; data-og-host=&quot;prepare-frontend-interview.vercel.app&quot; data-og-source-url=&quot;https://prepare-frontend-interview.vercel.app/react#uselayouteffect%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%8B%A0-%EC%A0%81-%EC%9E%88%EB%82%98%EC%9A%94&quot; data-og-url=&quot;https://prepare-frontend-interview.vercel.app/react#uselayouteffect%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%8B%A0-%EC%A0%81-%EC%9E%88%EB%82%98%EC%9A%94&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://prepare-frontend-interview.vercel.app/react#uselayouteffect%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%8B%A0-%EC%A0%81-%EC%9E%88%EB%82%98%EC%9A%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prepare-frontend-interview.vercel.app/react#uselayouteffect%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%8B%A0-%EC%A0%81-%EC%9E%88%EB%82%98%EC%9A%94&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;prepare_frontend_interview | 프론트엔드 면접 준비 핸드북&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;prepare-frontend-interview.vercel.app&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TIL</category>
      <category>useEffect</category>
      <category>useLayoutEffect</category>
      <author>joooii</author>
      <guid isPermaLink="true">https://joooii.tistory.com/44</guid>
      <comments>https://joooii.tistory.com/44#entry44comment</comments>
      <pubDate>Thu, 21 May 2026 17:20:23 +0900</pubDate>
    </item>
    <item>
      <title>[회고] 유레카 프론트엔드 부트캠프 최종 회고</title>
      <link>https://joooii.tistory.com/43</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;유레카 첫 포스트&amp;nbsp;&lt;/h3&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;[회고] 유레카 부트캠프 3기 프론트엔드 신청 및 후기&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;LG U+에서 주관하는 유레카 부트캠프!수료만 해도 LG U+ 서류/코테 면제(1년 이내 1회만 가능)라기에 좋은 기회라서 바로 보자마자 신청했습니다 서류자기소개서 문항은 아래와 같습니다. 저는 비&quot; data-og-host=&quot;joooii.tistory.com&quot; data-og-source-url=&quot;https://joooii.tistory.com/3&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/bs8GrF/dJMb8U8TFz7/AAAAAAAAAAAAAAAAAAAAADKzqSivohMpg9mk0QzrFZP9Kthiim9SSvfHdSdrKVaX/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1774969199&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=QlVKOatpsstpFbuEMTDSOgqkydM%3D&quot; data-og-url=&quot;https://joooii.tistory.com/3&quot;&gt;&lt;a href=&quot;https://joooii.tistory.com/3&quot; target=&quot;_blank&quot; data-source-url=&quot;https://joooii.tistory.com/3&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://blog.kakaocdn.net/dna/bs8GrF/dJMb8U8TFz7/AAAAAAAAAAAAAAAAAAAAADKzqSivohMpg9mk0QzrFZP9Kthiim9SSvfHdSdrKVaX/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1774969199&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=QlVKOatpsstpFbuEMTDSOgqkydM%3D')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;[회고] 유레카 부트캠프 3기 프론트엔드 신청 및 후기&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;LG U+에서 주관하는 유레카 부트캠프!수료만 해도 LG U+ 서류/코테 면제(1년 이내 1회만 가능)라기에 좋은 기회라서 바로 보자마자 신청했습니다 서류자기소개서 문항은 아래와 같습니다. 저는 비&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;joooii.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;br&gt;최종 회고를 올릴 날이 올지도 몰랐고, 그동안 꾸준히 포스트를 쓰게 될지도 몰랐다 ..&lt;br&gt;사실 유레카 후기 중 이전 기수 분께서 작성한 최종 회고를 보고 꼭 나도 최종 회고를 써야겠다고 다짐했고, 그 회고를 보면서 많은 도움과 힘이 됐기에 써보려고 한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 월별 회고&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;8월&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;운이 좋게 메카드한장 친구들을 만나서 같이 회식도 하고 빠르게 친해져서 행복했다!!&lt;br&gt;사실 생일이 있었는데 제주도 친구들이 서프라이즈로 올라와줘서 8월은 무탈하게 마무리했던 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;9월&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘을 배우고, 알고리즘 스터디도 진행을 하다 보니 매우 빠르게 지나갔던 것 같다.&lt;br&gt;사실 이 시기에 기존에 진행하던 팀프로젝트의 마감일이 얼마 남지 않았어서 정말 힘들었다.&lt;br&gt;스트레스를 너무 받고 시간도 촉박했어서 살 빠지고 입맛도 뚝 떨어졌어서 체력적으로나 멘탈적으로나 쉽지 않았던 9월이다 ..&lt;br&gt;&amp;nbsp;&lt;br&gt;이제는 말할 수 있다 이 프로젝트 끝나고 나니까 미니, 종합, 융합.. 사실 이때만큼이나 힘들진 않았다.. ㅎㅎ&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;10월&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;팀프로젝트를 잘 마무리하고 10월은 조금 편하게 지냈던 것 같다. 마침 추석 연휴가 길었기도 해서 마음 편하게 본가에서 휴식을 즐겼다. 이때는 백엔드 수업을 시작했는데 Spring Boot를 처음 접했고, 미니프로젝트의 좋은 밑거름이 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;아마 미니프로젝트 백엔드 구현도 10월 말쯤에 시작했던 것 같다. 기존에 3명이서 진행되었어야 할 미니프로젝트가 한 분이 부트캠프를 나가게 되면서 2명이서 진행하게 되었다. 사실 2명이서 잘할 수 있을까 걱정이 많이 됐던 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;11월&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;미니프로젝트를 같이 한 팀원과 의지를 끝까지 불태우며 백엔드 파트 구현을 잘 마무리하게 되어서 뿌듯했다!!&lt;br&gt;11월에는 정처기 실기도 공부했다. 공부량이 많지 않아서 떨어져서 아쉽긴 하지만. ㅎㅎ&lt;br&gt;이때 처음으로 TIL 명예의 전당에 오르게 되면서 TIL을 잘 적고자 하는 의지가 정말 커졌다. (&lt;a href=&quot;https://blog.naver.com/multicampus_it/224094467968&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://blog.naver.com/multicampus_it/224094467968&lt;/span&gt;&lt;/a&gt;)&lt;br&gt;&amp;nbsp;&lt;br&gt;무엇보다 선릉 속초오징어의 방어 맛은 절대 잊지 못한다.. 강추합니다 (묵은지에 참기름 뿌려서 꼭 드세용)&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ojslv/dJMcaf0k7pB/j2bixHdGbKDJODnFWrSsO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ojslv/dJMcaf0k7pB/j2bixHdGbKDJODnFWrSsO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ojslv/dJMcaf0k7pB/j2bixHdGbKDJODnFWrSsO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fojslv%2FdJMcaf0k7pB%2Fj2bixHdGbKDJODnFWrSsO0%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;437&quot; height=&quot;446&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;12월&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;SQLD 자격증을 취득했다. 정처기 실기 본 뒤에 거의 바로 붙어있어서 준비를 많이 못 했고, 실제로도 OMR 마킹 실수도 정말 많이 해서 떨어질 줄 알았는데, 간당간당하게 합격하게 되었다. 사실상 첫 자격증이라 더 뿌듯하다.&lt;br&gt;&amp;nbsp;&lt;br&gt;미니프로젝트 프론트엔드 파트 구현을 시작했다. 프론트엔드를 구현하면서 백엔드도 동시에 수정하려고 하니 정신없이 프로젝트를 진행했고 잘 마무리했다.&lt;br&gt;미니프로젝트를 거치며 기획-백엔드-UI 디자인-프론트엔드 사실상 프로젝트의 전반적인 흐름을 경험해보고 나니, 개발자로서 한층 성장했음을 느낄 수 있었다.&amp;nbsp;&lt;br&gt;백엔드 파트 구현은 어렵긴 했지만, ERD 설계, DB 구조 등 평소에 생각 못해봤을 기술도 접해보고 나니 자신감도 많이 생겼다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 우수훈련생에도 뽑혔다 !!!!&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;연말엔 좀 풀어지긴 했지만 부트캠프의 절반을 잘 마무리 지었던 것 같다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8IcHQ/dJMcag52U0i/PKkfvJWRYaJ1utGRBKqjs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8IcHQ/dJMcag52U0i/PKkfvJWRYaJ1utGRBKqjs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8IcHQ/dJMcag52U0i/PKkfvJWRYaJ1utGRBKqjs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8IcHQ%2FdJMcag52U0i%2FPKkfvJWRYaJ1utGRBKqjs0%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;379&quot; height=&quot;379&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1월&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;스타트업 취업 박람회에 갔다. 취업을 위한 박람회는 처음이라 기대를 많이 했지만, 볼 만한 게 별로 없어서 일찍 나오긴 했다. 그래도 박람회를 위해 이력서도 간단하게 적어봤던 게 도움이 됐던 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 종합 프로젝트도 진행하게 됐다. 기획을 중간에 바꾸면서 일정 관리가 쉽지 않았지만, 잘 마무리했다. 사실 종합 프로젝트하면서 가장 큰 성장을 했던 것 같다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YoI8B/dJMcahDQZXP/jgRCQnQxioUDmrQxRE2slk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YoI8B/dJMcahDQZXP/jgRCQnQxioUDmrQxRE2slk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YoI8B/dJMcahDQZXP/jgRCQnQxioUDmrQxRE2slk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYoI8B%2FdJMcahDQZXP%2FjgRCQnQxioUDmrQxRE2slk%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;351&quot; height=&quot;460&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2월&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;종합 프로젝트가 끝나자마자 융합 프로젝트 팀이 나오면서 백-프론트 전체 회식을 하게 되었다.&lt;br&gt;이때는 또 졸업식까지 겹쳤는데, 다음날 바로 멘토링이 잡혀서 서울-부산 당일치기를 했다는 사실.. 너무너무 힘들었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;설 당일에 진행했던 디코 단체 회의는 아직 잊지 못한다.. 즐거웠다는 의미이다 ㅎㅎ&lt;br&gt;사실 무엇보다도 현직자 멘토님과 대화를 나누면서 정말 존경스러웠다. 인생에 롤모델이 크게 없었는데 멘토님 같은 개발자로 성장하고 싶다는 생각이 처음 들게 되었다. 그만큼 배울 점이 너무 많았고, 개발자가 된다면 멘토님처럼 좋은 것을 알려줄 수 있는 개발자가 될 것이다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;821&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7ODjE/dJMcaarecqn/QAdKRsqizq1T10OIZRNeCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7ODjE/dJMcaarecqn/QAdKRsqizq1T10OIZRNeCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7ODjE/dJMcaarecqn/QAdKRsqizq1T10OIZRNeCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7ODjE%2FdJMcaarecqn%2FQAdKRsqizq1T10OIZRNeCk%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;469&quot; height=&quot;548&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;821&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3월&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;융합 프로젝트의 마지막을 불태웠다. 중간중간에 힘이 들기도 하고, 멘탈이 약해질 때도 있었지만 그래도 어찌저찌 잘 마무리했다.&lt;br&gt;팀장님의 멋진 발표와 잘하는 팀원들 덕분에 본선에 진출하는 좋은 경험도 했다!!&lt;br&gt;&amp;nbsp;&lt;br&gt;또 본선에서는 예상치 못했던 .. 심사위원분들의 QnA를 받아서 횡설수설 답변했던 것 같아서 아쉽긴 하다. 그래도 사실 현직자분들 앞에서 그렇게 질의응답을 할 수 있는 기회가 흔치 않아서 값진 경험이었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;비록 상은 받지 못했어도 전혀 아쉽지 않았다. 좋은 팀원과 함께하기도 했고, 그동안 너무 성장해서 이 모든 프로젝트를 포기하지 않고 해냈다는 점이 제일 뿌듯했기 때문이다. 그리고 마지막 전체 회식 때 또 절제 못 해서 만취한 게 아쉬지만 후회는 없다 ^,,^&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8quq8/dJMcafMRhxr/Oq0rk7DIxrKPx6vO8uDvi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8quq8/dJMcafMRhxr/Oq0rk7DIxrKPx6vO8uDvi0/img.png&quot; data-alt=&quot;본선 진출 ㅎㅎ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8quq8/dJMcafMRhxr/Oq0rk7DIxrKPx6vO8uDvi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8quq8%2FdJMcafMRhxr%2FOq0rk7DIxrKPx6vO8uDvi0%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;278&quot; height=&quot;247&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;본선 진출 ㅎㅎ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hk7rt/dJMcadIct9t/cXRJsKfbNz64UsD0YjO2ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hk7rt/dJMcadIct9t/cXRJsKfbNz64UsD0YjO2ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hk7rt/dJMcadIct9t/cXRJsKfbNz64UsD0YjO2ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHk7rt%2FdJMcadIct9t%2FcXRJsKfbNz64UsD0YjO2ck%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;421&quot; height=&quot;448&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tYPgH/dJMcadBoQuv/SmYXi8qfUzstuGU4h89CQk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tYPgH/dJMcadBoQuv/SmYXi8qfUzstuGU4h89CQk/img.jpg&quot; data-alt=&quot;이제 잠은 집에서만 잘게요 ..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tYPgH/dJMcadBoQuv/SmYXi8qfUzstuGU4h89CQk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtYPgH%2FdJMcadBoQuv%2FSmYXi8qfUzstuGU4h89CQk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1179&quot; height=&quot;2169&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2169&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이제 잠은 집에서만 잘게요 ..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;  성장 포인트&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 개발적인 성장&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사실 부트캠프를 수강하기 전에는 주변에 프론트엔드 개발 동료도 없을 뿐더러 인사이트를 얻을 만한 곳이 오직 GDG 동아리였다. 이마저도 수료를 하고 나니 개발에 대한 의지가 약간 꺾였던 것 같다. 그래서 충동적으로 부트캠프를 찾아보고, 바로 지원했던 것이기도 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;부트캠프를 수강한 이후로는 프론트엔드 반 사람들과 개발에 대한 얘기와 고민을 나누고 나니 의지가 불타오르게 되고, 좋은 코드에 대한 고민도 많이 하게 됐던 것 같다. 이 과정에서 뭔가 살아있음을 느꼈다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 기록하는 습관&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;매주 TIL 작성을 위해 나머지 공부를 하고, 이를 기록하여 내 것으로 만들고자 했던 경험은 개발자로 성장하기 위해 정말 값진 성장 포인트인 것 같다.&amp;nbsp;&lt;br&gt;TIL을 작성함과 동시에 프로젝트를 진행하며 겪었던 트러블슈팅, 개발 위키 등 개발 관련 포스트도 함께 기록하는 것이 습관이 됐고, 좋은 습관을 들인 것 같아 기쁘다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 의사소통 개선&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;여러 차례 팀프로젝트를 진행하면서 다른 사람과 의사소통하는 스킬이 부쩍 늘은 것 같다는 생각이 든다. 이전에는 조금 미숙하게 의사전달을 했다면, 지금은 더 고민해 보고 어떤 방식으로 의사 표현을 하면 좋을 지에 대해 깨달으면서 사회성이 조금 더 늘어난 것 같기도 하다 ㅎㅎ&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 좋은 인연&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;부트캠프 중 최고의 최고는 좋은 인연을 만든 것이다. 맨 처음에는 잘 적응할 수 있을까 싶었는데 다 너무 친절했고, 마지막엔 프론트엔드 반 모두와 친해져서 행복했고, 모두모두 고생 많이 한 걸 보면서 나도 의지를 불태울 수 있었다. 돈으로 살 수 없는 값진 7개월이었던 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;  정말 마치며...&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;부캠 막바지가 하필 공채 시즌이라 부랴부랴 서류 준비하고, 지원도 해보면서 이제 취업만 잘하면 되겠다는 생각이 커졌다. 그리고 무엇보다 포폴과 이력서에 적을 것이 많다는 점이... 제일 뿌듯한 것 같다.&amp;nbsp;&lt;br&gt;또 의외로 내가 승부욕과 성공하고자 하는 욕심이 있다는 것도 알게 되었다.. ㅎㅎ&lt;br&gt;&amp;nbsp;&lt;br&gt;정처기 실기도 다시 준비하고, 프로젝트를 뺑이치면서 소홀했던 개발 공부와 코테 준비도 하면서 상반기 내지 하반기에는 취업을 하는 것이 목표이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이제는 날 관리해 줄 시간도, 사람도 없지만 그래도 최선을 다해서 정상적인 생활 패턴을 구축해야겠다. 끝 ~&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;이제는 말할 수 있다? 근데 말해도 되나? 유레카 유튜버님 덕분에 신기한 경험도 했다ㅎㅎ 뭔가 나를 되돌아본 느낌 .. &lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(&lt;/span&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=wDW0yvr2-mw&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;https://www.youtube.com/watch?v=wDW0yvr2-mw&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;)&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;그래서 유레카 추천하나요? 완전 추천합니다 ~&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OIeg1/dJMcadBoP4X/ZcMGg7ZsyKUHneCXV0uN2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OIeg1/dJMcadBoP4X/ZcMGg7ZsyKUHneCXV0uN2K/img.png&quot; data-alt=&quot;유레카 부트캠프 3기 끝 !&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OIeg1/dJMcadBoP4X/ZcMGg7ZsyKUHneCXV0uN2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOIeg1%2FdJMcadBoP4X%2FZcMGg7ZsyKUHneCXV0uN2K%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;3000&quot; height=&quot;2250&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;2250&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;유레카 부트캠프 3기 끝 !&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>회고</category>
      <category>유레카 부트캠프</category>
      <category>회고</category>
      <author>joooii</author>
      <guid isPermaLink="true">https://joooii.tistory.com/43</guid>
      <comments>https://joooii.tistory.com/43#entry43comment</comments>
      <pubDate>Tue, 31 Mar 2026 02:24:36 +0900</pubDate>
    </item>
    <item>
      <title>[회고] 유레카 부트캠프 프론트엔드 과정: 융합 프로젝트 O+T</title>
      <link>https://joooii.tistory.com/42</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;437&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9eREo/dJMcagkEndi/Az336NLkqmQG59AsBrZaL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9eREo/dJMcagkEndi/Az336NLkqmQG59AsBrZaL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9eREo/dJMcagkEndi/Az336NLkqmQG59AsBrZaL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9eREo%2FdJMcagkEndi%2FAz336NLkqmQG59AsBrZaL1%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;787&quot; height=&quot;437&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;437&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;약 1달 간 진행한 융합 프로젝트에 대한 회고를 해보고자 한다. 다른 프로젝트와는 달리 프론트엔드만을 담당했기에 프론트엔드 위주로 작성하려고 한다.&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개가 있었으며, 4개 중 순위를 매기면 강사님과 매니저님이 각 주제에 배정해주는 방식이었다.&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 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  FE Github&lt;span&gt;&amp;nbsp;&lt;/span&gt;- User Repo&lt;/h4&gt;
&lt;figure id=&quot;og_1774878016114&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - OpenTheTaste/frontend: 유레카 3기 융합 프로젝트 O+T의 프론트엔드 입니다&quot; data-og-description=&quot;유레카 3기 융합 프로젝트 O+T의 프론트엔드 입니다. Contribute to OpenTheTaste/frontend development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/OpenTheTaste/frontend&quot; data-og-url=&quot;https://github.com/OpenTheTaste/frontend&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qOj2z/dJMb9efd15n/nhStp21OyJqIASVcl6Mw50/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/yuXnY/dJMb9gxlum5/e0CEDt9GFdl1R45UtFkUW0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/oVFbV/dJMb85WTpc8/jGQW73fCjHPgSKZIrGany0/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://github.com/OpenTheTaste/frontend&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/OpenTheTaste/frontend&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qOj2z/dJMb9efd15n/nhStp21OyJqIASVcl6Mw50/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/yuXnY/dJMb9gxlum5/e0CEDt9GFdl1R45UtFkUW0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/oVFbV/dJMb85WTpc8/jGQW73fCjHPgSKZIrGany0/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&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;GitHub - OpenTheTaste/frontend: 유레카 3기 융합 프로젝트 O+T의 프론트엔드 입니다&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;유레카 3기 융합 프로젝트 O+T의 프론트엔드 입니다. Contribute to OpenTheTaste/frontend development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.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;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;  FE Github - Admin Repo&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1774878074959&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - OpenTheTaste/admin: 유레카 3기 융합 프로젝트 O+T의 프론트엔드 백오피스 입니다&quot; data-og-description=&quot;유레카 3기 융합 프로젝트 O+T의 프론트엔드 백오피스 입니다. Contribute to OpenTheTaste/admin development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/OpenTheTaste/admin&quot; data-og-url=&quot;https://github.com/OpenTheTaste/admin&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/CAzAV/dJMb9kT23KX/l1ZcEZl0TFaYR7uPfLWHKk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bd3Ax5/dJMb9cBIbCv/5KRy3Em6IIEsKUos5jfnM0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cdlrS2/dJMb9jOm0W0/m4plbX3GFGo4BkKA41blTK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=1080_528_1194_651&quot;&gt;&lt;a href=&quot;https://github.com/OpenTheTaste/admin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/OpenTheTaste/admin&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/CAzAV/dJMb9kT23KX/l1ZcEZl0TFaYR7uPfLWHKk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bd3Ax5/dJMb9cBIbCv/5KRy3Em6IIEsKUos5jfnM0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cdlrS2/dJMb9jOm0W0/m4plbX3GFGo4BkKA41blTK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=1080_528_1194_651');&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;GitHub - OpenTheTaste/admin: 유레카 3기 융합 프로젝트 O+T의 프론트엔드 백오피스 입니다&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;유레카 3기 융합 프로젝트 O+T의 프론트엔드 백오피스 입니다. Contribute to OpenTheTaste/admin development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.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;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  주제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;콘텐츠 OTT 서비스 플랫폼 (&lt;span style=&quot;color: #1f2328; text-align: -webkit-left;&quot;&gt;데이터 분석 기반 맞춤형 콘텐츠 추천 서비스&lt;/span&gt;&lt;span style=&quot;background-color: #f6f8fa; color: #1f2328; text-align: -webkit-left;&quot;&gt;)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;⚙️ 프론트엔드 기술스택&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;React `^19.2.3`&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Next.js&lt;span&gt; &lt;/span&gt;`^16.1.6`&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;TypeScript&lt;span&gt; &lt;/span&gt;`^5.9.3`&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Tailwind CSS&lt;span&gt; `^4.2.1`&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Tanstack Query&lt;span&gt; `^5.90.21`&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Zustand `^5.0.11`&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;husky `^9.1.7`&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;eslint `^9.39.3`&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  유저 플로우차트&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.5814%; text-align: center;&quot;&gt;&lt;b&gt;온보딩&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.4186%; text-align: center;&quot;&gt;&lt;b&gt;홈 (메인)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.5814%; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;145&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqBBHs/dJMcaaY2DPI/PxozEuKNXAS4l85G4bKY5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqBBHs/dJMcaaY2DPI/PxozEuKNXAS4l85G4bKY5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqBBHs/dJMcaaY2DPI/PxozEuKNXAS4l85G4bKY5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqBBHs%2FdJMcaaY2DPI%2FPxozEuKNXAS4l85G4bKY5k%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;145&quot; height=&quot;201&quot; data-origin-width=&quot;145&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 34.4186%; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XMkuU/dJMcahw7J5I/HwTPC58uDK2kkT6S09Dnt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XMkuU/dJMcahw7J5I/HwTPC58uDK2kkT6S09Dnt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XMkuU/dJMcahw7J5I/HwTPC58uDK2kkT6S09Dnt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXMkuU%2FdJMcahw7J5I%2FHwTPC58uDK2kkT6S09Dnt1%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;465&quot; height=&quot;286&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;b&gt;콘텐츠 상세 페이지&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;b&gt;마이페이지&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K37p0/dJMcab4IUuS/t2qQ0RNNQb1sWlaV7kQdw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K37p0/dJMcab4IUuS/t2qQ0RNNQb1sWlaV7kQdw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K37p0/dJMcab4IUuS/t2qQ0RNNQb1sWlaV7kQdw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK37p0%2FdJMcab4IUuS%2Ft2qQ0RNNQb1sWlaV7kQdw1%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;735&quot; height=&quot;340&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KxmTI/dJMcaflLoIP/9DV2IaBJBDi0j8tUtneWB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KxmTI/dJMcaflLoIP/9DV2IaBJBDi0j8tUtneWB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KxmTI/dJMcaflLoIP/9DV2IaBJBDi0j8tUtneWB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKxmTI%2FdJMcaflLoIP%2F9DV2IaBJBDi0j8tUtneWB0%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;905&quot; height=&quot;388&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  백오피스 유저 플로우차트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자와 에디터 백오피스 접근이 가능하지만, 에디터가 접근 가능한 도메인은 숏폼 관리 + 모니터링(대시보드 x)였다.&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;관리자 + 에디터&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%; height: 337px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;height: 40px; text-align: center;&quot;&gt;&lt;b&gt;로그인&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 40px; text-align: center;&quot;&gt;&lt;b&gt;숏폼 관리&amp;nbsp;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 40px; text-align: center;&quot;&gt;&lt;b&gt;모니터링 (대시보드는 only 관리자)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 297px;&quot;&gt;
&lt;td style=&quot;height: 297px; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FMOQg/dJMcafMReES/rl4AWBWyZu8W7kqoXPy9Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FMOQg/dJMcafMReES/rl4AWBWyZu8W7kqoXPy9Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FMOQg/dJMcafMReES/rl4AWBWyZu8W7kqoXPy9Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFMOQg%2FdJMcafMReES%2Frl4AWBWyZu8W7kqoXPy9Sk%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;488&quot; height=&quot;547&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 297px; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wnXEU/dJMcajn5biH/V28T6u8HTKFECDvTDU1Ck1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wnXEU/dJMcajn5biH/V28T6u8HTKFECDvTDU1Ck1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wnXEU/dJMcajn5biH/V28T6u8HTKFECDvTDU1Ck1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwnXEU%2FdJMcajn5biH%2FV28T6u8HTKFECDvTDU1Ck1%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;727&quot; height=&quot;558&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;height: 297px; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k3gBt/dJMcajn5bjj/lEDgMpnufa1E6GGxKwE3jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k3gBt/dJMcajn5bjj/lEDgMpnufa1E6GGxKwE3jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k3gBt/dJMcajn5bjj/lEDgMpnufa1E6GGxKwE3jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk3gBt%2FdJMcajn5bjj%2FlEDgMpnufa1E6GGxKwE3jk%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;316&quot; height=&quot;241&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;only 관리자&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3954%; text-align: center;&quot;&gt;&lt;b&gt;시리즈 관리&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.6976%; text-align: center;&quot;&gt;&lt;b&gt;콘텐츠 관리&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.907%; text-align: center;&quot;&gt;&lt;b&gt;전체 유저 관리&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.3954%; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;583&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CAoN9/dJMcacvPfyW/t8SSwy2KqI0cHlocDUbSVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CAoN9/dJMcacvPfyW/t8SSwy2KqI0cHlocDUbSVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CAoN9/dJMcacvPfyW/t8SSwy2KqI0cHlocDUbSVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCAoN9%2FdJMcacvPfyW%2Ft8SSwy2KqI0cHlocDUbSVK%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;833&quot; height=&quot;583&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;583&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 15.6976%; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tXW1e/dJMcabKsE0A/QD0acSBXafTFmFMnDG1vj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tXW1e/dJMcabKsE0A/QD0acSBXafTFmFMnDG1vj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tXW1e/dJMcabKsE0A/QD0acSBXafTFmFMnDG1vj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtXW1e%2FdJMcabKsE0A%2FQD0acSBXafTFmFMnDG1vj0%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;818&quot; height=&quot;616&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 17.907%; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uvGHC/dJMcabRbmXZ/sk0bUyAP6PXRPofSiP7CT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uvGHC/dJMcabRbmXZ/sk0bUyAP6PXRPofSiP7CT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uvGHC/dJMcabRbmXZ/sk0bUyAP6PXRPofSiP7CT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuvGHC%2FdJMcabRbmXZ%2Fsk0bUyAP6PXRPofSiP7CT1%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;133&quot; height=&quot;237&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  피그마 - 디자인시스템 및 UI 구성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유저 페이지&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;835&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZoP8r/dJMcaiitufJ/JRicD3xvFMmAKkqK4QsRH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZoP8r/dJMcaiitufJ/JRicD3xvFMmAKkqK4QsRH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZoP8r/dJMcaiitufJ/JRicD3xvFMmAKkqK4QsRH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZoP8r%2FdJMcaiitufJ%2FJRicD3xvFMmAKkqK4QsRH1%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;1001&quot; height=&quot;835&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;835&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;백오피스&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMxu6f/dJMcadOW0CQ/iY50oa7bILShUKcqTWIv3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMxu6f/dJMcadOW0CQ/iY50oa7bILShUKcqTWIv3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMxu6f/dJMcadOW0CQ/iY50oa7bILShUKcqTWIv3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMxu6f%2FdJMcadOW0CQ%2FiY50oa7bILShUKcqTWIv3K%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;629&quot; height=&quot;872&quot; data-origin-width=&quot;575&quot; data-origin-height=&quot;872&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  내가 맡은 파트&lt;/h2&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;b&gt;Jira 세팅 및 Github Actions를 통한 자동화 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에도 저번 종합 프로젝트와 같이 Jira와 Github Actions, husky를 사용해서 프로젝트 일정 + 이슈 관리를 도맡아 했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저번에 했던 것과 비슷하게 적용했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래도 종합때와는 다른 점은 프론트엔드/백엔드 구분을 위해서 아래 이미지와 같이 에픽 앞에 [분야]를 붙이도록 강제했던 점이 다르다고 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-30 23.09.02.png&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmRvHc/dJMcaflLpeE/mJMSOI1LA3x8LWwvbjz7Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmRvHc/dJMcaflLpeE/mJMSOI1LA3x8LWwvbjz7Nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmRvHc/dJMcaflLpeE/mJMSOI1LA3x8LWwvbjz7Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmRvHc%2FdJMcaflLpeE%2FmJMSOI1LA3x8LWwvbjz7Nk%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;454&quot; height=&quot;156&quot; data-filename=&quot;스크린샷 2026-03-30 23.09.02.png&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;156&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 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt; 콘텐츠 (시리즈) 상세 페이지&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트 기획 상 콘텐츠(단편, 시리즈별 에피소드), 시리즈 원본 페이지를 따로 구분지어서 개발했어야 해서 url를 구분 짓는 게 쉽지 않았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;타 OTT는 어떻게 url을 설정했나 확인해보기 위해 직접 눈물의 시장 조사(= 결제)를 하기도 했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아쉬운 점은 프로젝트 후반으로 갈수록 점점 추가되는 쿼리스트링으로 인해.. 이런 식으로 url을 정리해서 파악해야 될 정도로 복잡해진 것이 조금 아쉽긴 했다.&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;754&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/63gAu/dJMcabDFvEA/AEmeaFku5GIQkMcfV9agx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/63gAu/dJMcabDFvEA/AEmeaFku5GIQkMcfV9agx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/63gAu/dJMcabDFvEA/AEmeaFku5GIQkMcfV9agx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F63gAu%2FdJMcabDFvEA%2FAEmeaFku5GIQkMcfV9agx0%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;413&quot; height=&quot;355&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 해당 페이지를 구현하면서 댓글, 플레이리스트 목록 등 다양한 기능을 구현해 볼 수 있어서 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;추가적으로&amp;nbsp;&lt;/span&gt;홈 화면에서 진입하는 플레이리스트에 따라서 우측 하단의 다음 재생목록을 다르게 구현하기 위해서 쿼리스트링에 playlist param을 추가하는 부분도 구현했다. (아래 notion 참고)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1774881294227&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;[FE] 진입 경로 기반 재생목록 유지 구조 설계 | Notion&quot; data-og-description=&quot;개요&quot; data-og-host=&quot;funky-brochure-236.notion.site&quot; data-og-source-url=&quot;https://funky-brochure-236.notion.site/FE-3327f2b9ae92807292a9e44cdedeec9f?pvs=74&quot; data-og-url=&quot;https://funky-brochure-236.notion.site/FE-3327f2b9ae92807292a9e44cdedeec9f&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/reOYg/dJMb8TB9GIy/0FQ0Imn30xTFNX23qfppmk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b8v8pD/dJMb8QemhwY/zTXnTWR57aZtKF9V6RLiOK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://funky-brochure-236.notion.site/FE-3327f2b9ae92807292a9e44cdedeec9f?pvs=74&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://funky-brochure-236.notion.site/FE-3327f2b9ae92807292a9e44cdedeec9f?pvs=74&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/reOYg/dJMb8TB9GIy/0FQ0Imn30xTFNX23qfppmk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b8v8pD/dJMb8QemhwY/zTXnTWR57aZtKF9V6RLiOK/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;[FE] 진입 경로 기반 재생목록 유지 구조 설계 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;funky-brochure-236.notion.site&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 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&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;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;영상 플레이어&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 핵심이라고 볼 수 있는 기능을 감사하게도 저에게 기회를 주셔서 구현을 맡게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;플레이어를 구현하기 위해 react-player 라이브러리를 사용하려고 했으나, 라이브러리 특성상 구현의 한계가 있어서 직접 hls.js 를 커스텀해서 사용하기로 했다. 이 과정에서 재생바, 음량 조절, 화질 조절, 뒤/앞으로 가기 등 직접 구현하느라 코드가 거의 600줄이 되는 경험을 하게 되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;관리해야 할 상태도 너무 많았고 생각보다 신경 써야 될 UX적 측면이 많아서 좋은 경험이었다.&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;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uiEYl/dJMcabDFvLJ/cryNAd5Hf4KDtqCjqOI7OK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uiEYl/dJMcabDFvLJ/cryNAd5Hf4KDtqCjqOI7OK/img.gif&quot; data-alt=&quot;player 구현&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uiEYl/dJMcabDFvLJ/cryNAd5Hf4KDtqCjqOI7OK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/uiEYl/dJMcabDFvLJ/cryNAd5Hf4KDtqCjqOI7OK/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;692&quot; height=&quot;359&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;player 구현&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;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;영상 플레이어 - 다음 영상 자동 재생&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;필수 구현 기능 중, 다음 영상 자동 재생 기능을 구현해야 했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 영상 길이의 95%가 지나면, 다음 영상 자동 재생 banner 컴포넌트를 호출해서 클릭 시 다음 화 재생 or n초 뒤 다음화 자동 재생 기능을 구현했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;영상 플레이어 - PIP 구현&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 PIP (Picture In Picture)를 구현할 생각은 없었다. 그러나 현직자 멘토링 이후 우리 프로젝트의 컨셉을 2030 멀티태스킹으로 잡게 되면서 PIP는 선택이 아닌, 필수가 되었다. 그래서 구글링 해보니 쉬울 것 같아서 도입하기로 했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러나 JavaScript에서 브라우저 pip를 지원해주는 것은 플레이어 DOM을 벗어나지 않는 경우에만 지원이 됐고, DOM을 벗어나는 순간 영상 재생이 안 돼서 PIP도 커스텀해서 구현하게 되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;PIP의 핵심은 현재 재생 위치를 유지하기 위해 m3u8 src, currentTime을 zustand에 저장하여 전역 상태로 관리하게끔 하였고, 브라우저를 4분할로 나눠서 pip를 드래그하면 상/하, 좌/우 위치를 비교하여 가까운 가장자리로 이동하게 하는 등 PIP의 추가적인 기능을 구현하였다.&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;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgDslw/dJMcacvPf3Z/SNL18n5nlXkZCWxFUKyl30/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgDslw/dJMcacvPf3Z/SNL18n5nlXkZCWxFUKyl30/img.gif&quot; data-alt=&quot;pip 구현&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgDslw/dJMcacvPf3Z/SNL18n5nlXkZCWxFUKyl30/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dgDslw/dJMcacvPf3Z/SNL18n5nlXkZCWxFUKyl30/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;754&quot; height=&quot;391&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;pip 구현&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애니메이션을 구현하기 위해 framer motion을 사용하려고 했지만, 레퍼런스가 많지 않아서 기본 CSS의 scrollSnap을 사용하여 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1774881123947&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;[OT-367] [FIX]: scroll-snap으로 다음화 슬라이드 애니메이션 구현 &amp;middot; OpenTheTaste/frontend@d9603a3&quot; data-og-description=&quot;@@ -130,31 +135,63 @@ export const ShortsContainer = ({ initialShortsId }: ShortsContainerProps) =&amp;gt; {&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/OpenTheTaste/frontend/commit/d9603a36ae64bb8620df11aa7852d66ebaf355d4&quot; data-og-url=&quot;https://github.com/OpenTheTaste/frontend/commit/d9603a36ae64bb8620df11aa7852d66ebaf355d4&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bvqjw4/dJMb8953GUn/yHxf1G2XnGZK5H1ucmaWuk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/RvKpd/dJMb84p8Lkk/Be19tuSC6IpjTgiyTkgnhk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/OpenTheTaste/frontend/commit/d9603a36ae64bb8620df11aa7852d66ebaf355d4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/OpenTheTaste/frontend/commit/d9603a36ae64bb8620df11aa7852d66ebaf355d4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bvqjw4/dJMb8953GUn/yHxf1G2XnGZK5H1ucmaWuk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/RvKpd/dJMb84p8Lkk/Be19tuSC6IpjTgiyTkgnhk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;[OT-367] [FIX]: scroll-snap으로 다음화 슬라이드 애니메이션 구현 &amp;middot; OpenTheTaste/frontend@d9603a3&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;@@ -130,31 +135,63 @@ export const ShortsContainer = ({ initialShortsId }: ShortsContainerProps) =&amp;gt; {&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OfPzW/dJMcabwSp7S/ZpgcuZSaPkZ9UOEebtjQC1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OfPzW/dJMcabwSp7S/ZpgcuZSaPkZ9UOEebtjQC1/img.gif&quot; data-alt=&quot;숏폼 스와이프/스크롤 애니메이션&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OfPzW/dJMcabwSp7S/ZpgcuZSaPkZ9UOEebtjQC1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/OfPzW/dJMcabwSp7S/ZpgcuZSaPkZ9UOEebtjQC1/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;733&quot; height=&quot;381&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;666&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&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;백엔드 분들이 API를 잘 구현해 주셔서 빠르게 구현할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;백오피스 - 관리자 로그인, middleware 처리&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;백오피스 관리자 로그인은 OAuth를 사용하지 않고, id와 pw를 입력하는 로그인 방식을 채택했다. 쿠키에 저장된 토큰이 없으면, 백오피스에 접근하지 못하도록 하고 `/auth/login`으로 redirect 되도록 구현했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이때, 백엔드의 domains 설정이 배포 주소 기준으로 되어있어서 로컬 프론트 개발환경에서는 쿠키가 정상적으로 전달되지 않는 문제가 발생했다. 그래서 개발환경에서는 middleware의 토큰 검증을 수행하지 않도록 처리했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이에 관한 트러블슈팅은 아래 포스트에 작성해두었다.&lt;/p&gt;
&lt;figure id=&quot;og_1774882316701&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;[Next.js] middleware (proxy)에서 쿠키를 읽지 못한다? 알고보니 도메인 설정 (ft. 로컬 프론트 - 배포 서&quot; data-og-description=&quot;  문제 상황⚙️ 발생한 환경 및 프로그램&amp;#96;next.js ^16.1.16&amp;#96; , &amp;#96;배포 서버 - 로컬 프론트&amp;#96;   문제 상황 정의Next.js 16의 &amp;#96;proxy.ts&amp;#96;를 도입하여 인증 토큰 검증 로직을 추가했을 때, 로컬 개발 환경(&amp;#96;local&quot; data-og-host=&quot;joooii.tistory.com&quot; data-og-source-url=&quot;https://joooii.tistory.com/38&quot; data-og-url=&quot;https://joooii.tistory.com/38&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wCWGk/dJMb87NWu4e/efl7GXQjvChFlbpsGckRQK/img.png?width=800&amp;amp;height=143&amp;amp;face=0_0_800_143,https://scrap.kakaocdn.net/dn/cOpaGS/dJMb9aKFcgA/fJr3aKHqK61fY2KVfK97Kk/img.png?width=800&amp;amp;height=143&amp;amp;face=0_0_800_143,https://scrap.kakaocdn.net/dn/b53930/dJMb8868S7N/nnurGLiUshnx4SCPNmZmr0/img.jpg?width=563&amp;amp;height=374&amp;amp;face=0_0_563_374&quot;&gt;&lt;a href=&quot;https://joooii.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://joooii.tistory.com/38&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wCWGk/dJMb87NWu4e/efl7GXQjvChFlbpsGckRQK/img.png?width=800&amp;amp;height=143&amp;amp;face=0_0_800_143,https://scrap.kakaocdn.net/dn/cOpaGS/dJMb9aKFcgA/fJr3aKHqK61fY2KVfK97Kk/img.png?width=800&amp;amp;height=143&amp;amp;face=0_0_800_143,https://scrap.kakaocdn.net/dn/b53930/dJMb8868S7N/nnurGLiUshnx4SCPNmZmr0/img.jpg?width=563&amp;amp;height=374&amp;amp;face=0_0_563_374');&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] middleware (proxy)에서 쿠키를 읽지 못한다? 알고보니 도메인 설정 (ft. 로컬 프론트 - 배포 서&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  문제 상황⚙️ 발생한 환경 및 프로그램`next.js ^16.1.16` , `배포 서버 - 로컬 프론트`   문제 상황 정의Next.js 16의 `proxy.ts`를 도입하여 인증 토큰 검증 로직을 추가했을 때, 로컬 개발 환경(`local&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;joooii.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 style=&quot;color: #333333; text-align: start;&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;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;백오피스 - 콘텐츠/숏폼 관리 - multipart upload 구현&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;영상과 메타데이터를 AWS S3에 멀티파트로 업로드하는 기능 구현을 맡았다. 이전 프로젝트에서 S3에 이미지 url을 서버로 보내서 서버가 S3에 업로드하는 방식을 구현해 본 경험이 있었다. 그렇기에 이번에도 그 방식대로 하면 될 줄 알았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러나 이번에는 &quot;영상&quot;을 업로드하는 것이라 서버에 부하를 최소화하기 위해서 클라이언트 측에서 S3에 직접 업로드하는 방식을 채택했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 내용은 아래 notion에 작성해두었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1774881949531&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;[FE] S3 멀티파트 업로드 로직 | Notion&quot; data-og-description=&quot;개요&quot; data-og-host=&quot;funky-brochure-236.notion.site&quot; data-og-source-url=&quot;https://funky-brochure-236.notion.site/FE-S3-3327f2b9ae9280089d80fd86cb488deb&quot; data-og-url=&quot;https://funky-brochure-236.notion.site/FE-S3-3327f2b9ae9280089d80fd86cb488deb&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cid9n4/dJMb8YpVucI/9VPYKVFrFq1lmW3cHKJ6n1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bv29hs/dJMb8TB9GLe/bkrbWKkV1rykCIhRmxH010/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://funky-brochure-236.notion.site/FE-S3-3327f2b9ae9280089d80fd86cb488deb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://funky-brochure-236.notion.site/FE-S3-3327f2b9ae9280089d80fd86cb488deb&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cid9n4/dJMb8YpVucI/9VPYKVFrFq1lmW3cHKJ6n1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bv29hs/dJMb8TB9GLe/bkrbWKkV1rykCIhRmxH010/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;[FE] S3 멀티파트 업로드 로직 | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;funky-brochure-236.notion.site&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  시연영상&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=LIwTDKfkAxQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=LIwTDKfkAxQ&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=LIwTDKfkAxQ&quot; data-video-width=&quot;0&quot; data-video-height=&quot;0&quot; data-video-origin-width=&quot;0&quot; data-video-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;유레카 부트캠프 융합 프로젝트 2조 O+T 시연영상&quot; data-video-thumbnail=&quot;&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/LIwTDKfkAxQ&quot; width=&quot;0&quot; height=&quot;0&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignRight&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=LIwTDKfkAxQ&quot; data-video-width=&quot;0&quot; data-video-height=&quot;0&quot; data-video-origin-width=&quot;0&quot; data-video-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;유레카 부트캠프 융합 프로젝트 2조 O+T 시연영상&quot; data-video-thumbnail=&quot;&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/LIwTDKfkAxQ&quot; width=&quot;0&quot; height=&quot;0&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  회고&amp;nbsp;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  좋았던 점&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&lt;span&gt; Hls.js를 사용한 플레이어 구현&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;이제 OTT는 선택이 아닌, 필수가 되어버린 사회에서 플레이어를 구현할 수 있는 경험은 정말 뜻깊고 소중한 경험이었다. 특히 팀프로젝트 + 경진 대회라는 특별 조건이 붙으니 UX 측면에서 더욱 고심해서 구현할 수 있어서 좋았다. 그리고 무엇보다 다른 팀원도 구현해보고 싶어 했는데 기회를 양보해 주어서 너무 감사하다..&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;실무 같은 팀프로젝트&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;이번에는 정말 '프론트엔드'만 맡아서 구현했기에 지난 두 번의 팀프로젝트와는 규모가 차원이 달랐다. 지난 프로젝트에서는 백엔드도 야매로 구현했기 때문에 규모가 크지 않았다. 그러나 이번엔 백엔드 반 팀원들과 기획부터 함께 진행을 하면서 백엔드 팀원과 회의를 정말 많이 하고, 어떤 방향으로 구현되면 좋을지에 관해서 여러 차례 대화를 나누게 되니 의사소통 방식에 있어서도 고민을 많이 하게 되었던 것 같다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt; FSD 아키텍처 도입 및 alias 올바르게 사용하기&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;지난 종합 프로젝트 심사위원으로 와주셨던 멘토님께서 이번 멘토링 때도 와주셔서 alias를 올바르게 사용하는 방법을 알려주셨다. 그래서 index.ts를 사용해서 경로 설정을 하니 실제로 컴포넌트명을 수정하는 일이 생겼을 때, 편하고 빠르게 수정할 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 실무에서 사용하는 폴더 구조도 자세히 설명해 주셔서 폴더 구조에 관해서도 많이 고민해 보게 되었다. 멘토님께서 말씀해 주신 폴더 구조에 대해서 찾아보니 FSD 아키텍처와 유사해서 이를 실무에서 사용한다는 것도 알게 되었고,&amp;nbsp;이를 도입해 보는 게 어떠냐는 피드백을 받았다. 사실 프로젝트 진행 중간에 받은 피드백이라 폴더 구조 리팩토링 작업을 하는 게 쉽지는 않았고, 헷갈리기도 엄청 헷갈렸다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래도 한번 적용해보고 나니 자신감이 생겼던 것 같다. 그래도 아직 어렵긴 하다 ..&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다음 프로젝트를 하게 되면 처음부터 FSD 아키텍처와 모노레포를 도입해서 구현하는 작업도 해봐야겠다는 생각이 들었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;UX 측면에서의 깊은 고찰&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;이번 프로젝트를 하면서 UX 측면에서 깊게 생각해 보게 되었다. 아무래도 기존 OTT들이 많다 보니, 더 UX를 신경 쓰게 됐던 것 같다. 특히 백엔드 팀원분들도 배포 환경을 둘러보면서 보이지 않았던 UX 측면에서의 문제를 잡아주셔서 더 세세하게 신경쓰게 된 것 같아 감사하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  아쉬웠던 점&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt; 멘토링 시간 활용&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 총 4번의 멘토링 시간이 있었다. 사실 2번째 멘토링 때까지는 프로젝트의 전반적인 기획, UI 등을 피드백받을 수 있어서 알차게 사용했다. &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;그러나 사실 매주 멘토링이 있다 보니, 마지막 멘토링을 잘 활용을 못한 점이 아쉬웠다. 그리고 &lt;/span&gt;멘토님께서 포트폴리오, 이력서도 봐주신다고 하셨는데 준비된 게 없어서 보여드릴 것이 없었던 것이 참 정말 너무 아쉽다.. 이 글을 보는 4기 분들은 미리 준비하고 가시면 좋은 기회가 될 것 같다!&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;▪︎&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;프로젝트 측면에서 아쉬운 점이 없는 게 아쉽다&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 이번 프로젝트에서는 정말 최선을 다했다고 말할 수 있다. 그나마 아쉬운 점은 짧은 시간에 많은 필수 요구 기능을 구현해야 했어서 AI를 많이 사용했다는 점이 아쉽다. 그래도 내가 하고 싶었던 플레이어나 업로드 기능을 구현해 봤다는 점이 너무 좋았다!&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;마치며 ..&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;끝날 것 같지 않았던 마지막 최종 융합 프로젝트가 끝나게 되었다. 처음엔 백엔드분들과 정말 어색했고 낯을 많이 가렸다. 아직도 첫날의 팀회식이 기억에 남는다 .. 정말 어색했다... 그래도 매일 아침마다 회의 + 스크럼을 진행하면서 어색함이 점차 풀렸고, 매주 하루씩은 점심을 같이 먹는 시간을 가져서 더욱 프로젝트에 애정이 가게 되었던 것 같다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 사실 본선 진출하게 되었을 때 너무 행복했고, 본선 전날까지 수정의 수정을 거치며 후회가 없을 만큼 수정을 했던 것 같다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;수상을 하지 못한 점은 살짝 아쉽긴 하지만 좋은 사람들과 좋은 경험을 할 수 있어서 깔끔하게 보내줄 수 있을 것 같다!&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저는 부트캠프에 대한 마지막 회고로 돌아오겠습니다&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>회고</category>
      <category>OTT</category>
      <category>유레카 부트캠프</category>
      <category>융합프로젝트</category>
      <category>팀프로젝트</category>
      <author>joooii</author>
      <guid isPermaLink="true">https://joooii.tistory.com/42</guid>
      <comments>https://joooii.tistory.com/42#entry42comment</comments>
      <pubDate>Tue, 31 Mar 2026 00:38:53 +0900</pubDate>
    </item>
    <item>
      <title>[회고] 유레카 부트캠프 프론트엔드 과정: 미니 프로젝트 핏로그 모음</title>
      <link>https://joooii.tistory.com/41</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;프론트엔드 회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://joooii.tistory.com/28&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://joooii.tistory.com/28&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774623181078&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;지난번에 미니프로젝트 핏로그의 백엔드 부분이 끝나고, 최종적으로 프론트엔드 작업을 시작하게 되었다.   FE Github GitHub - FitLog-ureca/FE: FitLog FE 레포지토리입니다.FitLog FE 레포지토리입니다. Co&quot; data-og-host=&quot;joooii.tistory.com&quot; data-og-source-url=&quot;https://joooii.tistory.com/28&quot; data-og-url=&quot;https://joooii.tistory.com/28&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bMgB7W/dJMb83SiEYI/w8eSPDOkLLa7F7xke1QrK0/img.png?width=800&amp;amp;height=429&amp;amp;face=0_0_800_429,https://scrap.kakaocdn.net/dn/bv04iR/dJMb9iaQX9S/JAaNBMxZ0L33crenpvhw5k/img.png?width=800&amp;amp;height=429&amp;amp;face=0_0_800_429,https://scrap.kakaocdn.net/dn/bcgTHm/dJMb9cBHWJU/iw4SjsYUKryBHIlreSlyh0/img.png?width=4288&amp;amp;height=884&amp;amp;face=0_0_4288_884&quot;&gt;&lt;a href=&quot;https://joooii.tistory.com/28&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://joooii.tistory.com/28&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bMgB7W/dJMb83SiEYI/w8eSPDOkLLa7F7xke1QrK0/img.png?width=800&amp;amp;height=429&amp;amp;face=0_0_800_429,https://scrap.kakaocdn.net/dn/bv04iR/dJMb9iaQX9S/JAaNBMxZ0L33crenpvhw5k/img.png?width=800&amp;amp;height=429&amp;amp;face=0_0_800_429,https://scrap.kakaocdn.net/dn/bcgTHm/dJMb9cBHWJU/iw4SjsYUKryBHIlreSlyh0/img.png?width=4288&amp;amp;height=884&amp;amp;face=0_0_4288_884');&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;지난번에 미니프로젝트 핏로그의 백엔드 부분이 끝나고, 최종적으로 프론트엔드 작업을 시작하게 되었다.   FE Github GitHub - FitLog-ureca/FE: FitLog FE 레포지토리입니다.FitLog FE 레포지토리입니다. Co&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;joooii.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;h2 data-ke-size=&quot;size26&quot;&gt;백엔드 회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://joooii.tistory.com/14&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://joooii.tistory.com/14&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774623212620&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;유레카에서 첫 미니 프로젝트를 시작했다. (두근두근)   주제 선정프론트엔드의 꽃인 To Do List를 적용하기 위해서 고민을 하던 중 운동 목표를 세우고 이에 따라 운동을 수행하면 캘린더에 깃&quot; data-og-host=&quot;joooii.tistory.com&quot; data-og-source-url=&quot;https://joooii.tistory.com/14&quot; data-og-url=&quot;https://joooii.tistory.com/14&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/D0Ywf/dJMb8VNvdCB/WdNyVzskshFTWRfTXDxob1/img.png?width=800&amp;amp;height=429&amp;amp;face=0_0_800_429,https://scrap.kakaocdn.net/dn/3yuqg/dJMb8T9Zjfw/zcaU3kDmoxvKFfmMET7QNk/img.png?width=800&amp;amp;height=429&amp;amp;face=0_0_800_429,https://scrap.kakaocdn.net/dn/qoE4X/dJMb8YXLlmN/fPnTldGtNFgkqaRpowl59k/img.png?width=3420&amp;amp;height=1766&amp;amp;face=0_0_3420_1766&quot;&gt;&lt;a href=&quot;https://joooii.tistory.com/14&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://joooii.tistory.com/14&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/D0Ywf/dJMb8VNvdCB/WdNyVzskshFTWRfTXDxob1/img.png?width=800&amp;amp;height=429&amp;amp;face=0_0_800_429,https://scrap.kakaocdn.net/dn/3yuqg/dJMb8T9Zjfw/zcaU3kDmoxvKFfmMET7QNk/img.png?width=800&amp;amp;height=429&amp;amp;face=0_0_800_429,https://scrap.kakaocdn.net/dn/qoE4X/dJMb8YXLlmN/fPnTldGtNFgkqaRpowl59k/img.png?width=3420&amp;amp;height=1766&amp;amp;face=0_0_3420_1766');&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;유레카에서 첫 미니 프로젝트를 시작했다. (두근두근)   주제 선정프론트엔드의 꽃인 To Do List를 적용하기 위해서 고민을 하던 중 운동 목표를 세우고 이에 따라 운동을 수행하면 캘린더에 깃&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;joooii.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;</description>
      <category>회고</category>
      <author>joooii</author>
      <guid isPermaLink="true">https://joooii.tistory.com/41</guid>
      <comments>https://joooii.tistory.com/41#entry41comment</comments>
      <pubDate>Fri, 27 Mar 2026 23:53:52 +0900</pubDate>
    </item>
    <item>
      <title>[134일차] Tanstack Infinite Queries 톺아보기</title>
      <link>https://joooii.tistory.com/40</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;TIL 썸넬.png&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYRYhK%2FdJMcaiuZPt4%2FKqHVKvHoZkzxem1LM1pdk0%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;644&quot; height=&quot;328&quot; data-filename=&quot;TIL 썸넬.png&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1020&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;융합 프로젝트를 진행하면서 무한스크롤과 관련된 파트는 전부 다 Infinite Queries를 사용하여 구현하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2026-03-2400.27.23-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VvpJY/dJMcacWKUXc/oZkhoKPqe9GrSoPxJ6oGHk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VvpJY/dJMcacWKUXc/oZkhoKPqe9GrSoPxJ6oGHk/img.gif&quot; data-alt=&quot;무한스크롤 (아래 자세히 보면 로딩 아이콘이 표시되며 다음 Page를 렌더링한다)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VvpJY/dJMcacWKUXc/oZkhoKPqe9GrSoPxJ6oGHk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/VvpJY/dJMcacWKUXc/oZkhoKPqe9GrSoPxJ6oGHk/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;635&quot; height=&quot;330&quot; data-filename=&quot;2026-03-2400.27.23-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;무한스크롤 (아래 자세히 보면 로딩 아이콘이 표시되며 다음 Page를 렌더링한다)&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;왜 Infinite Queries를 사용하면 좋은지 한번 톺아보고자 한다.&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;400&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LqBSD/dJMcaaq71WQ/jcEkqwmbP2xFHM1qAqZqe0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LqBSD/dJMcaaq71WQ/jcEkqwmbP2xFHM1qAqZqe0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LqBSD/dJMcaaq71WQ/jcEkqwmbP2xFHM1qAqZqe0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLqBSD%2FdJMcaaq71WQ%2FjcEkqwmbP2xFHM1qAqZqe0%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;331&quot; height=&quot;331&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;400&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;h2 data-end=&quot;72&quot; data-start=&quot;47&quot; data-section-id=&quot;o4ace4&quot; data-ke-size=&quot;size26&quot;&gt;Infinite Queries란?&lt;/h2&gt;
&lt;p data-end=&quot;197&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;Infinite Queries는 &lt;span&gt;&lt;span&gt;TanStack Query&lt;/span&gt;&lt;/span&gt;에서 제공하는 기능으로, &lt;b&gt;페이지네이션이 필요한 데이터를 &amp;ldquo;연속된 페이지 형태&amp;rdquo;로 관리하는 데이터 패칭 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;275&quot; data-start=&quot;199&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 useQuery가 단일 요청-응답 구조라면, useInfiniteQuery는 여러 페이지 데이터를 누적하여 관리한다.&lt;/p&gt;
&lt;p data-end=&quot;290&quot; data-start=&quot;277&quot; data-ke-size=&quot;size16&quot;&gt;핵심은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;358&quot; data-start=&quot;292&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;322&quot; data-start=&quot;292&quot; data-section-id=&quot;1rvjq5b&quot;&gt;페이지 단위 데이터를 pages[] 형태로 관리&lt;/li&gt;
&lt;li data-end=&quot;358&quot; data-start=&quot;323&quot; data-section-id=&quot;1en4ab0&quot;&gt;다음 페이지 요청을 pageParam 기반으로 자동 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;tanstack_infinite_query_structure.svg&quot; data-origin-width=&quot;165&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJm1pg/dJMcagrkpc4/f9wkKijoNRkIM3Fa4x7J31/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJm1pg/dJMcagrkpc4/f9wkKijoNRkIM3Fa4x7J31/tfile.svg&quot; data-alt=&quot;Infinite Queries의 구조와 동작 방식&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJm1pg/dJMcagrkpc4/f9wkKijoNRkIM3Fa4x7J31/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJm1pg%2FdJMcagrkpc4%2Ff9wkKijoNRkIM3Fa4x7J31%2Ftfile.svg&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;604&quot; height=&quot;549&quot; data-filename=&quot;tanstack_infinite_query_structure.svg&quot; data-origin-width=&quot;165&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Infinite Queries의 구조와 동작 방식&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;384&quot; data-start=&quot;365&quot; data-section-id=&quot;x6o0jm&quot; data-ke-size=&quot;size26&quot;&gt;어떤 상황에서 어떻게 쓰이나요?&lt;/h2&gt;
&lt;p data-end=&quot;403&quot; data-start=&quot;386&quot; data-ke-size=&quot;size16&quot;&gt;다음과 같은 상황에서 사용된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;502&quot; data-start=&quot;405&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;438&quot; data-start=&quot;405&quot; data-section-id=&quot;1ljxt4s&quot;&gt;무한 스크롤 (SNS 피드, 콘텐츠 리스트, 숏폼 목록)&lt;/li&gt;
&lt;li data-end=&quot;463&quot; data-start=&quot;439&quot; data-section-id=&quot;1qr7885&quot;&gt;&amp;ldquo;더보기&amp;rdquo; 버튼 기반 pagination&lt;/li&gt;
&lt;li data-end=&quot;502&quot; data-start=&quot;464&quot; data-section-id=&quot;145jmf8&quot;&gt;cursor 기반 API (lastId, nextCursor 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;523&quot; data-start=&quot;504&quot; data-ke-size=&quot;size16&quot;&gt;기본적인 사용 구조는 아래 코드처럼 사용한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1774279022605&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const {
  data,
  fetchNextPage,
  hasNextPage,
} = useInfiniteQuery({
  queryKey: [&quot;list&quot;],
  queryFn: ({ pageParam = 0 }) =&amp;gt; fetchList(pageParam),
  getNextPageParam: (lastPage) =&amp;gt; lastPage.nextCursor,
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;gt; 응답에서 다음 cursor 추출 -&amp;gt; `fetchNextPage()` 호출 시 자동으로 다음 데이터 요청 -&amp;gt; 데이터는 `pages[]`에 누적되는 흐름이다.&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 style=&quot;color: #000000; text-align: start;&quot; data-section-id=&quot;x6o0jm&quot; data-start=&quot;365&quot; data-end=&quot;384&quot; data-ke-size=&quot;size26&quot;&gt;장점&lt;/h2&gt;
&lt;h3 data-end=&quot;890&quot; data-start=&quot;875&quot; data-section-id=&quot;1h81j8&quot; data-ke-size=&quot;size23&quot;&gt;1. 상태 관리 제거&lt;/h3&gt;
&lt;p data-end=&quot;965&quot; data-start=&quot;892&quot; data-ke-size=&quot;size16&quot;&gt;기존 방식에서는 page, cursor 등을 직접 관리해야 했지만, Infinite Query는 이를 내부적으로 처리한다.&lt;/p&gt;
&lt;p data-end=&quot;982&quot; data-start=&quot;967&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; 불필요한 상태 코드 감소&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;982&quot; data-start=&quot;967&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1007&quot; data-start=&quot;989&quot; data-section-id=&quot;16k9z0a&quot; data-ke-size=&quot;size23&quot;&gt;2. 데이터 흐름이 일관됨&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1085&quot; data-start=&quot;1009&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1059&quot; data-start=&quot;1009&quot; data-section-id=&quot;1cc6xhr&quot;&gt;queryFn &amp;rarr; getNextPageParam &amp;rarr; fetchNextPage&lt;/li&gt;
&lt;li data-end=&quot;1085&quot; data-start=&quot;1060&quot; data-section-id=&quot;zygn9o&quot;&gt;이 구조가 고정되어 있어 로직이 단순해진다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; 유지보수 시 이해 비용 감소&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1130&quot; data-start=&quot;1111&quot; data-section-id=&quot;12bi3tz&quot; data-ke-size=&quot;size23&quot;&gt;3. 캐싱이 자동으로 적용됨&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1188&quot; data-start=&quot;1132&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1147&quot; data-start=&quot;1132&quot; data-section-id=&quot;z1l7cw&quot;&gt;이전 페이지 데이터 유지&lt;/li&gt;
&lt;li data-end=&quot;1168&quot; data-start=&quot;1148&quot; data-section-id=&quot;1e0kwjt&quot;&gt;동일 queryKey 기준 재사용&lt;/li&gt;
&lt;li data-end=&quot;1188&quot; data-start=&quot;1169&quot; data-section-id=&quot;25sjnb&quot;&gt;뒤로가기 시 데이터 재요청 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1210&quot; data-start=&quot;1190&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; UX 개선 + 네트워크 비용 절감&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1210&quot; data-start=&quot;1190&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1232&quot; data-start=&quot;1217&quot; data-section-id=&quot;d2kcrs&quot; data-ke-size=&quot;size23&quot;&gt;4. 중복 요청 방지&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1280&quot; data-start=&quot;1234&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1250&quot; data-start=&quot;1234&quot; data-section-id=&quot;185m8z9&quot;&gt;내부적으로 요청 상태 관리&lt;/li&gt;
&lt;li data-end=&quot;1280&quot; data-start=&quot;1251&quot; data-section-id=&quot;104743v&quot;&gt;isFetchingNextPage로 제어 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1301&quot; data-start=&quot;1282&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; race condition 감소&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1301&quot; data-start=&quot;1282&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1331&quot; data-start=&quot;1308&quot; data-section-id=&quot;1dnfhe1&quot; data-ke-size=&quot;size23&quot;&gt;5. 무한 스크롤과 자연스럽게 결합&lt;/h3&gt;
&lt;p data-end=&quot;1389&quot; data-start=&quot;1333&quot; data-ke-size=&quot;size16&quot;&gt;IntersectionObserver와 결합하면 거의 표준적인 무한 스크롤 구조를 만들 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1389&quot; data-start=&quot;1333&quot; 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;intersection_observer_infinite_scroll.svg&quot; data-origin-width=&quot;255&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/txi34/dJMcacCtZku/wPlV4z6JTbIvzSg6kPzH90/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/txi34/dJMcacCtZku/wPlV4z6JTbIvzSg6kPzH90/tfile.svg&quot; data-alt=&quot;IntersectionObserver + fetchNextPage 연결 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/txi34/dJMcacCtZku/wPlV4z6JTbIvzSg6kPzH90/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftxi34%2FdJMcacCtZku%2FwPlV4z6JTbIvzSg6kPzH90%2Ftfile.svg&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;528&quot; height=&quot;311&quot; data-filename=&quot;intersection_observer_infinite_scroll.svg&quot; data-origin-width=&quot;255&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IntersectionObserver + fetchNextPage 연결 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;384&quot; data-start=&quot;365&quot; data-section-id=&quot;x6o0jm&quot; data-ke-size=&quot;size26&quot;&gt;단점&lt;/h2&gt;
&lt;h3 data-end=&quot;1428&quot; data-start=&quot;1409&quot; data-section-id=&quot;1140kox&quot; data-ke-size=&quot;size23&quot;&gt;1. 데이터 구조가 복잡해짐&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1774279239215&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data.pages.flatMap(page =&amp;gt; page.items)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1522&quot; data-start=&quot;1481&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1506&quot; data-start=&quot;1481&quot; data-section-id=&quot;kdkp3a&quot;&gt;단순 배열이 아니라 pages[] 구조&lt;/li&gt;
&lt;li data-end=&quot;1522&quot; data-start=&quot;1507&quot; data-section-id=&quot;13wa8no&quot;&gt;매번 flatten 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1545&quot; data-start=&quot;1529&quot; data-section-id=&quot;1pxy67m&quot; data-ke-size=&quot;size23&quot;&gt;2. 메모리 사용 증가&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1583&quot; data-start=&quot;1547&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1560&quot; data-start=&quot;1547&quot; data-section-id=&quot;s66zpc&quot;&gt;페이지가 계속 누적됨&lt;/li&gt;
&lt;li data-end=&quot;1583&quot; data-start=&quot;1561&quot; data-section-id=&quot;1m6gj7k&quot;&gt;리스트가 길어질수록 메모리 부담 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1627&quot; data-start=&quot;1585&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; virtualization (react-window 등) 필요할 수 있음&lt;/p&gt;
&lt;p data-end=&quot;1627&quot; data-start=&quot;1585&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1654&quot; data-start=&quot;1634&quot; data-section-id=&quot;1qf7le&quot; data-ke-size=&quot;size23&quot;&gt;3. 부분 업데이트가 까다로움&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1692&quot; data-start=&quot;1656&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1692&quot; data-start=&quot;1656&quot; data-section-id=&quot;tsz307&quot;&gt;특정 아이템만 수정하려면 전체 pages를 순회해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1725&quot; data-start=&quot;1699&quot; data-section-id=&quot;1ks5owl&quot; data-ke-size=&quot;size23&quot;&gt;4. SSR / 초기 데이터 처리 난이도&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1777&quot; data-start=&quot;1727&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1750&quot; data-start=&quot;1727&quot; data-section-id=&quot;141vhgd&quot;&gt;서버 컴포넌트 환경에서 다루기 까다로움&lt;/li&gt;
&lt;li data-end=&quot;1777&quot; data-start=&quot;1751&quot; data-section-id=&quot;xq7eyw&quot;&gt;hydration 시 구조 맞추는 작업 필요&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1800&quot; data-start=&quot;1784&quot; data-section-id=&quot;d3uwf9&quot; data-ke-size=&quot;size26&quot;&gt;기존 구현 방식과의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1811&quot; data-start=&quot;1802&quot; data-section-id=&quot;1fies6u&quot; data-ke-size=&quot;size23&quot;&gt;기존 방식&lt;/h3&gt;
&lt;pre id=&quot;code_1774279281931&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [page, setPage] = useState(0);
const [list, setList] = useState([]);

const fetchData = async () =&amp;gt; {
  const res = await api(page);
  setList(prev =&amp;gt; [...prev, ...res.items]);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제점)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2060&quot; data-start=&quot;2017&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2027&quot; data-start=&quot;2017&quot; data-section-id=&quot;1r6sgax&quot;&gt;상태 직접 관리&lt;/li&gt;
&lt;li data-end=&quot;2044&quot; data-start=&quot;2028&quot; data-section-id=&quot;1hh10pw&quot;&gt;중복 요청 방지 직접 구현&lt;/li&gt;
&lt;li data-end=&quot;2052&quot; data-start=&quot;2045&quot; data-section-id=&quot;14lrfib&quot;&gt;캐싱 없음&lt;/li&gt;
&lt;li data-end=&quot;2060&quot; data-start=&quot;2053&quot; data-section-id=&quot;91tmv5&quot;&gt;로직 분산&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;2088&quot; data-start=&quot;2067&quot; data-section-id=&quot;v4y9pi&quot; data-ke-size=&quot;size23&quot;&gt;Infinite Query 방식&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1774279309631&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useInfiniteQuery({
  queryKey: [&quot;list&quot;],
  queryFn,
  getNextPageParam,
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;before_after_comparison.svg&quot; data-origin-width=&quot;243&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYGpkH/dJMcaf61jnp/ZUZrZDqZtI1WezJxpGR0e1/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYGpkH/dJMcaf61jnp/ZUZrZDqZtI1WezJxpGR0e1/tfile.svg&quot; data-alt=&quot;기존 방식 vs Infinite Query 비교 도식화&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYGpkH/dJMcaf61jnp/ZUZrZDqZtI1WezJxpGR0e1/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYGpkH%2FdJMcaf61jnp%2FZUZrZDqZtI1WezJxpGR0e1%2Ftfile.svg&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;581&quot; height=&quot;359&quot; data-filename=&quot;before_after_comparison.svg&quot; data-origin-width=&quot;243&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기존 방식 vs Infinite Query 비교 도식화&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 융합프로젝트에서 유용하게 사용한 Infinite Queries에 대해서 알아보았다. 평소 무한스크롤을 구현할 때 tanstack을 사용하지 않고 직접 구현하느라 오랜 시간이 소요됐었는데, 이렇게 쉽게 무한스크롤을 구현할 수 있는 것에 감탄했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 덧 정말 마지막 부트캠프에서의 TIL이 되었다.&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;TIL 담당자님 그동안 감사했습니다 !!!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xmhxd/dJMcahRiRb9/e9UMD9gkZZcqnSKL6Grprk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xmhxd/dJMcahRiRb9/e9UMD9gkZZcqnSKL6Grprk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xmhxd/dJMcahRiRb9/e9UMD9gkZZcqnSKL6Grprk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXmhxd%2FdJMcahRiRb9%2Fe9UMD9gkZZcqnSKL6Grprk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;453&quot; height=&quot;362&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;588&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;figure id=&quot;og_1774279896602&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;Infinite Queries &amp;ndash; React Query 한글 문서&quot; data-og-description=&quot;Nextra: the next docs builder&quot; data-og-host=&quot;react-query.kro.kr&quot; data-og-source-url=&quot;https://react-query.kro.kr/docs/guides-and-concepts/infinite-queries&quot; data-og-url=&quot;https://react-query.kro.kr/docs/guides-and-concepts/infinite-queries&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://react-query.kro.kr/docs/guides-and-concepts/infinite-queries&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react-query.kro.kr/docs/guides-and-concepts/infinite-queries&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;Infinite Queries &amp;ndash; React Query 한글 문서&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Nextra: the next docs builder&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react-query.kro.kr&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>TIL</category>
      <category>멀티캠퍼스부트캠프</category>
      <category>부트캠프후기</category>
      <category>유레카 프론트엔드 부트캠프</category>
      <author>joooii</author>
      <guid isPermaLink="true">https://joooii.tistory.com/40</guid>
      <comments>https://joooii.tistory.com/40#entry40comment</comments>
      <pubDate>Tue, 24 Mar 2026 00:43:01 +0900</pubDate>
    </item>
    <item>
      <title>[130일차] 사용자 경험 향상을 위한 Skeleton UI 적용하기</title>
      <link>https://joooii.tistory.com/39</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;TIL 썸넬.png&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYRYhK%2FdJMcaiuZPt4%2FKqHVKvHoZkzxem1LM1pdk0%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;644&quot; height=&quot;328&quot; data-filename=&quot;TIL 썸넬.png&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1020&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;웹 개발에서 성능 최적화만큼이나 중요한 것이 바로 '사용자가 느끼는 속도'이다. 오늘은 단순히 데이터를 불러오는 것을 넘어, 대기 시간마저도 사용자 경험(UX)의 일부로 승화시키는 &lt;b data-index-in-node=&quot;101&quot; data-path-to-node=&quot;1&quot;&gt;스켈레톤 UI&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;746&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTbftk/dJMcagY4SQ8/I0QhOOC9OVyjZWmuTtD1k0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTbftk/dJMcagY4SQ8/I0QhOOC9OVyjZWmuTtD1k0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTbftk/dJMcagY4SQ8/I0QhOOC9OVyjZWmuTtD1k0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTbftk%2FdJMcagY4SQ8%2FI0QhOOC9OVyjZWmuTtD1k0%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;297&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;420&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;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;1. 스켈레톤 UI란 무엇인가?&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;스켈레톤 UI&lt;/b&gt;는 데이터가 로드되기 전, 실제 콘텐츠가 들어갈 자리에 미리 박스나 선 등으로 레이아웃의 '뼈대'를 그려주는 기법이다. 과거에는 화면 중앙에 빙글빙글 돌아가는 '스피너(Spinner)'를 주로 사용했지만, 이는 사용자에게 &quot;나는 지금 기다리고 있다&quot;는 부정적인 인식을 강조하는 경향이 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;반면 스켈레톤 UI는 다음과 같은 장점을 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;심리적 안정감:&lt;/b&gt; 화면의 전체적인 구조를 미리 보여줌으로써 콘텐츠가 곧 나타날 것이라는 예측 가능성을 제공한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;레이아웃 시프트(CLS) 방지:&lt;/b&gt; 데이터가 로드된 후 갑자기 요소들이 툭툭 튀어나오며 화면 밀림 현상이 발생하는 것을 막아준다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;인지적 로드 감소:&lt;/b&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;p data-ke-size=&quot;size16&quot;&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;493&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGh7xK/dJMcaibyzNL/zfJdelZm8SRG30u9bTYHB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGh7xK/dJMcaibyzNL/zfJdelZm8SRG30u9bTYHB1/img.png&quot; data-alt=&quot;정적&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGh7xK/dJMcaibyzNL/zfJdelZm8SRG30u9bTYHB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGh7xK%2FdJMcaibyzNL%2FzfJdelZm8SRG30u9bTYHB1%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;404&quot; height=&quot;379&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;462&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;&lt;b&gt;동적 스켈레톤&lt;/b&gt;은 아래 이미지와 같이 정적 스켈레톤 화면에 그라데이션이나 움직임이 추가된 형태의 UI이다. 움직임이 추가됨에 따라 사용자는 로딩중인 것을 인지하기 더 쉽다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2026-03-1721-22-47-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lKSTy/dJMcac3qOPu/5rHzkksESt6rd1GiyK9PP0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lKSTy/dJMcac3qOPu/5rHzkksESt6rd1GiyK9PP0/img.gif&quot; data-alt=&quot;동적&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lKSTy/dJMcac3qOPu/5rHzkksESt6rd1GiyK9PP0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/lKSTy/dJMcac3qOPu/5rHzkksESt6rd1GiyK9PP0/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;670&quot; height=&quot;377&quot; data-filename=&quot;2026-03-1721-22-47-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size26&quot;&gt;2. Next.js에서의 적용 방식 (App Router 기준)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;Next.js는 서버 컴포넌트와 스트리밍(Streaming) 기능을 지원하기 때문에 스켈레톤 UI를 적용하기에 최적화된 환경을 제공한다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size20&quot;&gt;1️⃣ loading.tsx를 통한 자동 스트리밍&lt;/h4&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;Next.js App Router에서는 폴더 구조 내에 loading.tsx 파일만 만들어두면, 해당 경로의 페이지 컴포넌트가 로드되는 동안 자동으로 이 파일을 보여준다. 이는 별도의 상태 관리가 필요 없어 매우 편리하다.&lt;/p&gt;
&lt;pre id=&quot;code_1773749963566&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Loading() {
  return (
    &amp;lt;div className=&quot;p-4 space-y-4&quot;&amp;gt;
      {/* 타이틀 스켈레톤 */}
      &amp;lt;div className=&quot;h-8 w-1/3 bg-gray-200 rounded animate-pulse&quot; /&amp;gt;
      {/* 카드 리스트 스켈레톤 */}
      {[1, 2, 3].map((i) =&amp;gt; (
        &amp;lt;div key={i} className=&quot;h-32 w-full bg-gray-100 rounded-lg animate-pulse&quot; /&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;h4 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size20&quot;&gt;2️⃣ Suspense를 이용한 정밀 제어&lt;/h4&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;페이지 전체가 로딩되는 것이 아니라, 특정 API 호출이 늦어지는 컴포넌트만 따로 스켈레톤을 처리하고 싶을 때 사용한다. 이를 통해 '중요한 정보'는 먼저 보여주고, '부수적인 정보'는 로딩 중으로 표시하는 전략적 렌더링이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1773749992200&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Suspense fallback={&amp;lt;CardSkeleton /&amp;gt;}&amp;gt;
  &amp;lt;SlowDataComponent /&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;3. 스켈레톤 UI를 더 쉽고 우아하게 적용하는 방법&lt;/h2&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;직접 CSS를 짜는 것도 좋지만, 생산성과 퀄리티를 위해 다음과 같은 전략을 사용하는 것이 효율적이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;Tailwind CSS의 animate-pulse 활용:&lt;/b&gt; 별도의 라이브러리 설치 없이도 부드러운 깜빡임 효과를 줄 수 있어 가장 가성비가 좋다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0&quot;&gt;이미지 플레이스홀더 (next/image):&lt;/b&gt; Next.js의 Image 컴포넌트에서 placeholder=&quot;blur&quot; 속성을 사용하면, 별도의 스켈레톤 없이도 이미지가 저해상도에서 고해상도로 부드럽게 전환되는 효과를 얻을 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,0&quot;&gt;Shadcn/ui 또는 Radix UI:&lt;/b&gt; 이미 잘 만들어진 Skeleton 프리미티브를 제공하므로, 프로젝트의 디자인 시스템에 맞춰 스타일만 수정하면 즉시 실전 투입이 가능하다.&lt;/li&gt;
&lt;li&gt;추가적으로 &lt;b&gt;react-loading-skelton&lt;/b&gt;과 같은 라이브러리를 활용하는 방법이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2472&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PlMDO/dJMcacWFQQg/F9HOUPT5UJrADxKieNhPtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PlMDO/dJMcacWFQQg/F9HOUPT5UJrADxKieNhPtk/img.png&quot; data-alt=&quot;https://www.npmjs.com/package/react-loading-skeleton&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PlMDO/dJMcacWFQQg/F9HOUPT5UJrADxKieNhPtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPlMDO%2FdJMcacWFQQg%2FF9HOUPT5UJrADxKieNhPtk%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;809&quot; height=&quot;348&quot; data-origin-width=&quot;2472&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.npmjs.com/package/react-loading-skeleton&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;스켈레톤 UI를 단순히 '그려 넣는 것'에 그치지 않고, 깊이 있게 고민해 본 결과 몇 가지 중요한 교훈을 얻었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23&quot;&gt;첫째, 실제 데이터와의 '싱크(Sync)'가 핵심이다.&lt;/b&gt; 스켈레톤은 회색 박스이지만, 그 크기와 여백(Margin/Padding)은 실제 데이터가 들어왔을 때와 정확히 일치해야 한다. 만약 1px이라도 차이가 나면 데이터가 로드되는 순간 화면이 미세하게 '덜컥'거리는 불쾌한 경험을 주게 된다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24&quot;&gt;둘째, '반짝임(Shimmer)' 효과의 속도 조절이다.&lt;/b&gt; 너무 빠르게 깜빡이면 사용자의 눈을 피로하게 만들고, 너무 느리면 정지된 화면처럼 느껴진다. 보통 1.5초에서 2초 사이의 부드러운 루프가 가장 적당하다는 것을 실험을 통해 느꼈다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25&quot;&gt;셋째, 모든 곳에 남발하지 말아야 한다.&lt;/b&gt; 초고속으로 로드되는 페이지에 굳이 스켈레톤을 넣으면 오히려 화면이 번쩍거리는 역효과를 낸다. 약 200~300ms 이상의 지연이 예상되는 곳에만 전략적으로 배치하는 것이 진정한 최적화이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;어느 덧 부트캠프에서의 마지막 TIL이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; 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;800&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDRB7F/dJMcagrfnMJ/HquRLhjsHT1RBKaUSE7k7K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDRB7F/dJMcagrfnMJ/HquRLhjsHT1RBKaUSE7k7K/img.jpg&quot; data-alt=&quot;다시 무직백수로 돌아갑니다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDRB7F/dJMcagrfnMJ/HquRLhjsHT1RBKaUSE7k7K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDRB7F%2FdJMcagrfnMJ%2FHquRLhjsHT1RBKaUSE7k7K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;282&quot; height=&quot;338&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;다시 무직백수로 돌아갑니다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BFImn/dJMcac3qOZh/htt0XsJVrWaodMCLGEKT91/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BFImn/dJMcac3qOZh/htt0XsJVrWaodMCLGEKT91/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BFImn/dJMcac3qOZh/htt0XsJVrWaodMCLGEKT91/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBFImn%2FdJMcac3qOZh%2Fhtt0XsJVrWaodMCLGEKT91%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;339&quot; height=&quot;254&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;512&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;</description>
      <category>TIL</category>
      <category>멀티캠퍼스부트캠프</category>
      <category>부트캠프후기</category>
      <category>유레카프론트엔드</category>
      <author>joooii</author>
      <guid isPermaLink="true">https://joooii.tistory.com/39</guid>
      <comments>https://joooii.tistory.com/39#entry39comment</comments>
      <pubDate>Tue, 17 Mar 2026 21:35:40 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] middleware (proxy)에서 쿠키를 읽지 못한다? 알고보니 도메인 설정 (ft. 로컬 프론트 - 배포 서버)</title>
      <link>https://joooii.tistory.com/38</link>
      <description>&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  문제 상황&lt;/h2&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;⚙️ 발생한 환경 및 프로그램&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`next.js ^16.1.16` , `배포 서버 - 로컬 프론트`&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 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;  문제 상황 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 16의 `proxy.ts`를 도입하여 &lt;b&gt;인증 토큰 검증 로직&lt;/b&gt;을 추가했을 때, &lt;b&gt;로컬 개발 환경&lt;/b&gt;(`localhost:3000`)에서 배포된 백엔드 서버와 통신하는 구조에서 로그인을 하면 login API가 배포/로컬 url로 각각 호출(총 2번) 되며 계속 `/auth/login`으로 튕기는 문제가 발생하였다.&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;1620&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVTcKk/dJMcaiCvMO4/5yGNEk4kKmZaPY0Tt3pbwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVTcKk/dJMcaiCvMO4/5yGNEk4kKmZaPY0Tt3pbwK/img.png&quot; data-alt=&quot;배포 주소&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVTcKk/dJMcaiCvMO4/5yGNEk4kKmZaPY0Tt3pbwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVTcKk%2FdJMcaiCvMO4%2F5yGNEk4kKmZaPY0Tt3pbwK%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;687&quot; height=&quot;123&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;290&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-12 01.01.52.png&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lvmh8/dJMcaaR3Rhq/VHukzog6snKxyeurpcw6CK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lvmh8/dJMcaaR3Rhq/VHukzog6snKxyeurpcw6CK/img.png&quot; data-alt=&quot;로컬 주소&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lvmh8/dJMcaaR3Rhq/VHukzog6snKxyeurpcw6CK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLvmh8%2FdJMcaaR3Rhq%2FVHukzog6snKxyeurpcw6CK%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;699&quot; height=&quot;122&quot; data-filename=&quot;스크린샷 2026-03-12 01.01.52.png&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;284&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인 API는 200 응답을 받고 쿠키도 브라우저 Application 탭에 저장되는 것을 확인&lt;/li&gt;
&lt;li&gt;하지만 `router.push(&quot;/&quot;)`로 이동 시 미들웨어가 토큰이 없다고 판단하여 `/auth/login`으로 리다이렉트&lt;/li&gt;
&lt;li&gt;Network 탭에서 `/series` 접근 시 307 리다이렉트로 다시 `/auth/login`으로 튕기는 것 확인&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  해결방안 및 과정&lt;/h2&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;  해결하기 위한 노력 및 시행착오&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. `proxy.ts`의 파일 위치&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;rarr; `/src/proxy.ts`로 제대로 된 위치에 있었다.&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;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;2. rewrites (`next.config.ts`) 시도&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;rarr; env 설정 문제로 문제가 해결되지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;3. 개발자도구 - [Application]에서 쿠키의 domain 값 확인 (잡았다)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;rarr; 배포 url로 고정이 되어 있었다. 백엔드 코드를 확인해 보니 쿠키를 생성할 때 아래처럼 배포 도메인명을 하드코딩하여 Domain 값을 설정하고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773245263415&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ResponseCookie cookie = ResponseCookie.from(name, value)
                .domain(&quot;도메인명&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;  근본 원인 파악&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;3번이 원인&lt;/b&gt;&lt;/span&gt;이었으며, 브라우저의 쿠키는 Domain 속성에 따라 전송되는 범위가 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;배포 도메인명&lt;/i&gt;으로 고정된 Domain 값으로 설정하는 경우, 브라우저는 해당 쿠키를 지정된 도메인에서만 전송하기 때문에 로컬 프론트 환경에서는 해당 쿠키를 포함하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, local에서 백엔드 요청 시 브라우저는 accessToken / refreshToken 쿠키를 전송하지 않았고, proxy에서는 '&lt;b&gt;토큰이 없는 상태&lt;/b&gt;'로 판단하게 되어 로그인 이후에도 &lt;i&gt;계속 로그인 페이지로 튕기는 현상&lt;/i&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;또한 Network 탭에서는 다음과 같은 현상이 관찰되었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre id=&quot;code_1773245536893&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/series &amp;rarr; 307 redirect &amp;rarr; /auth/login&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1063&quot; data-start=&quot;994&quot; data-ke-size=&quot;size16&quot;&gt;이는 proxy.ts의 인증 검증 로직이 정상적으로 동작했지만 &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;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  최종 해결 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경에서는 로컬 프론트와 배포 서버 간 쿠키 도메인 불일치가 발생하기 때문에 로컬에서 작업하는 경우 proxy에서 토큰 검증을 수행하지 않도록 분기 처리하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 백엔드에서 Domain을 하드코딩하여 설정한 것은 보안상 올바른 설정이므로 유지하고자 하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1773245607579&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function proxy(request: NextRequest) {
  const isDev = process.env.NODE_ENV === &quot;development&quot;;

  if (isDev) {
    return NextResponse.next();
  }

  const accessToken = request.cookies.get(&quot;accessToken&quot;)?.value;

  if (!accessToken) {
    return NextResponse.redirect(new URL(&quot;/auth&quot;, request.url));
  }

  return NextResponse.next();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 `&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NODE_ENV` 는 구동 환경 체크용 환경변수이고 development인 경우 &lt;b&gt;개발 환경&lt;/b&gt;, production인 경우 &lt;b&gt;배포 환경&lt;/b&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;위와 같이 구현하여 개발 환경 (local) 에서는 토큰 검증을 하지 않도록 코드를 구성하였다.&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;&lt;b&gt;개발 환경&lt;/b&gt;에서는 &lt;u&gt;1) 토큰 검증을 스킵&lt;/u&gt; &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;rarr; &lt;u&gt;2)&lt;/u&gt;&lt;span&gt;&lt;u&gt;&amp;nbsp;정상적으로 페이지 이동&lt;/u&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;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;b&gt;배포 환경&lt;/b&gt;에서는 1&lt;u&gt;) 동일 도메인에서 쿠키 전달&lt;/u&gt; &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;rarr; &lt;u&gt;2)&lt;span&gt; proxy.ts에서 정상적으로 토큰 인증 검증 수행&lt;/span&gt;&lt;/u&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span&gt;하도록 구성하였다.&lt;/span&gt;&lt;/span&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 프로젝트를 진행할 때는 큰 문제없이 쉽게 middleware를 구현했었다. 지금 와서 생각해 보니 배포 서버가 아닌, &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;또한 다른 배포서버가 있는 프로젝트도 백엔드의 Domain 설정과 middleware 코드를 뜯어보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;middleware는 지금과 동일한 구조로 작성되어 있었는데, 백엔드의 Domain에서 도메인을&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;이러한 차이점으로 인해 에러를 찾기 쉽지 않았다. 구글링을 해봐도 많은 자료가 있지 않아서 약간 애를 먹었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경험을 통해 백엔드에서 쿠키에 토큰을 저장할 때 Domain을 지정하냐/하지 않느냐에 따라 보안상의 중요성도 알게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  참고자료&lt;/h2&gt;
&lt;figure id=&quot;og_1773246828749&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;[Next.js] middleware가 쿠키를 못 읽는 이유? Secure와 HTTPS의 함정&quot; data-og-description=&quot;#Next.js #pN룰 #디자인시스템 #Middleware #HTTPS #MUI #BDS #Playwright #CSRF ✅ BDS + MUI 마이그레이션 중 겪은 middleware 디버깅 &amp;amp; 코드리뷰 회고현재 프로젝트는 Buzzle Design System (BDS)과 MUI를 기반으로 한 디자인 &quot; data-og-host=&quot;dingx2-story.tistory.com&quot; data-og-source-url=&quot;https://dingx2-story.tistory.com/164&quot; data-og-url=&quot;https://dingx2-story.tistory.com/164&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wByY1/dJMb8WexZkp/fTqPIRpu0TkSbfZngOhnf1/img.png?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254,https://scrap.kakaocdn.net/dn/c3kE8C/dJMb8QMakj8/JuPwrdJMsdga4cx8MpYb21/img.png?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254,https://scrap.kakaocdn.net/dn/csa1SE/dJMb8Rj0hLz/1p4UIxjlukqwSITtEuR1F1/img.png?width=1570&amp;amp;height=500&amp;amp;face=0_0_1570_500&quot;&gt;&lt;a href=&quot;https://dingx2-story.tistory.com/164&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dingx2-story.tistory.com/164&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wByY1/dJMb8WexZkp/fTqPIRpu0TkSbfZngOhnf1/img.png?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254,https://scrap.kakaocdn.net/dn/c3kE8C/dJMb8QMakj8/JuPwrdJMsdga4cx8MpYb21/img.png?width=800&amp;amp;height=254&amp;amp;face=0_0_800_254,https://scrap.kakaocdn.net/dn/csa1SE/dJMb8Rj0hLz/1p4UIxjlukqwSITtEuR1F1/img.png?width=1570&amp;amp;height=500&amp;amp;face=0_0_1570_500');&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] middleware가 쿠키를 못 읽는 이유? Secure와 HTTPS의 함정&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;#Next.js #pN룰 #디자인시스템 #Middleware #HTTPS #MUI #BDS #Playwright #CSRF ✅ BDS + MUI 마이그레이션 중 겪은 middleware 디버깅 &amp;amp; 코드리뷰 회고현재 프로젝트는 Buzzle Design System (BDS)과 MUI를 기반으로 한 디자인&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dingx2-story.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;</description>
      <category>트러블슈팅</category>
      <category>Cookie</category>
      <category>middleware</category>
      <category>next</category>
      <category>proxy</category>
      <author>joooii</author>
      <guid isPermaLink="true">https://joooii.tistory.com/38</guid>
      <comments>https://joooii.tistory.com/38#entry38comment</comments>
      <pubDate>Thu, 12 Mar 2026 01:34:16 +0900</pubDate>
    </item>
    <item>
      <title>[125일차] Tanstack Query</title>
      <link>https://joooii.tistory.com/37</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;TIL 썸넬.png&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYRYhK%2FdJMcaiuZPt4%2FKqHVKvHoZkzxem1LM1pdk0%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;644&quot; height=&quot;328&quot; data-filename=&quot;TIL 썸넬.png&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1020&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;React 애플리케이션을 개발하다 보면 서버에서 데이터를 가져오고 관리하는 작업이 매우 빈번하게 발생한다.&lt;br /&gt;예를 들어 API 요청을 통해 데이터를 가져오고, 로딩 상태를 관리하고, 오류 처리를 하고, 데이터가 변경되면 다시 요청을 보내는 등의 작업이 반복된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;412&quot; data-start=&quot;310&quot; data-ke-size=&quot;size16&quot;&gt;보통 이러한 작업을 `useEffect`와 `useState`를 이용하여 직접 관리할 수도 있지만, 프로젝트 규모가 커질수록 코드가 복잡해지고 상태 관리가 어려워지는 문제가 발생한다.&lt;/p&gt;
&lt;p data-end=&quot;412&quot; data-start=&quot;310&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;514&quot; data-start=&quot;414&quot; data-ke-size=&quot;size16&quot;&gt;또한 전역 상태 관리 라이브러리(Redux, Zustand 등)를 이용해 서버 데이터를 관리하려고 하면, 서버 상태와 UI 상태가 섞이면서 코드가 복잡해지는 문제가 생길 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;514&quot; data-start=&quot;414&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;579&quot; data-start=&quot;516&quot; data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위해 등장한 라이브러리가 &lt;b&gt;TanStack Query (React Query)&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p data-end=&quot;579&quot; data-start=&quot;516&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;683&quot; data-start=&quot;581&quot; data-ke-size=&quot;size16&quot;&gt;Tanstack Query는 &lt;b&gt;서버 상태(Server State)&lt;/b&gt; 를 효율적으로 관리하기 위한 라이브러리로, API 요청, 캐싱, 데이터 동기화 등을 쉽게 처리할 수 있도록 도와준다.&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/G3Yl2/dJMcadA8DKy/hIshGOxwbuPMwnmeutchT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G3Yl2/dJMcadA8DKy/hIshGOxwbuPMwnmeutchT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G3Yl2/dJMcadA8DKy/hIshGOxwbuPMwnmeutchT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG3Yl2%2FdJMcadA8DKy%2FhIshGOxwbuPMwnmeutchT1%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;695&quot; height=&quot;391&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-end=&quot;683&quot; data-start=&quot;581&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;683&quot; data-start=&quot;581&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;709&quot; data-start=&quot;690&quot; data-section-id=&quot;kdnnhg&quot;&gt;Tanstack Query 초기 설정&lt;/h1&gt;
&lt;p data-end=&quot;797&quot; data-start=&quot;711&quot; data-ke-size=&quot;size16&quot;&gt;Tanstack Query를 사용하기 위해서는 먼저 &lt;b&gt;QueryClient&lt;/b&gt;를 생성하고 애플리케이션을 QueryClientProvider로 감싸야 한다.&lt;/p&gt;
&lt;p data-end=&quot;847&quot; data-start=&quot;799&quot; data-ke-size=&quot;size16&quot;&gt;QueryClient는 서버 상태를 저장하고 관리하는 &lt;b&gt;캐시 저장소 역할&lt;/b&gt;을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1773150005655&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createRoot } from &quot;react-dom/client&quot;;
import { BrowserRouter } from &quot;react-router&quot;;
import { QueryClientProvider, QueryClient } from &quot;@tanstack/react-query&quot;;

const queryClient = new QueryClient();

createRoot(document.getElementById(&quot;root&quot;)!).render(
  &amp;lt;BrowserRouter&amp;gt;
    &amp;lt;QueryClientProvider client={queryClient}&amp;gt;
      &amp;lt;App /&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;
  &amp;lt;/BrowserRouter&amp;gt;,
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게&amp;nbsp;설정하면&amp;nbsp;애플리케이션&amp;nbsp;전체에서&amp;nbsp;React&amp;nbsp;Query를&amp;nbsp;사용할&amp;nbsp;수&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;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;1314&quot; data-start=&quot;1304&quot; data-section-id=&quot;wq9qgf&quot;&gt;useQuery&lt;/h1&gt;
&lt;p data-end=&quot;1363&quot; data-start=&quot;1316&quot; data-ke-size=&quot;size16&quot;&gt;useQuery는 서버에서 데이터를 &lt;b&gt;조회(GET)&lt;/b&gt; 할 때 사용하는 훅이다.&lt;/p&gt;
&lt;p data-end=&quot;1416&quot; data-start=&quot;1365&quot; data-ke-size=&quot;size16&quot;&gt;API 요청을 실행하고 그 결과를 캐싱하며, 로딩 상태와 에러 상태까지 자동으로 관리해준다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1773150039575&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useQuery } from &quot;@tanstack/react-query&quot;;

async function fetchTodos() {
    const response = await fetch(`${API_URL}/todos`);
    if (!response.ok) throw new Error(&quot;Fetch failed&quot;);

    return response.json();
}

const { data, isLoading, error } = useQuery({
    queryKey: [&quot;todos&quot;],
    queryFn: fetchTodos,
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1768&quot; data-start=&quot;1742&quot; data-ke-size=&quot;size16&quot;&gt;useQuery는 다음과 같은 상태를 제공한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1855&quot; data-start=&quot;1770&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1795&quot; data-start=&quot;1770&quot; data-section-id=&quot;nsexsl&quot;&gt;&lt;b&gt;data&lt;/b&gt; : 서버에서 받아온 데이터&lt;/li&gt;
&lt;li data-end=&quot;1827&quot; data-start=&quot;1796&quot; data-section-id=&quot;qsy52e&quot;&gt;&lt;b&gt;isLoading&lt;/b&gt; : 데이터 요청 중인지 여부&lt;/li&gt;
&lt;li data-end=&quot;1855&quot; data-start=&quot;1828&quot; data-section-id=&quot;1hoawn2&quot;&gt;&lt;b&gt;error&lt;/b&gt; : 요청 실패 시 에러 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1888&quot; data-start=&quot;1857&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해 비동기 요청 상태를 간단하게 처리할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1888&quot; data-start=&quot;1857&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1888&quot; data-start=&quot;1857&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;1902&quot; data-start=&quot;1895&quot; data-section-id=&quot;1qdleii&quot;&gt;폴더 구조&lt;/h1&gt;
&lt;p data-end=&quot;1952&quot; data-start=&quot;1904&quot; data-ke-size=&quot;size16&quot;&gt;Tanstack&amp;nbsp;Query를 사용할 때는 보통 &lt;b&gt;API 로직과 훅을 분리&lt;/b&gt;해서 관리한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1773150059282&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;src
├ api
│ └ fetch-todos.ts
├ hooks
│ └ queries
│ └ useTodosData.ts&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2068&quot; data-start=&quot;2050&quot; data-ke-size=&quot;size16&quot;&gt;API 요청 로직을 따로 분리하면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2110&quot; data-start=&quot;2070&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2082&quot; data-start=&quot;2070&quot; data-section-id=&quot;jg5j76&quot;&gt;재사용성이 높아지고&lt;/li&gt;
&lt;li data-end=&quot;2094&quot; data-start=&quot;2083&quot; data-section-id=&quot;114j7pk&quot;&gt;테스트가 쉬워지며&lt;/li&gt;
&lt;li data-end=&quot;2110&quot; data-start=&quot;2095&quot; data-section-id=&quot;1x27052&quot;&gt;코드 구조가 명확해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;2131&quot; data-start=&quot;2117&quot; data-section-id=&quot;131xk4v&quot;&gt;캐싱 (Caching)&lt;/h1&gt;
&lt;p data-end=&quot;2176&quot; data-start=&quot;2133&quot; data-ke-size=&quot;size16&quot;&gt;Tanstack&amp;nbsp;Query의 가장 큰 특징 중 하나는 &lt;b&gt;강력한 캐싱 기능&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;2218&quot; data-start=&quot;2178&quot; data-ke-size=&quot;size16&quot;&gt;Tanstack&amp;nbsp;Query는 &lt;b&gt;queryKey&lt;/b&gt;를 기준으로 데이터를 캐싱한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1773150074007&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useQuery({
    queryKey: [&quot;todos&quot;],
    queryFn: fetchTodos,
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2346&quot; data-start=&quot;2293&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 동일한 queryKey를 사용하는 요청은 &lt;b&gt;자동으로 캐시 데이터를 재사용&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;p data-end=&quot;2353&quot; data-start=&quot;2348&quot; 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;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;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-end=&quot;2353&quot; data-start=&quot;2348&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2353&quot; data-start=&quot;2348&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;2422&quot; data-start=&quot;2412&quot; data-section-id=&quot;sw8v9q&quot;&gt;캐싱 상태 흐름&lt;/h1&gt;
&lt;p data-end=&quot;2456&quot; data-start=&quot;2424&quot; data-ke-size=&quot;size16&quot;&gt;Tanstack&amp;nbsp;Query의 캐시는 다음과 같은 상태를 가진다.&lt;/p&gt;
&lt;h3 data-end=&quot;2470&quot; data-start=&quot;2458&quot; data-section-id=&quot;16ith3u&quot; data-ke-size=&quot;size23&quot;&gt;fetching&lt;/h3&gt;
&lt;p data-end=&quot;2487&quot; data-start=&quot;2472&quot; data-ke-size=&quot;size16&quot;&gt;: 데이터를 처음 요청하는 상태&lt;/p&gt;
&lt;h3 data-end=&quot;2498&quot; data-start=&quot;2489&quot; data-section-id=&quot;7fw64s&quot; data-ke-size=&quot;size23&quot;&gt;fresh&lt;/h3&gt;
&lt;p data-end=&quot;2510&quot; data-start=&quot;2500&quot; data-ke-size=&quot;size16&quot;&gt;: 데이터가 최신 상태&lt;/p&gt;
&lt;h3 data-end=&quot;2521&quot; data-start=&quot;2512&quot; data-section-id=&quot;7y3bah&quot; data-ke-size=&quot;size23&quot;&gt;stale&lt;/h3&gt;
&lt;p data-end=&quot;2534&quot; data-start=&quot;2523&quot; data-ke-size=&quot;size16&quot;&gt;: 데이터가 오래된 상태. stale 상태가 되면 특정 상황에서 &lt;b&gt;자동 리페칭(refetch)&lt;/b&gt; 이 발생한다.&lt;/p&gt;
&lt;p data-end=&quot;2597&quot; data-start=&quot;2586&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 리페칭 상황은 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;2652&quot; data-start=&quot;2599&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;2612&quot; data-start=&quot;2599&quot; data-section-id=&quot;uceacb&quot;&gt;컴포넌트 mount&lt;/li&gt;
&lt;li data-end=&quot;2628&quot; data-start=&quot;2613&quot; data-section-id=&quot;1dkzjko&quot;&gt;브라우저 탭 focus&lt;/li&gt;
&lt;li data-end=&quot;2640&quot; data-start=&quot;2629&quot; data-section-id=&quot;1heh4tk&quot;&gt;네트워크 재연결&lt;/li&gt;
&lt;li data-end=&quot;2652&quot; data-start=&quot;2641&quot; data-section-id=&quot;8tefcz&quot;&gt;일정 시간 간격&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;1167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZCno6/dJMcagEJgSC/Iq5gqcOMhD9MKUNjfrGr8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZCno6/dJMcagEJgSC/Iq5gqcOMhD9MKUNjfrGr8k/img.png&quot; data-alt=&quot;react-query-devtools 시각화 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZCno6/dJMcagEJgSC/Iq5gqcOMhD9MKUNjfrGr8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZCno6%2FdJMcagEJgSC%2FIq5gqcOMhD9MKUNjfrGr8k%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;647&quot; height=&quot;720&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;1167&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;react-query-devtools 시각화 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h1 data-end=&quot;2670&quot; data-start=&quot;2659&quot; data-section-id=&quot;1hl36l8&quot;&gt;staleTime&lt;/h1&gt;
&lt;p data-end=&quot;2721&quot; data-start=&quot;2672&quot; data-ke-size=&quot;size16&quot;&gt;staleTime은 캐시 데이터가 &lt;b&gt;fresh 상태로 유지되는 시간&lt;/b&gt;을 의미한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1773150151142&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;staleTime: 5000&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2793&quot; data-start=&quot;2751&quot; data-ke-size=&quot;size16&quot;&gt;위 설정은 데이터를 &lt;b&gt;5초 동안 fresh 상태로 유지&lt;/b&gt;한다는 의미이다.&lt;/p&gt;
&lt;p data-end=&quot;2793&quot; data-start=&quot;2751&quot; 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;869&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3j5mj/dJMcaiJftnR/r9OKzRLYVYHLsWzZ7JakQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3j5mj/dJMcaiJftnR/r9OKzRLYVYHLsWzZ7JakQK/img.png&quot; data-alt=&quot;캐싱 매커니즘 총 정리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3j5mj/dJMcaiJftnR/r9OKzRLYVYHLsWzZ7JakQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3j5mj%2FdJMcaiJftnR%2Fr9OKzRLYVYHLsWzZ7JakQK%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;869&quot; height=&quot;338&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;캐싱 매커니즘 총 정리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;2793&quot; data-start=&quot;2751&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;2813&quot; data-start=&quot;2800&quot; data-section-id=&quot;1rumgno&quot;&gt;useMutation&lt;/h1&gt;
&lt;p data-end=&quot;2857&quot; data-start=&quot;2815&quot; data-ke-size=&quot;size16&quot;&gt;useMutation은 서버 데이터를 &lt;b&gt;수정할 때 사용하는 훅&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;2864&quot; data-start=&quot;2859&quot; data-ke-size=&quot;size16&quot;&gt;대표적으로&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2895&quot; data-start=&quot;2866&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2872&quot; data-start=&quot;2866&quot; data-section-id=&quot;1j4al8w&quot;&gt;POST&lt;/li&gt;
&lt;li data-end=&quot;2878&quot; data-start=&quot;2873&quot; data-section-id=&quot;1o4obd&quot;&gt;PUT&lt;/li&gt;
&lt;li data-end=&quot;2886&quot; data-start=&quot;2879&quot; data-section-id=&quot;175uwkm&quot;&gt;PATCH&lt;/li&gt;
&lt;li data-end=&quot;2895&quot; data-start=&quot;2887&quot; data-section-id=&quot;1uxxenl&quot;&gt;DELETE&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2906&quot; data-start=&quot;2897&quot; data-ke-size=&quot;size16&quot;&gt;요청에 사용된다.&lt;/p&gt;
&lt;p data-end=&quot;2950&quot; data-start=&quot;2908&quot; data-ke-size=&quot;size16&quot;&gt;useQuery와 달리 &lt;b&gt;컴포넌트 마운트 시 자동 실행되지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2970&quot; data-start=&quot;2952&quot; data-ke-size=&quot;size16&quot;&gt;사용자 이벤트 발생 시 실행된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre id=&quot;code_1773150176814&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { mutate } = useMutation({
	mutationFn: createTodo,
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1773150182970&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mutate(content);&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;h1 data-end=&quot;3095&quot; data-start=&quot;3081&quot; data-section-id=&quot;bfg7mn&quot;&gt;Mutation 이벤트&lt;/h1&gt;
&lt;p data-end=&quot;3137&quot; data-start=&quot;3097&quot; data-ke-size=&quot;size16&quot;&gt;Tanstack&amp;nbsp;Query는 mutation 과정에서 여러 이벤트를 제공한다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 58.4878%;&quot; border=&quot;1&quot; data-end=&quot;3246&quot; data-start=&quot;3139&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;3246&quot; data-start=&quot;3162&quot;&gt;
&lt;tr data-end=&quot;3182&quot; data-start=&quot;3162&quot;&gt;
&lt;td style=&quot;width: 26.0465%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3173&quot; data-start=&quot;3162&quot;&gt;&lt;b&gt;onMutate&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.3256%;&quot; data-end=&quot;3182&quot; data-start=&quot;3173&quot; data-col-size=&quot;sm&quot;&gt;요청 시작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3204&quot; data-start=&quot;3183&quot;&gt;
&lt;td style=&quot;width: 26.0465%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3195&quot; data-start=&quot;3183&quot;&gt;&lt;b&gt;onSuccess&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.3256%;&quot; data-end=&quot;3204&quot; data-start=&quot;3195&quot; data-col-size=&quot;sm&quot;&gt;요청 성공&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3224&quot; data-start=&quot;3205&quot;&gt;
&lt;td style=&quot;width: 26.0465%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3215&quot; data-start=&quot;3205&quot;&gt;&lt;b&gt;onError&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.3256%;&quot; data-end=&quot;3224&quot; data-start=&quot;3215&quot; data-col-size=&quot;sm&quot;&gt;요청 실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3246&quot; data-start=&quot;3225&quot;&gt;
&lt;td style=&quot;width: 26.0465%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;3237&quot; data-start=&quot;3225&quot;&gt;&lt;b&gt;onSettled&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 32.3256%;&quot; data-end=&quot;3246&quot; data-start=&quot;3237&quot; data-col-size=&quot;sm&quot;&gt;요청 완료&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3273&quot; data-start=&quot;3248&quot; data-ke-size=&quot;size16&quot;&gt;이를 활용하면 다양한 동작을 처리할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;3273&quot; data-start=&quot;3248&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3273&quot; data-start=&quot;3248&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;3292&quot; data-start=&quot;3280&quot; data-section-id=&quot;kiixi&quot;&gt;캐시 데이터 무효화&lt;/h1&gt;
&lt;p data-end=&quot;3345&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;데이터가 변경되었을 때 기존 캐시 데이터를 &lt;b&gt;무효화(invalidate)&lt;/b&gt; 할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre id=&quot;code_1773150230832&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;queryClient.invalidateQueries({
	queryKey: [&quot;todos&quot;],
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3451&quot; data-start=&quot;3418&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 해당 캐시 데이터가 &lt;b&gt;자동으로 리페칭&lt;/b&gt;된다.&amp;nbsp;즉, 새로고침 없이도 UI가 최신 데이터로 업데이트된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqxG86/dJMcaiWMImk/Yx0ZqzO5o05Is3K8OkyQdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqxG86/dJMcaiWMImk/Yx0ZqzO5o05Is3K8OkyQdK/img.png&quot; data-alt=&quot;todos가 추가됨에 따라 캐시 데이터가 무효화됨 -&amp;amp;gt; 200 데이터 refetching 발생 =&amp;amp;gt; 새로고침 없이 자동 refetch 됨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqxG86/dJMcaiWMImk/Yx0ZqzO5o05Is3K8OkyQdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqxG86%2FdJMcaiWMImk%2FYx0ZqzO5o05Is3K8OkyQdK%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;672&quot; height=&quot;93&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;todos가 추가됨에 따라 캐시 데이터가 무효화됨 -&amp;gt; 200 데이터 refetching 발생 =&amp;gt; 새로고침 없이 자동 refetch 됨&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;3451&quot; data-start=&quot;3418&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;3500&quot; data-start=&quot;3491&quot; data-section-id=&quot;h40kbb&quot;&gt;쿼리키 팩토리&lt;/h1&gt;
&lt;p data-end=&quot;3534&quot; data-start=&quot;3502&quot; data-ke-size=&quot;size16&quot;&gt;쿼리키를 &lt;b&gt;객체 형태&lt;/b&gt;로 관리하면 캐시 관리가 더욱 쉬워진다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;pre id=&quot;code_1773150256177&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const QUERY_KEYS = {
    todo: {
        all: [&quot;todo&quot;],
        list: [&quot;todo&quot;, &quot;list&quot;],
        detail: (id: string) =&amp;gt; [&quot;todo&quot;, &quot;detail&quot;, id],
    },
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3697&quot; data-start=&quot;3693&quot; data-ke-size=&quot;size16&quot;&gt;사용 예시)&lt;/p&gt;
&lt;pre id=&quot;code_1773150283322&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;queryKey: QUERY_KEYS.todo.list&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3779&quot; data-start=&quot;3742&quot; data-ke-size=&quot;size16&quot;&gt;이 방식을 &lt;b&gt;Query Key Factory 패턴&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-end=&quot;3779&quot; data-start=&quot;3742&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3779&quot; data-start=&quot;3742&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;3796&quot; data-start=&quot;3786&quot; data-section-id=&quot;531s1z&quot;&gt;낙관적 업데이트&lt;/h1&gt;
&lt;p data-end=&quot;3868&quot; data-start=&quot;3798&quot; data-ke-size=&quot;size16&quot;&gt;낙관적 업데이트(Optimistic Update)는 &lt;b&gt;요청이 성공할 것이라고 가정하고 UI를 먼저 업데이트하는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre id=&quot;code_1773150319565&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;onMutate: (updatedTodo) =&amp;gt; {
    queryClient.setQueryData([&quot;todos&quot;], (prevTodos) =&amp;gt;
        prevTodos.map((todo) =&amp;gt;
            todo.id === updatedTodo.id
                ? { ...todo, ...updatedTodo }
                : todo
            )
    );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;4101&quot; data-start=&quot;4092&quot; data-ke-size=&quot;size16&quot;&gt;이 방식의 장점은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4125&quot; data-start=&quot;4103&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4114&quot; data-start=&quot;4103&quot; data-section-id=&quot;jbyews&quot;&gt;사용자 경험 향상&lt;/li&gt;
&lt;li data-end=&quot;4125&quot; data-start=&quot;4115&quot; data-section-id=&quot;1gbnkjp&quot;&gt;빠른 UI 반응&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;4145&quot; data-start=&quot;4137&quot; data-section-id=&quot;189a6dz&quot;&gt;캐시 정규화&lt;/h1&gt;
&lt;p data-end=&quot;4194&quot; data-start=&quot;4147&quot; data-ke-size=&quot;size16&quot;&gt;캐시 정규화는 &lt;b&gt;중첩된 데이터를 평탄화(flatten)&lt;/b&gt; 해서 관리하는 방식이다.&lt;/p&gt;
&lt;p data-end=&quot;4246&quot; data-start=&quot;4196&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 투두 리스트 데이터를 그대로 저장하면 동일 데이터가 여러 곳에 중복될 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;4258&quot; data-start=&quot;4248&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4309&quot; data-start=&quot;4260&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4281&quot; data-start=&quot;4260&quot; data-section-id=&quot;10y42b8&quot;&gt;리스트 캐시에는 &lt;b&gt;id만 저장&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;4309&quot; data-start=&quot;4282&quot; data-section-id=&quot;v38y0k&quot;&gt;개별 데이터는 &lt;b&gt;detail 캐시로 저장&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4328&quot; data-start=&quot;4311&quot; data-ke-size=&quot;size16&quot;&gt;하는 방식으로 관리할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1773150359300&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;todos.forEach((todo) =&amp;gt; {
    queryClient.setQueryData(
	    QUERY_KEYS.todo.detail(todo.id),
    	todo
    );
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;4457&quot; data-start=&quot;4451&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4502&quot; data-start=&quot;4459&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4470&quot; data-start=&quot;4459&quot; data-section-id=&quot;1nzqk1g&quot;&gt;데이터 중복 제거&lt;/li&gt;
&lt;li data-end=&quot;4490&quot; data-start=&quot;4471&quot; data-section-id=&quot;9jvsww&quot;&gt;개별 데이터 업데이트 효율 증가&lt;/li&gt;
&lt;li data-end=&quot;4502&quot; data-start=&quot;4491&quot; data-section-id=&quot;1fcgc88&quot;&gt;캐시 관리 단순화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4514&quot; data-start=&quot;4504&quot; 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;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IuyiG/dJMcacI5URU/Xvkj3unnMwS7MTTqqMsdW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IuyiG/dJMcacI5URU/Xvkj3unnMwS7MTTqqMsdW1/img.png&quot; data-alt=&quot;복잡한 구조로 중첩되어 있는 데이터&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IuyiG/dJMcacI5URU/Xvkj3unnMwS7MTTqqMsdW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIuyiG%2FdJMcacI5URU%2FXvkj3unnMwS7MTTqqMsdW1%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;643&quot; height=&quot;362&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;복잡한 구조로 중첩되어 있는 데이터&lt;/figcaption&gt;
&lt;/figure&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/bLvmWp/dJMcabi7wnP/ZRuRECv4nDHheYDumHQOJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLvmWp/dJMcabi7wnP/ZRuRECv4nDHheYDumHQOJk/img.png&quot; data-alt=&quot;이렇게 평탄화 &amp;amp;rarr; 각 개별 아이템에 더 쉽게 접근 가능&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLvmWp/dJMcabi7wnP/ZRuRECv4nDHheYDumHQOJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLvmWp%2FdJMcabi7wnP%2FZRuRECv4nDHheYDumHQOJk%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;659&quot; height=&quot;371&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이렇게 평탄화 &amp;rarr; 각 개별 아이템에 더 쉽게 접근 가능&lt;/figcaption&gt;
&lt;/figure&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/U9d03/dJMcabwCe5n/nffzw7M8NRGOdm7GmtZgH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U9d03/dJMcabwCe5n/nffzw7M8NRGOdm7GmtZgH0/img.png&quot; data-alt=&quot;중복된 데이터 존재 시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U9d03/dJMcabwCe5n/nffzw7M8NRGOdm7GmtZgH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU9d03%2FdJMcabwCe5n%2Fnffzw7M8NRGOdm7GmtZgH0%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;655&quot; height=&quot;368&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;중복된 데이터 존재 시&lt;/figcaption&gt;
&lt;/figure&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/b4r3kt/dJMcabwCe5t/0ASrfZOUD1eZNsmv61VQZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4r3kt/dJMcabwCe5t/0ASrfZOUD1eZNsmv61VQZ0/img.png&quot; data-alt=&quot;중복 제거 &amp;amp;rarr; 효율적인 데이터 저장&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4r3kt/dJMcabwCe5t/0ASrfZOUD1eZNsmv61VQZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4r3kt%2FdJMcabwCe5t%2F0ASrfZOUD1eZNsmv61VQZ0%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;649&quot; height=&quot;365&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;중복 제거 &amp;rarr; 효율적인 데이터 저장&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1 data-end=&quot;4525&quot; data-start=&quot;4521&quot; data-section-id=&quot;yih0cq&quot;&gt;회고&lt;/h1&gt;
&lt;p data-end=&quot;4624&quot; data-start=&quot;4527&quot; data-ke-size=&quot;size16&quot;&gt;이번 실습을 통해 Tanstack Query가 단순히 API 요청을 편하게 만드는 라이브러리가 아니라, &lt;b&gt;서버 상태를 효율적으로 관리하기 위한 도구&lt;/b&gt;라는 것을 이해하게 되었다.&lt;/p&gt;
&lt;p data-end=&quot;4731&quot; data-start=&quot;4626&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 useEffect와 useState를 이용하여 API 요청과 로딩 상태를 직접 관리했는데, 프로젝트 규모가 커질수록 코드가 복잡해지고 상태 관리가 어려워지는 문제가 있었다.&lt;/p&gt;
&lt;p data-end=&quot;4750&quot; data-start=&quot;4733&quot; data-ke-size=&quot;size16&quot;&gt;Tanstack&amp;nbsp;Query를 사용하면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4788&quot; data-start=&quot;4752&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4759&quot; data-start=&quot;4752&quot; data-section-id=&quot;1hlzb4&quot;&gt;자동 캐싱&lt;/li&gt;
&lt;li data-end=&quot;4768&quot; data-start=&quot;4760&quot; data-section-id=&quot;owfnq5&quot;&gt;리페칭 관리&lt;/li&gt;
&lt;li data-end=&quot;4779&quot; data-start=&quot;4769&quot; data-section-id=&quot;2k9895&quot;&gt;낙관적 업데이트&lt;/li&gt;
&lt;li data-end=&quot;4788&quot; data-start=&quot;4780&quot; data-section-id=&quot;1oepa18&quot;&gt;캐시 무효화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4827&quot; data-start=&quot;4790&quot; data-ke-size=&quot;size16&quot;&gt;등의 기능을 통해 서버 상태 관리가 훨씬 단순해진다는 것을 느꼈다.&lt;/p&gt;
&lt;p data-end=&quot;4902&quot; data-start=&quot;4829&quot; data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;캐싱과 낙관적 업데이트 개념&lt;/b&gt;은 실제 서비스에서도 성능과 사용자 경험을 개선하는 데 중요한 역할을 할 것이라고 생각한다.&lt;/p&gt;
&lt;p data-end=&quot;4959&quot; data-start=&quot;4904&quot; data-ke-size=&quot;size16&quot;&gt;앞으로 API 요청이 많은 프로젝트에서는 Tanstack Query를 적극적으로 활용하면 좋을 것 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;4959&quot; data-start=&quot;4904&quot; data-ke-size=&quot;size26&quot;&gt;참고 자료 및 이미지 출처&lt;/h2&gt;
&lt;p data-end=&quot;4959&quot; data-start=&quot;4904&quot; data-ke-size=&quot;size16&quot;&gt;한 입 크기로 잘라먹는 React.js 실전 프로젝트 - SNS편&lt;/p&gt;
&lt;p data-end=&quot;4959&quot; data-start=&quot;4904&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.inflearn.com/course/%ED%95%9C-%EC%9E%85-%ED%81%AC%EA%B8%B0%EB%A1%9C-%EC%9E%98%EB%9D%BC%EB%A8%B9%EB%8A%94-%EC%8B%A4%EC%A0%84-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8?cid=336312&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.inflearn.com/course/%ED%95%9C-%EC%9E%85-%ED%81%AC%EA%B8%B0%EB%A1%9C-%EC%9E%98%EB%9D%BC%EB%A8%B9%EB%8A%94-%EC%8B%A4%EC%A0%84-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8?cid=336312&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TIL</category>
      <category>멀티캠퍼스부트캠프</category>
      <category>부트캠프후기</category>
      <category>유레카 프론트엔드</category>
      <author>joooii</author>
      <guid isPermaLink="true">https://joooii.tistory.com/37</guid>
      <comments>https://joooii.tistory.com/37#entry37comment</comments>
      <pubDate>Tue, 10 Mar 2026 22:54:12 +0900</pubDate>
    </item>
    <item>
      <title>[122일차] 모노레포와 멀티레포</title>
      <link>https://joooii.tistory.com/36</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;TIL 썸넬.png&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYRYhK%2FdJMcaiuZPt4%2FKqHVKvHoZkzxem1LM1pdk0%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;644&quot; height=&quot;328&quot; data-filename=&quot;TIL 썸넬.png&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1020&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;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-end=&quot;405&quot; data-start=&quot;301&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 규모가 작을 때는 크게 고민하지 않을 수 있지만, 프로젝트가 커지고 서비스가 여러 개로 분리되기 시작하면 &lt;b&gt;코드 관리 방식이 협업 효율과 유지보수에 큰 영향을 미치게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;405&quot; data-start=&quot;301&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;530&quot; data-start=&quot;407&quot; data-ke-size=&quot;size16&quot;&gt;이때 사용되는 대표적인 개념이 &lt;b&gt;모노레포(Monorepo)&lt;/b&gt;와 &lt;b&gt;멀티레포(Multi Repo)&lt;/b&gt;이다.&lt;br /&gt;또한 최근에는 모노레포 환경에서 효율적인 개발을 돕는 &lt;b&gt;Turborepo&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;h2 data-ke-size=&quot;size26&quot;&gt;멀티레포 (Multi Repo, Poly Repo)&lt;/h2&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;305&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5QRQH/dJMcac3hEYP/Zchki3NEP9VkOifWikik20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5QRQH/dJMcac3hEYP/Zchki3NEP9VkOifWikik20/img.png&quot; data-alt=&quot;https://velog.io/@miso1489/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%99%80-%EB%A9%80%ED%8B%B0%EB%A0%88%ED%8F%AC&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5QRQH/dJMcac3hEYP/Zchki3NEP9VkOifWikik20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5QRQH%2FdJMcac3hEYP%2FZchki3NEP9VkOifWikik20%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;305&quot; height=&quot;270&quot; data-origin-width=&quot;305&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://velog.io/@miso1489/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%99%80-%EB%A9%80%ED%8B%B0%EB%A0%88%ED%8F%AC&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멀티레포 장점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 프로젝트 별 독립적 개발 가능, 각 프로젝트 특성에 맞게 최적화 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 소규모 프로젝트나 독립적인 개발이 필요한 경우 적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ CI/CD 파이프라인 단순: 빌드, 테스트, 배포 파이프라인을 프로젝트 단위로 구성하기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멀티레포 단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1️⃣&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;코드 재사용 및 공유 어려움: 공통으로 사용되는 코드를 각 저장소에 복사해서 사용해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 의존성 관리 복잡성: 동일한 라이브러리를 사용할 때 버전 관리를 동일하게 맞춰야 되므로 개발 비용이 늘어남&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ 협업의 불편함: 코드 컨벤션 통일 어려움&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모노레포 (Mono Repo)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모노레포&lt;/b&gt;는 여러 프로젝트를 하나의 Git 저장소에서 관리하는 구조이다. 즉, 하나의 저장소 안에 여러 애플리케이션과 패키지를 함께 포함하는 방식이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 대규모 서비스에서 코드 공유와 협업 효율을 높이기 위해 모노레포를 사용하는 경우가 점점 늘어나고 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1772639171687&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;repo
 ├ apps
 │   ├ web
 │   ├ admin
 │   └ mobile
 │
 ├ packages
 │   ├ ui
 │   ├ utils
 │   └ config&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pH0xc/dJMcaaqTOds/1Zp2zDyHKS2QW4w7SlmioK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pH0xc/dJMcaaqTOds/1Zp2zDyHKS2QW4w7SlmioK/img.png&quot; data-alt=&quot;https://velog.io/@miso1489/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%99%80-%EB%A9%80%ED%8B%B0%EB%A0%88%ED%8F%AC&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pH0xc/dJMcaaqTOds/1Zp2zDyHKS2QW4w7SlmioK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpH0xc%2FdJMcaaqTOds%2F1Zp2zDyHKS2QW4w7SlmioK%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;357&quot; height=&quot;314&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://velog.io/@miso1489/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%99%80-%EB%A9%80%ED%8B%B0%EB%A0%88%ED%8F%AC&lt;/figcaption&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 코드 공유 및 재사용성 향상: 공통 코드를 한 곳에서 관리하기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 일관된 버전 관리: 프로젝트 전체의 의존성을 하나의 레포에서 관리하기 때문에 동일한 버전의 라이브러리를 사용하게 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ 개발 환경 통일: 동일한 lint 규칙 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4️⃣&amp;nbsp;리팩토링과 변경 관리 용이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5️⃣&amp;nbsp;협업 효율성 증가: 일관된 코딩 스타일과 패턴 유지 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모노레포 단점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 초기 설정 복잡&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 높은 러닝커브&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ 코드 충돌 위험&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4️⃣ 코드 관리 복잡성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5️⃣ 배포 복잡성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;터보레포 (Turbo Repo)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;터보레포&lt;/b&gt;는 모노레포 환경에서 효율적인 개발을 지원하는 고성능 빌드 시스템이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모노레포에서는 여러 프로젝트가 하나의 저장소에 존재하기 때문에 모든 프로젝트를 매번 빌드하게 되면 시간이 오래 걸릴 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;3982&quot; data-start=&quot;3934&quot; data-ke-size=&quot;size16&quot;&gt;터보레포는 이러한 문제를 해결하기 위해 &lt;b&gt;작업 캐싱과 병렬 실행 기능을 제공한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;3982&quot; data-start=&quot;3934&quot; data-ke-size=&quot;size16&quot;&gt;최근에는 여러 애플리케이션이 공통 코드와 디자인 시스템을 공유하는 경우가 많아지면서, 모노레포와 Turborepo 같은 도구를 활용하는 방식이 점점 더 많이 사용되고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rwu4w/dJMcacI13Fi/temXYF3kCA1dSXJxpt7fQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rwu4w/dJMcacI13Fi/temXYF3kCA1dSXJxpt7fQK/img.png&quot; data-alt=&quot;https://turborepo.dev/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rwu4w/dJMcacI13Fi/temXYF3kCA1dSXJxpt7fQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frwu4w%2FdJMcacI13Fi%2FtemXYF3kCA1dSXJxpt7fQK%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;589&quot; height=&quot;309&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://turborepo.dev/&lt;/figcaption&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1️⃣&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;효율적인 빌드 시스템: 작업 결과를 캐싱하여 이미 실행된 작업을 다시 실행하지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 병렬 실행: 여러 작업을 동시 실행하므로 작업 시간 단축&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3️⃣&lt;span&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 frontend 레포에서 서비스, 백오피스를 단순하게 도메인 분리를 통해서 구분하고 있었다. 멘토링에서 받았던 피드백인 레포 구조에 대해서 다시 생각해 보면서 레포 구조를 바꾸자는 의견이 나왔다. 우리 프로젝트가 이미 UI는 완성된 상태이며, 마감 기한까지 얼마 안 남은 상황이었기 때문에 비용과 시간이 그나마 덜한 멀티 레포 구조로 변경하게 되었다.&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;456&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfY6Yi/dJMcahwNGr6/lH4Nqz8UJOncPMBGOmNHT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfY6Yi/dJMcahwNGr6/lH4Nqz8UJOncPMBGOmNHT1/img.png&quot; data-alt=&quot;멀티레포!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfY6Yi/dJMcahwNGr6/lH4Nqz8UJOncPMBGOmNHT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfY6Yi%2FdJMcahwNGr6%2FlH4Nqz8UJOncPMBGOmNHT1%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;343&quot; height=&quot;185&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;246&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;사실 계속 해왔던 방식만 생각해 왔어서 모노레포는 굳이 생각해보지 않았다. 처음부터 고민해 보고 결정했어야 되는 사항이었는데, 이를 간과했던 것 같아서 아쉽긴 하다.&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;250&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdHSAb/dJMb99S2Pyz/noZuex3YwynvN7Du1K4vHK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdHSAb/dJMb99S2Pyz/noZuex3YwynvN7Du1K4vHK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdHSAb/dJMb99S2Pyz/noZuex3YwynvN7Du1K4vHK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdHSAb%2FdJMb99S2Pyz%2FnoZuex3YwynvN7Du1K4vHK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;378&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;378&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_8BF3DB060419-1.jpeg&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;1613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CaVz1/dJMcaiCq2k9/bJvgTpGNlnlMXVCm7KOR0K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CaVz1/dJMcaiCq2k9/bJvgTpGNlnlMXVCm7KOR0K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CaVz1/dJMcaiCq2k9/bJvgTpGNlnlMXVCm7KOR0K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCaVz1%2FdJMcaiCq2k9%2FbJvgTpGNlnlMXVCm7KOR0K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;519&quot; height=&quot;649&quot; data-filename=&quot;IMG_8BF3DB060419-1.jpeg&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;1613&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@miso1489/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%99%80-%EB%A9%80%ED%8B%B0%EB%A0%88%ED%8F%AC&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@miso1489/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%99%80-%EB%A9%80%ED%8B%B0%EB%A0%88%ED%8F%AC&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772638419740&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;모노레포 vs 멀티레포&quot; data-og-description=&quot;원티드 5월 프론트엔드 챌린지를 하면서 모노레포에 대해 알게 되었다. 모노레포란 무엇인지, 이와 반대되는 멀티레포는 무엇인지 알아보자.&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@miso1489/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%99%80-%EB%A9%80%ED%8B%B0%EB%A0%88%ED%8F%AC&quot; data-og-url=&quot;https://velog.io/@miso1489/모노레포와-멀티레포&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rTREe/dJMb8VNs44k/uYH2ww8ZDkkl1OlDI4KwVK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/UXuKU/dJMb8RjZFmC/hlEPAiZEjAN7Aa1KyjJva0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/eSVGI/dJMb8SpFGgD/rfK0kTHvaRWcUKKLYlZ7HK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://velog.io/@miso1489/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%99%80-%EB%A9%80%ED%8B%B0%EB%A0%88%ED%8F%AC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@miso1489/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EC%99%80-%EB%A9%80%ED%8B%B0%EB%A0%88%ED%8F%AC&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rTREe/dJMb8VNs44k/uYH2ww8ZDkkl1OlDI4KwVK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/UXuKU/dJMb8RjZFmC/hlEPAiZEjAN7Aa1KyjJva0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/eSVGI/dJMb8SpFGgD/rfK0kTHvaRWcUKKLYlZ7HK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;모노레포 vs 멀티레포&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;원티드 5월 프론트엔드 챌린지를 하면서 모노레포에 대해 알게 되었다. 모노레포란 무엇인지, 이와 반대되는 멀티레포는 무엇인지 알아보자.&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>TIL</category>
      <category>멀티캠퍼스부트캠프</category>
      <category>부트캠프후기</category>
      <category>유레카 프론트엔드</category>
      <author>joooii</author>
      <guid isPermaLink="true">https://joooii.tistory.com/36</guid>
      <comments>https://joooii.tistory.com/36#entry36comment</comments>
      <pubDate>Thu, 5 Mar 2026 01:29:42 +0900</pubDate>
    </item>
    <item>
      <title>[119일차] 프론트엔드 FSD 아키텍처</title>
      <link>https://joooii.tistory.com/35</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;TIL 썸넬.png&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YRYhK/dJMcaiuZPt4/KqHVKvHoZkzxem1LM1pdk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYRYhK%2FdJMcaiuZPt4%2FKqHVKvHoZkzxem1LM1pdk0%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;644&quot; height=&quot;328&quot; data-filename=&quot;TIL 썸넬.png&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;1020&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;FSD 아키텍처로 가는 것이 어떤 지 피드백을 주셔서 FSD 아키텍처에 대해서 알아보고자 한다.&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;FSD 아키텍처란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #060b11; text-align: start;&quot;&gt;FSD 아키텍처는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Feature-Sliced Design(기능 분할 설계)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #060b11; text-align: start;&quot;&gt;&amp;nbsp;의 약자로,&lt;span&gt;&amp;nbsp;&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;이를 위해 FSD 아키텍처를 도입하는 개발자들이 늘어나게 되었다.&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;FSD는 총 3가지의 개념이 존재한다: &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Layer, Slice, Segment&lt;/b&gt;&lt;/span&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;스크린샷 2026-02-28 15.32.11.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTdZeG/dJMcagkibKG/uUv8kXI6xaz3VC9MOla9C0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTdZeG/dJMcagkibKG/uUv8kXI6xaz3VC9MOla9C0/img.png&quot; data-alt=&quot;FSD 아키텍처 (FSD 공식문서)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTdZeG/dJMcagkibKG/uUv8kXI6xaz3VC9MOla9C0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTdZeG%2FdJMcagkibKG%2FuUv8kXI6xaz3VC9MOla9C0%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;1530&quot; height=&quot;796&quot; data-filename=&quot;스크린샷 2026-02-28 15.32.11.png&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FSD 아키텍처 (FSD 공식문서)&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;h2 data-ke-size=&quot;size26&quot;&gt;레이어 (Layer)&lt;/h2&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;레이어의 수는 최대 7개로 제한되어 있으며 필요한 Layer만 추가하여 사 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JqD7g/dJMcagkibLQ/WLOVN5k80BzIyj0woPkGyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JqD7g/dJMcagkibLQ/WLOVN5k80BzIyj0woPkGyK/img.png&quot; data-alt=&quot;Layer 구분&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JqD7g/dJMcagkibLQ/WLOVN5k80BzIyj0woPkGyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJqD7g%2FdJMcagkibLQ%2FWLOVN5k80BzIyj0woPkGyK%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;348&quot; height=&quot;238&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Layer 구분&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;table style=&quot;border-collapse: collapse; width: 100%; height: 137px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 16.2403%; height: 18px;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 59.7286%; height: 18px;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 24.031%; height: 18px;&quot;&gt;&lt;b&gt;특이사항&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.2403%; height: 17px;&quot;&gt;app&lt;/td&gt;
&lt;td style=&quot;width: 59.7286%; height: 17px;&quot;&gt;- 애플리케이션 로직이 초기화되는 곳&lt;br /&gt;- 프로바이더, 라우터, 전역 스타일, 전역 타입 선언 등이 정의됨&lt;/td&gt;
&lt;td style=&quot;width: 24.031%; height: 17px;&quot;&gt;- 애플리케이션의 진입점 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.2403%; height: 17px;&quot;&gt;processess&lt;/td&gt;
&lt;td style=&quot;width: 59.7286%; height: 17px;&quot;&gt;- 여러 단계로 이루어진 등록과 같이 여러 페이지에 걸쳐 있는 프로세스 처리&lt;/td&gt;
&lt;td style=&quot;width: 24.031%; height: 17px;&quot;&gt;- 선택적 레이어&lt;br /&gt;- 잘 사용하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.2403%; height: 17px;&quot;&gt;pages&lt;/td&gt;
&lt;td style=&quot;width: 59.7286%; height: 17px;&quot;&gt;- 애플리케이션 페이지가 포함됨&lt;/td&gt;
&lt;td style=&quot;width: 24.031%; height: 17px;&quot;&gt;- 대부분 페이지 1개는 슬라이스 1개 (단, 유사 페이지는 하나의 슬라이스로 묶기 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.2403%; height: 17px;&quot;&gt;widgets&lt;/td&gt;
&lt;td style=&quot;width: 59.7286%; height: 17px;&quot;&gt;- 독립적으로 작동&lt;br /&gt;- 여러 페이지에서 재사용되는 대규모 기능 or UI 컴포넌트&lt;br /&gt;- 보통 하나의 완전한 기능&lt;/td&gt;
&lt;td style=&quot;width: 24.031%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.2403%; height: 17px;&quot;&gt;features&lt;/td&gt;
&lt;td style=&quot;width: 59.7286%; height: 17px;&quot;&gt;- 제품 전반에 걸쳐 재사용되는 기능 구현체&lt;br /&gt;- 사용자에게 실질적인 비즈니스 가치를 제공하는 동작&lt;br /&gt;- 동사적 개념 (&quot;사용자가 ~한다&quot;)&lt;/td&gt;
&lt;td style=&quot;width: 24.031%; height: 17px;&quot;&gt;- feature 간 직접 참조 금지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.2403%; height: 17px;&quot;&gt;entities&lt;/td&gt;
&lt;td style=&quot;width: 59.7286%; height: 17px;&quot;&gt;- 비즈니스 데이터&lt;br /&gt;- 명사적 개념 (&quot;~이 뭔지 정의한다&quot;)&lt;/td&gt;
&lt;td style=&quot;width: 24.031%; height: 17px;&quot;&gt;- entities &lt;span data-token-index=&quot;0&quot;&gt;&amp;rarr;&lt;/span&gt; feature 참조 금지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.2403%; height: 17px;&quot;&gt;shared&lt;/td&gt;
&lt;td style=&quot;width: 59.7286%; height: 17px;&quot;&gt;- 기본 구성 요소를 모아둠&lt;br /&gt;- 재사용 가능한 기능, 특히 프로젝트/비즈니스의 특성과 분리되어 있을 때 (반드시 그럴 필요는 x)&lt;br /&gt;- UI 키드, axios 설정, 앱 설정, 비즈니스 로직에 묶이지 않은 헬퍼 등 포함&lt;/td&gt;
&lt;td style=&quot;width: 24.031%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4O9wi/dJMcacvsRKs/jaBKige4qi4jK2AiX857h0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4O9wi/dJMcacvsRKs/jaBKige4qi4jK2AiX857h0/img.png&quot; data-alt=&quot;각 레이어의 사용 범위&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4O9wi/dJMcacvsRKs/jaBKige4qi4jK2AiX857h0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4O9wi%2FdJMcacvsRKs%2FjaBKige4qi4jK2AiX857h0%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;700&quot; height=&quot;374&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;374&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;레이어는 위에서 아래로의 단방향 의존성을 가진다는 특징이 있다.&amp;nbsp; &lt;span style=&quot;color: #9d9d9d;&quot;&gt;ex) entities 레이어는 features 레이어 기능 사용 X&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;&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;슬라이스 (Slices)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;슬라이스&lt;/b&gt;는 FSD 아키텍처에서 2번째 계층이며, 레이어 내에서 코드를 비즈니스 도메인별로 분해하는 역할을 한다. 슬라이스의 주요 목표는 코드를 값별로 그룹화하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이스 이름은 프로젝트의 비즈니스 영역에 따라 직접 결정되므로 표준화되어 있지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 레이어 안에서 다른 슬라이스를 참조할 수 없으며, 이는 높은 응집도와 낮은 결합도를 유지하는 데 도움을 준다.&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;sdot; 응집도 (Coupling): 모듈의 독립성. 모듈 내부 구성요소 간 연관 정도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;sdot;&lt;span&gt; 결합도 (Coupling): 외부 모듈과의 연관도 or 모듈 간 상호의존성 정도&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 style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;➡️ &lt;b&gt;응집도는 &lt;span style=&quot;color: #ee2323;&quot;&gt;높게&lt;/span&gt;, 결합도는 &lt;span style=&quot;color: #ee2323;&quot;&gt;낮게&lt;/span&gt;&lt;/b&gt; 유지하는 것이 좋다!&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XZ4St/dJMcad179iD/PxLV6adqtMZ9gKJuDGo18k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XZ4St/dJMcad179iD/PxLV6adqtMZ9gKJuDGo18k/img.png&quot; data-alt=&quot;슬라이스 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XZ4St/dJMcad179iD/PxLV6adqtMZ9gKJuDGo18k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXZ4St%2FdJMcad179iD%2FPxLV6adqtMZ9gKJuDGo18k%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;357&quot; height=&quot;683&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;683&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;&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;세그먼트 (Segements)&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;b&gt;세그먼트&lt;/b&gt;는 FSD 아키텍처에서 마지막 계층이며, 코드 내에서 기술적 목적에 따라 구분한다. 팀 합의에 따라 세그먼트의 구성과 이름이 변경될 수 있다. 일반적으로 사용되는 세그먼트는 아래와 같다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 91.9767%; height: 253px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.6724%;&quot;&gt;&lt;b&gt;네이밍&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.3276%;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.6724%;&quot;&gt;api&lt;/td&gt;
&lt;td style=&quot;width: 76.3276%;&quot;&gt;필요한 서버 요청&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.6724%;&quot;&gt;ui&lt;/td&gt;
&lt;td style=&quot;width: 76.3276%;&quot;&gt;슬라이스의 ui 컴포넌트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.6724%;&quot;&gt;model&lt;/td&gt;
&lt;td style=&quot;width: 76.3276%;&quot;&gt;- 비즈니스 로직, 즉 상태와의 상호작용&lt;br /&gt;- actions 및 selectors가 이에 해당&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.6724%;&quot;&gt;lib&lt;/td&gt;
&lt;td style=&quot;width: 76.3276%;&quot;&gt;슬라이스 내에서 사용되는 보조 기능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.6724%;&quot;&gt;config&lt;/td&gt;
&lt;td style=&quot;width: 76.3276%;&quot;&gt;슬라이스에 필요한 구성값이지만, 구성 세그먼트는 거의 필요 X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.6724%;&quot;&gt;consts&lt;/td&gt;
&lt;td style=&quot;width: 76.3276%;&quot;&gt;필요한 상수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;공개 API&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 슬라이스와 세그먼트에는 공개 API 가 있다. 공개 API는 `index.js` 또는 `index.ts` 파일이며, 이 파일을 통해 슬라이스 또는 세그먼트에서 필요한 기능만 외부로 추출하고 불필요한 기능은 격리할 수 있다.&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;sdot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;애플리케이션 슬라이스와 세그먼트는 공개 API 인덱스 파일에 정의된 슬라이스의 기능과 컴포넌트만 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;sdot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;공개 API에 정의되지 않은 슬라이스 또는 세그먼트의 내부 부분은 격리된 것으로 간주되며 슬라이스 또는 세그먼트 내부에서만 접근 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공개 API는 import 및 export로 단순하게 작동하므로 애플리케이션을 변경할 때 코드의 모든 곳에서 import를 변경할 필요가 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3N1qZ/dJMcagdyqbD/ZyrofjgC9s19ggopsHaVWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3N1qZ/dJMcagdyqbD/ZyrofjgC9s19ggopsHaVWK/img.png&quot; data-alt=&quot;index.js 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3N1qZ/dJMcagdyqbD/ZyrofjgC9s19ggopsHaVWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3N1qZ%2FdJMcagdyqbD%2FZyrofjgC9s19ggopsHaVWK%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;466&quot; height=&quot;153&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;index.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;이를 활용하여 `tsconfig.json` 에서 경로를 지정해 주면 alias(@)를 활용하여 각 컴포넌트에서 간단하게 import 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cX03rD/dJMcagR9wvC/4h5l1RqJwOVrJrpKps7wp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cX03rD/dJMcagR9wvC/4h5l1RqJwOVrJrpKps7wp0/img.png&quot; data-alt=&quot;tsconfig.ts 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cX03rD/dJMcagR9wvC/4h5l1RqJwOVrJrpKps7wp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcX03rD%2FdJMcagR9wvC%2F4h5l1RqJwOVrJrpKps7wp0%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;664&quot; height=&quot;180&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tsconfig.ts 예시&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;h2 data-ke-size=&quot;size26&quot;&gt;FSD의 장단점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 구조가 명확하여, 팀 간 협업 및 신규 작업자 온보딩이 용이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 추가 기능 작업 시, Side Effect 발생 확률이 줄어듦 (레이어-슬라이스 로 코드가 구분되어 있기 때문)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ 유지보수성 향상 (재사용 가능한 코드 레벨과 지역적으로만 사용하는 코드 레벨을 레이어에 따라 구분할 수 있기 때문)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ 높은 진입 장벽&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ 인식, 팀 문화 및 개념 준수가 필수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ Challenge와 Problem을 즉시 해결해야 함 (코드 문제와 개념에서 벗어난 부분을 즉시 확인 할 수 있음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제까지 프로젝트는 아래와 같이 전통적인 폴더구조로 작업을 했었다. 실제로 FSD를 알고 있긴 했으나, 잘 모르고 사용하기보다는 그냥 기존 폴더 구조 방향을 유지하려고 했었다.&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-filename=&quot;스크린샷 2026-02-28 16.16.49.png&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPKHNX/dJMcafeFuwm/RCnprtMNAS8Rv9W0Ol2zi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPKHNX/dJMcafeFuwm/RCnprtMNAS8Rv9W0Ol2zi1/img.png&quot; data-alt=&quot;이전 폴더구조 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPKHNX/dJMcafeFuwm/RCnprtMNAS8Rv9W0Ol2zi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPKHNX%2FdJMcafeFuwm%2FRCnprtMNAS8Rv9W0Ol2zi1%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;352&quot; height=&quot;362&quot; data-filename=&quot;스크린샷 2026-02-28 16.16.49.png&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;606&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;</description>
      <category>TIL</category>
      <category>멀티캠퍼스부트캠프</category>
      <category>부트캠프후기</category>
      <category>유레카 프론트엔드</category>
      <author>joooii</author>
      <guid isPermaLink="true">https://joooii.tistory.com/35</guid>
      <comments>https://joooii.tistory.com/35#entry35comment</comments>
      <pubDate>Sat, 28 Feb 2026 16:22:54 +0900</pubDate>
    </item>
  </channel>
</rss>