<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>DogGae</title>
    <link>https://codingmoondoll.tistory.com/</link>
    <description>가랑비에 옷 젖듯이</description>
    <language>ko</language>
    <pubDate>Wed, 12 Aug 2026 07:52:11 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>DogGae</managingEditor>
    <image>
      <title>DogGae</title>
      <url>https://tistory1.daumcdn.net/tistory/2802975/attach/67d7edefbde342e19ec381d8ed678d56</url>
      <link>https://codingmoondoll.tistory.com</link>
    </image>
    <item>
      <title>Firebase Authentication 과 Firebase Admin 을 이용한 JWT 기반 세션관리 구현</title>
      <link>https://codingmoondoll.tistory.com/entry/Firebase-Authentication-%EA%B3%BC-Firebase-Admin-%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-JWT-%EA%B8%B0%EB%B0%98-%EC%84%B8%EC%85%98%EA%B4%80%EB%A6%AC-%EA%B5%AC%ED%98%84</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;최근 들어 AI 덕분에 토이 프로젝트를 매우 쉽게 시작할 수 있게 되었다. 하지만 프로젝트가 '회원' 기반의 서비스라면, 회원 인증/인가 플로우를 구현할 필요가 있다. 이번엔 아내와 함께 제작중인 신혼부부 가계부 서비스에 인증/인가 플로우를 구현한 과정을 소개해볼까 한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Firebase Authentication 소개&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://firebase.google.com/docs/auth?hl=ko&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://firebase.google.com/docs/auth?hl=ko&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Firebase Authentication&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Firebase 인증을 사용하면 코드 몇 줄만으로 손쉬운 사용자 인증, 로그인, 온보딩을 위한 엔드 투 엔드 ID 솔루션을 앱에 추가할 수 있습니다.&quot; data-og-host=&quot;firebase.google.com&quot; data-og-source-url=&quot;https://firebase.google.com/docs/auth?hl=ko&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/ec2MdA/hyZKPwLY4o/AAAAAAAAAAAAAAAAAAAAAGqXVprJSwcNlC0b8zC1QFz_05q2h1uEWkWr3E56ahsZ/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1761922799&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=WpTpOic4c81yhhawT%2BeDVTPHh18%3D&quot; data-og-url=&quot;https://firebase.google.com/docs/auth?hl=ko&quot;&gt;&lt;a href=&quot;https://firebase.google.com/docs/auth?hl=ko&quot; target=&quot;_blank&quot; data-source-url=&quot;https://firebase.google.com/docs/auth?hl=ko&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://blog.kakaocdn.net/dna/ec2MdA/hyZKPwLY4o/AAAAAAAAAAAAAAAAAAAAAGqXVprJSwcNlC0b8zC1QFz_05q2h1uEWkWr3E56ahsZ/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1761922799&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=WpTpOic4c81yhhawT%2BeDVTPHh18%3D')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Firebase Authentication&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Firebase 인증을 사용하면 코드 몇 줄만으로 손쉬운 사용자 인증, 로그인, 온보딩을 위한 엔드 투 엔드 ID 솔루션을 앱에 추가할 수 있습니다.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;firebase.google.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;회원의 인증/인가를 담당할 서버를 구축하는 것은 너무나도 큰 작업이기 때문에 이를 직접 구현하기 보다는 널리 쓰이는 Firebase Authentication 라이브러리를 사용했다. 해당 라이브러리는 클라이언트 사이드에서 구글 Firebase 서버와 통신한다. 회원가입, 로그인, 로그아웃을 구현할 수 있는 인터페이스를 제공하며, 이 과정에서 인증 토큰과 리프레시 토큰을 발급한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Firebase Authentication 의 단점&lt;/h3&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;회사 혹은 개인의 자체 API 서버에서 JWT 를 활용할 수 없음.&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;당연한 말이지만, Firebase Authentication 이 발급하는 JWT는 내가 만든 백엔드 서버에서 사용할 수 없다. JWT 의 유효성 여부를 Firebase 와 통신하지 않고 확인할 수 없기 때문이다. 또한 발급된 JWT 는 쿠키에 자동으로 저장되지 않기 때문에 사용하기도 까다롭다.&lt;br&gt;&amp;nbsp;&lt;br&gt;다만, 모든 Firebase 라이브러리는 해당 JWT 를 기반으로 인가를 수행하므로 프로젝트에서 Authentication 외에도 다른 Firebase 서비스를 이용한다면 별도의 인증을 받을 필요가 없어지는 장점이 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;다른 기기의 JWT 까지 무효화할 수 없음&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase Authentication 은 클라이언트에서 실행되기 때문에, 로그아웃을 하더라도 다른 곳에서 로그인 되어 있는 계정의 JWT 까지 무효화 시킬 수는 없다. 인증토큰과 리프레시 토큰의 만료 기간 또한 직접 설정할 수 없다. 이게 큰 문제가 아니라고 생각하기 쉽겠지만, 실제 운영되고 있는 서비스에서는 치명적으로 작용할 수 있다. 회원 중 한명이 다른 기기를 통해 로그인 후, 로그아웃을 하지 못한 상황에서, 다른 누군가가 그 기기를 통해 계정에 접근하는 것을 막을 수단이 없기 때문이다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;클라이언트사이드에 저장되는 토큰&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 환경에서 구동되는 Firebase Authentication 는 토큰을 indexedDB 에 저장한다. 사이트에 악의적인 스크립트가 심긴 경우, 해커가 JWT 를 취득할 수도 있다(쉽지는 않겠지만).&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Firebase Admin&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://firebase.google.com/docs/admin/setup?hl=ko&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://firebase.google.com/docs/admin/setup?hl=ko&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;서버에 Firebase Admin SDK 추가&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Admin SDK는 권한이 있는 환경에서 Firebase와 상호작용할 수 있는 서버 라이브러리 집합입니다.&quot; data-og-host=&quot;firebase.google.com&quot; data-og-source-url=&quot;https://firebase.google.com/docs/admin/setup?hl=ko&quot; data-og-image=&quot;&quot; data-og-url=&quot;https://firebase.google.com/docs/admin/setup?hl=ko&quot;&gt;&lt;a href=&quot;https://firebase.google.com/docs/admin/setup?hl=ko&quot; target=&quot;_blank&quot; data-source-url=&quot;https://firebase.google.com/docs/admin/setup?hl=ko&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;서버에 Firebase Admin SDK 추가&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Admin SDK는 권한이 있는 환경에서 Firebase와 상호작용할 수 있는 서버 라이브러리 집합입니다.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;firebase.google.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;위의 단점들을 제거하기 위해서는, 우리 프로젝트 백엔드에서 사용될 자체 인증, 리프레시 토큰이 필요하다. 이 또한 직접 구현하기 보다, Firebase Admin 라이브러리를 이용할 수 있다. Firebase Admin 은 Firebase Authentication 에서 발급한 인증 토큰을 기반으로 자체 백엔드에서 사용할 JWT 를 직접 생성할 수 있으며, 만료기간을 직접 설정할 수도 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;게다가 계정과 관련된 모든 JWT 를 무효화 하는 등의 더 강력한 관리가 가능하다.&amp;nbsp;리프레시 토큰을 생성하는 인터페이스까지 제공하지는 않지만 JWT 안의 회원 정보를 기반으로 직접 생성한 JWT 를 리프레시 토큰으로 백엔드에서 저장하고 있으면 자체 토큰 리프레시를 구현할 수 있으며 자체적으로 발급하는 리프레시 토큰의 만료기간을 설정할 수도 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Firebase Authenticatoin 과 Firebase Admin 을 이용한 전체 인증/인가&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;두 라이브러리를 활용한 인증 / 인가 플로우는 아래와 같다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2296&quot; data-origin-height=&quot;1186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t8bhY/btsQ4i24Eu4/2RnnjC8nrX8fMbZRMKu0i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t8bhY/btsQ4i24Eu4/2RnnjC8nrX8fMbZRMKu0i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t8bhY/btsQ4i24Eu4/2RnnjC8nrX8fMbZRMKu0i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft8bhY%2FbtsQ4i24Eu4%2F2RnnjC8nrX8fMbZRMKu0i1%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;2296&quot; height=&quot;1186&quot; data-origin-width=&quot;2296&quot; data-origin-height=&quot;1186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2810&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ywV99/btsQ3EZNXPD/fxilK9b9ZOTqyMnk7dndqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ywV99/btsQ3EZNXPD/fxilK9b9ZOTqyMnk7dndqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ywV99/btsQ3EZNXPD/fxilK9b9ZOTqyMnk7dndqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FywV99%2FbtsQ3EZNXPD%2FfxilK9b9ZOTqyMnk7dndqK%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;2810&quot; height=&quot;830&quot; data-origin-width=&quot;2810&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;인증 단계에서는 이용자의 ID / PW 를 기반으로 Firebase 에 인증을 거쳐야 하지만, 인가 단계부터는 Firebase 서버를 통하지 않고 오직 백엔드 서버와 통신하는 것을 확인할 수 있다. 프로젝트 초기에는 위 과정을 거쳐서 자체 JWT 을 발급한 후, 만료기간을 1일로 설정해서 하루가 지난 후에는 유저가 자동으로 로그아웃 되도록 설정할 수 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Firebase Authenticatoin 과 Firebase Admin 을 이용한 토큰 리프레시&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;하루마다 로그인이 풀려서 매번 다시 로그인 하는게 귀찮고, 세션이 만료되는 절묘한 타이밍에 서비스를 이용하면 도중에 로그아웃 된다는 QA 이슈가 와이프로부터 들어왔다. 만료시간을 늘리는 것은 근본적인 해결책이 될 수 없고, 토큰 탈취 시의 공격 가능 시간도 늘어나기 때문에 토큰 리프레시 플로우를 구현하기로 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이전에도 말했지만, Firebase Admin 은 토큰 리프레시를 위한 인테페이스를 제공하지 않는다. 따라서 직접 그 플로우를 구현해야 하는데, 내가 생각해낸 방법은 크게 두가지였다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 자체 Refresh JWT 생성&lt;/h4&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;Firebase SDK 에서 발행하는 JWT 를 uuid 를 기반으로 하나의 임의로 생성된 Refresh JWT 와 매핑시킨다.&lt;/li&gt;&lt;li&gt;클라이언트에 해당 Refresh JWT 를 내려줘서 HTTP Only 쿠키에 저장한다.&lt;/li&gt;&lt;/ul&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;클라이언트에서 토큰 만료로 인해 요청이 실패할 시, 지니고 있는 자체 Refresh JWT 를 기반으로 인증 JWT 를 재발급 받는다.&lt;/li&gt;&lt;li&gt;그 후 재요청한다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. &lt;span style=&quot;color: #333333;&quot;&gt;Firebase Authentication 에서 사용하는 Refresh JWT 기반 재인증.&lt;/span&gt;&lt;/h4&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;/ul&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;클라이언트에서 토큰 만료로 인해 요청이 실패할 시, 지니고 있는 Firebase Refresh JWT 를 활용하여 Firebase Auth JWT 를 재발급 받는다.&lt;/li&gt;&lt;li&gt;해당 Firebase Auth JWT 를 사용하여 Firebase Admin 이 새로운 자체 인증 JWT 내려주고 HTTP Only 쿠키에 저장시킨다.&lt;/li&gt;&lt;li&gt;그 후 재요청한다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;아무리 생각해봐도 1번이 정석이고 2번은 문제를 우회하는 것에 가깝지만, 1번을 구현하려면 자체 Refresh JWT 매핑 테이블을 어딘가 저장하는 플로우를 구현해야 하므로 당장 빠르게 적용할 수 있는 방법은 2번이라고 생각하였다. Firebase Refresh JWT 는 만료기간이 별도로 없기에, 빠른 시일 내에 1번을 구현하는 것이 필요하다고 본다.&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 프론트엔드 깊게 이해하기</category>
      <category>Firebase</category>
      <category>JWT</category>
      <author>DogGae</author>
      <guid isPermaLink="true">https://codingmoondoll.tistory.com/146</guid>
      <comments>https://codingmoondoll.tistory.com/entry/Firebase-Authentication-%EA%B3%BC-Firebase-Admin-%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-JWT-%EA%B8%B0%EB%B0%98-%EC%84%B8%EC%85%98%EA%B4%80%EB%A6%AC-%EA%B5%AC%ED%98%84#entry146comment</comments>
      <pubDate>Thu, 9 Oct 2025 00:38:19 +0900</pubDate>
    </item>
    <item>
      <title>Notebook lm 사용 후기 - 학습 전 단계</title>
      <link>https://codingmoondoll.tistory.com/entry/Notebook-lm-%EC%82%AC%EC%9A%A9-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 도서를 사내 스터디의 일환으로 읽게 되었다. 이론 뿐만 아니라 실습에 대한 내용도 있기 때문에 분량이 600 페이지에 달한다. 누군가는 처음엔 내용이 완전히 이해되지 않더라도 쭉 읽어보고 그 후에 다시 읽으면서 내용을 정리하라고 하지만, 이 정도의 분량을 두번이나 읽는 것은 염두가 안나는 것이 사실이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.yes24.com/product/goods/153499985&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.yes24.com/product/goods/153499985&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1759735387402&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;밑바닥부터 시작하는 웹 브라우저 - 예스24&quot; data-og-description=&quot;웹 브라우저! 쌓고, 뜯고, 이해하라하나씩 이해하는 웹의 작동 원리크롬, 파이어폭스, 브레이브, 웨일 같은 웹 브라우저는 겉으로는 단순해 보이지만, 그 밑에는 무려 1천만 줄에 달하는 코드가 &quot; data-og-host=&quot;www.yes24.com&quot; data-og-source-url=&quot;https://www.yes24.com/product/goods/153499985&quot; data-og-url=&quot;https://www.yes24.com/product/goods/153499985&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SxEJd/hyZKHLWtG7/P8DFAHtsfkU0bhy5NvHqZK/img.jpg?width=934&amp;amp;height=1200&amp;amp;face=0_0_934_1200,https://scrap.kakaocdn.net/dn/cBRnUM/hyZKq55KP2/N4becMcNJk9jgcgasbPo1k/img.jpg?width=934&amp;amp;height=1200&amp;amp;face=0_0_934_1200,https://scrap.kakaocdn.net/dn/j4rJs/hyZKROyUeY/li2Ipfj4bTbnPNN3kLwiR1/img.jpg?width=934&amp;amp;height=1200&amp;amp;face=0_0_934_1200&quot;&gt;&lt;a href=&quot;https://www.yes24.com/product/goods/153499985&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.yes24.com/product/goods/153499985&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SxEJd/hyZKHLWtG7/P8DFAHtsfkU0bhy5NvHqZK/img.jpg?width=934&amp;amp;height=1200&amp;amp;face=0_0_934_1200,https://scrap.kakaocdn.net/dn/cBRnUM/hyZKq55KP2/N4becMcNJk9jgcgasbPo1k/img.jpg?width=934&amp;amp;height=1200&amp;amp;face=0_0_934_1200,https://scrap.kakaocdn.net/dn/j4rJs/hyZKROyUeY/li2Ipfj4bTbnPNN3kLwiR1/img.jpg?width=934&amp;amp;height=1200&amp;amp;face=0_0_934_1200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;밑바닥부터 시작하는 웹 브라우저 - 예스24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;웹 브라우저! 쌓고, 뜯고, 이해하라하나씩 이해하는 웹의 작동 원리크롬, 파이어폭스, 브레이브, 웨일 같은 웹 브라우저는 겉으로는 단순해 보이지만, 그 밑에는 무려 1천만 줄에 달하는 코드가&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.yes24.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이참에 요즘 유명한 학습 도구인 Notebook LM 을 한번 이 책을 학습하는데 활용해보기로 했다. 아래 내용은 '책을 읽기 전에' Notebook LM 을 통해 전반적인 구조를 숙지하는 과정에서 어떤 도움을 받을 수 있었는지를 정리한 글이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어떤 도움을 받을 수 있을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 내용의 초기 '색인' 을 생성하는데 도움을 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OHHUG/btsQ4OtxVqa/XS9h1vcM7YmO3ZLPmOfgH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OHHUG/btsQ4OtxVqa/XS9h1vcM7YmO3ZLPmOfgH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OHHUG/btsQ4OtxVqa/XS9h1vcM7YmO3ZLPmOfgH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOHHUG%2FbtsQ4OtxVqa%2FXS9h1vcM7YmO3ZLPmOfgH1%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;501&quot; height=&quot;501&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠시 딴 이야기를 하자면, 지식은 하나의 트리처럼 여러 항목들이 계층적인 관계를 맺는 형태를 취한다. &lt;b&gt;'지식'&lt;/b&gt; 자체가 학습의 목적이라면 전체 트리를 최대한 풍성하게 구성하고 내용을 잊지 않기 위해 해당 트리를 주기적으로 순회하며 복습하는 것이 필요할 것이다. 그러나 나처럼 학습의 목적이 &lt;b&gt;'응용'&lt;/b&gt;인 사람들에게는&lt;b&gt;,&lt;/b&gt; 트리의 깊이와 크기 자체보다는 이 트리를 눈 앞의 문제를 해결하는데 얼마다 도움을 줄 수 있느냐가 더 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 내게&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;학습에서 가장 중요한 요소는 &lt;b&gt;'색인'&lt;/b&gt;이다. 내게 문제를 해결할 지식이 있는지 없는지를 판별하고, 있다면 최대한 빠르게 가져오는 것이고 없다면 그 즉시 다운로드 받을 수만 있으면 족하다. 이 도서를 읽는 이유도, 앞으로 내가 개발자로서 직면하게 될 문제들을 해결하는데 사용될 색인을 구성하는 것이라고 말할 수 있을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;br /&gt;색인을 구성하는 방법은 다음의 구성을 따른다.&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1. 자료가 어떤 정보를 지니고 있는지 분류하여 항목화 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;2. 각 항목별 핵심 내용을 추출하고, 세부 내용은 해당 지식을 활용할 때 참조할 수 있도록 링크를 달아둔다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3. 각 항목들이 어떠한 연관 관계가 있다면 서로의 링크를 달아둔다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;여기서 1번을 '자료를 학습하기 전' 단계에 진행하면 좋다. 1번을 자료를 읽기 전에 해두면 2, 3번에 좀 더 집중할 수 있기 때문이다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 자료의 구성은 '목차'를 통해 잘 정리되어 있는 경우가 많지만 불필요한 부분도 많다. Notebook LM 을 사용하면 핵심만 담긴 초기 색인을 생성할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;어떤 기능이 있을까?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;초기 색인을 생성하기 위한 기능 두 가지는 '마인드맵'과 '보고서' 기능이다. 마인드 맵은 자료를 첨부한 후 바로 추출해낼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;마인드맵&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;마인드맵은 목차와 비슷해보이지만, 좀 더 다른 형태의 내용 분류를 이미지로 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2874&quot; data-origin-height=&quot;1510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coKbb1/btsQ2TQk174/siWZmgIEkfoHVeZRKehSj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coKbb1/btsQ2TQk174/siWZmgIEkfoHVeZRKehSj0/img.png&quot; data-alt=&quot;자료 첨부 이후 대시보드 &amp;amp;gt; 스튜디오 &amp;amp;gt; 마인드맵 선택&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coKbb1/btsQ2TQk174/siWZmgIEkfoHVeZRKehSj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoKbb1%2FbtsQ2TQk174%2FsiWZmgIEkfoHVeZRKehSj0%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;2874&quot; height=&quot;1510&quot; data-origin-width=&quot;2874&quot; data-origin-height=&quot;1510&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;자료 첨부 이후 대시보드 &amp;gt; 스튜디오 &amp;gt; 마인드맵 선택&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpfKhf/btsQ5hvteDT/SLj4lueC70H4g3VL5AesA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpfKhf/btsQ5hvteDT/SLj4lueC70H4g3VL5AesA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpfKhf/btsQ5hvteDT/SLj4lueC70H4g3VL5AesA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpfKhf%2FbtsQ5hvteDT%2FSLj4lueC70H4g3VL5AesA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2880&quot; height=&quot;1537&quot; data-origin-width=&quot;2880&quot; data-origin-height=&quot;1537&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;실제 목차의 구성과 비교해보았을 때, 다음의 특징이 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;불필요한 감사의 말, 서론 등의 섹션 정보가 제거되었다.&lt;/li&gt;
&lt;li&gt;키워드 중심으로 더 단순화된 색인이 생성되었다.&lt;/li&gt;
&lt;li&gt;마인드&amp;nbsp;맵&amp;nbsp;항목을&amp;nbsp;클릭하여&amp;nbsp;해당&amp;nbsp;항목과&amp;nbsp;관련된&amp;nbsp;채팅으로&amp;nbsp;넘어갈&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메타인지 과정에 매우 좋을 듯하다. 자료를 정리하다가 모르는 내용이 나오면 채팅을 통해 학습하고 채팅 링크를 마인드 맵에 붙일 수 있을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;보고서&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보고서는 자료가 개괄적으로 어떤 내용을 다루는지를 보여준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2860&quot; data-origin-height=&quot;1506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI0sCk/btsQ2q13vEg/qtqnr4JtEHeLaYoJYCK8zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI0sCk/btsQ2q13vEg/qtqnr4JtEHeLaYoJYCK8zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI0sCk/btsQ2q13vEg/qtqnr4JtEHeLaYoJYCK8zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI0sCk%2FbtsQ2q13vEg%2Fqtqnr4JtEHeLaYoJYCK8zk%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;2860&quot; height=&quot;1506&quot; data-origin-width=&quot;2860&quot; data-origin-height=&quot;1506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 66.1619%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;1176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzOuDi/btsQ4qzHGRu/5XkBxaf4lPw6gZbQECCEY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzOuDi/btsQ4qzHGRu/5XkBxaf4lPw6gZbQECCEY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzOuDi/btsQ4qzHGRu/5XkBxaf4lPw6gZbQECCEY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzOuDi%2FbtsQ4qzHGRu%2F5XkBxaf4lPw6gZbQECCEY0%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;551&quot; height=&quot;609&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1064&quot; data-origin-height=&quot;1176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 색인을 마인드 맵을 통해 생성한 후에는 실제 자료를 읽으면서 핵심 내용과 세부 내용을 구분해나가야 한다. 미리 요약 정보를 이렇게 읽어두면 작업이 쉬워진다.&lt;/li&gt;
&lt;li&gt;핵심적이지 않은 내용이 자동으로 생략되어, 꽤나 빠르게 읽힌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습의 목적은 '색인 생성'이며 이를 위한 초기 색인은 자료를 실제로 읽어보기 전에 수행해볼 수 있다. 이 과정에서 Notebook LM 의 마인드맵과 보고서 기능을 활용해봤더니, 만족할만한 초기 색인을 더 빠른 시간 안에 생성할 수 있었다. Chat GPT 보다 Notebook LM 이 어떤 좋은 점이 있는지 이해하기 어려웠는데, 이제는 학습에 정말 많이 활용해볼 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발, 학습 Tips</category>
      <author>DogGae</author>
      <guid isPermaLink="true">https://codingmoondoll.tistory.com/144</guid>
      <comments>https://codingmoondoll.tistory.com/entry/Notebook-lm-%EC%82%AC%EC%9A%A9-%ED%9B%84%EA%B8%B0#entry144comment</comments>
      <pubDate>Mon, 6 Oct 2025 15:40:53 +0900</pubDate>
    </item>
    <item>
      <title>3년 9개월 경력, 이제서야 돌아보다 (1)</title>
      <link>https://codingmoondoll.tistory.com/entry/3%EB%85%84-9%EA%B0%9C%EC%9B%94-%EA%B2%BD%EB%A0%A5-%EC%9D%B4%EC%A0%9C%EC%84%9C%EC%95%BC-%EB%8F%8C%EC%95%84%EB%B3%B4%EB%8B%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 되기 전에는 프로그래밍으로 먹고 살 수 있을지가 막막했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;개발자가 된 후에는 더 막막해졌다.&lt;br /&gt;높은 연봉을 받는 것을 받을 수 있을지 막막하고, 남다른 차별점을 가질 수 있을지가 막막하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때마다 내가 했던 방법을 이번에도 써보기로 했다. 그 간의 과정을 회고해보는 것이다.&lt;br /&gt;개발자가 된지 3년 9개월만에 회고를 해본다니, 많이 늦은 감이 없지 않다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 개발자가 되었나?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍이 내게 즐거움을 주고, 내 불안을 없애주었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠깐 딴소리지만, 그림을 그려본 적이 있는가? 역설적이게도, 그림을 그리는 과정 자체는 그리 즐겁지 않다. 괜찮은 그림을 그리기 위해서는 디테일을 위해 엄청나게 긴 시간을 써야한다. 나는 초상화를 그렸었는데, 코를 그리는 데만 2시간 50분을 썼다. '이게 뭐하는 짓이지?' 라는 생각을 수도 없이 했다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그런 식으로 눈, 코, 입, 머리카락을 그리고 나면 사람의 얼굴 하나가 도화지에 채워진다. 7시간을 그렸는데 얻을 수 있는 것은 그게 전부다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;하지만 그것으로 많은 것이 바뀐다. 아침에 첫째로 떠오르는 것은 어제의 그림이다. 일상의 중간에 몰래 스케치북을 꺼내본다. 어제의 그림을 펼쳐보면 부족한 부분이 눈에 들어온다. 다음엔 훨씬 더 잘 그릴 수 있을 것이라는 생각이 든다. 이전 장들을 넘겨 지금까지 그린 것들을 보고 나면, 생각이 확신이 된다. 7시간 동안 고통스러웠지만, 덕분에 일주일이 즐거워진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;군대 복무 시절, 후임이 프로그래밍을 할줄 알아서 나도 한번 해보고 싶어졌다. 후임은 백엔드 개발을 했었는데, 뭔가 따라하는 것처럼 보이고 싶지 않아 프론트엔드를 택했다. &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그림 하나를 그리는데 7시간이 걸렸지만, 프로그램 하나를 만드는데는 1년이 걸렸다. 그리고 그 1년의 기간동안, 아주 많이 고통스러웠다. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;IDE 가 없어서 처음엔 메모장으로 코드를 썼고, 얼마 안되는 휴식시간 동안 유튜브로 강의를 들었다. 나온지 8년이 넘은 옛날 개발 서적으로 공부를 했는데, 내용이 너무 불친절해 머리가 아팠다. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;결과물은 처참했다. 인터넷에서 긁어온 코드들이 덕지 덕지 붙어 있어서 조금만 잘못 건드리면 버그가 났고, 내 프로그램을 쓰는 후임들은 그것을 칭찬해주면서도 불편한 부분들을 계속 말해주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;군대를 전역할 즈음에, 내가 그렸던 그림을 모두 버렸다. 이제 딱히 뭔가를 잘 그리고 싶지 않았기 때문이다. 어렸을 때 내 꿈은 화가였지만 화가가 된 내 모습을 상상할 수는 없었다. 그러나 프로그래머가 된 내 모습은 바로 눈앞에 있는 것처럼 선명하게 보였다. 돈은 많이 벌까? 야근은 많이 할까? 결혼은 할 수 있을까? 배는 많이 안나올까? 내가 작성한 코드를 다음날의 내가 고치고 있으리라는 건 알 수 있었다. 그렇게 고친 코드가, 아마 이전 코드보다 훨씬 나을 것이다. 내가 평생 행복해지리라는 확신이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 나는 프로그래머가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결혼은 했고, 야근은 적당히 하고 있으며, 배는 계속 나오고 있다.&lt;/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>생각들</category>
      <author>DogGae</author>
      <guid isPermaLink="true">https://codingmoondoll.tistory.com/140</guid>
      <comments>https://codingmoondoll.tistory.com/entry/3%EB%85%84-9%EA%B0%9C%EC%9B%94-%EA%B2%BD%EB%A0%A5-%EC%9D%B4%EC%A0%9C%EC%84%9C%EC%95%BC-%EB%8F%8C%EC%95%84%EB%B3%B4%EB%8B%A4#entry140comment</comments>
      <pubDate>Thu, 25 Sep 2025 07:48:41 +0900</pubDate>
    </item>
    <item>
      <title>개인 프로젝트 코드베이스에 커서 룰 적용하기</title>
      <link>https://codingmoondoll.tistory.com/entry/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BD%94%EB%93%9C%EB%B2%A0%EC%9D%B4%EC%8A%A4%EC%97%90-%EC%BB%A4%EC%84%9C-%EB%A3%B0-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codingmoondoll.tistory.com/entry/%EC%9E%90%EA%BE%B8-%EB%8F%88-%EC%93%B0%EB%8A%94-%EC%99%80%EC%9D%B4%ED%94%84-%EC%9E%90%EC%A0%9C%EC%8B%9C%ED%82%A4%EB%A0%A4%EA%B3%A0-%EA%B0%80%EA%B3%84%EB%B6%80-%EA%B0%9C%EB%B0%9C%ED%95%9C-%ED%9B%84%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 글&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 아내와 함께 진행한 가계부 서비스에 커밋이 좀처럼 올라오고 있지 않다. 처음 생각했던 기본 기능들을 구현했으니 이제 기능을 고도화 하고 코드 리팩터링을 진행해야 할 타이밍인데, 항상 이 타이밍에 흥미를 잃는 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l064Z/btsPJXSulG7/fvbulKVDP3bcmreZqymftK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l064Z/btsPJXSulG7/fvbulKVDP3bcmreZqymftK/img.png&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;721&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.2442%; margin-right: 10px;&quot; data-widthpercent=&quot;50.84&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l064Z/btsPJXSulG7/fvbulKVDP3bcmreZqymftK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl064Z%2FbtsPJXSulG7%2FfvbulKVDP3bcmreZqymftK%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;497&quot; height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3vdtI/btsPHlgrVjg/khCZ2k5rpQ29HW5I3qWvO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3vdtI/btsPHlgrVjg/khCZ2k5rpQ29HW5I3qWvO0/img.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.593%;&quot; data-widthpercent=&quot;49.16&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3vdtI/btsPHlgrVjg/khCZ2k5rpQ29HW5I3qWvO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3vdtI%2FbtsPHlgrVjg%2FkhCZ2k5rpQ29HW5I3qWvO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;그러나 착실하게 쌓이고 있는 와이프의 QA 항목...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 '회사에서 하던 일을 집에서 또 하기 싫다' 라는 무의식적인 마음이 드는 것이 아닐까 싶기도 하다. 그러나 이대로 프로젝트를 가만히 두었다가는 내 레포 목록의 자리만 차지하게 될 것이다. 이 프로젝트를 회사에서 하는 것과 다르게 하기 위해서, 나도 '바이브 코딩'을 시도해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BBG2X/btsPIeVcYlH/qOQVI3QBvSvtvWMCK1ZFWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BBG2X/btsPIeVcYlH/qOQVI3QBvSvtvWMCK1ZFWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BBG2X/btsPIeVcYlH/qOQVI3QBvSvtvWMCK1ZFWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBBG2X%2FbtsPIeVcYlH%2FqOQVI3QBvSvtvWMCK1ZFWK%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;1026&quot; height=&quot;888&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;888&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;바로 지금껏 열심히 작성해온 나의 코드...! 이걸 기반으로 AI 가 코드를 생성할 때 참조할 컨텍스트를 최대한 많이 만들어볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적으로 시작해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 내가 사용중인 IDE 에서는 AI 가 코드 생성에 참조할 수 있는 지침으로 '커서 룰'을 작성할 수 있는 기능을 제공한다. 커서 룰은 사람이 직접 작성할 수도 있지만 AI 에게 작성하도록 시킬 수도 있다. 커서룰을 만드는 과정은 아래 영상의 내용을 참고했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=5rCk0tjkvNM&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=5rCk0tjkvNM&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;ASK: 이 프로젝트 구조를 자세히 파악해줘.&lt;br /&gt;코드 컨벤션, 스타일, 디자인 규칙을 모두 분석해서 자세히 설명해줘.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 1분 정도 시간이 지나자 분석이 완료되었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;1132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0dlK4/btsPF7iIX4m/cLkvwaKLir6l2PmRhxzgi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0dlK4/btsPF7iIX4m/cLkvwaKLir6l2PmRhxzgi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0dlK4/btsPF7iIX4m/cLkvwaKLir6l2PmRhxzgi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0dlK4%2FbtsPF7iIX4m%2FcLkvwaKLir6l2PmRhxzgi0%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;754&quot; height=&quot;670&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;1132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 룰 생성 요청해보자&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;/Generate Cursor Rules&lt;br /&gt;내용은 영어로 작성해. 대표적인 프롬프트 엔지니어링 기법을 적용해서 최적화해줘.&lt;br /&gt;반드시 지켜야할 규칙, 반드시 하지 말아야할 것들을 특히 강조해서 적어줘.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1185&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bexMS7/btsPF7JPiB3/4aKc3AYfHSzLAhVn5o53M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bexMS7/btsPF7JPiB3/4aKc3AYfHSzLAhVn5o53M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bexMS7/btsPF7JPiB3/4aKc3AYfHSzLAhVn5o53M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbexMS7%2FbtsPF7JPiB3%2F4aKc3AYfHSzLAhVn5o53M1%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;1185&quot; height=&quot;836&quot; data-origin-width=&quot;1185&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;룰이 전부 영어로 되어 있으니 읽기 힘들다. 한글보다 영어가 매 요청에 사용하는 토큰의 개수가 적기 때문에 커서 룰은 영어로 작성하는 것이 좋지만, 그렇다고 읽기 힘든 걸 참을 필요는 없다. 한글 번역판도 같이 요청하도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;만든 커서 룰을 한글로 번역한 문서를 적절한 곳에 생성해줘. 커서 룰로 참조되지 않게 .cursorignore 의 항목에 해당 한글 문서를 추가해줘&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VC1Tq/btsPHfmHy1K/us1OiDA3wSOQcahqkfUmK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VC1Tq/btsPHfmHy1K/us1OiDA3wSOQcahqkfUmK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VC1Tq/btsPHfmHy1K/us1OiDA3wSOQcahqkfUmK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVC1Tq%2FbtsPHfmHy1K%2Fus1OiDA3wSOQcahqkfUmK1%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;1197&quot; height=&quot;786&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;786&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 검토해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이 프로젝트에서 Modal 과 같은 Overlay 형태의 UI 는 overlay-kit 이라는 라이브러리를 통해 개발하고 있다. 해당 컨벤션이 빠진 것 같으니 이를 추가하도록 요청해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;나는 이 프로젝트에서 Modal 과 같은 Overlay 형태의 UI 는 overlay-kit 이라는 라이브러리를 통해 개발하고 있어. overlay-kit 라이브러리가 사용되는 코드를 확인해서 커서 룰을 추가해줘. 한글 번역본에 해당 내용을 반영하는 것도 잊지마&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;827&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m1DsJ/btsPHEtkUud/vu9xxQGZAHYblHGCgzrV61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m1DsJ/btsPHEtkUud/vu9xxQGZAHYblHGCgzrV61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m1DsJ/btsPHEtkUud/vu9xxQGZAHYblHGCgzrV61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm1DsJ%2FbtsPHEtkUud%2Fvu9xxQGZAHYblHGCgzrV61%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;808&quot; height=&quot;827&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;827&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋다. 이제 다음 시간에는 직접 코드를 생성해보면서 해당 컨텍스트가 제대로 들어갔는지 확인하고, 튜닝해보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>기타</category>
      <author>DogGae</author>
      <guid isPermaLink="true">https://codingmoondoll.tistory.com/139</guid>
      <comments>https://codingmoondoll.tistory.com/entry/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BD%94%EB%93%9C%EB%B2%A0%EC%9D%B4%EC%8A%A4%EC%97%90-%EC%BB%A4%EC%84%9C-%EB%A3%B0-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0#entry139comment</comments>
      <pubDate>Tue, 5 Aug 2025 22:16:57 +0900</pubDate>
    </item>
    <item>
      <title>자꾸 돈 쓰는 우리 부부 자제시키려고 가계부 개발한 후기</title>
      <link>https://codingmoondoll.tistory.com/entry/%EC%9E%90%EA%BE%B8-%EB%8F%88-%EC%93%B0%EB%8A%94-%EC%99%80%EC%9D%B4%ED%94%84-%EC%9E%90%EC%A0%9C%EC%8B%9C%ED%82%A4%EB%A0%A4%EA%B3%A0-%EA%B0%80%EA%B3%84%EB%B6%80-%EA%B0%9C%EB%B0%9C%ED%95%9C-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;나는 4월 중순에 결혼했고, 결혼 직후 신혼여행을 다녀왔다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 감사한 분들께 마저 인사를 돌리고 나서, 모든 것이 드디어 마무리 되었다고 생각할 때 쯤.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/88U2p/btsPqkn4gI2/kVoVEGpUEUkWdgvZOhU8fk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/88U2p/btsPqkn4gI2/kVoVEGpUEUkWdgvZOhU8fk/img.jpg&quot; data-alt=&quot;사실 이 정도는 아니긴한데&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/88U2p/btsPqkn4gI2/kVoVEGpUEUkWdgvZOhU8fk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F88U2p%2FbtsPqkn4gI2%2FkVoVEGpUEUkWdgvZOhU8fk%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;563&quot; height=&quot;562&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사실 이 정도는 아니긴한데&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;통장잔고가 거의 비어 있다는 것을 깨달았다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원래 계산대로라면 그래도 천만원에 가까운 돈이 남아 있었어야 했는데, &lt;span style=&quot;text-align: start;&quot;&gt;어째서일까?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내가 애초에 계산을 잘못했나? 최근 너무 많은 일들이 지나갔기에 슈퍼 회피형인 나는 솔직히 그려러니 하고 넘어가고 싶었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 우리 와이프는 나와는 다른 인종이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;몇달간의 내역을 뒤진 끝에 와이프는 우리가 신혼여행에 원래 예산보다 900만원을 더 썼다는 결론에 도달한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;신혼여행을 다녀온 후에도, 코스트코에 한번 가서 40만원을 쓰는 등 우리 부부는 돈을 펑펑 뿌리고 다녔다는 것이 밝혀졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이대로는 안된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'이제 돈을 아껴써야지!' 같은 다짐은 하나마나고, 뭔가 더 시스템적인 해결책이 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 작성하기로 한 가계부.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPf9aA/btsPpQU65YO/9BzRVCwo5kHFDBU5KrqIK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPf9aA/btsPpQU65YO/9BzRVCwo5kHFDBU5KrqIK1/img.png&quot; data-alt=&quot;예전엔 필요없다고 생각했었는데&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPf9aA/btsPpQU65YO/9BzRVCwo5kHFDBU5KrqIK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPf9aA%2FbtsPpQU65YO%2F9BzRVCwo5kHFDBU5KrqIK1%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;685&quot; height=&quot;385&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예전엔 필요없다고 생각했었는데&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;종이 가계부를 쓸 생각은 전혀 없고, 앱을 통해 가계부 관리를 하려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가계부 어플들은 많지만, 자산과 소득을 합친 부부가 함께 소득과 지출을 관리할 수 있는 서비스는 발견하기 어려웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 적합해보이는 서비스가 뱅크샐러드 앱의 '우리집 돈 관리' 였지만&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8sxOw/btsPqLMguk6/LRLwR4976pH8n33BnPecAk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8sxOw/btsPqLMguk6/LRLwR4976pH8n33BnPecAk/img.jpg&quot; data-alt=&quot;왜 웃고 있는건데 빡치게&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8sxOw/btsPqLMguk6/LRLwR4976pH8n33BnPecAk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8sxOw%2FbtsPqLMguk6%2FLRLwR4976pH8n33BnPecAk%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;322&quot; height=&quot;698&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2340&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;왜 웃고 있는건데 빡치게&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;7월 31일에 서비스 종료 예정이라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 외에 쓸만한 가계부 어플을 발견하지는 못해서 결국...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그냥 우리가 만들기로 했다. (솔직히 다 변명이고 그냥 만들고 싶었음)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF2oTu/btsPqx1MQrc/5UAib1quLdGFo5gYhgJMW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF2oTu/btsPqx1MQrc/5UAib1quLdGFo5gYhgJMW1/img.png&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;1114&quot; data-is-animation=&quot;false&quot; width=&quot;250&quot; height=&quot;512&quot; style=&quot;width: 32.5789%; margin-right: 10px;&quot; data-widthpercent=&quot;33.35&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF2oTu/btsPqx1MQrc/5UAib1quLdGFo5gYhgJMW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF2oTu%2FbtsPqx1MQrc%2F5UAib1quLdGFo5gYhgJMW1%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;544&quot; height=&quot;1114&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBm909/btsPpvcETK9/7vCD09zQDWqKBHjNSmsXPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBm909/btsPpvcETK9/7vCD09zQDWqKBHjNSmsXPK/img.png&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;1196&quot; data-is-animation=&quot;false&quot; width=&quot;268&quot; height=&quot;545&quot; style=&quot;width: 32.7997%; margin-right: 10px;&quot; data-widthpercent=&quot;33.58&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBm909/btsPpvcETK9/7vCD09zQDWqKBHjNSmsXPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBm909%2FbtsPpvcETK9%2F7vCD09zQDWqKBHjNSmsXPK%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;588&quot; height=&quot;1196&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btEVL9/btsPqQ7O4tL/ObJAkPDwkBA8zTPxEfEat0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btEVL9/btsPqQ7O4tL/ObJAkPDwkBA8zTPxEfEat0/img.png&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;1194&quot; style=&quot;width: 32.2958%;&quot; data-widthpercent=&quot;33.07&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btEVL9/btsPqQ7O4tL/ObJAkPDwkBA8zTPxEfEat0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtEVL9%2FbtsPqQ7O4tL%2FObJAkPDwkBA8zTPxEfEat0%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;578&quot; height=&quot;1194&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;만드는데 생각보다 오래걸렸던 갤러리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;운 좋게도 나는 취미의 일환으로 만들어둔 '갤러리' 웹앱이 있었고, 이 코드베이스 위에 가계부 서비스를 만들기로 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;금융분야에서 근무하고 있는 와이프를 기획자로 강제 임명하고 기능명세서를 뽑아오라 닥달했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2528&quot; data-origin-height=&quot;1248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r8bZk/btsPqaMxZzz/KIo7S5qUPTDFVyJAb1Z3fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r8bZk/btsPqaMxZzz/KIo7S5qUPTDFVyJAb1Z3fk/img.png&quot; data-alt=&quot;캡처된 건 일부고 아래 더 있다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r8bZk/btsPqaMxZzz/KIo7S5qUPTDFVyJAb1Z3fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr8bZk%2FbtsPqaMxZzz%2FKIo7S5qUPTDFVyJAb1Z3fk%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;2528&quot; height=&quot;1248&quot; data-origin-width=&quot;2528&quot; data-origin-height=&quot;1248&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;캡처된 건 일부고 아래 더 있다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: center;&quot;&gt;시켜서 억지로 만든 것 치고는 괜찮은데...?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: center;&quot;&gt;적어준 모든 기능을 다 구현하는건 당장은 어렵고, 가장 기본적인 기능만을 추출해서 구현하기로 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;지출, 수입 요약&lt;/li&gt;
&lt;li&gt;지출&amp;nbsp;예산&amp;nbsp;설정&lt;/li&gt;
&lt;li&gt;수입 목표 설정&lt;/li&gt;
&lt;li&gt;지출,&amp;nbsp;수입&amp;nbsp;내역&amp;nbsp;기록&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위의 4가지 기본 기능만을 구현해서 일단 대강 모양새를 갖추었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGwuL8/btsPqxgsSEe/gDLNQzxkydsy5KwwRChOmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGwuL8/btsPqxgsSEe/gDLNQzxkydsy5KwwRChOmK/img.png&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;794&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.7668%; margin-right: 10px;&quot; data-widthpercent=&quot;33.55&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGwuL8/btsPqxgsSEe/gDLNQzxkydsy5KwwRChOmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGwuL8%2FbtsPqxgsSEe%2FgDLNQzxkydsy5KwwRChOmK%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;389&quot; height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3c15I/btsPpvDMVeq/XTWiEYkwAfADjRvmvem6y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3c15I/btsPpvDMVeq/XTWiEYkwAfADjRvmvem6y0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;798&quot; data-filename=&quot;스크린샷 2025-07-20 오전 1.32.45.png&quot; style=&quot;width: 32.6026%; margin-right: 10px;&quot; data-widthpercent=&quot;33.38&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3c15I/btsPpvDMVeq/XTWiEYkwAfADjRvmvem6y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3c15I%2FbtsPpvDMVeq%2FXTWiEYkwAfADjRvmvem6y0%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;389&quot; height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnFtFW/btsPqAEeiAu/iEoqt1NQDjNb00PmPQB9ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnFtFW/btsPqAEeiAu/iEoqt1NQDjNb00PmPQB9ok/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;795&quot; data-filename=&quot;스크린샷 2025-07-20 오전 1.33.15.png&quot; data-widthpercent=&quot;33.07&quot; style=&quot;width: 32.305%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnFtFW/btsPqAEeiAu/iEoqt1NQDjNb00PmPQB9ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnFtFW%2FbtsPqAEeiAu%2FiEoqt1NQDjNb00PmPQB9ok%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;384&quot; height=&quot;795&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;디자이너가 따로 없어서인지 아직까지는 그렇게 막 있어보이는 느낌은 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지금은 모든 기록과 예산 설정 등을 수기로 해야하지만, 좀 더 간편하게 가계부를 작성할 수 있게 하기 위해서 다음의 추가 개발 단계를 거칠 생각을 하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마이데이터 API 연동 (가능하다면?)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;LLM 서비스 기반 UI/UX 구현 - 채팅 혹은 음성으로 가계부 작성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스크린샷이나 영수증 사진 등을 통해 가계부를 작성할 수 있는 OCR 기능 구현&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;609&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6wwFT/btsPre1J1Ci/UiHwCoHhfx9rHnU4Rza9x0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6wwFT/btsPre1J1Ci/UiHwCoHhfx9rHnU4Rza9x0/img.jpg&quot; data-alt=&quot;크오오오옷!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6wwFT/btsPre1J1Ci/UiHwCoHhfx9rHnU4Rza9x0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6wwFT%2FbtsPre1J1Ci%2FUiHwCoHhfx9rHnU4Rza9x0%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;1125&quot; height=&quot;609&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;609&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;크오오오옷!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;솔직히 말해서 어디까지 진행해볼 수 있을지 모르겠지만, 차근차근 해나갈 생각이다. 위 작업을 진행하면서 몇가지 느낀점을 적어보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기획은 온전히 와이프의 머릿속에서 나왔지만, 그것을 실제 서비스로 옮기기 위해서는 개발자인 나와 정말 많은 대화를 해야했다. 와이프가 생각한 예산과 지출, 목표라는 개념의 상관관계를 데이터와 UI 로 풀어내는 것이 쉽지는 않았다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빠른 작업을 위해 마크업과 스타일링에는 AI 의 도움을 받았다. UI 상으로는 꽤 괜찮은 결과물이 나왔지만 코드 퀄리티 자체는 처참했기 때문에 정말 많이 리팩토링하고 수정해야 했다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하루에 5분씩이라도 개발하려는 습관을 들이지 않으면 생각 이상으로 잘 진행되지 않는다. AI 와 기존 개발 경험만을 믿고 하루 혹은 이틀만에 끝낼 수 있다고 생각한건 오만이었다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>기타</category>
      <author>DogGae</author>
      <guid isPermaLink="true">https://codingmoondoll.tistory.com/138</guid>
      <comments>https://codingmoondoll.tistory.com/entry/%EC%9E%90%EA%BE%B8-%EB%8F%88-%EC%93%B0%EB%8A%94-%EC%99%80%EC%9D%B4%ED%94%84-%EC%9E%90%EC%A0%9C%EC%8B%9C%ED%82%A4%EB%A0%A4%EA%B3%A0-%EA%B0%80%EA%B3%84%EB%B6%80-%EA%B0%9C%EB%B0%9C%ED%95%9C-%ED%9B%84%EA%B8%B0#entry138comment</comments>
      <pubDate>Sat, 19 Jul 2025 20:04:30 +0900</pubDate>
    </item>
    <item>
      <title>리액트 커스텀 훅 사용 팁</title>
      <link>https://codingmoondoll.tistory.com/entry/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%9B%85-%EC%82%AC%EC%9A%A9-%ED%8C%81</link>
      <description>&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://www.youtube.com/watch?v=J-g9ZJha8FE&quot;&gt;https://www.youtube.com/watch?v=J-g9ZJha8FE&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useRef 를 사용하여 디펜던시 문제 해결하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbFtbY/btrmpwSz15Y/t4dCBj9npksItETkq6bYRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbFtbY/btrmpwSz15Y/t4dCBj9npksItETkq6bYRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbFtbY/btrmpwSz15Y/t4dCBj9npksItETkq6bYRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbFtbY%2FbtrmpwSz15Y%2Ft4dCBj9npksItETkq6bYRk%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;584&quot; height=&quot;220&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;220&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;위와 같은 코드는 리액트 코드를 작성하면서 심심치 않게 볼 수 있습니다. useEffect 안에서 사용하는 함수가 외부 함수라서 이를 useEffect 에 넣어버리고 의존성에 등록해두지 않으면 linter 가 이를 지적할 것입니다. 해당 함수가 업데이트 될 필요가 있다고 합시다. 이때 useEffect 안에서 상태를 바꾸는 로직이 있다면 리렌더링이 수행될 것이고, 해당 외부함수가 useCallback 처리 되어 있지 않으면 매번 아예 새로 생성될 것이기 때문에 무한 렌더링의 수렁에 빠지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgp9Nm/btrmpxqndjo/4koD5IMgF9652ezI3bmUX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgp9Nm/btrmpxqndjo/4koD5IMgF9652ezI3bmUX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgp9Nm/btrmpxqndjo/4koD5IMgF9652ezI3bmUX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgp9Nm%2Fbtrmpxqndjo%2F4koD5IMgF9652ezI3bmUX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;488&quot; height=&quot;210&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;210&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;하지만 그렇다고 useCallback 처리를 하는 것도 한계가 있습니다. 분명 해당 함수는 그 내용이 업데이트 되어야 할 필요가 있습니다. 하지만 useCallback 을 쓰더라도 만일 해당 함수 안에서 다른 함수를 가져다 쓰고 있다면, 그 가져다 쓰는 함수에 대해서도 useCallback을 써야 하는 악순환이 일어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kMGas/btrmxHyezvc/R2JRqcPn7jMk994S8LdoDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kMGas/btrmxHyezvc/R2JRqcPn7jMk994S8LdoDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kMGas/btrmxHyezvc/R2JRqcPn7jMk994S8LdoDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkMGas%2FbtrmxHyezvc%2FR2JRqcPn7jMk994S8LdoDk%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;497&quot; height=&quot;311&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;311&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;이때 사용할 수 있는 패턴이 useRef 를 이용하는 것입니다. useRef 로 생성한 ref 객체는 컴포넌트가 리렌더링되더라도 다시 생성되지 않고 기존의 것을 사용합니다. 그리고 ref.current 이런 식으로 'current'라는 속성을 통해 생성한 함수가 업데이트 될 때마다 그 업데이트 된 함수를 가져다 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpy2Bh/btrmtfpfAyH/s730x4AY5vMx8f3XdFb750/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpy2Bh/btrmtfpfAyH/s730x4AY5vMx8f3XdFb750/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpy2Bh/btrmtfpfAyH/s730x4AY5vMx8f3XdFb750/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbpy2Bh%2FbtrmtfpfAyH%2Fs730x4AY5vMx8f3XdFb750%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;467&quot; height=&quot;328&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;328&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;useReducer 를 이용하여 더 안정적으로 상태 변경하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많이들 useState 훅이 반환하는 setState 를 활용한 상태 변경이 더 단순하고 이에 더 익숙해져 있을 것이라 생각하지만, setState는 그 자체로는 매우 위험할 수도 있는 메서드입니다. 어떤 인자가 들어오든, 별도의 예외처리 없이 그 값 그대로 상태를 업데이트 해버릴 수 있으니까요. 물론 커스텀 훅등을 이용해서 상태변경 인터페이스를 구축하고 setState 가 동작하도록 만들 수 있지만, 문제는 이를 강제할 수 없다는 점에 있습니다.&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;useReducer 를 쓰게 되면 강제적으로 상태를 변경하는 데 쓰일 인터페이스로서의 리듀서 함수를 만들지 않을 수 없게 됩니다. 여기서도 set 동작을 그대로 수행할 수도 있지만, 상태변경 인터페이스를 만드는 것을 강제한다는 점에서 setState 보다 안전한 선택이 될 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Multi-Contexts 를 활용하여 전역 상태로 인한 리렌더링 줄이기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트의 createContext 훅을 활용하여 컨텍스트 객체를 만들고 Provider 를 통해 상태를 넘겨주면 해당 상태는 변경과 동시에 해당 상태를 사용하는 모든 곳에서 리렌더링이 일어나게 만드는 전역 상태가 됩니다. 문제는 단순히 setState 나 dispatch 만 쓰는 컴포넌트 상에서도 관련 상태가 변화하면 리렌더링이 수행된다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOfGwt/btrmtfvZkic/uZCV8B3NpyQ2sHDPHJgm11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOfGwt/btrmtfvZkic/uZCV8B3NpyQ2sHDPHJgm11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOfGwt/btrmtfvZkic/uZCV8B3NpyQ2sHDPHJgm11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOfGwt%2FbtrmtfvZkic%2FuZCV8B3NpyQ2sHDPHJgm11%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;762&quot; height=&quot;256&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(출처 : &lt;a href=&quot;https://www.youtube.com/watch?v=J-g9ZJha8FE&quot;&gt;https://www.youtube.com/watch?v=J-g9ZJha8FE&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 막기 위해 state 와 상태변경 메서드를 다른 컨텍스트의 Provider 로 넘겨주는 방법을 사용할 수 있습니다. 리덕스가 이러한 방법을 채택하고 있고, 상태변경 메서드 자체는 그 값이 바뀔 일이 없으니 그것만 사용하는 컴포넌트 상에서는 전역 상태 변경에 따른 리렌더링이 일어나지 않게 됩니다.&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;여러 전역 상태를 하나의 'store'로 관리하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createContext 를 여러번 호출하여 컨텍스트를 여러개 만들고, 프로바이더도 여러개 사용하다 보면 코드가 자칫 지저분해지기 쉽습니다. 게다가 매번 useContext 를 쓸 때마다 컨텍스트 객체를 불러와서 사용하는 것은 귀찮고 코드 양을 늘리게 되죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caAsbR/btrmwQvKe9q/OSM7B3eieJtkTrvOoZrvI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caAsbR/btrmwQvKe9q/OSM7B3eieJtkTrvOoZrvI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caAsbR/btrmwQvKe9q/OSM7B3eieJtkTrvOoZrvI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaAsbR%2FbtrmwQvKe9q%2FOSM7B3eieJtkTrvOoZrvI1%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;619&quot; height=&quot;406&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;406&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;같이 사용되어야 할 context 끼리 묶어서 'store' 를 만드는 것이 좋은 대안이 될 수 있습니다. Provider, state 를 불러오는 훅, dispatch를 불러오는 훅을 한번에 리턴하는 store 훅을 만들면 더 깔끔한 코드로 전역 상태를 이용할 수 있습니다.&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;로컬스토리지로 Persistency 관리하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역 상태들은 매우 중요한 데이터들이지만 새로고침 하면 이들이 완전히 다 날아가버리는 문제점이 있습니다. 전역 상태를 생성, 변경할 때마다 로컬 스토리지를 활용하면 간단하게 Persistency 관리도 수행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 프론트엔드/리액트</category>
      <author>DogGae</author>
      <guid isPermaLink="true">https://codingmoondoll.tistory.com/137</guid>
      <comments>https://codingmoondoll.tistory.com/entry/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%9B%85-%EC%82%AC%EC%9A%A9-%ED%8C%81#entry137comment</comments>
      <pubDate>Tue, 30 Nov 2021 01:34:10 +0900</pubDate>
    </item>
    <item>
      <title>이벤트 루프</title>
      <link>https://codingmoondoll.tistory.com/entry/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84</link>
      <description>&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://www.youtube.com/watch?v=cCOL7MC4Pl0&quot;&gt;https://www.youtube.com/watch?v=cCOL7MC4Pl0&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이벤트 루프란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트의 단일 스레드 환경에 맞춰서 비동기 처리와 이벤트 핸들링을 수행하는데 사용되는 제어방식입니다. 간단히 설명하자면, 끊임없이 콜스택의 상태를 감지하며 태스크 큐의 콜백을 콜스택에 밀어넣는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 자바스크립트는 단일스레드 방식을 택한 것일까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 루프의 배경에는 흔히 자바스크립트가 '단일 스레드로만 동작한다'는 사실이 깔려 있습니다. 하지만 다른 언어들은 스레드를 나누는 일이 흔한데, 왜 하필 자바스크립트는 단일 스레드를 고집하는 것일까요? 이는 자바스크립트의 주요 실행 환경이 브라우저라는 점에 기반합니다. 이용자가 발생시킨 이벤트와 서버 요청에 대한 응답이 언제 처리되어야 하는 지를 예측할 수 없기 때문에 스레드를 나누어 연산 능력을 떨어뜨리기보다는 하나의 스레드로 모든 처리를 빠르게 수행하는 것을 자바스크립트는 선택한 것입니다. 그리고 이를 이벤트 루프가 보조하여 비동기 처리와 이벤트 핸들링이 지금처럼 수행될 수 있는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 이벤트 루프의 방식이 필요했던 것일까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 잘 생각해보면 브라우저는 굳이 이벤트 루프 방식을 택할 이유가 없어보이기도 합니다. 비동기적으로 실행시켜야 할 함수가 필요하면 그냥 여러 일을 동시에 왔다 갔다하면 그만이고 이벤트 핸들링은 그냥 이벤트가 발생하는 대로 바로 핸들러를 콜스택에 넣어버리면 되는 일 아닐까요? 굳이 콜백 큐니 뭐니 하는 것들이 필요한 이유가 뭘까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;병렬 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬처리가 선택될 수 없는 이유는 분명합니다. 처리에 걸리는 시간에 따라서 나중에 추가된 콜백이 이전에 추가된 콜백보다 먼저 수행될 수 있기 때문입니다. 각 콜백은 서로 먼저 수행되기 위한 '경쟁상태'에 놓이게 되고 이러면 개발자는 정확히 어떠한 순서로 렌더링이 수행될지 예측하기 어려워집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;only 콜스택&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트와 비동기 처리가 오직 콜스택을 통해서만 일어나게 된다면 '중요한 일을 먼저 처리'하는 것이 불가능해집니다. 예를 들어 서버로부터 응답이 도착해서 그 응답 결과를 화면상에 반영해야 하는 경우, 만일 그 전에 너무나 많은 스택들이 쌓여 있을 때 그 일은 한참 뒤로 밀리고 말 것입니다. 이렇듯 단순히 콜스택만 있다면 더 중요한 일에 우선순위를 매기는 작업이 불가능해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이벤트 루프는 어떤 식으로 동작할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TyKd1/btrle6NiDUp/rfuq1Sz6YB9fPA0kXLAvsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TyKd1/btrle6NiDUp/rfuq1Sz6YB9fPA0kXLAvsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TyKd1/btrle6NiDUp/rfuq1Sz6YB9fPA0kXLAvsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTyKd1%2Fbtrle6NiDUp%2Frfuq1Sz6YB9fPA0kXLAvsk%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;795&quot; height=&quot;397&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(출처 : &lt;a href=&quot;https://www.youtube.com/watch?v=cCOL7MC4Pl0&quot;&gt;https://www.youtube.com/watch?v=cCOL7MC4Pl0&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림을 통해 이벤트 루프의 동작을 한눈에 볼 수 있습니다. 태스크 큐에 콜백이 담겼지만 콜스택은 비어있다면 이벤트루프는 이를 감지하여 해당 콜백을 처리합니다. 만일 그 콜백이 렌더링 프로세스를 트리거한다면 렌더링이 수행됩니다. 중요한 것은 이 한번의 사이클이 콜백 하나당 하나씩 수행된다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 말해서, 이벤트 루프는 태스크 큐의 종류를 3가지로 나눔으로써 우선순위를 구현합니다. 1순위는 프라미스 콜백이 담기는 마이크로태스크큐, 2순위는 requestAnimationFrame 함수의 콜백이 담기는 애니메이션 콜백큐, 3순위는 이벤트 핸들러와 setTimeout, setInterval 등의 webAPI 를 통해 넘겨진 콜백이 담기는 매크로태스크큐입니다. 이벤트 루프는 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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 프론트엔드 깊게 이해하기/자바스크립트 이해하기</category>
      <author>DogGae</author>
      <guid isPermaLink="true">https://codingmoondoll.tistory.com/136</guid>
      <comments>https://codingmoondoll.tistory.com/entry/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84#entry136comment</comments>
      <pubDate>Tue, 16 Nov 2021 21:59:25 +0900</pubDate>
    </item>
    <item>
      <title>나만의 UI / 상태관리 라이브러리 (chreact.js)</title>
      <link>https://codingmoondoll.tistory.com/entry/%EB%82%98%EB%A7%8C%EC%9D%98-UI-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-CHReactjs</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;컨셉트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 사용법은 리액트와 유사하게 구성하였습니다. 기존의 JSX 문법이 아닌 독자적인 문법을 한번 고안해보고 싶어서 함수 컴포넌트가 객체가 아닌 제가 template 이라고 명명한 특수한 형식의 문자열을 return 하도록 지정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636128723943&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import useState from &quot;../lib/hooks/useState&quot;;
import Counter from &quot;./Counter&quot;;

const App = () =&amp;gt; {
  const [count, setCount] = useState(0);

  const onIncrease = () =&amp;gt; {
    setCount(count + 1);
  };

  const onDecrease = () =&amp;gt; {
    setCount(count - 1);
  };

  const onReset = () =&amp;gt; {
    setCount(0);
  };

  return `
    div.container
      (1)span.count
        (2)text: ${count}
      (1)${Counter({
        onIncrease,
        onDecrease,
        onReset,
      })}
  `;
};

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Template 문법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Template 의 형식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;template 은 간단히 말해 html 을 대체하는 문자열이라고 부를 수 있겠습니다. 이 문자열은 크게 다음의 형식을 갖습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636128759739&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;div#purchase.primary-button
  @data-test-id: testButton
  $background-color: red
  $color: white&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해를 돕기 위해 문자열의 중간중간을 개행으로 구분지어놓았지만, 개행이나 공백은 넣어도 넣지 않아도 동일하게 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;'#' 은 id 를 지정할 때 사용하는 기호입니다. id 는 한 template 이 여러개를 가질 수 없도록 자체적으로 제한이 걸려 있습니다.&lt;/li&gt;
&lt;li&gt;'.' 은 class 를 지정할 때 사용하는 기호입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.class1.class2 형식으로 여러개의 클래스를 한번에 지정하는 것도 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;'@' 는 attribute 를 지정할 때 사용하는 기호입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@data-test-id: test1 @data-user-id: user1 형식으로 여러 개의 attribute 를 지정하는 것도 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;'$' 은 style 을 지정할 때 사용하는 기호입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$background-color: red $color: white 형식으로 여러 개의 style 을 지정하는 것 또한 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Template Nesting&lt;/h3&gt;
&lt;pre id=&quot;code_1636128821589&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;div#purchase.primary-button
  @data-test-id: testButton
  $background-color: red
  $color: white
  (1)span
    (2)text: 테스트 버튼
  (1)${ChildCompoenent(props)}&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;하나의 Template 이 대변하는 것은 하나의 HTML 엘리먼트입니다. 여기서 해당 Element 가 가지는 children 을 지정하기 위해 마찬가지로 Template 을 사용할 수도 있고, 최종적으로 Template 을 리턴하는 함수 컴포넌트를 실행시킨 결과물을 넣을 수 있습니다. children 을 지정할 때는 해당 children 이 루트 Element 로부터 어느 정도의 depth 를 가지는 지를 (숫자) 형식으로 지정해주어야 합니다.&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;만일 HTML 요소가 아니라 단순 텍스트 노드를 child 로 지정하고 싶다면 맨 앞에 'test:' 를 지정해주어야 합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636128841850&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import &quot;./index.css&quot;;

import App from &quot;./components/App&quot;;
import initRenderer from &quot;./lib/render&quot;;

const $root = document.querySelector(&quot;#root&quot;);

initRenderer($root, App);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 초기화 방법은 리액트와 완전히 유사합니다. 동적으로 생성되는 HTMLElement 가 붙여질 root 엘리먼트 노드와 전체 Template 문자열을 반환할 루트 함수 컴포넌트를 Renderer 에 넘기면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Store&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적으로 구조를 설명하기 전에, 라이브러리를 import 하는 즉시 생성되는 store 인스턴스에 대해 알아둘 필요가 있습니다. react 는 ReactDom.render() 를 하나의 루트 요소에만 수행할 수 있는 것이 아니라 여러 루트 요소에 적용시킬 수 있습니다. 즉 하나의 어플리케이션이 여러개의 독립적인 VDom 을 가질 수 있고 서로가 간섭하지 않을 수 있게 만들 수 있다는 이야기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636128866443&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const $root1 = document.querySelector(&quot;#root1&quot;);
const $root2 = document.querySelector(&quot;#root2&quot;);

initRenderer($root1, App1);
initRenderer($root2, App2);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 여러개의 root 요소를 지정하는 방식은 리액트를 활용하는데 있어서 거의 시도되지 않는 방식이지만, 그래도 리액트의 이 같은 유연함을 흉내내보고자 했습니다. initRender 함수가 수행되면 Store 에는 'VStorage' 라는 이름의 인스턴스가 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ces4V1/btrj2dtGDwV/EmufdUIbSF7fE6yHp0CmX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ces4V1/btrj2dtGDwV/EmufdUIbSF7fE6yHp0CmX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ces4V1/btrj2dtGDwV/EmufdUIbSF7fE6yHp0CmX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fces4V1%2Fbtrj2dtGDwV%2FEmufdUIbSF7fE6yHp0CmX1%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;438&quot; height=&quot;358&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VStorage&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;initRender 함수가 수행되면 Store 에는 'VStorage' 라는 이름의 인스턴스가 생성됩니다. 이 VStorage 에는 VDom 을 비롯해서 상태관리와 동적 렌더링을 수행하기 위한 모든 객체가 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMeMZF/btrj4XbXfHu/cxRez2t9F0IsfAumtuvYmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMeMZF/btrj4XbXfHu/cxRez2t9F0IsfAumtuvYmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMeMZF/btrj4XbXfHu/cxRez2t9F0IsfAumtuvYmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMeMZF%2Fbtrj4XbXfHu%2FcxRez2t9F0IsfAumtuvYmK%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;479&quot; height=&quot;520&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;stateStorage&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4TMTY/btrj2fZr8FB/r4rejkzDyn72oucTBghn0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4TMTY/btrj2fZr8FB/r4rejkzDyn72oucTBghn0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4TMTY/btrj2fZr8FB/r4rejkzDyn72oucTBghn0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4TMTY%2Fbtrj2fZr8FB%2Fr4rejkzDyn72oucTBghn0K%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;836&quot; height=&quot;478&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;478&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;stateStorage 는 말 그대로 각 컴포넌트들이 지닌 상태들이 저장되는 객체입니다. 하나의 컴포넌트 안의 상태들은 하나의 배열에 담깁니다. 그리고 해당 함수 컴포넌트가 실행되는 순서가 key 가 되어 stateStorage 객체 안에 담기게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;handlerStorage&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;517&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uBY0h/btrj2d8mQTU/JUkTaxBAT6QXsflLOe6JhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uBY0h/btrj2d8mQTU/JUkTaxBAT6QXsflLOe6JhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uBY0h/btrj2d8mQTU/JUkTaxBAT6QXsflLOe6JhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuBY0h%2Fbtrj2d8mQTU%2FJUkTaxBAT6QXsflLOe6JhK%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;956&quot; height=&quot;517&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;517&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;handlerStorage 는 각 컴포넌트에서 바인딩 시킨 이벤트 핸들러 함수들이 담기는 객체입니다. 이때 해당 이벤트 핸들러의 이벤트 유형 (ex: click, submit...ect) 이 각각의 핸들러들을 분류하는 key 가 되며, 핸들러 함수들은 같은 이벤트 유형끼리 배열에 담겨서 key 와 매칭됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bksm1q/btrj1ZCq9Dq/ubRzlMTkO0ki8UF2EVCxz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bksm1q/btrj1ZCq9Dq/ubRzlMTkO0ki8UF2EVCxz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bksm1q/btrj1ZCq9Dq/ubRzlMTkO0ki8UF2EVCxz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbksm1q%2Fbtrj1ZCq9Dq%2FubRzlMTkO0ki8UF2EVCxz1%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;622&quot; height=&quot;333&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;333&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;이때 각각의 handler 객체들은 이벤트 위임에 사용될 Template 문자열과(위에서 언급한 Template 문자열과 동일) 실제 핸들링에 사용될 콜백 함수가 담깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTMLElementStorage&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IlFo4/btrj6c00GhZ/LqhOLTqvGy1oIzLmYniIo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IlFo4/btrj6c00GhZ/LqhOLTqvGy1oIzLmYniIo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IlFo4/btrj6c00GhZ/LqhOLTqvGy1oIzLmYniIo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIlFo4%2Fbtrj6c00GhZ%2FLqhOLTqvGy1oIzLmYniIo1%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;557&quot; height=&quot;480&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;480&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;HTMLElementStorage 은 동적 렌더링 과정에서 생성되는 실제 Dom 노드들이 담기는 객체입니다. 이 노드들은 자신이 생성된 순서를 key 로 하여 담기게 됩니다. 동적 렌더링 과정에서 생성되는 실제 Dom 노드에 대한 참조를 따로 저장하는 이유는 diffing 알고리즘을 통한 부분 렌더링을 위해서입니다. (아래 글에서 추가로 설명하겠습니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;VDom&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VDom 은 전체 Dom 트리를 '흉내내는' 객체입니다. Dom 이 여러 개의 노드로 이루어져 있듯, VDom 또한 여러 개의 VElement 로 이루어져 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP5maH/btrj3pz9w0v/bkkDQyHO4ODFIgZXQDQZ90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP5maH/btrj3pz9w0v/bkkDQyHO4ODFIgZXQDQZ90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP5maH/btrj3pz9w0v/bkkDQyHO4ODFIgZXQDQZ90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP5maH%2Fbtrj3pz9w0v%2FbkkDQyHO4ODFIgZXQDQZ90%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;480&quot; height=&quot;638&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;VElement&lt;/b&gt; 는 위와 같은 구조를 가집니다. 실제 Dom 노드의 태그 타입, id, class, attribute, style 을 대변하는 속성들로 이루어져 있으며 이 정보를 이용해서 실제 Dom 노드가 동적으로 생성됩니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxafxS/btrj2fLTxl8/lCLRPXAO2izJUCLBuo3KuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxafxS/btrj2fLTxl8/lCLRPXAO2izJUCLBuo3KuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxafxS/btrj2fLTxl8/lCLRPXAO2izJUCLBuo3KuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxafxS%2Fbtrj2fLTxl8%2FlCLRPXAO2izJUCLBuo3KuK%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;1292&quot; height=&quot;239&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;initRender&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용될 render 함수와 VStorage 를 store 에 등록하고 초기 렌더링을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체 코드 (참고용)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636129116059&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function initRenderer(
  $root: Element | null,
  rootComponent: Component
) {
  if (!$root) {
    throw Error(&quot;존재하지 않는 루트 태그입니다&quot;);
  }

  const vStorage = new VStorage($root);

  store.addRenderer($root.id, () =&amp;gt; render($root, rootComponent));
  store.addVStorage($root.id, vStorage);

  render($root, rootComponent);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;App() : 루트 컴포넌트 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 VDom 을 구성하는데 사용될 Template 문자열을 얻기 위해 루트 함수 컴포넌트를 실행시킵니다.&lt;/li&gt;
&lt;li&gt;이 과정에서 함수 내부의 useState 와 useHandler 함수가 호출되고 상태와 핸들러 바인딩이 수행됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(관련 설명은 상태 관리 부분에서 하겠습니다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체 코드 (참고용)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636129139324&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import useState from &quot;../lib/hooks/useState&quot;;
import Counter from &quot;./Counter&quot;;

const App = () =&amp;gt; {
  const [count, setCount] = useState(0);

  const onIncrease = () =&amp;gt; {
    setCount(count + 1);
  };

  const onDecrease = () =&amp;gt; {
    setCount(count - 1);
  };

  const onReset = () =&amp;gt; {
    setCount(0);
  };

  return `
    div.container
			$background-color:white
      (1)span.count
        (2)text: ${count}
      (1)${Counter({
        onIncrease,
        onDecrease,
        onReset,
      })}
  `;
};

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;createVElement&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;얻어낸 Template 문자열에 대한 파싱을 수행합니다. 1차 파싱 결과로 parentTemplate 과 childrenTemplates 를 얻어냅니다.&lt;/li&gt;
&lt;li&gt;parentTemplate 을 한번 더 파싱해서 id, class, style, attribute 정보를 얻어냅니다&lt;/li&gt;
&lt;li&gt;얻어낸 정보를 바탕으로 VElement 를 생성합니다.&lt;/li&gt;
&lt;li&gt;이제 얻어낸 childrenTemplates 배열을 대상으로 createVElement 를 재귀적으로 실행해 자식 VElement 들을 얻어냅니다.&lt;/li&gt;
&lt;li&gt;얻어낸 자식 VElement 배열을 부모 VElement 의 children 속성에 붙입니다.&lt;/li&gt;
&lt;li&gt;최종적으로 만들어낸 VElement 를 return 합니다&lt;/li&gt;
&lt;li&gt;이렇게 얻어낸 VElement 의 트리가 VDom 이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체 코드 (참고용)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636129162724&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const createVElement = (template: ReturnType&amp;lt;Component&amp;gt;, depth: number) =&amp;gt; {
  const [parentTemplate, childrenTemplates] = separateTemplate(template, depth);
  const noBlankParentTemplate = parentTemplate
    .replace(/\n/g, &quot;&quot;)
    .replace(/ /g, &quot;&quot;);

  if (getIdCounts(noBlankParentTemplate) &amp;gt; 1) {
    throw Error(&quot;id 가 여럿인 태그를 만들 수 없습니다&quot;);
  }

  const vElement: VElement = {
    type: &quot;div&quot;,
    attribute: {},
    style: {},
    children: [],
  };

  setVElementProperty(vElement, getVElementProperty(noBlankParentTemplate));
  setVElementChildren(vElement, childrenTemplates, depth);

  return vElement;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;vStorage.compare&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 VDom 객체와 현재 VDom 객체를 비교하면서 변경사항이 감지되면 해당 변경사항을 현존하는 실제 DOM 에 반영시킵니다. 만일 전체 VDom 구조가 이전과 달라진 것이 감지되면 비교를 멈추고 전달 받은 콜백 함수를 수행합니다. 해당 콜백 함수는 현재로서는 renderHTML 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체 코드 (참고용)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636129181084&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;compare(latestVDom: VElement, onFindDifference: Function) {
  if (!this.VDom) {
    throw Error(&quot;업데이트 될 VDOM 이 초기화되지 않았습니다.&quot;);
  }

  const distortionFound = this.updater(this.VDom, latestVDom);

  if (distortionFound === true) {
    onFindDifference();
  }
}&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;변경 사항을 감지하고 DOM 상에 반영하는 알고리즘은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;태그 자체가 변경됬음이 감지되면 VDom 구조가 달라졌다고 인식합니다.&lt;/li&gt;
&lt;li&gt;기존 요소의 자식이 Element 에서 text 노드로 바뀌었다면 VDom 구조가 달려졌다고 인식합니다.&lt;/li&gt;
&lt;li&gt;자식의 개수가 바뀌었다면 VDom 구조가 달라졌다고 인식합니다.&lt;/li&gt;
&lt;li&gt;id, class, attribute, style 상의 변경사항이 있으면 해당 VElement 와 대응하는 실제 노드에 변경사항을 반영합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이게 가능한 이유는 비교를 위해 VDom 안의 VElement 들을 조회하는 순서가 HTMLElementStorage 안에 실제 Dom 노드들이 담기는 순서와 일치하기 때문입니다. 굳이 querySelector 메서드를 사용하지 않아도 비교를 수행하는 함수는 지금이 몇번째 비교인지를 알고 있으면 변경사항을 반영해야할 Dom 노드를 곧바로 참조할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;renderHTML&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 렌더링이거나 VDom 구조 자체의 변경이 감지되었을 경우 수행됩니다. 전달 받은 VDom(VElement) 을 바탕으로 createHTMLElement 함수를 수행하여 루트 Dom 노드를 얻어낸 후 DOM 의 내용물을 해당 Dom 노드로 덮어버립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체 코드 (참고용)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636129204810&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const renderHTML = (
  $root: Element,
  rootElement: VElement,
  isInitial: boolean
) =&amp;gt; {
  const rootHTMLElement = createHTMLElement(rootElement);

  if (isInitial) {
    $root.appendChild(rootHTMLElement);
  } else {
    $root.replaceChild(rootHTMLElement, $root.firstChild!);
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;createHTMLElement&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;renderHTML 안에서 수행되는 함수입니다. 전달 받은 VElement 의 내용물을 바탕으로 실제 Dom 노드를 생성합니다. VElement.children 을 기반으로 children VElement 또한 HTMLElement 로 변환시켜 만들어낸 Dom 노드의 children 으로 붙입니다. 이때 HTMLElement 가 만들어지는 동시에, 해당 노드에 대한 참조가 HTMLElementStorage 안에 담깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체 코드 (참고용)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636129225026&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const createHTMLElement = (vElement: VElement): HTMLElement =&amp;gt; {
  const { type, attribute, style, children } = vElement;
  const vStorage = store.getCurrentVStorage();
  vStorage.increaseElementIndex();
  const elementIndex = vStorage.getElementIndex();
  const HTMLElement = document.createElement(type);

  setHTMLElementAttributes(HTMLElement, attribute);

  const elementStyles = getHTMLElementStyles(style);
  HTMLElement.setAttribute(&quot;style&quot;, elementStyles);

  setHTMLElementChildren(HTMLElement, children);

  vStorage.setElement(elementIndex, HTMLElement);

  return HTMLElement;
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;State&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636129241667&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [count, setCount] = useState(0);&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;state 는 useState 훅을 수행함과 동시에 생성됩니다. useState는 항상 실행 순서가 보장됩니다. 따라서 useState 는 자신이 몇번째 컴포넌트에서 수행되는 몇번째 useState 인지만 알 수 있으면 실제 state 를 담고 있는 객체와 자신이 return 해야 하는 state 의 위치를 모르더라도 기존의 state 를 참조할 수 있습니다.&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;setState 는 항상 자신이 실행될 때마다 기존의 state 값과 현재 state 값이 다른지를 검사합니다. 만일 기존의 state 값과 다르다는 것이 감지되면 위에서 설명한 렌더링 프로세스를 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체 코드 (참고용)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636129275337&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import store from &quot;../store&quot;;

const useState = &amp;lt;T&amp;gt;(defaultValue: T): [T, (state: T) =&amp;gt; void] =&amp;gt; {
  const vStorage = store.getCurrentVStorage();
  vStorage.increaseStateIndex();
  const stateIndex = vStorage.getStateIndex();

  if (!vStorage.getState(stateIndex)) {
    vStorage.setState(stateIndex, defaultValue);
  }

  const state = vStorage.getState(stateIndex);

  const setState = (newState: T) =&amp;gt; {
    if (state === newState) {
      return;
    }

    vStorage.setState(stateIndex, newState);
    const renderer = store.getCurrentRenderer();
    renderer();
  };

  return [state, setState];
};

export default useState;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Props&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props 는 그저 함수 컴포넌트가 매개변수로 받는 객체에 불과합니다. 내부에는 상위 컴포넌트로 받은 상태, 핸들링을 위한 함수, 그 외 여러 값이 있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Handler&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636129304677&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useHandler(&quot;click&quot;, { template: &quot;button.increase-button&quot;, callback: onIncrease, });&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;엘리먼트에 대한 이벤트 바인딩을 수행하기 위해서는 useHandler 훅을 사용합니다. 첫번째 인자로 어떤 이벤트에 대한 바인딩을 수행할 것인지를 넣고 두번째 인자로는 template 과 callback 을 담은 객체를 집어넣어 사용합니다.&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;template 은 프로퍼티 명이 template 이지만 정확히는 이벤트 위임을 위해 사용될 문자열입니다. 실제로 이벤트 바인딩을 수행할 대상 엘리먼트를 나타내는 Template 전체를 넣어도 되지만 해당 엘리먼트를 특정할 수만 있다면 그 일부만을 집어넣는 것 도한 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 특성을 이용해서 여러 요소에 대한 이벤트 바인딩을 한번에 수행할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636129318076&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;template: &quot;button.increase-button&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 넘겨진 template 값이 위와 같다면 increase-button 이라는 class 를 지니고 있는 모든 button 요소에서 발생한 click 이벤트에 대한 callback 함수 호출이 수행될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체 코드 (참고용)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636129340048&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { DomEvent, Handler } from &quot;../@types/types&quot;;
import store from &quot;../store&quot;;

const useHandler = (
  event: DomEvent,
  handler: Handler,
  shouldUpdate: boolean = true
) =&amp;gt; {
  const vStorage = store.getCurrentVStorage();
  const prevHandler = vStorage
    .getHandlers(event)
    ?.find((prevHandler) =&amp;gt; prevHandler.template === handler.template);

  if (!shouldUpdate) {
    prevHandler ?? vStorage.setHandler(event, handler);
    return;
  }

  vStorage.setHandler(event, handler);
};

export default useHandler;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GlobalEventBinding&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useHandler 를 통한 이벤트 바인딩이 가능한 이유는 root 엘리먼트에 이미 수행된 GlobalEventBinding 때문입니다. initRender 가 수행됨과 동시에, initRender 가 넘겨받은 root 엘리먼트에는 click, submit, hover, mouseover, keyup 등 모든 이벤트 유형에 대한 이벤트 바인딩이 수행됩니다. 이로 인해 addEventListener 메서드가 수행되는 시점은 전체를 통틀어 VStorage 가 초기화되는 한번 밖에 없습니다.&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;GlobalEventBinding 시에 바인딩되는 함수는 handlerStorage 안을 조회합니다. 현재 발생한 이벤트에 대해서 등록된 핸들러 중 target 엘리먼트를 가리키는 template 문자열을 지닌 handler 객체를 발견한다면 해당 handler 객체의 callback 함수를 실행시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체 코드 (참고용)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636129363988&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const addAllDefaultEventListener = (
  handlerStorage: HandlerStorage,
  $root: Element
) =&amp;gt; {
  domEventList.forEach((domEvent) =&amp;gt; {
    handlerStorage[domEvent] = [];
    const globalClickEventHandler = getGlobalEventHandler(
      handlerStorage[domEvent]!
    );
    $root.addEventListener(domEvent, globalClickEventHandler);
  });
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1636129385363&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const getGlobalEventHandler = (eventHandlers: Handler[]) =&amp;gt; ({ target }: Event) =&amp;gt; {
    eventHandlers.forEach((handler) =&amp;gt; {
      const selector = getElementSelector(handler.template);
      const vElementProperty = getVElementProperty(handler.template);

      if (!(target instanceof HTMLElement)) {
        return;
      }

      if (!isRightTarget(target, vElementProperty)) {
        const $closest = target.closest(selector) as HTMLElement;
        if (!$closest) {
          return;
        }

        if (!isRightTarget($closest, vElementProperty)) {
          return;
        }
      }

      handler.callback();
    });
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 프론트엔드 깊게 이해하기/리액트 이해하기</category>
      <author>DogGae</author>
      <guid isPermaLink="true">https://codingmoondoll.tistory.com/135</guid>
      <comments>https://codingmoondoll.tistory.com/entry/%EB%82%98%EB%A7%8C%EC%9D%98-UI-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-CHReactjs#entry135comment</comments>
      <pubDate>Sat, 6 Nov 2021 01:24:08 +0900</pubDate>
    </item>
    <item>
      <title>클라우드 프론트 Quick Start</title>
      <link>https://codingmoondoll.tistory.com/entry/%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%ED%94%84%EB%A1%A0%ED%8A%B8-Quick-Start</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;실습용 파일 준비&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloud Front 를 직접 활용해보기 위해 간단한 웹사이트를 위한 HTML/CSS/Javascript 파일들을 준비해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1248&quot; data-origin-height=&quot;631&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cL8x6r/btrdpsLexXa/4hWPmgtVJN2jEExzHvCt9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cL8x6r/btrdpsLexXa/4hWPmgtVJN2jEExzHvCt9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cL8x6r/btrdpsLexXa/4hWPmgtVJN2jEExzHvCt9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcL8x6r%2FbtrdpsLexXa%2F4hWPmgtVJN2jEExzHvCt9K%2Fimg.png&quot; data-origin-width=&quot;1248&quot; data-origin-height=&quot;631&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 과거에 제가 사용했던 포트폴리오 웹사이트를 사용하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;S3 에 파일 업로드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(s3 업로드 방법은 여기서 따로 다루지는 않겠습니다) 별다른 설정 없이 s3 에 index.html 을 비롯한 각종 웹 자원들을 업로드했다면 기본으로 퍼블릭 엑세스가 차단되어 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1119&quot; data-origin-height=&quot;835&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZM4Bq/btrdo80zSOq/KzJa9AaCLo7GH4kiDwggs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZM4Bq/btrdo80zSOq/KzJa9AaCLo7GH4kiDwggs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZM4Bq/btrdo80zSOq/KzJa9AaCLo7GH4kiDwggs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdZM4Bq%2Fbtrdo80zSOq%2FKzJa9AaCLo7GH4kiDwggs1%2Fimg.png&quot; data-origin-width=&quot;1119&quot; data-origin-height=&quot;835&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;221&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv6Tu8/btrduVLIxUa/MVnc4PMqSGcnLdphbz2zg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv6Tu8/btrduVLIxUa/MVnc4PMqSGcnLdphbz2zg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv6Tu8/btrduVLIxUa/MVnc4PMqSGcnLdphbz2zg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv6Tu8%2FbtrduVLIxUa%2FMVnc4PMqSGcnLdphbz2zg0%2Fimg.png&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;221&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이건 정적 호스팅을 활성화하더라도 마찬가지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;249&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JixgP/btrdsv7wXKk/2krkfRLx9r0OsWWpMRd6Ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JixgP/btrdsv7wXKk/2krkfRLx9r0OsWWpMRd6Ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JixgP/btrdsv7wXKk/2krkfRLx9r0OsWWpMRd6Ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJixgP%2Fbtrdsv7wXKk%2F2krkfRLx9r0OsWWpMRd6Ak%2Fimg.png&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;249&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 파일의 접근권한을 이대로 내버려둔채로 정적 호스팅을 위해 Cloud Front 서비스를 이용해봅시다.&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;Cloud Front 도메인 배포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1091&quot; data-origin-height=&quot;539&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C6Asw/btrdsvsVku6/UYKfchNHGQMhc5CHNYTP41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C6Asw/btrdsvsVku6/UYKfchNHGQMhc5CHNYTP41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C6Asw/btrdsvsVku6/UYKfchNHGQMhc5CHNYTP41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC6Asw%2FbtrdsvsVku6%2FUYKfchNHGQMhc5CHNYTP41%2Fimg.png&quot; data-origin-width=&quot;1091&quot; data-origin-height=&quot;539&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;처음 cloud front 를 이용한다면 위와 같은 화면을 보게 될 것입니다. Create a CloudFront distribution 버튼을 눌러서 본격적으로 클라우드 프론트 도메인을 만들어봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;788&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWziGz/btrdo71CmKj/fio1Q1Sxf2Rci6EsGkh5p1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWziGz/btrdo71CmKj/fio1Q1Sxf2Rci6EsGkh5p1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWziGz/btrdo71CmKj/fio1Q1Sxf2Rci6EsGkh5p1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWziGz%2Fbtrdo71CmKj%2Ffio1Q1Sxf2Rci6EsGkh5p1%2Fimg.png&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;788&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 눌렀다면 위와 같은 많은 설정 항목들을 마주하실 것입니다. 항목이 어마무시하게 많은지라 중요한 설정들 위주로 설명드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Origin&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Origin domain&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 프론트가 'Origin' 으로 설정할 도메인을 입력하는 부분입니다. 말씀드렸 듯이, 클라우드 프론트가 하는 일은 '중계'입니다. 그렇기에 콘텐츠를 받아올 서버가 따로 필요하고 이를 Origin 이라고 부릅니다. 직접 사용하고 있는 도메인을 그대로 입력해도 되지만 저는 그냥 편하게 S3에서 제공해주는 정적 호스팅을 위해 마련된 도메인을 Origin domain 으로 설정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Origin path&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 위에서 설정한 도메인 url에 더해서 추가적인 path를 클라우드 프론트가 제공하는 도메인의 루트 path로 지정하고 싶다면 이 옵션을 사용할 수 있습니다. 저는 이용자에게 전달하고자 하는 index.html 이 버킷의 루트 디렉토리에 존재하므로 딱히 지정하지는 않겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;S3 bucket access&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 클라우드 프론트가 사용할 origin이 S3에 접근하게 되는 origin 이라면 이 설정 항목을 잘 활용해야 합니다. 클라우드 프론트는 S3에 접근하는데 사용되는 권한 객체로서의 OAI 라는 것을 생성할 수 있습니다. S3는 클라우드 프론트의 배포 도메인이 특정 bucket 에 접근하려 할 경우, 버킷이 허용하는 OAI를 해당 배포 도메인이 가지고 있는지를 검사하여 파일을 내어줄지 내어주지 않을지를 판단합니다.&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;이 OAI를 만들기 위해 복잡한 작업이 필요하지는 않습니다. 기존에 생성한 OAI가 없다면 create new OAI 버튼을 눌러 새로 생성한 후 적용하기만 하면 되고, 기존에 생성한 것을 그대로 재활용할 수도 있습니다.&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;Default cache behavior&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;636&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TztwL/btrdqi2qhTX/8A3XbwBDLdprAWQ7GWcB2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TztwL/btrdqi2qhTX/8A3XbwBDLdprAWQ7GWcB2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TztwL/btrdqi2qhTX/8A3XbwBDLdprAWQ7GWcB2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTztwL%2Fbtrdqi2qhTX%2F8A3XbwBDLdprAWQ7GWcB2k%2Fimg.png&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;636&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Path pattern&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 캐싱이 적용되었으면 하는 파일들과 그렇지 않을 파일들을 분류하고 싶다면 이 작성란을 정규표현식으로 채워주시면 됩니다. 저는 굳이 지정하지 않겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Viewer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작성란에서 할 수 있는 일은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이용자가 오떤 이유에서든 http를 이용해서 사이트에 접근해도 이를 https 로 리디렉션 시킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;허용할 HTTP 메서드 종류를 선택합니다. 이 Cloud Front 는 단지 S3 와 이용자를 중계해주는 역할을 해줄 것이기 때문에 저는 GET, HEAD 만을 허용하도록 하겠습니다.&lt;/li&gt;
&lt;li&gt;이용자가 클라우드 프론트 서버에 오직 클라우드 프론트가 배포한 도메인을 통해서만 접속할 수 있도록 제한합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cache key and origin requests&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;400&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0QlqJ/btrdptcmkW7/hQKQLU12VVHddfo09kk2mK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0QlqJ/btrdptcmkW7/hQKQLU12VVHddfo09kk2mK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0QlqJ/btrdptcmkW7/hQKQLU12VVHddfo09kk2mK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0QlqJ%2FbtrdptcmkW7%2FhQKQLU12VVHddfo09kk2mK%2Fimg.png&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;400&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이전에 말했듯 자세한 캐싱 설정을 이곳에서 수행할 수 있습니다. 이 웹사이트는 포트폴리오 웹사이트이기에 업데이트가 잦을 수 있고 어떤 경우에서든 최신 내용이 보여져야 하기 때문에 저는 캐싱 자체를 disabled 로 설정하도록 하겠습니다.&lt;/li&gt;
&lt;li&gt;Origin request policy 를 따로 생성하여 설정하면 이용자가 특정 쿠키, 요청 헤더, queryString 을 가져야만 접속할 수 있도록 제한하는 규칙을 설정할 수 있습니다. 저의 경우는 필요 없으니 None 인 상태로 두겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;캐시 정책 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 본인이 직접 캐시 정책을 생성해보고 싶다면 Create policy 를 눌러서 캐싱 정책을 직접 만들 수 있습니다. 만일 원하는 것이 캐싱을 '어느 정도 시간 동안' 유지할 것인지라면 간단히 TTL 만을 설정하는 것으로도 충분합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;193&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zRy0c/btrdrvzVoNO/ex4g9WUlD6ZOujuYZg0cvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zRy0c/btrdrvzVoNO/ex4g9WUlD6ZOujuYZg0cvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zRy0c/btrdrvzVoNO/ex4g9WUlD6ZOujuYZg0cvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzRy0c%2FbtrdrvzVoNO%2Fex4g9WUlD6ZOujuYZg0cvk%2Fimg.png&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;193&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;하지만 이때 명심해야 할 것이 있습니다. 이 TTL 은 단순히 클라우드 프론트의 캐싱 외에도 origin 의 캐싱 정책 또한 고려해야 해서 설정된다는 것입니다. 이를 이해하지 못하면 왜 TTL 설정에 최소, 최대, 기본 TTL 항목이 존재하는지 이해하기 어려울 것입니다. TTL 값 하나만 설정하면 될텐데 왜 저렇게 3가지 분류를 적용해야하는 것일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 클라우드 프론트가 중계 해주는 Origin 서버 자체의 TTL 값이 설정되어 있을 수 있기 때문입니다. 보통의 경우, TTL은 http 요청의 헤더에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1630170438430&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Cache-Control: max-age=3600&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;위의 형식으로 Cache-Control 을 명시해줬을 때 설정됩니다. 위의 경우엔 3600초 동안 캐싱을 유지하겠다는 의미입니다. 이러한 맥락에서 생각해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;'최소 TTL'은 만일 max-age 값이 최소 TTL 로 설정한 값보다 작다면 그 설정을 무시하고 최소 TTL 을 실제 TTL 로 적용하겠다는 의미&lt;/li&gt;
&lt;li&gt;'최대 TTL'은 만일 max-age 값이 최대 TTL 로 설정한 값보다 크다면 그 설정을 무시하고 최대 TTL 을 실제 TTL 로 적용하겠다는 의미&lt;/li&gt;
&lt;li&gt;'기본 TTL'은 만일 Origin 서버 자체에 설정된 TTL 이 없다면 적용될 TTL 을 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Origin의 캐시 무효화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 위의 캐시 정책은 클라우드 프론트가 중계하는 모든 요청에 일괄적으로 적용된다는 문제점이 존재합니다. 다른 모든 요청에는 캐싱이 모두 적용하고 싶지만, 특정 파일 (ex: bundle.js)은 캐싱을 아예 적용하고 싶지 않을 수도 있습니다. 해당 파일 만큼은 항상 서버에서 최신 파일을 가져왔으면 싶은거죠. 그럴 땐 클라우드 프론트의 '무효화' 기능을 활용해볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;457&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SL1hC/btrdqjG1eTK/q7gZi47Qu4jvixt273Rgj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SL1hC/btrdqjG1eTK/q7gZi47Qu4jvixt273Rgj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SL1hC/btrdqjG1eTK/q7gZi47Qu4jvixt273Rgj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSL1hC%2FbtrdqjG1eTK%2Fq7gZi47Qu4jvixt273Rgj0%2Fimg.png&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;457&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;사용법은 간단합니다. 캐싱을 적용하고 싶지 않은 파일의 경로를 명시한 항목들을 생성해주고 '무효화 생성' 버튼을 눌러주면 됩니다. 이때 와일드카드(*) 를 통해 여러 파일들을 한꺼번에 지정할 수도 있습니다. 이 무효화를 적용하면 기존 Origin 서버의 해당 파일에 대한 캐싱 정책 또한 무효화 됩니다.&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;Settings&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;961&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kYdzO/btrdpLX5J5i/JnRgeIcQTRsfUKC2G6OBjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kYdzO/btrdpLX5J5i/JnRgeIcQTRsfUKC2G6OBjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kYdzO/btrdpLX5J5i/JnRgeIcQTRsfUKC2G6OBjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYdzO%2FbtrdpLX5J5i%2FJnRgeIcQTRsfUKC2G6OBjk%2Fimg.png&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;961&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 프론트의 나머지 설정들입니다. 아래의 항목들에 대해서 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라우드 프론트가 특정 지역에만 국한된 CDN 만을 이용할지를 설정합니다. 당연히 가장 좋은 선택지는 모든 CDN 을 이용하는 것이겠지만 이럴 경우 클라우드 프론트 설정을 변경했을 때, 그 변경 사항이 반영되기까지의 시간이 오래 걸립니다. 게다가 가격도 조금 더 비쌉니다.&lt;/li&gt;
&lt;li&gt;따로 구매한 도메인 주소를 적용시킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;직접 구매하고 지정한 SSL 인증서를 이곳에 적용시킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;도메인의 루트 경로로 접속했을 때 어떤 파일에 접근할지도 설정해줄 수 있습니다. 저의 경우는 index.html 을 설정하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도메인 배포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1368&quot; data-origin-height=&quot;311&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nyyKO/btrdpEdjEYJ/BakE5ItFW6k5QIkhXpSDF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nyyKO/btrdpEdjEYJ/BakE5ItFW6k5QIkhXpSDF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nyyKO/btrdpEdjEYJ/BakE5ItFW6k5QIkhXpSDF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnyyKO%2FbtrdpEdjEYJ%2FBakE5ItFW6k5QIkhXpSDF1%2Fimg.png&quot; data-origin-width=&quot;1368&quot; data-origin-height=&quot;311&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;모든 설정을 마무리하고 배포 시작 버튼을 눌렀다면 다음과 같은 화면을 볼 수 있습니다. 도메인이 배포되기까지 적어도 15분은 걸립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리디렉션 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이용자가 서비스가 제공하지 않는 컨텐츠의 경로로 접근하려고 하면 이를 리디렉션 시켜줄 필요가 있습니다. 제공하는 웹앱이 SPA라면 새로고침에 대한 처리를 해야하니 이 설정은 필수라고 말할 수 있겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;382&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8fRsb/btrdpDMee2N/LVVwkseBUqQBD2KDxjpxsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8fRsb/btrdpDMee2N/LVVwkseBUqQBD2KDxjpxsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8fRsb/btrdpDMee2N/LVVwkseBUqQBD2KDxjpxsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8fRsb%2FbtrdpDMee2N%2FLVVwkseBUqQBD2KDxjpxsK%2Fimg.png&quot; data-origin-width=&quot;1380&quot; data-origin-height=&quot;382&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;클라우드 프론트에서는 별도의 리디렉션 설정 페이지가 존재하지는 않습니다. 하지만 이용자가 클라우드 프론트가 배포한 도메인에 잘못된 방식으로 접근했을 때 어떤 처리를 할지에 대한 설정을 이용해볼 수 있습니다. Create custom error response 를 눌러서 설정 페이지에 진입할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;잘못된 경로로 진입시 루트 경로로 리디렉션&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;722&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VspRr/btrdsxxw2NL/HrIMPcW3zuKxikwIi03KCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VspRr/btrdsxxw2NL/HrIMPcW3zuKxikwIi03KCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VspRr/btrdsxxw2NL/HrIMPcW3zuKxikwIi03KCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVspRr%2Fbtrdsxxw2NL%2FHrIMPcW3zuKxikwIi03KCK%2Fimg.png&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;722&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&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;이용자가 접근할 수 없는 페이지에 접근하려고 하거나 SPA 어플리케이션 상에서 루트 경로 외의 페이지 경로에서 새로고침이 일어났을 때의 리디렉션 처리에 대한 예시입니다. 파일을 찾을 수 없다는 404 에러가 뜰 때는 루트 경로로 리디렉션 되도록 지정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>데브옵스/AWS</category>
      <author>DogGae</author>
      <guid isPermaLink="true">https://codingmoondoll.tistory.com/134</guid>
      <comments>https://codingmoondoll.tistory.com/entry/%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%ED%94%84%EB%A1%A0%ED%8A%B8-Quick-Start#entry134comment</comments>
      <pubDate>Sun, 29 Aug 2021 02:09:10 +0900</pubDate>
    </item>
    <item>
      <title>Cloud Front 란?</title>
      <link>https://codingmoondoll.tistory.com/entry/Cloud-Front-%EB%9E%80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 말해서, 직접 만들 수 있는 CDN(Content Delivery Network) 라고 볼 수 있습니다.&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;CDN 은 간단히 말해 세계에 널리 퍼진 중계서버들의 집합이라고 보시면 됩니다. 보통 S3 와 같은 일반적인 정적 호스팅을 통해 웹 컨텐츠를 이용자들에게 제공하게 되면, 예를 들어 한국에 해당 정적 서버를 호스팅하는 컴퓨터가 위치하고 있다고 가정하면 유럽과 같은 다른 먼 나라의 이용자들에게는 한국에 거주하는 이용자들에 비해 빠른 로딩 속도를 제공하기 어려울 것입니다. 물리적인 거리가 증가함에 따라 컨텐츠를 로드하는데 걸리는 시간 또한 증가할 것이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;330&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tAepH/btrdo5bouwR/hsqF5KvwJyXgU9nbbFmwCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tAepH/btrdo5bouwR/hsqF5KvwJyXgU9nbbFmwCk/img.png&quot; data-alt=&quot;(출처 : https://www.javatpoint.com/aws-cloudfront-cdn )&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tAepH/btrdo5bouwR/hsqF5KvwJyXgU9nbbFmwCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtAepH%2Fbtrdo5bouwR%2FhsqF5KvwJyXgU9nbbFmwCk%2Fimg.png&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;330&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;figcaption&gt;(출처 : https://www.javatpoint.com/aws-cloudfront-cdn )&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;이 비효율을 없애기 위해, 전세계에 뿌려져 있는 각종 중계서버들, 즉 CDN이 웹 자원을 대신 전달하는 역할을 해줄 수 있습니다. 프론트엔드 개발자는 단순히 정적 서버에 빌드된 번들 자바스크립트 파일과 각종 정적 파일을 업로드만 하면 배포를 마무리할 수 있겠지만 그 외에도 서비스 품질을 향상 시킬 수 있는 인프라 작업들이 존재하는 것입니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커스텀 캐싱 전략 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 호스팅 서비스만을 이용하면 캐싱 전략을 파일별로 일일히 설정해야 하는 경우가 많습니다. 그러나 클라우드 프론트는 기존의 정적 파일 자체의 캐싱 정책과 더불어서 정말 많은 캐싱 전략을 일괄적으로 간편하게 설정할 수 있도록 보조해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;웹 자원을 Public 권한으로 공개하지 않아도 됨&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 와 같은 정적 호스팅 서비스는 모든 이용자를 대상으로 호스팅 서비스를 제공해야 하기 때문에 정적 호스팅을 적용하기 위해서 모든 웹 자원의 접근 권한을 Public 으로 설정할 것을 강제하는 경우가 많습니다. 그러나 Cloud Front 를 통해 생성된 도메인을 통해 접근해야만 웹 자원에 접근할 수 있도록 설정해둔다면 서비스 이용자 외에 다른 사람이 웹 자원에 접근하는 것을 막을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로딩 속도를 향상시킬 수 있는 각종 부가 기능을 제공&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloud Front 는 위에서 말한 것과 같이 이용자가 엣지 로케이션(이용자와 가장 가까운 거리에 있는 중계 서버 위치)를 통해 웹 자원에 접근할 수 있게 만들기 때문에 그저 이용하는 것만으로 로딩 성능을 향상시킬 수 있습니다. 하지만 Cloud Front 는 그것 외에도 로딩 성능을 증대 시킬 수 있는 많은 부가적인 기능들을 제공해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>데브옵스/AWS</category>
      <author>DogGae</author>
      <guid isPermaLink="true">https://codingmoondoll.tistory.com/133</guid>
      <comments>https://codingmoondoll.tistory.com/entry/Cloud-Front-%EB%9E%80#entry133comment</comments>
      <pubDate>Sun, 29 Aug 2021 02:03:20 +0900</pubDate>
    </item>
  </channel>
</rss>