<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>WWWALKINPCM :: 박찬민의 블로그</title>
    <link>https://walkinpcm.tistory.com/</link>
    <description>프론트엔드 개발자로써 공부하며 정리하는 내용들을 기록합니다.</description>
    <language>ko</language>
    <pubDate>Sat, 30 May 2026 02:37:16 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>walkinpcm</managingEditor>
    <image>
      <title>WWWALKINPCM :: 박찬민의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/3850844/attach/658a39a9a39c4ef1a02239c2492d7cdc</url>
      <link>https://walkinpcm.tistory.com</link>
    </image>
    <item>
      <title>Amplify Console의 Preview URL을 Custom Domain으로 사용하는 방법</title>
      <link>https://walkinpcm.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Amplify Console을 통해서 웹 어플리케이션의 Hosting을 한지 어언 2년 반이 지났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 만족하면서 사용하고 있는데, 그 중에서 Amplify의 가장 큰 장점을 말해보라고하면 저는 Preview 기능을 선택할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Preview 기능은 PR 단위로 환경을 만들어주기 때문에, 여러 작업을 동시에 진행하여 PR을 각각 올렸을 때 모든 작업을 분리된 환경에서 개별적으로 테스트 할 수 있게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 Amplify Console을 도입할 때 제 상황이 딱 여러 작업의 테스트를 동시에 하면 좋은 상황이었기에 너무 만족스럽게 Preview 기능을 활용하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, Preview 기능으로 만들어진 환경은 기본적으로 amplify 도메인으로 호스팅 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Preview는 보통 개발환경으로 사용하고 있어서 도메인이 무엇이든 크게 영향을 받는 일은 없습니다. 그러나 소유하고 있지 않은 도메인을 사용하는 것은 어딘가 조금은 불편함을 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면, API 서버에 요청을 보내면 막힐 수 있습니다. API 서버는 보안상 허가된 도메인으로부터 요청이 왔을 때만 응답을 하기도 합니다. 그런데 Amplify 도메인으로 호스팅 되는 Preview 환경의 URL은 API 서버가 모를 가능성이 무척 높아서 API 요청을 허용하지 않을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 Amplify 웹앱에 부여받은 전용 Amplify 도메인을 API 서버에서 허용 도메인에 넣더라도 조금은 불편합니다. API 서버를 관리하는 입장에서는 무엇인지 알 수 없는 도메인이 허용 리스트에 있을텐데, 그냥 봐서는 어떤 서비스인지 파악하기 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 불편함들을 없애기 위해서는 Preview 환경도 내가 소유하고 있는 커스텀 도메인을 이용하면 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amplify App에 이미 Custom Domain이 적용되어 있다면 Preview 환경에도 적용시키는 것은 아주 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(주의! Preview 환경에 custom Domain을 적용하려면 Amplify App에 루트 도메인이 적용되어 있어야 합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amplify Console에서 '도메인 관리' 페이지로 들어갑니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-30 오전 2.50.16.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZpG6H/btrPPXHnAb5/7oV3xDx9DbLbLMdNgJTf4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZpG6H/btrPPXHnAb5/7oV3xDx9DbLbLMdNgJTf4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZpG6H/btrPPXHnAb5/7oV3xDx9DbLbLMdNgJTf4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZpG6H%2FbtrPPXHnAb5%2F7oV3xDx9DbLbLMdNgJTf4K%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;2000&quot; height=&quot;616&quot; data-filename=&quot;스크린샷 2022-10-30 오전 2.50.16.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 페이지에서 '하위 도메인 관리' 버튼을 누르면 하위 도메인을 설정할 수 있는 페이지가 나오고 하단에 '패턴과 일치하는 브랜치 이름에 대한 하위 도메인을 자동으로 생성' 체크박스가 있습니다. 이 버튼을 클릭하면 패턴을 입력하는 텍스트 필드가 나오는데 기본값이 이미 입력되어 있습니다. 이를 수정없이 그대로 두고 '업데이트' 버튼을 누르면 설정은 끝입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-30 오전 2.03.15.png&quot; data-origin-width=&quot;1976&quot; data-origin-height=&quot;1284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blY4FB/btrPR6JKxSP/XdOWHBeDdXmKwGUfqUZMrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blY4FB/btrPR6JKxSP/XdOWHBeDdXmKwGUfqUZMrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blY4FB/btrPR6JKxSP/XdOWHBeDdXmKwGUfqUZMrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblY4FB%2FbtrPR6JKxSP%2FXdOWHBeDdXmKwGUfqUZMrk%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;1976&quot; height=&quot;1284&quot; data-filename=&quot;스크린샷 2022-10-30 오전 2.03.15.png&quot; data-origin-width=&quot;1976&quot; data-origin-height=&quot;1284&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;이후로 Preview 환경을 만들면 커스텀 도메인이 적용된 Preview URL을 볼 수 있습니다. 아래 이미지에서 pr-24는 하위 도메인 설정 전이고, pr-28은 하위 도메인 설정 후 입니다. 역시 알 수 없는 Amplify App ID(가려 놓은 부분)가 붙은 URL보다는 내가 가진 도메인을 보았을 때 어떤 서비스인지 바로 인지됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-30 오전 2.22.20.png&quot; data-origin-width=&quot;2010&quot; data-origin-height=&quot;1168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSDNNv/btrPVDNzoA8/Klt7DfXnZSLqBiu1R3wY7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSDNNv/btrPVDNzoA8/Klt7DfXnZSLqBiu1R3wY7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSDNNv/btrPVDNzoA8/Klt7DfXnZSLqBiu1R3wY7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSDNNv%2FbtrPVDNzoA8%2FKlt7DfXnZSLqBiu1R3wY7K%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;2010&quot; height=&quot;1168&quot; data-filename=&quot;스크린샷 2022-10-30 오전 2.22.20.png&quot; data-origin-width=&quot;2010&quot; data-origin-height=&quot;1168&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;h4 data-ke-size=&quot;size20&quot;&gt;참고자료&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 첫 Amplify Console 소개 포스팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - &lt;a href=&quot;https://walkinpcm.tistory.com/11&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://walkinpcm.tistory.com/11&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Amplify Console을 이용해서 개발환경을 개선한 경험을 공유한 사내 블로그 포스팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - &lt;a href=&quot;http://blog.hwahae.co.kr/all/tech/tech-tech/3848/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://blog.hwahae.co.kr/all/tech/tech-tech/3848/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Preview 주소를 Custom Domain으로 이용하는 방법을 안내하는 공식 문서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - &lt;a href=&quot;https://docs.aws.amazon.com/amplify/latest/userguide/to-set-up-automatic-subdomains-for-a-Route-53-custom-domain.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.aws.amazon.com/amplify/latest/userguide/to-set-up-automatic-subdomains-for-a-Route-53-custom-domain.html&lt;/a&gt;&lt;/p&gt;</description>
      <category>TECH</category>
      <category>Amplify</category>
      <category>AWS</category>
      <category>Custom Domain</category>
      <category>Preview</category>
      <category>미리보기</category>
      <category>커스텀 도메인</category>
      <author>walkinpcm</author>
      <guid isPermaLink="true">https://walkinpcm.tistory.com/27</guid>
      <comments>https://walkinpcm.tistory.com/27#entry27comment</comments>
      <pubDate>Tue, 25 Oct 2022 19:03:14 +0900</pubDate>
    </item>
    <item>
      <title>Amplify Console에서 환경(브랜치) 제거를 막자</title>
      <link>https://walkinpcm.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Amplify Console에서 웹 호스팅을 하고 있고, 약 2년 동안 대체로 아주 만족하면서 사용하고 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Amplify Console에서 웹 호스팅을 하는 방법은 아래 링크에서 볼 수 있습니다.&lt;br /&gt;&lt;a href=&quot;https://walkinpcm.tistory.com/11&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://walkinpcm.tistory.com/11&lt;/a&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;그런데, 2년이나 사용하면서 간과하고 있던 것이 하나 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그건 바로 '권한'입니다. 구체적으로는 Production Branch를 삭제하지 못하게 하는 권한입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amplify Console에서는 Git의 Branch 연결만으로 손 쉽게 배포환경이 만들어지고, Branch의 연결 해제만으로 손 쉽게 배포환경이 제거됩니다. 여기서 '손 쉽게 배포환경이 제거'되는 것은 큰 위험요인입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람은 언제든지 실수를 할 수 있기 때문에 언제든지 Production Branch를 삭제하여 Production 서비스를 없애버릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게도 Production Branch는 삭제할 수 없게 권한을 막아서 Production 서비스를 실수로 없애는 사고를 막아야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 AWS IAM을 이용하면 특정 Amplify App의 특정 Branch의 삭제를 막는 정책을 적용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에서 부터는 실험 상황을 만들어서 Branch 삭제를 막는 방법을 정리해보겠습니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS IAM에서 테스트를 위한 '사용자'를 만들고 'AdministratorAccess-Amplify' 권한을 부여 했습니다. 아래의 모든 작업은 이 사용자로 수행합니다.&lt;/li&gt;
&lt;li&gt;Github Repository에 [do-not-remove] Branch를 만듭니다.&lt;/li&gt;
&lt;li&gt;Amplify Console에 [do-not-remove] Branch를 연결 시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2320&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGvTIH/btrPC98Sv9l/rNpGQmBFNMBhkrgRIPoXnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGvTIH/btrPC98Sv9l/rNpGQmBFNMBhkrgRIPoXnK/img.png&quot; data-alt=&quot;Amplify Console에 [do-not-remove] Branch가 연결된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGvTIH/btrPC98Sv9l/rNpGQmBFNMBhkrgRIPoXnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGvTIH%2FbtrPC98Sv9l%2FrNpGQmBFNMBhkrgRIPoXnK%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;2320&quot; height=&quot;320&quot; data-origin-width=&quot;2320&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Amplify Console에 [do-not-remove] Branch가 연결된 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;# 사용자에게서 Branch 삭제 권한 제외하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 정책을 적용하면 특정 Branch의 삭제를 못하게 만들 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1666794160975&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;VisualEditor0&quot;,
            &quot;Effect&quot;: &quot;Deny&quot;,
            &quot;Action&quot;: &quot;amplify:DeleteBranch&quot;,
            &quot;Resource&quot;: &quot;arn:aws:amplify:{region}:{account-id}:apps/{app-id}/branches/{branch-name}&quot;
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 정책에서 region, account-id, app-id, branch-name은 각자 알맞게 넣어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정책을 해석해보자면, 'Resource'에 명시한 [amplify app]의 [branch-name]에 대한 [amplify:DeleteBranch] 동작을 [Deny] 막는다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정책 적용 확인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 정책을 사용자에게 적용하면 사용자가 Branch를 연결해제(삭제)하려고 할 때 아래와 같이 막히는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고로, IAM에서 사용자에게 정책을 적용시켰을 때 몇 초 정도는 적용되기까지 딜레이가 있습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2804&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/35MAC/btrPFZi8Mws/vppywUPn88o98wKxqvxCB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/35MAC/btrPFZi8Mws/vppywUPn88o98wKxqvxCB0/img.png&quot; data-alt=&quot;'브랜치 분리'를 시도해본다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/35MAC/btrPFZi8Mws/vppywUPn88o98wKxqvxCB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F35MAC%2FbtrPFZi8Mws%2FvppywUPn88o98wKxqvxCB0%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;2804&quot; height=&quot;320&quot; data-origin-width=&quot;2804&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;'브랜치 분리'를 시도해본다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddxsEd/btrPB2WqAcV/7VwRh49lvDEzc3QdtTKSYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddxsEd/btrPB2WqAcV/7VwRh49lvDEzc3QdtTKSYk/img.png&quot; data-alt=&quot;Branch 삭제를 못 한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddxsEd/btrPB2WqAcV/7VwRh49lvDEzc3QdtTKSYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddxsEd%2FbtrPB2WqAcV%2F7VwRh49lvDEzc3QdtTKSYk%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;1600&quot; height=&quot;238&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Branch 삭제를 못 한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참고자료&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amplify/latest/userguide/security_iam_permissions-reference.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.aws.amazon.com/amplify/latest/userguide/security_iam_permissions-reference.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>TECH</category>
      <category>Amplify</category>
      <category>AWS</category>
      <category>Hosting</category>
      <category>permission</category>
      <category>권한</category>
      <author>walkinpcm</author>
      <guid isPermaLink="true">https://walkinpcm.tistory.com/26</guid>
      <comments>https://walkinpcm.tistory.com/26#entry26comment</comments>
      <pubDate>Tue, 25 Oct 2022 19:02:33 +0900</pubDate>
    </item>
    <item>
      <title>MSW를 이용해서 쉽고 깔끔하게 API Mocking 하자</title>
      <link>https://walkinpcm.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발을 하면서 백엔드 API를 연동하는 개발을 해본 사람이라면, 아마도 API 개발이 늦어져서 API 연동 작업이 지연되는 경험이 있을 거라고 생각합니다. API 개발이 지연되는 만큼 전체 과제 기간도 지연되면 참 좋겠지만, 현실적으로 과제 기간 지연은 쉽지 않고 나도 최대한 빠르게 개발을 완료해서 멋있는 개발자가 되고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 개발이 늦어질 때는 보통 API로 받아오는 응답을 Mocking해서 최대한 프론트엔드에서 먼저 개발을 해두고 API가 개발된 이후에 Mocking 코드를 제거하고 실제 API로 실행시켜 보면서 코드를 다듬습니다. 저는 처음에는 API를 연동하는 코드와 같은 파일에 Mocking 데이터들을 하드코딩하고 실제 API 연동 코드는 주석 처리해서 사용했습니다. 그런데, 이렇게하면 API 개발 완료 이후에 하드코딩한 Mocking 데이터를 삭제하고 API 연동 코드는 주석을 해제한 뒤에 실제 API 코드로도 잘 동작하는데 다시 확인해야하는 번거로움이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중에 아주 매력적인 라이브러리를 만났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSW. ( Mock Service Worker )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSW는 API를 쉽고 깔끔하게 Mocking 할 수 있게 도와주는 라이브러리입니다. 도입은 30분만에 빠르고 가볍게 할 수 있는데, 그 효과는 정말 뛰어납니다. 개발 시간을 효과적으로 줄여주고 개발 경험을 크게 향상 시켜줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSW의 가장 큰 특징과 장점은&amp;nbsp;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;Service Worker에서 API를 Mocking 하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;API 요청이 서버로 전달되는 순서를 간략하게 따져보자면 웹어플리케이션 코드에서 axios와 같은 라이브러리로 API 요청을 보내고 이 요청은 브라우저에서 Service Worker를 거친 다음에 네트워크로 나가게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;MSW는 이 중에서 요청의 순서 중 마지막인 Service Worker에서 요청을 가로챈 뒤 Mocking 데이터로 응답을 반환해버립니다. (네트워크로 요청이 나가지 않습니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, 웹어플리케이션 코드를 어떤 언어로, 어떤 프레임워크로, 어떤 라이브러리로 작성하든 &amp;amp; 어떤 라이브러리로 HTTP 요청을 보내든 상관없습니다. 의존성이 전혀 없고, Mocking 코드 또한 별도의 파일로 관리됩니다. 즉, 웹어플리케이션 코드에 Mocking 데이터가 추가 되지 않습니다. 웹어플리케이션 코드는 처음부터 실제 API를 호출하듯이 작성할 수 있습니다. 실제 API 개발 이후에 웹어플리케이션 코드를 추가적으로 수정할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;효과는 아주 크지만 도입은 아주 간단합니다. 공식 문서에도 도입 방법이 아주 간단하고 친절하게 안내 되어 있습니다.&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;MSW 도입 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) msw 라이브러리 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;msw 라이브러리를 devDependency로 설치합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1652269562770&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add msw --dev&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;(2) handler 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 endpoint를 가진 API를 어떤 응답으로 Mocking할지 정의하는 곳이 handler입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 루트 디렉토리 또는 `src`폴더 하위에 `mocks` 폴더를 만들고 그 하위에 `handlers.js`파일을 만들어서 다양한 handler를 정의합니다. (폴더명이나 파일명은 원하는 이름으로 변경해도 됩니다. 다른 곳에서 참조할 때 경로만 잘 명시해주면 됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;handlers.js 내에서 아래와 같이 API Mocking 코드를 작성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1652270146500&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/** src/mocks/handlers.js */

import { rest } from 'msw'

export const handlers = [
  // Handles a GET /user request
  rest.get('/user', (req, res, ctx) =&amp;gt; {
    return res(
      ctx.status(200),
      ctx.json({
        // Mocking할 Data를 정의합니다. API 스펙에서 응답 예시를 복사해서 넣어주면 됩니다.
        // 아래는 예시입니다.
        name: 'walkinpcm',
        blog: 'blog.walkinpcm.com',
      }),
    )
  }),
  // 다른 handler도 배열 안에 정의하면 됩니다.
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 handler를 정의하여 사용하다가 더이상 Mocking이 필요없는 API는 배열에서 제거해주거나 주석처리하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) Service Worker 셋업&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Service Worker를 사용하기 위해서 worker 코드를 작성해줘야하는데, MSW에서는 이 코드를 개발자가 직접 짜게하지 않고 라이브러리에서 제공해줍니다. 제공해주는 코드를 적용하기 위해서 아래 명령어를 실행합니다.&lt;br /&gt;아래 명령어를 실행하면 public/mockServiceWorker.js 가 생성되며 package.json 파일에 msw.workerDirectory가 추가됩니다. package.json에 정보를 설정하는 이유는, 추후에 msw가 업데이트 되면서 mockServiceWorker 스크립트가 변경되면 이 변경사항을 자동으로 로컬 파일에 반영해주시 위함이라고 합니다. (worker 코드를 직접 작성하지 않게 해주면서 코드가 갱신되어도 갱신까지 알아서 해주는 편리함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 `public/`은 웹어플리케이션의 Public Directory입니다. 웹어플리케이션을 호스팅할 때 정적 리소스들을 호스팅하는 폴더에 mockServiceWorker.js 파일도 함께 호스팅 되어야 합니다. 저는 CRA 기반의 프로젝트라서 `public/` 경로를 넣었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1652270390441&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx msw init public/ --save&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;(4) worker 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;worker에 핸들러를 등록해줍니다. `handlers.js`와 동일한 위치에 `browser.js`파일을 생성하고 아래 내용을 입력합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1652270666645&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/** src/mocks/browser.js */
import { setupWorker } from 'msw'
import { handlers } from './handlers'

// 정의했던 handler들을 worker에 등록합니다.
export const worker = setupWorker(...handlers)&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;(5) worker 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 웹어플리케이션이 실행될 때 Service Worker도 함께 실행해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넣어줘야 하는 코드는 아래에서 if문 입니다. 각자 프로젝트에서 웹어플리케이션의 최초 실행 코드에 if문 코드를 입력하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 CRA 기반 프로젝트라서 `src/index.js`에 작성하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1652270882189&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/** src/index.js */
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

// 웹어플리케이션에서 가장 먼저 실행되는 코드에 아래 if문을 추가합니다.
if (process.env.NODE_ENV === 'development') {
  const { worker } = require('./mocks/browser')
  worker.start()
}

ReactDOM.render(&amp;lt;App /&amp;gt;, document.getElementById('root'))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 개발환경에서만 msw가 동작하도록 제한합니다. 당연하게도 운영환경에서는 실제 API로 요청을 보내야하니깐요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 MSW를 도입하는 방법이었습니다. 실제로 해보면 잘 모르고 따라해도 30분이면 합니다. 아주 적은 시간인데, 실제로 업무할 때 사용해보면 너무 큰 만족을 느낄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 개발경험을 향상 시켜주는 좋은 라이브러리는 널리널리 퍼뜨려야겠습니다.&lt;/p&gt;</description>
      <category>TECH</category>
      <category>API</category>
      <category>Javascript</category>
      <category>Mocking</category>
      <category>MSW</category>
      <author>walkinpcm</author>
      <guid isPermaLink="true">https://walkinpcm.tistory.com/25</guid>
      <comments>https://walkinpcm.tistory.com/25#entry25comment</comments>
      <pubDate>Wed, 11 May 2022 21:28:11 +0900</pubDate>
    </item>
    <item>
      <title>MUI의 Datepicker에 사용하는 Input을 커스텀 해보자</title>
      <link>https://walkinpcm.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 &lt;a href=&quot;https://mui.com/components/date-picker/#main-content&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MUI(Material-UI)의 Datepicker&lt;/a&gt;를 Input만 스타일을 수정해서 사용하는 경우가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MUI의 Datepicker는 기본적으로 아래와 같은 Input 스타일을 가지고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cU3CuB/btrqBGanlBo/w0ycsPfB27gWIrHqd1qmP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cU3CuB/btrqBGanlBo/w0ycsPfB27gWIrHqd1qmP1/img.png&quot; data-alt=&quot;MUI Datepicker의 기본 Input 스타일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cU3CuB/btrqBGanlBo/w0ycsPfB27gWIrHqd1qmP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcU3CuB%2FbtrqBGanlBo%2Fw0ycsPfB27gWIrHqd1qmP1%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;1672&quot; height=&quot;258&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MUI Datepicker의 기본 Input 스타일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 여기에 우리 팀이 커스텀해둔 TextField를 사용하고, 캘린더 아이콘을 사용하고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Datepicker 컴포넌트는 당연하게도 Input에 사용할 컴포넌트를 바꿀 수 있는 방법을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Datepicker의 prop중에 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;renderInput&lt;/span&gt;&amp;nbsp;&lt;/span&gt; prop을 이용하면 됩니다. &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;renderInput&lt;/span&gt;&amp;nbsp;&lt;/span&gt; prop에 Input으로 사용할 컴포넌트를 return하는 함수를 넣어줍니다. (함수의 매개변수는 &lt;a href=&quot;https://mui.com/components/date-picker/#custom-input-component&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;저는 TextField를 return 하게 넣어줬습니다. 그리고 캘린더 아이콘을 변경하기 위해서 TextField의 InputProps.endAdornment 속성에 캘린더 아이콘을 버튼에 넣었습니다. (&lt;a href=&quot;https://mui.com/components/text-fields/#input-adornments&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TextField의 endAdornment 관련 문서&lt;/a&gt;)&lt;/p&gt;
&lt;pre id=&quot;code_1641984228420&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;DatePicker
  // ...
  renderInput={({ inputRef, inputProps }: any) =&amp;gt; (
    &amp;lt;TextField
      InputProps={{
        endAdornment: (
          &amp;lt;MuiInputAdornment position=&quot;end&quot;&amp;gt;
            &amp;lt;Button&amp;gt;
              &amp;lt;CalendarIcon /&amp;gt;
            &amp;lt;/Button&amp;gt;
          &amp;lt;/MuiInputAdornment&amp;gt;
        ),
      }}
    /&amp;gt;
  )}
  // ...
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하니깐 Input의 모양은 제가 원하던 모양으로 만들어졌습니다. 그런데, 한가지 이슈가 있었습니다. Datepicker의 기본 캘린더 아이콘을 사용할 때는 아이콘을 눌렀을 때 날짜선택창이 나타나는데, 제가 커스텀으로 넣은 캘린더 아이콘 버튼을 눌렀을 때는 날짜선택창이 나타나지 않았습니다. (지금 생각해보니 MUI 기본 로직에서 제가 넣은 버튼에 알아서 onClick 핸들러를 넣을 수 있는.. 단서가 아무 것도 없네요..ㅎㅎㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Datepicker의 기본 캘린더 아이콘을 사용하지 않을 것이라면, 날짜선택창이 뜰 수 있게 직접 제어해줘야 합니다. 이를 위해서 Datepicker 컴포넌트에는&amp;nbsp;&lt;span style=&quot;color: #000000; background-color: #dddddd;&quot;&gt; open &lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;color: #000000; background-color: #dddddd;&quot;&gt; onOpen &lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;color: #000000; background-color: #dddddd;&quot;&gt; onClose&amp;nbsp;&lt;/span&gt; prop이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;open: boolean type이며, 날짜선택창의 열림 여부를 결정합니다. true로 넘기면 날짜선택창이 열리고 false를 넘기면 닫힙니다.&lt;/li&gt;
&lt;li&gt;onOpen: Datepicker에서 날짜선택창을 열려는 시도를 감지하는 리스너입니다.&lt;/li&gt;
&lt;li&gt;onClose: Datepicker에서 날짜선택창을 닫으려는 시도를 감지하는 리스너입니다.&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;수동으로 날짜선택창을 제어하려면, 날짜선택창의 열림 상태를 저장하는 state를 하나 만들어서 open prop에 할당하고, Datepicker의 &lt;span style=&quot;color: #000000; background-color: #dddddd;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;onOpen&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;color: #000000; background-color: #dddddd;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;onClose&amp;nbsp;&lt;/span&gt; prop에 각각 open state를 true, false로 변경하는 핸들러 함수를 할당하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 커스텀 캘린더 아이콘 버튼의 onClick 이벤트 리스너에도 open state를 true로 변경하는 핸들러 함수를 할당합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641986948810&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 날짜 선택창을 띄우는 상태 제어
const [isOpen, setIsOpen] = useState&amp;lt;boolean&amp;gt;(false);
const setOpen = useCallback(() =&amp;gt; {
  setIsOpen(true);
}, [setIsOpen]);
const setClose = useCallback(() =&amp;gt; {
  setIsOpen(false);
}, [setIsOpen]);

// ...
&amp;lt;DatePicker
  open={isOpen}
  onOpen={setOpen}
  onClose={setClose}
  // ...
  renderInput={({ inputRef, inputProps }: any) =&amp;gt; (
    &amp;lt;Box&amp;gt;
      &amp;lt;TextField
        InputProps={{
          endAdornment: (
            &amp;lt;MuiInputAdornment position=&quot;end&quot;&amp;gt;
              &amp;lt;Button onClick={setOpen}&amp;gt;
                &amp;lt;CalendarIcon /&amp;gt;
              &amp;lt;/Button&amp;gt;
            &amp;lt;/MuiInputAdornment&amp;gt;
          ),
        }}
      /&amp;gt;
    &amp;lt;/Box&amp;gt;
  )}
  // ...
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 하면 원하는 Input 스타일을 가진 Datepicker 컴포넌트를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 한가지 아쉬운 점이 있었습니다. 날짜선택창을 열었을 때, 캘린더 아이콘 버튼을 포함한 TextField를 기준으로 아래-가운데 정렬되지 않고 오직 값이 들어가는 input 영역을 기준으로 아래-가운데 정렬되었습니다. 그래서 눈으로 보기에 날짜선택창이 가운데보다 조금 왼쪽으로 이동되어 있어 보였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는, Datepicker가 날짜선택창을 띄울 때, 위치를 잡는 기준이 input에 있어서 그렇습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래처럼 ref를 이용해서 날짜선택창이 위치를 잡는 Element를 직접 지정해주었습니다. 저는 TextField 전체를 감싸는 Box 컴포넌트를 추가하여 그곳을 기준으로 날짜선택창이 뜨도록 바꿔주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1641987491258&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Datepicker 컴포넌트의 prop
PopperProps={{
  placement: 'bottom', // 날짜선택창이 뜨는 위치
  anchorEl: popperAnchorRef.current,
}}

// Datepicker 컴포넌트의 renderInput에서 return하는 컴포넌트
&amp;lt;Box ref={popperAnchorRef}&amp;gt;
  &amp;lt;TextField
    // ...
  /&amp;gt;
&amp;lt;/Box&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지해서 원하는 스타일을 가진 Datepicker를 완성할 수 있었습니다.&lt;/p&gt;</description>
      <category>TECH</category>
      <category>DatePicker</category>
      <category>Javascript</category>
      <category>Material-Ui</category>
      <category>MUI</category>
      <author>walkinpcm</author>
      <guid isPermaLink="true">https://walkinpcm.tistory.com/23</guid>
      <comments>https://walkinpcm.tistory.com/23#entry23comment</comments>
      <pubDate>Wed, 12 Jan 2022 20:46:07 +0900</pubDate>
    </item>
    <item>
      <title>emotion을 이용해서 MUI(Material-UI)의 Tooltip 컴포넌트에 커스텀 스타일 적용하는 방법</title>
      <link>https://walkinpcm.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 MUI(Material-UI)에서 제공하는 &lt;a href=&quot;https://mui.com/components/tooltips&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Tooltip 컴포넌트&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;- create-react-app 기반의 React 프로젝트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- MUI v5&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- @emotion/react 사용&lt;/p&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;아래 예시는 Box 컴포넌트의 백그라운드 색상을 흰색으로 설정하는 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1636546459143&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { css } from '@emotion/react';
import { Box as MuiBox } from '@mui/material';
import type { BoxProps as MuiBoxProps } from '@mui/material';

const styles = {
  root: css`
    background-color: #ffffff;
  `,
};

const BoxWhite = ({ ...other }: MuiBoxProps): JSX.Element =&amp;gt; {
  return &amp;lt;MuiBox css={styles.root} {...other} /&amp;gt;;
};

export default BoxWhite;&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;그러나, Tooltip 컴포넌트는 부모 컴포넌트의 DOM 계층 밖에 랜더됩니다. 그래서 Tooptip 컴포넌트에 스타일을 지정하는 것 만으로 원하는 스타일을 만들 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련해서 아래 2개의 링크에서 Tooptip 컴포넌트의 스타일을 커스텀 하는 방법을 안내하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://mui.com/guides/interoperability/#portals&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mui.com/guides/interoperability/#portals&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://mui.com/components/tooltips/#customization&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mui.com/components/tooltips/#customization&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;그러나, 위 안내들에서는 MUI에서 제공하는 &lt;a href=&quot;https://mui.com/system/styled/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;styled 유틸&lt;/a&gt;을 사용하는 방법만 안내하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 프로젝트 내에서 스타일링 할때 emotion을 이용하도록 통일하고 있기 때문에 위 문서들의 방법을 그대로 사용할 수는 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MUI v5는 emotion을 공식적으로 지원한다고 하고 있기 때문에 분명 방법이 있을거라고 생각하고 검색을 해보니 아래와 같이 방법으로 해결할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면, Tooltip 컴포넌트의 스타일을 커스텀하려면, `classes` 속성에 스타일을 지정해줘야합니다. 그래야 별도의 DOM 트리에 그려진 Tooltip 요소의 스타일을 지정할 수 있습니다. 그리고 emotion을 이용해서 classes 속성에 값을 넣어주려면 emotion에서 제공하는 &lt;a href=&quot;https://emotion.sh/docs/class-names&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ClassNames 컴포넌트&lt;/a&gt;로 Tooptip 컴포넌트를 감싸고, ClassNames 컴포넌트로 인해서 사용할 수 있는 css 유틸을 이용하면 Tooltip의 classes 속성에 넣어줄 값을 만들 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1636547850626&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Tooltip as MuiTooltip } from '@mui/material';
import type { TooltipProps as MuiTooltipProps } from '@mui/material';
import { ClassNames } from '@emotion/react';

const Tooltip = ({
  placement = 'right-start',
  children,
  ...other
}: MuiTooltipProps): JSX.Element =&amp;gt; {
  return (
    &amp;lt;ClassNames&amp;gt;
      {({ css }) =&amp;gt; (
        &amp;lt;MuiTooltip
          classes={{
            popper: css`
              &amp;amp;[data-popper-placement*='right']
                .MuiTooltip-tooltipPlacementRight.MuiTooltip-tooltip {
                margin-left: 8px;
              }
            `,
            tooltip: css`
              margin: 0;
              font-size: 12px;
              max-width: none;
            `,
          }}
          placement={placement}
          {...other}
        &amp;gt;
          {children}
        &amp;lt;/MuiTooltip&amp;gt;
      )}
    &amp;lt;/ClassNames&amp;gt;
  );
};

export default Tooltip;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>TECH</category>
      <category>CSS</category>
      <category>Emotion</category>
      <category>Material-Ui</category>
      <category>MUI</category>
      <category>tooltip</category>
      <author>walkinpcm</author>
      <guid isPermaLink="true">https://walkinpcm.tistory.com/22</guid>
      <comments>https://walkinpcm.tistory.com/22#entry22comment</comments>
      <pubDate>Wed, 10 Nov 2021 22:04:26 +0900</pubDate>
    </item>
    <item>
      <title>Error Handling(에러 핸들링) - (1) 고민 풀어보기</title>
      <link>https://walkinpcm.tistory.com/21</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Error Handling에 대한 고민 시작&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹서비스를 유지보수하면서 버그를 수정하거나 일부 기능을 개선하고 새로운 기능을 개발하면서 점점 Error를 처리하는게 고민거리가 되어가고 있습니다. 지금 유지보수 하고 있는 프로젝트는 Error 상황에 대해서 깊게 고민하지 않고 만들어져 있는데 그 결과 아래와 같은 문제점이 있어서 유지보수 할 수록 마음의 짐을 쌓는 기분이 들었습니다.&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;Network Error를 처리하려고 할 때, 어디서 어떻게 할지 매번 고민합니다. Error 처리에 대한 기준이나 원칙 등이 명시된 문서가 없어서 개발자가 바뀌어 버리니깐 프로젝트의 개발 체계가 없어졌습니다.&lt;/li&gt;
&lt;li&gt;Error 발생시 노출하는 Alert의 스타일이 통일되어 있지 않습니다. 여기저기서 사용하는 Alert의 스타일이 달라서 새로 Alert을 추가할때 결국 개발자 개인의 성향에 따라 Alert 스타일을 선택하게 됩니다.&lt;/li&gt;
&lt;li&gt;전역적으로 처리되어야 할 Error가 지역적으로 처리되고 있고 한 파일 내의 코드 간 유착이 심해서 전역적 처리로 변경하려 할 때의 수정 범위가 넓고 사이드 이팩트를 예측하기 힘들었습니다.&lt;/li&gt;
&lt;li&gt;Error 처리에 대한 고민이 없었기에, Error 로깅 체계가 잡혀있지 않아서 사용자가 불편함을 겪고 있진 않은지, 우리 서비스가 제대로 잘 가치를 제공하고 있는지 확인하기 어렵습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 생각나는 것만 적어보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Error는 분명 발생하지 않는게 제일 좋은 상황이기에 저도 그동안 소홀히 생각하고 있었던 것 같습니다. 그러나 프로젝트를 유지보수 해보면서 Error 처리가 제대로 되어 있지 않을 때 개발이 어려워 지는 것을 느끼고 있고, 다양한 사용자가 다양한 환경에서 이용하는 서비스에서 Error가 발생하지 않는 다는 것은 거의 불가능하기에 Error를 제대로 처리하는 것이 개발자에게도 서비스에도 좋은 것이라는 것을 깨달았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Error Handling 체계를 잡고 싶다! 어떤 것들을 고려해야 할까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Error Handling은 결국 'Error 상황 발생'부터 'Error 상황 종료'까지의 흐름이라고 생각합니다. (당연한 소리)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 이 시작부터 끝까지의 흐름이 중구난방이었지만, 앞으로는 체계를 잡고 싶습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;882&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTdJKn/btrhFjwccYd/zulzbkCFZZGx7GsOOhYq7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTdJKn/btrhFjwccYd/zulzbkCFZZGx7GsOOhYq7k/img.png&quot; data-alt=&quot;추상적으로 표현한 &amp;amp;#39;Error 상황 발생&amp;amp;#39;부터 &amp;amp;#39;Error 상황 종료&amp;amp;#39;까지의 흐름. 왼쪽은 중구난방 흐름, 오른쪽은 체계 잡힌 흐름.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTdJKn/btrhFjwccYd/zulzbkCFZZGx7GsOOhYq7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTdJKn%2FbtrhFjwccYd%2FzulzbkCFZZGx7GsOOhYq7k%2Fimg.png&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;882&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;추상적으로 표현한 'Error 상황 발생'부터 'Error 상황 종료'까지의 흐름. 왼쪽은 중구난방 흐름, 오른쪽은 체계 잡힌 흐름.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 체계를 잡기 위해서는 어떤 것들을 고려해야할지 머리에 떠오르는 것들을 정리해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Error 발생원&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 Error&lt;/li&gt;
&lt;li&gt;API Error 응답&lt;/li&gt;
&lt;li&gt;랜더링 오류&lt;/li&gt;
&lt;li&gt;이벤트 핸들러에서의 로직 오류&lt;/li&gt;
&lt;li&gt;img, script 등의 리소스 요청 오류&lt;/li&gt;
&lt;li&gt;예측 불가한 런타임 Error&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Error 발생원에 따라서 Error를 감지할 수 있는 위치와 Error 내용이 다르기 때문에 발생원에 따라서 각기 다른 흐름으로 처리가 시작되어야 한다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Error 상황 종료 방법&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Alert&lt;/li&gt;
&lt;li&gt;페이지 이동 (ex. 로그인 페이지로 이동)&lt;/li&gt;
&lt;li&gt;동일 페이지에서 후처리 로직 실행 (ex. 화면에 Error 메세지 표시, 내부 State 변경)&lt;/li&gt;
&lt;li&gt;Error Reporting&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Error가 발생하면 거의 대부분의 경우에는 사용자에게 추가적인 액션을 줘야한다고 생각합니다. 그렇지 않으면 사용자는 무작정 기다리다가 결국 '이거 왜이래. 별로네' 생각하고 서비스를 떠나버릴 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 입장에서는 Error 상황을 종료하는 방법이 정해져 있어야 사람이 바뀌거나 세월이 흘러도 항상 일관적인 Error 상황 종료를 할 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막에 넣은 Error Reporting은 단독으로 수행하면 안된다고 생각합니다. Error Reporting은 개발자가 사용자에게서 발생하는 Error 정보를 확인하기 위한 방법일 뿐이고 사용자가 화면에서 확인할 수 있는 액션은 없기 때문에 다른 종료 방법과 함께 수행되야 한다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Error 처리 로직을 작성할 수 있는 위치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Error 발생원 중에서 가장 예측 가능한 발생원은 Network, API Error라고 생각합니다. 저는 axiox와 react-query 라이브러리를 사용하고 있는데요. 이러한 상황에서 Error 처리 로직을 작성할 수 있는 위치는 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;axios의 interceptors - 모든 XHR 성공 또는 실패 응답에 대해서 전역적인 로직을 추가할 수 있습니다.&lt;/li&gt;
&lt;li&gt;react-query의 default error handler - 지역적으로 error handler를 설정하지 않은 모든 XHR에 대해서 실패 응답 발생 시 수행되는 handler&lt;/li&gt;
&lt;li&gt;react-query의 error handler - 지역적으로 설정한 error handler&lt;/li&gt;
&lt;li&gt;react의 ErrorBoundary - react를 이용할 때 랜더링 과정에서 발생하는 Error를 감지할 수 있습니다.&lt;/li&gt;
&lt;li&gt;window.onerror - 브라우저에서 발생하는 모든 error에 대한 핸들러를 등록할 수 있습니다.&lt;/li&gt;
&lt;li&gt;try-catch&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 목록 중에는 전역적인 처리를 할 수 있는 부분도 있고 지역적인 처리만 할 수 있는 부분도 있습니다. Error 처리 방법에 따라서 전역적으로 처리하는게 좋은 경우가 있고 지역적으로 처리하는게 좋은 경우가 있을 것입니다. 이 기준이 세워져 있어야 세월이 흘러도 적절한 위치에서 Error가 처리 될 것입니다. 예를 들어서, HTTP 401 Error는 어떤 API였든간에 인증오류이기 때문에 전역적 처리 로직에서 처리하는게 좋습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 고민과 구체화 할 것들을 풀어놓고 하나씩 리서치하면서 다음 포스팅에서 실제 프로젝트에 적용할 수 있는 원칙과 코드를 정리해보겠습니다.&lt;/p&gt;</description>
      <category>TECH</category>
      <category>axios</category>
      <category>Error Handling</category>
      <category>Error 처리</category>
      <category>Javascript</category>
      <category>react-query</category>
      <category>에러 처리</category>
      <category>에러 핸들링</category>
      <category>자바스크립트</category>
      <author>walkinpcm</author>
      <guid isPermaLink="true">https://walkinpcm.tistory.com/21</guid>
      <comments>https://walkinpcm.tistory.com/21#entry21comment</comments>
      <pubDate>Wed, 13 Oct 2021 22:11:41 +0900</pubDate>
    </item>
    <item>
      <title>AWS S3에 파일을 업로드 하기 위한 Pre-signed URL과 Pre-signed POST</title>
      <link>https://walkinpcm.tistory.com/20</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 흔히 파일서버로 AWS S3를 이용합니다. 그래서 파일 업로드 기능을 만들때 S3에 어떻게 파일을 업로드 할지 고민하게 되는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Front-end 입장에서는 S3에 파일을 업로드 하는 방법으로 크게 2가지를 생각할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Front-end에서 AWS SDK를 이용해서 직접 업로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- API 서버에 파일을 전달하고 API 서버에서 S3에 업로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 방법은 AWS SDK를 써서 S3이용이 가능해야하기 때문에 Front-end 코드에서 AWS Access Key와 Secret Key를 알고 있어야합니다. 코드에 하드코딩 하지 않더라도 브라우저에서 AWS SDK를 이용하는 시점에는 결국 자바스크립트에서 Key 정보를 가지고 있어야합니다. 즉, Front-end에서 AWS SDK를 이용하는 방법은 Key 정보가 브라우저에서 노출 될 수 있고 악성 해커가 이를 이용하면 나의 S3 리소스를 자유롭게 이용할 수 있는 위험이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 방법은 API 서버에서 파일을 업로드하기 때문에 AWS Access Key와 Secert Key 정보를 서버가 알고있으면 돼서 Key 정보가 노출되는 위험이 없습니다. 하지만 파일 자체가 서버를 통하기 때문에 서버의 리소스를 사용하게되고 과도한 업로드 작업이 생기면 서버에 과부하가 걸리게 될 수 있습니다. 또한, 서버를 한번 거쳐가는 지연시간이 생기게 됩니다.&lt;/p&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로 업로드 해야할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에서는 S3에 객체를 업로드하고 다운로드 하기 위한 방법으로 Pre-signed URL 기능을 지원하고 있습니다.&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;Pre-signed URL 이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pre-signed URL이란 말그대로 '미리 서명된 URL'입니다. 여기서 URL은 S3 객체에 대한 접근 주소입니다. 그런데 단순히 객체의 저장 위치만을 가리키는게 아니라 Pre-signed URL을 이용해서 객체에 접근할 수 있는 권한 정보와 접근 가능 기간 등의 정보를 담고 있습니다. 그래서 특정 Pre-signed URL을 이용해서 수행할 수 있는 작업이 이미 제한되어 있고 객체에 접근 가능한 기간(Expire Time)이 지나면 더이상 해당 URL은 사용할 수 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1631100998537&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Pre-signed URL 예시
&quot;
https://BUCKETURL/test.txt?
  X-Amz-Algorithm=AWS4-HMAC-SHA256
  &amp;amp;X-Amz-Credential=&amp;lt;YOUR_ACCESS_KEY_ID&amp;gt;/20160115/ap-northeast-2/s3/aws4_request&amp;amp;
  &amp;amp;X-Amz-Date=20160115T000000Z
  &amp;amp;X-Amz-Expires=86400
  &amp;amp;X-Amz-SignedHeaders=host
  &amp;amp;X-Amz-Signature=&amp;lt;SIGNATURE_VALUE&amp;gt;
&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;Front-end에서는 Pre-signed URL로 HTTP Get 요청을 하면 객체를 다운로드 할 수 있고, 파일을 body에 담아서 HTTP Put 요청을 하면 파일을 업로드 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pre-signed URL을 이용하여 파일을 업로드하는 절차는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Front-end에서 API 서버로 파일 업로드를 위한 Pre-signed URL 생성 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. API 서버에서 AWS Key 정보를 이용해서 Pre-signed URL을 생성하고 Front-end로 응답&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Front-end에서는 전달받은 Pre-signed URL을 이용해서 HTTP Put 요청으로 S3에 파일 업로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pre-signed URL을 이용한 파일 업로드는 Front-end에서 AWS S3로 바로 파일을 업로드 할 수 있으면서도 AWS Key 정보를 API 서버에 숨겨져 있어서 보안 위험을 줄일 수 있습니다.&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;그렇다면 Pre-signed POST 방식은 뭐지?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에서는 S3에 파일을 업로드 하기 위해서 Pre-signed URL이외에 Pre-signed POST 방식도 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Pre-signed URL은 HTTP Get, Put 요청으로 각각 파일 다운로드와 업로드를 할 수 있는 반면에, Pre-signed POST는 이름 그대로 HTTP Post 방식으로 파일을 업로드만 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pre-signed URL은 권한, 시간 등의 정보가 URL의 query string(url parameter)으로 붙습니다. 그러나 Pre-signed POST는 파일을 업로드 할 때 FormData에 파일과 함께 권한, 시간 등의 정보도 넣습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pre-signed POST를 이용하여 파일을 업로드하는 절차는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Front-end에서 API 서버로 파일 업로드를 위한 Pre-signed POST 정보 생성 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. API 서버에서 AWS Key 정보를 이용해서 Pre-signed POST 정보를 생성하고 Front-end로 응답&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Front-end에서는 전달받은 Pre-signed POST 정보를 FormData에 추가하고 (물론 파일도 FormData에 넣고) Request Body에 담아서 HTTP Post 요청으로 S3에 파일 업로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631102771024&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Pre-signed POST 정보 예시
{
  'url': 'https://mybucket.s3.amazonaws.com',
  'fields': {
    'acl': 'public-read',
    'key': 'mykey',
    'signature': 'mysignature',
    'policy': 'mybase64 encoded policy'
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시를 기준으로, fields 객체에 있는 정보들을 모두 FormData에 추가하여 url에 명시된 주소로 HTTP Post 요청을 보내면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참고자료&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://aws.amazon.com/ko/blogs/korea/aws-api-call-2-s3-pre-signed-url/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://aws.amazon.com/ko/blogs/korea/aws-api-call-2-s3-pre-signed-url/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.generate_presigned_post&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.generate_presigned_post&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TECH</category>
      <category>AWS</category>
      <category>S3</category>
      <author>walkinpcm</author>
      <guid isPermaLink="true">https://walkinpcm.tistory.com/20</guid>
      <comments>https://walkinpcm.tistory.com/20#entry20comment</comments>
      <pubDate>Wed, 8 Sep 2021 21:18:49 +0900</pubDate>
    </item>
    <item>
      <title>Jira 보드에 Story 이슈 기준으로 스웜레인 만드는 방법</title>
      <link>https://walkinpcm.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 참여하는 프로젝트에서 과제 관리를 Jira에서 하기로 하였고, Epic-Story-Task 3가지 이슈 타입을 주요하게 사용하기로 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Epic 이슈 하위에 User Story 단위로 Story 이슈를 만들기로 하였고, Story의 목표를 달성하기 위한 실무 작업들을 Task 이슈로 다루기로 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 따라서 효과적으로 Task의 진행상황을 보기 위해서 보드에서 Story 이슈 단위로 스웜레인을 구성하여 Story 이슈별 Task 상태를 확인하고 싶었습니다.&lt;/p&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가지 문제가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) Story 이슈에는 Epic 이슈처럼 자식 이슈를 연결 할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 스웜레인 기준에 기본적으로 Story 이슈가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 가지 문제는 다행히 해결할 수 있는 대안이 있었고, 그 방법은 각각 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 이슈 Link Type에 부모/자식 관계를 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 스웜레인 기준을 '쿼리'로 선택해서 직접 스웜레인을 구성하고 싶은 Story 이슈별로 등록한다.&lt;/p&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;&lt;b&gt;(1) 이슈 Link Type에 부모/자식 관계를 추가한다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Story 이슈와 Task 이슈를 부모-자식 관계를 갖게 하기 위해서 이슈 Link Type에 아래 2가지를 추가하고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- is parent of&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- is child of&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이슈 LInk Type은 이슈 설정에서 관리할 수 있는데요. 메뉴를 찾기가 쉽지 않더라구요. 어떻게든 찾아가는 방법을 알게 되긴했지만, 편하게 찾아가려고 페이지 경로를 기록해둡니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이슈 Link Type 설정 페이지: {Jira 프로젝트 호스트 주소}/secure/admin/ViewLinkTypes!default.jspa&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;설정 페이지만 잘 찾아가면 Link Type을 추가하는 것은 쉬웠습니다.&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;934&quot; data-origin-height=&quot;556&quot; width=&quot;437&quot; height=&quot;260&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/npkR0/btq9DvKreYm/JkUEKg3R9rombR1FKzQWt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/npkR0/btq9DvKreYm/JkUEKg3R9rombR1FKzQWt0/img.png&quot; data-alt=&quot;(예시) 이슈 Link Type 추가 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/npkR0/btq9DvKreYm/JkUEKg3R9rombR1FKzQWt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnpkR0%2Fbtq9DvKreYm%2FJkUEKg3R9rombR1FKzQWt0%2Fimg.png&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;556&quot; width=&quot;437&quot; height=&quot;260&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;(예시) 이슈 Link Type 추가 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;(2) 스웜레인 기준을 '쿼리'로 선택해서 직접 스웜레인을 구성하고 싶은 Story 이슈별로 등록한다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스웜레인 기준을 '쿼리'로 선택하면 직접 JQL을 작성하여 스웜레인을 구성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 하고 싶은 것은 Story 이슈별로 스웜레인을 만들고, 해당 스웜레인에는 각 Story 이슈와 부모-자식으로 연결되어 있는 Task 이슈들을 보고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 달성하기 위해서 아래와 같이 JQL을 작성하였습니다. 테스트를 위해서 2개의 스웜레인을 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심은 JQL에서 linkedIssues 구문입니다. 이 구문을 이용하면 이슈간의 관계를 기반으로 이슈를 필터링 할 수 있습니다.&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;1402&quot; data-origin-height=&quot;184&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmgOBt/btq9zF1zCxx/MkrGLFzA0hQSzfqJhFQgX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmgOBt/btq9zF1zCxx/MkrGLFzA0hQSzfqJhFQgX1/img.png&quot; data-alt=&quot;(예시) linkedIssues 구문을 이용한 JQL로 스웜레인 구성하기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmgOBt/btq9zF1zCxx/MkrGLFzA0hQSzfqJhFQgX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmgOBt%2Fbtq9zF1zCxx%2FMkrGLFzA0hQSzfqJhFQgX1%2Fimg.png&quot; data-origin-width=&quot;1402&quot; data-origin-height=&quot;184&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;(예시) linkedIssues 구문을 이용한 JQL로 스웜레인 구성하기&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;linkedIssues 구문의 사용법은 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1626265317030&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;issue in linkedIssues({기준 이슈 식별자}, {Link Type})

ex. issue in linkedIssues(JIR-7, &quot;is parent of&quot;)
의미. JIR-7 이슈가 'is parent of' type으로 연결하고 있는 이슈들&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;이렇게 하여 Story 이슈를 기반으로 스웜레인이 구성된 보드를 만들 수 있었습니다.&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;1088&quot; data-origin-height=&quot;1068&quot; width=&quot;438&quot; height=&quot;430&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EbeyZ/btq9vjrj2xn/6xYLQEvwGfCaK7kt0ufWXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EbeyZ/btq9vjrj2xn/6xYLQEvwGfCaK7kt0ufWXK/img.png&quot; data-alt=&quot;(예시) Story 이슈를 기준으로 구성한 보드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EbeyZ/btq9vjrj2xn/6xYLQEvwGfCaK7kt0ufWXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEbeyZ%2Fbtq9vjrj2xn%2F6xYLQEvwGfCaK7kt0ufWXK%2Fimg.png&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;1068&quot; width=&quot;438&quot; height=&quot;430&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;(예시) Story 이슈를 기준으로 구성한 보드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>TECH</category>
      <category>jira</category>
      <category>swimlane</category>
      <category>스웜레인</category>
      <author>walkinpcm</author>
      <guid isPermaLink="true">https://walkinpcm.tistory.com/19</guid>
      <comments>https://walkinpcm.tistory.com/19#entry19comment</comments>
      <pubDate>Wed, 14 Jul 2021 21:32:28 +0900</pubDate>
    </item>
    <item>
      <title>AWS CloudFront의 Origin으로 S3를 사용할 때, REST API 엔드포인트를 입력하는 것과 웹사이트 엔드포인트를 입력하는 것의 차이</title>
      <link>https://walkinpcm.tistory.com/18</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AWS S3와 CloudFront를 이용해서 정적 웹사이트를 호스팅하는 구성은 이제 무척 보편화 되었다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 Vue.js나 React.js 기반의 CSR(Client-side Rendering) 프로젝트를 배포하는 경우에 S3, CloudFront 조합을 많이 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(최근에는 &lt;a href=&quot;https://walkinpcm.tistory.com/11&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Amplify Console&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;처음에는 S3의 웹호스팅 기능을 활성화하고 CloudFront 배포를 생성할 때 Origin에 S3의 웹사이트 엔드포인트를 직접 입력하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 나중에는 CloudFront의 Origin에 S3의 Rest API 엔드포인트를 넣고 OAI(Origin Access Identity)를 이용해서 S3에 CloudFront만 접근가능하도록 제한할 수 있는 방법을 알게되었습니다.&lt;/p&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가지 방법(Origin에 S3 웹사이트 엔드포인트 넣는 방법, Origin에 S3 REST API 엔드포인트 넣는 방법)은 몇가지 차이점이 있어서 이번 글에서 정리해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;용어의 의미 정리&lt;/b&gt;&lt;br /&gt;- S3 웹사이트 엔드포인트&lt;br /&gt;&amp;nbsp; &amp;nbsp; - S3의 웹호스팅 기능을 활성화 하면 생성되는 웹사이트 주소&lt;br /&gt;&amp;nbsp; &amp;nbsp; - ex) http://DOC-EXAMPLE-BUCKET.s3-website-region.amazonaws.com&lt;br /&gt;- S3 REST API 엔드포인트&lt;br /&gt;&amp;nbsp; &amp;nbsp; - S3 버킷에 저장되는 객체마다 접근 가능한 URL이 부여되는데, 이때의 Host 주소&lt;br /&gt;&amp;nbsp; &amp;nbsp; - ex) https://bucketname.s3.Region.amazonaws.com&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;S3의 웹호스팅 기능 활성화의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- S3 웹사이트 엔드포인트 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 버킷 설정에서 웹호스팅 기능을 활성화 해줘야합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;660&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1wk4J/btq7Z15eyqW/lGlxPtVGuWusgLy97QL7k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1wk4J/btq7Z15eyqW/lGlxPtVGuWusgLy97QL7k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1wk4J/btq7Z15eyqW/lGlxPtVGuWusgLy97QL7k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1wk4J%2Fbtq7Z15eyqW%2FlGlxPtVGuWusgLy97QL7k0%2Fimg.png&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;660&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;&lt;b&gt;- S3 REST API 엔드포인트 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 버킷의 웹호스팅 기능을 활성화할 필요가 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;336&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/soTFf/btq7VZOPq71/snKkaKkW6LsxyFLF0cIkRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/soTFf/btq7VZOPq71/snKkaKkW6LsxyFLF0cIkRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/soTFf/btq7VZOPq71/snKkaKkW6LsxyFLF0cIkRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsoTFf%2Fbtq7VZOPq71%2FsnKkaKkW6LsxyFLF0cIkRk%2Fimg.png&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;336&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;h2 data-ke-size=&quot;size26&quot;&gt;CloudFront의 Origin 입력 방법의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- S3 웹사이트 엔드포인트 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CloudFront 배포를 생성할 때, Origin에 웹사이트 엔드포인트에서 프로토콜(http://) 부분을 제거하고 직접 복사/붙여넣기 해야합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;98&quot; width=&quot;600&quot; height=&quot;47&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Arnjn/btq7YpMMs2W/uXRrpHi4ti2uYQDRtpsIJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Arnjn/btq7YpMMs2W/uXRrpHi4ti2uYQDRtpsIJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Arnjn/btq7YpMMs2W/uXRrpHi4ti2uYQDRtpsIJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FArnjn%2Fbtq7YpMMs2W%2FuXRrpHi4ti2uYQDRtpsIJ0%2Fimg.png&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;98&quot; width=&quot;600&quot; height=&quot;47&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;&lt;b&gt;- S3 REST API 엔드포인트 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CloudFront 배포를 생성할 때, Origin에 커서를 찍었을 때 나타나는 버킷 목록에서 선택합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;604&quot; width=&quot;500&quot; height=&quot;276&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3N5Ej/btq7XrRDKpJ/LusVZrCLvyFqgfiVKqeyMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3N5Ej/btq7XrRDKpJ/LusVZrCLvyFqgfiVKqeyMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3N5Ej/btq7XrRDKpJ/LusVZrCLvyFqgfiVKqeyMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3N5Ej%2Fbtq7XrRDKpJ%2FLusVZrCLvyFqgfiVKqeyMk%2Fimg.png&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;604&quot; width=&quot;500&quot; height=&quot;276&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;h2 data-ke-size=&quot;size26&quot;&gt;웹사이트 접속 방법의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- S3 웹사이트 엔드포인트 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3의 웹호스팅 기능을 활성화 했을 때 나오는 웹사이트 엔드포인트로 접속 가능하고, CloudFront 배포를 생성할 때 생기는 엔드포인트로도 접속 가능합니다. 즉, 웹사이트 접속을 2가지 주소로 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- S3 REST API 엔드포인트 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST API 엔드포인트를 Origin으로 입력하면, OAI(Origin Access Identity)를 이용해서 S3에 접근할 수 있는 권한을 CloudFront에만 부여할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면, 웹사이트 접속을 위해서는 CloudFront 배포를 생성할 때 생기는 엔드포인트로만 웹사이트에 접속 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 REST API 엔드포인트를 사용할 때는 S3 버킷의 웹호스팅 기능을 활성화 하지 않기 때문에 접속 방법이 CloudFront 엔드포인트 밖에 없긴 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹서버인지 아닌지의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- S3 웹사이트 엔드포인트 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 웹호스팅 기능을 사용한다는 것은 웹서버를 가동한다는 것입니다. 웹서버의 특징 중 하나는 브라우저의 주소창에 입력한 주소가 html 확장자로 끝나지 않는다면 자동으로 해당 경로 하위의 index.html 파일을 반환한다는 것입니다. 그래서 CloudFront 배포로 생성된 엔드포인트를 사용하더라도 Origin이 웹서버이기 때문에 동일한 행동을 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서, https://abc.com/about 을 브라우저 주소창에 입력한다면 자동으로 웹서버의 about 폴더 하위의 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;&lt;b&gt;- S3 REST API 엔드포인트 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3 REST API 엔드포인트를 CloudFront Origin으로 설정한 경우에는 CloudFront가 Origin(S3)로 객체를 요청하는게 웹서버로 요청하는게 아니라 정말 객체 저장소에 객체를 요청하는 것입니다. 그래서 Host 뒷 부분의 경로(= 객체 Key)와 매칭되는 실제 객체가 없으면 에러를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, CSR 프로젝트를 S3에 올려서 사용할 때는 거의 모든 경로에 대해서 실제 객체가 존재하지 않을 것이라서 항상 에러가 발생하는데요. 객체가 존재하지 않아서 발생하는 에러를 방지하기 위해서 CloudFront 설정에서 403, 404 에러 발생시 /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;h4 data-ke-size=&quot;size20&quot;&gt;참고링크&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CloudFront를&amp;nbsp;사용하여&amp;nbsp;Amazon&amp;nbsp;S3에&amp;nbsp;호스팅되는&amp;nbsp;정적&amp;nbsp;웹&amp;nbsp;사이트를&amp;nbsp;제공하려면&amp;nbsp;어떻게&amp;nbsp;해야&amp;nbsp;합니까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - &lt;a href=&quot;https://aws.amazon.com/ko/premiumsupport/knowledge-center/cloudfront-serve-static-website/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://aws.amazon.com/ko/premiumsupport/knowledge-center/cloudfront-serve-static-website/&lt;/a&gt;&lt;/p&gt;</description>
      <category>TECH</category>
      <category>AWS</category>
      <category>CloudFront</category>
      <category>origin</category>
      <category>S3</category>
      <category>Static Web Hosting</category>
      <category>정적웹호스팅</category>
      <author>walkinpcm</author>
      <guid isPermaLink="true">https://walkinpcm.tistory.com/18</guid>
      <comments>https://walkinpcm.tistory.com/18#entry18comment</comments>
      <pubDate>Wed, 23 Jun 2021 23:08:59 +0900</pubDate>
    </item>
    <item>
      <title>AWS Amplify Console에서 monorepo를 연결해서 사용할 때의 주의사항</title>
      <link>https://walkinpcm.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 AWS Amplify Console을 이용해서 Frontend 프로젝트를 웹호스팅 하는 방법을 정리한 적이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(링크: &lt;a href=&quot;https://walkinpcm.tistory.com/11&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;walkinpcm.tistory.com/11&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;비교적 최근에는 Amplify Console에서 Git Repository를 연결할 때 monorepo 임을 선택할 수 있는 설정이 생겼습니다. 저도 최근에 monorepo라고 설정하여 Amplify Console을 사용해 봤는데, 몇가지 인지해야하는 주의사항이 발견되어서 정리해봅니다.&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;(1) 스크립트의 실행 위치는 package directory 입니다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Amplify Console에 monorepo의 git repo 자체를 연결하기 때문에 빌드를 실행할 때의 directory 위치가 repo의 root directory라고 생각할 수 있습니다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;354&quot; data-ke-size=&quot;size16&quot;&gt;하지만, 빌드를 실행하는 directory 위치는 monorepo 연결 설정에서 입력한 package directory입니다. 그렇기 때문에 빌드스펙에 작성하는 빌드 스크립트는 package directory내의 package.json에 있는 스크립트를 이용해야 합니다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;354&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-renderer-start-pos=&quot;354&quot; data-ke-size=&quot;size26&quot;&gt;(2) Preview 기능은 monorepo에는 적합하지 않습니다.&lt;/h2&gt;
&lt;p data-renderer-start-pos=&quot;354&quot; data-ke-size=&quot;size16&quot;&gt;Preview 기능은 지정한 branch를 타겟으로 Pull Request(이하 PR)를 생성했을 때, PR의 마지막 커밋을 기준으로 자동으로 빌드하고 배포하여 서비스 환경을 구축 해주는 기능입니다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;354&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;659&quot; data-ke-size=&quot;size16&quot;&gt;monorepo에서 package별로 main branch 역할을 하는 브랜치를 각각 만들어서 사용한다면 문제가 없을 것입니다. 그러나, 현재 저는 monorepo에서 main branch 하나를 상시 유지 브랜치로 사용하고 있고 모든 package의 작업 브랜치는 같은 main branch를 타겟으로 PR을 생성합니다.&lt;br /&gt;이로 인해서, Amplify Console에 연결한 (가정)A package 이외의 (가정)B package의 작업 브랜치를 monorepo의 main branch로 PR을 생성했을 때도 Preview 기능이 동작하게 됩니다. 이러면 불필요한 인프라 환경이 만들어져서 비용 측면에서 비효율적입니다. 또한 Github에서 B package의 PR을 볼때 의미 없는 Preview 환경이 표시됩니다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1070&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1070&quot; data-ke-size=&quot;size16&quot;&gt;그래서 monorepo이면서 모든 package가 같은 main branch를 이용한다면, Preview 기능을 사용하지 않는 것을 추천합니다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1152&quot; data-ke-size=&quot;size16&quot;&gt;대안으로 아래의 방법을 추천합니다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1173&quot; data-ke-size=&quot;size16&quot;&gt;(1) 작업 브랜치 전용의 개발 환경이 필요하면, 작업 브랜치를 upstream remote에 push하고, 이 브랜치를 Amplify Console에서 수동으로 브랜치를 연결해서 새로운 환경을 생성합니다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1294&quot; data-ke-size=&quot;size16&quot;&gt;(2) 위의 방법이 번거롭다면, Amplify Console의 &lt;span data-renderer-mark=&quot;true&quot;&gt;Branch autodetection&lt;/span&gt;기능과 &lt;span data-renderer-mark=&quot;true&quot;&gt;Branch auto-disconnection&lt;/span&gt;기능을 이용합니다. 규칙으로, 개발환경이 필요한 브랜치는 특정 문자열로 시작하도록 정하여 autodetection의 패턴으로 등록하시면 자동으로 개발환경을 만들 수 있습니다. 작업이 끝난 브랜치는 불필요한 비용 과금을 줄이기 위해서 브랜치를 지워서 개발환경도 제거합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;블로그이미지1.png&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;608&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Sg5ap/btq4MT3GEMJ/dWTp9EwBbWcj7bN4q759N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Sg5ap/btq4MT3GEMJ/dWTp9EwBbWcj7bN4q759N1/img.png&quot; data-alt=&quot;Branch autodetection과 Branch auto-disconnection 설정 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Sg5ap/btq4MT3GEMJ/dWTp9EwBbWcj7bN4q759N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSg5ap%2Fbtq4MT3GEMJ%2FdWTp9EwBbWcj7bN4q759N1%2Fimg.png&quot; data-filename=&quot;블로그이미지1.png&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;608&quot; data-ke-mobilestyle=&quot;widthContent&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;Branch autodetection과 Branch auto-disconnection 설정 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1294&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-renderer-start-pos=&quot;1294&quot; data-ke-size=&quot;size26&quot;&gt;(3) 배포 기준이 되는 브랜치는 Auto-build 기능을 disable 해야합니다.&lt;/h2&gt;
&lt;p data-renderer-start-pos=&quot;1294&quot; data-ke-size=&quot;size16&quot;&gt;위의 (2)번 이유와 동일하게, 모든 package가 동일한 main branch를 사용하기 때문에 (가정. Amplify Console에 연결되지 않은 package)B package의 코드가 main branch에 merge 되었을 때 (가정. Amplify Console에 연결된 package)A package가 새로 빌드 됩니다. 즉, A package가 불필요하게 다시 빌드되고 배포되는 것 입니다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1815&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1815&quot; data-ke-size=&quot;size16&quot;&gt;이런 불필요한 빌드와 배포를 방지하기 위해서 auto-build 기능을 disable하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1875&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-renderer-start-pos=&quot;1875&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면, 배포가 필요할 때는 어떻게 할까요? Amplify Console의 webhooks 기능을 이용합니다. webhooks를 생성해서 이용하면 필요한 시점에 직접 빌드를 트리거 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;블로그이미지2.png&quot; data-origin-width=&quot;1798&quot; data-origin-height=&quot;434&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQafm9/btq4NGC3aKV/xRWY0lR7nGWsVBG191AOkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQafm9/btq4NGC3aKV/xRWY0lR7nGWsVBG191AOkk/img.png&quot; data-alt=&quot;Webhook 설정 화면 (아직 Webhook을 만들지 않은 상태)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQafm9/btq4NGC3aKV/xRWY0lR7nGWsVBG191AOkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQafm9%2Fbtq4NGC3aKV%2FxRWY0lR7nGWsVBG191AOkk%2Fimg.png&quot; data-filename=&quot;블로그이미지2.png&quot; data-origin-width=&quot;1798&quot; data-origin-height=&quot;434&quot; data-ke-mobilestyle=&quot;widthContent&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;Webhook 설정 화면 (아직 Webhook을 만들지 않은 상태)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>TECH</category>
      <category>Amplify Console</category>
      <category>AWS</category>
      <category>frontend</category>
      <category>monorepo</category>
      <author>walkinpcm</author>
      <guid isPermaLink="true">https://walkinpcm.tistory.com/17</guid>
      <comments>https://walkinpcm.tistory.com/17#entry17comment</comments>
      <pubDate>Wed, 12 May 2021 20:56:11 +0900</pubDate>
    </item>
  </channel>
</rss>