<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코드사냥꾼의 전리품</title>
    <link>https://codechasseur.tistory.com/</link>
    <description>깃허브에서도 TIL 작성 진행중 
https://github.com/bxxmi</description>
    <language>ko</language>
    <pubDate>Tue, 19 May 2026 01:52:08 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코드사냥꾼</managingEditor>
    <image>
      <title>코드사냥꾼의 전리품</title>
      <url>https://tistory1.daumcdn.net/tistory/3392103/attach/cae903ee2bc34d2fa05b9c15847791c8</url>
      <link>https://codechasseur.tistory.com</link>
    </image>
    <item>
      <title>Cursor + Figma MCP vs Claude Code CLI + Figma MCP 테스트 후기</title>
      <link>https://codechasseur.tistory.com/116</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 들어가며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼마 전, &lt;b&gt;Figma MCP를 활용해 UI 개발 속도를 크게 높인 회사 사례&lt;/b&gt;를 접하면서&lt;br /&gt;&amp;ldquo;나도 프로젝트에서 한번 써보고 싶다&amp;rdquo;는 생각을 계속 가지고 있었다.&lt;br /&gt;그러다 마침 프로젝트가 하나 마무리되면서 시간이 생겨, 본격적으로 Figma MCP 기반의 UI 자동화를 실험해보기로 했다.&lt;/p&gt;
&lt;p data-end=&quot;372&quot; data-start=&quot;347&quot; data-ke-size=&quot;size16&quot;&gt;현재 내가 사용하는 개발 환경은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;431&quot; data-start=&quot;374&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;393&quot; data-start=&quot;374&quot;&gt;&lt;b&gt;IDE:&lt;/b&gt; Cursor&lt;/li&gt;
&lt;li data-end=&quot;431&quot; data-start=&quot;394&quot;&gt;&lt;b&gt;AI 모델:&lt;/b&gt; Claude Code CLI (Opus 4.5)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;457&quot; data-start=&quot;433&quot; data-ke-size=&quot;size16&quot;&gt;여기서 자연스럽게 한 가지 궁금증이 생겼다.&lt;/p&gt;
&lt;blockquote data-end=&quot;575&quot; data-start=&quot;459&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;575&quot; data-start=&quot;461&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;Cursor와 Figma MCP를 직접 연동하는 경우와,&lt;br /&gt;Cursor IDE 내부에서 Claude Code CLI + Figma MCP 조합을 사용하는 경우는&lt;br /&gt;실제로 어떤 차이가 있을까?&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;721&quot; data-start=&quot;577&quot; data-ke-size=&quot;size16&quot;&gt;두 방식 모두 Figma MCP를 사용한다는 점은 같지만,&lt;br /&gt;&lt;b&gt;IDE&amp;middot;모델&amp;middot;맥락 접근 방식이 서로 완전히 다르기 때문에 결과물도 다를 것&lt;/b&gt;이라고 예상했다.&lt;br /&gt;그래서 이번 글에서는 이 두 가지 방식의 차이를 직접 비교해본 테스트 결과를 정리해보려 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1449&quot; data-start=&quot;1434&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 어떻게 비교했는가&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;949&quot; data-start=&quot;868&quot; data-ke-size=&quot;size16&quot;&gt;비교 테스트는 최근 내가 실제 프로젝트에서 만들었던 &lt;b&gt;&amp;lsquo;에디터 설정 패널 UI&amp;rsquo;&lt;/b&gt;를 Figma MCP로 다시 구현해보는 방식으로 진행했다.&lt;/p&gt;
&lt;p data-end=&quot;966&quot; data-start=&quot;951&quot; data-ke-size=&quot;size16&quot;&gt;테스트한 조합은 두 가지다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1060&quot; data-start=&quot;968&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;999&quot; data-start=&quot;968&quot;&gt;&lt;b&gt;Cursor IDE + Figma MCP&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1060&quot; data-start=&quot;1000&quot;&gt;&lt;b&gt;Cursor IDE 내부에서 Claude Code CLI(Opus 4.5) + Figma MCP&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;1086&quot; data-start=&quot;1062&quot; data-ke-size=&quot;size16&quot;&gt;그리고 아래 네 가지를 집중적으로 확인했다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1245&quot; data-start=&quot;1088&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1132&quot; data-start=&quot;1088&quot;&gt;&lt;b&gt;동일한 Figma를 기반으로 했을 때 UI 결과물이 어떻게 다른지&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1173&quot; data-start=&quot;1133&quot;&gt;&lt;b&gt; 프로젝트에 이미 존재하는 컴포넌트를 얼마나 잘 재사용하는지&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1212&quot; data-start=&quot;1174&quot;&gt;&lt;b&gt;상태/로직을 해석하고 구성하는 능력이 얼마나 차이나는지&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1245&quot; data-start=&quot;1213&quot;&gt;&lt;b&gt;&lt;b&gt; &lt;/b&gt;각 방식이 사용한 토큰량은 실제로 어느 정도인지&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 테스트 결과물 비교&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적인 결과물 비교에 앞서 내가 만들어야 하는 에디터 설정 패널 UI는 아래와 같았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-11 17.43.57.png&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;1798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ptmJ8/dJMcagEfi8k/NHA2oa0cx425XIfuxrkrDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ptmJ8/dJMcagEfi8k/NHA2oa0cx425XIfuxrkrDk/img.png&quot; data-alt=&quot;figma&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ptmJ8/dJMcagEfi8k/NHA2oa0cx425XIfuxrkrDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FptmJ8%2FdJMcagEfi8k%2FNHA2oa0cx425XIfuxrkrDk%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;796&quot; height=&quot;1798&quot; data-filename=&quot;스크린샷 2026-01-11 17.43.57.png&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;1798&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;figma&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;3-1. Cursor IDE + Figma MCP 결과물&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-11 19.06.47.png&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;1756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9CdLc/dJMcaiPwQmM/voRowFkIdfOYDfex9nDJ01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9CdLc/dJMcaiPwQmM/voRowFkIdfOYDfex9nDJ01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9CdLc/dJMcaiPwQmM/voRowFkIdfOYDfex9nDJ01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9CdLc%2FdJMcaiPwQmM%2FvoRowFkIdfOYDfex9nDJ01%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;792&quot; height=&quot;1756&quot; data-filename=&quot;스크린샷 2026-01-11 19.06.47.png&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;1756&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;b&gt;3-2. Cursor IDE 내부에서 Claude Code CLI(Opus 4.5) + Figma MCP&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-11 19.06.54.png&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;1758&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKEQzf/dJMcacIBrux/xySXDE7M05eBoSC2NYbCWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKEQzf/dJMcacIBrux/xySXDE7M05eBoSC2NYbCWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKEQzf/dJMcacIBrux/xySXDE7M05eBoSC2NYbCWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKEQzf%2FdJMcacIBrux%2FxySXDE7M05eBoSC2NYbCWK%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;784&quot; height=&quot;1758&quot; data-filename=&quot;스크린샷 2026-01-11 19.06.54.png&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;1758&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;790&quot; data-start=&quot;666&quot; data-ke-size=&quot;size16&quot;&gt;여기까지만 보면 두 방식 모두 Figma에 정의된 UI를 꽤 정확하게 재현해냈다.&lt;br /&gt;특히 프로젝트 내부에서 사용하는 스타일 토큰도 적절히 반영된 점은 동일하다!!&lt;/p&gt;
&lt;p data-end=&quot;790&quot; data-start=&quot;666&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 코드 구조는 어떨까?&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;790&quot; data-start=&quot;666&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 코드 구조 비교&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겉으로 봤을 때 UI는 거의 동일하지만 실제 코드를 살펴보면 &lt;b&gt;색상 관련 상태를 어떻게 연결했느냐에서 차이&lt;/b&gt;가 보였다.&lt;/p&gt;
&lt;h4 data-end=&quot;736&quot; data-start=&quot;713&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4-1. Cursor + Figma MCP&lt;/b&gt;&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768126612187&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;ColorPicker value={COLORS.GRAY['800']} css={colorPickerStyle} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;959&quot; data-start=&quot;816&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;320&quot; data-start=&quot;263&quot;&gt;ColorPicker에 value만 지정하고, onChange와 같은 핸들러는 없다.&lt;/li&gt;
&lt;li data-end=&quot;381&quot; data-start=&quot;321&quot;&gt;사용자가 색을 바꿔도 이 컴포넌트 관점에서는 &lt;b&gt;단순히 현재 색상을 보여주는 UI 요소&lt;/b&gt;에 그친다.&lt;br /&gt;즉, &lt;b&gt;UI 뼈대와 모양은 잘 구현되어 있지만, 상태와의 연결은 최소한으로만 되어 있는 상태&lt;/b&gt;다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;997&quot; data-start=&quot;961&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4-2. Cursor + Claude CLI + Figma MCP&lt;/b&gt;&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768126672005&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [titleColor, setTitleColor] = useState(COLORS.GRAY['800']);
&amp;lt;ColorPicker value={titleColor} css={colorPickerStyle} onChange={setTitleColor} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1402&quot; data-start=&quot;1161&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1668&quot; data-start=&quot;1659&quot;&gt;색상 상태를 분리해서 관리한다. 즉, &quot;색상 변경 UI를 그렸다&quot;가 아니라 &lt;b&gt;&quot;&lt;/b&gt;&lt;b&gt;이 프로젝트에서 실제로 색상을 바꿀 수 있는 설정 요소다&quot; &lt;/b&gt;라는 의도가 코드에 드러나도록 만들어진 느낌이었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1009&quot; data-start=&quot;982&quot; data-ke-size=&quot;size16&quot;&gt;그래서 같은 Figma를 기반으로 만들어졌음에도,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1126&quot; data-start=&quot;1011&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1060&quot; data-start=&quot;1011&quot;&gt;Cursor 쪽 코드는 &lt;b&gt;UI 모양과 구조를 잘 살린 뼈대&lt;/b&gt;에 더 가까웠고,&lt;/li&gt;
&lt;li data-end=&quot;1126&quot; data-start=&quot;1061&quot;&gt;Claude 쪽 코드는 &lt;b&gt;실제로 동작하는 설정 패널로 바로 이어 붙일 수 있는 코드&lt;/b&gt;에 더 가깝다고 느껴졌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;1668&quot; data-start=&quot;1659&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4-3. 동적 커스텀 필드 추가 요청을 했을 경우&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 한 가지가 더 궁금해졌다. &lt;b&gt;Figma에는 정의되어 있지 않은 기능&lt;/b&gt;을 추가해달라고 했을 때&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1316&quot; data-start=&quot;1241&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1265&quot; data-start=&quot;1241&quot;&gt;기존 프로젝트 컴포넌트를 재활용하는지&lt;/li&gt;
&lt;li data-end=&quot;1290&quot; data-start=&quot;1266&quot;&gt;재사용성을 고려해 로직을 구성하는지&lt;/li&gt;
&lt;li data-end=&quot;1316&quot; data-start=&quot;1291&quot;&gt;그리고 동적 필드 UI를 어떻게 설계하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1335&quot; data-start=&quot;1318&quot; data-ke-size=&quot;size16&quot;&gt;를 한번 더 비교해보고 싶었다.&lt;/p&gt;
&lt;p data-end=&quot;1387&quot; data-start=&quot;1337&quot; data-ke-size=&quot;size16&quot;&gt;그래서 동일한 Figma 패널에 대해 두 방식 모두에게 &lt;b&gt;&quot;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;커스텀 필드를 동적으로 추가&amp;middot;삭제할 수 있는 영역을 만들어줘. 전체 값은 CustomField[] 배열로 관리하고, 값이 비어있을 때의 문구도 추가해줘.&quot; &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;와&amp;nbsp;&lt;/span&gt;&lt;/span&gt;같은 요구를 추가로 던졌다.&lt;/p&gt;
&lt;p data-end=&quot;1387&quot; data-start=&quot;1337&quot; data-ke-size=&quot;size16&quot;&gt;두 결과물 모두 CustomField 타입과 customFields 상태와 추가/삭제/변경 핸들러를 만드는 데에는 성공했지만,&lt;br /&gt;디테일한 구현 방식에서 약간의 차이가 있었다.&lt;/p&gt;
&lt;h4 data-end=&quot;593&quot; data-start=&quot;563&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4-3-1. Cursor + Figma MCP&lt;/b&gt;&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768127821711&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface CustomField { id: string; value: string; }

const [customFields, setCustomFields] = useState&amp;lt;CustomField[]&amp;gt;([]);

const handleAddCustomField = () =&amp;gt; {
   const newField: CustomField = { id: `field_${Date.now()}`, value: '', };
   setCustomFields(prev =&amp;gt; [...prev, newField]);
 };
   
const handleRemoveCustomField = (id: string) =&amp;gt; {
   setCustomFields(prev =&amp;gt; prev.filter(field =&amp;gt; field.id !== id));
};
   
const handleChangeCustomFieldValue = (id: string, value: string) =&amp;gt; { 
   setCustomFields(prev =&amp;gt; prev.map(field =&amp;gt; (field.id === id ? { ...field, value } : field))); 
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1249&quot; data-start=&quot;1197&quot; data-ke-size=&quot;size16&quot;&gt;UI 쪽을 보면, &lt;b&gt;기존 디자인 시스템 컴포넌트를 적극적으로 활용&lt;/b&gt;하는 쪽으로 구현되었다.&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768127909843&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Flex direction=&quot;column&quot; gap={8}&amp;gt;
 &amp;lt;Flex css={labelContainerStyle} items=&quot;center&quot; justify=&quot;space-between&quot;&amp;gt;
  &amp;lt;Tooltip message=&quot;동적으로 커스텀 필드를 추가할 수 있습니다.&quot; position=&quot;bottomLeft&quot; onlyText&amp;gt;
   &amp;lt;span css={labelStyle}&amp;gt;커스텀 필드&amp;lt;/span&amp;gt;
  &amp;lt;/Tooltip&amp;gt;
  &amp;lt;ButtonSecondary icon={&amp;lt;PlusOutlined /&amp;gt;} size=&quot;small&quot; onClick={handleAddCustomField}&amp;gt;필드 추가&amp;lt;/ButtonSecondary&amp;gt; 
 &amp;lt;/Flex&amp;gt;
 
 {customFields.length &amp;gt; 0 &amp;amp;&amp;amp; (
   &amp;lt;Flex css={customFieldListStyle} direction=&quot;column&quot; gap={8}&amp;gt;
    {customFields.map((field, index) =&amp;gt; (
      &amp;lt;Flex key={field.id} css={customFieldItemStyle} gap={8} items=&quot;center&quot;&amp;gt;
       &amp;lt;span css={customFieldIndexStyle}&amp;gt;{index + 1}&amp;lt;/span&amp;gt;
       &amp;lt;TextInput value={field.value} placeholder=&quot;값을 입력하세요.&quot; css={customFieldInputStyle} onChange={e =&amp;gt; handleChangeCustomFieldValue(field.id, e.target.value)} /&amp;gt;
       &amp;lt;ButtonText css={customFieldDeleteBtnStyle} icon={&amp;lt;CloseOutlined /&amp;gt;} size=&quot;small&quot; onClick={() =&amp;gt; handleRemoveCustomField(field.id)} /&amp;gt;
      &amp;lt;/Flex&amp;gt; 
       ))}
   &amp;lt;/Flex&amp;gt;  
  )}
  
 {customFields.length === 0 &amp;amp;&amp;amp; &amp;lt;div css={emptyCustomFieldStyle}&amp;gt;추가된 필드가 없습니다.&amp;lt;/div&amp;gt;}
 &amp;lt;/Flex&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2512&quot; data-start=&quot;2495&quot; data-ke-size=&quot;size16&quot;&gt;여기서는&amp;nbsp;&lt;b&gt;ButtonSecondary, ButtonText, TextInput, Flex 등 기존에 정의되어있던 내부 공통 컴포넌트를 최대한 재사용&lt;/b&gt;했다는 점과&amp;nbsp;인덱스를 동그란 배지(customFieldIndexStyle)로 보여주고 리스트 전체를 박스로 감싸는 등 &lt;b&gt;UI 완성도&lt;/b&gt;에 신경을 많이 썼다는 점이 인상깊었다.&lt;/p&gt;
&lt;h4 data-end=&quot;2884&quot; data-start=&quot;2841&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4-3-2. Cursor + Claude CLI + Figma MCP&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;2908&quot; data-start=&quot;2886&quot; data-ke-size=&quot;size16&quot;&gt;Claude 의 비지니스 로직은 동일했지만&amp;nbsp;UI는 조금 다른 방향성을 취했다.&lt;/p&gt;
&lt;pre id=&quot;code_1768128109992&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Flex direction=&quot;column&quot; gap={8}&amp;gt;
 &amp;lt;Flex css={labelContainerStyle} items=&quot;center&quot; justify=&quot;space-between&quot;&amp;gt;
  &amp;lt;span css={labelStyle}&amp;gt;커스텀 필드&amp;lt;/span&amp;gt;
  &amp;lt;button css={addFieldButtonStyle} type=&quot;button&quot; onClick={handleAddCustomField}&amp;gt;
   &amp;lt;PlusOutlined /&amp;gt;
   &amp;lt;span&amp;gt;필드 추가&amp;lt;/span&amp;gt;
  &amp;lt;/button&amp;gt;
 &amp;lt;/Flex&amp;gt;
 
 {customFields.map(field =&amp;gt; (
   &amp;lt;Flex key={field.id} gap={8} items=&quot;center&quot;&amp;gt;
    &amp;lt;TextInput value={field.value} placeholder=&quot;값을 입력하세요.&quot; css={customFieldInputStyle} onChange={e =&amp;gt; handleCustomFieldChange(field.id, e.target.value)} /&amp;gt;
    &amp;lt;button css={deleteFieldButtonStyle} type=&quot;button&quot; onClick={() =&amp;gt; handleRemoveCustomField(field.id)}&amp;gt;
     &amp;lt;DeleteOutlined /&amp;gt;
    &amp;lt;/button&amp;gt;
   &amp;lt;/Flex&amp;gt;
  ))}
  
  {customFields.length === 0 &amp;amp;&amp;amp; &amp;lt;span css={emptyFieldTextStyle}&amp;gt;추가된 필드가 없습니다.&amp;lt;/span&amp;gt;}  
&amp;lt;/Flex&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;4485&quot; data-start=&quot;4480&quot; data-ke-size=&quot;size16&quot;&gt;여기서는 필드 추가/삭제 버튼을 &lt;b&gt;디자인 시스템 컴포넌트가 아닌, 네이티브 button + Emotion 스타일&lt;/b&gt;로 풀어냈고 인덱스 표시, 리스트 박스 등은 과감히 생략하고 다소 심플한 구조로 구현했다.&lt;/p&gt;
&lt;h4 data-end=&quot;4732&quot; data-start=&quot;4705&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4-3-3. 커스텀 필드에서 느껴진 차이&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;4778&quot; data-start=&quot;4734&quot; data-ke-size=&quot;size16&quot;&gt;같은 요구사항을 던졌을 때:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5008&quot; data-start=&quot;4780&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4887&quot; data-start=&quot;4780&quot;&gt;&lt;b&gt;Cursor + Figma MCP&lt;/b&gt;&lt;br /&gt;&amp;rarr; 이미 있는 컴포넌트들을 적극적으로 재사용하면서&lt;br /&gt;&amp;rarr; Figma처럼 보이는 완성형 UI를 만들어주는 쪽에 강점이 있었다고 느꼈다.&lt;/li&gt;
&lt;li data-end=&quot;5008&quot; data-start=&quot;4889&quot;&gt;&lt;b&gt;Cursor + Claude CLI + Figma MCP&lt;/b&gt;&lt;br /&gt;&amp;rarr; id 생성, 버튼 역할 분리, 상태 관리 등&lt;br /&gt;&amp;rarr; &lt;b&gt;실제 동작하는 폼 로직과 구조 &lt;/b&gt;쪽에 조금 더 신경을 쓴 느낌이었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;5083&quot; data-start=&quot;5010&quot; data-ke-size=&quot;size16&quot;&gt;둘 다 요구사항은 잘 만족했지만 &lt;b&gt;어디에 중심을 두고 코드를 작성하는지 보여서&amp;nbsp;&lt;/b&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;h3 data-end=&quot;5083&quot; data-start=&quot;5010&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 토큰 사용량 비교&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-end=&quot;2363&quot; data-start=&quot;2284&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5-1. Cursor + Figma MCP&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY2h4S/dJMcagqGdxY/rGiqgubpGyVKn7VgoE3ZrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY2h4S/dJMcagqGdxY/rGiqgubpGyVKn7VgoE3ZrK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;92&quot; data-filename=&quot;스크린샷 2026-01-11 17.24.24.png&quot; style=&quot;width: 45.1533%; margin-right: 10px;&quot; data-widthpercent=&quot;45.68&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY2h4S/dJMcagqGdxY/rGiqgubpGyVKn7VgoE3ZrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY2h4S%2FdJMcagqGdxY%2FrGiqgubpGyVKn7VgoE3ZrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHvIDI/dJMcahXr9vW/REkM1yJ9OQGWdT5hh9710K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHvIDI/dJMcahXr9vW/REkM1yJ9OQGWdT5hh9710K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;78&quot; data-filename=&quot;스크린샷 2026-01-11 17.42.38.png&quot; style=&quot;width: 53.6839%;&quot; data-widthpercent=&quot;54.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHvIDI/dJMcahXr9vW/REkM1yJ9OQGWdT5hh9710K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHvIDI%2FdJMcahXr9vW%2FREkM1yJ9OQGWdT5hh9710K%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;504&quot; height=&quot;78&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5-2. &lt;/b&gt;&lt;b&gt;Cursor + Claude CLI + Figma MCP&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccQLDr/dJMcai23pm8/0k2XzmCDW189WDaakIJXxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccQLDr/dJMcai23pm8/0k2XzmCDW189WDaakIJXxk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;552&quot; data-filename=&quot;스크린샷 2026-01-11 18.08.56.png&quot; style=&quot;width: 54.0735%; margin-right: 10px;&quot; data-widthpercent=&quot;54.71&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccQLDr/dJMcai23pm8/0k2XzmCDW189WDaakIJXxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccQLDr%2FdJMcai23pm8%2F0k2XzmCDW189WDaakIJXxk%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;1114&quot; height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmxV2H/dJMcaaRxPFx/FgxTKb9J13LIJftnn5Nbek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmxV2H/dJMcaaRxPFx/FgxTKb9J13LIJftnn5Nbek/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;668&quot; data-filename=&quot;스크린샷 2026-01-11 18.12.44.png&quot; style=&quot;width: 44.7637%;&quot; data-widthpercent=&quot;45.29&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmxV2H/dJMcaaRxPFx/FgxTKb9J13LIJftnn5Nbek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmxV2H%2FdJMcaaRxPFx%2FFgxTKb9J13LIJftnn5Nbek%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;1116&quot; height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;/div&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. 그래서 어떤 조합으로 사용할 것인가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 방식이 더 좋다고 단정하기는 어려웠고, 효과적으로 활용하려면 더 연구와 경험이 필요하다고 느꼈다.&lt;br /&gt;일단 UI 초안 단계에서는 토큰 낭비를 줄이기 위해 &lt;b&gt;아토믹 컴포넌트 단위로 빠르게 스케치를 뽑는 용도&lt;/b&gt;로 Cursor + Figma MCP를 적극적으로 사용할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이후 로직 연결이나 실제 기능 구현 단계에서는 기존 프로젝트 맥락을 잘 따라오는 &lt;b&gt;Cursor + Claude CLI 조합&lt;/b&gt;을 활용하는 방식이 가장 현실적인 흐름이라고 생각한다.&lt;/p&gt;</description>
      <category>개발회고 </category>
      <author>코드사냥꾼</author>
      <guid isPermaLink="true">https://codechasseur.tistory.com/116</guid>
      <comments>https://codechasseur.tistory.com/116#entry116comment</comments>
      <pubDate>Sun, 11 Jan 2026 19:58:12 +0900</pubDate>
    </item>
    <item>
      <title>귀찮지만 중요한 반복작업을 AI와 함께 자동화해보자! (w. Cursor AI)</title>
      <link>https://codechasseur.tistory.com/115</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;feconf 2025를 다녀오고 나서 바로 실천해야겠다고 마음먹은 것은 Cursor AI를 활용해서 &lt;b&gt;귀찮은 반복작업을 자동화하는 일&lt;/b&gt;이었다.&lt;br /&gt;&lt;span style=&quot;font-size: 0.87em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;  &lt;i&gt;f&lt;/i&gt;econf 2025 후기가 궁금하다면, &lt;/span&gt;&lt;a style=&quot;font-size: 0.87em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; href=&quot;https://codechasseur.tistory.com/114&quot; data-end=&quot;458&quot; data-start=&quot;416&quot;&gt;여기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 Cursor AI를 사용하면서 &quot;어떻게 해야 진짜 잘 활용할 수 있을까?&quot;, &quot;AI 시대에 (프론트엔드) 개발자로서 나는 어떻게 성장해야 하지?&quot;를 항상 고민했었다.&lt;/p&gt;
&lt;p data-end=&quot;545&quot; data-start=&quot;357&quot; data-ke-size=&quot;size16&quot;&gt;단순히 &quot;특정 라이브러리 예제를 보여줘&quot; 같은 생성형 AI 사용법만으로는 오히려 의존도만 높아지고, 실질적인 성장이나 생산성 향상으로는 잘 이어지지 않는다는 생각이 들었기 때문이다.&lt;br /&gt;&lt;br /&gt;정말 중요한 건 하루 중 대부분을 보내는 회사에서 &lt;b&gt;어떻게 AI를 활용해 업무의 효율을 올리고, 그 과정에서 성장할 수 있을까&lt;/b&gt; 고민하는 자세라고 생각했다.&lt;/p&gt;
&lt;p data-end=&quot;627&quot; data-start=&quot;547&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이번 컨퍼런스에서 얻은 인사이트를 바탕으로&lt;br /&gt;거창한 것보다는 내가 실제로 가장 귀찮음을 느끼는 부분부터 조금씩 개선해보려고 했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;어떤 것을 자동화 할 것인가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무 프로세스 중에서 효율적으로 처리하고 싶었던 반복 작업은 아래 두 가지였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;627&quot; data-start=&quot;547&quot;&gt;Figma에서 받은 스타일 코드 변환&lt;/li&gt;
&lt;li data-end=&quot;627&quot; data-start=&quot;547&quot;&gt;Tanstack Query 구문 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TanStack Query 작성은 실제로 사용하는 데이터 구조에 따라 달라지는 부분이 많아서 우선순위에서 잠시 미뤘다.&lt;br /&gt;대신, 퍼블리싱 단계에서 특히 내가 번거롭게 느꼈던 &lt;b&gt;&amp;lsquo;Figma 스타일 코드 변환&amp;rsquo;부터&lt;/b&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실제 예시&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무 시 디자이너와는 Figma를 통해 협업한다.&lt;br /&gt;UI 시안을 전달받으면 먼저 &amp;lt;div&amp;gt;, &amp;lt;section&amp;gt; 등을 사용해 마크업 구조를 잡고 이후 스타일을 적용한다.&lt;/p&gt;
&lt;p data-end=&quot;169&quot; data-start=&quot;136&quot; data-ke-size=&quot;size16&quot;&gt;Figma에서는 보통 아래와 같은 형태로 스타일이 전달된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-08-27 12.55.11.png&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lm2ex/btsP6D9eNEU/iWzEAV0pvO8H8Dr0LHYWM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lm2ex/btsP6D9eNEU/iWzEAV0pvO8H8Dr0LHYWM0/img.png&quot; data-alt=&quot;Figma에서 정의된 스타일 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lm2ex/btsP6D9eNEU/iWzEAV0pvO8H8Dr0LHYWM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLm2ex%2FbtsP6D9eNEU%2FiWzEAV0pvO8H8Dr0LHYWM0%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;1270&quot; height=&quot;396&quot; data-filename=&quot;스크린샷 2025-08-27 12.55.11.png&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figma에서 정의된 스타일 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-08-27 12.55.30.png&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baXq9P/btsP86WdNV9/R7fGu2GjViuNzgLoJIaN1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baXq9P/btsP86WdNV9/R7fGu2GjViuNzgLoJIaN1K/img.png&quot; data-alt=&quot;Figma에서 정의된 스타일 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baXq9P/btsP86WdNV9/R7fGu2GjViuNzgLoJIaN1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaXq9P%2FbtsP86WdNV9%2FR7fGu2GjViuNzgLoJIaN1K%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;1238&quot; height=&quot;242&quot; data-filename=&quot;스크린샷 2025-08-27 12.55.30.png&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figma에서 정의된 스타일 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;253&quot; data-start=&quot;153&quot; data-ke-size=&quot;size16&quot;&gt;사실 이렇게 그대로 붙여 넣어도 UI 스타일링에는 전혀 문제가 없다!&lt;br /&gt;다만 프로젝트에는 정해진 코드 컨벤션이 있고, 유지보수를 고려했을 때 현재 방식은 가독성이 떨어진다.&lt;/p&gt;
&lt;p data-end=&quot;600&quot; data-start=&quot;503&quot; data-ke-size=&quot;size16&quot;&gt;또한, 디자이너와 협의한 색상, 폰트 굵기 등의 &lt;b&gt;디자인 토큰&lt;/b&gt;이 정의되어 있기 때문에,&lt;br /&gt;이를 적극적으로 활용하는 편이 코드의 일관성을 높이고 소통에도 훨씬 유리하다.&lt;/p&gt;
&lt;p data-end=&quot;645&quot; data-start=&quot;602&quot; data-ke-size=&quot;size16&quot;&gt;그래서 매번 코드를 붙여넣은 뒤 아래와 같이 수동으로 변환했다.&lt;/p&gt;
&lt;pre id=&quot;code_1756476830820&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const style = css`
    color: ${COLORS.GRAY['200']};
    font-weight: ${FONT_WEIGHT.BOLD};
`;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 반복 작업을 더 효율적으로 처리하기 위해 &lt;b&gt;Cursor AI에 간단한 규칙을 만들어 적용해 보기로&lt;/b&gt; 했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;규칙을 만들고 Cursor AI에&lt;span&gt;&amp;nbsp;&lt;/span&gt;적용해보자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;352&quot; data-start=&quot;306&quot; data-ke-size=&quot;size16&quot;&gt;디자인 토큰으로 변환하는 과정에서 사용되는 color, background, font-weight 등의 속성에 대해&lt;br /&gt;정규식을 활용해 자동으로 치환해 주는 &lt;b&gt;Cursor AI 룰&lt;/b&gt;을 작성해 보았다. 아래는 예시이다!&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1756477043754&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# font-weight 숫자를 토큰으로 변환
Find: font-weight:\s*400
Replace: ${FONT_WEIGHT.REGULAR}

Find: font-weight:\s*600
Replace: ${FONT_WEIGHT.SEMIBOLD}

Find: font-weight:\s*700
Replace: ${FONT_WEIGHT.BOLD}

# 색상
Find: color:\s*#4B5563
Replace: ${COLORS.GRAY['600']}

# border-color
Find: border:\s*1px solid #E5E6E9
Replace: border: 1px solid ${COLORS.GRAY['200']};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;870&quot; data-start=&quot;785&quot; data-ke-size=&quot;size16&quot;&gt;해당 룰은 design-tokens-rule.mdc라는 파일로 만들어서 관리했고 fix css라는 명령어를 입력하면 코드를 자동으로 변환해 줬다!&lt;/p&gt;
&lt;p data-end=&quot;870&quot; data-start=&quot;785&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Before&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-08-29 23.44.53.png&quot; data-origin-width=&quot;1790&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NTAyD/btsQdYRWXda/NvmnXfiab1R9X7koLK5Nl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NTAyD/btsQdYRWXda/NvmnXfiab1R9X7koLK5Nl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NTAyD/btsQdYRWXda/NvmnXfiab1R9X7koLK5Nl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNTAyD%2FbtsQdYRWXda%2FNvmnXfiab1R9X7koLK5Nl0%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;1790&quot; height=&quot;254&quot; data-filename=&quot;스크린샷 2025-08-29 23.44.53.png&quot; data-origin-width=&quot;1790&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1571&quot; data-start=&quot;1424&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;After&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-08-29 23.45.05.png&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cesXu5/btsQcq2KPNa/9OLqu37eCdZG4E3FPYgYd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cesXu5/btsQcq2KPNa/9OLqu37eCdZG4E3FPYgYd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cesXu5/btsQcq2KPNa/9OLqu37eCdZG4E3FPYgYd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcesXu5%2FbtsQcq2KPNa%2F9OLqu37eCdZG4E3FPYgYd1%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;1816&quot; height=&quot;544&quot; data-filename=&quot;스크린샷 2025-08-29 23.45.05.png&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1571&quot; data-start=&quot;1424&quot; data-ke-size=&quot;size16&quot;&gt;단지, 해당 코드에서 fix css 명령어만 작성했을 뿐인데 정의해 둔 디자인 토큰으로 자동 변환되고 심지어 필요한 경우에는 COLORS와 같은 외부 변수도 자동으로 import 된다. (import 처리 역시 Cursor AI 룰에 포함시켜 둔 부분이다!)&lt;/p&gt;
&lt;p data-end=&quot;382&quot; data-start=&quot;277&quot; data-ke-size=&quot;size16&quot;&gt;굉장히 작은 부분의 개선이지만 이번 프로젝트를 진행하면서 &lt;b&gt;가장 만족스러웠던 작업&lt;/b&gt;이었다. &lt;br /&gt;그래서 관련 내용을 내부 문서로 정리해 두고 Cursor AI를 사용하는 팀 내 다른 분들에게도 슬랙으로 공유했다.&lt;/p&gt;
&lt;p data-end=&quot;456&quot; data-start=&quot;384&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;스크린샷 2025-08-29 23.51.21.png&quot; data-origin-width=&quot;1666&quot; data-origin-height=&quot;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drHRiE/btsQc6bTWF8/itJJSo2JETXG70HR2kgXsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drHRiE/btsQc6bTWF8/itJJSo2JETXG70HR2kgXsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drHRiE/btsQc6bTWF8/itJJSo2JETXG70HR2kgXsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrHRiE%2FbtsQc6bTWF8%2FitJJSo2JETXG70HR2kgXsk%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;683&quot; height=&quot;409&quot; data-filename=&quot;스크린샷 2025-08-29 23.51.21.png&quot; data-origin-width=&quot;1666&quot; data-origin-height=&quot;998&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;만약, 이 글을 보시고 더 나은 방식이나 아이디어가 떠오르신다면 공유해 주시면 감사하겠습니다.&lt;br /&gt;저도 이제 막 고민하면서 알아가는 중이라 함께 개선하면 좋을 것 같습니다.  &lt;/p&gt;</description>
      <category>개발회고 </category>
      <author>코드사냥꾼</author>
      <guid isPermaLink="true">https://codechasseur.tistory.com/115</guid>
      <comments>https://codechasseur.tistory.com/115#entry115comment</comments>
      <pubDate>Fri, 29 Aug 2025 23:56:00 +0900</pubDate>
    </item>
    <item>
      <title>[FEconf2025] 컨퍼런스 참석 및 세션 별 후기</title>
      <link>https://codechasseur.tistory.com/114</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-start=&quot;526&quot; data-end=&quot;653&quot; data-ke-size=&quot;size16&quot;&gt;회사와 집을 오가는 일상이 어느새 익숙한 루틴이 되어버렸다.&lt;br /&gt;그러다 보니 집에 오면 묘한 보상심리에서 &amp;ldquo;온/오프는 확실하게 해야지!&amp;rdquo;라는 다짐과 함께, 개발과는 전혀 상관없는 취미 생활에만 몰두하게 되곤 했다.&lt;/p&gt;
&lt;p data-end=&quot;470&quot; data-start=&quot;407&quot; data-ke-size=&quot;size16&quot;&gt;그런데 문득, 이렇게 놀기만 하다가는(?) 어느 순간 우물 안 개구리가 되어버릴 수도 있겠다는 생각이 들었다.&lt;/p&gt;
&lt;p data-end=&quot;598&quot; data-start=&quot;472&quot; data-ke-size=&quot;size16&quot;&gt;개발자가 된 이후로 나는 이 우물 안 개구리 상태를 특히 경계하게 됐다.&lt;br /&gt;아무래도 컴퓨터 언어로 다양한 문제를 마주하고 해결해야 하는 직업이다 보니 흐름을 놓치면 나는 그저&amp;hellip; 대과거 사람이 되는 것 같달까? 허허&lt;/p&gt;
&lt;p data-end=&quot;714&quot; data-start=&quot;600&quot; data-ke-size=&quot;size16&quot;&gt;그래서 나는 누군가의 경험을 듣고, 그 안에서 인사이트를 얻는 걸 좋아한다.&lt;br /&gt;교육이든 컨퍼런스든 뭔가 열린다는 소식을 들으면 &amp;ldquo;일단 가보자&amp;rdquo;는 마음으로, 기회가 닿을 때마다 참석하려고 노력하는 편이다.&lt;/p&gt;
&lt;p data-end=&quot;805&quot; data-start=&quot;716&quot; data-ke-size=&quot;size16&quot;&gt;마침 FEConf가 열린다는 소식을 들었고, 게다가 평소 관심 있던 주제까지 포함되어 있었다.&lt;br /&gt;망설일 이유가 없었다. 자연스럽게 결제 버튼을 누르고 있었다.&lt;/p&gt;
&lt;p data-end=&quot;830&quot; data-start=&quot;812&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;뜻밖의 몰입, 라이트닝 톡&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;960&quot; data-start=&quot;832&quot; data-ke-size=&quot;size16&quot;&gt;이번 컨퍼런스에서 가장 인상 깊었던 건, 예상 밖에도 라이트닝 톡 세션이었다.&lt;br /&gt;짧은 시간 안에 각자의 시행착오와 고민을 솔직하게 공유하는 포맷이 너무나 와닿았고, 오히려 실전에서 바로 써먹을 수 있는 인사이트들이 넘쳐났다.&lt;/p&gt;
&lt;p data-end=&quot;1057&quot; data-start=&quot;962&quot; data-ke-size=&quot;size16&quot;&gt;그래서 나는 미리 정해둔 세션들을 과감히 넘기고 &lt;b&gt;C홀 지박령&lt;/b&gt;이 되어 라이트닝 톡만 들었다.&lt;br /&gt;(게다가 해당 세션들은 영상도 안 올라온다는 희소성까지!)&lt;/p&gt;
&lt;p data-end=&quot;1204&quot; data-start=&quot;1064&quot; data-ke-size=&quot;size16&quot;&gt;내가 직접 듣고 메모했던 인상 깊은 세션들과 몰입했던 라이트닝 톡을 중심으로 회고를 정리하려고 한다.&lt;br /&gt;무엇이 그렇게까지 나를 집중하게 만들었는지, 그 안에서 어떤 생각의 전환이 있었는지를 돌아보며 공유해보려 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;후기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2025-08-25-12-20-37 001.jpeg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K46tw/btsP5eUxx1U/Tb5fSOSwth1G61l6ECKY0k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K46tw/btsP5eUxx1U/Tb5fSOSwth1G61l6ECKY0k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K46tw/btsP5eUxx1U/Tb5fSOSwth1G61l6ECKY0k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK46tw%2FbtsP5eUxx1U%2FTb5fSOSwth1G61l6ECKY0k%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;3024&quot; height=&quot;4032&quot; data-filename=&quot;KakaoTalk_Photo_2025-08-25-12-20-37 001.jpeg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행사는 세종대학교 광개토관에서 열렸다.&lt;br /&gt;주제별로 A, B, C홀로 나뉘어 세션이 진행되어서 듣고 싶은 세션을 골라 자유롭게 이동할 수 있었다.&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;KakaoTalk_Photo_2025-08-25-12-25-04.jpeg&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHO8ax/btsP5uiP0hX/0806I3IXbyP3x5uUqzV5M1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHO8ax/btsP5uiP0hX/0806I3IXbyP3x5uUqzV5M1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHO8ax/btsP5uiP0hX/0806I3IXbyP3x5uUqzV5M1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHO8ax%2FbtsP5uiP0hX%2F0806I3IXbyP3x5uUqzV5M1%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;921&quot; data-filename=&quot;KakaoTalk_Photo_2025-08-25-12-25-04.jpeg&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;921&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 모노레포 절망편 세션은 아쉽게도 보지 못했다.&lt;br /&gt;위에서 썼듯 나는 &lt;b&gt;C홀 라이트닝 톡 세션의 지박령&lt;/b&gt;이 되어버린 상태였고&amp;hellip;&lt;br /&gt;결국 자리를 뜰 수가 없었다. (진짜 너무 재밌었음)&lt;/p&gt;
&lt;p data-end=&quot;278&quot; data-start=&quot;218&quot; data-ke-size=&quot;size16&quot;&gt;해당 세션은 꼭 보고 싶었던 주제라 아쉬움이 크지만,&lt;br /&gt;&lt;b&gt;추후 영상이 업로드되면 반드시 챙겨볼 예정!&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;653&quot; data-start=&quot;526&quot; data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;[세션] Memo를 지울 결심 : React Compiler&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-end=&quot;529&quot; data-start=&quot;393&quot; data-ke-size=&quot;size16&quot;&gt;해당 세션은 리멤버앤컴퍼니의 장용석 님이 진행하셨다.&lt;/p&gt;
&lt;p data-end=&quot;328&quot; data-start=&quot;199&quot; data-ke-size=&quot;size16&quot;&gt;이 세션을 들으면서, 리멤버앤컴퍼니의 FE 직군은 동작 원리까지 깊이 파악하고 실제로 적용한다는 인상을 받았다.&lt;br /&gt;처음엔 React Compiler를 소개하고 적용 효과를 공유해 주시는 자리일 줄 알았는데 내 예상과는 달랐다.&lt;/p&gt;
&lt;p data-end=&quot;426&quot; data-start=&quot;330&quot; data-ke-size=&quot;size16&quot;&gt;사실 나는 새로운 기술을 마주할 때, 동작 원리에 대해 코드로까지 깊이 파고들지는 못했었다.&lt;br /&gt;이번 세션을 계기로, 점진적으로라도 이런 자세를 키워야겠다는 생각이 들었다.&lt;/p&gt;
&lt;p data-end=&quot;564&quot; data-start=&quot;428&quot; data-ke-size=&quot;size16&quot;&gt;비록 지금 당장 React Compiler를 적용하진 못하더라도 이번 세션을 통해 React Compiler 자체에 대해 알게 되었고,&lt;br /&gt;Memo와의 차이점을 통해 캐싱을 잘 활용하려면 어디까지 고민해야 하는지도 인사이트를 얻을 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;749&quot; data-start=&quot;566&quot; data-ke-size=&quot;size16&quot;&gt;아쉬운 점이 있다면, 실제 현업에서의 경험담이나 사례도 더 듣고 싶었다는 것이다.&lt;br /&gt;&lt;br /&gt;세션에서는 주로 기술의 동작 원리에 집중해서 설명해주셨는데,&lt;br /&gt;그전에는 &lt;b&gt;&lt;i&gt;어떤 문제를 겪었는지, 그래서 리멤버는 왜 Compiler를 도입했는지, 도입 이후에는 실제로 어떤 효과가 있었는지&lt;/i&gt; &lt;/b&gt;같은&lt;br /&gt;현실적인 이야기도 해주실 줄 알고 기대했다.&lt;/p&gt;
&lt;p data-end=&quot;848&quot; data-start=&quot;751&quot; data-ke-size=&quot;size16&quot;&gt;그렇지만 아마도 이런 부분을 구체적으로 언급하지 않으셨던 것은,&lt;br /&gt;결국 발표를 듣는 &lt;b&gt;&lt;i&gt;각자가 직접 고민하고 자기 경험에 비춰서 인사이트를 얻으라는 의도가 아니었을까&lt;/i&gt; &lt;/b&gt;싶기도 하다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;[세션] 1년에 10억 원을 절약한, 강남언니의 SEO 웹 전략 공개&amp;nbsp;&lt;/b&gt;&lt;/span&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;나는 사내에서 운영 중인 여러 웹 소개 사이트를 만들다 보니 SEO나 웹 퍼포먼스에 대해 자연스럽게 관심을 가졌었다.&lt;br /&gt;마케팅 팀과 협업하면서 sitemap.xml, robots.txt, Google Search Console 등도 최대한 잘 활용하려고 노력했고, &lt;br /&gt;SSR을 위해 Next.js도 적용해봤다.&lt;/p&gt;
&lt;p data-end=&quot;420&quot; data-start=&quot;325&quot; data-ke-size=&quot;size16&quot;&gt;그래도 사실 항상 &amp;ldquo;이렇게 하는 게 맞나..?&amp;rdquo; 고민이 있었고,&lt;br /&gt;특히 사용자 수가 많은 서비스에서는 실제로 어떻게 SEO를 운영하고 있는지 궁금해서 이 세션을 듣게 됐다.&lt;/p&gt;
&lt;p data-end=&quot;566&quot; data-start=&quot;422&quot; data-ke-size=&quot;size16&quot;&gt;이번 세션을 통해 내가 했던 시도들이 비즈니스 성공을 위한 연결 과정이었다는 걸 다시금 느꼈고,&lt;br /&gt;생각보다 더 다양한 SEO 전략과 실제 성과로 이어지는 노하우들이 많다는 것도 알게 됐다.&lt;br /&gt;&lt;br /&gt;아직도 배울 게 정말 많다는 걸 깨달은, 의미 있는 시간이었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;[라이트닝 톡] 과거에도 없었고 현재에도 없는 사수에 기대지 않는 성장법&lt;/b&gt;&lt;/span&gt;&lt;/h4&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;KakaoTalk_Photo_2025-08-25-22-24-34 004.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMTkx8/btsP611VOyK/qIz5slZIsHgBFJt2lD09q1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMTkx8/btsP611VOyK/qIz5slZIsHgBFJt2lD09q1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMTkx8/btsP611VOyK/qIz5slZIsHgBFJt2lD09q1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMTkx8%2FbtsP611VOyK%2FqIz5slZIsHgBFJt2lD09q1%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;4032&quot; height=&quot;3024&quot; data-filename=&quot;KakaoTalk_Photo_2025-08-25-22-24-34 004.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세션을 꼭 들어야겠다고 마음먹은 건, 지금 내 상황과 닮아 있었기 때문이다.&lt;br /&gt;3년 차가 되는 동안, 특별한 멘토나 사수 없이 스스로 문제를 해결해 온 시간이 많았다.&lt;br /&gt;동료 개발자분들이 계시긴 했지만, 각자 다른 프로젝트를 맡고 있어 직접적으로 교류할 기회가 많지 않았다.&lt;br /&gt;지금은 한 팀으로 함께 일하고 있지만, 여전히 혼자 온전히 스스로 부딪혀야하고 고민하는 일이 많다.&lt;/p&gt;
&lt;p data-end=&quot;358&quot; data-start=&quot;292&quot; data-ke-size=&quot;size16&quot;&gt;그래서 나와 비슷한 환경에 있는 주니어 개발자분들은 이런 어려움과 막막함을 어떻게 극복하면서 성장하고 있는지 궁금했다.&lt;/p&gt;
&lt;p data-end=&quot;478&quot; data-start=&quot;360&quot; data-ke-size=&quot;size16&quot;&gt;세션을 들으면서 혼자서 시행착오를 겪으며 성장하는 경험도 충분히 의미가 있다는 걸 다시 한 번 느꼈다.&lt;br /&gt;돌이켜보면 이런 과정을 통해 자연스럽게 프로젝트에 애정이 생기고, 더 발전시키고 싶은 마음도 커지는 것 같다.&lt;/p&gt;
&lt;p data-end=&quot;566&quot; data-start=&quot;480&quot; data-ke-size=&quot;size16&quot;&gt;어찌 보면 내가 실무에서 해온 방식이 전혀 틀린 게 아니었구나, 나처럼 성장해가는 동료도 있구나&amp;mdash;&lt;br /&gt;라는 점에서 혼자만의 라포가 생긴 인상적인 세션이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;[라이트닝 톡] 우리 팀은 Figma MCP + Cursor(AI)와 함께 일합니다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; 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;KakaoTalk_Photo_2025-08-25-22-24-35 011.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb37n6/btsP5rUNKiS/gSEjYqdATD3rzdT3Ig39S0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb37n6/btsP5rUNKiS/gSEjYqdATD3rzdT3Ig39S0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb37n6/btsP5rUNKiS/gSEjYqdATD3rzdT3Ig39S0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb37n6%2FbtsP5rUNKiS%2FgSEjYqdATD3rzdT3Ig39S0%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;4032&quot; height=&quot;3024&quot; data-filename=&quot;KakaoTalk_Photo_2025-08-25-22-24-35 011.jpeg&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Cursor AI를 사용하고 신세계를 경험한 입장에서, 내가 자주 사용하는 서비스인 오늘의 집에서는 이 도구를 어떻게 활용하면서 프론트엔드 팀의 생산성을 높이고 있을지 궁금했다.&lt;/p&gt;
&lt;p data-end=&quot;294&quot; data-start=&quot;194&quot; data-ke-size=&quot;size16&quot;&gt;우선 세션 제목부터가 마크업 단계에서의 피로도를 상당히 줄여줄 수 있을 것 같은 인상을 줬고, 무엇보다 오늘의집의 실제 경험에서 나온 인사이트를 얻을 수 있을 거라는 기대가 컸다.&lt;/p&gt;
&lt;p data-end=&quot;322&quot; data-start=&quot;296&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Serif KR';&quot;&gt;개인적으로 정말 만족스러운, 최고의 세션이었다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;512&quot; data-start=&quot;324&quot; data-ke-size=&quot;size16&quot;&gt;부끄럽게도 Figma MCP에 대해서 세션 당일 처음 알게 되었지만, Cursor AI에 사내 규칙을 명확하게 작성해 rule로 적용하는 방식은 특히 인상 깊었다. 반복되는 작업들을 자동화할 수 있다는 점도 좋았고, 디자인 시스템을 도입했다가 너무 많은 variation을 감당하지 못해 결국 없앴던 회사의 경험과도 많이 겹쳤다... 하핫&lt;/p&gt;
&lt;p data-end=&quot;692&quot; data-start=&quot;514&quot; data-ke-size=&quot;size16&quot;&gt;디자인 토큰이 정의되어 있다면 이를 기반으로 자동 스타일링 룰을 구성하는 것도 충분히 가능하겠다는 생각이 들었다. 최근 내가 진행 중인 프로젝트에서 &lt;a href=&quot;https://codechasseur.tistory.com/113&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;테스트 코드를 도입&lt;/a&gt;하고 있는데, 예외 처리나 테스트 코드 작성에 대한 rule을 직접 정의해서 도입해보면 실제로 생산성을 크게 끌어올릴 수 있을 것 같았다.&lt;/p&gt;
&lt;p data-end=&quot;920&quot; data-start=&quot;694&quot; data-ke-size=&quot;size16&quot;&gt;참고로 시간이 잠깐 남아서 오늘의 집 부스에서 짧게 커리어 상담을 나눴었다. 요즘같은 AI 시대에 오늘의집에서는 이를 어떻게 바라보고 활용하는지 물었을 때 돌아온 답변은 &amp;ldquo;생산성을 위해 필요한 '시점'에 사용한다&amp;rdquo;는 것이었다. 처음엔 그 '시점'이 다소 막연하게 느껴졌지만, 세션을 듣고 나니 그 의미가 명확해졌다. &lt;br /&gt;&lt;br /&gt;업무 중 반복적인 작업에 피로감을 느끼거나 그로 인해 시간이 지연될 때가 바로 AI 도입의 타이밍이라는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;1041&quot; data-start=&quot;922&quot; data-ke-size=&quot;size16&quot;&gt;아이러니하게도 평소 업무 중 그런 피로를 자주 느끼고 있었음에도, 막상 &amp;lsquo;언제&amp;rsquo; AI를 써야 하는지 명확하게 떠오르지 않았던 나 자신을 떠올리게 됐다. 결국은 구체적인 맥락이 있을 때 비로소 실감이 되는 것 같다.&lt;/p&gt;
&lt;p data-end=&quot;1132&quot; data-start=&quot;1043&quot; data-ke-size=&quot;size16&quot;&gt;이번 FEConf에서 가장 많은 인사이트를 얻은 세션이었고, 앞으로 내가 AI와 함께 어떻게 일해 나갈지에 대해 분명한 방향을 잡을 수 있는 계기가 되었다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;1219&quot; data-start=&quot;1134&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;KakaoTalk_Photo_2025-08-25-22-24-33 002.jpeg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5at0O/btsP65iXWlr/dX6LpD0XsjMJRt2vS8xQGk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5at0O/btsP65iXWlr/dX6LpD0XsjMJRt2vS8xQGk/img.jpg&quot; data-alt=&quot;모요 부스의 FE 고민보드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5at0O/btsP65iXWlr/dX6LpD0XsjMJRt2vS8xQGk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5at0O%2FbtsP65iXWlr%2FdX6LpD0XsjMJRt2vS8xQGk%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;3024&quot; height=&quot;4032&quot; data-filename=&quot;KakaoTalk_Photo_2025-08-25-22-24-33 002.jpeg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모요 부스의 FE 고민보드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;1219&quot; data-start=&quot;1134&quot; data-ke-size=&quot;size16&quot;&gt;다양한 사람들의 고민을 듣고 보면서&lt;br /&gt;얼굴도 모르는 사람들이지만, 각자가 가진&amp;nbsp;&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 data-end=&quot;369&quot; data-start=&quot;239&quot; data-ke-size=&quot;size16&quot;&gt;역시 이렇게 여기저기 다니며 인사이트를 얻는 경험은 언제나 즐거운 것 같다.&lt;br /&gt;다음에 기회가 된다면 단순한 참석자가 아니라, 컨퍼런스 스태프로도 한번 참여해보고 싶다.&lt;/p&gt;</description>
      <category>ETC</category>
      <category>fe</category>
      <category>feconf2025</category>
      <category>FEconf2025 후기</category>
      <category>feconf후기</category>
      <category>frontend</category>
      <category>개발자 컨퍼런스</category>
      <category>프론트엔드</category>
      <author>코드사냥꾼</author>
      <guid isPermaLink="true">https://codechasseur.tistory.com/114</guid>
      <comments>https://codechasseur.tistory.com/114#entry114comment</comments>
      <pubDate>Mon, 25 Aug 2025 23:01:03 +0900</pubDate>
    </item>
    <item>
      <title>고민 많은 3년차 프론트엔드 개발자의 테스트 코드 도입 과정 (feat. 더이상 미룰 수 없다.)</title>
      <link>https://codechasseur.tistory.com/113</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpLSRZ/btsPF1t5ZaT/lyTlQpgj2AZRJ8DVY9WWd1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpLSRZ/btsPF1t5ZaT/lyTlQpgj2AZRJ8DVY9WWd1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpLSRZ/btsPF1t5ZaT/lyTlQpgj2AZRJ8DVY9WWd1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpLSRZ%2FbtsPF1t5ZaT%2FlyTlQpgj2AZRJ8DVY9WWd1%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;625&quot; height=&quot;406&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;0. 들어가며&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2년째 멈춰 있던 내 개발 블로그 .... 반성 또 반성&lt;br /&gt;벌써 만 3년 차 개발자가 되었는데(말도 안된다!!!!) 그동안 겪었던 여러 고민과 경험을 기록해 보려고 한다.&lt;br /&gt;미래의 나에게 동기부여가 될 수 있고, 또 누군가에게는 작은 공감이 되길 바라면서!&lt;br /&gt;이번 글에서는 프로젝트를 하면서 테스트 코드를 도입하게 된 배경과, 고민했던 전략들을 솔직하게 풀어내려고 했다.&lt;br /&gt;글이 조금 길고 서술 위주일 수 있지만, 내 이야기를 나누며 함께 생각해 보는 시간이 되면 좋겠다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;1. 도입 배경&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년 초까지 플랫폼 서비스를 개발하면서, 서비스 특성상 안정성에 대한 고민이 깊어졌다. 이 고민은 자연스럽게 테스트 주도 개발(TDD) 방법론에 대한 관심으로 이어졌고, 그 결과 NextStep의 테스트 코드 수업을 수강하게 되었다. 처음엔 Jest를 사용해서 테스트 코드를 작성하는 과제 수행에 집중하느라 본질에 대해 깊이 생각하지 못했지만 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;TDD가 단순한 안정성을 넘어 지속 가능한 코드 품질 유지에 매우 중요한 방법&lt;/b&gt;&lt;/span&gt;임을 깨달았다. 특정 기능 요구사항에 맞는 테스트 케이스를 명확히 작성하고, 단일 책임 원칙에 따라 독립적인 함수를 만들면 컴포넌트에서 쉽게 재사용할 수 있고 이것이 곧 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;좋은 구조적 추상화로 이어진다&lt;/b&gt;&lt;/span&gt;는 생각이 들었다.&lt;br /&gt;그래서 욕심만큼은 당장 테스트 코드를 도입하고 싶었다.&lt;br /&gt;하지만 당시 개발 일정이 매우 촉박했고 여러 우선순위가 겹치다 보니 테스트 코드 도입은 자연스럽게 뒤로 미뤄졌다.&lt;br /&gt;그러다 작년 중순부터 신규 서비스를 담당하면서 상황이 바뀌었다. 이 서비스는 고객 요구사항에 따라 기능과 API 응답 구조가 잦은 변화를 겪었고 변화가 빠른 만큼 공유가 원활하지 않은 부분도 발생해 필드 누락이나 계산 결과 불일치가 반복됐다. 이 과정에서 오는 피로도는 날이 갈수록 증가됐다.&lt;br /&gt;나는 이 정보 격차를 해소하기 위해 내가 할 수 있는 것은 무엇일지 고민했고, 결국 지금이 테스트 코드 도입이 적기라고 확신했다.&lt;br /&gt;그래서 바로 팀장님께 반복되는 위 문제를 위해 도입하려고 한다는 의견을 드렸고 감사하게도 팀장님은 내 의견에 동의해주셔서 도입할 수 있게 됐다.  &lt;br /&gt;또, 팀장님뿐만 아니라 나의 고민과 테스트 코드 도입에 대한 확신을 팀원들과 공유하기 위해 문서를 작성했고, 함께 고민하고 의견을 나누는 문화를 만들고자 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y1ECR/btsPGqArhjT/aKQ8b84dgQojXMs5Kgagr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y1ECR/btsPGqArhjT/aKQ8b84dgQojXMs5Kgagr0/img.png&quot; data-alt=&quot;팀 채널에 공유&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y1ECR/btsPGqArhjT/aKQ8b84dgQojXMs5Kgagr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy1ECR%2FbtsPGqArhjT%2FaKQ8b84dgQojXMs5Kgagr0%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;1558&quot; height=&quot;826&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;팀 채널에 공유&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 테스트 코드를 향한 나의 전략&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 테스트 코드를 도입하게 되었지만, 이제 또 어떻게 작성할 것인지가 또 고민이었다.&lt;br /&gt;(저처럼 고민이 많은 분들 계신가요?  )&lt;br /&gt;&amp;lsquo;유의미한 테스트 코드란 무엇일까?&amp;rsquo;&lt;br /&gt;&amp;lsquo;빠르게 진행되는 일정 속에서 테스트 코드 작성이 병목이 되진 않을까?&amp;rsquo;&lt;br /&gt;하지만 반복되는 문제점이 명확했기에 더 이상 고민만 할 수 없었다.&lt;br /&gt;&amp;lsquo;일단 해보자!&amp;rsquo;라는 마음으로 빠르게 전략을 세웠다.&lt;br /&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 data-end=&quot;540&quot; data-start=&quot;424&quot;&gt;&lt;b&gt;유의미한 테스트 코드 작성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;540&quot; data-start=&quot;424&quot;&gt;지라 티켓을 통해 반복적으로 발생한 이슈에 대응할 수 있도록 한다.&lt;/li&gt;
&lt;li data-end=&quot;540&quot; data-start=&quot;424&quot;&gt;백엔드 응답 구조 변경으로 인한 필드 누락 가능성이 높은 부분을 집중적으로 검증한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;686&quot; data-start=&quot;542&quot;&gt;&lt;b&gt;점진적 도입과 확대&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;686&quot; data-start=&quot;542&quot;&gt;모든 기능을 한꺼번에 적용하기보다 변동 가능성과 이슈가 큰 핵심 기능부터 우선 적용한다.&lt;/li&gt;
&lt;li data-end=&quot;686&quot; data-start=&quot;542&quot;&gt;접속 요청 조회, 정책, 보고서 등 중요 기능을 중심으로 유틸 함수 단위 테스트부터 시작해 점차 범위를 넓혀나간다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;801&quot; data-start=&quot;688&quot;&gt;&lt;b&gt;빠른 실행과 피드백&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;801&quot; data-start=&quot;688&quot;&gt;고민보다 실행에 집중하고, 빠르게 테스트 코드를 작성하며 팀원들의 피드백을 적극적으로 수용한다.&lt;/li&gt;
&lt;li data-end=&quot;801&quot; data-start=&quot;688&quot;&gt;다양한 테스트 방법론을 적용하는 것보다 시행착오를 겪으면서 개선해 나가고 성장하는 자세를 갖는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;913&quot; data-start=&quot;803&quot;&gt;&lt;b&gt;도구 선택과 환경 최적화&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;913&quot; data-start=&quot;803&quot;&gt;Vite 기반 프로젝트 특성에 맞게 Vitest를 선택해 빠르게 설정한다.&lt;/li&gt;
&lt;li data-end=&quot;913&quot; data-start=&quot;803&quot;&gt;API 모킹을 위해 MSW를 도입해 안정적인 테스트 환경을 구축한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 프로젝트 적용 예시&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보고서와 통계 페이지의 계산 결과가 달라지는 문제를 해결하기 위해 동일한 계산 결과가 나오는지 검증하는 테스트 코드를 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 말해서 두 함수가 같은 값을 반환하는지를 비교하는 테스트 예시이다.&lt;/p&gt;
&lt;pre id=&quot;code_1754134304082&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// example) Statistics.test.ts

describe('통계 페이지 통합테스트', () =&amp;gt; {
  const TOTAL_VERIFICATION_REQUESTS = 150;
  const PASSED_REQUESTS = 40;

  test('통계 페이지와 보고서의 탐지율 데이터는 동일해야함', () =&amp;gt; {
    const statisticsData: Pick&amp;lt;StatisticsItem, 'totalCount' | 'passedCount'&amp;gt; = {
      totalCount: TOTAL_VERIFICATION_REQUESTS,
      passedCount: PASSED_REQUESTS,
    };

    const reportData: Pick&amp;lt;ReportItem, 'totalCount' | 'passedCount'&amp;gt; = {
      totalCount: TOTAL_VERIFICATION_REQUESTS,
      passedCount: PASSED_REQUESTS,
    };

    const statisticsRatio = getBotDetectRatio(
      statisticsData.totalCount - statisticsData.passedCount,
      statisticsData.totalCount,
    );

    const reportRatio = calculatePercentages(
      reportData.totalCount - statReportData.passedCount,
      reportData.totalCount,
    );

    expect(statisticsData).toBe(reportRatio);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 테스트가 조기에 이슈를 감지하는 데 유의미하다고 생각하지만 분명 아래와 같은 한계도 존재한다고 생각한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;269&quot; data-start=&quot;157&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;getBotDetectionRatio와 calculatePercentages 함수는 이름만 다르고 내부 로직은 동일해서 유틸 함수 계산 값 일치 검증 자체는 크게 의미가 없을 수 있다. (이 부분은 동일한 유틸 함수를 사용하도록 변경이 필요하다.)&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;325&quot; data-start=&quot;270&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;실제 API 응답 구조를 반영하지 않아, 데이터 변화에 따른 영향을 완벽히 검증하지 못한다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;398&quot; data-start=&quot;326&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;타입에 필드가 추가돼도, 해당 필드가 실제 코드에서 참조되지 않으면 테스트 실패가 발생하지 않아 문제를 놓칠 위험이 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 2, 3번째 문제는 MSW를 도입해 실제 API 응답을 모킹하면 더 나아지지 않을까 생각이 들어서 개선해 나가보려고 한다!!&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. MSW 도입 후 테스트 코드 개선 (25.08.04)&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1754310234119&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { setupServer } from 'msw/node';

import { statisticsHandlers } from '~/features/statistics/mocks/handlers';
import { reportHandlers } from '~/features/report/mocks/handlers';

export const mockServer = setupServer(...statisticsHandlers, ...reportHandlers);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 node 환경의 setupServer 함수를 사용했다. 그 이유는 간단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM API가 필요하지 않았고 네트워크 요청 흐름을 모킹하여 비즈니스 로직이 올바르게 동작하는지 통합적으로 검증하는 데 초점을 두었기 때문이다. node 환경에서 브라우저 없이 서버 요청을 가로챌 수 있어서 내가 작성하려는 유닛 테스트와 통합 테스트에 적합하다고 생각했다.&lt;/p&gt;
&lt;pre id=&quot;code_1754310729545&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { http, HttpResponse } from 'msw';

// 통계 데이터 핸들러 예시
export const statisticsHandlers = [
  http.get('*/v1/stat/:id', () =&amp;gt; {
    return HttpResponse.json({ data: statiticsResponse });
  }),
];

// 보고서 데이터 핸들러 예시
export const reportHandlers = [
  http.get('*/v1/report/:id', () =&amp;gt; {
    return HttpResponse.json({ data: reportResponse });
  }),
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 핸들러를 설정한 후 미리 작성한 mock data를 넣어줬다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 MSW가 모킹한 데이터를 가지고 테스트 코드를 작성했다. 계산식에 필요한 데이터 필드가 있는지 검증하고, 그 값이 있을 때 계산한 값이 동일한지 보여주는 것으로 변경했다.&lt;/p&gt;
&lt;pre id=&quot;code_1754311344535&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;describe('봇 통계 통합 테스트', () =&amp;gt; {
  test('서로 다른 API에서 받아온 데이터로 계산한 탐지율이 일치한다', async () =&amp;gt; {
    const statData = await fetchStat('123');
    const reportData = await fetchReport('123');

    expect(statData).toHaveProperty('secondaryVerificationRequests');
    expect(statData).toHaveProperty('passedRequests');
    
    expect(reportData).toHaveProperty('secondaryVerificationRequests');
    expect(reportData).toHaveProperty('passedRequests');

    const statRatio = getBotDetectRatio(
      statData.secondaryVerificationRequests - statData.passedRequests,
      statData.secondaryVerificationRequests,
    );

    const reportRatio = calculatePercentages(
      reportData.secondaryVerificationRequests - reportData.passedRequests,
      reportData.secondaryVerificationRequests,
    );

    expect(statRatio).toBe(reportRatio);
  });
});&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;API 응답 구조와 동일한 목데이터를 MSW로 모킹하여 테스트에 활용했다는 점이 이전 코드와 가장 큰 차이점이다.&lt;br /&gt;이를 통해 데이터 필드가 변경되었을 때도 빠르게 감지할 수 있고, 실제 API 응답과 유사한 환경에서 받아온 값을 기반으로 계산 결과를 검증할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;즉, 하드코딩된 데이터가 아닌 서버에서 오는 데이터 흐름을 모방하여 더 신뢰성 높은 통합 테스트가 가능해졌다!  &lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드를 도입한 목적은 기능별로 반복적으로 발생하는 이슈와 유관 부서와의 정보 누락 격차를 줄이기 위함이었다.&lt;br /&gt;아직 도입 초기 단계라 주요 기능 일부에만 적용된 상태여서 큰 효과를 체감하기엔 이르지만, QA 과정에서 발생할 수 있는 문제 의존도를 낮추고 서비스 안정성을 확보하는 데 큰 도움이 될 것이라고 기대하고있다.&lt;br /&gt;테스트 작성 과정에서 테스트 시나리오 작성 방식에 대해 고민하는 계기가 됐고, 프로젝트 개선의 첫걸음을 내딛은 것 같아 의미가 컸다.&lt;br /&gt;앞으로도 많은 시행착오가 있겠지만, &lt;b&gt;완벽해야 한다는 욕심을 버리고&lt;/b&gt; 꾸준히 테스트 코드를 작성하면서 더 빠르고 안정적인 테스트 환경을 구축하는 것을 목표로 삼았다.&lt;/p&gt;</description>
      <category>개발회고 </category>
      <category>frontend</category>
      <category>Vitest</category>
      <category>개발자</category>
      <category>테스트코드</category>
      <category>프론트엔드</category>
      <category>회고</category>
      <author>코드사냥꾼</author>
      <guid isPermaLink="true">https://codechasseur.tistory.com/113</guid>
      <comments>https://codechasseur.tistory.com/113#entry113comment</comments>
      <pubDate>Fri, 1 Aug 2025 17:13:35 +0900</pubDate>
    </item>
    <item>
      <title>2022년, 지난 날의 회고</title>
      <link>https://codechasseur.tistory.com/112</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&quot;너무 바빴고, 부족함에 많이 울었고 힘들었다.&quot;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;내가 경험했던 22년은 위의 한 문장으로 정리할 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;개발자로서의 인생 첫 회고를 부정적인 단어로 시작하는 것이 아쉽지만&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;언젠가 다시 꺼내보았을 때 미래의 나에게도 큰 동기부여가 될 것 같아 솔직하게 써보려 한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;u&gt;1. 2022년 5월 말, 첫 취직  &lt;/u&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;감사하게도 좋은 기회를 통해 B2B 스타트업에서 프론트엔드 개발자로 일하게 되었다. 당시 기존 제품을 SaaS 플랫폼화 하여 글로벌 진출을 목표하고 있어서 맡을 업무에 대한 기대감이 컸고, 실패하더라도 도전하는 것에 큰 의미를 두는 사내 가치관이 나와 잘 맞아서 행복했다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;당시 팀 내 프론트엔드 개발자는 나 포함 3명이었고, 신입 개발자로 이루어져 있었다. 그래서일까?&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;만나게 된 동료분들도 엄청난 열의가 있으셨고, 무조건적인 'Yes'가 아닌 같이 '왜?'를 고민할 수 있어서 입사 후 2주 동안 굉장히 재밌게 다녔었다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;u&gt;2. 2022년 6월 초, 디자인 시스템 개발&lt;br /&gt;&lt;/u&gt;팀 개편을 통해 사내 모든 프론트엔드 개발자 분들이 모여 6명의 프론트엔드 개발 팀이 꾸려졌고,&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;디자이너 분들과 함께 협업하며 사내 서비스 UI 통일성을 위한 디자인 시스템 개발 업무를 맡게 되었다.&lt;br /&gt;&lt;br /&gt;개인적으로 사용자 경험 향상을 위한 UI/UX 전략에 관심과 큰 재미를 느끼고 있는 상태였고, &lt;br /&gt;무엇보다&amp;nbsp;첫 업무이기에 그 어느 때보다 들떴던 날이었다.&lt;br /&gt;&lt;br /&gt;내가 디자인 시스템 개발 과정에서 가장 중요하게 생각했던 것은 컴포넌트의 유연함과 편리함이었다. &lt;br /&gt;디자인 시스템을 실제 사용하며 개발하는 것은 나의 동료이고, 어떤 상황에서든 쉽고 빠르게 적용시켜야 하기 때문이다.&lt;br /&gt;&lt;br /&gt;그래서 &lt;span&gt;여러 디자인 시스템 레퍼런스를 참고하면서&amp;nbsp;&lt;/span&gt;자주 쓰이는 속성들이 무엇인지 파악했고, 최대한 사용자의 작업 공수를 줄이기 위해 비지니스 로직을 컴포넌트 안에서 구현했다. &lt;br /&gt;&lt;br /&gt;그럼에도 부족한 것 투성이었지만 같은 팀 동료 분께서 사용성이 너무 좋아서 편리했다고 말씀해 주셨던 적이 있는데 내 노력을 알아주신 것 같아 감사했고 뿌듯했었다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;그렇게 7월에 진행했던 프로젝트와 병행하다&lt;span&gt;&lt;span&gt;&amp;nbsp;사내 프로세스에 따라&amp;nbsp;&lt;/span&gt;&lt;/span&gt;해당 업무는 잠정 보류가 되었다.... &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;u&gt;3. 2022년 7월 초, SaaS 플랫폼 개발&lt;br /&gt;&lt;/u&gt;기존 서비스를 SaaS 플랫폼으로 새로 개발하는 프로젝트에 투입됐다. SaaS 플랫폼 개발로 AWS를 다뤄볼 수 있는 기회였기에 너무 재밌을 것 같았고 빨리 개발하고 싶었다.&lt;br /&gt;&lt;br /&gt;그런데 첫 MVP 개발 일정 산정 시 우리에게 주어진 시간은 3주 남짓한 시간이었다. 지금 생각해 보면 큰 무리가 되는 일정은 아니지만 &lt;span&gt;시니어 프론트엔드 개발자가 단 한 명도 없는 환경에서 &lt;span&gt;실무 개발 경험이 전무한&amp;nbsp;&lt;/span&gt;신입 3명이 주력 서비스를 개발해 내야 한다는 부담감이 컸었다. &lt;/span&gt;또, 기획이 하루 아침에 변경되어 디자인을 갈아엎는 일도 많았으며 백엔드 개발자와 소통하는 프로세스가 제대로 갖춰지지 않은 상태였다.&lt;br /&gt;&lt;br /&gt;이때, 막막함이 밀려와 많이 힘들었고 회의감이 크게 왔었던 것 같다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;짧은 시간 내 개발 완료를 해야 하다 보니 내가 짠 코드가 어떤 사이드 이펙트를 불러올지 예상하지 못한 채 구현했고, 당연히 코드 퀄리티도 좋지 않았다. 개발하는 것이 너무 재밌고 어떻게 작성해야 재사용성이 높은 코드로 만들 수 있을까 고민하는 시간이 즐거웠는데 그런 것은 당장 고민하지 말라는 목소리가 들려와서 힘들었다.&lt;br /&gt;&lt;br /&gt;그저 코딩하는 기계가 된 느낌..?  &lt;br /&gt;그 당시에는 우물 안 개구리가 되어서 성장하는 느낌도 들지 않았고, 다양한 의구심들이 나를 괴롭혔다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그래도 동료들과 틈틈히 고민을 나누고 바쁜 와중에도 서로 도와가며 밤낮으로 열심히 한 결과 기간 안에 목표한 분량까지 완료해 낼 수 있었다. 지금 생각해 보면 이때 많이 성장한 것 같고 단단한 사람이 된 것 같다.&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;마무리&lt;/u&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;22년 5월부터 12월까지 시간이 정말 빠르게 지나간 것 같다.&lt;br /&gt;스타트업 업계 특성 상 의사 결정이 매우 빠르게 진행됐고, 필요하다면 야근도 n일 ~ n주씩 했다.&lt;br /&gt;&lt;br /&gt;현재, 새로운 프로젝트를 혼자 개발 중이어서 산더미처럼 쌓인 일에 여전히 바쁜 하루를 보내고 있다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;사실 어떻게 보면 쉽지 않은 환경이지만 그럼에도 해냈고, 회고를 쓰는 2023년 2월에도 하나씩 해내며 보람을 느끼고 있다.&lt;br /&gt;비록 쉽지 않다 하더라도 여태까지 잘 해냈던 것처럼 앞으로도 잘할 것이라고 믿으며 첫 회고를 마친다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>개발회고 </category>
      <category>2022년</category>
      <category>개발자회고</category>
      <category>프론트엔드</category>
      <category>프론트엔드개발자</category>
      <category>회고</category>
      <author>코드사냥꾼</author>
      <guid isPermaLink="true">https://codechasseur.tistory.com/112</guid>
      <comments>https://codechasseur.tistory.com/112#entry112comment</comments>
      <pubDate>Sun, 12 Feb 2023 21:52:53 +0900</pubDate>
    </item>
    <item>
      <title>[CSS] pointer-events를 사용해서 마우스 이벤트 방지 및 제어하기</title>
      <link>https://codechasseur.tistory.com/111</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;pointer-events ?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;해당 속성을 통해 마우스 이벤트(hover, click, drag 등)에 어떻게 동작할지 지정할 수 있다. 다만, 대부분의 속성 값은 SVG 전용이기 때문에 기본 html 태그에는 &lt;b&gt;none&lt;/b&gt; 또는 &lt;b&gt;auto&lt;/b&gt; 값만 설정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;[속성 값]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;none&amp;nbsp;: 요소가 포인터 이벤트의 대상이 되지 않는다. 그러나 해당 요소의 자손이 다른 pointer-events 값을 지정한 경우, 그 자손은 대상이 될 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;auto&amp;nbsp;: 요소가 pointer-events 속성을 지정하지 않은 것처럼 행동한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;inherit&amp;nbsp;: 부모 요소로부터 pointer-events 값을 상속받는다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;(더 많은 속성 값은 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events&lt;/a&gt; 에서 확인 가능)&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;마우스 이벤트를 막는다? 그럼 보편적인 event.preventDefault() 사용하면 되지 않을까?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;요소의 기본 동작을 막는 상황(submit 타입의 button 또는 링크가 걸린 a 태그 등)에서는 당연히 대체해서 사용할 수 있다. 하지만, 필자의 경험 상 기본 태그들에 대해 클릭 이벤트를 막아야 할 경우가 적지 않게 있었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;예를 들면, 버튼으로 만든 div 태그를 어떤 조건에 따라 unclickable 상태로 만들어야 하는 경우이다. div 태그는 button 또는 a 태그와 같이 클릭 이벤트에 대한 정의가 되어 있지 않다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서, div 태그를 unclickable 하게 만들려면 해당 요소에 &lt;b&gt;pointer-events: none&lt;/b&gt; 값을 부여하는 것이다. (개인적으로 매우 유용하게 사용하고 있는 css 속성 )&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;주의 사항&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;부모 요소에 해당 속성 적용 시 하위 요소들 모두 동일 적용된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;해당 속성이 지정되었더라도 마우스 이벤트의 이벤트 리스너가 호출되지 않을 거란 보장은 없다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;부모 요소에 &lt;b&gt;pointer-events: none&amp;nbsp;&lt;/b&gt;속성이 부여되어 있고, 하위 요소가&amp;nbsp;&lt;b&gt;pointer-events: auto&amp;nbsp;&lt;/b&gt;속성을 가졌다면, 해당 자식 요소는 이벤트 버블링 또는 캡쳐링 과정에서 부모 요소의 이벤트 리스너가 호출될 수 있다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;브라우저 호환성&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-12-18 18.16.46.png&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7qlPr/btrTQ1sayMW/gwcG7gw8IQPS4sjqkMYxtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7qlPr/btrTQ1sayMW/gwcG7gw8IQPS4sjqkMYxtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7qlPr/btrTQ1sayMW/gwcG7gw8IQPS4sjqkMYxtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7qlPr%2FbtrTQ1sayMW%2FgwcG7gw8IQPS4sjqkMYxtK%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;1628&quot; height=&quot;712&quot; data-filename=&quot;스크린샷 2022-12-18 18.16.46.png&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>CSS</category>
      <category>frontend</category>
      <category>pointer-events</category>
      <category>Til</category>
      <category>마우스이벤트막기</category>
      <category>포인터이벤트</category>
      <category>프론트엔드</category>
      <author>코드사냥꾼</author>
      <guid isPermaLink="true">https://codechasseur.tistory.com/111</guid>
      <comments>https://codechasseur.tistory.com/111#entry111comment</comments>
      <pubDate>Sun, 18 Dec 2022 18:21:23 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] x만큼 간격이 있는 n개의 숫자 - JS</title>
      <link>https://codechasseur.tistory.com/110</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/12954?language=javascript&quot;&gt;https://programmers.co.kr/learn/courses/30/lessons/12954?language=javascript&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1633002807635&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;코딩테스트 연습 - x만큼 간격이 있는 n개의 숫자&quot; data-og-description=&quot;함수 solution은 정수 x와 자연수 n을 입력 받아, x부터 시작해 x씩 증가하는 숫자를 n개 지니는 리스트를 리턴해야 합니다. 다음 제한 조건을 보고, 조건을 만족하는 함수, solution을 완성해주세요. &quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/12954?language=javascript&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/12954?language=javascript&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bPrb48/hyLNiNZldg/1UBMUNSXjGcvsSKrnyWcg1/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/bgkDtW/hyLOat0hNu/tGNvG9k6Re9FpCrFvloUpK/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/12954?language=javascript&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/12954?language=javascript&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bPrb48/hyLNiNZldg/1UBMUNSXjGcvsSKrnyWcg1/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/bgkDtW/hyLOat0hNu/tGNvG9k6Re9FpCrFvloUpK/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626');&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;코딩테스트 연습 - x만큼 간격이 있는 n개의 숫자&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;함수 solution은 정수 x와 자연수 n을 입력 받아, x부터 시작해 x씩 증가하는 숫자를 n개 지니는 리스트를 리턴해야 합니다. 다음 제한 조건을 보고, 조건을 만족하는 함수, solution을 완성해주세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.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;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  해결&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1633002835194&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(x, n) {
    let answer = [];

    for(let i = 1; i &amp;lt;= n; i++) {
        answer.push(x*i);
    }
    return answer;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <author>코드사냥꾼</author>
      <guid isPermaLink="true">https://codechasseur.tistory.com/110</guid>
      <comments>https://codechasseur.tistory.com/110#entry110comment</comments>
      <pubDate>Thu, 30 Sep 2021 20:53:58 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 직사각형 별찍기 - JS</title>
      <link>https://codechasseur.tistory.com/109</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/12969&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://programmers.co.kr/learn/courses/30/lessons/12969&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1633002174044&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코딩테스트 연습 - 직사각형 별찍기&quot; data-og-description=&quot;이 문제에는 표준 입력으로 두 개의 정수 n과 m이 주어집니다. 별(*) 문자를 이용해 가로의 길이가 n, 세로의 길이가 m인 직사각형 형태를 출력해보세요. 제한 조건 n과 m은 각각 1000 이하인 자연수&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/12969&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/12969&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iKPdo/hyLNkYZPBy/xN3pYHewMu0V8BneyQlxek/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/Q9kfk/hyLNlXTyk0/xgzikQGuukdr7fhLwnMGv0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/12969&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/12969&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iKPdo/hyLNkYZPBy/xN3pYHewMu0V8BneyQlxek/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/Q9kfk/hyLNlXTyk0/xgzikQGuukdr7fhLwnMGv0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626');&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;이 문제에는 표준 입력으로 두 개의 정수 n과 m이 주어집니다. 별(*) 문자를 이용해 가로의 길이가 n, 세로의 길이가 m인 직사각형 형태를 출력해보세요. 제한 조건 n과 m은 각각 1000 이하인 자연수&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.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;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  해결&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1633002142787&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;process.stdin.setEncoding('utf8');
process.stdin.on('data', data =&amp;gt; {
    const n = data.split(&quot; &quot;);
    const a = Number(n[0]), b = Number(n[1]);

    let result = &quot;&quot;;
    
    for(let i = 0; i &amp;lt; b; i++) {
        for(let j = 0; j &amp;lt; a; j++) {
           result += &quot;*&quot;;
        }
        result += &quot;\n&quot;;
    }
    console.log(result);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩테스트</category>
      <author>코드사냥꾼</author>
      <guid isPermaLink="true">https://codechasseur.tistory.com/109</guid>
      <comments>https://codechasseur.tistory.com/109#entry109comment</comments>
      <pubDate>Thu, 30 Sep 2021 20:43:35 +0900</pubDate>
    </item>
    <item>
      <title>[Github] 레파지토리 변경하기</title>
      <link>https://codechasseur.tistory.com/108</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하나의 레파지토리에 성격이 다른 폴더들이 존재할 때 정리를 하고 싶어 진다. 다른 레파지토리로 변경하고 싶을 때 아래와 같이 하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1630904825625&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ mv 옮길 폴더명 옮겨질 디렉토리&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때, 디렉토리는 &lt;b&gt;pwd를&lt;/b&gt; 통해 옮겨질 경로를 확인하면 된다. 이후, git으로 관리할 폴더 안에서 아래와 같이 선언한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1630904929760&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ git init&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px; color: #000000;&quot;&gt;&lt;b&gt;ls -al&lt;/b&gt;&amp;nbsp;명령을 통해 .git 이 폴더에 잘 생겼는지 확인한다. 이때, git init 명령만으로는 관리를 위한 원격 저장소가 생기지 않는다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px; color: #000000;&quot;&gt;그래서, &lt;b&gt;'git remote add origin 새로운 레파지토리 주소'&lt;/b&gt;를 통해 해당 폴더를 관리할 레파지토리 주소를 원격 저장소로 지정해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1630905339425&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 현재 디렉토리의 원격 저장소 확인
$ git remote -v

// 원격 저장소 지정
$ git remote add origin 새로운 레파지토리 주소&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 작업 후 다시 &lt;b&gt;git remote -v&lt;/b&gt; 명령을 통해 확인하면 잘 지정된 것을 볼 수 있다. 이후에는 git add/commit 다음 똑같이 push 하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  이때, 커밋 메시지는 메시지 컨벤션에 따라 &lt;b&gt;rename을&lt;/b&gt; 사용해야 한다!&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ETC</category>
      <category>github</category>
      <category>Repository</category>
      <category>깃허브</category>
      <category>레파지토리변경</category>
      <author>코드사냥꾼</author>
      <guid isPermaLink="true">https://codechasseur.tistory.com/108</guid>
      <comments>https://codechasseur.tistory.com/108#entry108comment</comments>
      <pubDate>Mon, 6 Sep 2021 14:24:30 +0900</pubDate>
    </item>
    <item>
      <title>[ETC] 웹 페이지의 HTML 구조 한 눈에 보기</title>
      <link>https://codechasseur.tistory.com/107</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;클론 코딩을 하거나 주어진 과제를 통해 HTML 구조를 잡을 때 쉬우면서도 어렵게 느껴진다. 그 이유는 시맨틱 태그를 적절하게 사용해야하며 어떻게 구조를 잡는 것이 더 효율적인지 고민에 빠지기 때문이다. 이럴 때 나는 잘 만들어진 홈페이지의 구조를 보는데 솔직히 코드만 보고는 명확하게 파악하기 힘들다. 이를 해결하기 위한 방법을 찾아보다가 아주 좋은 팁을 발견하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 크롬 개발자 도구 console창에 들어가서 다음과 같이 DOM 요소를 조작하는 것!!!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;사용 방법&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1630388903669&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;document.querySelector('*').style.boxSizing = 'border-box';
document.querySelectorAll('div').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('span').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('ul').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('li').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('dd').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('dl').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('dd').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('section').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('h1').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('a').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('img').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('form').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('button').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('header').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('footer').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('input').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&quot;);
document.querySelectorAll('p').forEach(e =&amp;gt; e.style.border = &quot;1px solid dodgerblue&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;조금 더 리팩토링 된 코드&lt;/p&gt;
&lt;pre id=&quot;code_1630388934392&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;['div', 'span', 'ul', 'li', 'dd', 'dl', 'section', 'h1', 'a', 'img', 'form', 'button', 'header', 'footer', 'input', 'p'].forEach(e =&amp;gt; {
    document.querySelectorAll(e).forEach(element =&amp;gt; {
        element.style.outline = &quot;1px solid dodgerblue&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;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@oneook/%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-HTML-%EA%B5%AC%EC%A1%B0-%ED%95%9C%EB%88%88%EC%97%90-%EB%B3%B4%EB%8A%94-%EA%BC%BC%EC%88%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@oneook/%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-HTML-%EA%B5%AC%EC%A1%B0-%ED%95%9C%EB%88%88%EC%97%90-%EB%B3%B4%EB%8A%94-%EA%BC%BC%EC%88%98&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1630388948622&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;웹페이지 HTML 구조 한눈에 보는 꼼수&quot; data-og-description=&quot;HTML은 쉬우면서도 어렵다. 맥락에 맞게 군더더기 없이 시맨틱 태그를 잘 활용해서 작성해야 한다. HTML 작성을 잘 한다는 것은 마치 탄탄한 글을 쓰는 것과 같다.마음에 드는 페이지가 있고, 레이&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@oneook/%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-HTML-%EA%B5%AC%EC%A1%B0-%ED%95%9C%EB%88%88%EC%97%90-%EB%B3%B4%EB%8A%94-%EA%BC%BC%EC%88%98&quot; data-og-url=&quot;https://velog.io/@oneook/웹페이지-HTML-구조-한눈에-보는-꼼수&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bgYGRT/hyLq59klzF/OkyAkL96UtpQpFbifDXtd1/img.jpg?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/TGnhU/hyLqXi93dY/rLeKBMtN5NnQvLfg634IA1/img.jpg?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402&quot;&gt;&lt;a href=&quot;https://velog.io/@oneook/%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-HTML-%EA%B5%AC%EC%A1%B0-%ED%95%9C%EB%88%88%EC%97%90-%EB%B3%B4%EB%8A%94-%EA%BC%BC%EC%88%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@oneook/%EC%9B%B9%ED%8E%98%EC%9D%B4%EC%A7%80-HTML-%EA%B5%AC%EC%A1%B0-%ED%95%9C%EB%88%88%EC%97%90-%EB%B3%B4%EB%8A%94-%EA%BC%BC%EC%88%98&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bgYGRT/hyLq59klzF/OkyAkL96UtpQpFbifDXtd1/img.jpg?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402,https://scrap.kakaocdn.net/dn/TGnhU/hyLqXi93dY/rLeKBMtN5NnQvLfg634IA1/img.jpg?width=768&amp;amp;height=402&amp;amp;face=0_0_768_402');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;웹페이지 HTML 구조 한눈에 보는 꼼수&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;HTML은 쉬우면서도 어렵다. 맥락에 맞게 군더더기 없이 시맨틱 태그를 잘 활용해서 작성해야 한다. HTML 작성을 잘 한다는 것은 마치 탄탄한 글을 쓰는 것과 같다.마음에 드는 페이지가 있고, 레이&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>ETC</category>
      <author>코드사냥꾼</author>
      <guid isPermaLink="true">https://codechasseur.tistory.com/107</guid>
      <comments>https://codechasseur.tistory.com/107#entry107comment</comments>
      <pubDate>Tue, 31 Aug 2021 14:51:31 +0900</pubDate>
    </item>
  </channel>
</rss>