<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>매트의 개발 블로그</title>
    <link>https://kkangdda.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 21:06:15 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>kkangdda</managingEditor>
    <image>
      <title>매트의 개발 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/3310890/attach/140ad0e9b61241138d126c79dd652430</url>
      <link>https://kkangdda.tistory.com</link>
    </image>
    <item>
      <title>[test tool 2-2] Jest+React+TypeScript - 이미 개발된 코드에 unit test를 적용해보자 (+코드 리팩토링은 덤)</title>
      <link>https://kkangdda.tistory.com/131</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;이전 글에서 사용자 시나리오 &amp;amp; 테스트 코드 작성에 대해 이야기하였다,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;이 글에서는 3번 테스트 코드를 활용하여 코드 리팩토링을 한 경험에 대해 써보고자 한다!&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;기존 코드 &amp;amp; 기획서를 참고하여 사용자 시나리오를 작성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;테스트 코드를 작성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;테스트를 돌려보며 안전하게 코드 리팩토링를 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;총 3개의 테스트 파일이 있고, 이는 시나리오대로 나눈 것이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;모든 파일에 대한 테스트를 돌려보면 아래와 같이 나온다 (all passed!)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1666&quot; data-origin-height=&quot;2648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chnZqB/btszXFmFGfL/2IPQrB62bxv3l4pMh7jDY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chnZqB/btszXFmFGfL/2IPQrB62bxv3l4pMh7jDY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chnZqB/btszXFmFGfL/2IPQrB62bxv3l4pMh7jDY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchnZqB%2FbtszXFmFGfL%2F2IPQrB62bxv3l4pMh7jDY1%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;607&quot; height=&quot;965&quot; data-origin-width=&quot;1666&quot; data-origin-height=&quot;2648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;u&gt;2개 이상에서 똑같이 쓰이고 있는 코드가 있는데, 이를 공통 컴포넌트로 빼서 코드 리팩토링을 하려고 한다.&lt;/u&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;원래 같았으면 코드수정을 한 뒤, 하나하나 눌러보며 UI 테스트를 했었는데,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;간단한 화면이면 테스트도 금방 끝나겠지만 이번엔 유저 이벤트에 따라 체크해야 하는 사항이 워낙 많은 화면이라서 테스트가 꽤 오래 걸린다.. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;그러므로! 이번에는 열심히 짠 테스트 코드를 믿고 코드 리팩토링을 했다!&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;1. 코드 리팩토링 하기&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2110&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p7c6n/btszQECPb7R/lEUkMKz21cjXEhcUJ2YJ80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p7c6n/btszQECPb7R/lEUkMKz21cjXEhcUJ2YJ80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p7c6n/btszQECPb7R/lEUkMKz21cjXEhcUJ2YJ80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp7c6n%2FbtszQECPb7R%2FlEUkMKz21cjXEhcUJ2YJ80%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;617&quot; height=&quot;902&quot; data-origin-width=&quot;2110&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;약 100줄에 달하는 코드를 공통 컴포넌트로 빼버리기 완료☑️&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;2. 테스트 코드 돌려보기&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;유저 이벤트에 대한 테스트 코드를 작성했기 때문에, 'jest test' 한 번으로 리팩토링한 코드를 점검할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2140&quot; data-origin-height=&quot;1468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XtMsP/btszYE1R9Is/1JdjrPCzPA0DtU4wewXUEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XtMsP/btszYE1R9Is/1JdjrPCzPA0DtU4wewXUEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XtMsP/btszYE1R9Is/1JdjrPCzPA0DtU4wewXUEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXtMsP%2FbtszYE1R9Is%2F1JdjrPCzPA0DtU4wewXUEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;514&quot; data-origin-width=&quot;2140&quot; data-origin-height=&quot;1468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;역시.. 테스트 코드가 빛을 발했다  ✨&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;에러 내용을 살펴보면, disabled 되어야 하는 element가 disabled 되지 않았다고 말해주고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;3. 테스트 결과를 토대로 코드 수정&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;disabledConditions={{
	...
	az: !isSubnetAdvanced || azList.length === 1 
}}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;테스트 description을 시나리오에 맞춰서 작성했기 때문에, 어떤 상황인지 금방 파악이 가능하다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;디버깅 시간도 줄고, 바로 코드 수정 완료☑️&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;4. 다시 테스트!&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;1240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpaO0H/btszUep44ip/BWUwKultsnY7vjGK9nGBdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpaO0H/btszUep44ip/BWUwKultsnY7vjGK9nGBdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpaO0H/btszUep44ip/BWUwKultsnY7vjGK9nGBdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpaO0H%2FbtszUep44ip%2FBWUwKultsnY7vjGK9nGBdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;581&quot; data-origin-width=&quot;1602&quot; data-origin-height=&quot;1240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;5. 기분 좋게 소스 커밋&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qjJJD/btszX1pvQd3/PqoZiMYRNaDO3FRJK6NII1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qjJJD/btszX1pvQd3/PqoZiMYRNaDO3FRJK6NII1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qjJJD/btszX1pvQd3/PqoZiMYRNaDO3FRJK6NII1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqjJJD%2FbtszX1pvQd3%2FPqoZiMYRNaDO3FRJK6NII1%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;352&quot; height=&quot;265&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;이미 개발된 코드에 대해 테스트 코드를 짜는 건 처음이기에 시간이 좀 걸렸지만,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;코드 수정을 할 때마다 느꼈던 손으로 하는 테스트에 대한 부담감 + 귀찮음이 테스트 코드로 싹 사라졌다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;또한 테스트를 해야 하는 내용이 늘어날수록 사람이 테스트하는 것에 한계가 있는데, 이것 또한 해결해 주었다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;실제로 여러 컴포넌트들이 엮어있는 화면을 개발하고 있어서 예상치 못한 사이드 이팩트가 나오는 경우가 많다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;하지만 이제는 태스트 코드가 1차적으로 검수를 해주기 때문에 테스트 코드를 수시로 돌려보며 버그를 잡아냈고 코드 안정성을 높일 수 있었다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>  ETC/Tools</category>
      <category>jest</category>
      <category>Unit Test</category>
      <author>kkangdda</author>
      <guid isPermaLink="true">https://kkangdda.tistory.com/131</guid>
      <comments>https://kkangdda.tistory.com/131#entry131comment</comments>
      <pubDate>Tue, 7 Nov 2023 18:09:38 +0900</pubDate>
    </item>
    <item>
      <title>[test tool 2-1] Jest+React+TypeScript - 이미 개발된 코드에 unit test를 적용해보자 (+코드 리팩토링은 덤)</title>
      <link>https://kkangdda.tistory.com/130</link>
      <description>&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;계기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;개발하고 있는 화면에서 유저의 이벤트에 따라 체크를 해야 하는 케이스들이 점점 늘어남에 따라 테스트 코드에 대한 필요성을 느꼈다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byoAwu/btszJ1x7QTw/v1dx7X5m9NxeuGtquzc8g1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byoAwu/btszJ1x7QTw/v1dx7X5m9NxeuGtquzc8g1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byoAwu/btszJ1x7QTw/v1dx7X5m9NxeuGtquzc8g1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyoAwu%2FbtszJ1x7QTw%2Fv1dx7X5m9NxeuGtquzc8g1%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;516&quot; height=&quot;470&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;화면 내부는 위처럼 크게 4개 컴포넌트로 구성되어 있다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그리고 컴포넌트 내부에서 발생하는 유저의 이벤트에 따라 다른 컴포넌트에 영향이 간다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;예를 들어, component 1에 있는 내용에 따라 component 3에 있는 셀렉박스 선택지가 바뀌는 등..&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유저의 시나리오가 많아지면서 코드를 수정할 때마다 직접 일일이 테스트해야하는 케이스들이 늘어났다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  (하나하나 눌러보느라 아픈 내 손가락..)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 코드 작성에 대한 필요성을 느껴질 때쯤, 아래의 글을 보게 되었다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;a href=&quot;https://techblog.woowahan.com/8942/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://techblog.woowahan.com/8942/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;단위 테스트로 복잡한 도메인의 프론트 프로젝트 정복하기(feat. Jest) | 우아한형제들 기술블로그&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;최근 저는 복잡한 도메인의 서비스를 개발하는 개발자라면 공감할 만한 문제를 겪고 있었습니다. 찬호 님, 서비스가 비마트일 때, 공급사 반품 상세페이지에서 공급사 계정으로 로그인한 사용&quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/8942/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HBcfE/hyUrxaoGzW/Re11TJb8YaYs6zgDPhiV30/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/FR1nw/hyUrsNGKEc/5WXZmM55wjHeJOordf7DoK/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/xZb4p/hyUrrH08DT/FsJaVlDitdK75KmZwFE1l1/img.png?width=1024&amp;amp;height=321&amp;amp;face=0_0_1024_321&quot; data-og-url=&quot;https://techblog.woowahan.com/8942/&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/8942/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/8942/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HBcfE/hyUrxaoGzW/Re11TJb8YaYs6zgDPhiV30/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/FR1nw/hyUrsNGKEc/5WXZmM55wjHeJOordf7DoK/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/xZb4p/hyUrrH08DT/FsJaVlDitdK75KmZwFE1l1/img.png?width=1024&amp;amp;height=321&amp;amp;face=0_0_1024_321');&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;단위 테스트로 복잡한 도메인의 프론트 프로젝트 정복하기(feat. Jest) | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최근 저는 복잡한 도메인의 서비스를 개발하는 개발자라면 공감할 만한 문제를 겪고 있었습니다. 찬호 님, 서비스가 비마트일 때, 공급사 반품 상세페이지에서 공급사 계정으로 로그인한 사용&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/span&gt;&lt;/figure&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; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이미 만들어진 코드를 기반으로 테스트 코드를 작성하는 점&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사용자 &amp;harr;️ 어플리케이션 간의 상호작용에 대한 테스트를 한다는 점&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;테스트 코드를 활용하여 컴포넌트 내 코드 리팩토링을 한다는 점&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;u&gt;위 세가지가 나의 상황에 딱 맞아떨어졌고.. 글을 참고하여 테스트 코드를 구축하였다!&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;test 종류&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;먼저 테스트 종류에 대해 간단하게 알아보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;617&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmJ2T8/btszKzOM1Er/9u8Y3gcnGDVH7HfwsdjIm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmJ2T8/btszKzOM1Er/9u8Y3gcnGDVH7HfwsdjIm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmJ2T8/btszKzOM1Er/9u8Y3gcnGDVH7HfwsdjIm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmJ2T8%2FbtszKzOM1Er%2F9u8Y3gcnGDVH7HfwsdjIm1%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;743&quot; height=&quot;382&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;617&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. Unit = 단위테스트&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;코드의 작은 부분을 테스트 하는 것 (ex. 함수)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;일반적으로는 Class나 Method 범위를 테스트&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. Integration = 통합테스트&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;서로 다른 시스템들의 상호작용이 잘 이뤄지는지 테스트하는 것&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Unit test와 달리 개발자가 변경할 수 없는 부분까지 묶어서 검증할 때 사용되는 테스트 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(ex. 외부 라이브러리, db)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. e2e = 종단 간 테스트&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사용자와 어플리케이션의 상호작용이 잘 이뤄지는지 테스트하는 것&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;소프트웨어의 내부 구조 보다는 비즈니스 쪽에 초점을 두어 실제 시나리오대로 잘 동작하는지 테스트&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;4. TDD&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Test-driven development&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;테스트가 주가 되어 개발하는 방법&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;적용 방식&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보통은 개발하기 전 테스트 코드 작성 후 테스트가 동작하는 코드를 작성한다.(=TDD 방식)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 나는 이미 개발된 코드를 바탕으로 단위 테스트를 추가하는 방식을 채택하였다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존 코드 &amp;amp; 기획서를 참고하여 사용자 시나리오를 작성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;테스트 코드를 작성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;테스트를 돌려보며 안전하게 코드 리팩토링를 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1. 시나리오 작성&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2346&quot; data-origin-height=&quot;1484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rVd0n/btsAaiyxaKJ/Fvyxgc9Sp9bfpWlNl9q0L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rVd0n/btsAaiyxaKJ/Fvyxgc9Sp9bfpWlNl9q0L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rVd0n/btsAaiyxaKJ/Fvyxgc9Sp9bfpWlNl9q0L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrVd0n%2FbtsAaiyxaKJ%2FFvyxgc9Sp9bfpWlNl9q0L0%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;2346&quot; height=&quot;1484&quot; data-origin-width=&quot;2346&quot; data-origin-height=&quot;1484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사용자 이벤트를 if문처럼 구분을 하여 각 이벤트에 발생하는 동작들을 작성하였다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시나리오 내용은 크게 2가지로 나누어 볼 수 있다 :&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이벤트가 발생한 컴포넌트 내부에서 일어나는 동작들 + 이로 인해 다른 컴포넌트에서 일어나는 동작들&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 말한 것처럼, 4개의 구분되어있는 컴포넌트끼리 사용자의 이벤트 동작에 따라 엮어있어서 시나리오가 꽤 복잡하게 나왔다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;u&gt;초록색으로 하이라이트된 부분이 다른 컴포넌트에 끼치는 영향들이며, 이 동작들에 대해 테스트 코드를 작성할 예정이다.&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2. 테스트 코드 작성&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;개발 환경&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1. react-testing-library&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- create-react-app 하면 기본으로 설치됨&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 테스트를 위해 실제 dom을 랜더링 하고 wrapper에 감싸여 있음&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-  DOM API 사용 가능&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 컴포넌트 내부에 있는 상태변경 로직에 따라 랜더링이 여러 번 되기 때문에 waitFor() 사용하여 화면이 랜더링 될 때까지 기다려야 함&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 랜더링 된 결과를 테스트하고 싶을 때 유용함&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;설치&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;npm install -d @testing-library/[원하는 패키지]

&quot;devDependencies&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@testing-library/dom&quot;: &quot;^9.3.3&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@testing-library/react&quot;: &quot;^11.1.0&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@testing-library/user-event&quot;: &quot;^12.1.10&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2. jest&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- react-testing-library는 jest를 기본세팅으로 사용함&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 다양한 api 제공 (&lt;/span&gt;&lt;a href=&quot;https://jestjs.io/docs/api&quot; target=&quot;_self&quot;&gt;https://jestjs.io/docs/api&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- @testing-library/jest-dom 으로 dom 객체를 활용 가능&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설치&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&quot;devDependencies&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@testing-library/jest-dom&quot;: &quot;^5.11.4&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;jest-environment-jsdom&quot;: &quot;^27.0.6&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;ts-jest&quot;: &quot;^29.0.0&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Jest config 설정&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로젝트 루트 &amp;gt; jest.config.js&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;module.exports = {
&amp;nbsp;&amp;nbsp;verbose: true,
&amp;nbsp;&amp;nbsp;testPathIgnorePatterns: ['&amp;lt;rootDir&amp;gt;/node_modules/(?!@custom-lib-name)'],
&amp;nbsp;&amp;nbsp;rootDir: '/Users/me/Documents/my-project',
&amp;nbsp;&amp;nbsp;testEnvironment: 'jsdom',
&amp;nbsp;&amp;nbsp;coveragePathIgnorePatterns: ['src/components/index.ts']
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- verbose : 개별 테스트 결과를 hierarchy로 보여줌&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- testPathIgnorePatterns : 변환하지 않는 경로 (regex 가능)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- rootDir : 루트 디렉토리&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- testEnvironment : 테스트 환경&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- coveragePathIgnorePatterns : skip 할 테스트 적용 범위 (regex 가능)&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Babel config 설정&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;yarn add --dev @babel/preset-typescript @types/jest&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로젝트 루트 &amp;gt; babel.config.js&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;module.exports = {
&amp;nbsp;&amp;nbsp;presets: [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;['@babel/preset-env', { targets: { node: 'current' } }],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;['@babel/preset-react', { runtime: 'automatic' }],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'@babel/preset-typescript'
&amp;nbsp;&amp;nbsp;]
};&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;테스트 파일 기본 구조&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;TestComponent.tsx&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import React from &quot;react&quot;;

const TestComponent = () =&amp;gt; {
&amp;nbsp;&amp;nbsp;return (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div&amp;gt;TestComponent&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;);
};

export { TestComponent };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;TestComponent.test.js&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1. 테스트하고자 하는 컴포넌트 랜더링&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const RenderTestComponet = () =&amp;gt; {
	return (&amp;lt;TestComponent /&amp;gt;);
};

let container = null;
beforeEach(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;container = document.createElement('div');
&amp;nbsp;&amp;nbsp;document.body.appendChild(container);
&amp;nbsp;&amp;nbsp;act(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;render(&amp;lt;RenderTestComponet /&amp;gt;, container);
&amp;nbsp;&amp;nbsp;});
});
afterEach(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;unmountComponentAtNode(container);
&amp;nbsp;&amp;nbsp;container.remove();
&amp;nbsp;&amp;nbsp;container = null;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1) react-dom/test-utils : act()&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;state가 변경되는 비동기 작업을 외부에서 하는 경우 act()를 사용하여 re-render 횟수를 줄인다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2) jest lifecycle :&amp;nbsp; beforeEach(), afterEach()&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;jest lifecycle 함수를 사용하여 여러 개 테스트 함수에서 같은 컴포넌트를 사용해야 할 때 같은 환경을 render 하여 테스트할 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;예시에서는 beforeEach &amp;amp; afterEach를 사용하여 매 테스트마다 같은 환경을 세팅하여 테스트하도록 하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;한 컴포넌트를 렌더링 하고 여러 테스트를 해당 컴포넌트 내에서 순차적으로 실행해야 한다면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;beforeAll(), afterAll()를 사용하면 된다.&amp;nbsp;&lt;br /&gt;나는 아래와 같이 동일한 컴포넌트 내에서 실행되어야 하는 테스트 함수들을 describe()으로 묶어주었고,&lt;br /&gt;테스트 실행 전 beforeAll(), afterAll()를 사용하여 컴포넌트를 render 해주었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699782385668&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;describe('동일한 컴포넌트를 사용하는 테스트들 묶음', () =&amp;gt; {
	let container = null;
        beforeAll(() =&amp;gt; {
          container = document.createElement('div');
          document.body.appendChild(container);
          act(() =&amp;gt; {
            render(&amp;lt;RenderTestComponet /&amp;gt;, container);
          });
        });
        afterAll(() =&amp;gt; {
          unmountComponentAtNode(container);
          container.remove();
          container = null;
        });
        describe('조건 1일 때,', () =&amp;gt; {
            it('~~해야한다.', () =&amp;gt; { ... 테스트 내용 })
        });
        describe('조건 2일 때,', () =&amp;gt; {
            it('~~해야한다.', () =&amp;gt; { ... 테스트 내용 })
        });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;조건 1,2에 대한 테스트가 총 2가지가 있다.&lt;br /&gt;먼저 TestComponent를 render 한 후, 조건 1 테스트 진행, 그리고 조건 2 테스트가 진행된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2. mocking&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1) 모듈 mocking&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699780916150&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jest.mock('react-router-dom', () =&amp;gt; ({
    useLocation: jest.fn().mockReturnValue({
      pathname: '/another-route',
      search: '',
      hash: '',
      state: null,
      key: 'abcdefghi',
    }),
}));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;React Router의 useLocation hook을 사용할 때 아래와 같은 에러 발생하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&quot;TypeError: Cannot read property 'location' of undefined&quot;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이럴 땐, jest의 mock() 함수를 사용하여 모듈을 통째로 mock 할 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 하면 useLocation()을 호출 시 내가 미리 세팅한 값이 반환된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2) 함수 mocking&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699781589157&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import fetchUserData from '@remotes/fetchUserData';
const { data: userData = {} as IUserData } = fetchUserData();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위처럼, 테스트에서 render 된 컴포넌트 내부에서 fetchUserData()라는 함수로 외부 데이터를 불러오고 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 실제 데이터가 아닌 mock 데이터를 사용하도록 하고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699781565849&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jest.mock('@remotes/fetchUserData', () =&amp;gt; {
  return jest.fn(() =&amp;gt; ({
    data: {
      name: 'test-user',
      id: 'test-user-id',
      age: 0
    }
  }));
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;TestComponent.test.js 에서 jest의 mock()를 사용해서 fetch 함수의 위치를 명시해 준 다음,&lt;br /&gt;해당 함수를 jest.fn()으로 mocking 하여 원하는 값을 사용하게 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3. 테스트 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699783238188&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;describe('테스트 1', () =&amp;gt; {
  describe('1. ~~ 했을 때', () =&amp;gt; {
    describe('1-1. ~~ 인 경우', () =&amp;gt; {
      it('~~~ 해준다.', async () =&amp;gt; {
        await act(async () =&amp;gt; {
          const element = await screen.getByTestId('info-div');
          const input = element.querySelector('input');

          await userEvent.clear(input);
          await userEvent.type(input, '입력 값');

          await waitFor(() =&amp;gt; {
            expect(screen.getByDisplayValue('입력 값')).toBeInTheDocument();
          });
        });
      });
    });
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1) describe(), it()&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;describe에서는 조건문을, it에서는 테스트 내용을 작성한다.&lt;br /&gt;참고로 describe 대신 xdescribe를 사용하면 해당 범위 내부의 테스트는 실행되지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2) &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;@testing-library/dom :&amp;nbsp;&lt;/span&gt;getByTestId()&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;되도록이면 테스트할 때 필요한 element들에는 test-id를 주어 좀 더 쉽고 정확하게 해당 element를 찾을 수 있도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3) @testing-library/user-event : userEvent&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;clear, type 등 화면 동작을 발생시킬 수 있다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;type 하기 전에 먼저 자동입력된 값이 있다면 이 값을 clear 한 후 type 해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3) @testing-library/dom : waitFor()&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;컴포넌트 내부에서 발생하는 이벤트에 따라 랜더링이 바뀌는 경우, &lt;br /&gt;waitFor()를 사용하여 화면이 랜더링 된 후 다음 동작을 실행해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;4) jest : expect()&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;조건에 만족하는지 최종적으로 확인하는 함수이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;4. script 설정&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;package.json&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699842478702&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
	&quot;test&quot; : &quot;jest&quot;,
	&quot;coverage&quot;: &quot;jest --coverage&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;그 외&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1. tsconfig에서 설정한 path alias에 대한 경로 세팅&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;import 경로가 길 경우, '@path' 형식으로 alias를 설정해 주는 경우가 많은데,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;테스트 설정 파일에서 경로에 alias로 설정한 경로가 어느 경로를 가리키는 건지 설정해줘야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;tsconfig&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699784213306&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;paths&quot;: {
      &quot;@vip-app/*&quot;: [&quot;./vip/app/*&quot;],
      &quot;@vip-pages/*&quot;: [&quot;./vip/pages/*&quot;]
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;jest.config.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699784247272&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;moduleNameMapper: {
    '^@vip-app/(.*)$': '&amp;lt;rootDir&amp;gt;/src/base/vip/app/$1',
    '^@vip-pages/(.*)$': '&amp;lt;rootDir&amp;gt;/src/base/vip/pages/$1'
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;moduleNameMapper에서 path 경로를 지정해 주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2. 특정 라이브러리 global로 설정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;테스트하고자 하는 컴포넌트 내에서 uuid를 계산하는 로직에서 crypto 라이브러리를 사용하고 있었다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그러나 jest 테스트 중에서는 해당 라이브러리를 찾지 못해 아래와 같은 에러가 났다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&quot;ReferenceError: crypto is not defined&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이를 해결하기 위해선 jest.config.js에서 해당 라이브러리를 global로 설정해 주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1699784448081&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;globals: {
    crypto: require('crypto')
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3. jest test&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1) jest --clearCache : 테스트 실행 전 캐쉬를 삭제한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2) jest --slient : 각종 warning 메세지를 skip하여 &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: justify;&quot;&gt;테스트 결과에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;표시되지 않도록 한다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3) jest test/{특정 파일 이름} : 테스트 파일이 여러개 있을 때 특정 파일만 테스트 돌릴 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3. 테스트 코드를 활용하여 코드 리팩토링&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;힘들게 짠 테스트코드가 제일로 기특하게 느껴졌던 코드 리팩토링 과정.. &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000; 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; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;a href=&quot;https://kkangdda.tistory.com/131&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kkangdda.tistory.com/131&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1699785907717&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[test tool 2-2] Jest+React+TypeScript - 이미 개발된 코드에 e2e test를 적용해보자 (+코드 리팩토링은 덤)&quot; data-og-description=&quot;이전 글에서 사용자 시나리오 &amp;amp; 테스트 코드 작성에 대해 이야기하였다, 이 글에서는 3번 테스트 코드를 활용하여 코드 리팩토링을 한 경험에 대해 써보고자 한다! 기존 코드 &amp;amp; 기획서를 참고하&quot; data-og-host=&quot;kkangdda.tistory.com&quot; data-og-source-url=&quot;https://kkangdda.tistory.com/131&quot; data-og-url=&quot;https://kkangdda.tistory.com/131&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b4dbA1/hyUuTkFKMR/Y9UIrI4vE0asZ93kxpV5E0/img.png?width=800&amp;amp;height=1170&amp;amp;face=0_0_800_1170,https://scrap.kakaocdn.net/dn/cu78yh/hyUuZrFk5j/4Ibfjv4Ph0AJkMAhypK7QK/img.png?width=800&amp;amp;height=1170&amp;amp;face=0_0_800_1170,https://scrap.kakaocdn.net/dn/bhjIVu/hyUu0D6Ima/AOqcCNjbCAb2qBRh4suAJ1/img.png?width=2140&amp;amp;height=1468&amp;amp;face=0_0_2140_1468&quot;&gt;&lt;a href=&quot;https://kkangdda.tistory.com/131&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kkangdda.tistory.com/131&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b4dbA1/hyUuTkFKMR/Y9UIrI4vE0asZ93kxpV5E0/img.png?width=800&amp;amp;height=1170&amp;amp;face=0_0_800_1170,https://scrap.kakaocdn.net/dn/cu78yh/hyUuZrFk5j/4Ibfjv4Ph0AJkMAhypK7QK/img.png?width=800&amp;amp;height=1170&amp;amp;face=0_0_800_1170,https://scrap.kakaocdn.net/dn/bhjIVu/hyUu0D6Ima/AOqcCNjbCAb2qBRh4suAJ1/img.png?width=2140&amp;amp;height=1468&amp;amp;face=0_0_2140_1468');&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;[test tool 2-2] Jest+React+TypeScript - 이미 개발된 코드에 e2e test를 적용해보자 (+코드 리팩토링은 덤)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 글에서 사용자 시나리오 &amp;amp; 테스트 코드 작성에 대해 이야기하였다, 이 글에서는 3번 테스트 코드를 활용하여 코드 리팩토링을 한 경험에 대해 써보고자 한다! 기존 코드 &amp;amp; 기획서를 참고하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kkangdda.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>  ETC/Tools</category>
      <category>jest</category>
      <category>uinit test</category>
      <author>kkangdda</author>
      <guid isPermaLink="true">https://kkangdda.tistory.com/130</guid>
      <comments>https://kkangdda.tistory.com/130#entry130comment</comments>
      <pubDate>Sun, 5 Nov 2023 20:31:23 +0900</pubDate>
    </item>
    <item>
      <title>isFallback으로 로딩바 보여주기</title>
      <link>https://kkangdda.tistory.com/129</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-pages&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-pages&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1696414555545&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Functions: getStaticPaths | Next.js&quot; data-og-description=&quot;Using Pages Router Features available in /pages&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-pages&quot; data-og-url=&quot;https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-pages&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bcmAzm/hyT56ku760/pMCDxj5kpRhkcNTKL396K0/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/vGazp/hyT54GZgrB/KVTre3OuSo3yeYtuWkiMS0/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-pages&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-pages&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bcmAzm/hyT56ku760/pMCDxj5kpRhkcNTKL396K0/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/vGazp/hyT54GZgrB/KVTre3OuSo3yeYtuWkiMS0/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441');&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;Functions: getStaticPaths | Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Using Pages Router Features available in /pages&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;next/router &amp;rarr; router.isFallback으로 렌더링 인지 가능&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696414569847&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const router = useRouter()

  if (router.isFallback) {
    return &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;화면 처음 진입 시, getStaticProps 함수가 끝날 때까지 isFallback=true이기 때문에 이때 로딩바를 보여준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-video-to-gif (4).gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcfAk8/btsw8VrKk5c/IEkjXuPEyKsCFBgZDxXjek/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcfAk8/btsw8VrKk5c/IEkjXuPEyKsCFBgZDxXjek/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcfAk8/btsw8VrKk5c/IEkjXuPEyKsCFBgZDxXjek/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bcfAk8/btsw8VrKk5c/IEkjXuPEyKsCFBgZDxXjek/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;300&quot; data-filename=&quot;ezgif.com-video-to-gif (4).gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;새 로고침해서 화면 진입 시 router.&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;isFallback&lt;/span&gt;을 로그 찍어보면 true -&amp;gt; false 찍히고,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;true일 때 로딩바가 표시되는 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>  React/Next.js</category>
      <category>isFallback</category>
      <category>next.js</category>
      <category>로딩바</category>
      <author>kkangdda</author>
      <guid isPermaLink="true">https://kkangdda.tistory.com/129</guid>
      <comments>https://kkangdda.tistory.com/129#entry129comment</comments>
      <pubDate>Wed, 4 Oct 2023 19:18:41 +0900</pubDate>
    </item>
    <item>
      <title>error 페이지 커스텀</title>
      <link>https://kkangdda.tistory.com/128</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에러페이지를 서버에서 제공하는 경우 비용이 증가되고, 서버에서 가져오기 때문에 느리다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  next.js에서는 static 파일로 에러페이지를 제공하고 있고 커스텀도 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFAXA9/btsv8y7dAyI/p2pL8KZErqR11nTtrV4kLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFAXA9/btsv8y7dAyI/p2pL8KZErqR11nTtrV4kLk/img.png&quot; data-alt=&quot;next.js에서 기본적으로 제공해주는 에러페이지 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFAXA9/btsv8y7dAyI/p2pL8KZErqR11nTtrV4kLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFAXA9%2Fbtsv8y7dAyI%2Fp2pL8KZErqR11nTtrV4kLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;248&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;next.js에서 기본적으로 제공해주는 에러페이지 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;404 에러 페이지 커스텀&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;pages&lt;/span&gt; 하위에 404.tsx 생성 :&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696339212736&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default () =&amp;gt; {
    return &amp;lt;div&amp;gt; 404에러 &amp;lt;/div&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빌드를 하면 404 페이지가 정적으로 생성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lgCvO/btswcLdvFCg/ufKcOZooKjaq6dHpNPcwW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lgCvO/btswcLdvFCg/ufKcOZooKjaq6dHpNPcwW0/img.png&quot; data-alt=&quot;커스텀한 에러 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lgCvO/btswcLdvFCg/ufKcOZooKjaq6dHpNPcwW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlgCvO%2FbtswcLdvFCg%2FufKcOZooKjaq6dHpNPcwW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;304&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;커스텀한 에러 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;server 에러 페이지 커스텀&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;404와 같은 client 에러는 static으로 만들어서 불필요한 서버 호출 막는 게 좋다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만, 500에러와 같은 server 에러는 보통 정적으로 제공하지 않는다.&lt;/span&gt;&lt;br /&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;이에 맞춰 error 컴포넌트로 server 에러 페이지를 커스텀하는 게 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;pages 하위에 _error.tsx 생성 :&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696339398579&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Error({ statusCode }: { statusCode: any})  {
    return (
        &amp;lt;p&amp;gt;
            {statusCode
                ? `An error ${statusCode} occurred on server`
                : 'An error occurred on client'}
        &amp;lt;/p&amp;gt;
    )
}

Error.getInitialProps = ({ res, err }: { res: any, err: any }) =&amp;gt; {
    const statusCode = res ? res.statusCode : err ? err.statusCode : 404
    return { statusCode }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;앞서 만들어 두었던 404.tsx 파일을 남겨두었기 때문에 404 에러일 때는 해당 페이지로 넘어가고, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 외 에러는 error 컴포넌트를 보여주는 방식으로 구성하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;참고로 서버에러 같은 경우는 개발모드일 때는 에러로그를 보여주기 때문에, &lt;br /&gt;커스텀한 에러페이지를 확인하고 싶으면 빌드 후 확인해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696339482314&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run build &amp;amp; npm run start&lt;/code&gt;&lt;/pre&gt;</description>
      <category>  React/Next.js</category>
      <category>next.js</category>
      <category>에러 페이지</category>
      <author>kkangdda</author>
      <guid isPermaLink="true">https://kkangdda.tistory.com/128</guid>
      <comments>https://kkangdda.tistory.com/128#entry128comment</comments>
      <pubDate>Tue, 3 Oct 2023 22:27:30 +0900</pubDate>
    </item>
    <item>
      <title>pages-router에서의 pre-rendering 적용 (정적 생성, SSR)</title>
      <link>https://kkangdda.tistory.com/127</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;pre-rendering&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Next.js의 모든 페이지는 기본적으로 사전 렌더링을 한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  더 좋은 퍼포먼스&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;  &lt;/span&gt;검색 엔진 최적화 (SEO)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;pre-rendering의 2가지 렌더링 방식&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;둘의 차이는 = 언제 html 파일을 생성하는가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1) 정적 생성 (static generation)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;0&quot;&gt;- &lt;span style=&quot;background-color: #9feec3;&quot;&gt;프로젝트 빌드하는 시점&lt;/span&gt;에 html 파일 생성 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;2&quot;&gt;- 모든 요청에 파일들을 재사용함 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 퍼포먼스 이유로 next.js는 정적 생성을 권고함&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt; - 정적 생성된 페이지들은 CDN에 캐시 됨&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 주요 함수 : getStaticProps, getStaticPath&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2) server side rendering (SSR, dynamic rendering)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;0&quot;&gt;- &lt;span style=&quot;background-color: #9feec3;&quot;&gt;매 요청마다&lt;/span&gt; html 생성 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 항상 최신 상태 유지 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;span style=&quot;text-align: start;&quot;&gt;주요 함수 :&amp;nbsp;&lt;/span&gt;getServerSideProps&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PVHAE/btswcNbkuuG/cZoZJThsBIzdJ3EjaXeK4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PVHAE/btswcNbkuuG/cZoZJThsBIzdJ3EjaXeK4k/img.png&quot; data-alt=&quot;초기에는 아무것도 없는 상태 -&amp;amp;gt; JavaScript가 로드된 이후 -&amp;amp;gt; 페이지 안 요소들을 채움&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PVHAE/btswcNbkuuG/cZoZJThsBIzdJ3EjaXeK4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPVHAE%2FbtswcNbkuuG%2FcZoZJThsBIzdJ3EjaXeK4k%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;1386&quot; height=&quot;688&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;초기에는 아무것도 없는 상태 -&amp;gt; JavaScript가 로드된 이후 -&amp;gt; 페이지 안 요소들을 채움&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7J3ev/btsv8hq1YHJ/tbLiA7wcgldKTrSzcNJeuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7J3ev/btsv8hq1YHJ/tbLiA7wcgldKTrSzcNJeuK/img.png&quot; data-alt=&quot;초기에는 사전에 만든 HTML요소들이 있음 (+meta) -&amp;amp;gt; JavaScript가 로드된 이후 -&amp;amp;gt; Link와 같이 interactive한 컴포넌트들이 채워짐 (=hydration)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7J3ev/btsv8hq1YHJ/tbLiA7wcgldKTrSzcNJeuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7J3ev%2Fbtsv8hq1YHJ%2FtbLiA7wcgldKTrSzcNJeuK%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;1386&quot; height=&quot;824&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;초기에는 사전에 만든 HTML요소들이 있음 (+meta) -&amp;gt; JavaScript가 로드된 이후 -&amp;gt; Link와 같이 interactive한 컴포넌트들이 채워짐 (=hydration)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;server side rendering 적용 전&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;api 호출 후 &amp;rarr; 페이지에 데이터를 넣어주는 방식&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2774&quot; data-origin-height=&quot;1300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NSyc8/btsv9094A6k/X4GlkTwOQyxA7qkG3fmkA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NSyc8/btsv9094A6k/X4GlkTwOQyxA7qkG3fmkA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NSyc8/btsv9094A6k/X4GlkTwOQyxA7qkG3fmkA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNSyc8%2Fbtsv9094A6k%2FX4GlkTwOQyxA7qkG3fmkA0%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;2774&quot; height=&quot;1300&quot; data-origin-width=&quot;2774&quot; data-origin-height=&quot;1300&quot;/&gt;&lt;/span&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1666&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVMBve/btsv9IBHXPU/kqXUo3kBxOegS48G26Mt91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVMBve/btsv9IBHXPU/kqXUo3kBxOegS48G26Mt91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVMBve/btsv9IBHXPU/kqXUo3kBxOegS48G26Mt91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVMBve%2Fbtsv9IBHXPU%2FkqXUo3kBxOegS48G26Mt91%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;1666&quot; height=&quot;548&quot; data-origin-width=&quot;1666&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;껍데기만 pre-rendering 된 상태이며, api 호출해서 얻은 데이터 정보는 들어가 있지 않다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1-1. &lt;/b&gt;&lt;b&gt;getStaticProps &amp;gt; &lt;/b&gt;&lt;b&gt;정적 생성 방식 사용 (목록 페이지)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f3c000; color: #000000;&quot;&gt;&amp;lt;before&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1696338012328&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;export default function Page() {

    const [list, setList] = useState([]);

    const API_URL = process.env.NEXT_PUBLIC_API_URL as string;
    const getData = () =&amp;gt; {
        Axios.get(API_URL).then((res: any) =&amp;gt; setList(res.data));
    }

    useEffect(() =&amp;gt; {
        getData();
    }, []);

 return (
     &amp;lt;div&amp;gt;
      &amp;lt;Head&amp;gt;
       &amp;lt;title&amp;gt;@제목제목@&amp;lt;/title&amp;gt;
      &amp;lt;/Head&amp;gt;
      &amp;lt;h1&amp;gt;This is Page Router Homepage!&amp;lt;/h1&amp;gt;
         &amp;lt;ItemList list={list} /&amp;gt;
     &amp;lt;/div&amp;gt;
 )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;useEffect()를 써서 페이지 진입 시, api 호출 후 데이터를 useState에 넣어주고 화면에 표시해 주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1778&quot; data-origin-height=&quot;1752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HVqCf/btsA8tmonm1/a9bPqkD3wQJTttQzSWNbv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HVqCf/btsA8tmonm1/a9bPqkD3wQJTttQzSWNbv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HVqCf/btsA8tmonm1/a9bPqkD3wQJTttQzSWNbv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHVqCf%2FbtsA8tmonm1%2Fa9bPqkD3wQJTttQzSWNbv0%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;435&quot; height=&quot;429&quot; data-origin-width=&quot;1778&quot; data-origin-height=&quot;1752&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JavaScript 차단한 상태에서는 목록 데이터가 표시되지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f3c000; color: #000000;&quot;&gt;&amp;lt;after&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1696334619079&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Page({list}: { list: any }) {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;Head&amp;gt;
                &amp;lt;title&amp;gt;@제목제목@&amp;lt;/title&amp;gt;
            &amp;lt;/Head&amp;gt;
            &amp;lt;h1&amp;gt;This is Page Router Homepage!&amp;lt;/h1&amp;gt;
            {list &amp;amp;&amp;amp; (&amp;lt;ItemList list={list}/&amp;gt;)}
        &amp;lt;/div&amp;gt;
    )
};

export const getStaticProps = async () =&amp;gt; {
    const API_URL = process.env.apiUrl as string;
    const res = await Axios.get(API_URL);
    const data = res.data;

    // 응답 값을 Post에게 props로 넘겨줄 수 있음
    return {
        props: {
            list: data,
            name: process.env.name
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'getStaticProps' 사용하여 응답 값을 props로 넘겨주어 화면에 데이터를 표시해 주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1776&quot; data-origin-height=&quot;1720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r2KNW/btswko3ka65/ZoaY3gff61QYk0CIaM5OhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r2KNW/btswko3ka65/ZoaY3gff61QYk0CIaM5OhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r2KNW/btswko3ka65/ZoaY3gff61QYk0CIaM5OhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr2KNW%2Fbtswko3ka65%2FZoaY3gff61QYk0CIaM5OhK%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;435&quot; height=&quot;421&quot; data-origin-width=&quot;1776&quot; data-origin-height=&quot;1720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JavaScript가 차단된 상태에서도 데이터가 정상적으로 표시되는 것 확인 가능하다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한, 페이지 소스 보기를 해보면 데이터들이 들어가 있다. 이는 미리 만들어진 static 한 html를 제공하고 있기 때문이다.&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;pre id=&quot;code_1696335629745&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZAyyK/btswBE5zkOC/gubWntyJjR2QyNkMWPEhtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZAyyK/btswBE5zkOC/gubWntyJjR2QyNkMWPEhtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZAyyK/btswBE5zkOC/gubWntyJjR2QyNkMWPEhtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZAyyK%2FbtswBE5zkOC%2FgubWntyJjR2QyNkMWPEhtK%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;435&quot; height=&quot;482&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;build 후, index.html이 생성되어 있는 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696335935965&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run start // 빌드된 파일 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;11111.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LQdXD/btsv8z56ilN/ICP7M6ZxLYgjP4TWMJJBxk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LQdXD/btsv8z56ilN/ICP7M6ZxLYgjP4TWMJJBxk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LQdXD/btsv8z56ilN/ICP7M6ZxLYgjP4TWMJJBxk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/LQdXD/btsv8z56ilN/ICP7M6ZxLYgjP4TWMJJBxk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;231&quot; data-filename=&quot;11111.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;index.html은 미리 만들어진 html이고, 캐싱되기 때문에 새로고침 할수록 로딩시간이 점점 짧아진다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1-2.&amp;nbsp;&lt;/b&gt;&lt;b&gt;getStaticProps + &lt;/b&gt;&lt;b&gt;getStaticPath &amp;gt; &lt;/b&gt;&lt;b&gt;정적 생성 방식 사용 (상세 페이지)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;getStaticPath를 사용해서 dynamic path를 사용할 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696336869292&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default ({item, name} : {item: any, name: string}) =&amp;gt; {
    return (
        &amp;lt;&amp;gt;
            {item &amp;amp;&amp;amp; (
                &amp;lt;&amp;gt;
                    &amp;lt;Head&amp;gt;
                        &amp;lt;title&amp;gt;{item.name}&amp;lt;/title&amp;gt;
                        &amp;lt;meta name=&quot;desc&quot; content={item.description} /&amp;gt;
                    &amp;lt;/Head&amp;gt;
                    {name} 환경임!
                    &amp;lt;Item item={item} /&amp;gt;
                &amp;lt;/&amp;gt;
            )}
        &amp;lt;/&amp;gt;
    );
}

// dynamic paths
export const getStaticPaths = async () =&amp;gt; {
    return {
        paths: [
            {params: {id: '495'}},
            {params: {id: '477'}}
        ],
        fallback: false
    };
}

export const getStaticProps = async(context: any  ) =&amp;gt; {
    const id = context.params.id;
    const API_URL = `http://makeup-api.herokuapp.com/api/v1/products/${id}.json`;
    const res = await Axios.get(API_URL);
    const data = res.data;
    return {
        props: {
            item: data,
            name: process.env.name
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;id = 495, 477로 path를 미리 지정해 주면 아래와 같이 해당하는 정적페이지가 build시 미리 생성된다. &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696337095514&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run buld&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vzeGy/btsv8ccc5AO/E6GTsZkoFSofngmAcVE7ZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vzeGy/btsv8ccc5AO/E6GTsZkoFSofngmAcVE7ZK/img.png&quot; data-alt=&quot;지정해준 id에 대한 html, json 파일이 미리 만들어져있음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vzeGy/btsv8ccc5AO/E6GTsZkoFSofngmAcVE7ZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvzeGy%2Fbtsv8ccc5AO%2FE6GTsZkoFSofngmAcVE7ZK%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;1504&quot; height=&quot;388&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;지정해준 id에 대한 html, json 파일이 미리 만들어져있음&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;+ fallback 속성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696337168948&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fallback: false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;getStaticPaths 함수 내에서 fallback을 false로 주었는데, 이렇게 하면 495, 477 외 다른 아이템을 눌렀을 시 404 에러가 뜬다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696337223110&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fallback: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;true로 지정할 경우, 미리 지정한 id 외 다른 id의 상세페이지로 들어가면 pre-rendering 목록에 해당 상세페이지를 만들어놔서 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 후부터는 정적으로 생성된 페이지를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0lmwm/btswJBgru6m/UakcoISwyOCYPeamrDhYzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0lmwm/btswJBgru6m/UakcoISwyOCYPeamrDhYzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0lmwm/btswJBgru6m/UakcoISwyOCYPeamrDhYzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0lmwm%2FbtswJBgru6m%2FUakcoISwyOCYPeamrDhYzK%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;582&quot; height=&quot;390&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ex. path에 미리 지정한 477, 495 id 외에 488에 대한 상세페이지도 정적 파일로 생성되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://kkangdda.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kkangdda.tistory.com/129&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1696414764094&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;isFallback으로 로딩바 보여주기&quot; data-og-description=&quot;https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-pages Functions: getStaticPaths | Next.js Using Pages Router Features available in /pages nextjs.org next/router &amp;rarr; router.isFallback으로 렌더링 인지 가능 const route&quot; data-og-host=&quot;kkangdda.tistory.com&quot; data-og-source-url=&quot;https://kkangdda.tistory.com/129&quot; data-og-url=&quot;https://kkangdda.tistory.com/129&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cm11gL/hyT5RVaIFf/u53XFRjNFJyDJaJ1A6pjO0/img.gif?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/b0P1DM/hyT5Tk87Xg/krkhoc97DDcWcbbeRZJ6tK/img.gif?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300&quot;&gt;&lt;a href=&quot;https://kkangdda.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kkangdda.tistory.com/129&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cm11gL/hyT5RVaIFf/u53XFRjNFJyDJaJ1A6pjO0/img.gif?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/b0P1DM/hyT5Tk87Xg/krkhoc97DDcWcbbeRZJ6tK/img.gif?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300');&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;isFallback으로 로딩바 보여주기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-pages Functions: getStaticPaths | Next.js Using Pages Router Features available in /pages nextjs.org next/router &amp;rarr; router.isFallback으로 렌더링 인지 가능 const route&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kkangdda.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2. &lt;/b&gt;&lt;b&gt;getServerSideProps &amp;gt; &lt;/b&gt;&lt;b&gt;server side rendering 방식 사용 (상세 페이지)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;title, meta에 api 호출 데이터의 name, description을 넣어서 SSR 전, 후를 비교하고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696336222727&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;&amp;gt;
  &amp;lt;Head&amp;gt;
     &amp;lt;title&amp;gt;{item.name}&amp;lt;/title&amp;gt;
     &amp;lt;meta name=&quot;desc&quot; content={item.description} /&amp;gt;
  &amp;lt;/Head&amp;gt;
     &amp;lt;Item item={item} /&amp;gt;
&amp;lt;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f3c000; color: #000000;&quot;&gt;&amp;lt;before&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1696338079978&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default () =&amp;gt; {
    const router = useRouter();
    const {id} = router.query;

    const [item, setItem] = useState({});

    const API_URL = `http://makeup-api.herokuapp.com/api/v1/products/${id}.json`;

    const getData = () =&amp;gt; {
        Axios.get(API_URL).then((res: any) =&amp;gt; setItem(res.data));
    }

    useEffect(() =&amp;gt; {
        if (id) {
            getData();
        }
    }, [id]);

    return &amp;lt;Item item={item} /&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;예전과 같이, useEffect를 써서 api 호출 후 받은 데이터를 화면에 표시해 준다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2040&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGAgOB/btswarzHsxk/VxNmWK1C7KxLvY0xnz9zI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGAgOB/btswarzHsxk/VxNmWK1C7KxLvY0xnz9zI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGAgOB/btswarzHsxk/VxNmWK1C7KxLvY0xnz9zI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGAgOB%2FbtswarzHsxk%2FVxNmWK1C7KxLvY0xnz9zI1%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;2040&quot; height=&quot;460&quot; data-origin-width=&quot;2040&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1696336288900&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;title&amp;gt;&amp;lt;/title&amp;gt;&amp;lt;meta name=&quot;desc&quot; content=&quot;&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;title, meta 모두 비어있는 상태이다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f3c000; color: #000000;&quot;&gt;&amp;lt;after&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1696336639323&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default ({item} : {item: any}) =&amp;gt; {
    return (
        &amp;lt;&amp;gt;
            &amp;lt;Head&amp;gt;
                &amp;lt;title&amp;gt;{item.name}&amp;lt;/title&amp;gt;
                &amp;lt;meta name=&quot;desc&quot; content={item.description} /&amp;gt;
            &amp;lt;/Head&amp;gt;
            &amp;lt;Item item={item} /&amp;gt;
        &amp;lt;/&amp;gt;
    );
}

export const getServerSideProps = async(context: any  ) =&amp;gt; {
    const id = context.params.id;
    const API_URL = `http://makeup-api.herokuapp.com/api/v1/products/${id}.json`;
    const res = await Axios.get(API_URL);
    const data = res.data;

    // 응답 값을 Post에게 props로 넘겨줄 수 있음
    return {
        props: {
            item: data
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'getServerSideProps' 사용하여 응답 값을 props로 넘겨주어 화면에 데이터를 표시해 주었다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2044&quot; data-origin-height=&quot;1414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmVx2M/btsv8zkJJDe/Anii0tlUkEVmU5Q1xH6NH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmVx2M/btsv8zkJJDe/Anii0tlUkEVmU5Q1xH6NH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmVx2M/btsv8zkJJDe/Anii0tlUkEVmU5Q1xH6NH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmVx2M%2Fbtsv8zkJJDe%2FAnii0tlUkEVmU5Q1xH6NH1%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;2044&quot; height=&quot;1414&quot; data-origin-width=&quot;2044&quot; data-origin-height=&quot;1414&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1696336399729&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;title&amp;gt;Maybelline Face Studio Master Hi-Light Light Booster Bronzer&amp;lt;/title&amp;gt;
&amp;lt;meta name=&quot;desc&quot; content=&quot;Maybelline Face Studio Master Hi-Light Light Boosting bronzer formula has an expert 
balance of shade + shimmer illuminator for natural glow. Skin goes 
soft-lit with zero glitz.
		For Best Results: Brush over all shades in palette and gently sweep over 
cheekbones, brow bones, and temples, or anywhere light naturally touches
 the face.&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요청 시 서버에서 데이터를 가져온 후 html을 생성해서 페이지를 보여주기 때문에, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;title과 meta에 api 호출하여 받은 데이터값이 들어가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRvbMT/btswbeAgoZT/KrCigfiskPwFxhkWyF95UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRvbMT/btswbeAgoZT/KrCigfiskPwFxhkWyF95UK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRvbMT/btswbeAgoZT/KrCigfiskPwFxhkWyF95UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRvbMT%2FbtswbeAgoZT%2FKrCigfiskPwFxhkWyF95UK%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;435&quot; height=&quot;269&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;986&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가로 JavaScript를 꺼보고 상세화면에 들어가 봤는데, 데이터가 여전히 제대로 표시되고 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3. next/link의 prefetch&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1696337587516&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Link href=&quot;/dashboard&quot; prefetch={true}&amp;gt;
      Dashboard
&amp;lt;/Link&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아이템에 링크를 걸기 위해 next/link의 Link 태그를 사용하였는데, 여기에는 prefetch라는 속성이 있다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(default=true, production모드에서만 사용 가능)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;이 기능 덕분에,&amp;nbsp;&lt;/span&gt;사용자가 보는 화면 영역(viewport) 안에 들어오는 link에 걸려있는 내용들은 모두 preload 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-video-to-gif (2).gif&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edoJaw/btswPVsejVE/tmnhKMHfBKAdplBwH1TMGk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edoJaw/btswPVsejVE/tmnhKMHfBKAdplBwH1TMGk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edoJaw/btswPVsejVE/tmnhKMHfBKAdplBwH1TMGk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/edoJaw/btswPVsejVE/tmnhKMHfBKAdplBwH1TMGk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;421&quot; height=&quot;433&quot; data-filename=&quot;ezgif.com-video-to-gif (2).gif&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어, 사용자가 스크롤하면 화면에 들어오는 link들에 대한 페이지들은 사진과 같이 정적파일로 생성된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;미리 pre-fetch 하여 정적파일로 생성하기 때문에 사용자가 실제로 클릭 시 빠른 화면 전환이 가능해진다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;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;a title=&quot;https://www.youtube.com/playlist?list=PLZKTXPmaJk8Lx3TqPlcEAzTL8zcpBz7NP&quot; href=&quot;https://www.youtube.com/playlist?list=PLZKTXPmaJk8Lx3TqPlcEAzTL8zcpBz7NP&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.youtube.com/playlist?list=PLZKTXPmaJk8Lx3TqPlcEAzTL8zcpBz7NP&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/learn/foundations/about-nextjs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://nextjs.org/learn/foundations/about-nextjs&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/&quot;&gt;https://nextjs.org/&lt;/a&gt;&lt;/p&gt;</description>
      <category>  React/Next.js</category>
      <category>next.js</category>
      <category>Pre-rendering</category>
      <category>Prefetch</category>
      <category>ssr</category>
      <category>static generagtion</category>
      <author>kkangdda</author>
      <guid isPermaLink="true">https://kkangdda.tistory.com/127</guid>
      <comments>https://kkangdda.tistory.com/127#entry127comment</comments>
      <pubDate>Tue, 3 Oct 2023 22:03:36 +0900</pubDate>
    </item>
    <item>
      <title>[CI/CD적용기3] 프로젝트 build - 로컬 vs Dockerfile vs Github Actions</title>
      <link>https://kkangdda.tistory.com/123</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Github Actions를 사용하기 앞서 세팅해줘야하는 내용은 이전 포스트 참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kkangdda.tistory.com/122&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.09.07 - [  ETC/CICD] - [CI/CD적용기2] Docker + Github Actions으로 배포 자동화&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1694252607266&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[CI/CD적용기2] Docker + Github Actions으로 배포 자동화&quot; data-og-description=&quot;목표 : - Github Actions를 사용하여 CI/CD를 구축한다 - workflow : - build my project -&amp;gt; login to my Docker hub -&amp;gt; build and push my Image - 즉, 빌드한 결과물을 Docker Image로 만들어서 내 Docker Hub으로 push 하는 일련의 작&quot; data-og-host=&quot;kkangdda.tistory.com&quot; data-og-source-url=&quot;https://kkangdda.tistory.com/122&quot; data-og-url=&quot;https://kkangdda.tistory.com/122&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/isPIW/hyTSsgMR4g/3j2CcCNGMXwDVAkOKfytDk/img.png?width=800&amp;amp;height=545&amp;amp;face=0_0_800_545,https://scrap.kakaocdn.net/dn/eGuF0T/hyTSyVzKox/vtt4IkMePyQBm4tOpqufEK/img.png?width=800&amp;amp;height=545&amp;amp;face=0_0_800_545,https://scrap.kakaocdn.net/dn/LupUQ/hyTSCXYf4G/Y7xP5eAk4nKkUVRtE0Qfg1/img.png?width=2394&amp;amp;height=1274&amp;amp;face=0_0_2394_1274&quot;&gt;&lt;a href=&quot;https://kkangdda.tistory.com/122&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kkangdda.tistory.com/122&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/isPIW/hyTSsgMR4g/3j2CcCNGMXwDVAkOKfytDk/img.png?width=800&amp;amp;height=545&amp;amp;face=0_0_800_545,https://scrap.kakaocdn.net/dn/eGuF0T/hyTSyVzKox/vtt4IkMePyQBm4tOpqufEK/img.png?width=800&amp;amp;height=545&amp;amp;face=0_0_800_545,https://scrap.kakaocdn.net/dn/LupUQ/hyTSCXYf4G/Y7xP5eAk4nKkUVRtE0Qfg1/img.png?width=2394&amp;amp;height=1274&amp;amp;face=0_0_2394_1274');&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;[CI/CD적용기2] Docker + Github Actions으로 배포 자동화&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;목표 : - Github Actions를 사용하여 CI/CD를 구축한다 - workflow : - build my project -&amp;gt; login to my Docker hub -&amp;gt; build and push my Image - 즉, 빌드한 결과물을 Docker Image로 만들어서 내 Docker Hub으로 push 하는 일련의 작&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kkangdda.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000; text-align: start;&quot;&gt;Github Action 코드를 짜다 보니, 프로젝트 빌드하는 과정을 어떤 식으로 해야지 효율적인지 궁금해졌다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000; text-align: start;&quot;&gt;빌드하는 부분을 총 3번의 다른 방식으로 구성해 보았고, Github Action의 걸린 시간을 살펴보았다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;&lt;b&gt;첫 번째 방식, 로컬 pc에서 내가 직접 프로젝트 build -&amp;gt; Github Action에서 나머지 작업 자동화&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;Dockerfile&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694246546184&quot; class=&quot;dockerfile&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# nginx 이미지를 사용합니다. 뒤에 tag가 없으면 latest 를 사용합니다.
FROM nginx

# root 에 app 폴더를 생성
RUN mkdir /app

# work dir 고정
WORKDIR /app

# work dir 에 build 폴더 생성 /app/build
RUN mkdir ./build

# host pc의 현재경로의 build 폴더를 workdir 의 build 폴더로 복사
ADD ./build ./build

# nginx 의 default.conf 를 삭제
RUN rm /etc/nginx/conf.d/default.conf

# host pc 의 nginx.conf 를 아래 경로에 복사
COPY ./nginx.conf /etc/nginx/conf.d

# 80 포트 오픈
EXPOSE 80

# container 실행 시 자동으로 실행할 command. nginx 시작함
CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;github-action.yml&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694246546184&quot; class=&quot;http&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: WORKFLOW_FOR_DOCKER

on:
  push:
    branches: [ &quot;main&quot; ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

jobs:
  # 1. 빌드 머신 준비
  build:
    runs-on: ubuntu-latest

    steps:
      # 2. 빌드 머신에 Repository Check Out
      - uses: actions/checkout@v3

      - name: Start Actions
        run: echo Start Actions!

      # 3. Docker Meta를 이용하여 생성할 이미지의 이름과 버전 정보 태깅 
      - name: Docker meta
        id: docker_meta
        uses: crazy-max/ghaction-docker-meta@v1
        with:
          images: iDaeun/daeun-portfolio-app
          tag-semver: |
            {{version}}
            {{major}}.{{minor}}

      # 4. 빌드 머신에 Docker 빌드에 필요한 사항 준비
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1
        
      # 5. 이미지를 업로드 할 내 도커허브 로그인
      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
          
      # 6. 도커 이미지 빌드하고 허브로 푸쉬
      - name: Build and push
        uses: docker/build-push-action@v2
        with:
          context: .
          file: ./Dockerfile
          platforms: linux/amd64
          push: true
          tags: ${{ steps.docker_meta.outputs.tags }}
          labels: ${{ steps.docker_meta.outputs.labels }}

      - name: Finish Git Actions
        run: |
          echo Finished All Actions  
          echo Please Check Your Docker Hub!&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;on &amp;gt; push &amp;gt; branch [&quot;main&quot;] : main 브랜치에 코드를 푸쉬하면 Github Action이 yml 파일에 정의한 작업내용에 따라 실행된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;먼저 프로젝트를 빌드해 놓을 뒤 main 브랜치에 강제로 test 코드를 푸쉬하여 Github Action이 시작되도록 하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;그 결과, 걸린 시간 : 22초&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1039&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beRt5T/btstr3B0spn/p188e8i2kO7q3PfwQyQwT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beRt5T/btstr3B0spn/p188e8i2kO7q3PfwQyQwT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beRt5T/btstr3B0spn/p188e8i2kO7q3PfwQyQwT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeRt5T%2Fbtstr3B0spn%2Fp188e8i2kO7q3PfwQyQwT1%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;1039&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1039&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;&lt;b&gt;두 번째 방식, Dockerfile 내부에서 프로젝트 build 자동화 -&amp;gt; Github Action에서 나머지 작업 자동화&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;Dockerfile&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694246546186&quot; class=&quot;dockerfile&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;## builder
FROM node:17-alpine AS builder

# work dir 고정
WORKDIR /app

COPY package-lock.json ./
COPY package.json ./
RUN npm ci

COPY . ./
RUN npm run build

# nginx 이미지를 사용합니다. 뒤에 tag가 없으면 latest 를 사용합니다.
FROM nginx

# host pc의 현재경로의 build 폴더를 workdir 의 build 폴더로 복사
COPY --from=builder /app/build /app/build

CMD [&quot;ls&quot;, &quot;-al&quot;]

# nginx 의 default.conf 를 삭제
RUN rm /etc/nginx/conf.d/default.conf
# host pc 의 nginx.conf 를 아래 경로에 복사
COPY ./nginx.conf /etc/nginx/conf.d

# 80 포트 오픈
EXPOSE 80

# container 실행 시 자동으로 실행할 command. nginx 시작함
CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;--from : 새로운 빌드 스테이지를 뜻한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;'builder'라고 정해준 스테이지에서 node를 사용해 주었고, 그 아래 새로운 스테이지에서 nginx를 사용하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;이렇게 해서 아래와 같은 순서로 실행되도록 하였다 :&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;1. 첫 번째 스테이지 (builder) : node사용하여 npm으로 프로젝트 빌드 (이전에는 내가 직접 로컬 pc에서 빌드했던 과정)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;2. 두 번째 스테이지 : builder 스테이지에서 빌드한 결과물을 COPY 하여 가져오고, nginx로 Docker Image 빌드&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;github-action.yml &amp;gt; 첫 번째 방식과 동일함&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;그 결과, 걸린 시간 : 1분 23초&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr2kzK/btstr06kLmU/b1GV8cBa6smdbIukNdDzgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr2kzK/btstr06kLmU/b1GV8cBa6smdbIukNdDzgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr2kzK/btstr06kLmU/b1GV8cBa6smdbIukNdDzgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr2kzK%2Fbtstr06kLmU%2Fb1GV8cBa6smdbIukNdDzgK%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;1032&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;&lt;b&gt;세 번째 방식,&amp;nbsp;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;Github Action에서 프로젝트 build + Docker 작업 모두 자동화&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;Dockerfile&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694246546187&quot; class=&quot;dockerfile&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# nginx 이미지를 사용합니다. 뒤에 tag가 없으면 latest 를 사용합니다.
FROM nginx

# work dir 고정
WORKDIR /app

# host pc의 현재경로의 build 폴더를 workdir 의 build 폴더로 복사
ADD ./build ./build

# nginx 의 default.conf 를 삭제
RUN rm /etc/nginx/conf.d/default.conf

# host pc 의 nginx.conf 를 아래 경로에 복사
COPY ./nginx.conf /etc/nginx/conf.d

# 80 포트 오픈
EXPOSE 80

# container 실행 시 자동으로 실행할 command. nginx 시작함
CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;두 번째 방식에서 추가해 줬던 프로젝트 빌드 과정을 삭제했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;github-action.yml&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694246546188&quot; class=&quot;http&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: WORKFLOW_FOR_DOCKER

on:
  push:
    branches: [ &quot;main&quot; ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

jobs:
  # 1. 빌드 머신 준비
  build:
    runs-on: ubuntu-latest

    steps:
      # 2. 빌드 머신에 Repository Check Out
      - uses: actions/checkout@v3

      - name: Start Actions
        run: echo Start Actions!

      ## 개선 ##
      - uses: actions/setup-node@v3
        with:
          node-version: 16.14.2
      - run: npm ci
      # - run : npm test (build 보다 먼저 test 실행)
      - run: npm run build

      - name: Log Test
        run: ls -al
        shell: bash
      #########

      # 3. Docker Meta를 이용하여 생성할 이미지의 이름과 버전 정보 태깅 
      - name: Docker meta
        id: docker_meta
        uses: crazy-max/ghaction-docker-meta@v1
        with:
          images: iDaeun/daeun-portfolio-app
          tag-semver: |
            {{version}}
            {{major}}.{{minor}}
      # 4. 빌드 머신에 Docker 빌드에 필요한 사항 준비
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1
        
      # 5. 이미지를 업로드 할 내 도커허브 로그인
      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
          
      # 6. 도커 이미지 빌드하고 허브로 푸쉬
      - name: Build and push
        uses: docker/build-push-action@v2
        with:
          context: .
          file: ./Dockerfile
          platforms: linux/amd64
          push: true
          tags: ${{ steps.docker_meta.outputs.tags }}
          labels: ${{ steps.docker_meta.outputs.labels }}

      - name: Finish Git Actions
        run: |
          echo Finished All Actions  
          echo Please Check Your Docker Hub!&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;Github Action workflow에는 아래의 코드만 추가해 주었다 :&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694246546189&quot; class=&quot;clean&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      ## 개선 ##
      - uses: actions/setup-node@v3
        with:
          node-version: 16.14.2
      - run: npm ci
      # - run : npm test (build 보다 먼저 test 실행)
      - run: npm run build

      - name: Log Test
        run: ls -al
        shell: bash
      #########&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;이는 node를 사용하여 npm install &amp;amp; build를 해주는 과정이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;두 번째 방식에서는 Dockerfile에 해당 과정을 넣어주었다면, 이번에는 Github Action에 넣어준 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;그 결과, 걸린 시간 : 1분&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H7xpF/btstzwbkTNs/8Jg9OG2jCwM8QlFKYpIzB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H7xpF/btstzwbkTNs/8Jg9OG2jCwM8QlFKYpIzB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H7xpF/btstzwbkTNs/8Jg9OG2jCwM8QlFKYpIzB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH7xpF%2FbtstzwbkTNs%2F8Jg9OG2jCwM8QlFKYpIzB0%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;1178&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Github Actions에서 걸린 시간만 보면 첫 번째 방식이 제일 짧았지만, 이는 로컬에서 프로젝트를 빌드하는 시간을 뺀 시간이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 나는 프로젝트 빌드하는 과정 또한 자동화하고 싶었기 때문에, 두 번째와 세 번째 방식이 내가 원하는 방향이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 방식은 프로젝트 빌드를 하는 주체만 다른데, 하나는 Dockerfile에서, 다른 하나는 Github Actions에서 실행한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 방식의 시간차는 약 23초로, Github Actions workflow에서 프로젝트 빌드를 해주는 게 더 빨랐다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생각해 보면 Dockerfile에서 프로젝트 빌드를 하기 위해서는 프로젝트의 모든 소스를 COPY 한 뒤에 이를 빌드하는 방식이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그에 반해 Github Actions에는 소스를 COPY 할 필요 없이 바로 npm install &amp;amp; build를 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물론 더 추가해야 하는 부분은 많지만, 프로젝트 빌드 자동화를 생각으로만 해보다가 직접 구성해 보고 개선시켜 보니 Github Actions를 이해할 수 있는 좋은 기회였다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>  ETC/CICD</category>
      <category>dockerfile</category>
      <category>dockerhub</category>
      <category>Github Actions</category>
      <author>kkangdda</author>
      <guid isPermaLink="true">https://kkangdda.tistory.com/123</guid>
      <comments>https://kkangdda.tistory.com/123#entry123comment</comments>
      <pubDate>Thu, 7 Sep 2023 23:04:45 +0900</pubDate>
    </item>
    <item>
      <title>[CI/CD적용기2] Docker + Github Actions으로 배포 자동화</title>
      <link>https://kkangdda.tistory.com/122</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;목표 :&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- Github Actions를 사용하여 CI/CD를 구축한다&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- workflow :&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp;- build my project -&amp;gt; login to my Docker hub -&amp;gt; build and push my Image&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 즉, 빌드한 결과물을 Docker Image로 만들어서 내 Docker Hub으로 push 하는 일련의 작업을 자동화한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; (&lt;/span&gt;&lt;a href=&quot;https://kkangdda.tistory.com/121&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;https://kkangdda.tistory.com/121&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &amp;gt;&amp;nbsp;기존에 직접 손으로 했던 내용을 Github Action으로 자동화)&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. Github Action workflow yml파일 생성&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2240&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3govk/btstsN0c8dM/X7px1pzmxfK8szd5r8kM6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3govk/btstsN0c8dM/X7px1pzmxfK8szd5r8kM6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3govk/btstsN0c8dM/X7px1pzmxfK8szd5r8kM6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3govk%2FbtstsN0c8dM%2FX7px1pzmxfK8szd5r8kM6K%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;2240&quot; height=&quot;492&quot; data-origin-width=&quot;2240&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자신의 repository &amp;gt; Actions &amp;gt; set up a workflow yourself 를 누르면 .github/workflow 하위에 yml 파일을 작성할 수 있다.&lt;/span&gt;&lt;br /&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;1200&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3xtAX/btstyuY9GDd/KiVpBUZlK4fAyETDDPJeZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3xtAX/btstyuY9GDd/KiVpBUZlK4fAyETDDPJeZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3xtAX/btstyuY9GDd/KiVpBUZlK4fAyETDDPJeZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3xtAX%2FbtstyuY9GDd%2FKiVpBUZlK4fAyETDDPJeZ1%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;1200&quot; height=&quot;818&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;818&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- workflow : 가장 상위 개념 = 자동화 작업 과정&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- job : 독립된 환경에서 실행되는 작업들의 묶음&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; - 하나의 workflow 안에서 job 단위로 완전히 격리된 환경(가상머신 또는 컨테이너)에서 실행됨&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- 병렬처리 가능해짐 -&amp;gt; 성능 올라감&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- 여러 실행환경에서 실행할 수 있음 (ex. 리눅스, 윈도우즈)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(job을 설정하는 부분은 이 포스트 참고 : &lt;/span&gt;&lt;a href=&quot;https://www.daleseo.com/github-actions-jobs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;https://www.daleseo.com/github-actions-jobs/&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #000000;&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- step : 하나의 job 안에서 순차적으로 실행되는 단계&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- action : 작업 공유 메커니즘, Github Marketplace에서 다양한 action을 사용할 수 있음&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내가 작성한 github-action.yml&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;name: WORKFLOW_FOR_DOCKER

on:
&amp;nbsp;&amp;nbsp;push:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;branches: [ &quot;main&quot; ]

&amp;nbsp;&amp;nbsp;# Allows you to run this workflow manually from the Actions tab
&amp;nbsp;&amp;nbsp;workflow_dispatch:

jobs:
&amp;nbsp;&amp;nbsp;# 1. 빌드 머신 준비
&amp;nbsp;&amp;nbsp;build:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;runs-on: ubuntu-latest

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;steps:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 2. 빌드 머신에 Repository Check Out
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- uses: actions/checkout@v3

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: Start Actions
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;run: echo Start Actions!

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 3. Docker Meta를 이용하여 생성할 이미지의 이름과 버전 정보 태깅 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: Docker meta
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id: docker_meta
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uses: crazy-max/ghaction-docker-meta@v1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;images: iDaeun/daeun-portfolio-app
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tag-semver: |
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{{version}}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{{major}}.{{minor}}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 4. 빌드 머신에 Docker 빌드에 필요한 사항 준비
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: Set up Docker Buildx
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uses: docker/setup-buildx-action@v1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 5. 이미지를 업로드 할 내 도커허브 로그인
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: Login to DockerHub
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uses: docker/login-action@v1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;username: ${{ secrets.DOCKERHUB_USERNAME }}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;password: ${{ secrets.DOCKERHUB_TOKEN }}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 6. 도커 이미지 빌드하고 허브로 푸쉬
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: Build and push
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uses: docker/build-push-action@v2
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context: .
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;file: ./Dockerfile
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;platforms: linux/amd64
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;push: true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tags: ${{ steps.docker_meta.outputs.tags }}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;labels: ${{ steps.docker_meta.outputs.labels }}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- name: Finish Git Actions
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;run: |
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo Finished All Actions  
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo Please Check Your Docker Hub!&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2. 필요한 Secret 등록&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Docker Hub에 이미지를 push 해야 하기 때문에 로그인할 때 필요한 내용을 아래와 같이 Secret으로 등록해줘야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2394&quot; data-origin-height=&quot;1274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqdKlr/btstx4zDf38/D4u0Nt9idYfnd5QudU7wTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqdKlr/btstx4zDf38/D4u0Nt9idYfnd5QudU7wTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqdKlr/btstx4zDf38/D4u0Nt9idYfnd5QudU7wTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqdKlr%2Fbtstx4zDf38%2FD4u0Nt9idYfnd5QudU7wTk%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;2394&quot; height=&quot;1274&quot; data-origin-width=&quot;2394&quot; data-origin-height=&quot;1274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;등록된 Secret Key로 Github Action workflow yml 내에서 참조하여 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3. Docker에 관련된 파일은 이전 포스트 참고&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kkangdda.tistory.com/121&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.09.07 - [  ETC/CICD] - [CI/CD적용기1] React앱을 Docker Image으로 생성 후 Docker hub에 업로드&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1694351827923&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[CI/CD적용기1] React앱을 Docker Image으로 생성 후 Docker hub에 업로드&quot; data-og-description=&quot;목표: - 어플리케이션을 빌드한 결과물을 Docker Image로 만든다. - Docker Image를 내 Docker Hub repository에 올린다. - 로컬 pc에서 Docker Hub에 올린 Image를 pull 받아서 실행한다. 1. React 앱 생성 (create-react-app) &quot; data-og-host=&quot;kkangdda.tistory.com&quot; data-og-source-url=&quot;https://kkangdda.tistory.com/121&quot; data-og-url=&quot;https://kkangdda.tistory.com/121&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/BsB8j/hyTSA0ADvH/NTPOQhkSabQAuGlNZdC530/img.png?width=800&amp;amp;height=321&amp;amp;face=0_0_800_321,https://scrap.kakaocdn.net/dn/cDK5CM/hyTSvrtiS0/S8H6ODzMo0RlnYXG6tHur1/img.png?width=800&amp;amp;height=321&amp;amp;face=0_0_800_321,https://scrap.kakaocdn.net/dn/gZgSc/hyTSuzjlzy/ig6dsCvo5sgo67M9jwEupK/img.png?width=1620&amp;amp;height=970&amp;amp;face=0_0_1620_970&quot;&gt;&lt;a href=&quot;https://kkangdda.tistory.com/121&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kkangdda.tistory.com/121&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/BsB8j/hyTSA0ADvH/NTPOQhkSabQAuGlNZdC530/img.png?width=800&amp;amp;height=321&amp;amp;face=0_0_800_321,https://scrap.kakaocdn.net/dn/cDK5CM/hyTSvrtiS0/S8H6ODzMo0RlnYXG6tHur1/img.png?width=800&amp;amp;height=321&amp;amp;face=0_0_800_321,https://scrap.kakaocdn.net/dn/gZgSc/hyTSuzjlzy/ig6dsCvo5sgo67M9jwEupK/img.png?width=1620&amp;amp;height=970&amp;amp;face=0_0_1620_970');&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;[CI/CD적용기1] React앱을 Docker Image으로 생성 후 Docker hub에 업로드&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;목표: - 어플리케이션을 빌드한 결과물을 Docker Image로 만든다. - Docker Image를 내 Docker Hub repository에 올린다. - 로컬 pc에서 Docker Hub에 올린 Image를 pull 받아서 실행한다. 1. React 앱 생성 (create-react-app)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kkangdda.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;4.&amp;nbsp; Run Github Actions !&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;main 브랜치에 코드를 푸쉬하면 Github Action이 yml 파일에 정의한 작업내용에 따라 실행된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 프로젝트를 빌드해 놓은 뒤 main 브랜치에 강제로 test 코드를 푸쉬하여 Github Action이 시작되도록 하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ67AD/btstzoxB1SP/xWsU3JcBRqnZLw2O8RDlck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ67AD/btstzoxB1SP/xWsU3JcBRqnZLw2O8RDlck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ67AD/btstzoxB1SP/xWsU3JcBRqnZLw2O8RDlck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ67AD%2FbtstzoxB1SP%2FxWsU3JcBRqnZLw2O8RDlck%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;1588&quot; height=&quot;644&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;644&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;2186&quot; data-origin-height=&quot;1162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNPj9E/btstvQWGu7z/8UI8D0K6TW3VEbE461bqNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNPj9E/btstvQWGu7z/8UI8D0K6TW3VEbE461bqNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNPj9E/btstvQWGu7z/8UI8D0K6TW3VEbE461bqNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNPj9E%2FbtstvQWGu7z%2F8UI8D0K6TW3VEbE461bqNK%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;2186&quot; height=&quot;1162&quot; data-origin-width=&quot;2186&quot; data-origin-height=&quot;1162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;steps 내에서 내가 정의한 작업 name들을 확인할 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작업들이 끝난 뒤, docker hub으로 가면 내가 빌드한 프로젝트가 Docker Image로 빌드되어 올라간 걸 확인할 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로컬에서 해당 Docker Image를 pull 받아 띄워보는 것까지 끝!&lt;/span&gt;&lt;/p&gt;</description>
      <category>  ETC/CICD</category>
      <category>dockerfile</category>
      <category>Github Actions</category>
      <author>kkangdda</author>
      <guid isPermaLink="true">https://kkangdda.tistory.com/122</guid>
      <comments>https://kkangdda.tistory.com/122#entry122comment</comments>
      <pubDate>Thu, 7 Sep 2023 22:19:38 +0900</pubDate>
    </item>
    <item>
      <title>[CI/CD적용기1] React앱을 Docker Image으로 생성 후 Docker hub에 업로드</title>
      <link>https://kkangdda.tistory.com/121</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;목표:&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;- 어플리케이션을 빌드한 결과물을 Docker Image로 만든다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;- Docker Image를 내 Docker Hub repository에 올린다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;- 로컬 pc에서 Docker Hub에 올린 Image를 pull 받아서 실행한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;1. React 앱 생성 (create-react-app)&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;2. 빌드&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1694093190702&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm build&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;3. Docker를 사용하기 위해 프로젝트 루트에 Dockerfile, nginx.conf 파일 생성&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;Dockerfile&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694093238937&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# nginx 이미지를 사용합니다. 뒤에 tag가 없으면 latest 를 사용합니다.
FROM nginx

# root 에 app 폴더를 생성
RUN mkdir /app

# work dir 고정
WORKDIR /app

# work dir 에 build 폴더 생성 /app/build
RUN mkdir ./build

# host pc의 현재경로의 build 폴더를 workdir 의 build 폴더로 복사
ADD ./build ./build

# nginx 의 default.conf 를 삭제
RUN rm /etc/nginx/conf.d/default.conf

# host pc 의 nginx.conf 를 아래 경로에 복사
COPY ./nginx.conf /etc/nginx/conf.d

# 80 포트 오픈
EXPOSE 80

# container 실행 시 자동으로 실행할 command. nginx 시작함
CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;nginx.conf&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694093255494&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
    listen 80;
    location / {
        root    /app/build;
        index   index.html;
        try_files $uri $uri/ /index.html;
    }
}

80 포트에 / 경로로 들어오면 /app/build 폴더에서 index.html 을 찾음&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;4. Docker Image로 빌드&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1694093376149&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 이미지 이름 = 식별자 / 이름
sudo docker build --tag idaeun/daeun-portfolio-app .&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;주의!&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;Docker Hub에 이미지를 올리기 위해서는 이미지 이름에 식별자로 도커허브 ID를 추가해야한다!&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;(예제에서는 'idaeun')&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694094243867&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 이미지 확인
docker images&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;번외 ) &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;[error] credential&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694094254561&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Dockerfile:1
--------------------
   1 | &amp;gt;&amp;gt;&amp;gt; FROM nginx
   2 |
   3 |     # root 에 app 폴더를 생성
--------------------
ERROR: failed to solve: nginx: error getting credentials - err: exit status 1, out: ``&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;[해결] Docker config 삭제 뒤 다시 로그인&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694094291423&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cat ~/.docker/config.json
{
	&quot;auths&quot;: {
		&quot;https://index.docker.io/v1/&quot;: {}
	},
	&quot;credsStore&quot;: &quot;desktop&quot;,
	&quot;currentContext&quot;: &quot;desktop-linux&quot;
}%&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1694094313845&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rm ~/.docker/config.json
docker login
docker build&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;5. Docker Hub에 내 Image push&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1694093970693&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo docker push idaeun/daeun-portfolio-app&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;Docker Hub에 올라간 모습&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhYJwL/btstg3Csb5D/0QhC1ZtX02tnHtIGkpHVyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhYJwL/btstg3Csb5D/0QhC1ZtX02tnHtIGkpHVyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhYJwL/btstg3Csb5D/0QhC1ZtX02tnHtIGkpHVyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhYJwL%2Fbtstg3Csb5D%2F0QhC1ZtX02tnHtIGkpHVyk%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;1620&quot; height=&quot;652&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;번외 )&amp;nbsp;[error] denied&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694094713362&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;denied: requested access to the resource is denied&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;[해결] Docker 로그인&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694094745804&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker login&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;6. Docker Container 실행&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1694094112053&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# -d = 백그라운드 모드
# -p 8300:80 = 포트 연결 (외부 노출 포트 : 내부 포트)
sudo docker run -d --name 컨테이너이름 -p 8300:80 이미지이름&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;7. 로컬 PC &amp;gt; localhost:8300에서 확인&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GOoU0/btstlgulajI/WVcwSsMxAi9ZdKB726NKO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GOoU0/btstlgulajI/WVcwSsMxAi9ZdKB726NKO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GOoU0/btstlgulajI/WVcwSsMxAi9ZdKB726NKO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGOoU0%2FbtstlgulajI%2FWVcwSsMxAi9ZdKB726NKO1%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;1620&quot; height=&quot;970&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;970&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  ETC/CICD</category>
      <category>Docker</category>
      <category>docker hub</category>
      <category>docker image</category>
      <category>react</category>
      <author>kkangdda</author>
      <guid isPermaLink="true">https://kkangdda.tistory.com/121</guid>
      <comments>https://kkangdda.tistory.com/121#entry121comment</comments>
      <pubDate>Thu, 7 Sep 2023 22:17:43 +0900</pubDate>
    </item>
    <item>
      <title>[DVA-C02] AWS Certified Developer Associate 자격증 후기</title>
      <link>https://kkangdda.tistory.com/120</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1123&quot; data-origin-height=&quot;689&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q8eHn/btsp7I8uvgZ/MkNAULtVNrjp7xOasFsoa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q8eHn/btsp7I8uvgZ/MkNAULtVNrjp7xOasFsoa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q8eHn/btsp7I8uvgZ/MkNAULtVNrjp7xOasFsoa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq8eHn%2Fbtsp7I8uvgZ%2FMkNAULtVNrjp7xOasFsoa0%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;1123&quot; height=&quot;689&quot; data-origin-width=&quot;1123&quot; data-origin-height=&quot;689&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;AWS에서는 위와 같이 단계별로 자격증 시험을 제공하고 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;나는 클라우드 도메인에 대한 지식을 쌓기 위해 회사에서 추천받은 &lt;b&gt;AWS Developer Associate&lt;/b&gt; 자격증을 준비했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;참고로 Developer Associate는 최근 DVA-C01에서 DVA-C02로 바뀌었고,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;해당 버전의 시험에 대한 후기는 그리 많지 않았다.&lt;br /&gt;하지만 유명한 자격증인만큼 강의도 많고 문제 자료도 많으니 걱정할 필요 없다!&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;aws-certified-developer-associate.png&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt8BLt/btsp7Bazd0b/lrQp6mjvKRA9Dj65ll0WvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt8BLt/btsp7Bazd0b/lrQp6mjvKRA9Dj65ll0WvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt8BLt/btsp7Bazd0b/lrQp6mjvKRA9Dj65ll0WvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt8BLt%2Fbtsp7Bazd0b%2FlrQp6mjvKRA9Dj65ll0WvK%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;210&quot; height=&quot;210&quot; data-filename=&quot;aws-certified-developer-associate.png&quot; data-origin-width=&quot;210&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;준비기간은 약 1달 반, 출근 전 매일 한 시간씩 공부했고 주말에는 3시간 정도 시간 내서 공부하였다.&lt;br /&gt;참고로 기존에 AWS 운영 경험이 있거나 다른 AWS관련 자격증이 있는 사람이라면 더 수월할 것 같다. &lt;s&gt;(당연한 얘기)&lt;/s&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아래와 같이 두 가지 자료로 공부했다 :&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1. 유데미 강의&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://www.udemy.com/course/best-aws-certified-developer-associate/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.udemy.com/course/best-aws-certified-developer-associate/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- 한글 자막 제공&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- 이론 강의 + 실습 영상 제공&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- 주제 별 모의 테스트 제공&lt;br /&gt;- 마지막 실전 테스트 제공 (처음에 많이 틀려도 다시 한번 풀어보기!)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;1648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ovGdz/btsp64REytb/tBX3WwK4ehGMEIid2o6y90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ovGdz/btsp64REytb/tBX3WwK4ehGMEIid2o6y90/img.png&quot; data-alt=&quot;정리의 흔적..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ovGdz/btsp64REytb/tBX3WwK4ehGMEIid2o6y90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FovGdz%2Fbtsp64REytb%2FtBX3WwK4ehGMEIid2o6y90%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;447&quot; height=&quot;746&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;1648&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정리의 흔적..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;노션에 강의 주제별로 정리하면서 공부하였고, PDF도 같이 제공하고 있기 때문에 참고해가면서 이론적인 부분을 정리했다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이론 강의 후에는 실습 영상도 있는데, 시험만을 위하면 굳이 보지 않아도 되지만 AWS를 공부하는 것에 중점을 두었기 때문에 2배속으로 켜두고 최대한 실습까지 다 보려고 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2. 덤프 문제&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://www.examtopics.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.examtopics.com/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1691311803513&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Free Exam Prep By IT Professionals | ExamTopics&quot; data-og-description=&quot;ExamTopics The only source for free &amp;amp; accurate actual exam questions &amp;amp; answers, passing your exam easily is guaranteed, and for free!&quot; data-og-host=&quot;www.examtopics.com&quot; data-og-source-url=&quot;https://www.examtopics.com/&quot; data-og-url=&quot;https://www.examtopics.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.examtopics.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.examtopics.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Free Exam Prep By IT Professionals | ExamTopics&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ExamTopics The only source for free &amp;amp; accurate actual exam questions &amp;amp; answers, passing your exam easily is guaranteed, and for free!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.examtopics.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마지막 주에는 덤프 문제들만 계속 풀었다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사이트에서 제공하는 답안보다는 커뮤니티 투표에서 사람들이 가장 많이 뽑은 답을 참고하였다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사람들이 열정적으로 설명해놓았기 때문에 Community Discussion도 같이 보는 것을 추천한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;문제를 3회독 하였고, 오답도 노션에 따로 정리했다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;답변을 외우기 보단 정답을 이해하는 게 중요한 것 같다 (물론 이해 안 가는 건 그냥 외웠..)&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdpDiH/btsp8AhWa1s/dJrp7EwncbmDKTksTVeML0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdpDiH/btsp8AhWa1s/dJrp7EwncbmDKTksTVeML0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdpDiH/btsp8AhWa1s/dJrp7EwncbmDKTksTVeML0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdpDiH%2Fbtsp8AhWa1s%2FdJrp7EwncbmDKTksTVeML0%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;1190&quot; height=&quot;546&quot; data-origin-width=&quot;1190&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마지막까지 시험을 미룰까 말까 고민했었다 (2번까지 미룰 수 있는 걸로 알고 있음)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;왜냐하면 유데미에서 실전 문제를 풀었을 때 60% 정도밖에 안 나왔고... 덤프문제는 더 어려워서 거의 틀렸었고.. &lt;br /&gt;하지만 다행히도 공부한 부분에서 많이 나왔기 때문에 수월하게 풀 수 있었다!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUDMnV/btsp8AoJ61F/IjSKSEzjDNJ0fCasNveGz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUDMnV/btsp8AoJ61F/IjSKSEzjDNJ0fCasNveGz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUDMnV/btsp8AoJ61F/IjSKSEzjDNJ0fCasNveGz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUDMnV%2Fbtsp8AoJ61F%2FIjSKSEzjDNJ0fCasNveGz1%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;366&quot; height=&quot;244&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시험 꿀팁이 있다면&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- 덤프를 많이 풀면 풀수록 좋다 (시험에서 덤프 문제가 많이 나왔다. 그 외엔 평소에 공부한 부분에서 나왔다.)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- AWS 서비스 &amp;amp; 키워드 정리하기 (ex. Cognito 사용자풀, Cognito 자격증명풀 -&amp;gt; 차이점, 특징)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- 시험문제가 길 수도 있는데 그중 키워드를 잘 뽑아내야 한다 (ex. 순환되는 자격증명 -&amp;gt; AWS Secret Manager)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- 질문의 포인트를 이해해야 한다 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(ex.&amp;nbsp;&lt;/span&gt;최소한의 관리 오버헤드로, 최소한의 개발 노력으로 등등)&lt;/span&gt;&lt;/p&gt;</description>
      <category>  ETC/Network</category>
      <category>AWS Developer Associate</category>
      <category>AWS 자격증</category>
      <category>DVA-C02</category>
      <author>kkangdda</author>
      <guid isPermaLink="true">https://kkangdda.tistory.com/120</guid>
      <comments>https://kkangdda.tistory.com/120#entry120comment</comments>
      <pubDate>Sun, 6 Aug 2023 18:20:17 +0900</pubDate>
    </item>
    <item>
      <title>네트워크 스루풋 측정 (feat. iperf3 말고 iperf..)</title>
      <link>https://kkangdda.tistory.com/119</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://repost.aws/knowledge-center/network-throughput-benchmark-linux-ec2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://repost.aws/knowledge-center/network-throughput-benchmark-linux-ec2&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1686558480068&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Benchmark network throughput between EC2 Linux instances in the same VPC&quot; data-og-description=&quot;I want to measure the network bandwidth between Amazon Elastic Compute Cloud (Amazon EC2) Linux instances in the same Amazon Virtual Private Cloud (Amazon VPC). How can I do that?&quot; data-og-host=&quot;repost.aws&quot; data-og-source-url=&quot;https://repost.aws/knowledge-center/network-throughput-benchmark-linux-ec2&quot; data-og-url=&quot;https://repost.aws/knowledge-center/network-throughput-benchmark-linux-ec2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3BrzF/hySYLuaa8B/sKvIeHUe70xgFaA8MYZ0O0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://repost.aws/knowledge-center/network-throughput-benchmark-linux-ec2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://repost.aws/knowledge-center/network-throughput-benchmark-linux-ec2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3BrzF/hySYLuaa8B/sKvIeHUe70xgFaA8MYZ0O0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Benchmark network throughput between EC2 Linux instances in the same VPC&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I want to measure the network bandwidth between Amazon Elastic Compute Cloud (Amazon EC2) Linux instances in the same Amazon Virtual Private Cloud (Amazon VPC). How can I do that?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;repost.aws&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;참고하여 iperf3대신 iperf로 테스트하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;iperf는 2.x버전이고, iperf3은 3.x버전을 뜻하는데, iperf를 사용하는 이유는 :&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;- 2.x버전이 멀티쓰레드를 지원함&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;- 3.x버전에서 -P태그로 parallel stream을 사용할 수 있지만, 3.x버전은 싱글쓰레드이고 싱글CPU에 의해 제한이 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 각 서버(ubuntu)에 iperf를 설치하였다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1686559167181&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apt-get install -y iperf&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;VPC간 TCP 네트워크 성능 테스트&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://kkangdda.tistory.com/118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.06.10 - [  WEB지식/Network] - NCP에서 VPC Peering 구축하기&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1686559227310&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;NCP에서 VPC Peering 구축하기&quot; data-og-description=&quot;VPC Peering 란? (VPC 간 사설 통신망 Peering) &amp;quot;인터넷 통신을 거치지 않고 비공인 IP를 통해서 내부 네트워크로 다른 VPC와 통신하도록 하는 기술&amp;quot; 즉, 2개 이상의 VPC를 연결하고 싶을 때 사용할 수 있다.&quot; data-og-host=&quot;kkangdda.tistory.com&quot; data-og-source-url=&quot;https://kkangdda.tistory.com/118&quot; data-og-url=&quot;https://kkangdda.tistory.com/118&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cjthKN/hySWYPhDe8/hAVKePKUsnDwBARXTHlCKK/img.png?width=800&amp;amp;height=608&amp;amp;face=0_0_800_608,https://scrap.kakaocdn.net/dn/dAI01I/hySYJQuxMs/9IOPFGrhkMfcWVcJ2KSFe1/img.png?width=800&amp;amp;height=608&amp;amp;face=0_0_800_608,https://scrap.kakaocdn.net/dn/mak6g/hySYGzsjXy/YJKBK7QIKnWzNqN3Q4qV2K/img.png?width=1412&amp;amp;height=1468&amp;amp;face=0_0_1412_1468&quot;&gt;&lt;a href=&quot;https://kkangdda.tistory.com/118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kkangdda.tistory.com/118&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cjthKN/hySWYPhDe8/hAVKePKUsnDwBARXTHlCKK/img.png?width=800&amp;amp;height=608&amp;amp;face=0_0_800_608,https://scrap.kakaocdn.net/dn/dAI01I/hySYJQuxMs/9IOPFGrhkMfcWVcJ2KSFe1/img.png?width=800&amp;amp;height=608&amp;amp;face=0_0_800_608,https://scrap.kakaocdn.net/dn/mak6g/hySYGzsjXy/YJKBK7QIKnWzNqN3Q4qV2K/img.png?width=1412&amp;amp;height=1468&amp;amp;face=0_0_1412_1468');&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;NCP에서 VPC Peering 구축하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;VPC Peering 란? (VPC 간 사설 통신망 Peering) &quot;인터넷 통신을 거치지 않고 비공인 IP를 통해서 내부 네트워크로 다른 VPC와 통신하도록 하는 기술&quot; 즉, 2개 이상의 VPC를 연결하고 싶을 때 사용할 수 있다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kkangdda.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞전에 NCP에서 peering한 VPC끼리 테스트를 진행하였다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. 서버&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1686559270096&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;iperf -s [-p 5001]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. 클라이언트&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1686559293953&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ iperf -c 상대방주소 --parallel 40 -i 1 -t 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;--parallel 40 : 40 Threads (test with 40 parallel connections)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;-i 1 : log interval 1 second&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;-t 2 : total test time 2 seconds&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;결과&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;테스트 instance 스펙&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;- s2-g2-s50(vCPU 2EA, Memory 8GB, [SSD]Disk 50GB)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;- mtu 8950&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1686559553509&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@pub-vm-b:~# iperf -c 10.0.0.6 --parallel 40 -i 1 -t 2 -p 5001
------------------------------------------------------------
Client connecting to 10.0.0.6, TCP port 5001
TCP window size:  520 KByte (default)
------------------------------------------------------------
[  3] local 10.100.0.6 port 45534 connected with 10.0.0.6 port 5001
[ 12] local 10.100.0.6 port 45554 connected with 10.0.0.6 port 5001
[  5] local 10.100.0.6 port 45538 connected with 10.0.0.6 port 5001
[  4] local 10.100.0.6 port 45536 connected with 10.0.0.6 port 5001
[ 13] local 10.100.0.6 port 45552 connected with 10.0.0.6 port 5001
[ 41] local 10.100.0.6 port 45610 connected with 10.0.0.6 port 5001
[  7] local 10.100.0.6 port 45542 connected with 10.0.0.6 port 5001
...
[SUM]  0.0- 1.0 sec   159 MBytes  1.33 Gbits/sec
[SUM]  1.0- 2.0 sec   125 MBytes  1.05 Gbits/sec
[SUM]  0.0- 2.2 sec   284 MBytes  1.06 Gbits/sec&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;---&amp;gt; 대역폭은 &lt;b&gt;1.06 Gbits/sec&lt;/b&gt; 으로 나왔다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>  ETC/Network</category>
      <category>Iperf</category>
      <category>NCP</category>
      <category>VPC Peering</category>
      <author>kkangdda</author>
      <guid isPermaLink="true">https://kkangdda.tistory.com/119</guid>
      <comments>https://kkangdda.tistory.com/119#entry119comment</comments>
      <pubDate>Mon, 12 Jun 2023 20:00:09 +0900</pubDate>
    </item>
  </channel>
</rss>