<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>하얀하얀IT</title>
    <link>https://whitefrost-developer.tistory.com/</link>
    <description>개발공부리뷰블로그</description>
    <language>ko</language>
    <pubDate>Wed, 17 Jun 2026 06:55:58 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>WHITE_FROST</managingEditor>
    <image>
      <title>하얀하얀IT</title>
      <url>https://tistory1.daumcdn.net/tistory/2838812/attach/a6bd0969a5734e0ca42468d41ff37427</url>
      <link>https://whitefrost-developer.tistory.com</link>
    </image>
    <item>
      <title>오라클 클라우드 Always Free 정책 변경 - ARM 인스턴스 4 OCPU/24GB &amp;rarr; 2 OCPU/12GB로 축소하기</title>
      <link>https://whitefrost-developer.tistory.com/148</link>
      <description>&lt;p&gt;Oracle Cloud의 ARM 인스턴스를 활용하고 있다. 그런데 최근 Oracle Cloud의 &lt;strong&gt;Always Free 정책에 변경 사항&lt;/strong&gt;이 생겼다는 알림을 받았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-14 오후 2.53.31.png&quot; data-origin-width=&quot;2062&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLhhnH/dJMcaiKotHv/VGnbDuVen98xzreJzskIU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLhhnH/dJMcaiKotHv/VGnbDuVen98xzreJzskIU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLhhnH/dJMcaiKotHv/VGnbDuVen98xzreJzskIU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLhhnH%2FdJMcaiKotHv%2FVGnbDuVen98xzreJzskIU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;276&quot; data-filename=&quot;스크린샷 2026-06-14 오후 2.53.31.png&quot; data-origin-width=&quot;2062&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;핵심은 기존 ARM(A1.Flex) 인스턴스의 Always Free 한도가 &lt;strong&gt;4 OCPU / 24GB → 2 OCPU / 12GB&lt;/strong&gt;로 줄어들었다.&lt;/p&gt;
&lt;p&gt;이 한도를 초과해서 계속 사용하면 &lt;strong&gt;표준 요금이 부과되거나, 사용이 강제로 중지&lt;/strong&gt;될 수 있다고 한다. 그래서 기존에 4 OCPU / 24GB로 운영하던 인스턴스를 새로운 한도에 맞춰 줄이는 작업을 진행했다. 이 글에서는 변경 사항과 대응 방법을 정리한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. 무엇이 바뀌었나&lt;/h2&gt;
&lt;p&gt;오라클 공식 문서(Always Free Resources) 기준으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OCI Ampere A1 Compute (ARM)&lt;/strong&gt; : VM.Standard.A1.Flex 셰이프 사용 시, 매월 &lt;strong&gt;1,500 OCPU시간 + 9,000 GB시간&lt;/strong&gt;까지 무료&lt;/li&gt;
&lt;li&gt;Always Free 테넌시 기준으로 환산하면 &lt;strong&gt;2 OCPU + 12GB 메모리&lt;/strong&gt;에 해당&lt;/li&gt;
&lt;li&gt;A1.Flex는 유동 셰이프(flexible shape)이기 때문에, 이 자원을 &lt;strong&gt;단일 인스턴스에 모두 할당&lt;/strong&gt;하거나 &lt;strong&gt;1 OCPU + 6GB씩 인스턴스 2개로 나눠서&lt;/strong&gt; 사용할 수도 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;즉, 기존에 익숙했던 &amp;quot;4코어 24GB&amp;quot; 구성은 더 이상 Always Free 범위가 아니고, &lt;strong&gt;2코어 12GB&lt;/strong&gt;가 새로운 기준선이 된 것이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. 한도 초과 시 어떻게 되나&lt;/h2&gt;
&lt;p&gt;공지에 따르면 변경된 기준을 초과한 사용량에 대해서는&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;표준 요금이 부과되거나&lt;/li&gt;
&lt;li&gt;사용이 강제로 중지될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이라고 안내되어 있다. 즉, 그동안 4 OCPU / 24GB로 잘 쓰고 있던 인스턴스를 그대로 두면 어느 시점부터 과금 대상이 되거나 강제 조치를 당할 수 있다는 뜻이다. 무료로 계속 쓰고 싶다면 새 기준에 맞춰 직접 사양을 줄여야 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. 인스턴스 사양 줄이는 방법&lt;/h2&gt;
&lt;p&gt;OCI 콘솔에서 직접 수정할 수 있다. 절차는 다음과 같다.&lt;/p&gt;
&lt;p&gt;1. OCI 콘솔 → &lt;strong&gt;Compute → Instances&lt;/strong&gt;로 이동해 대상 인스턴스로 들어간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-14 오후 2.55.15.png&quot; data-origin-width=&quot;1650&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7hWB0/dJMb997Ntrw/u8vPJ1liABvOhBs6GKt1q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7hWB0/dJMb997Ntrw/u8vPJ1liABvOhBs6GKt1q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7hWB0/dJMb997Ntrw/u8vPJ1liABvOhBs6GKt1q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7hWB0%2FdJMb997Ntrw%2Fu8vPJ1liABvOhBs6GKt1q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;267&quot; data-filename=&quot;스크린샷 2026-06-14 오후 2.55.15.png&quot; data-origin-width=&quot;1650&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;2. 인스턴스 상세 페이지에서 Action(추가 작업) → Edit을 클릭한다.&lt;/p&gt;
&lt;p&gt;3. Edit Instance 화면에서 &lt;strong&gt;Ampere Shape Configuration&lt;/strong&gt; 항목으로 이동한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-14 오후 2.56.38.png&quot; data-origin-width=&quot;2598&quot; data-origin-height=&quot;1566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7pcsi/dJMcafUwhLm/aeiSQfTKHZK1HcL4PVDK10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7pcsi/dJMcafUwhLm/aeiSQfTKHZK1HcL4PVDK10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7pcsi/dJMcafUwhLm/aeiSQfTKHZK1HcL4PVDK10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7pcsi%2FdJMcafUwhLm%2FaeiSQfTKHZK1HcL4PVDK10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;482&quot; data-filename=&quot;스크린샷 2026-06-14 오후 2.56.38.png&quot; data-origin-width=&quot;2598&quot; data-origin-height=&quot;1566&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;strong&gt;Number of OCPUs&lt;/strong&gt;를 &lt;code&gt;4&lt;/code&gt; → &lt;code&gt;2&lt;/code&gt;로 변경한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;5. Amount of Memory (GB)를 &lt;code&gt;24&lt;/code&gt; → &lt;code&gt;12&lt;/code&gt;로 변경한다.&lt;/p&gt;
&lt;p&gt;6. 변경 사항을 저장한다.&lt;/p&gt;
&lt;p&gt;저장 후 인스턴스가 새 사양으로 재구성되며, 이제부터는 Always Free 한도 내에서 운영되는 상태가 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. 후기&lt;/h2&gt;
&lt;p&gt;솔직히 두 달 정도 4 OCPU / 24GB 환경에서 잘 써왔던 터라, 갑자기 사양이 절반으로 줄어드는 건 꽤 아쉬웠다. 돌리고 있던 서비스들 입장에서는 자원이 빠듯해질 수도 있는 변화다.&lt;/p&gt;
&lt;p&gt;그래도 어쨌든 &lt;strong&gt;무료로 계속 쓸 수 있다는 게 가장 중요&lt;/strong&gt;하니, 새로운 한도에 맞춰 운영하는 쪽으로 정리했다. &amp;quot;그래도 무료서버니까 쓰자&amp;quot; 하는 마음으로  &lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. 참고: 남은 자원 활용 팁&lt;/h2&gt;
&lt;p&gt;공식 문서에 따르면 Always Free A1.Flex 자원(2 OCPU / 12GB)은 꼭 단일 인스턴스에만 쓸 필요는 없다. 1 OCPU / 6GB씩 인스턴스 2개로 나눠서 운영하는 것도 가능하다.&lt;/p&gt;
&lt;p&gt;용도별로 인스턴스를 분리하면 한쪽에 문제가 생겨도 다른 쪽에 영향을 주지 않는다는 장점이 있다. 예를 들어 하나는 웹서버/리버스 프록시용, 다른 하나는 백엔드 서비스용으로 나누는 식이다. 현재는 2 OCPU / 12GB 단일 인스턴스로 운영 중이지만, 추후 용도가 늘어나면 분리 운영도 고려해볼 만하다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;참고 문서&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en-us/iaas/Content/FreeTier/freetier_topic-Always_Free_Resources.html&quot;&gt;Oracle Cloud - Always Free Resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Study/Infra</category>
      <category>free tier</category>
      <category>Infra</category>
      <category>Oracle cloud</category>
      <category>오라클클라우드</category>
      <category>정책 변경</category>
      <category>프리티어</category>
      <author>WHITE_FROST</author>
      <guid isPermaLink="true">https://whitefrost-developer.tistory.com/148</guid>
      <comments>https://whitefrost-developer.tistory.com/148#entry148comment</comments>
      <pubDate>Sun, 14 Jun 2026 15:38:41 +0900</pubDate>
    </item>
    <item>
      <title>오라클 클라우드 VPN 서버 구축 (WireGuard VPN)</title>
      <link>https://whitefrost-developer.tistory.com/147</link>
      <description>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;오라클 클라우드 프리티어 싱가포르 인스턴스에 WireGuard 기반 VPN 서버를 구축하는 과정이다. wg-easy라는 Docker 이미지를 사용해 웹 UI로 간편하게 관리할 수 있도록 구성한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;환경&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Oracle Cloud Infrastructure (OCI) Free Tier&lt;/li&gt;
&lt;li&gt;리전: ap-singapore-1 (싱가포르)&lt;/li&gt;
&lt;li&gt;OS: Ubuntu (ARM Ampere A1)&lt;/li&gt;
&lt;li&gt;Docker 29.4.3&lt;/li&gt;
&lt;li&gt;Docker Compose 5.1.3&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. Docker 설치&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 패키지 업데이트
sudo apt update &amp;amp;&amp;amp; sudo apt upgrade -y&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;시스템 패키지 목록을 최신화하고 업그레이드한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Docker 공식 설치 스크립트 실행
curl -fsSL https://get.docker.com | sh&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Docker 공식 스크립트를 받아 자동으로 설치한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 현재 유저를 docker 그룹에 추가
sudo usermod -aG docker $USER&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;이 명령어 없이는 docker 명령어를 실행할 때마다 sudo가 필요하다. 그룹에 추가하면 일반 유저 권한으로도 docker를 사용할 수 있다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 그룹 변경 즉시 적용
newgrp docker&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;재로그인 없이 docker 그룹 권한을 현재 세션에 바로 적용한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 설치 확인
docker --version
docker compose version&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;정상 설치 시 아래와 같이 출력된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Docker version 29.4.3, build 055a478
Docker Compose version v5.1.3&lt;/code&gt;&lt;/pre&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;2. 오라클 콘솔 Security List 포트 개방&lt;/h2&gt;
&lt;p&gt;오라클 클라우드는 방화벽이 &lt;strong&gt;2중 구조다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;인터넷 → OCI Security List (콘솔) → 서버 내부 iptables → 서비스&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;두 곳 모두 포트를 열어줘야 한다.&lt;/p&gt;
&lt;h3&gt;OCI 콘솔에서 Ingress Rules 추가&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Networking → Virtual Cloud Networks
→ VCN 클릭 → Security 탭
→ Default Security List → Add Ingress Rules&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Ingress Rule 1 (WireGuard 터널)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Stateless&lt;/td&gt;
&lt;td&gt;체크 해제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source Type&lt;/td&gt;
&lt;td&gt;CIDR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source CIDR&lt;/td&gt;
&lt;td&gt;0.0.0.0/0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP Protocol&lt;/td&gt;
&lt;td&gt;UDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination Port&lt;/td&gt;
&lt;td&gt;51820&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;51820은 WireGuard의 기본 포트. 실제 VPN 터널 트래픽이 이 포트로 들어온다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;Ingress Rule 2 (wg-easy 웹 UI)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Stateless&lt;/td&gt;
&lt;td&gt;체크 해제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source Type&lt;/td&gt;
&lt;td&gt;CIDR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source CIDR&lt;/td&gt;
&lt;td&gt;0.0.0.0/0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP Protocol&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination Port&lt;/td&gt;
&lt;td&gt;51821&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;51821은 wg-easy 웹 관리 UI 포트. 브라우저에서 VPN 클라이언트를 추가하고 QR코드를 발급받는 용도이다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;3. 서버 내부 iptables 포트 개방&lt;/h2&gt;
&lt;p&gt;오라클 Ubuntu는 ufw가 기본 설치되어 있지 않아 iptables를 직접 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# WireGuard 포트 허용
sudo iptables -I INPUT -p udp --dport 51820 -j ACCEPT&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;-I INPUT&lt;/code&gt; : INPUT 체인 맨 앞에 규칙 삽입&lt;br&gt;&lt;code&gt;-p udp&lt;/code&gt; : UDP 프로토콜&lt;br&gt;&lt;code&gt;--dport 51820&lt;/code&gt; : 목적지 포트 51820&lt;br&gt;&lt;code&gt;-j ACCEPT&lt;/code&gt; : 해당 트래픽 허용&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# wg-easy 웹 UI 포트 허용
sudo iptables -I INPUT -p tcp --dport 51821 -j ACCEPT&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 재부팅 후에도 규칙 유지되도록 저장
sudo apt install iptables-persistent -y
sudo netfilter-persistent save&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;iptables 규칙은 기본적으로 재부팅하면 초기화된다. &lt;code&gt;iptables-persistent&lt;/code&gt; 패키지가 부팅 시 규칙을 자동으로 복원&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 규칙 적용 확인
sudo iptables -L INPUT | grep -E &amp;#39;51820|51821&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;아래와 같이 출력되면 정상&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code&gt;ACCEPT     tcp  --  anywhere   anywhere   tcp dpt:51821
ACCEPT     udp  --  anywhere   anywhere   udp dpt:51820&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;4. wg-easy란?&lt;/h2&gt;
&lt;p&gt;WireGuard VPN 서버를 쉽게 관리할 수 있는 Docker 이미지다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wg-easy
  ├── WireGuard 서버 (51820 UDP) → 실제 VPN 터널
  └── 웹 UI (51821 TCP)          → 클라이언트 관리 페이지&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;웹 UI에서 클라이언트 추가 시 QR코드를 발급해줘서 폰에서 스캔만 하면 바로 연결된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. 비밀번호 해시 생성&lt;/h2&gt;
&lt;p&gt;wg-easy는 보안을 위해 평문 비밀번호 대신 &lt;strong&gt;bcrypt 해시&lt;/strong&gt;를 사용한다&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run --rm -it ghcr.io/wg-easy/wg-easy wgpw &amp;#39;원하는비밀번호&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--rm&lt;/code&gt; : 명령어 실행 후 컨테이너 자동 삭제&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-it&lt;/code&gt; : 터미널 입출력 활성화&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wgpw&lt;/code&gt; : wg-easy 내장 비밀번호 해시 생성 명령어&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이미지가 없으면 자동으로 pull 해오기 때문에 별도 설치 없이 바로 실행 가능하다.&lt;br&gt;실행 결과로 아래와 같은 해시가 출력된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$2b$12$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;이 값을 복사해둔다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6. docker-compose.yml 작성&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir ~/wg-easy &amp;amp;&amp;amp; cd ~/wg-easy
nano docker-compose.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아래 내용을 붙여넣는다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy
    container_name: wg-easy
    environment:
      - LANG=en
      - WG_HOST=&amp;lt;서버_공인IP&amp;gt;
      - PASSWORD_HASH=$$2b$$12$$나머지해시
    volumes:
      - ~/.wg-easy:/etc/wireguard
    ports:
      - &amp;quot;51820:51820/udp&amp;quot;
      - &amp;quot;51821:51821/tcp&amp;quot;
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
    restart: unless-stopped&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;환경변수 설명&lt;/strong&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LANG=en&lt;/code&gt; : 웹 UI 언어 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WG_HOST&lt;/code&gt; : 서버 공인 IP (클라이언트가 접속할 주소)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PASSWORD_HASH&lt;/code&gt; : 웹 UI 로그인 비밀번호 해시&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;주의: &lt;code&gt;$&lt;/code&gt; → &lt;code&gt;$$&lt;/code&gt; 로 변환 필수&lt;/strong&gt;&lt;br&gt;docker-compose.yml에서 &lt;code&gt;$&lt;/code&gt;는 변수 선언 특수문자로 인식됩니다. 해시값의 모든 &lt;code&gt;$&lt;/code&gt;를 &lt;code&gt;$$&lt;/code&gt;로 바꿔야 정상 동작한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;code&gt;원본:  $2b$12$xxxx
변환:  $$2b$$12$$xxxx&lt;/code&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;strong&gt;볼륨/권한 설명&lt;/strong&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;~/.wg-easy:/etc/wireguard&lt;/code&gt; : WireGuard 설정 파일 영구 저장&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NET_ADMIN&lt;/code&gt; : 네트워크 인터페이스 제어 권한&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SYS_MODULE&lt;/code&gt; : 커널 모듈 로드 권한&lt;/li&gt;
&lt;li&gt;&lt;code&gt;net.ipv4.ip_forward=1&lt;/code&gt; : IP 포워딩 활성화 (VPN 트래픽을 인터넷으로 전달하기 위해 필수)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;저장은 &lt;code&gt;Ctrl+X&lt;/code&gt; → &lt;code&gt;Y&lt;/code&gt; → &lt;code&gt;Enter&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;7. 컨테이너 실행&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;docker compose up -d&lt;/code&gt;&lt;/pre&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;-d&lt;/code&gt; : 백그라운드 실행 (detached mode)&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;아래와 같이 출력되면 성공이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[+] Running 2/2
 ✔ Network wg-easy_default  Created
 ✔ Container wg-easy        Started&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;8. 웹 UI 접속&lt;/h2&gt;
&lt;p&gt;브라우저에서 아래 주소로 접속한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;http://&amp;lt;서버_공인IP&amp;gt;:51821&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;로그인 화면이 뜨면 설정한 비밀번호를 입력한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;9. 클라이언트 추가 및 연결&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;앱 설치&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기기&lt;/th&gt;
&lt;th&gt;설치&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;iPhone&lt;/td&gt;
&lt;td&gt;App Store → WireGuard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android&lt;/td&gt;
&lt;td&gt;Play Store → WireGuard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MacBook&lt;/td&gt;
&lt;td&gt;Mac App Store → WireGuard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;클라이언트 추가&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;웹 UI → + New Client
→ 이름 입력 (예: iPhone, MacBook)
→ Create
→ QR코드 생성&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;폰에서 연결&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WireGuard 앱 → + 버튼
→ QR코드 스캔
→ 터널 추가됨
→ 토글 ON&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;10. VPN 연결 확인&lt;/h2&gt;
&lt;p&gt;브라우저에서 아래 사이트 접속 후 IP 확인&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://ifconfig.me&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;서버 공인 IP(싱가포르 IP)로 표시되면 VPN이 정상적으로 동작한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;보안 관련 참고사항&lt;/h2&gt;
&lt;p&gt;51821 포트(웹 UI)를 24시간 열어두면 봇들의 로그인 시도가 발생할 수 있다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;운용 방식&lt;/th&gt;
&lt;th&gt;장점&lt;/th&gt;
&lt;th&gt;단점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;항상 열어두기&lt;/td&gt;
&lt;td&gt;편함&lt;/td&gt;
&lt;td&gt;브루트포스 시도 노출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;필요할 때만 열기&lt;/td&gt;
&lt;td&gt;안전&lt;/td&gt;
&lt;td&gt;콘솔 접속 번거로움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;비밀번호를 충분히 강하게 설정했다면 열어두는 것도 현실적으로 큰 문제는 없다.. wg-easy 자체적으로 로그인 실패 횟수 제한이 있어 브루트포스를 어느정도 방어한다.&lt;/p&gt;</description>
      <category>Study/Infra</category>
      <category>Oracle cloud</category>
      <category>vpn</category>
      <category>wireguard</category>
      <author>WHITE_FROST</author>
      <guid isPermaLink="true">https://whitefrost-developer.tistory.com/147</guid>
      <comments>https://whitefrost-developer.tistory.com/147#entry147comment</comments>
      <pubDate>Tue, 26 May 2026 12:54:50 +0900</pubDate>
    </item>
    <item>
      <title>YOLO11 실전 배포에서 마주친 이미지 처리 문제들</title>
      <link>https://whitefrost-developer.tistory.com/146</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;1272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7QzBn/dJMcaaMaerb/A7Gx2vPaAK0x41BSQ50CMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7QzBn/dJMcaaMaerb/A7Gx2vPaAK0x41BSQ50CMk/img.png&quot; data-alt=&quot;이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7QzBn/dJMcaaMaerb/A7Gx2vPaAK0x41BSQ50CMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7QzBn%2FdJMcaaMaerb%2FA7Gx2vPaAK0x41BSQ50CMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;526&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;1272&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떤 프로젝트였나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 영양제 관리 플랫폼을 개발하면서 YOLO11 모델을 직접 학습시켜 영양제를 인식하는 기능을 구현했다. 이 과정에 모델을 학습시키는 것에서 생기는 문제를 마주쳤고, 그 과정을 기록한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 1. 학습은 잘 됐는데 실제 서비스에서 인식을 못 한다?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 오래 헤맸던 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 학습은 분명히 잘 됐는데, 실제로 핸드폰으로 찍은 사진을 넣으면 인식을 제대로 못 하는 상황이 반복됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 &lt;b&gt;해상도 차이&lt;/b&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습할 때 사용한 이미지는 &lt;b&gt;1000&amp;times;1000 이내&lt;/b&gt;로 맞춰서 넣었는데, 실제 서비스에서는 핸드폰으로 찍은 원본 이미지를 그대로 모델에 박아버렸던 것이다. 최신 스마트폰 사진은 3000&amp;times;4000을 훌쩍 넘는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 입장에서는 학습 때 본 적 없는 크기의 이미지가 들어오니 특징을 제대로 추출하지 못한 거였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결책: 비율 유지 리사이징&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 가로 또는 세로가 1000px 초과 시 비율 유지하며 리사이징
img.thumbnail((1000, 1000))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가로세로 비율을 유지하면서 1000&amp;times;1000 박스 안에 들어오도록 줄여주면 인식률이 확 올라갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 학습데이터가 적을경우 학습한 데이터의 종류도 확실히 파악하고 처리를하자&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 2. 리사이징 했는데도 가끔 인식을 못 한다?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리사이징을 적용했는데도 여전히 인식이 안 되는 케이스가 있었다. 그런데 이상하게 수동으로 편집한 이미지는 잘 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 &lt;b&gt;EXIF 회전 정보&lt;/b&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스마트폰은 사진을 찍을 때 픽셀 데이터는 항상 센서 기준(가로 방향)으로 저장하고, &quot;이 사진은 90도 돌려서 봐야 해&quot;라는 정보를 EXIF 메타데이터에 따로 저장한다. 갤러리 앱이나 브라우저는 이걸 읽어서 자동으로 돌려서 보여주니까 우리 눈엔 항상 정방향으로 보이는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 PIL 같은 라이브러리가 이 EXIF 정보를 &lt;b&gt;기본적으로 무시&lt;/b&gt;한다는 것. 그래서 모델한테는 영양제가 옆으로 누운 이미지가 그대로 들어가버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결책: exif_transpose() 적용&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;from PIL import Image, ImageOps

img = Image.open(&quot;photo.jpg&quot;)
img = ImageOps.exif_transpose(img)  # EXIF 회전 정보를 실제 픽셀에 반영&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;exif_transpose()&lt;/code&gt;는 Pillow에서 공식으로 제공하는 함수로, EXIF의 회전 정보를 읽어서 픽셀 데이터를 실제로 돌려준다. 이걸 리사이징 전에 먼저 적용해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 3. 인식은 됐는데 바운딩 박스 위치가 이상하다?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리사이징한 이미지로 분석하면 좌표도 리사이징된 이미지 기준으로 나온다. 그런데 프론트엔드에서는 원본 고해상도 이미지 위에 바운딩 박스를 그려야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결책: 좌표 역스케일링&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;fix&quot;&gt;&lt;code&gt;원본 좌표 = 리사이즈 좌표 &amp;times; (원본 크기 / 리사이즈 크기)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리사이즈 비율만큼 다시 곱해주면 원본 이미지 기준 좌표로 복원할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;문제&lt;/th&gt;
&lt;th&gt;원인&lt;/th&gt;
&lt;th&gt;해결&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;원본 이미지 인식 불가&lt;/td&gt;
&lt;td&gt;학습 해상도와 입력 해상도 차이&lt;/td&gt;
&lt;td&gt;1000px 이내로 비율 유지 리사이징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;리사이징 후에도 인식 실패&lt;/td&gt;
&lt;td&gt;EXIF 회전 정보 미처리&lt;/td&gt;
&lt;td&gt;&lt;code&gt;exif_transpose()&lt;/code&gt; 선적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;바운딩 박스 위치 틀림&lt;/td&gt;
&lt;td&gt;리사이즈 좌표 그대로 사용&lt;/td&gt;
&lt;td&gt;역스케일링으로 원본 좌표 복원&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 서비스에서 이미지를 다룰 때는 &lt;b&gt;해상도 정규화&lt;/b&gt;와 &lt;b&gt;메타데이터 처리&lt;/b&gt; 두 가지를 반드시 챙겨야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;table style=&quot;width: 100%; border-collapse: collapse;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; padding: 8px;&quot; align=&quot;center&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;yaksok.png&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;1272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rq9Us/dJMcah5D3lu/Si3UKbJXIWJELPZI4dOYm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rq9Us/dJMcah5D3lu/Si3UKbJXIWJELPZI4dOYm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rq9Us/dJMcah5D3lu/Si3UKbJXIWJELPZI4dOYm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRq9Us%2FdJMcah5D3lu%2FSi3UKbJXIWJELPZI4dOYm0%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;968&quot; height=&quot;1272&quot; data-filename=&quot;yaksok.png&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;1272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span&gt;리사이징시에는 정확하게 인식&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; padding: 8px;&quot; align=&quot;center&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;yaksok1.png&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;1320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bluEWt/dJMcah5D3lx/j9tMKTllqQm8R0k7FdVU2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bluEWt/dJMcah5D3lx/j9tMKTllqQm8R0k7FdVU2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bluEWt/dJMcah5D3lx/j9tMKTllqQm8R0k7FdVU2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbluEWt%2FdJMcah5D3lx%2Fj9tMKTllqQm8R0k7FdVU2k%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;1002&quot; height=&quot;1320&quot; data-filename=&quot;yaksok1.png&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;1320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span&gt;원본 사진은 인식을 못한다&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; padding: 8px;&quot; align=&quot;center&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;yaksok2.png&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba0wyh/dJMcabROxf6/70YshGNOH0QjMU8Ss8kbUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba0wyh/dJMcabROxf6/70YshGNOH0QjMU8Ss8kbUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba0wyh/dJMcabROxf6/70YshGNOH0QjMU8Ss8kbUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba0wyh%2FdJMcabROxf6%2F70YshGNOH0QjMU8Ss8kbUK%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;646&quot; height=&quot;832&quot; data-filename=&quot;yaksok2.png&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span&gt;리사이징 시에는 정확히 인식&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; padding: 8px;&quot; align=&quot;center&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;yaksok3.png&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;1008&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t2B0q/dJMcabROxge/aDtg6u9sXYy8SkkRpM9UNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t2B0q/dJMcabROxge/aDtg6u9sXYy8SkkRpM9UNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t2B0q/dJMcabROxge/aDtg6u9sXYy8SkkRpM9UNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft2B0q%2FdJMcabROxge%2FaDtg6u9sXYy8SkkRpM9UNK%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;774&quot; height=&quot;1008&quot; data-filename=&quot;yaksok3.png&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;1008&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span&gt;원본사진은 제대로 인식을 못함&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>AI/AI 모델 &amp;amp; API</category>
      <category>Yolo11</category>
      <category>모델 학습</category>
      <category>트러블 슈팅</category>
      <author>WHITE_FROST</author>
      <guid isPermaLink="true">https://whitefrost-developer.tistory.com/146</guid>
      <comments>https://whitefrost-developer.tistory.com/146#entry146comment</comments>
      <pubDate>Mon, 25 May 2026 18:34:22 +0900</pubDate>
    </item>
    <item>
      <title>오라클 클라우드에 접속하는 봇들을 막자</title>
      <link>https://whitefrost-developer.tistory.com/145</link>
      <description>&lt;p&gt;오라클 클라우드 무료 티어로 서버를 하나 띄웠다. 처음엔 WireGuard VPN만 올려두고 아무것도 안 했다. 그런데 SSH 로그를 열어봤더니 이미 난리가 나 있었다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;May 11 23:26:00 sshd: Connection closed by authenticating user root 158.180.74.12 [preauth]
May 11 23:27:17 sshd: Connection reset by authenticating user root 2.57.122.196 [preauth]
May 11 23:28:13 sshd: Disconnected from authenticating user root 176.65.132.50 [preauth]
May 11 23:31:19 sshd: Connection reset by authenticating user root 2.57.122.194 [preauth]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;공개 IP가 할당된 순간부터 전 세계 봇들이 이미 두드리고 있었던 거다. 내가 서버를 켠 게 아니라 IP가 생긴 것만으로 표적이 된 셈이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;왜 이런 일이 생기나?&lt;/h2&gt;
&lt;p&gt;인터넷에는 24시간 돌아가는 자동화 스캐너들이 있다. 이 봇들은 전 세계 IP 대역을 순서대로 훑으면서 열린 포트를 찾고, SSH 같은 서비스가 뜨면 자동으로 로그인을 시도한다.&lt;/p&gt;
&lt;p&gt;주로 시도하는 계정은 &lt;code&gt;root&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;ubuntu&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt; 같은 기본 계정들이다. 비밀번호도 &lt;code&gt;123456&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;admin&lt;/code&gt; 같은 걸 리스트로 돌린다. 이걸 &lt;strong&gt;브루트포스 공격&lt;/strong&gt;이라고 한다.&lt;/p&gt;
&lt;p&gt;로그에서 &lt;code&gt;[preauth]&lt;/code&gt;가 붙어있는 건 인증 단계조차 넘지 못했다는 뜻이다. 즉, 시도는 했지만 전부 실패한 거다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;오라클 클라우드 기본 설정은 꽤 튼튼하다&lt;/h2&gt;
&lt;p&gt;다행히 오라클 클라우드 인스턴스는 기본적으로:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SSH 비밀번호 로그인이 비활성화되어 있다&lt;/li&gt;
&lt;li&gt;키 파일(pem)이 없으면 접속이 불가능하다&lt;/li&gt;
&lt;li&gt;root 직접 로그인도 막혀있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그래서 봇들이 아무리 두드려도 키 없이는 들어올 수 없다. 하지만 그렇다고 마냥 방치하는 건 찜찜하다. 로그가 쌓이고, 서버 리소스도 조금씩 낭비된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Fail2ban으로 자동 차단하기&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Fail2ban&lt;/strong&gt;은 로그를 실시간으로 감시하다가 일정 횟수 이상 실패한 IP를 자동으로 차단해주는 도구다.&lt;/p&gt;
&lt;p&gt;설치는 간단하다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;`sudo apt install fail2ban -y
sudo systemctl enable fail2ban --now`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;설치 후 차단된 IP 목록 확인:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;`sudo fail2ban-client status sshd`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이런 식으로 출력된다:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;`Status for the jail: sshd
|- Currently failed:  3
|- Total failed:      47
`- Banned IP list:    2.57.122.189 45.148.10.121 ...`&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;아까 로그에서 계속 보이던 &lt;code&gt;2.57.122.xxx&lt;/code&gt; 같은 봇들이 여기 들어오게 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;더 나아가서 — SSH 포트 바꾸기&lt;/h2&gt;
&lt;p&gt;봇들은 대부분 기본 포트인 22번만 스캔한다. SSH 포트를 22가 아닌 다른 번호로 바꾸는 것만으로도 봇 트래픽의 99%가 사라진다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;`sudo nano /etc/ssh/sshd_config
# Port 22 → Port 2222`&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;단, 포트를 바꾸면 오라클 콘솔의 Security List에서도 새 포트를 열어줘야 한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;공개 IP를 가진 서버는 켜는 순간부터 공격 대상이 된다. 이건 특별한 일이 아니라 인터넷에서 서버를 운영한다는 게 원래 그런 거다.&lt;/p&gt;
&lt;p&gt;중요한 건:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SSH 키 인증만 허용&lt;/strong&gt; — 비밀번호 로그인은 비활성화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fail2ban 설치&lt;/strong&gt; — 반복 시도 IP 자동 차단&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;포트 변경 (선택)&lt;/strong&gt; — 봇 트래픽 대부분 차단&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 세 가지만 해도 웬만한 자동화 공격은 다 막힌다. 로그를 보면서 봇들이 튕겨나가는 걸 확인하는 것도 나름 재미있는 경험이었다.&lt;/p&gt;</description>
      <category>Study/Infra</category>
      <category>Infra</category>
      <category>오라클클라우드</category>
      <author>WHITE_FROST</author>
      <guid isPermaLink="true">https://whitefrost-developer.tistory.com/145</guid>
      <comments>https://whitefrost-developer.tistory.com/145#entry145comment</comments>
      <pubDate>Sun, 24 May 2026 17:20:20 +0900</pubDate>
    </item>
    <item>
      <title>클로드 디자인(Claude Design) 종속성 벗어나기: 완벽한 로컬 오픈소스 대안, OpenDesign 리뷰</title>
      <link>https://whitefrost-developer.tistory.com/144</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 AI를 활용한 UI/UX 프로토타이핑 도구들이 쏟아지는 가운데, 단연 돋보이는 것은 Anthropic이 발표한 Claude Design 이다. 간단한 프롬프트만으로 와이어프레임부터 고해상도 프로토타입, 슬라이드 덱까지 순식간에 만들어내는 퍼포먼스는 개발자 입장에서도 상당히 인상적이다. 하 지만 강력한 기능 이면에는 항상 '종속성&amp;rsquo;이라는 고민이 따른다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;2164&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eKHT3L/dJMcaiXrNV1/j7ilQGTUU1oYqXZ2PrfDv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eKHT3L/dJMcaiXrNV1/j7ilQGTUU1oYqXZ2PrfDv1/img.png&quot; data-alt=&quot;Claude Design 요금&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eKHT3L/dJMcaiXrNV1/j7ilQGTUU1oYqXZ2PrfDv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeKHT3L%2FdJMcaiXrNV1%2Fj7ilQGTUU1oYqXZ2PrfDv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;226&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;2164&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Claude Design 요금&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드가 아무리 뛰어난 결과물을 내어준다고 해도, 결국 우리는 플랫폼이 정해놓은 '주간 한도' 와 '불투명한 요금제'라는 울타리 안에서만 움직일수밖에 없다. 개발 환경에서 특정 SaaS에 완전히 종속된다는 것은 장기적인 생산성 관점에서 리스크가 될 수 밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게다가 클로드의 Claude Design은 아직까지는 요금제가 정해지지 않은 상태이다. 결국 점점 더 큰 요금제를 강요하게 될 수 밖에 없다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;1. Claude Design, 무엇이 강점이고 무엇이 한계인가?&lt;/span&gt; &lt;/h2&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;image1.png&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wxr6v/dJMcaiDa4fF/wDRjGkyYuOP3edV4qsbSZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wxr6v/dJMcaiDa4fF/wDRjGkyYuOP3edV4qsbSZ0/img.png&quot; data-alt=&quot;Claude Design 선택화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wxr6v/dJMcaiDa4fF/wDRjGkyYuOP3edV4qsbSZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwxr6v%2FdJMcaiDa4fF%2FwDRjGkyYuOP3edV4qsbSZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;464&quot; data-filename=&quot;image1.png&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Claude Design 선택화면&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;현재 클로드 디자인은 크게 다음 4가지 기능을 지원한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Prototype&lt;/b&gt;: 간단한 와이어프레임부터 고해상도 프로토타입 제작&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Slide deck&lt;/b&gt;: 슬라이드 및 프레젠테이션 제작&lt;/li&gt;
&lt;li&gt;&lt;b&gt;From template&lt;/b&gt;: 템플릿을 활용한 짧은 쇼츠 영상 제작&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Other&lt;/b&gt;: 디자인 시스템 구축 등 기타 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image2.png&quot; data-origin-width=&quot;3586&quot; data-origin-height=&quot;2002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nVjhc/dJMb990AuXo/ipGvUzU3kk4dBrt4K7BJPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nVjhc/dJMb990AuXo/ipGvUzU3kk4dBrt4K7BJPK/img.png&quot; data-alt=&quot;Claude Design 실행화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nVjhc/dJMb990AuXo/ipGvUzU3kk4dBrt4K7BJPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnVjhc%2FdJMb990AuXo%2FipGvUzU3kk4dBrt4K7BJPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;447&quot; data-filename=&quot;image2.png&quot; data-origin-width=&quot;3586&quot; data-origin-height=&quot;2002&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Claude Design 실행화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prototype 실행하면 다음과 같은 화면이 나오게 된다.&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;image3.png&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8mE8p/dJMcafNgYU3/N815Ul20WZK9r9cMDwkpOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8mE8p/dJMcafNgYU3/N815Ul20WZK9r9cMDwkpOK/img.png&quot; data-alt=&quot;프롬프트 입력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8mE8p/dJMcafNgYU3/N815Ul20WZK9r9cMDwkpOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8mE8p%2FdJMcafNgYU3%2FN815Ul20WZK9r9cMDwkpOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;137&quot; data-filename=&quot;image3.png&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프롬프트 입력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과로 아래와 같은 새로운 입력창이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image4.png&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;1726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bebK1t/dJMcaad6JYs/1GGRsqkImrbAYWkLMCpXW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bebK1t/dJMcaad6JYs/1GGRsqkImrbAYWkLMCpXW1/img.png&quot; data-alt=&quot;Claude QNA&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bebK1t/dJMcaad6JYs/1GGRsqkImrbAYWkLMCpXW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbebK1t%2FdJMcaad6JYs%2F1GGRsqkImrbAYWkLMCpXW1%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;642&quot; data-filename=&quot;image4.png&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;1726&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Claude QNA&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드 디자인 아니 클로드의 가장 큰 장점인 프롬프트를 단순하게 받아들이지 않고 사용자의 목표나 목적등을 구체적으로 역질문 하면서 프롬프트를 더 깎아 나가준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image5.png&quot; data-origin-width=&quot;2752&quot; data-origin-height=&quot;1750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uO5p3/dJMcaf0J8qB/yJdjPmfdRSVnGkHYPSGeBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uO5p3/dJMcaf0J8qB/yJdjPmfdRSVnGkHYPSGeBk/img.png&quot; data-alt=&quot;결과페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uO5p3/dJMcaf0J8qB/yJdjPmfdRSVnGkHYPSGeBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuO5p3%2FdJMcaf0J8qB%2FyJdjPmfdRSVnGkHYPSGeBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;509&quot; data-filename=&quot;image5.png&quot; data-origin-width=&quot;2752&quot; data-origin-height=&quot;1750&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과가 생각보다 만족스럽지는 않았지만, 그래도 이정도의 퀄리티를 빠르게 만드는 부분은 정말 항상 대단하다고 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 결과물 역시 단순한 이미지가 아니라, Vercel이나 로컬 환경으로 즉시 넘길 수 있도록 HTML/React 기반 코드로 Handoff를 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 바로 이 지점에서 치명적인 한계가 발생한다. 아무리 클로드 디자인이 훌륭하게 디자인을 뽑아내 준다고 한들, 이 모든 작업은 결국 막대한 토큰 소모와 직결되서 , 디자인 시안을 몇 번만 수정하고 코드로 변환하다 보면 금세 토큰을 다 써버리거나 주간 한도 초과 메시지를 만나게 될것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 입장에서 편히 작업을 돌릴 수 없다는 것이 도구로서 엄청난 제약일 수 밖에 없다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;2. 완벽한 탈중앙화 대안: OpenDesign&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로드의 훌륭한 워크플로우는 그대로 가져가되, 실행 환경의 주도권은 내가 쥐고 싶다면 &lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/nexu-io/open-design&quot; data-token-index=&quot;1&quot;&gt;&lt;span&gt;nexu-io/open-design&lt;/span&gt;&lt;/a&gt;이 훌륭한 해답이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image6.png&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVCUDY/dJMcahddlOe/QGm2Fu17aa2HpRsnXVkrDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVCUDY/dJMcahddlOe/QGm2Fu17aa2HpRsnXVkrDk/img.png&quot; data-alt=&quot;Open Design 설명&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVCUDY/dJMcahddlOe/QGm2Fu17aa2HpRsnXVkrDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVCUDY%2FdJMcahddlOe%2FQGm2Fu17aa2HpRsnXVkrDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;450&quot; data-filename=&quot;image6.png&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Open Design 설명&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apache 2.0 라이선스로 공개된 이 프로젝트로 &quot;클로드 디자인의 95%를 가져온 오픈소스 대안&quot;이라고 소개 할만큼 대안이 될 정도로 충분하다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자체적인 AI를 강제하는 대신, 내 로컬에 깔려있는 Claude Code, Cursor, Gemini CLI 등을 감지하여 디자인 워크플로우에 연결해 주는 것을 직접 통제할 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치와 실행은 아래와 같이 간단하다.&lt;/p&gt;
&lt;pre id=&quot;code_1777818203459&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git clone https://github.com/nexu-io/open-design.git
cd open-design
corepack enable
corepack pnpm --version   # 10.33.2
pnpm install
pnpm tools-dev run web&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image7.png&quot; data-origin-width=&quot;1812&quot; data-origin-height=&quot;1478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcpiAT/dJMcabYnL4v/L4LY4ocDx4RtdvS1eWBs9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcpiAT/dJMcabYnL4v/L4LY4ocDx4RtdvS1eWBs9k/img.png&quot; data-alt=&quot;Open Design 모델 선택&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcpiAT/dJMcabYnL4v/L4LY4ocDx4RtdvS1eWBs9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcpiAT%2FdJMcabYnL4v%2FL4LY4ocDx4RtdvS1eWBs9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;653&quot; data-filename=&quot;image7.png&quot; data-origin-width=&quot;1812&quot; data-origin-height=&quot;1478&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Open Design 모델 선택&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세팅 화면에 진입하면 현재 내 환경에 설치된 CLI 도구들을 자동으로 스캔하여 실행 모드를 선택할 수 있다. 특정 AI에 종속되지 않고 언제든 백엔드 모델을 교체할 수 있는 유연성이 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image8.png&quot; data-origin-width=&quot;3590&quot; data-origin-height=&quot;1998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqOr53/dJMcaaLWsY5/W5NCZ9JoH294g21y3wMxOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqOr53/dJMcaaLWsY5/W5NCZ9JoH294g21y3wMxOk/img.png&quot; data-alt=&quot;Open Design 실행이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqOr53/dJMcaaLWsY5/W5NCZ9JoH294g21y3wMxOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqOr53%2FdJMcaaLWsY5%2FW5NCZ9JoH294g21y3wMxOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;445&quot; data-filename=&quot;image8.png&quot; data-origin-width=&quot;3590&quot; data-origin-height=&quot;1998&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Open Design 실행이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디서 많이 보질 않았는가? 괜히 Claude Design의 95% 를 가지고 왔다는게 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진행 방식도 비슷하게 된다. 처음 Claude Design 과는 다르게 &quot; 면접관에게 보여줄 개발자 랜딩 페이지 &quot; 라 프롬프트를 쳤다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image9.png&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;1514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Zp8s/dJMcaiQGXQ6/6N01RkMX7AOOqpPO5ForuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Zp8s/dJMcaiQGXQ6/6N01RkMX7AOOqpPO5ForuK/img.png&quot; data-alt=&quot;Open Design QnA&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Zp8s/dJMcaiQGXQ6/6N01RkMX7AOOqpPO5ForuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Zp8s%2FdJMcaiQGXQ6%2F6N01RkMX7AOOqpPO5ForuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;698&quot; data-filename=&quot;image9.png&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;1514&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Open Design QnA&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과로 여기도 역시 이렇게 질문을 해주는 QnA 창이 등장한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image10.png&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;1518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuDa7a/dJMcagFpkQw/el7R5FgMXYutShkvnIQImK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuDa7a/dJMcagFpkQw/el7R5FgMXYutShkvnIQImK/img.png&quot; data-alt=&quot;Open Design Color 선택&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuDa7a/dJMcagFpkQw/el7R5FgMXYutShkvnIQImK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuDa7a%2FdJMcagFpkQw%2Fel7R5FgMXYutShkvnIQImK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;670&quot; data-filename=&quot;image10.png&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;1518&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Open Design Color 선택&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비주얼 톤, 컬러 팔레트와 타이포그래피 등을 세밀하게 조정할 수 있는 설정창도 나오는게 인상적이다. 아마 색까지 디테일하게 물어보면 더 좋게 이미지를 뽑아주지 않을까 싶다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image11.png&quot; data-origin-width=&quot;3600&quot; data-origin-height=&quot;2024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DMDTD/dJMcaciGflz/mng2CGr3UKO5YBIgd9dBz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DMDTD/dJMcaciGflz/mng2CGr3UKO5YBIgd9dBz1/img.png&quot; data-alt=&quot;Open Design 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DMDTD/dJMcaciGflz/mng2CGr3UKO5YBIgd9dBz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDMDTD%2FdJMcaciGflz%2Fmng2CGr3UKO5YBIgd9dBz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;450&quot; data-filename=&quot;image11.png&quot; data-origin-width=&quot;3600&quot; data-origin-height=&quot;2024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Open Design 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;더 좋은점&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image12.png&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;1650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nkjTv/dJMcaaFbStv/fbkg2OTZXkBxR1TdZE93Ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nkjTv/dJMcaaFbStv/fbkg2OTZXkBxR1TdZE93Ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nkjTv/dJMcaaFbStv/fbkg2OTZXkBxR1TdZE93Ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnkjTv%2FdJMcaaFbStv%2Ffbkg2OTZXkBxR1TdZE93Ak%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;750&quot; height=&quot;1113&quot; data-filename=&quot;image12.png&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;1650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p id=&quot;p-rc_6a66ddcd8237435a-71&quot; data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-path-to-node=&quot;6,0&quot;&gt;여기에 더 좋은 점은, OpenDesign이 단순한 UI/UX 디자인 생성기에 그치지 않는다. 상단 탭을 보면 디자인뿐만 아니라 Image, Video, Audio 생성 툴까지 통합되어 있는 걸 볼 수 있다.&lt;/span&gt;&lt;span data-path-to-node=&quot;6,2&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;p-rc_6a66ddcd8237435a-72&quot; data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-path-to-node=&quot;7,2&quot;&gt;다만 한 가지 주의할 점은, Image 생성 같은 기능들을 사용할 때는 별도의 API Key를 요구한다. 이 부분은 미리 세팅할 때 참고하시면 좋을 듯.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;총평: 도구의 주인이 되기 위한 한 걸음&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenDesign을 직접 로컬에 띄워 테스트해 보며 느낀 점은, 이제 AI 기반의 디자인 제너레이터 역시 특정 기업의 독점적인 서비스에서 점차 오픈 생태계로 풀려나오고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여전히 완성도 면에서는 공식 클로드 디자인이 한 발 앞서 있을 수 있다. 하지만, 구독료나 토큰 제한의 눈치를 보지 않고, 내가 선호하는 LLM 모델을 붙여 무제한으로 디자인 이터레이션을 돌려보는것은 개발자에게 엄청난 자유도를 부여한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 남이 만들어둔 SaaS를 소비하는 것에 그치지 않고, 로컬 환경에서 직접 워크플로우를 통제하고 싶으시다면 OpenDesign은 충분히 클론하고 세팅해 볼 가치가 있는 프로젝트다.&lt;/p&gt;</description>
      <category>AI/AI 내용</category>
      <category>Ai</category>
      <category>Claude</category>
      <category>ClaudeDesign</category>
      <category>OpenDesign</category>
      <category>오픈소스</category>
      <category>클로드디자인</category>
      <author>WHITE_FROST</author>
      <guid isPermaLink="true">https://whitefrost-developer.tistory.com/144</guid>
      <comments>https://whitefrost-developer.tistory.com/144#entry144comment</comments>
      <pubDate>Sun, 3 May 2026 23:38:45 +0900</pubDate>
    </item>
    <item>
      <title>클로드 코드 공부 기록</title>
      <link>https://whitefrost-developer.tistory.com/143</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;클로드코드를 사용하고 있지만 막상 사용하다보면 &amp;ldquo;내가 이걸 잘 쓰고 있는건가?&amp;rdquo; , &amp;ldquo;다른 사람들은 나보다 더 잘 쓰는거 같은데?&amp;rdquo; 라는 생각과 함께 계속해서 새롭게 들어오는 지식들과 합쳐지다 보니 뇌속에 정리가 안되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 클로드 코드 공부 내용을 기록 하려고한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1단계 : 아무거나 만들어보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;to-do app , 랜딩페이지, 계산기 같은 간단한 SaaS 서비스 하나 만들어보기&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;완벽한 결과물일 필요도 없고, 클로드 코드가 어떻게 돌아가는지 알아보는 단계이다.&lt;/li&gt;
&lt;li&gt;다만 중요한점은 / 명령어로 작업 환경과 감각을 익혀보자&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2단계 : 결과물이 마음에 안든다 &amp;rarr; SDD / TDD&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1단계에서 최종결과물로 만들어졌지만 구조가 지저분하고 수정해도 만족스럽지 못할때 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기준을 먼저 세우는기획의 단계이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설계 하는 단계이다.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;어떤 하나를 만들어달라&amp;rdquo; 라는 요청의 개념에서 이 기능의 목적은 뭐고, 입력은 뭐고, 성공조건, 실패케이스 등 어떻게 통과하는지를 주는것이다.&lt;/li&gt;
&lt;li&gt;SDD , TDD 작성하기&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3단계 : 반복되는 작업이 보인다. &amp;rarr; Skills&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3단계 에서는 작업하는 단계에서 계속 반복하는 작업이 있을것이다. 이 부분을 캐치하는것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 자주쓰는 방식, 체크리스트, 플레이북, 노하우를 Claude가 다시 사용할 수 있게 만드는것&lt;/li&gt;
&lt;li&gt;Skills 는 MCP 와 다르게 재사용 가능한 능력의 개념이다.&lt;/li&gt;
&lt;li&gt;/ 형태로 호출해보기, 직접 Skills 만들어보기, 다른사람이 만든 Skills 사용하기&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4단계 : 메모리와 컨텍스트 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 룰, 파일 구조를 매번 다시 설명하고 있을때 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CLAUDE.md 를 작성할것, /init 을 통한 초안 작성하고 메모리를 점진적으로 축적할 것&lt;/li&gt;
&lt;li&gt;빌드 / 테스트 명령, 폴더 역할, 선호 라이브러리, 커밋전 점검 항목&lt;/li&gt;
&lt;li&gt;메모리 관리, 컨텍스트 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5단계 : 자동화의 시작 &amp;rarr; Hooks 를 통한 외부 연결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude 에게 일을 시키는 것이 아니라 작업 흐름 자체를 자동화 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Hooks 의 개념 &amp;rarr; 클로드 코드 라이프 사이클에서 특정 지점에 자동 실행되는 사용자 정의 명령어&lt;/li&gt;
&lt;li&gt;파일 수정후 &amp;rarr; 포맷팅, 검증, 알림, 규칙 위반시 차단등자동화 작업이다.&lt;/li&gt;
&lt;li&gt;MCP로 외부 서비스 연결 가능&lt;/li&gt;
&lt;li&gt;Chrome, Github Actions, 스케줄 같은 작업 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6단계 : 프로젝트가 커졋을때 &amp;rarr; Subagents / Agent Teams&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일도 많아지고 역할도 나뉘고 병렬처리의 시작이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Subagents 는 한 세션 안에서 분리된 컨텍스트로 하위작업을 수행하고 요약된 결과를 메인으로 돌린다.&lt;/li&gt;
&lt;li&gt;Agent Teams 는 독립적인 세션들이 서로 소통하며 협업 구조이다. (front , back, test)&lt;/li&gt;
&lt;li&gt;Subagents는 짧고 집중된 작업을 깨끗하게 분리할때 좋다.&lt;/li&gt;
&lt;li&gt;Agent Teams 는 서로의 역할을 나누면서 조금 더 큰단위의 일을 굴릴때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7단계 : 클로드 코드 작업 시스템을 설계하는 단계 Agent Harness&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code 위에 도구, 컨텍스트 관리, 실행 환경을 넣은 하나의 agentic harness단계이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI agents 가 코드를 잘 짤 수 있는 환경을 만들어주는것이 Harness ( AI 가 인간의 개입 없이 최대한 스스로 만들 수 있게끔 하는 그 시스템을 만들자 하는게 하네스 엔지니어링의 시작 )&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉 위에서 했던 모든 단계 , 제약 조건 + 도구 + 피드백 루프 + 문서화 등 이 모든것의 전체 운영 환경을 만들어주는것이 Harness engineering 이다.&lt;/li&gt;
&lt;li&gt;Harness engineering : 하네스를 체계적으로 설계하고 개선하는 기술&lt;/li&gt;
&lt;li&gt;실수 시 프롬프트 변경이 아니라 실수가 구조적으로 재발하지 못하도록 만들어내는것!&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7단계의 Claude 에 관한 개발단계를 적었다. 직접 해본 경험이 있어서 추후에 빠른 학습이 될거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 단계별로 글을 추가적을 작성하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/AI 내용</category>
      <category>Claude</category>
      <category>공부단계</category>
      <author>WHITE_FROST</author>
      <guid isPermaLink="true">https://whitefrost-developer.tistory.com/143</guid>
      <comments>https://whitefrost-developer.tistory.com/143#entry143comment</comments>
      <pubDate>Tue, 21 Apr 2026 04:51:48 +0900</pubDate>
    </item>
    <item>
      <title>4월에 글을 다시 시작하며</title>
      <link>https://whitefrost-developer.tistory.com/142</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 1월의 마무리 글에 매달 시작과 끝에 내 상황에 대한 정리를 다시 해보겠다고 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 일정으로 인해 매달 초랑 말에 글을 쓰지 못했지만 그래도 1월의 내용을 리뷰하며 글을 작성해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1월의 다짐은 다음과 같이 총 5개를 작성했다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;[스펙]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;ADsP &amp;amp; SQLD 시험 접수 / OPIc 등급 상향&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;[취업]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;마스터 자소서 완성 및 10곳 지원 / 포트폴리오 최적화&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;[인프라]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가상화와 자동화가 적용된 '홈랩(Home Lab)' 구축&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;[AI]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;바이브 코딩 루틴 정립 &amp;amp; 개발자의 본질적 가치 찾기&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;[알고리즘]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 골드 1 달성 (문제 해결력 기르기)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 회고를 하나씩 하고 1~4 월이 되는동안 어떤 일이 있었고 무엇을 했는지 정리해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[스펙]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;ADsP &amp;amp; SQLD 시험 접수 / OPIc 등급 상향 (&lt;span style=&quot;color: #ee2323;&quot;&gt;Fail&lt;/span&gt;)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스펙적으로는 나아진게 없다. 우선 ADsP 는 신청을 하지 못했고 SQLD는 이번에 아쉽게 떨어졌다. OPIc 역시 준비를 하지 않았기때문에 변동사항은 없다. 이번 취업준비를 하면서 대부분의 서류가 떨어졌는데, 이유를 분석하면 확실히 영어 등급의 상향이 필수적이라는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[취업]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;마스터 자소서 완성 및 10곳 지원 / 포트폴리오 최적화 ( &lt;span style=&quot;color: #f3c000;&quot;&gt;Half-success&lt;/span&gt; )&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 10곳 이상은 확실히 지원했다. 결과가 안좋지 이번에 자기소개서를 적으면서 경험을 최적화시킨게 제일 좋은경험 아니였을까 싶다. 포트폴리오도 만들었다. 하지만 더 보충이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[인프라]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가상화와 자동화가 적용된 '홈랩(Home Lab)' 구축 (&lt;span style=&quot;color: #409d00;&quot;&gt;Success&lt;/span&gt;)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 알리익스프레스에서 산 미니피씨에 홈랩을 구축했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈랩 구축이야기는 나중에 천천히 해보겠다. 현재는 도메인 구입후 연결까지한 상태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[AI]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;바이브 코딩 루틴 정립 &amp;amp; 개발자의 본질적 가치 찾기 (&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;Half-success&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이러니한 부분인 AI시대에 개발자의 본질적인 가치는 찾았다. 하지만 오히려 바이브 코딩 루틴은 적립하지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[알고리즘]&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;백준 골드 1 달성&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(&lt;span style=&quot;color: #409d00;&quot;&gt;Success&lt;/span&gt;)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삼성 소프트웨어 B형 시험을 하면서 여러 문제를 풀다보니 자연스럽게 골드 1을 달성했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;687474703a2f2f6d617a617373756d6e6964612e7774662f6170692f76322f67656e65726174655f62616467653f626f6a3d616b6b61646961.svg&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyI7yR/dJMcagynHeh/U1j9lfzWMz9HjrkRKZPfrK/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyI7yR/dJMcagynHeh/U1j9lfzWMz9HjrkRKZPfrK/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyI7yR/dJMcagynHeh/U1j9lfzWMz9HjrkRKZPfrK/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyI7yR%2FdJMcagynHeh%2FU1j9lfzWMz9HjrkRKZPfrK%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;170&quot; data-filename=&quot;687474703a2f2f6d617a617373756d6e6964612e7774662f6170692f76322f67656e65726174655f62616467653f626f6a3d616b6b61646961.svg&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그 동안 무엇을 했는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 ~ 2 월은 AI&amp;nbsp; 영양제 분석 프로젝트를 진행했고 2 ~ 4월은 중고거래 플랫폼 관련 프로젝트를 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두개의 프로젝트를 하면서 나의 AI에 대한 도전을 계속했다. OCR 및 모델 튜닝 생성을 하기도 하였고 AI 검색이라는 기능도 개발한 경험이 되었다. 다만 아쉬운 점은 서류에서 많은 탈락이있어서 자존감이 조금 낮아졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSAFY 종료까지 아직 3개월 남았다. 3개월안에 취업하자&lt;/p&gt;</description>
      <category>Me/공부일상</category>
      <author>WHITE_FROST</author>
      <guid isPermaLink="true">https://whitefrost-developer.tistory.com/142</guid>
      <comments>https://whitefrost-developer.tistory.com/142#entry142comment</comments>
      <pubDate>Thu, 16 Apr 2026 09:26:33 +0900</pubDate>
    </item>
    <item>
      <title>더 나은 UX를 위한 프론트엔드 전략에 관하여</title>
      <link>https://whitefrost-developer.tistory.com/140</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TMC25 | Engineering - 더 나은 UX를 위한 프론트엔드 전략&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Q-_crvz4tv8&quot;&gt;https://www.youtube.com/watch?v=Q-_crvz4tv8&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용을 참고해서 글을 작성했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용자의 경험을 결정 짓는 최고의 UX&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 3가지로 정의된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;유려한 UI&lt;/b&gt; : 보기 좋고 사용하기 편한 디자인&lt;/li&gt;
&lt;li&gt;&lt;b&gt;화면의 안정성&lt;/b&gt; : Layout Shift 없는 견고한 화면&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로딩 속도&lt;/b&gt; : 사용자가 기다리는 시간의 최소화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 로딩 속도를 이용하여 사용자의 UX를 개선하는 계획들을 살펴봤다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. SSR(Server Side Rendering)의 전략적 활용이 필요하다.&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;이미지.png&quot; data-origin-width=&quot;2140&quot; data-origin-height=&quot;1797&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crALfU/dJMcaaRMrve/kwuO30yg5Vcev7B7H5NgZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crALfU/dJMcaaRMrve/kwuO30yg5Vcev7B7H5NgZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crALfU/dJMcaaRMrve/kwuO30yg5Vcev7B7H5NgZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrALfU%2FdJMcaaRMrve%2FkwuO30yg5Vcev7B7H5NgZK%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;600&quot; height=&quot;504&quot; data-filename=&quot;이미지.png&quot; data-origin-width=&quot;2140&quot; data-origin-height=&quot;1797&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;토스(Toss)의 렌더링 전략: 선택과 집중&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면이 위의 사진과 같다면&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;자산 영역 (상단):&lt;/b&gt; 내 통장에 얼마가 있는지 보여주는 가장 중요한 정보. (핵심가치)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;소비 내역 (하단):&lt;/b&gt; 스크롤을 내려야 볼 수 있거나, 상대적으로 덜 급한 정보.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 토스는 &lt;b&gt;'자산 영역'을 SSR로 처리&lt;/b&gt;하여 서버 통신과 동시에 사용자에게 즉각적으로 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 데이터 양이 많고 상대적으로 덜 중요한 '소비 내역'은 클라이언트 사이드에서 비동기로 불러오는 전략을 취한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 우리는 &amp;ldquo;서버의 부하를 막기 위해 CSR을 사용한다&amp;rdquo;거 나 혹은 &amp;ldquo;SEO(검색 최적화 엔진)을 위해 SSR을 사용한다고 알고 있었다. 하지만 위의 토스의 사례로 &amp;ldquo;사용자의 시선이 가장 먼저 닿는 곳&amp;rdquo; 을 먼저 파악하여 해당 부분만을 서버에서 미리 렌더링하여 유저의 경험을 극대화 시켜준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSR 대상 선정 기준&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 어떤 컴포넌트를 SSR로 태워야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당장 위에서는 중요한 정보라고 말은 했지만 중요하다고 모든건 SSR로 돌리면 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR은 결국 서버 리소스를 사용하는 비용이 발생하므로 다음 두 가지 측면을 고려해서 선정해야된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비즈니스 측면:&lt;/b&gt; 사용자에게 가장 가치 있는 정보인가? (LCP와 직결)LCP는 사용자가 페이지가 떴을때 인지하는 시점이다. 토스와 같은 핀테크의 가장 큰 목적은 자산이다. 때문에, 가장 핵심 부분이 늦게 뜨면 사용자가 앱 전체가 느리다고 느끼기 때문에 결국 신뢰도 문제로 직결된다.&lt;/li&gt;
&lt;li&gt;단순히 &quot;중요하다&quot;는 말을 넘어 Core Web Vitals(웹 성능 지표)와 사용자의 심리랑 이어서 생각해야된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기술적인 측면:&lt;/b&gt; 서버 부하를 감당할 수 있는가? 데이터 페칭 비용이 합리적인가?&lt;/li&gt;
&lt;li&gt;SSR은 모든 데이터가 준비될 때까지 응답을 줄 수 없다. 만약 데이터 양이 많고 조회속도가 느린다면 이런경우는 절데 SSR을 담으면 안되다. 이런 경우 뒤의 Caching 전략으로 해결한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국, SSR(Server Side Rendering)의 전략적 활용이 전체적인 UX를 향상시킨다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 체감 속도를 높이는 마법 Caching)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토스는 거래 내역과 같은 데이터를 보여줄 때 &lt;b&gt;로컬 캐싱&lt;/b&gt; 전략을 적극적으로 활용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;캐싱.png&quot; data-origin-width=&quot;2028&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0FKB6/dJMcabQEc8m/rbtDWY4mUgYi2UHXBN8Zg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0FKB6/dJMcabQEc8m/rbtDWY4mUgYi2UHXBN8Zg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0FKB6/dJMcabQEc8m/rbtDWY4mUgYi2UHXBN8Zg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0FKB6%2FdJMcabQEc8m%2FrbtDWY4mUgYi2UHXBN8Zg1%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;2028&quot; height=&quot;1000&quot; data-filename=&quot;캐싱.png&quot; data-origin-width=&quot;2028&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;최초 진입:&lt;/b&gt; API를 호출하여 데이터를 받아오고, 이를 로컬 스토리지에 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재진입:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;즉시 렌더링:&lt;/b&gt; API 응답을 기다리지 않고, &lt;b&gt;저장해 둔 이전 내역을 먼저 화면에 뿌려준다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;백그라운드 갱신:&lt;/b&gt; 뒤단에서 동기 API를 호출하여 최신 데이터를 받아온다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UI 업데이트:&lt;/b&gt; 데이터가 변경되었다면 자연스럽게 화면을 갱신한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식을 통해 사용자는 앱을 켤 때마다 &quot;로딩 없이 바로 뜬다&quot;는 쾌적한 경험을 느끼게된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 자바스크립트 다이어트 (Bundle Diet)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 영상을 보며 개인적으로 가장 인상 깊었던, 그리고 미처 깊게 생각하지 못했던 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &quot;평가 시간(Evaluation Time)&quot;에 대한 이야기다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;우선 평가 시간이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 &amp;lsquo;다운로드 &amp;rarr; 파싱 &amp;rarr; 컴파일 &amp;amp; 실행&amp;rsquo; 과 같은 작업을 거치는데 이때 파싱과 컴파일 &amp;amp; 실행 이 평가 시간이다. '평가 시간'이 길어지면 화면은 보이지만 버튼을 눌러도 반응하지 않는(TTI: Time to Interactive가 늦어지는) 현상이 발생한다.&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;1231231313333.jpg&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sgiGC/dJMcafeuJPI/moNW9Gg9XdAXSVUiZfY581/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sgiGC/dJMcafeuJPI/moNW9Gg9XdAXSVUiZfY581/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sgiGC/dJMcafeuJPI/moNW9Gg9XdAXSVUiZfY581/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsgiGC%2FdJMcafeuJPI%2FmoNW9Gg9XdAXSVUiZfY581%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;1024&quot; height=&quot;559&quot; data-filename=&quot;1231231313333.jpg&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다이어트 실천 가이드 (Webpack Bundle Analyzer)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webpack Bundle Analyzer 같은 도구를 사용하면 프로젝트의 '비만도'를 시각적으로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분석 결과를 바탕으로 다음 3단계 다이어트를 진행할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 미사용 의존성 제거 (Remove Unused Dependencies)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 진행하다 보면 package.json에는 남아있지만, 실제 코드에서는 더 이상 쓰지 않는 '유령 라이브러리'들이 쌓이게 된다. 이를 찾아내는 방법이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;문제점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트 초기에 테스트용으로 설치했다가 까먹고 지우지 않은 라이브러리.&lt;/li&gt;
&lt;li&gt;기획 변경으로 기능이 삭제되었으나, 라이브러리 설치 내역은 남은 경우.&lt;/li&gt;
&lt;li&gt;이들은 번들 사이즈를 키우고 빌드 시간을 늦추게된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결 도구: depcheck&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일일이 코드를 뒤지는 것은 불가능합니다. depcheck라는 CLI 도구를 사용하면 프로젝트를 스캔하여 쓰지 않는 의존성을 찾아준다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설치 및 사용법:&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;npx depcheck
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;실행 결과:&lt;/b&gt; Unused dependencies 리스트를 보여준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주의할 점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ESLint, Prettier, Webpack 플러그인 등은 소스 코드 내에서 import 하지 않지만 설정 파일에서 쓰인다. 때문에, depcheck가 이를 '미사용'으로 오탐지할 수 있으니, 무작정 지우지 말고 &lt;b&gt;개발 의존성(devDependencies)&lt;/b&gt; 인지 확인 후 삭제해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 경량화 (Lightweight Alternatives)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;기능은 같지만 몸집은 가벼운&quot; 라이브러리를 선택하는 전략이다. 단순히 용량만 줄이는 게 아니라, &lt;b&gt;트리 쉐이킹(Tree-shaking)&lt;/b&gt; 지원 여부가 핵심입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;대표적인 예시 1: 날짜 라이브러리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Before (Moment.js):&lt;/b&gt; 너무 무겁고, 객체 지향적이며, 내가 쓰지 않는 모든 언어팩(Locale)까지 번들에 포함시킨다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;After (Day.js / date-fns):&lt;/b&gt; Moment.js와 문법이 거의 같으면서 용량은 &lt;b&gt;약 30~100배 가볍습니다.&lt;/b&gt; 필요한 기능만 쏙쏙 뽑아 쓸 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대표적인 예시 2: 유틸리티 라이브러리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Before (Lodash):&lt;/b&gt; import _ from 'lodash' 형태로 통째로 불러오면 번들 사이즈가 급격히 커진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;After (Lodash-es):&lt;/b&gt; ES Module을 지원하는 lodash-es를 사용하여 필요한 함수만 import { debounce } from 'lodash-es' 형태로 가져오면, 사용하지 않는 나머지 함수들은 번들에서 제외(Tree-shaking)된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;꿀팁 도구: Bundlephobia&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://bundlephobia.com/&quot;&gt;Bundlephobia.com&lt;/a&gt;에 라이브러리 이름을 검색하면 용량을 알려주고, 대체 가능한 가벼운 라이브러리(Similar packages)를 추천해 준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 중복 제거 (Deduplication)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 많은 개발자가 놓치는 '숨겨진 살' 같은 존재다. 내가 설치한 라이브러리가 또 다른 라이브러리를 의존하면 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;발생 원인 (의존성 지옥):&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트가 A 라이브러리와 B 라이브러리를 사용한다.&lt;/li&gt;
&lt;li&gt;A는 library-x의 &lt;b&gt;1.0 버전&lt;/b&gt;을 사용&lt;/li&gt;
&lt;li&gt;B는 library-x의 &lt;b&gt;2.0 버전&lt;/b&gt;을 사용&lt;/li&gt;
&lt;li&gt;결과적으로 내 번들에는 library-x가 &lt;b&gt;두 개(1.0, 2.0)&lt;/b&gt; 가 들어가게 되는 문제가 생성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확인 방법:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;터미널에 npm ls &amp;lt;라이브러리명&amp;gt;을 입력하면 중복 설치 여부를 알 수 있다.&lt;/li&gt;
&lt;li&gt;또는 Webpack Bundle Analyzer를 돌렸을 때 같은 이름의 덩어리가 여러 개 보이면 100% 중복이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결 방법:&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;npm dedupe (혹은 yarn dedupe):&lt;/b&gt; 패키지 매니저가 자동으로 버전을 호환 가능한 범위 내에서 하나로 합치려고 시도한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;강제 통일 (Resolution/Overrides):&lt;/b&gt; package.json에 resolutions (yarn) 또는 overrides (npm) 필드를 추가하여 특정 버전을 강제로 쓰도록 명시합니다.JSON&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// package.json 예시
&quot;overrides&quot;: {
  &quot;library-x&quot;: &quot;2.0.0&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. API Waterfall 개선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 응답 &amp;rarr; 요청 응답 &amp;rarr; 요청 응답 &amp;rarr; api water fall 개선을 통해 빠르게 처리하는 형식으로 변경한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Waterfall (나쁜 예):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;User 정보 요청 (1초 소요) &amp;rarr; 응답 도착 &amp;rarr; 게시글 목록 요청 (1초 소요) &amp;rarr; 응답 도착&lt;/li&gt;
&lt;li&gt;&lt;b&gt;총 소요 시간: 2초&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Parallel (좋은 예):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;User 정보 요청 (1초) &amp;amp; 게시글 목록 요청 (1초) &amp;rarr; &lt;b&gt;동시에 시작!&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;총 소요 시간: 1초 (가장 느린 요청 기준)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;API Waterfall의 발생원인&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 무의식적인 await의 연속 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 흔한 실수다. 서로 상관없는 데이터인데 습관적으로 코드를 순서대로 짜는 경우&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// ❌ Bad: Waterfall 발생
async function loadData() {
  const userData = await fetchUser();       // 이거 끝날 때까지 기다림
  const bannerData = await fetchBanner();   // 윗줄 끝나야 시작함
  return { userData, bannerData };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② 컴포넌트 구조상의 문제 (Fetch-on-Render)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트가 다 그려지고(데이터 로딩 완료) 나서야 자식 컴포넌트가 렌더링되는데, 자식 컴포넌트 안에도 API 요청이 있는 경우다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;[부모]&lt;/b&gt; 사용자 프로필 로딩 중... (완료!)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;렌더링 &amp;rarr; &lt;b&gt;[자식]&lt;/b&gt; 친구 목록 컴포넌트 등장!
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;[자식]&lt;/b&gt; 이제 친구 목록 로딩 시작... (또 기다림)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 해결 방법 (개선 전략)&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① Promise.all로 병렬 처리 (Parallelization)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 의존성(인과관계)이 없는 데이터라면 동시에 출발시킨다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// ✅ Good: 병렬 처리
async function loadData() {
  // 두 요청을 동시에 보냄
  const [userData, bannerData] = await Promise.all([
    fetchUser(),
    fetchBanner()
  ]);
  return { userData, bannerData };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② 데이터 프리페칭 (Prefetching) / 라우트 레벨 데이터 로딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 마운트될 때 요청하는 것이 아니라, 페이지에 진입하는 순간(라우팅 시점)이나 마우스가 버튼에 올라갔을 때 미리 데이터를 요청한다. Next.js의 getServerSideProps나 React Query의 prefetchQuery가 이런 역할을 도와준다.&lt;/p&gt;</description>
      <category>Study/TypeScript</category>
      <category>frontend</category>
      <category>js</category>
      <category>react</category>
      <category>TS</category>
      <category>UI/UX</category>
      <author>WHITE_FROST</author>
      <guid isPermaLink="true">https://whitefrost-developer.tistory.com/140</guid>
      <comments>https://whitefrost-developer.tistory.com/140#entry140comment</comments>
      <pubDate>Thu, 12 Feb 2026 14:16:05 +0900</pubDate>
    </item>
    <item>
      <title>객체 탐지 모델 YOLO 와 R-CNN</title>
      <link>https://whitefrost-developer.tistory.com/139</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트를 진행하며 사진 속 요소들의 종류를 파악해야 하는 문제에 직면했다. 이를 해결하기 위해 AI 기술 중 하나인 객체 탐지 모델을 도입하기로 결정하였으며, 학습한 내용과 개인적인 견해를 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 컴퓨터비전에서의 문제들은 크게 다음과 같이 분류할 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Classification (분류)&lt;/li&gt;
&lt;li&gt;Object Detection (객체 탐지):&lt;/li&gt;
&lt;li&gt;Image Segmentation (이미지 분할)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;a.png&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg0TZo/dJMcabiGBsS/3SyvlLBUOKdxDFmLP8LC5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg0TZo/dJMcabiGBsS/3SyvlLBUOKdxDFmLP8LC5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg0TZo/dJMcabiGBsS/3SyvlLBUOKdxDFmLP8LC5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg0TZo%2FdJMcabiGBsS%2F3SyvlLBUOKdxDFmLP8LC5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;647&quot; height=&quot;268&quot; data-filename=&quot;a.png&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이중 현재 글에 다룰것은 Object Detection 이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 객체 탐지 모델이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 탐지 모델은 사진이나 영상 속에서 물체의 종류를 식별하는 기술이다. 이 모델의 가장 큰 특징은 단순히 객체의 존재 여부를 판단하는 것을 넘어, 해당 요소가 어느 좌표에 있는지 사각형 박스로 표시한다는 점이다. 위치 정보를 정확히 파악하는 것이 중요한 프로젝트에서 핵심적인 역할을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 탐지의 대표적인 두가지와 방법이 있는데 대표적인 모델 두가지를 소개해보겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. YOLO (You Only Look Once)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YOLO는 이름 그대로 이미지를 보고 바로 물체를 찾아내는 방식이다. 기술적으로는 1-Stage Detector로 분류된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;b.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;851&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LC3dA/dJMcabprRzw/QpVs8qBWv1sZclSibGjp60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LC3dA/dJMcabprRzw/QpVs8qBWv1sZclSibGjp60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LC3dA/dJMcabprRzw/QpVs8qBWv1sZclSibGjp60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLC3dA%2FdJMcabprRzw%2FQpVs8qBWv1sZclSibGjp60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;851&quot; data-filename=&quot;b.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;851&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;YOLO의 동작 방식&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;이미지 분할&lt;/b&gt;: 입력 이미지를 $S \times S$ 크기의 균일한 그리드로 나눈다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Bounding Box 예측&lt;/b&gt;: 각 그리드 셀에서 $B$개의 박스를 예측한다. 일반적으로 셀당 2개씩 예측하여 총 $2S^2$개의 박스 후보를 도출한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;신뢰도 점수 계산&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$Confidence = Pr(Object) \times IOU(pred, truth)$&lt;/li&gt;
&lt;li&gt;박스 내 객체 존재 확률($Pr$)과 실제 박스와의 겹침 정도($IOU$)를 곱해 점수를 산출한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클래스 확률 계산&lt;/b&gt;: 각 셀에서 검출된 물체가 어떤 클래스에 속하는지 확률을 구한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최종 검출&lt;/b&gt;: 신뢰도 점수가 높은 박스들만 남기고 나머지는 제거하여 최종 위치를 결정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번의 연산으로 모든 과정을 처리하기 때문에 속도가 매우 빠르며 실시간 영상 분석에 최적화되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. R-CNN (Region-based CNN)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;R-CNN은 YOLO와 달리 객체가 있을 법한 영역을 먼저 찾고 분류하는 2-Stage Detector 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;R-CNN의 동작 방식&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;c.png&quot; data-origin-width=&quot;1343&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJRHQT/dJMcahDaZZx/MuZ0WrQT0QvsE0fCkSM4E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJRHQT/dJMcahDaZZx/MuZ0WrQT0QvsE0fCkSM4E1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJRHQT/dJMcahDaZZx/MuZ0WrQT0QvsE0fCkSM4E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJRHQT%2FdJMcahDaZZx%2FMuZ0WrQT0QvsE0fCkSM4E1%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;1343&quot; height=&quot;357&quot; data-filename=&quot;c.png&quot; data-origin-width=&quot;1343&quot; data-origin-height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;영역 제안&lt;/b&gt;: 이미지 내에서 객체가 있을 법한 후보 영역을 약 2,000개 정도 추출한다. (주로 Selective Search 알고리즘 사용)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징 추출&lt;/b&gt;: 추출된 각 후보 영역을 동일한 크기로 조절한 후, CNN 모델에 통과시켜 특징 벡터를 추출한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분류&lt;/b&gt;: 추출된 특징을 바탕으로 SVM(Support Vector Machine)을 통해 해당 영역이 어떤 물체인지 분류한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;경계 상자 조정&lt;/b&gt;: 예측된 박스의 위치를 실제 물체 위치에 더 가깝도록 정밀하게 조정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 단계별로 연산이 이루어지므로 YOLO보다 속도는 느리지만, 더 높은 정확도를 기대할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. YOLO vs R-CNN: 비교와 선택&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 모델은 성능과 목적에 따라 확연한 차이를 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교 항목 YOLO (1-Stage) R-CNN 계열 (2-Stage)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;속도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;매우 빠름 (실시간 가능)&lt;/td&gt;
&lt;td&gt;비교적 느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;정확도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;상대적으로 낮음 (특히 작은 객체)&lt;/td&gt;
&lt;td&gt;매우 높음 (정밀한 검출)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;학습 난이도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;상대적으로 낮음&lt;/td&gt;
&lt;td&gt;복잡하고 연산 자원 많이 소모&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;b&gt;YOLO 선택 시점&lt;/b&gt;: 자율주행, 실시간 CCTV 보안 관제, 모바일 기기 구동 등 &lt;b&gt;실시간 응답성&lt;/b&gt;이 최우선일 때 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;R-CNN 선택 시점&lt;/b&gt;: 의료 영상 분석(X-ray, MRI), 정밀한 불량품 검수 등 속도보다는 &lt;b&gt;오탐 없는 정확도&lt;/b&gt;가 절대적으로 중요할 때 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 개발 시 비용에 대한 고찰&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 멀티모달 LLM이 등장하여 해상 사진 분석이나 OCR 결과를 매우 쉽게 얻을 수 있다. 그럼에도 불구하고 전용 모델 개발자가 개발 해야된다면 이유는 다음과 같을것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;첫째, 압도적인 운영 비용,&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 토큰을 엄청 소모한다. LLM API는 호출당 비용이 발생한다. 하루 수만 건 이상의 데이터를 처리하는 서비스라면 API 비용만으로 막대한 지출이 발생한다. 반면, 직접 구축한 YOLO 모델은 초기 학습 후 자체 서버에서 운영하므로 데이터 처리량이 늘어날수록 단가가 급격히 낮아진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;d.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brNP5N/dJMcah4gAyH/Dk7IXa3ZRuRAZDPaiPbLq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brNP5N/dJMcah4gAyH/Dk7IXa3ZRuRAZDPaiPbLq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brNP5N/dJMcah4gAyH/Dk7IXa3ZRuRAZDPaiPbLq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrNP5N%2FdJMcah4gAyH%2FDk7IXa3ZRuRAZDPaiPbLq1%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;512&quot; height=&quot;307&quot; data-filename=&quot;d.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;둘째, 지연 시간과 데이터 보안 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;API 방식은 외부 서버를 거쳐야 하므로 통신 속도의 영향을 받으며, 민감한 내부 데이터를 외부 서버로 전송해야 한다는 리스크가 있다. 직접 모델을 구축하면 로컬에서 즉각 처리가 가능하며 보안성 또한 완벽하게 확보할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;끝으로&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유로 어떤 서비스할 때 확실히 문제에 따라 올바른 AI 솔루션을 내는것이 중요하다고 생각한다.프로젝트의 규모와 목적에 맞춰 이 둘을 전략적으로 선택하는 능력이 현대 AI 개발자의 진정한 경쟁력이라 판단한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ganghee-lee.tistory.com/35&quot;&gt;https://ganghee-lee.tistory.com/35&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dotiromoook.tistory.com/24&quot;&gt;https://dotiromoook.tistory.com/24&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ultralytics.com/ko/blog/what-is-r-cnn-a-quick-overview&quot;&gt;https://www.ultralytics.com/ko/blog/what-is-r-cnn-a-quick-overview&lt;/a&gt;&lt;/p&gt;</description>
      <category>AI/AI 내용</category>
      <category>computervision</category>
      <category>objectdetection</category>
      <category>R-CNN</category>
      <category>yolo</category>
      <category>객체탐지</category>
      <category>딥러닝</category>
      <category>인공지능</category>
      <category>컴퓨터비전</category>
      <author>WHITE_FROST</author>
      <guid isPermaLink="true">https://whitefrost-developer.tistory.com/139</guid>
      <comments>https://whitefrost-developer.tistory.com/139#entry139comment</comments>
      <pubDate>Wed, 21 Jan 2026 20:48:28 +0900</pubDate>
    </item>
    <item>
      <title>이벤트 루프</title>
      <link>https://whitefrost-developer.tistory.com/138</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이벤트 루프가 뭐예요?&quot; 누군가 나에게 이렇게 묻다면 나는 자신 있게 답을 할 수 있을까? 솔직히 현재의 나는 명쾌하게 대답할 자신이 없다. 그래서 이 글은 미래의 나를 위해, 그리고 이 질문에 답하기 위해 작성하는 정리 노트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size26&quot;&gt;1. 자바스크립트는 싱글 스레드인데 어떻게 멀티태스킹을 할까?&lt;/h2&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;이벤트 루프를 논하기 전에 먼저 해결해야 할 의문이 있다. &lt;b data-index-in-node=&quot;33&quot; data-path-to-node=&quot;8&quot;&gt;&quot;자바스크립트는 싱글 스레드(Single Thread) 언어다.&quot;&lt;/b&gt; 이 말은 한 번에 하나의 작업만 할 수 있다는 뜻인데, 실제 우리가 쓰는 웹 사이트는 파일도 다운로드하고, 애니메이션도 보여주고, 서버 요청도 동시에 처리하는 것처럼 보인다. 이게 어떻게 가능한 걸까?&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;. 자바스크립트 엔진은 '요청'만 보내고, 실제로 시간이 오래 걸리는 작업(네트워크, 타이머 등)은 &lt;b data-index-in-node=&quot;96&quot; data-path-to-node=&quot;9&quot;&gt;브라우저가 제공하는 Web APIs&lt;/b&gt; 영역에서 수행된다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;10&quot;&gt;비동기 + 논블로킹(Async + Non-blocking)&lt;/b&gt; 방식이다.&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;11&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;11,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0&quot;&gt;비동기 + 논블로킹이란?&lt;/b&gt; 메인 스레드가 작업을 직접 붙들고 있는 게 아니라, 다른 곳(Web APIs)에 &quot;이거 해줘&quot;라고 위임하고 바로 다음 일을 처리하는 방식이다. 위임된 작업이 끝나면 결과만 나중에(Callback) 받아 처리한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;이 과정에서 &lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;12&quot;&gt;동작의 타이밍을 제어하고 조율하는 관리자&lt;/b&gt;가 바로 이벤트 루프(Event Loop)다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size26&quot;&gt;2. 이벤트 루프(Event Loop)의 정의&lt;/h2&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;이벤트 루프는 브라우저 내부에서 두 가지를 끊임없이 확인하고 중재하는 역할을 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;호출 스택(Call Stack)에 현재 실행 중인 코드가 있는가?&lt;/li&gt;
&lt;li&gt;태스크 큐(Task Queue)에 대기 중인 작업이 있는가?&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;호출 스택이 텅 비어있다면, 태스크 큐에 대기 중인 작업을 꺼내와 실행시킨다. 이 단순한 반복 작업이 자바스크립트 비동기의 핵심이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size26&quot;&gt;3. 주요 구성 요소 뜯어보기&lt;/h2&gt;
&lt;p data-path-to-node=&quot;19&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;image.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;1308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf7eXG/dJMcaaD1s32/qKMAVCDSE46XgtTOKhx0O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf7eXG/dJMcaaD1s32/qKMAVCDSE46XgtTOKhx0O0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf7eXG/dJMcaaD1s32/qKMAVCDSE46XgtTOKhx0O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf7eXG%2FdJMcaaD1s32%2FqKMAVCDSE46XgtTOKhx0O0%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;1602&quot; height=&quot;1308&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;1308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size23&quot;&gt;① 호출 스택 (Call Stack)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;호출 스택은 자바스크립트 엔진이 코드를 실행하는 공간이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;LIFO (Last In, First Out):&lt;/b&gt; 나중에 들어온 녀석이 먼저 나가는 '스택' 구조다.&lt;/li&gt;
&lt;li&gt;함수가 실행되면 스택에 쌓이고(Push), 실행이 끝나면 스택에서 제거된다(Pop).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23&quot;&gt;핵심:&lt;/b&gt; 자바스크립트는 싱글 스레드이므로 &lt;b data-index-in-node=&quot;22&quot; data-path-to-node=&quot;23&quot;&gt;호출 스택이 딱 하나&lt;/b&gt;다. 한 번에 하나의 함수만 실행할 수 있다는 뜻이다. 만약 여기서 무거운 작업을 돌리면 브라우저는 멈춰버린다(Blocking).&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size23&quot;&gt;② Web APIs:&lt;/h3&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;브라우저가 제공하는 멀티 스레드 공간이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;26&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;setTimeout, fetch, DOM Event 등이 여기서 처리된다.&lt;/li&gt;
&lt;li&gt;자바스크립트 엔진이 복잡하고 어려운 작업을 Web APIs에 요청하여 백그라운드에서 열심히 일을 처리한다. 일이 끝나면 결과물(콜백 함수)을 &lt;b data-index-in-node=&quot;88&quot; data-path-to-node=&quot;26,1,0&quot;&gt;태스크 큐&lt;/b&gt;로 넘겨준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;27&quot; data-ke-size=&quot;size23&quot;&gt;③ 태스크 큐 (Task Queue)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;Web APIs에서 처리가 끝난 콜백 함수들이 실행되기 위해 기다리는 대기실이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;29&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;29,0,0&quot;&gt;FIFO (First In, First Out):&lt;/b&gt; 먼저 들어온 녀석이 먼저 나간다.&lt;/li&gt;
&lt;li&gt;이벤트 루프는 호출 스택이 비었을 때만 여기서 콜백을 꺼내간다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 심화: 마이크로 태스크 vs 매크로 태스크&lt;/h2&gt;
&lt;h4 data-path-to-node=&quot;33&quot; data-ke-size=&quot;size20&quot;&gt;마이크로 태스크 큐 (Micro Task Queue)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;34&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;34,0,0&quot;&gt;대상:&lt;/b&gt; Promise (.then/catch/finally), async/await, MutationObserver 등&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;34,1,0&quot;&gt;특징:&lt;/b&gt; 우선순위가 &lt;b data-index-in-node=&quot;10&quot; data-path-to-node=&quot;34,1,0&quot;&gt;가장 높다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;호출 스택이 비면, 이벤트 루프는 매크로 태스크보다 &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;34,2,0&quot;&gt;마이크로 태스크 큐를 먼저 확인&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;34,3,0&quot;&gt;주의:&lt;/b&gt; 마이크로 태스크 큐에 있는 작업은 큐가 &lt;b data-index-in-node=&quot;26&quot; data-path-to-node=&quot;34,3,0&quot;&gt;완전히 빌 때까지&lt;/b&gt; 계속해서 실행된다. 만약 마이크로 태스크가 계속 추가되면(무한 루프 등), 매크로 태스크는 실행 기회를 못 얻고 화면 갱신(렌더링)도 차단되어 브라우저가 먹통이 될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;35&quot; data-ke-size=&quot;size20&quot;&gt;매크로 태스크 큐 (Macro Task Queue)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;36&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,0,0&quot;&gt;대상:&lt;/b&gt; setTimeout, setInterval, fetch, DOM 이벤트 등 일반적인 비동기 작업&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;36,1,0&quot;&gt;특징:&lt;/b&gt; 마이크로 태스크 큐가 텅 비어야 실행된다.&lt;/li&gt;
&lt;li&gt;일반적으로 매크로 태스크 하나를 실행한 후에는 브라우저가 화면을 다시 그릴(Render) 기회를 가질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 결론&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 루프는 호출 스택이 비어 있는지를 지속적으로 확인하고,비어 있을 경우 태스크 큐에 대기 중인 작업을 호출 스택으로 옮겨 실행을 관리하는 메커니즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처 및 참고 자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/&quot;&gt;https://inpa.tistory.com/entry/&lt;/a&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/%F0%9F%94%84-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EA%B5%AC%EC%A1%B0-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC&quot;&gt; -자바스크립트-이벤트-루프-구조-동작-원리&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=8aGhZQkoFbQ&quot;&gt;https://www.youtube.com/watch?v=8aGhZQkoFbQ&lt;/a&gt;&lt;/p&gt;</description>
      <category>Study/JavaScript</category>
      <category>eventloop</category>
      <category>javascript</category>
      <category>js지식</category>
      <category>이벤트루프</category>
      <author>WHITE_FROST</author>
      <guid isPermaLink="true">https://whitefrost-developer.tistory.com/138</guid>
      <comments>https://whitefrost-developer.tistory.com/138#entry138comment</comments>
      <pubDate>Wed, 14 Jan 2026 20:40:49 +0900</pubDate>
    </item>
  </channel>
</rss>