<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코딩하는 폴제트</title>
    <link>https://zakelstorm.tistory.com/</link>
    <description>프로그래밍, front-end,  개발관련 도서 요약 등의 내용을 포스팅하는 개발자 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Mon, 18 May 2026 06:16:14 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>zakelstorm</managingEditor>
    <item>
      <title>Frontend 최적화</title>
      <link>https://zakelstorm.tistory.com/189</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1&amp;nbsp;&lt;/h2&gt;
&lt;h1 id=&quot;%EA%B0%9C%EC%9A%94&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;8&quot;&gt;개요&lt;/h1&gt;
&lt;h3 id=&quot;%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;최적화 기법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;11&quot;&gt;&lt;b&gt;이미지 사이즈 최적화&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;13&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;14&quot;&gt;너무 큰 사이즈의 이미지는 네트워크 트래픽이 증가해 서비스 로딩이 오래걸림.&lt;/li&gt;
&lt;li data-nodeid=&quot;16&quot;&gt;너무 작은 사이즈의 이미지는 화질이 저하되어 서비스 이용이 불편해짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;18&quot;&gt;&lt;b&gt;코드 분할&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;20&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;21&quot;&gt;첫페이지 진입 시 당장 사용하지 않는 코드는 코드분할을 통해 따로 로드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;23&quot;&gt;&lt;b&gt;텍스트 압축&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;25&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;26&quot;&gt;HTML, CSS ,JS 등을 다운로드 전에 서버에서 미리 압축.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;28&quot;&gt;&lt;b&gt;병목 코드 최적화&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;30&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;31&quot;&gt;너무 느리게 다운로드 되거나 느리게 실행되는 특정 자바스크립트 코드를 찾아 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%EC%B5%9C%EC%A0%81%ED%99%94-%ED%88%B4&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;33&quot; data-ke-size=&quot;size23&quot;&gt;최적화 툴&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;34&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;35&quot;&gt;크롬 개발자 도구
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;37&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;38&quot;&gt;Network 패널&lt;/li&gt;
&lt;li data-nodeid=&quot;40&quot;&gt;Performance 패널&lt;/li&gt;
&lt;li data-nodeid=&quot;42&quot;&gt;Lighthouse 패널&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;44&quot;&gt;webpack-bundle-analyzer&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;46&quot;&gt;최적화&lt;/h1&gt;
&lt;h2 id=&quot;Lighthouse-%EC%9D%B4%EC%9A%A9%ED%95%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EA%B2%80%EC%82%AC-%EB%B0%8F-%EA%B0%9C%EC%84%A0&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;47&quot; data-ke-size=&quot;size26&quot;&gt;Lighthouse 이용한 페이지 검사 및 개선&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;48&quot; data-ke-size=&quot;size16&quot;&gt;모든 최적화 포인트를 외우고 있으면 좋겠지만 현실적으로 불가능하므로 Lighthouse의 도움을 받는다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;검사 지표를 보고 Lighthouse가 제공한 Opportunity와 Diagnotics를 적용한다&lt;/b&gt;&lt;/p&gt;
&lt;h3 id=&quot;%EA%B0%9C%EC%84%A0-%EC%9D%B4%EC%A0%84-%EA%B2%80%EC%82%AC%EA%B2%B0%EA%B3%BC&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;49&quot; data-ke-size=&quot;size23&quot;&gt;개선 이전 검사결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;1582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8XQWz/dJMcafyk1uz/ZTozts7SKsoCeU9XvMFeD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8XQWz/dJMcafyk1uz/ZTozts7SKsoCeU9XvMFeD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8XQWz/dJMcafyk1uz/ZTozts7SKsoCeU9XvMFeD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8XQWz%2FdJMcafyk1uz%2FZTozts7SKsoCeU9XvMFeD1%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;1652&quot; height=&quot;1582&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;1582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 id=&quot;web-vitals&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;51&quot; data-ke-size=&quot;size20&quot;&gt;web vitals&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;52&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;53&quot;&gt;FCP(First): DOM 컨텐츠의 첫번째 부분을 렌더링 하는데 걸리는 시간&lt;/li&gt;
&lt;li data-nodeid=&quot;55&quot;&gt;Speed Index(SI): 페이지의 점진적 완성 과정의 속도. 시간에 따른 시각적 완성도 곡선 아래의 면적을 계산&lt;/li&gt;
&lt;li data-nodeid=&quot;57&quot;&gt;LCP(Largest): 페이지 내 가장 큰 이미지나 텍스트 요소가 렌더링되기까지 걸리는 시간.&lt;/li&gt;
&lt;li data-nodeid=&quot;59&quot;&gt;TTI(Time To Interactive): 사용자가 페이지와 상호 작용이 가능한 시점까지 걸리는 시간&lt;/li&gt;
&lt;li data-nodeid=&quot;61&quot;&gt;TBT(Total Blocking Time): 페이지가 사용자 입력에 응답하지 않도록 차단된 시간을 총합한 지표. FCP,TTI 사이의 시간동안 발생&lt;/li&gt;
&lt;li data-nodeid=&quot;63&quot;&gt;CLS(Cumulative Layout Shift): 페이지 로드과정에서 발생한 예기치 못한 레이아웃 이동&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%82%AC%EC%9D%B4%EC%A6%88-%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;65&quot; data-ke-size=&quot;size26&quot;&gt;이미지 사이즈 최적화&lt;/h2&gt;
&lt;h3 id=&quot;%EB%B9%84%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B6%84%EC%84%9D&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;66&quot; data-ke-size=&quot;size23&quot;&gt;비효율적인 이미지 분석&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;67&quot; data-ke-size=&quot;size16&quot;&gt;Diagnotics 항목을 보면 다음과 같은 비효율적인 이미지가 로드되는 상황이 나타난다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-20 16.32.18.633.png&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;1634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cC2i50/dJMcaaX6rmI/CreK3GCv3y82zURe365O81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cC2i50/dJMcaaX6rmI/CreK3GCv3y82zURe365O81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cC2i50/dJMcaaX6rmI/CreK3GCv3y82zURe365O81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcC2i50%2FdJMcaaX6rmI%2FCreK3GCv3y82zURe365O81%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;714&quot; height=&quot;778&quot; data-filename=&quot;Inline-image-2025-07-20 16.32.18.633.png&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;1634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-20 16.36.50.245.png&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDEMrB/dJMcaaX6rmR/aSs2v9djwU9Ahft296WiD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDEMrB/dJMcaaX6rmR/aSs2v9djwU9Ahft296WiD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDEMrB/dJMcaaX6rmR/aSs2v9djwU9Ahft296WiD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDEMrB%2FdJMcaaX6rmR%2FaSs2v9djwU9Ahft296WiD1%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;653&quot; height=&quot;392&quot; data-filename=&quot;Inline-image-2025-07-20 16.36.50.245.png&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;%EB%B3%91%EB%AA%A9-%EC%BD%94%EB%93%9C-%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;70&quot; data-ke-size=&quot;size26&quot;&gt;병목 코드 최적화&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;71&quot; data-ke-size=&quot;size16&quot;&gt;Perfomance 패널에서 메인스레드의 작업을 상세하게 살펴보고 느린 작업이 무엇인지 확인. (Start profiling and reload page) 버튼 클릭.&lt;/p&gt;
&lt;h3 id=&quot;CPU-%EC%B0%A8%ED%8A%B8&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;72&quot; data-ke-size=&quot;size23&quot;&gt;CPU 차트&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;73&quot; data-ke-size=&quot;size16&quot;&gt;어느 타이밍에 어떤작업이 진행되는지 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-20 16.55.10.309.png&quot; data-origin-width=&quot;2972&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lrYPP/dJMcag45B1b/mUD9e74Sn0Je5kSkkjQ7Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lrYPP/dJMcag45B1b/mUD9e74Sn0Je5kSkkjQ7Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lrYPP/dJMcag45B1b/mUD9e74Sn0Je5kSkkjQ7Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlrYPP%2FdJMcag45B1b%2FmUD9e74Sn0Je5kSkkjQ7Tk%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;2972&quot; height=&quot;202&quot; data-filename=&quot;Inline-image-2025-07-20 16.55.10.309.png&quot; data-origin-width=&quot;2972&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;74&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;75&quot;&gt;노란색: 자바스크립트 실행&lt;/li&gt;
&lt;li data-nodeid=&quot;77&quot;&gt;보라색: 렌더링/레이아웃&lt;/li&gt;
&lt;li data-nodeid=&quot;79&quot;&gt;초록색: 페인팅&lt;/li&gt;
&lt;li data-nodeid=&quot;81&quot;&gt;회색: 기타&lt;/li&gt;
&lt;li data-nodeid=&quot;83&quot;&gt;빨간색: 병목지점. 특정 작업이 메인 스레드를 오랫동안 잡고 있을때&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Network-%ED%83%80%EC%9E%84%EB%9D%BC%EC%9D%B8&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;85&quot; data-ke-size=&quot;size26&quot;&gt;Network 타임라인&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;86&quot; data-ke-size=&quot;size16&quot;&gt;서비스 로드 과정에서 네트워크 요청을 시간 순서에 따라 보여줌. 오래걸리는 작업은 클릭하여 선택후 하단 탭의 summary를 통해 문제 파악 가능.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpMC4l/dJMcaaKy1f5/pk8ZtLIGqKcRwRVmAe9RM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpMC4l/dJMcaaKy1f5/pk8ZtLIGqKcRwRVmAe9RM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpMC4l/dJMcaaKy1f5/pk8ZtLIGqKcRwRVmAe9RM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpMC4l%2FdJMcaaKy1f5%2Fpk8ZtLIGqKcRwRVmAe9RM1%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;1314&quot; height=&quot;788&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(여기서 JS 파일의 크기는 1.0MB)&lt;/p&gt;
&lt;h3 id=&quot;%ED%94%8C%EB%A0%88%EC%9E%84-%EC%B0%A8%ED%8A%B8&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;87&quot; data-ke-size=&quot;size23&quot;&gt;플레임 차트&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;88&quot; data-ke-size=&quot;size16&quot;&gt;어떤 작업이 오래 걸리는지 파악 가능.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BZknl/dJMcadgaAQZ/SkdpvQL2HEthuOKhI2I5f1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BZknl/dJMcadgaAQZ/SkdpvQL2HEthuOKhI2I5f1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BZknl/dJMcadgaAQZ/SkdpvQL2HEthuOKhI2I5f1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBZknl%2FdJMcadgaAQZ%2FSkdpvQL2HEthuOKhI2I5f1%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;1496&quot; height=&quot;770&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&quot;%ED%95%98%EB%8B%A8-%ED%83%AD&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;89&quot; data-ke-size=&quot;size23&quot;&gt;하단 탭&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;90&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;91&quot;&gt;&lt;b&gt;Summary&lt;/b&gt;: 선택 영역에서 발생한 작업 시간의 총합과 각 작업이 차지하는 비중&lt;/li&gt;
&lt;li data-nodeid=&quot;93&quot;&gt;&lt;b&gt;Bottom up&lt;/b&gt;: 가장 최하위에 있는 작업부터 상위 작업까지 보여줌&lt;/li&gt;
&lt;li data-nodeid=&quot;95&quot;&gt;&lt;b&gt;Call Tree&lt;/b&gt;: 가장 상위에 있는 작업부터 하위 작업 순으로 보여줌&lt;/li&gt;
&lt;li data-nodeid=&quot;97&quot;&gt;&lt;b&gt;Event Log&lt;/b&gt;: 브라우저 이벤트(ex. Loading, Experience Scripting, Rendering, Paint)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%EB%B6%84%EC%84%9D-%EB%B0%A9%EB%B2%95&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;99&quot; data-ke-size=&quot;size23&quot;&gt;분석 방법&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;100&quot; data-ke-size=&quot;size16&quot;&gt;병목이 걸리는 Task를 선택후 하단 탭의 Bottom up 섹션을 통해 어떤 작업의 Self time이 가장 큰지 확인. 이 경우 removeSpecialCharacter의 작업이 병목을 유발함.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2878&quot; data-origin-height=&quot;1486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/26x9i/dJMcacn45gM/6igIzwGbZUdJ7gvezUPhWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/26x9i/dJMcacn45gM/6igIzwGbZUdJ7gvezUPhWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/26x9i/dJMcacn45gM/6igIzwGbZUdJ7gvezUPhWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F26x9i%2FdJMcacn45gM%2F6igIzwGbZUdJ7gvezUPhWk%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;2878&quot; height=&quot;1486&quot; data-origin-width=&quot;2878&quot; data-origin-height=&quot;1486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;matlab&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;101&quot;&gt;&lt;code&gt;function removeSpecialCharacter(str) {
  const removeCharacters = ['#', '_', '*', '~', '&amp;amp;', ';', '!', '[', ']', '`', '&amp;gt;', '\n', '=', '-']
  let _str = str
  let i = 0,
    j = 0

  for (i = 0; i &amp;lt; removeCharacters.length; i++) {
    j = 0
    while (j &amp;lt; _str.length) {
      if (_str[j] === removeCharacters[i]) {
        _str = _str.substring(0, j).concat(_str.substring(j + 1))
        continue
      }
      j++
    }
  }

  return _str
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;102&quot; data-ke-size=&quot;size16&quot;&gt;실제로 보면&lt;span&gt;&amp;nbsp;&lt;/span&gt;replace&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수를 사용하면 될것을&lt;span&gt;&amp;nbsp;&lt;/span&gt;for&lt;span&gt;&amp;nbsp;&lt;/span&gt;루프를 돌고있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;102&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;%EC%BD%94%EB%93%9C-%EB%B6%84%ED%95%A0&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;103&quot; data-ke-size=&quot;size26&quot;&gt;코드 분할&lt;/h2&gt;
&lt;h3 id=&quot;%EB%B6%84%ED%95%A0-%EA%B8%B0%EC%A4%80&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;104&quot; data-ke-size=&quot;size23&quot;&gt;분할 기준&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;105&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-nodeid=&quot;106&quot;&gt;&lt;b&gt;Network 타임라인&lt;/b&gt;를 통해 유난히 크고 다운로드가 오래걸리는 자바스크립트 파일을 확인.&lt;/li&gt;
&lt;li data-nodeid=&quot;108&quot;&gt;&lt;b&gt;webpack-bundle-analyzer&lt;/b&gt;를 통해 해당 자바스크립트 파일이 어떤 코드로 이루어져 있는지 확인.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2980&quot; data-origin-height=&quot;1762&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBgx7y/dJMcafrzBt0/gkLAFYzzJ8NF7ok7KSCeHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBgx7y/dJMcafrzBt0/gkLAFYzzJ8NF7ok7KSCeHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBgx7y/dJMcafrzBt0/gkLAFYzzJ8NF7ok7KSCeHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBgx7y%2FdJMcafrzBt0%2FgkLAFYzzJ8NF7ok7KSCeHk%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;601&quot; height=&quot;355&quot; data-origin-width=&quot;2980&quot; data-origin-height=&quot;1762&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. refractor&lt;span&gt;&amp;nbsp;&lt;/span&gt;패키지의 용량이 너무 크다. 직접 설치한 패키지가 아니라면&lt;span&gt;&amp;nbsp;&lt;/span&gt;lock.json파일을 확인하여 어느 패키지가 의존성을 가지고 있는지 확인. -&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;react-syntax-highlighter&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. react-syntax-highlighter&lt;span&gt;&amp;nbsp;&lt;/span&gt;패키지는 Code Block 컴포넌트에서만 사용되니 굳이 처음 진입시 로드할 필요는 없다. 따라서 lazy load&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-20 17.40.44.643.png&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E2dfJ/dJMcaiIyzi8/xlf8KnZUsRfkPbG5kksCD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E2dfJ/dJMcaiIyzi8/xlf8KnZUsRfkPbG5kksCD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E2dfJ/dJMcaiIyzi8/xlf8KnZUsRfkPbG5kksCD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE2dfJ%2FdJMcaiIyzi8%2Fxlf8KnZUsRfkPbG5kksCD1%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;1318&quot; height=&quot;228&quot; data-filename=&quot;Inline-image-2025-07-20 17.40.44.643.png&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;5. 결과확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-20 17.56.50.180.png&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;974&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4mT3m/dJMcaf6aXUp/qjOBHOaliSoJUgIKG0j80k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4mT3m/dJMcaf6aXUp/qjOBHOaliSoJUgIKG0j80k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4mT3m/dJMcaf6aXUp/qjOBHOaliSoJUgIKG0j80k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4mT3m%2FdJMcaf6aXUp%2FqjOBHOaliSoJUgIKG0j80k%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;1340&quot; height=&quot;974&quot; data-filename=&quot;Inline-image-2025-07-20 17.56.50.180.png&quot; data-origin-width=&quot;1340&quot; data-origin-height=&quot;974&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;116&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 id=&quot;Page%EB%B3%84-%EB%B6%84%ED%95%A0&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;116&quot; data-ke-size=&quot;size20&quot;&gt;Page별 분할&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;117&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;118&quot;&gt;기존 Route설정파일에서 일반 import (List-&amp;gt;Detail 페이지로 이동)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-20 16.08.16.660.png&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r6jCN/dJMcaiohWRj/D4HtU8x5e80LSGBKQ50xa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r6jCN/dJMcaiohWRj/D4HtU8x5e80LSGBKQ50xa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r6jCN/dJMcaiohWRj/D4HtU8x5e80LSGBKQ50xa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr6jCN%2FdJMcaiohWRj%2FD4HtU8x5e80LSGBKQ50xa0%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;1652&quot; height=&quot;366&quot; data-filename=&quot;Inline-image-2025-07-20 16.08.16.660.png&quot; data-origin-width=&quot;1652&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Page별 lazy import (List-&amp;gt;Detail 페이지로 이동&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-20 16.09.17.531 (1).png&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xHEAY/dJMcafkNZER/sgOYJIKn0TmLg8JpHDTmNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xHEAY/dJMcafkNZER/sgOYJIKn0TmLg8JpHDTmNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xHEAY/dJMcafkNZER/sgOYJIKn0TmLg8JpHDTmNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxHEAY%2FdJMcafkNZER%2FsgOYJIKn0TmLg8JpHDTmNK%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;1646&quot; height=&quot;622&quot; data-filename=&quot;Inline-image-2025-07-20 16.09.17.531 (1).png&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;117&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;122&quot;&gt;차이점
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;124&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;125&quot;&gt;List 페이지 진입 시 진입 속도가 빨라졌다(8ms-&amp;gt;2ms)&lt;/li&gt;
&lt;li data-nodeid=&quot;127&quot;&gt;Detail 페이지 진입 시 또다시 js 파일을 로드하게 된다.(분할을 했기 때문에)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;%ED%85%8D%EC%8A%A4%ED%8A%B8-%EC%95%95%EC%B6%95&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;129&quot; data-ke-size=&quot;size26&quot;&gt;텍스트 압축&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;130&quot; data-ke-size=&quot;size16&quot;&gt;서버에서 gzip으로 압축해보내주자!&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;130&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;130&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;130&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;130&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;130&quot; data-ke-size=&quot;size26&quot;&gt;2&lt;/h2&gt;
&lt;h1 id=&quot;%EA%B0%9C%EC%9A%94&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;303&quot;&gt;개요&lt;/h1&gt;
&lt;h3 id=&quot;%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;304&quot; data-ke-size=&quot;size23&quot;&gt;최적화 기법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;305&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;306&quot;&gt;&lt;b&gt;CSS 애니메이션 최적화&lt;/b&gt;&lt;/li&gt;
&lt;li data-nodeid=&quot;308&quot;&gt;&lt;b&gt;컴포넌트 지연 로딩&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;310&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;311&quot;&gt;이전에는 페이지를 분할했지만 이제는 단일 컴포넌트를 분할하여 컴포넌트가 쓰이는 순간에 로드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;313&quot;&gt;&lt;b&gt;컴포넌트 사전 로딩&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;315&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;316&quot;&gt;분할된 코드를 preload하여 필요한 시점보다 먼저 다운로드.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;318&quot;&gt;&lt;b&gt;이미지 사전로딩&lt;/b&gt;&lt;br /&gt;- 이미지를 필요한 시점보다 먼저 다운로드하고 필요할때 로드&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%EC%B5%9C%EC%A0%81%ED%99%94-%ED%88%B4&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;320&quot; data-ke-size=&quot;size23&quot;&gt;최적화 툴&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;321&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;322&quot;&gt;크롬 개발자 도구
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;324&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;325&quot;&gt;Network 패널&lt;/li&gt;
&lt;li data-nodeid=&quot;327&quot;&gt;Performance 패널&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;329&quot;&gt;webpack-bundle-analyzer&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;331&quot; data-ke-size=&quot;size26&quot;&gt;최적화&lt;/h2&gt;
&lt;h3 id=&quot;%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;332&quot; data-ke-size=&quot;size23&quot;&gt;애니메이션 최적화&lt;/h3&gt;
&lt;h4 id=&quot;Jank-%ED%98%84%EC%83%81&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;333&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Jank 현상&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;334&quot; data-ke-size=&quot;size16&quot;&gt;웹페이지나 앱에서 발생하는 시각적 끊김 현상. 브라우저가 정상적으로 60FPS로 화면을 그리지 못하기때문에 발생. Jank 현상은 주로 reflow, repaint로 인해 발생함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;335&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;336&quot;&gt;reflow: 요소의 크기나 위치가 변경되어 레이아웃을 다시 계산하는 과정. width, height, margin, padding, position 변경 시 발생&lt;/li&gt;
&lt;li data-nodeid=&quot;338&quot;&gt;repaint: 요소의 시각적 스타일만 변경되어 다시 그리는 과정. color, background-color, visibility, outline 변경 시 발생.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;340&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Jank 현상을 피하는 방법&lt;/b&gt;&lt;br /&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;transform, opacity, will-change&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 속성을 사용. 해당 Element를 벌도의 레이어로 분리하고 작업을 GPU에 위임함으로써 레이아웃 단계와 페인트 단계를 건너뜀. (=하드웨어 가속)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;341&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하드웨어 가속&lt;/b&gt;&lt;br /&gt;현재 width 로 애니메이션을 조절하다보니 리플로우 작업(layout 시작~paint끝)이 1frame(1/60초)동안 끝나질 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-20 23.36.12.571.png&quot; data-origin-width=&quot;2818&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKYUn0/dJMcafLSeSo/iRvsDkkrhKVtq3GzLVjKrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKYUn0/dJMcafLSeSo/iRvsDkkrhKVtq3GzLVjKrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKYUn0/dJMcafLSeSo/iRvsDkkrhKVtq3GzLVjKrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKYUn0%2FdJMcafLSeSo%2FiRvsDkkrhKVtq3GzLVjKrK%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;2818&quot; height=&quot;506&quot; data-filename=&quot;Inline-image-2025-07-20 23.36.12.571.png&quot; data-origin-width=&quot;2818&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;341&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이를 transform으로 변환해보면 결과는 다음과 같다.&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;Inline-image-2025-07-20 23.46.55.980.png&quot; data-origin-width=&quot;2810&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcffEi/dJMcaiaLgMq/JsHVPsYYr2OiomcoQfxZm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcffEi/dJMcaiaLgMq/JsHVPsYYr2OiomcoQfxZm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcffEi/dJMcaiaLgMq/JsHVPsYYr2OiomcoQfxZm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcffEi%2FdJMcaiaLgMq%2FJsHVPsYYr2OiomcoQfxZm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2810&quot; height=&quot;234&quot; data-filename=&quot;Inline-image-2025-07-20 23.46.55.980.png&quot; data-origin-width=&quot;2810&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&quot;%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%A7%80%EC%97%B0-%EB%A1%9C%EB%94%A9&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;343&quot; data-ke-size=&quot;size23&quot;&gt;컴포넌트 지연 로딩&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;344&quot; data-ke-size=&quot;size16&quot;&gt;서비스 첫화면부터 필요하지 않는 컴포넌트의 경우 지연로딩을 통해 chunk를 분리해준다. 여기서는 import한 Modal 컴포넌트의 경우 코드 분할해준다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;345&quot; data-ke-size=&quot;size16&quot;&gt;※ 컴포넌트를 lazy load 할때에는 suspense를 감싸주자.&lt;/p&gt;
&lt;h3 id=&quot;%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%82%AC%EC%A0%84-%EB%A1%9C%EB%94%A9&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;346&quot; data-ke-size=&quot;size23&quot;&gt;컴포넌트 사전 로딩&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;347&quot; data-ke-size=&quot;size16&quot;&gt;모달을 지연로딩시키면 모달을 열었을때 네트워크를 통해 모달 코드를 새로 로드해야했다. 그러니 당연하게도 다음과 같이 모달이 뜨기전까지 지연이 발생한다.(click event ~ evaluate script)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-21 00.07.52.527.png&quot; data-origin-width=&quot;2808&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XjMQK/dJMcaacI0Mt/qjojLaiK1BdnZPoUKhNTU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XjMQK/dJMcaacI0Mt/qjojLaiK1BdnZPoUKhNTU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XjMQK/dJMcaacI0Mt/qjojLaiK1BdnZPoUKhNTU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXjMQK%2FdJMcaacI0Mt%2FqjojLaiK1BdnZPoUKhNTU1%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;2808&quot; height=&quot;704&quot; data-filename=&quot;Inline-image-2025-07-21 00.07.52.527.png&quot; data-origin-width=&quot;2808&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이러한 지연을 해결하기 위해 렌더링이 완료된 직후 여유가 있을때 Modal 컴포넌트를 가져온다.&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;Inline-image-2025-07-21 00.21.12.918.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpdTOM/dJMcacBCBXG/creA3zj9ZN0QjY52AfmNPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpdTOM/dJMcacBCBXG/creA3zj9ZN0QjY52AfmNPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpdTOM/dJMcacBCBXG/creA3zj9ZN0QjY52AfmNPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpdTOM%2FdJMcacBCBXG%2FcreA3zj9ZN0QjY52AfmNPk%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;1264&quot; height=&quot;960&quot; data-filename=&quot;Inline-image-2025-07-21 00.21.12.918.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;349&quot;&gt;&lt;code&gt;function App() {
   const [showModal,setShowModal] = useState(false)
   const [ImageModal, setImageModal] = useState(null);


   useEffect(() =&amp;gt; {
    import('./components/ImageModal').then(res=&amp;gt;{
        // res.default는 함수형이기 때문에 setState 함수가 state 업데이트 함수로 오해함
        setImageModal(()=&amp;gt;res.default)
    })
  }, []); 

    return (
        &amp;lt;div className=&quot;App&quot;&amp;gt;
            &amp;lt;Header /&amp;gt;
            &amp;lt;InfoTable /&amp;gt;
            &amp;lt;ButtonModal onClick={() =&amp;gt; { setShowModal(true) }}&amp;gt;올림픽 사진 보기&amp;lt;/ButtonModal&amp;gt;
            &amp;lt;SurveyChart /&amp;gt;
            &amp;lt;Footer /&amp;gt;
            {showModal ? &amp;lt;Suspense fallback={null}&amp;gt;&amp;lt;ImageModal closeModal={() =&amp;gt; { setShowModal(false) }} /&amp;gt;&amp;lt;/Suspense&amp;gt; : null}
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%82%AC%EC%A0%84-%EB%A1%9C%EB%94%A9&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;350&quot; data-ke-size=&quot;size23&quot;&gt;이미지 사전 로딩&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;351&quot; data-ke-size=&quot;size16&quot;&gt;모달을 열때 이미지가 제때 뜨지 않는다. 이로인해 CLS 이슈가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;2234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QZxWM/dJMcaiPj2Xs/kbc43MBdz93wO9PAUWKnvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QZxWM/dJMcaiPj2Xs/kbc43MBdz93wO9PAUWKnvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QZxWM/dJMcaiPj2Xs/kbc43MBdz93wO9PAUWKnvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQZxWM%2FdJMcaiPj2Xs%2Fkbc43MBdz93wO9PAUWKnvK%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;3456&quot; height=&quot;2234&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;2234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Modal을 로드할때 Modal 내 필요한 이미지들을 모두 로드한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;353&quot;&gt;&lt;code&gt;function App() {
   const [showModal,setShowModal] = useState(false)
   const [ImageModal, setImageModal] = useState(null);


   useEffect(() =&amp;gt; {
    import('./components/ImageModal').then(res=&amp;gt;{
        // res.default는 함수형이기 때문에 setState 함수가 state 업데이트 함수로 오해함
        setImageModal(()=&amp;gt;res.default)
        images.forEach(image=&amp;gt;{
            const img = new Image()
            img.src=image.original
        })
    })
  }, []); 

    return (
        &amp;lt;div className=&quot;App&quot;&amp;gt;
            &amp;lt;Header /&amp;gt;
            &amp;lt;InfoTable /&amp;gt;
            &amp;lt;ButtonModal onClick={() =&amp;gt; { setShowModal(true) }}&amp;gt;올림픽 사진 보기&amp;lt;/ButtonModal&amp;gt;
            &amp;lt;SurveyChart /&amp;gt;
            &amp;lt;Footer /&amp;gt;
            {showModal ? &amp;lt;Suspense fallback={null}&amp;gt;&amp;lt;ImageModal closeModal={() =&amp;gt; { setShowModal(false) }} /&amp;gt;&amp;lt;/Suspense&amp;gt; : null}
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3&lt;/h2&gt;
&lt;h1 id=&quot;%EA%B0%9C%EC%9A%94&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;441&quot;&gt;개요&lt;/h1&gt;
&lt;h3 id=&quot;%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;442&quot; data-ke-size=&quot;size23&quot;&gt;최적화 기법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;443&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;444&quot;&gt;&lt;b&gt;이미지 지연 로딩&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;446&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;447&quot;&gt;첫화면에 당장 필요하지 않은 이미지가 로드 되지않도록 지연&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;449&quot;&gt;&lt;b&gt;이미지 사이즈 최적화&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;451&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;452&quot;&gt;이전에는 CDN에서 로드된 이미지의 크기를 수정했지만 이번에는 정적 이미지를 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;454&quot;&gt;&lt;b&gt;폰트 최적화&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;456&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;457&quot;&gt;커스텀 폰트 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;459&quot;&gt;&lt;b&gt;캐시 최적화&lt;/b&gt;&lt;/li&gt;
&lt;li data-nodeid=&quot;461&quot;&gt;&lt;b&gt;불필요한 CSS 제거&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;463&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;464&quot;&gt;불필요한 CSS 코드를 제거하여 파일사이즈 줄이기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%EC%B5%9C%EC%A0%81%ED%99%94-%ED%88%B4&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;466&quot; data-ke-size=&quot;size23&quot;&gt;최적화 툴&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;467&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;468&quot;&gt;크롬 개발자 도구
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;470&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;471&quot;&gt;Coverage 패널: 웹페이지를 렌더링하는 과정에서 어떤 코드가 실행되었는지, 얼마나 실행되었는지(비율) 확인.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;473&quot;&gt;Squoosh: 이미지 압축 도구&lt;/li&gt;
&lt;li data-nodeid=&quot;475&quot;&gt;PurgeCSS: 사용하지 않는 CSS 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;477&quot;&gt;최적화&lt;/h1&gt;
&lt;h2 id=&quot;%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%A7%80%EC%97%B0%EB%A1%9C%EB%94%A9&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;478&quot; data-ke-size=&quot;size26&quot;&gt;이미지 지연로딩&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;479&quot; data-ke-size=&quot;size16&quot;&gt;이미지 영역이 화면에 보이는 순간 또는 그 직전에 이미지를 로드.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;480&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;481&quot;&gt;Scroll Event 감지 방식: scroll이 이동할때마다 콜백함수가 실행되기 때문에 내부에 무거운 로직이 들어가면 브라우저 메인스레드에 부담이감.&lt;/li&gt;
&lt;li data-nodeid=&quot;483&quot;&gt;IntersectionObserver 방식: 스크롤 할때마다 콜백함수가 실행되는 것이 아님.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;485&quot;&gt;&lt;code&gt;function Card(props) {
	const img = useRef(null)

	useEffect(()=&amp;gt;{
		const intersectionObserver = new IntersectionObserver((e)=&amp;gt;{
			if(e[0].isIntersecting){
				console.log(e[0].target)
				const imgComponent= img.current.querySelector('img')
				imgComponent.src = imgComponent.dataset.src	
				intersectionObserver.unobserve(e[0].target)
			}

		},{
			root: document,
			rootMargin: '0px',
			threshold:1
		})

		intersectionObserver.observe(img.current)

		return ()=&amp;gt;intersectionObserver.disconnect()
	},[])

	return (
		&amp;lt;div ref={img} className=&quot;Card text-center&quot;&amp;gt;
			&amp;lt;img data-src={props.image} /&amp;gt;
			&amp;lt;div className=&quot;p-5 font-semibold text-gray-700 text-xl md:text-lg lg:text-xl keep-all&quot;&amp;gt;
				{props.children}
			&amp;lt;/div&amp;gt;
		&amp;lt;/div&amp;gt;
	)
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;486&quot; data-ke-size=&quot;size16&quot;&gt;※ img 컴포넌트에 대한&lt;span&gt;&amp;nbsp;&lt;/span&gt;display:none-&amp;gt;display:block&lt;span&gt;&amp;nbsp;&lt;/span&gt;의 변경으로 위 문제를 해결 할 수 없다. 무조건 src를 넣는 시점을 제어해야한다.&lt;/p&gt;
&lt;h2 id=&quot;%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%82%AC%EC%9D%B4%EC%A6%88-%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;487&quot; data-ke-size=&quot;size26&quot;&gt;이미지 사이즈 최적화&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;488&quot; data-ke-size=&quot;size16&quot;&gt;Card 내 이미지의 크기가 커 로딩속도가 너무 느리다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;489&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;490&quot;&gt;사이즈: PNG &amp;gt; JPG &amp;gt; WebP&lt;/li&gt;
&lt;li data-nodeid=&quot;492&quot;&gt;화질: PNG = WebP &amp;gt; JPG&lt;/li&gt;
&lt;li data-nodeid=&quot;494&quot;&gt;호환성: PNG = JPG &amp;gt; WebP&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;496&quot; data-ke-size=&quot;size16&quot;&gt;JPG/PNG 포맷의 이미지를 WebP 포맷으로 변환하여 고화질, 저용량 이미지로 최적화.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;497&quot; data-ke-size=&quot;size16&quot;&gt;WebP를 지원하지 못하는 브라우저의 경우 JPG/PNG로 로드하게 해줘야한다. 이를 대비하기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;picture&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 이용한다.(&lt;a href=&quot;https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_picture&quot;&gt;https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_picture&lt;/a&gt;)&lt;/p&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;498&quot;&gt;&lt;code&gt;# 뷰포트 크기에 따라 구분 
&amp;lt;picture&amp;gt;
  &amp;lt;source media=&quot;(min-width:650px)&quot; srcset=&quot;img_pink_flowers.jpg&quot;&amp;gt;
  &amp;lt;source media=&quot;(min-width:450px)&quot; srcset=&quot;img_white_flowers.jpg&quot;&amp;gt;
  &amp;lt;img src=&quot;img_orange_flowers.jpg&quot; alt=&quot;Flowers&quot; style=&quot;width:auto;&quot;&amp;gt;
&amp;lt;/picture&amp;gt;

# 이미지 포맷에 따라 구분
&amp;lt;picture&amp;gt;
  &amp;lt;source type=&quot;image/avif&quot; srcset=&quot;img_pink_flowers.avif&quot;&amp;gt;
  &amp;lt;source type=&quot;image/webp&quot; srcset=&quot;img_pink_flowers.webp&quot;&amp;gt;
  &amp;lt;img src=&quot;img_pink_flowers.jpg&quot; alt=&quot;Flowers&quot; style=&quot;width:auto;&quot;&amp;gt;
&amp;lt;/picture&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;%EB%8F%99%EC%98%81%EC%83%81-%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;499&quot; data-ke-size=&quot;size26&quot;&gt;동영상 최적화&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;500&quot; data-ke-size=&quot;size16&quot;&gt;동영상 압축 웹 사이트에서 동영상 너비,높이,Bitrate, Audio 유무 등을 조정하여 메모리 최적화.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;501&quot; data-ke-size=&quot;size16&quot;&gt;단, 화질이 저화 되므로 영상이 중요하지 않다면 이를 보완하기위해 CSS 상으로 blur등의 필터를 씌운다&lt;/p&gt;
&lt;h2 id=&quot;%ED%8F%B0%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;502&quot; data-ke-size=&quot;size26&quot;&gt;폰트 최적화&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;503&quot; data-ke-size=&quot;size16&quot;&gt;페이지 로드 후 폰트가 적용되어 텍스트 스타일이 변하는 모습이 관측된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-21 01.43.34.906.png&quot; data-origin-width=&quot;3308&quot; data-origin-height=&quot;110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hky0U/dJMcadAtbBq/oNXKLwf6sSfAqNnv9Nuz20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hky0U/dJMcadAtbBq/oNXKLwf6sSfAqNnv9Nuz20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hky0U/dJMcadAtbBq/oNXKLwf6sSfAqNnv9Nuz20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHky0U%2FdJMcadAtbBq%2FoNXKLwf6sSfAqNnv9Nuz20%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;3308&quot; height=&quot;110&quot; data-filename=&quot;Inline-image-2025-07-21 01.43.34.906.png&quot; data-origin-width=&quot;3308&quot; data-origin-height=&quot;110&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에 따라 FOUT(텍스트 출력 후 폰트스타일 적용)/FOIT(폰트스타일 준비 완료 후 텍스트 출력) 방식이 다르다&lt;/p&gt;
&lt;h3 id=&quot;%ED%8F%B0%ED%8A%B8-%EC%A0%81%EC%9A%A9-%EC%8B%9C%EC%A0%90-%EC%A0%9C%EC%96%B4&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;504&quot; data-ke-size=&quot;size23&quot;&gt;폰트 적용 시점 제어&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;505&quot; data-ke-size=&quot;size16&quot;&gt;@font-face를 통해 font를 등록할때&lt;span&gt;&amp;nbsp;&lt;/span&gt;font-display에 폰트 표현방식 설정&lt;/p&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;506&quot;&gt;&lt;code&gt;@font-face{
  font-family: BMYEONGSUNG;
  src: url('./assets/fonts/BMYEONGSUNG.ttf');
  font-display: fallback;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;507&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;508&quot;&gt;auto: 브라우저 기본 동작&lt;/li&gt;
&lt;li data-nodeid=&quot;510&quot;&gt;block: FOIT (timeout=3s)&lt;/li&gt;
&lt;li data-nodeid=&quot;512&quot;&gt;swap: FOUT&lt;/li&gt;
&lt;li data-nodeid=&quot;514&quot;&gt;fallback: FOIT (timeout=0.1s) // 3초 후에도 불러오지 못한경우 기본 폰트로 유지&lt;/li&gt;
&lt;li data-nodeid=&quot;516&quot;&gt;optional: FOIT // 네트워크 상태에 따라 기본 폰트로 유지할지 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;518&quot; data-ke-size=&quot;size16&quot;&gt;※&lt;span&gt;&amp;nbsp;&lt;/span&gt;block방식을 선택할 경우 3초후 갑자기 텍스트가 나타날 수 있으니 fade-in 애니메이션을 함께 넣어주면 좋다&lt;/p&gt;
&lt;h3 id=&quot;%ED%8F%B0%ED%8A%B8-%EC%82%AC%EC%9D%B4%EC%A6%88-%EA%B0%90%EC%86%8C&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;519&quot; data-ke-size=&quot;size23&quot;&gt;폰트 사이즈 감소&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;520&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;521&quot;&gt;&lt;b&gt;압축률&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;523&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;524&quot;&gt;EOT &amp;lt; TTF/OTF &amp;lt; WOFF &amp;lt; WOFF2&lt;/li&gt;
&lt;li data-nodeid=&quot;526&quot;&gt;Transfonter라는 서비스에서 확장자 변환.&lt;/li&gt;
&lt;li data-nodeid=&quot;528&quot;&gt;우선순위에 순서로 src 설정&lt;/li&gt;
&lt;li data-nodeid=&quot;521&quot;&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8;&quot; data-nodeid=&quot;530&quot;&gt;&lt;code&gt;@font-face{
  font-family: BMYEONGSUNG;
  src: url('./assets/fonts/BMYEONGSUNG.woff2') format('woff2'), url('./assets/fonts/BMYEONGSUNG.woff') format('woff'), url('./assets/fonts/BMYEONGSUNG.ttf') format('ttf'),
  font-display: fallback;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;531&quot;&gt;&lt;b&gt;서브셋 폰트 사용&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;533&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;534&quot;&gt;특정 영역에서 특정 문자의 폰트 정보만 가지고 있으면 된다. 이를 서브셋 폰트라고 한다.&lt;/li&gt;
&lt;li data-nodeid=&quot;536&quot;&gt;Transfonter 서비스에서 원하는 문자를 넣고 폰트를 생성하면 서브셋 폰트를 생성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8;&quot; data-nodeid=&quot;538&quot;&gt;&lt;code&gt;@font-face{
 font-family: BMYEONGSUNG;
 src: url('./assets/fonts/subset-BMYEONGSUNG.woff2') format('woff2'), url('./assets/fonts/subset-BMYEONGSUNG.woff') format('woff'), url('./assets/fonts/subset-BMYEONGSUNG.ttf') format('ttf'),
 font-display: fallback;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;539&quot;&gt;&lt;b&gt;Data-URI&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;541&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;542&quot;&gt;transfonter 서비스에서 Base64로 encoding에서 이 결과물을 src에 넣으면 폰트 로드시간을 획기적으로 줄일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;%EC%BA%90%EC%8B%9C%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;544&quot; data-ke-size=&quot;size26&quot;&gt;캐시최적화&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;545&quot; data-ke-size=&quot;size16&quot;&gt;Lighthouse Diagnostics를 보면 다음과 같은 진단이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-21 02.10.59.413.png&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;1542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2wBZr/dJMcai2QSCZ/qoEFNrDRE0iVJrBOTsbTZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2wBZr/dJMcai2QSCZ/qoEFNrDRE0iVJrBOTsbTZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2wBZr/dJMcai2QSCZ/qoEFNrDRE0iVJrBOTsbTZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2wBZr%2FdJMcai2QSCZ%2FqoEFNrDRE0iVJrBOTsbTZ1%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;1406&quot; height=&quot;1542&quot; data-filename=&quot;Inline-image-2025-07-21 02.10.59.413.png&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;1542&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;실제로 저중에 하나의 결과를 보면 Response Header에 Cache-Control헤더가 없다&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;Inline-image-2025-07-21 02.11.46.575.png&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;894&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctfhaM/dJMcaiaLgPA/7a7xtttx7XVIjXLxjUZQAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctfhaM/dJMcaiaLgPA/7a7xtttx7XVIjXLxjUZQAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctfhaM/dJMcaiaLgPA/7a7xtttx7XVIjXLxjUZQAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctfhaM%2FdJMcaiaLgPA%2F7a7xtttx7XVIjXLxjUZQAK%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;2032&quot; height=&quot;894&quot; data-filename=&quot;Inline-image-2025-07-21 02.11.46.575.png&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;894&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;547&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;548&quot;&gt;&lt;b&gt;웹 캐시 종류&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;550&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;551&quot;&gt;메모리 캐시: RAM에 저장하는 방식&lt;/li&gt;
&lt;li data-nodeid=&quot;553&quot;&gt;디스크 캐시: 파일 형태로 디스크에 저장하는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;%EC%BA%90%EC%8B%9C-%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;555&quot; data-ke-size=&quot;size26&quot;&gt;캐시 최적화&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;556&quot; data-ke-size=&quot;size16&quot;&gt;Lighthouse Diagnostics 섹션에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;Serve static assets with an efficient cache policy항목을 펼쳐 캐시가 적용되지 않은 파일을 확인. 해당 파일들은 실제로 응답 헤더에&lt;span&gt;&amp;nbsp;&lt;/span&gt;cache-control&lt;span&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-filename=&quot;Inline-image-2025-07-21 11.04.59.970.png&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1qQsr/dJMcagcXgYP/ONplePXjb3aW031CM5uvK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1qQsr/dJMcagcXgYP/ONplePXjb3aW031CM5uvK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1qQsr/dJMcagcXgYP/ONplePXjb3aW031CM5uvK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1qQsr%2FdJMcagcXgYP%2FONplePXjb3aW031CM5uvK1%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;1272&quot; height=&quot;768&quot; data-filename=&quot;Inline-image-2025-07-21 11.04.59.970.png&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;558&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;559&quot;&gt;memory cache: RAM에 저장하는 방식. 브라우저를 끄지않고 페이지 이동, 새로고침 했을때 많이 나타남.&lt;/li&gt;
&lt;li data-nodeid=&quot;561&quot;&gt;disk cache: 파일 형태로 디스크에 저장하는 방식. 브라우저를 껐다 켰을때 나타남.&lt;br /&gt;어떤 캐시를 사용할지는 브라우저가 특정 알고리즘에 의해 알아서 처리. (사용자 제어 불가)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;Cache-Control&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;563&quot; data-ke-size=&quot;size23&quot;&gt;Cache-Control&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;564&quot; data-ke-size=&quot;size16&quot;&gt;FE 서버에서 설정해줘야한다!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;565&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;566&quot;&gt;cache 전략
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;568&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;569&quot;&gt;no-cache: 캐시 써도 되는데, 매번 서버에 확인. revalidation에서 304 응답받으면 cache 사용.&lt;/li&gt;
&lt;li data-nodeid=&quot;571&quot;&gt;no-store: 캐시 사용 금지. 매번 새로 받아올것&lt;/li&gt;
&lt;li data-nodeid=&quot;573&quot;&gt;public: CDN, 프록시 서버에서도 캐시 가능&lt;/li&gt;
&lt;li data-nodeid=&quot;575&quot;&gt;private: 사용자 브라우저에만 캐시 허용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;%EC%A0%81%EC%A0%88%ED%95%9C-%EC%BA%90%EC%8B%9C-%EC%9C%A0%ED%9A%A8%EC%8B%9C%EA%B0%84&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;577&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;적절한 캐시 유효시간&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;578&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;579&quot;&gt;HTML:&lt;span&gt;&amp;nbsp;&lt;/span&gt;no-cache. 항상 최신버전의 웹 서비스를 제공하기 위함.&lt;/li&gt;
&lt;li data-nodeid=&quot;581&quot;&gt;JS,CSS, Font:&lt;span&gt;&amp;nbsp;&lt;/span&gt;public, max-age=31536000. 해시값을 가지고 있어 파일이 변경되면 자동으로 캐시가 풀림&lt;/li&gt;
&lt;li data-nodeid=&quot;583&quot;&gt;default:&lt;span&gt;&amp;nbsp;&lt;/span&gt;no-store&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;%EB%B6%88%ED%95%84%EC%9A%94%ED%95%9C-CSS-%EC%A0%9C%EA%B1%B0&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;585&quot; data-ke-size=&quot;size26&quot;&gt;불필요한 CSS 제거&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;586&quot; data-ke-size=&quot;size16&quot;&gt;Lighthouse를 통해 상황을 파악하고 Coverage 패널을 통해 얼마나 불필요한 코드가 들어있는지 상세하게 알 수 있음.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-21 11.33.32.464.png&quot; data-origin-width=&quot;1412&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czUr5o/dJMcajtVdKa/JXigY560XNopG0ou8ZnNYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czUr5o/dJMcajtVdKa/JXigY560XNopG0ou8ZnNYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czUr5o/dJMcajtVdKa/JXigY560XNopG0ou8ZnNYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczUr5o%2FdJMcajtVdKa%2FJXigY560XNopG0ou8ZnNYK%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;1412&quot; height=&quot;588&quot; data-filename=&quot;Inline-image-2025-07-21 11.33.32.464.png&quot; data-origin-width=&quot;1412&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-21 11.37.10.304.png&quot; data-origin-width=&quot;3484&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsIyGq/dJMcaajuIgK/g8CUq2R5VsnTXzPybEFO60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsIyGq/dJMcaajuIgK/g8CUq2R5VsnTXzPybEFO60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsIyGq/dJMcaajuIgK/g8CUq2R5VsnTXzPybEFO60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsIyGq%2FdJMcaajuIgK%2Fg8CUq2R5VsnTXzPybEFO60%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;3484&quot; height=&quot;624&quot; data-filename=&quot;Inline-image-2025-07-21 11.37.10.304.png&quot; data-origin-width=&quot;3484&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Inline-image-2025-07-21 11.38.29.751.png&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d0sMr7/dJMcaajuIgM/kDaCpJKRRU0iR6nDM5BlVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0sMr7/dJMcaajuIgM/kDaCpJKRRU0iR6nDM5BlVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0sMr7/dJMcaajuIgM/kDaCpJKRRU0iR6nDM5BlVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd0sMr7%2FdJMcaajuIgM%2FkDaCpJKRRU0iR6nDM5BlVk%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;989&quot; height=&quot;302&quot; data-filename=&quot;Inline-image-2025-07-21 11.38.29.751.png&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위의 경우 모두 tailwind css라이브러리에서 추가한 것이다.&lt;br /&gt;이를 해결하기위해 purgeCSS를 사용한다. (하지만 요즘 tailwind는 이런 문제를 자체적으로 해결해준다. v4 기준) 텍스트를 추출하여 사용여부를 판단하는 원리로 작동한다.&lt;/p&gt;
&lt;pre class=&quot;gradle&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;590&quot;&gt;&lt;code&gt;npm install -D purgecss

npx purgecss --css ./build/static/css/*.css --output ./build/static/css/ --content ./build/index.html ./build/static/js/*.js&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;Inline-image-2025-07-21 11.44.03.817.png&quot; data-origin-width=&quot;1805&quot; data-origin-height=&quot;54&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lHRKv/dJMcaajuIgV/RkwKPicIxxDoayNtCUkwc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lHRKv/dJMcaajuIgV/RkwKPicIxxDoayNtCUkwc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lHRKv/dJMcaajuIgV/RkwKPicIxxDoayNtCUkwc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlHRKv%2FdJMcaajuIgV%2FRkwKPicIxxDoayNtCUkwc0%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;1805&quot; height=&quot;54&quot; data-filename=&quot;Inline-image-2025-07-21 11.44.03.817.png&quot; data-origin-width=&quot;1805&quot; data-origin-height=&quot;54&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4&lt;/h2&gt;
&lt;h1 id=&quot;%EA%B0%9C%EC%9A%94&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;769&quot;&gt;개요&lt;/h1&gt;
&lt;h3 id=&quot;%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;770&quot; data-ke-size=&quot;size23&quot;&gt;최적화 기법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;771&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;772&quot;&gt;&lt;b&gt;이미지 지연 로딩&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;774&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;775&quot;&gt;IntersectionObserver 대신&lt;span&gt;&amp;nbsp;&lt;/span&gt;react-lazy-load-image-component&lt;span&gt;&amp;nbsp;&lt;/span&gt;라이브러리 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;777&quot;&gt;&lt;b&gt;레이아웃 이동 피하기&lt;/b&gt;&lt;/li&gt;
&lt;li data-nodeid=&quot;779&quot;&gt;&lt;b&gt;리덕스 렌더링 최적화&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%EC%B5%9C%EC%A0%81%ED%99%94-%ED%88%B4&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;781&quot; data-ke-size=&quot;size23&quot;&gt;최적화 툴&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;782&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;783&quot;&gt;크롬 개발자 도구
&lt;ul style=&quot;list-style-type: circle; color: #222222;&quot; data-nodeid=&quot;785&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-nodeid=&quot;786&quot;&gt;Network 패널&lt;/li&gt;
&lt;li data-nodeid=&quot;788&quot;&gt;Performance 패널&lt;/li&gt;
&lt;li data-nodeid=&quot;790&quot;&gt;Lighthouse 패널&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-nodeid=&quot;792&quot;&gt;React Developer Tools(Profiler)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;794&quot;&gt;최적화&lt;/h1&gt;
&lt;h2 id=&quot;%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%9D%B4%EB%8F%99-%ED%94%BC%ED%95%98%EA%B8%B0&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;795&quot; data-ke-size=&quot;size26&quot;&gt;레이아웃 이동 피하기&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;796&quot; data-ke-size=&quot;size16&quot;&gt;요소의 사이즈를 미리 예측하여 해당 사이즈만큼 공간을 확보&lt;/p&gt;
&lt;h2 id=&quot;%EB%A6%AC%EB%8D%95%EC%8A%A4-%EB%A6%AC%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;797&quot; data-ke-size=&quot;size26&quot;&gt;리덕스 리렌더링 최적화&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;798&quot; data-ke-size=&quot;size16&quot;&gt;React Developer Tools를 설치후&lt;span&gt;&amp;nbsp;&lt;/span&gt;Highlight updates when components render옵션 활성화. 불필요한 리렌더링 발생현황을 파악.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;798&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;798&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 id=&quot;Font-%EB%A1%9C%EB%94%A9-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%95%99%EC%8A%B5-%EC%9E%90%EB%A3%8C&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;829&quot;&gt;Font 로딩 최적화 학습 자료&lt;/h1&gt;
&lt;h2 id=&quot;%EA%B0%9C%EC%9A%94&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;830&quot; data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;831&quot; data-ke-size=&quot;size16&quot;&gt;이 문서는 웹 폰트 로딩 최적화와 관련된 핵심 개념들을 정리한 학습 자료입니다. 최근 커밋에서 적용된 font preload 최적화를 바탕으로 작성되었습니다.&lt;/p&gt;
&lt;h2 id=&quot;1.-%3Clink%3E-%ED%83%9C%EA%B7%B8%EB%9E%80%3F&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;832&quot; data-ke-size=&quot;size26&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;link&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;태그란?&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;833&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;link&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;태그는 HTML 문서와 외부 리소스 간의 관계를 정의하는 HTML 요소입니다.&lt;/p&gt;
&lt;h3 id=&quot;%EC%A3%BC%EC%9A%94-%ED%8A%B9%EC%A7%95&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;834&quot; data-ke-size=&quot;size23&quot;&gt;주요 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;835&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;836&quot;&gt;&lt;b&gt;빈 요소(void element)&lt;/b&gt;: 닫는 태그가 없음&lt;/li&gt;
&lt;li data-nodeid=&quot;838&quot;&gt;&lt;b&gt;외부 리소스 연결&lt;/b&gt;: CSS, 폰트, 아이콘 등의 외부 파일을 문서에 연결&lt;/li&gt;
&lt;li data-nodeid=&quot;840&quot;&gt;&lt;b&gt;메타데이터 제공&lt;/b&gt;: 브라우저에게 리소스의 성격과 처리 방법을 알려줌&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;842&quot; data-ke-size=&quot;size23&quot;&gt;기본 문법&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;843&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;관계&quot; href=&quot;파일경로&quot; type=&quot;MIME타입&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;2.-%3Clink%3E-%ED%83%9C%EA%B7%B8%EC%9D%98-%EC%A3%BC%EC%9A%94-%EC%86%8D%EC%84%B1%EA%B3%BC-%EC%86%8D%EC%84%B1%EA%B0%92&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;844&quot; data-ke-size=&quot;size26&quot;&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;link&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;태그의 주요 속성과 속성값&lt;/h2&gt;
&lt;h3 id=&quot;rel-%EC%86%8D%EC%84%B1-(relationship)&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;845&quot; data-ke-size=&quot;size23&quot;&gt;rel&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성 (relationship)&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;846&quot; data-ke-size=&quot;size16&quot;&gt;문서와 연결된 리소스 간의 관계를 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속성값설명예시&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #000000; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-nodeid=&quot;848&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-nodeid=&quot;858&quot;&gt;
&lt;tr data-nodeid=&quot;859&quot;&gt;
&lt;td data-nodeid=&quot;860&quot;&gt;stylesheet&lt;/td&gt;
&lt;td data-nodeid=&quot;861&quot;&gt;CSS 스타일시트 연결&lt;/td&gt;
&lt;td data-nodeid=&quot;862&quot;&gt;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;style.css&quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-nodeid=&quot;863&quot;&gt;
&lt;td data-nodeid=&quot;864&quot;&gt;preload&lt;/td&gt;
&lt;td data-nodeid=&quot;865&quot;&gt;리소스를 미리 로드 (높은 우선순위)&lt;/td&gt;
&lt;td data-nodeid=&quot;866&quot;&gt;&amp;lt;link rel=&quot;preload&quot; href=&quot;font.woff2&quot; as=&quot;font&quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-nodeid=&quot;867&quot;&gt;
&lt;td data-nodeid=&quot;868&quot;&gt;preconnect&lt;/td&gt;
&lt;td data-nodeid=&quot;869&quot;&gt;외부 도메인과 미리 연결&lt;/td&gt;
&lt;td data-nodeid=&quot;870&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-nodeid=&quot;871&quot;&gt;
&lt;td data-nodeid=&quot;872&quot;&gt;dns-prefetch&lt;/td&gt;
&lt;td data-nodeid=&quot;873&quot;&gt;DNS 조회를 미리 수행&lt;/td&gt;
&lt;td data-nodeid=&quot;874&quot;&gt;&amp;lt;link rel=&quot;dns-prefetch&quot; href=&quot;//example.com&quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-nodeid=&quot;875&quot;&gt;
&lt;td data-nodeid=&quot;876&quot;&gt;icon&lt;/td&gt;
&lt;td data-nodeid=&quot;877&quot;&gt;파비콘 설정&lt;/td&gt;
&lt;td data-nodeid=&quot;878&quot;&gt;&amp;lt;link rel=&quot;icon&quot; href=&quot;favicon.ico&quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-nodeid=&quot;879&quot;&gt;
&lt;td data-nodeid=&quot;880&quot;&gt;canonical&lt;/td&gt;
&lt;td data-nodeid=&quot;881&quot;&gt;정규 URL 지정&lt;/td&gt;
&lt;td data-nodeid=&quot;882&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;%F0%9F%A4%94-%ED%97%B7%EA%B0%88%EB%A6%AC%EA%B8%B0-%EC%89%AC%EC%9A%B4-3%EA%B0%80%EC%A7%80%3A-canonical%2C-dns-prefetch%2C-preconnect-%EC%83%81%EC%84%B8-%EB%B9%84%EA%B5%90&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;883&quot; data-ke-size=&quot;size23&quot;&gt;  헷갈리기 쉬운 3가지:&lt;span&gt;&amp;nbsp;&lt;/span&gt;canonical,&lt;span&gt;&amp;nbsp;&lt;/span&gt;dns-prefetch,&lt;span&gt;&amp;nbsp;&lt;/span&gt;preconnect&lt;span&gt;&amp;nbsp;&lt;/span&gt;상세 비교&lt;/h3&gt;
&lt;h4 id=&quot;1.-rel%3D%22canonical%22---SEO%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%A0%95%EA%B7%9C-URL-%EC%A7%80%EC%A0%95&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;884&quot; data-ke-size=&quot;size20&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;rel=&quot;canonical&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;- SEO를 위한 정규 URL 지정&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;885&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목적&lt;/b&gt;: 검색엔진에게 &quot;이 페이지의 공식적인 URL은 이것이다&quot;라고 알려주는 것&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;886&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 상황&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;887&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;888&quot;&gt;동일한 콘텐츠가 여러 URL로 접근 가능한 경우&lt;/li&gt;
&lt;li data-nodeid=&quot;890&quot;&gt;URL 파라미터로 인한 중복 페이지 문제 해결&lt;/li&gt;
&lt;li data-nodeid=&quot;892&quot;&gt;SEO 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;894&quot;&gt;&lt;code&gt;&amp;lt;!-- 예시 1: 파라미터가 있는 URL의 정규화 --&amp;gt;

&amp;lt;link rel=&quot;canonical&quot; href=&quot;https://shop.com/product?id=123&quot; /&amp;gt;

&amp;lt;!-- 예시 2: 모바일/데스크톱 버전 통합 --&amp;gt;

&amp;lt;link rel=&quot;canonical&quot; href=&quot;https://www.example.com/article&quot; /&amp;gt;

&amp;lt;!-- 예시 3: HTTPS 버전을 정규 URL로 지정 --&amp;gt;

&amp;lt;link rel=&quot;canonical&quot; href=&quot;https://example.com/page&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;895&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 효과&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;896&quot;&gt;&lt;code&gt;❌ canonical 없을 때:
Google 검색 결과에 중복 페이지들이 모두 나타남
- https://shop.com/product?id=123
- https://shop.com/product?id=123&amp;amp;ref=google
- https://shop.com/product?id=123&amp;amp;utm_source=ad

✅ canonical 있을 때:
Google이 정규 URL만 검색 결과에 표시
- https://shop.com/product?id=123 (정규 URL만 노출)&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;2.-rel%3D%22dns-prefetch%22---DNS-%EC%A1%B0%ED%9A%8C-%EB%AF%B8%EB%A6%AC-%EC%8B%A4%ED%96%89&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;897&quot; data-ke-size=&quot;size20&quot;&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;rel=&quot;dns-prefetch&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;- DNS 조회 미리 실행&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;898&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목적&lt;/b&gt;: 외부 도메인의 DNS 조회를 미리 수행하여 나중에 해당 도메인의 리소스를 빠르게 로드&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;899&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동작 과정&lt;/b&gt;:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;900&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-nodeid=&quot;901&quot;&gt;브라우저가&lt;span&gt;&amp;nbsp;&lt;/span&gt;dns-prefetch를 발견&lt;/li&gt;
&lt;li data-nodeid=&quot;903&quot;&gt;백그라운드에서 DNS 조회 실행 (IP 주소 확인)&lt;/li&gt;
&lt;li data-nodeid=&quot;905&quot;&gt;나중에 해당 도메인의 리소스가 필요할 때 DNS 조회 시간 절약&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;907&quot;&gt;&lt;code&gt;&amp;lt;!-- 예시: 외부 CDN에서 폰트를 로드할 예정 --&amp;gt;
&amp;lt;link rel=&quot;dns-prefetch&quot; href=&quot;//fonts.googleapis.com&quot; /&amp;gt;
&amp;lt;link rel=&quot;dns-prefetch&quot; href=&quot;//cdn.jsdelivr.net&quot; /&amp;gt;

&amp;lt;!-- 나중에 실제 리소스 로드 시 DNS 조회 시간 절약됨 --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://fonts.googleapis.com/css2?family=Inter&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;908&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;타이밍 비교&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;909&quot;&gt;&lt;code&gt;❌ dns-prefetch 없을 때:
1.  발견
2. fonts.googleapis.com DNS 조회 시작 (20-100ms)
3. DNS 조회 완료 후 연결 시작
4. 리소스 다운로드

✅ dns-prefetch 있을 때:
1.  발견
2. 백그라운드에서 DNS 조회 미리 완료
3. 나중에  발견
4. DNS 조회 생략하고 즉시 연결 시작 &amp;rarr; 더 빠름!&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;3.-rel%3D%22preconnect%22---%EC%97%B0%EA%B2%B0-%EA%B3%BC%EC%A0%95-%EC%A0%84%EC%B2%B4%EB%A5%BC-%EB%AF%B8%EB%A6%AC-%EC%8B%A4%ED%96%89&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;910&quot; data-ke-size=&quot;size20&quot;&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;rel=&quot;preconnect&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 연결 과정 전체를 미리 실행&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;911&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목적&lt;/b&gt;: DNS 조회 + TCP 연결 + TLS 핸드셰이크까지 모든 연결 과정을 미리 완료&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;912&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동작 과정&lt;/b&gt;:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;913&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-nodeid=&quot;914&quot;&gt;DNS 조회 (IP 주소 확인)&lt;/li&gt;
&lt;li data-nodeid=&quot;916&quot;&gt;TCP 연결 설정&lt;/li&gt;
&lt;li data-nodeid=&quot;918&quot;&gt;TLS/SSL 핸드셰이크 (HTTPS의 경우)&lt;/li&gt;
&lt;li data-nodeid=&quot;920&quot;&gt;연결 준비 완료 상태로 대기&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;922&quot;&gt;&lt;code&gt;&amp;lt;!-- 예시: Google Fonts에서 폰트를 확실히 로드할 예정 --&amp;gt;
&amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://fonts.googleapis.com&quot; /&amp;gt;
&amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://fonts.gstatic.com&quot; crossorigin /&amp;gt;

&amp;lt;!-- 나중에 실제 리소스 로드 시 연결 과정 전체가 생략됨 --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://fonts.googleapis.com/css2?family=Inter&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;923&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;dns-prefetch vs preconnect 비교&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;924&quot;&gt;&lt;code&gt;dns-prefetch (가벼움):
1. DNS 조회만 미리 실행 (20-100ms 절약)
2. 연결이 확실하지 않을 때 사용
3. 여러 도메인에 대해 사용해도 부담 적음

preconnect (강력함):
1. DNS + TCP + TLS 모두 미리 실행 (100-500ms 절약)
2. 연결이 확실할 때만 사용 (리소스 소모가 더 큼)
3. 중요한 2-3개 도메인에만 사용 권장&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;4.-%EC%8B%A4%EC%A0%9C-%EC%82%AC%EC%9A%A9-%EC%8B%9C%EB%82%98%EB%A6%AC%EC%98%A4%EB%B3%84-%EC%84%A0%ED%83%9D-%EA%B0%80%EC%9D%B4%EB%93%9C&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;925&quot; data-ke-size=&quot;size20&quot;&gt;4. 실제 사용 시나리오별 선택 가이드&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;926&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  시나리오 1: Google Fonts 사용&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;927&quot;&gt;&lt;code&gt;&amp;lt;!-- ✅ 권장: 확실히 폰트를 로드할 예정이므로 preconnect --&amp;gt;
&amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://fonts.googleapis.com&quot; /&amp;gt;
&amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://fonts.gstatic.com&quot; crossorigin /&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://fonts.googleapis.com/css2?family=Inter&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;928&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  시나리오 2: 조건부로 외부 리소스 로드 가능&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;929&quot;&gt;&lt;code&gt;&amp;lt;!-- ✅ 권장: 확실하지 않으므로 dns-prefetch --&amp;gt;
&amp;lt;link rel=&quot;dns-prefetch&quot; href=&quot;//cdn.example.com&quot; /&amp;gt;
&amp;lt;link rel=&quot;dns-prefetch&quot; href=&quot;//analytics.google.com&quot; /&amp;gt;

&amp;lt;script&amp;gt;
  // 특정 조건에서만 로드
  if (userConsent) {
    loadScript('https://analytics.google.com/analytics.js')
  }
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;930&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  시나리오 3: 여러 URL로 접근 가능한 상품 페이지&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;931&quot;&gt;&lt;code&gt;&amp;lt;!-- ✅ 권장: SEO를 위한 canonical --&amp;gt;
&amp;lt;!-- 현재 URL: /product?id=123&amp;amp;color=red&amp;amp;size=large --&amp;gt;
&amp;lt;link rel=&quot;canonical&quot; href=&quot;/product?id=123&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;5.-%EC%84%B1%EB%8A%A5-%EC%B8%A1%EC%A0%95%EC%9C%BC%EB%A1%9C-%EB%B3%B4%EB%8A%94-%EC%8B%A4%EC%A0%9C-%ED%9A%A8%EA%B3%BC&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;932&quot; data-ke-size=&quot;size20&quot;&gt;5. 성능 측정으로 보는 실제 효과&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;933&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DNS 조회 시간 측정&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;934&quot;&gt;&lt;code&gt;// 성능 측정 코드
const observer = new PerformanceObserver(list =&amp;gt; {
  for (const entry of list.getEntries()) {
    if (entry.name.includes('fonts.googleapis.com')) {
      console.log(
        'DNS 조회 시간:',
        entry.domainLookupEnd - entry.domainLookupStart
      )
      console.log('연결 시간:', entry.connectEnd - entry.connectStart)
      console.log('전체 시간:', entry.responseEnd - entry.startTime)
    }
  }
})
observer.observe({ entryTypes: ['navigation', 'resource'] })&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;935&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 측정 결과 예시&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;936&quot;&gt;&lt;code&gt;dns-prefetch 없음: DNS 조회 50ms + 연결 100ms = 150ms
dns-prefetch 적용: DNS 조회 0ms + 연결 100ms = 100ms (33% 개선)
preconnect 적용: DNS 조회 0ms + 연결 0ms = 0ms (100% 개선)&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;href-%EC%86%8D%EC%84%B1&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;937&quot; data-ke-size=&quot;size23&quot;&gt;href&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;938&quot; data-ke-size=&quot;size16&quot;&gt;연결할 리소스의 URL 또는 경로를 지정합니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;939&quot;&gt;&lt;code&gt;&amp;lt;!-- 절대 경로 --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;https://cdn.example.com/style.css&quot; /&amp;gt;

&amp;lt;!-- 상대 경로 --&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/css/style.css&quot; /&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;./style.css&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;type-%EC%86%8D%EC%84%B1&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;940&quot; data-ke-size=&quot;size23&quot;&gt;type&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;941&quot; data-ke-size=&quot;size16&quot;&gt;리소스의 MIME 타입을 명시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MIME 타입설명예시&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #000000; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-nodeid=&quot;943&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-nodeid=&quot;953&quot;&gt;
&lt;tr data-nodeid=&quot;954&quot;&gt;
&lt;td data-nodeid=&quot;955&quot;&gt;text/css&lt;/td&gt;
&lt;td data-nodeid=&quot;956&quot;&gt;CSS 파일&lt;/td&gt;
&lt;td data-nodeid=&quot;957&quot;&gt;&amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;style.css&quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-nodeid=&quot;958&quot;&gt;
&lt;td data-nodeid=&quot;959&quot;&gt;font/woff2&lt;/td&gt;
&lt;td data-nodeid=&quot;960&quot;&gt;WOFF2 폰트&lt;/td&gt;
&lt;td data-nodeid=&quot;961&quot;&gt;&amp;lt;link rel=&quot;preload&quot; type=&quot;font/woff2&quot; href=&quot;font.woff2&quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-nodeid=&quot;962&quot;&gt;
&lt;td data-nodeid=&quot;963&quot;&gt;font/ttf&lt;/td&gt;
&lt;td data-nodeid=&quot;964&quot;&gt;TrueType 폰트&lt;/td&gt;
&lt;td data-nodeid=&quot;965&quot;&gt;&amp;lt;link rel=&quot;preload&quot; type=&quot;font/ttf&quot; href=&quot;font.ttf&quot;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;as-%EC%86%8D%EC%84%B1-(preload%EC%99%80-%ED%95%A8%EA%BB%98-%EC%82%AC%EC%9A%A9)&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;966&quot; data-ke-size=&quot;size23&quot;&gt;as&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성 (preload와 함께 사용)&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;967&quot; data-ke-size=&quot;size16&quot;&gt;preload할 리소스의 타입을 지정합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;968&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;preload&quot; href=&quot;font.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; crossorigin /&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;image.jpg&quot; as=&quot;image&quot; /&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;script.js&quot; as=&quot;script&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;crossorigin-%EC%86%8D%EC%84%B1&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;969&quot; data-ke-size=&quot;size23&quot;&gt;crossorigin&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;970&quot; data-ke-size=&quot;size16&quot;&gt;교차 출처 요청 시 CORS 설정을 지정합니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;971&quot;&gt;&lt;code&gt;&amp;lt;!-- 자격 증명 없이 요청 --&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;font.woff2&quot; as=&quot;font&quot; crossorigin=&quot;anonymous&quot; /&amp;gt;

&amp;lt;!-- 자격 증명과 함께 요청 --&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;font.woff2&quot; as=&quot;font&quot; crossorigin=&quot;use-credentials&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;%EA%B8%B0%ED%83%80-%EC%9C%A0%EC%9A%A9%ED%95%9C-%EC%86%8D%EC%84%B1%EB%93%A4&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;972&quot; data-ke-size=&quot;size23&quot;&gt;기타 유용한 속성들&lt;/h3&gt;
&lt;h4 id=&quot;media-%EC%86%8D%EC%84%B1&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;973&quot; data-ke-size=&quot;size20&quot;&gt;media&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;974&quot; data-ke-size=&quot;size16&quot;&gt;특정 미디어 조건에서만 리소스를 로드합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;975&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;print.css&quot; media=&quot;print&quot; /&amp;gt;
&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;mobile.css&quot; media=&quot;(max-width: 768px)&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;sizes-%EC%86%8D%EC%84%B1&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;976&quot; data-ke-size=&quot;size20&quot;&gt;sizes&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;977&quot; data-ke-size=&quot;size16&quot;&gt;아이콘의 크기를 지정합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;978&quot;&gt;&lt;code&gt;&amp;lt;link rel=&quot;icon&quot; href=&quot;icon-32.png&quot; sizes=&quot;32x32&quot; type=&quot;image/png&quot; /&amp;gt;
&amp;lt;link rel=&quot;icon&quot; href=&quot;icon-192.png&quot; sizes=&quot;192x192&quot; type=&quot;image/png&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;3.-%40font-face%EC%99%80-%3Clink%3E%EC%9D%98-%EA%B4%80%EA%B3%84&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;979&quot; data-ke-size=&quot;size26&quot;&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;@font-face와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;link&amp;gt;의 관계&lt;/h2&gt;
&lt;h3 id=&quot;%40font-face-%EB%9E%80%3F&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;980&quot; data-ke-size=&quot;size23&quot;&gt;@font-face&lt;span&gt;&amp;nbsp;&lt;/span&gt;란?&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;981&quot; data-ke-size=&quot;size16&quot;&gt;CSS에서 웹 폰트를 정의하고 사용할 수 있게 해주는 CSS at-rule입니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;982&quot;&gt;&lt;code&gt;@font-face {
  font-family: 'MyCustomFont';
  src:
    url('/fonts/MyCustomFont.woff2') format('woff2'),
    url('/fonts/MyCustomFont.woff') format('woff');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;%3Clink-preload%3E%EC%99%80-%40font-face%EC%9D%98-%ED%98%91%EB%A0%A5&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;983&quot; data-ke-size=&quot;size23&quot;&gt;&amp;lt;link preload&amp;gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;@font-face의 협력&lt;/h3&gt;
&lt;h4 id=&quot;1.-%EB%AC%B8%EC%A0%9C-%EC%83%81%ED%99%A9&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;984&quot; data-ke-size=&quot;size20&quot;&gt;1. 문제 상황&lt;/h4&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;985&quot;&gt;&lt;code&gt;/* @font-face만 사용할 경우 */
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/MyFont.woff2');
}

.title {
  font-family: 'MyFont', sans-serif; /* 이 시점에서야 폰트 다운로드 시작 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;986&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;: 폰트가 실제로 사용되는 순간에야 다운로드가 시작됨 &amp;rarr; FOUT/FOIT 발생&lt;/p&gt;
&lt;h4 id=&quot;2.-%ED%95%B4%EA%B2%B0%EC%B1%85%3A-preload-%2B-%40font-face&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;987&quot; data-ke-size=&quot;size20&quot;&gt;2. 해결책: preload + @font-face&lt;/h4&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;988&quot;&gt;&lt;code&gt;&amp;lt;!-- HTML: 페이지 로드 즉시 폰트 다운로드 시작 --&amp;gt;
&amp;lt;link
  rel=&quot;preload&quot;
  href=&quot;/fonts/MyFont.woff2&quot;
  as=&quot;font&quot;
  type=&quot;font/woff2&quot;
  crossorigin=&quot;anonymous&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;989&quot;&gt;&lt;code&gt;/* CSS: 폰트 정의 및 사용 */
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/MyFont.woff2') format('woff2');
  font-display: swap; /* 로딩 중 fallback 폰트 표시 */
}

.title {
  font-family: 'MyFont', sans-serif; /* 이미 로드된 폰트 즉시 적용 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;3.-%EC%8B%A4%EC%A0%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A0%81%EC%9A%A9-%EC%98%88%EC%8B%9C-(%EC%BB%A4%EB%B0%8B-%EA%B8%B0%EC%A4%80)&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;990&quot; data-ke-size=&quot;size20&quot;&gt;3. 실제 프로젝트 적용 예시 (커밋 기준)&lt;/h4&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;991&quot;&gt;&lt;code&gt;&amp;lt;!-- _document.tsx --&amp;gt;
&amp;lt;link
  rel=&quot;preload&quot;
  href=&quot;/fonts/HyundaiSansHeadPro-Regular.ttf&quot;
  as=&quot;font&quot;
  type=&quot;font/ttf&quot;
  crossorigin=&quot;anonymous&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;992&quot;&gt;&lt;code&gt;/* globals.css */
@font-face {
  font-family: 'HyundaiSansHeadPro';
  src: url('/fonts/HyundaiSansHeadPro-Regular.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;%EB%A1%9C%EB%94%A9-%ED%83%80%EC%9E%84%EB%9D%BC%EC%9D%B8-%EB%B9%84%EA%B5%90&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;993&quot; data-ke-size=&quot;size23&quot;&gt;로딩 타임라인 비교&lt;/h3&gt;
&lt;h4 id=&quot;%E2%9D%8C-preload-%EC%97%86%EC%9D%B4&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;994&quot; data-ke-size=&quot;size20&quot;&gt;❌ preload 없이&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;995&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-nodeid=&quot;996&quot;&gt;HTML 파싱 완료&lt;/li&gt;
&lt;li data-nodeid=&quot;998&quot;&gt;CSS 파싱 완료&lt;/li&gt;
&lt;li data-nodeid=&quot;1000&quot;&gt;레이아웃 계산&lt;/li&gt;
&lt;li data-nodeid=&quot;1002&quot;&gt;폰트가 필요한 요소 발견&lt;/li&gt;
&lt;li data-nodeid=&quot;1004&quot;&gt;&lt;b&gt;폰트 다운로드 시작&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;larr; 늦음&lt;/li&gt;
&lt;li data-nodeid=&quot;1006&quot;&gt;폰트 적용&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;%E2%9C%85-preload-%EC%82%AC%EC%9A%A9&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1008&quot; data-ke-size=&quot;size20&quot;&gt;✅ preload 사용&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;1009&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-nodeid=&quot;1010&quot;&gt;HTML 파싱 시작&lt;/li&gt;
&lt;li data-nodeid=&quot;1012&quot;&gt;&lt;b&gt;preload 발견 &amp;rarr; 폰트 다운로드 시작&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;larr; 빠름&lt;/li&gt;
&lt;li data-nodeid=&quot;1014&quot;&gt;CSS 파싱 완료&lt;/li&gt;
&lt;li data-nodeid=&quot;1016&quot;&gt;레이아웃 계산&lt;/li&gt;
&lt;li data-nodeid=&quot;1018&quot;&gt;폰트가 필요한 요소 발견&lt;/li&gt;
&lt;li data-nodeid=&quot;1020&quot;&gt;이미 로드된 폰트 즉시 적용&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;4.-%40font-face-%EC%83%81%EC%84%B8-%EA%B0%80%EC%9D%B4%EB%93%9C&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1022&quot; data-ke-size=&quot;size26&quot;&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;@font-face&lt;span&gt;&amp;nbsp;&lt;/span&gt;상세 가이드&lt;/h2&gt;
&lt;h3 id=&quot;%EA%B8%B0%EB%B3%B8-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%86%8D%EC%84%B1&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1023&quot; data-ke-size=&quot;size23&quot;&gt;기본 구조와 속성&lt;/h3&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1024&quot;&gt;&lt;code&gt;@font-face {
  font-family: 'FontName'; /* 필수: 폰트 패밀리 이름 */
  src: url('font.woff2'); /* 필수: 폰트 파일 경로 */
  font-weight: normal; /* 선택: 폰트 굵기 */
  font-style: normal; /* 선택: 폰트 스타일 */
  font-display: swap; /* 선택: 로딩 동작 */
  unicode-range: U+0000-00FF; /* 선택: 유니코드 범위 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;%EC%A3%BC%EC%9A%94-%EC%86%8D%EC%84%B1-%EC%83%81%EC%84%B8-%EC%84%A4%EB%AA%85&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1025&quot; data-ke-size=&quot;size23&quot;&gt;주요 속성 상세 설명&lt;/h3&gt;
&lt;h4 id=&quot;font-family&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1026&quot; data-ke-size=&quot;size20&quot;&gt;font-family&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;1027&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;1028&quot;&gt;폰트의 이름을 정의&lt;/li&gt;
&lt;li data-nodeid=&quot;1030&quot;&gt;CSS에서 이 이름으로 폰트를 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1032&quot;&gt;&lt;code&gt;@font-face {
  font-family: 'MyCustomFont';
}

/* 사용 */
.text {
  font-family: 'MyCustomFont', Arial, sans-serif;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;src&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1033&quot; data-ke-size=&quot;size20&quot;&gt;src&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;1034&quot; data-ke-size=&quot;size16&quot;&gt;폰트 파일의 위치와 형식을 지정합니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1035&quot;&gt;&lt;code&gt;/* 단일 형식 */
src: url('/fonts/font.woff2') format('woff2');

/* 여러 형식 (fallback) */
src:
  url('/fonts/font.woff2') format('woff2'),
  url('/fonts/font.woff') format('woff'),
  url('/fonts/font.ttf') format('truetype');

/* 로컬 폰트 우선 확인 */
src:
  local('Arial Bold'),
  local('Arial-Bold'),
  url('/fonts/arial-bold.woff2') format('woff2');&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;font-weight&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1036&quot; data-ke-size=&quot;size20&quot;&gt;font-weight&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;1037&quot; data-ke-size=&quot;size16&quot;&gt;폰트의 굵기를 지정합니다.&lt;/p&gt;
&lt;pre class=&quot;scss&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1038&quot;&gt;&lt;code&gt;/* 숫자 값 */
font-weight: 400; /* normal */
font-weight: 700; /* bold */

/* 키워드 */
font-weight: normal;
font-weight: bold;

/* 범위 지정 (가변 폰트) */
font-weight: 100 900;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;font-style&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1039&quot; data-ke-size=&quot;size20&quot;&gt;font-style&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;1040&quot; data-ke-size=&quot;size16&quot;&gt;폰트의 스타일을 지정합니다.&lt;/p&gt;
&lt;pre class=&quot;maxima&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1041&quot;&gt;&lt;code&gt;font-style: normal; /* 기본값 */
font-style: italic; /* 이탤릭 */
font-style: oblique; /* 기울어진 */&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;font-display&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1042&quot; data-ke-size=&quot;size20&quot;&gt;font-display&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;1043&quot; data-ke-size=&quot;size16&quot;&gt;폰트 로딩 중 표시 방식을 제어합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값설명사용 사례&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #000000; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-nodeid=&quot;1045&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-nodeid=&quot;1055&quot;&gt;
&lt;tr data-nodeid=&quot;1056&quot;&gt;
&lt;td data-nodeid=&quot;1057&quot;&gt;auto&lt;/td&gt;
&lt;td data-nodeid=&quot;1058&quot;&gt;브라우저 기본 동작&lt;/td&gt;
&lt;td data-nodeid=&quot;1059&quot;&gt;기본값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-nodeid=&quot;1060&quot;&gt;
&lt;td data-nodeid=&quot;1061&quot;&gt;block&lt;/td&gt;
&lt;td data-nodeid=&quot;1062&quot;&gt;폰트 로드까지 텍스트 숨김 (최대 3초)&lt;/td&gt;
&lt;td data-nodeid=&quot;1063&quot;&gt;아이콘 폰트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-nodeid=&quot;1064&quot;&gt;
&lt;td data-nodeid=&quot;1065&quot;&gt;swap&lt;/td&gt;
&lt;td data-nodeid=&quot;1066&quot;&gt;즉시 fallback 폰트 표시, 로드 후 교체&lt;/td&gt;
&lt;td data-nodeid=&quot;1067&quot;&gt;&lt;b&gt;일반 텍스트 (권장)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-nodeid=&quot;1068&quot;&gt;
&lt;td data-nodeid=&quot;1069&quot;&gt;fallback&lt;/td&gt;
&lt;td data-nodeid=&quot;1070&quot;&gt;100ms 후 fallback 표시, 3초 내 로드 시 교체&lt;/td&gt;
&lt;td data-nodeid=&quot;1071&quot;&gt;성능 중시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-nodeid=&quot;1072&quot;&gt;
&lt;td data-nodeid=&quot;1073&quot;&gt;optional&lt;/td&gt;
&lt;td data-nodeid=&quot;1074&quot;&gt;100ms 후 fallback 표시, 캐시된 경우만 사용&lt;/td&gt;
&lt;td data-nodeid=&quot;1075&quot;&gt;매우 빠른 로딩 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1076&quot;&gt;&lt;code&gt;/* 권장: 즉시 텍스트 표시, 폰트 로드 후 교체 */
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/MyFont.woff2') format('woff2');
  font-display: swap;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;unicode-range&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1077&quot; data-ke-size=&quot;size20&quot;&gt;unicode-range&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;1078&quot; data-ke-size=&quot;size16&quot;&gt;특정 문자 범위에만 폰트를 적용합니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1079&quot;&gt;&lt;code&gt;/* 영문만 */
@font-face {
  font-family: 'EnglishFont';
  src: url('/fonts/english.woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}

/* 한글만 */
@font-face {
  font-family: 'KoreanFont';
  src: url('/fonts/korean.woff2');
  unicode-range: U+AC00-D7AF;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;%EA%B3%A0%EA%B8%89-%ED%8C%A8%ED%84%B4&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1080&quot; data-ke-size=&quot;size23&quot;&gt;고급 패턴&lt;/h3&gt;
&lt;h4 id=&quot;1.-%EB%8F%99%EC%9D%BC-%ED%8C%A8%EB%B0%80%EB%A6%AC-%EB%82%B4-%EB%8B%A4%EC%96%91%ED%95%9C-weight%2Fstyle&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1081&quot; data-ke-size=&quot;size20&quot;&gt;1. 동일 패밀리 내 다양한 weight/style&lt;/h4&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1082&quot;&gt;&lt;code&gt;/* Regular */
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/MyFont-Regular.woff2');
  font-weight: 400;
  font-style: normal;
}

/* Bold */
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/MyFont-Bold.woff2');
  font-weight: 700;
  font-style: normal;
}

/* Italic */
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/MyFont-Italic.woff2');
  font-weight: 400;
  font-style: italic;
}

/* 사용 */
.text {
  font-family: 'MyFont', sans-serif;
}
.text.bold {
  font-weight: 700; /* Bold 폰트 자동 선택 */
}
.text.italic {
  font-style: italic; /* Italic 폰트 자동 선택 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;2.-%EA%B0%80%EB%B3%80-%ED%8F%B0%ED%8A%B8-(Variable-Font)&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1083&quot; data-ke-size=&quot;size20&quot;&gt;2. 가변 폰트 (Variable Font)&lt;/h4&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1084&quot;&gt;&lt;code&gt;@font-face {
  font-family: 'VariableFont';
  src: url('/fonts/variable-font.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-stretch: 75% 125%;
}

/* 사용 */
.text {
  font-family: 'VariableFont';
  font-weight: 350; /* 정확한 굵기 */
  font-stretch: 110%; /* 폭 조절 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;5.-%3Clink%3E-%ED%83%9C%EA%B7%B8%EC%9D%98-%EC%9C%84%EC%B9%98%3A-%3Chead%3E-vs-%3Cbody%3E&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1085&quot; data-ke-size=&quot;size26&quot;&gt;5.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;link&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;태그의 위치:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;head&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;vs&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;body&amp;gt;&lt;/h2&gt;
&lt;h3 id=&quot;%3Chead%3E-%EB%82%B4-%3Clink%3E-(%ED%91%9C%EC%A4%80%EC%9D%B4%EC%9E%90-%EA%B6%8C%EC%9E%A5%EC%82%AC%ED%95%AD)&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1086&quot; data-ke-size=&quot;size23&quot;&gt;&amp;lt;head&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;내&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;link&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(표준이자 권장사항)&lt;/h3&gt;
&lt;h4 id=&quot;%EC%9E%A5%EC%A0%90&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1087&quot; data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;1088&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-nodeid=&quot;1089&quot;&gt;&lt;b&gt;빠른 로딩&lt;/b&gt;: HTML 파싱 초기에 발견되어 즉시 다운로드 시작&lt;/li&gt;
&lt;li data-nodeid=&quot;1091&quot;&gt;&lt;b&gt;렌더링 블로킹 방지&lt;/b&gt;: CSS는 렌더링을 블로킹하지만, 빠른 발견으로 지연 최소화&lt;/li&gt;
&lt;li data-nodeid=&quot;1093&quot;&gt;&lt;b&gt;표준 준수&lt;/b&gt;: HTML 명세에 따른 올바른 사용법&lt;/li&gt;
&lt;li data-nodeid=&quot;1095&quot;&gt;&lt;b&gt;SEO 친화적&lt;/b&gt;: 검색엔진이 메타데이터를 빠르게 인식&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1097&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;!-- ✅ 권장: head 내 위치 --&amp;gt;
    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/css/critical.css&quot; /&amp;gt;
    &amp;lt;link rel=&quot;preload&quot; href=&quot;/fonts/main.woff2&quot; as=&quot;font&quot; crossorigin /&amp;gt;
    &amp;lt;link rel=&quot;dns-prefetch&quot; href=&quot;//fonts.googleapis.com&quot; /&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Content&amp;lt;/h1&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;%EB%A1%9C%EB%94%A9-%ED%83%80%EC%9E%84%EB%9D%BC%EC%9D%B8-(head-%EB%82%B4-link)&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1098&quot; data-ke-size=&quot;size20&quot;&gt;로딩 타임라인 (head 내 link)&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1099&quot;&gt;&lt;code&gt;0ms    HTML 파싱 시작
1ms    &amp;lt;link&amp;gt; 발견 &amp;rarr; CSS/폰트 다운로드 시작
...    HTML 계속 파싱
100ms  &amp;lt;body&amp;gt; 파싱
150ms  CSS 다운로드 완료
200ms  렌더링 시작 (스타일 적용됨)&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;%3Cbody%3E-%EB%82%B4-%3Clink%3E-(%ED%8A%B9%EC%88%98%ED%95%9C-%EA%B2%BD%EC%9A%B0)&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1100&quot; data-ke-size=&quot;size23&quot;&gt;&amp;lt;body&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;내&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;link&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(특수한 경우)&lt;/h3&gt;
&lt;h4 id=&quot;%EC%82%AC%EC%9A%A9-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%BC%80%EC%9D%B4%EC%8A%A4&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1101&quot; data-ke-size=&quot;size20&quot;&gt;사용 가능한 케이스&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;1102&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-nodeid=&quot;1103&quot;&gt;&lt;b&gt;Progressive Enhancement&lt;/b&gt;: 점진적 개선&lt;/li&gt;
&lt;li data-nodeid=&quot;1105&quot;&gt;&lt;b&gt;조건부 로딩&lt;/b&gt;: 특정 상황에서만 필요한 리소스&lt;/li&gt;
&lt;li data-nodeid=&quot;1107&quot;&gt;&lt;b&gt;지연 로딩&lt;/b&gt;: 중요하지 않은 스타일의 지연 로딩&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1109&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;header&amp;gt;메인 콘텐츠&amp;lt;/header&amp;gt;

  &amp;lt;!--   모바일에서만 필요한 스타일 --&amp;gt;
  &amp;lt;script&amp;gt;
    if (window.innerWidth &amp;lt; 768) {
      document.head.appendChild(
        Object.assign(document.createElement('link'), {
          rel: 'stylesheet',
          href: '/css/mobile-only.css',
        })
      )
    }
  &amp;lt;/script&amp;gt;

  &amp;lt;!--   중요하지 않은 장식적 스타일 --&amp;gt;
  &amp;lt;section class=&quot;decorative-section&quot;&amp;gt;
    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/css/decorations.css&quot; /&amp;gt;
    &amp;lt;!-- 이 섹션의 스타일만 로드 --&amp;gt;
  &amp;lt;/section&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;%EB%A1%9C%EB%94%A9-%ED%83%80%EC%9E%84%EB%9D%BC%EC%9D%B8-(body-%EB%82%B4-link)&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1110&quot; data-ke-size=&quot;size20&quot;&gt;로딩 타임라인 (body 내 link)&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1111&quot;&gt;&lt;code&gt;0ms    HTML 파싱 시작
100ms  &amp;lt;body&amp;gt; 파싱
150ms  &amp;lt;link&amp;gt; 발견 &amp;rarr; CSS 다운로드 시작 (늦음!)
200ms  첫 렌더링 (스타일 없음)
250ms  CSS 다운로드 완료
300ms  리렌더링 (스타일 적용) &amp;rarr; FOUC 발생!&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;%EC%84%B1%EB%8A%A5-%EB%B9%84%EA%B5%90-%EB%B0%8F-%EA%B6%8C%EC%9E%A5%EC%82%AC%ED%95%AD&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1112&quot; data-ke-size=&quot;size23&quot;&gt;성능 비교 및 권장사항&lt;/h3&gt;
&lt;h4 id=&quot;%E2%9C%85-%EA%B6%8C%EC%9E%A5%3A-Critical-CSS%EB%8A%94-head%EC%97%90&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1113&quot; data-ke-size=&quot;size20&quot;&gt;✅ 권장: Critical CSS는 head에&lt;/h4&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1114&quot;&gt;&lt;code&gt;&amp;lt;head&amp;gt;
  &amp;lt;!-- 필수 스타일 --&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/css/critical.css&quot; /&amp;gt;

  &amp;lt;!-- 폰트 preload --&amp;gt;
  &amp;lt;link rel=&quot;preload&quot; href=&quot;/fonts/main.woff2&quot; as=&quot;font&quot; crossorigin /&amp;gt;

  &amp;lt;!-- Above-the-fold 콘텐츠에 필요한 모든 리소스 --&amp;gt;
&amp;lt;/head&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;%F0%9F%92%A1-%EA%B3%A0%EA%B8%89-%ED%8C%A8%ED%84%B4%3A-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C-%EC%A0%91%EA%B7%BC&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1115&quot; data-ke-size=&quot;size20&quot;&gt;  고급 패턴: 하이브리드 접근&lt;/h4&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1116&quot;&gt;&lt;code&gt;&amp;lt;head&amp;gt;
  &amp;lt;!-- Critical CSS 인라인 --&amp;gt;
  &amp;lt;style&amp;gt;
    /* 핵심 스타일만 인라인으로 포함 */
    body {
      font-family: system-ui;
    }
    .header {
      background: #000;
    }
  &amp;lt;/style&amp;gt;

  &amp;lt;!-- 폰트 preload --&amp;gt;
  &amp;lt;link rel=&quot;preload&quot; href=&quot;/fonts/main.woff2&quot; as=&quot;font&quot; crossorigin /&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;header class=&quot;header&quot;&amp;gt;즉시 스타일 적용됨&amp;lt;/header&amp;gt;

  &amp;lt;!-- 나머지 CSS는 비동기 로드 --&amp;gt;
  &amp;lt;link
    rel=&quot;preload&quot;
    href=&quot;/css/main.css&quot;
    as=&quot;style&quot;
    onload=&quot;this.onload=null;this.rel='stylesheet'&quot; /&amp;gt;
  &amp;lt;noscript&amp;gt;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/css/main.css&quot; /&amp;gt;&amp;lt;/noscript&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;6.-%EC%B5%9C%EC%A0%81%ED%99%94-Best-Practices&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1117&quot; data-ke-size=&quot;size26&quot;&gt;6. 최적화 Best Practices&lt;/h2&gt;
&lt;h3 id=&quot;1.-%ED%8F%B0%ED%8A%B8-%EB%A1%9C%EB%94%A9-%EC%B5%9C%EC%A0%81%ED%99%94-%EC%B2%B4%ED%81%AC%EB%A6%AC%EC%8A%A4%ED%8A%B8&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1118&quot; data-ke-size=&quot;size23&quot;&gt;1. 폰트 로딩 최적화 체크리스트&lt;/h3&gt;
&lt;h4 id=&quot;%E2%9C%85-%ED%95%84%EC%88%98-%EC%82%AC%ED%95%AD&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1119&quot; data-ke-size=&quot;size20&quot;&gt;✅ 필수 사항&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;1120&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot; data-te-task=&quot;&quot; data-nodeid=&quot;1121&quot;&gt;중요한 폰트는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;link rel=&quot;preload&quot;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사용&lt;/li&gt;
&lt;li style=&quot;list-style-type: none;&quot; data-te-task=&quot;&quot; data-nodeid=&quot;1123&quot;&gt;@font-face에&lt;span&gt;&amp;nbsp;&lt;/span&gt;font-display: swap&lt;span&gt;&amp;nbsp;&lt;/span&gt;설정&lt;/li&gt;
&lt;li style=&quot;list-style-type: none;&quot; data-te-task=&quot;&quot; data-nodeid=&quot;1125&quot;&gt;crossorigin=&quot;anonymous&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성 추가&lt;/li&gt;
&lt;li style=&quot;list-style-type: none;&quot; data-te-task=&quot;&quot; data-nodeid=&quot;1127&quot;&gt;WOFF2 형식 우선 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;%E2%9C%85-%EA%B6%8C%EC%9E%A5-%EC%82%AC%ED%95%AD&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1129&quot; data-ke-size=&quot;size20&quot;&gt;✅ 권장 사항&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;1130&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot; data-te-task=&quot;&quot; data-nodeid=&quot;1131&quot;&gt;폰트 서브셋팅으로 파일 크기 줄이기&lt;/li&gt;
&lt;li style=&quot;list-style-type: none;&quot; data-te-task=&quot;&quot; data-nodeid=&quot;1133&quot;&gt;unicode-range로 필요한 문자만 로드&lt;/li&gt;
&lt;li style=&quot;list-style-type: none;&quot; data-te-task=&quot;&quot; data-nodeid=&quot;1135&quot;&gt;시스템 폰트를 fallback으로 설정&lt;/li&gt;
&lt;li style=&quot;list-style-type: none;&quot; data-te-task=&quot;&quot; data-nodeid=&quot;1137&quot;&gt;폰트 로딩 전략 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2.-%EC%8B%A4%EC%A0%9C-%EA%B5%AC%ED%98%84-%EC%98%88%EC%8B%9C-(%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B8%B0%EC%A4%80)&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1139&quot; data-ke-size=&quot;size23&quot;&gt;2. 실제 구현 예시 (프로젝트 기준)&lt;/h3&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1140&quot;&gt;&lt;code&gt;&amp;lt;!-- _document.tsx --&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;!-- 1. 핵심 폰트 preload --&amp;gt;
  &amp;lt;link
    rel=&quot;preload&quot;
    href=&quot;/fonts/HyundaiSansTextPro-Regular.ttf&quot;
    as=&quot;font&quot;
    type=&quot;font/ttf&quot;
    crossorigin=&quot;anonymous&quot; /&amp;gt;
  &amp;lt;link
    rel=&quot;preload&quot;
    href=&quot;/fonts/HyundaiSansTextPro-Bold.ttf&quot;
    as=&quot;font&quot;
    type=&quot;font/ttf&quot;
    crossorigin=&quot;anonymous&quot; /&amp;gt;
&amp;lt;/head&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1141&quot;&gt;&lt;code&gt;/* globals.css */
/* 2. @font-face 정의 */
@font-face {
  font-family: 'HyundaiSansTextPro';
  src: url('/fonts/HyundaiSansTextPro-Regular.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
  font-display: swap; /* 3. FOUC 방지 */
}

@font-face {
  font-family: 'HyundaiSansTextPro';
  src: url('/fonts/HyundaiSansTextPro-Bold.ttf') format('truetype');
  font-weight: bold;
  font-style: normal;
  font-display: swap;
}

/* 4. 시스템 폰트 fallback과 함께 사용 */
body {
  font-family:
    'HyundaiSansTextPro',
    -apple-system,
    BlinkMacSystemFont,
    sans-serif;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;3.-%EC%84%B1%EB%8A%A5-%EC%B8%A1%EC%A0%95-%EB%B0%8F-%EB%94%94%EB%B2%84%EA%B9%85&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1142&quot; data-ke-size=&quot;size23&quot;&gt;3. 성능 측정 및 디버깅&lt;/h3&gt;
&lt;h4 id=&quot;Chrome-DevTools%EC%97%90%EC%84%9C-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1143&quot; data-ke-size=&quot;size20&quot;&gt;Chrome DevTools에서 확인하기&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;1144&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-nodeid=&quot;1145&quot;&gt;&lt;b&gt;Network 탭&lt;/b&gt;: 폰트 로딩 타이밍 확인&lt;/li&gt;
&lt;li data-nodeid=&quot;1147&quot;&gt;&lt;b&gt;Performance 탭&lt;/b&gt;: 렌더링 블로킹 확인&lt;/li&gt;
&lt;li data-nodeid=&quot;1149&quot;&gt;&lt;b&gt;Lighthouse&lt;/b&gt;: 폰트 최적화 제안 확인&lt;/li&gt;
&lt;li data-nodeid=&quot;1151&quot;&gt;&lt;b&gt;Coverage 탭&lt;/b&gt;: 사용되지 않는 폰트 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;%EC%A4%91%EC%9A%94-%EB%A9%94%ED%8A%B8%EB%A6%AD&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1153&quot; data-ke-size=&quot;size20&quot;&gt;중요 메트릭&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;1154&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;1155&quot;&gt;&lt;b&gt;FCP (First Contentful Paint)&lt;/b&gt;: 첫 콘텐츠 표시 시간&lt;/li&gt;
&lt;li data-nodeid=&quot;1157&quot;&gt;&lt;b&gt;LCP (Largest Contentful Paint)&lt;/b&gt;: 가장 큰 콘텐츠 표시 시간&lt;/li&gt;
&lt;li data-nodeid=&quot;1159&quot;&gt;&lt;b&gt;FOUT/FOIT&lt;/b&gt;: 폰트 교체/숨김 현상&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;7.-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD-%EB%B0%8F-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1161&quot; data-ke-size=&quot;size26&quot;&gt;7. 주의사항 및 트러블슈팅&lt;/h2&gt;
&lt;h3 id=&quot;%ED%9D%94%ED%95%9C-%EC%8B%A4%EC%88%98%EB%93%A4&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1162&quot; data-ke-size=&quot;size23&quot;&gt;흔한 실수들&lt;/h3&gt;
&lt;h4 id=&quot;%E2%9D%8C-%EC%9E%98%EB%AA%BB%EB%90%9C-%EC%98%88%EC%8B%9C&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1163&quot; data-ke-size=&quot;size20&quot;&gt;❌ 잘못된 예시&lt;/h4&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1164&quot;&gt;&lt;code&gt;&amp;lt;!-- 1. crossorigin 누락 --&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;/fonts/font.woff2&quot; as=&quot;font&quot; /&amp;gt;

&amp;lt;!-- 2. type 속성 누락 --&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;/fonts/font.woff2&quot; as=&quot;font&quot; crossorigin /&amp;gt;

&amp;lt;!-- 3. @font-face와 경로 불일치 --&amp;gt;
&amp;lt;link rel=&quot;preload&quot; href=&quot;/fonts/font.woff2&quot; as=&quot;font&quot; crossorigin /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1165&quot;&gt;&lt;code&gt;@font-face {
  font-family: 'MyFont';
  src: url('/fonts/font.ttf'); /* ❌ 다른 파일! */
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;%E2%9C%85-%EC%98%AC%EB%B0%94%EB%A5%B8-%EC%98%88%EC%8B%9C&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1166&quot; data-ke-size=&quot;size20&quot;&gt;✅ 올바른 예시&lt;/h4&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1167&quot;&gt;&lt;code&gt;&amp;lt;!-- 1. 모든 필수 속성 포함 --&amp;gt;
&amp;lt;link
  rel=&quot;preload&quot;
  href=&quot;/fonts/font.woff2&quot;
  as=&quot;font&quot;
  type=&quot;font/woff2&quot;
  crossorigin=&quot;anonymous&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1168&quot;&gt;&lt;code&gt;@font-face {
  font-family: 'MyFont';
  src: url('/fonts/font.woff2') format('woff2'); /* ✅ 동일한 파일 */
  font-display: swap;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&quot;%EB%94%94%EB%B2%84%EA%B9%85-%ED%8C%81&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1169&quot; data-ke-size=&quot;size23&quot;&gt;디버깅 팁&lt;/h3&gt;
&lt;h4 id=&quot;1.-%ED%8F%B0%ED%8A%B8%EA%B0%80-%EB%A1%9C%EB%93%9C%EB%90%98%EC%A7%80-%EC%95%8A%EC%9D%84-%EB%95%8C&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1170&quot; data-ke-size=&quot;size20&quot;&gt;1. 폰트가 로드되지 않을 때&lt;/h4&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;background-color: #f4f7f8; color: #222222; text-align: start;&quot; data-nodeid=&quot;1171&quot;&gt;&lt;code&gt;// 폰트 로딩 상태 확인
document.fonts.ready.then(() =&amp;gt; {
  console.log('모든 폰트 로드 완료')
})

// 특정 폰트 로딩 확인
if (document.fonts.check('16px MyFont')) {
  console.log('MyFont 사용 가능')
} else {
  console.log('MyFont 아직 로드되지 않음')
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div style=&quot;color: #222222;&quot; data-testid=&quot;CopyButton_SvgIcon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&quot;2.-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%98%A4%EB%A5%98-%ED%99%95%EC%9D%B8&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1172&quot; data-ke-size=&quot;size20&quot;&gt;2. 네트워크 오류 확인&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;1173&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-nodeid=&quot;1174&quot;&gt;CORS 에러:&lt;span&gt;&amp;nbsp;&lt;/span&gt;crossorigin&lt;span&gt;&amp;nbsp;&lt;/span&gt;속성 확인&lt;/li&gt;
&lt;li data-nodeid=&quot;1176&quot;&gt;404 에러: 파일 경로 확인&lt;/li&gt;
&lt;li data-nodeid=&quot;1178&quot;&gt;MIME 타입 에러: 서버 설정 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-nodeid=&quot;1180&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 id=&quot;%EC%9A%94%EC%95%BD&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1181&quot; data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;1182&quot; data-ke-size=&quot;size16&quot;&gt;Font 로딩 최적화의 핵심은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;lt;link rel=&quot;preload&quot;&amp;gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;@font-face의 조합&lt;/b&gt;입니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;1183&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-nodeid=&quot;1184&quot;&gt;&lt;b&gt;&amp;lt;link rel=&quot;preload&quot;&amp;gt;&lt;/b&gt;: 페이지 로드 즉시 폰트 다운로드 시작&lt;/li&gt;
&lt;li data-nodeid=&quot;1186&quot;&gt;&lt;b&gt;@font-face&lt;/b&gt;: 폰트 정의 및 fallback 설정&lt;/li&gt;
&lt;li data-nodeid=&quot;1188&quot;&gt;&lt;b&gt;font-display: swap&lt;/b&gt;: 로딩 중 텍스트 숨김 방지&lt;/li&gt;
&lt;li data-nodeid=&quot;1190&quot;&gt;&lt;b&gt;crossorigin=&quot;anonymous&quot;&lt;/b&gt;: CORS 문제 해결&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;1192&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해 FOUT/FOIT를 방지하고 사용자 경험을 크게 개선할 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;%EC%9B%B9-%ED%8F%B0%ED%8A%B8-%EB%A1%9C%EB%94%A9-%EB%AC%B8%EC%A0%9C%3A-FOUT-vs-FOIT&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1193&quot; data-ke-size=&quot;size23&quot;&gt;웹 폰트 로딩 문제: FOUT vs FOIT&lt;/h3&gt;
&lt;h4 id=&quot;FOUT-(Flash-of-Unstyled-Text)&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1194&quot; data-ke-size=&quot;size20&quot;&gt;FOUT (Flash of Unstyled Text)&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;1195&quot; data-ke-size=&quot;size16&quot;&gt;&quot;스타일이 적용되지 않은 텍스트의 깜빡임&quot;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;1196&quot; data-ke-size=&quot;size16&quot;&gt;동작 과정:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;1197&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-nodeid=&quot;1198&quot;&gt;페이지 로드 시 즉시 fallback 폰트로 텍스트 표시&lt;/li&gt;
&lt;li data-nodeid=&quot;1200&quot;&gt;웹 폰트 다운로드 완료 후 웹 폰트로 교체&lt;/li&gt;
&lt;li data-nodeid=&quot;1202&quot;&gt;폰트 교체 시 깜빡임/레이아웃 변화 발생&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;FOIT-(Flash-of-Invisible-Text)&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-nodeid=&quot;1204&quot; data-ke-size=&quot;size20&quot;&gt;FOIT (Flash of Invisible Text)&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;1205&quot; data-ke-size=&quot;size16&quot;&gt;&quot;보이지 않는 텍스트의 깜빡임&quot;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-nodeid=&quot;1206&quot; data-ke-size=&quot;size16&quot;&gt;동작 과정:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222222; text-align: start;&quot; data-nodeid=&quot;1207&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-nodeid=&quot;1208&quot;&gt;페이지 로드 시 텍스트를 숨김 (투명 처리)&lt;/li&gt;
&lt;li data-nodeid=&quot;1210&quot;&gt;웹 폰트 다운로드 완료까지 빈 공간만 표시&lt;/li&gt;
&lt;li data-nodeid=&quot;1212&quot;&gt;폰트 로드 후 갑자기 텍스트 나타남&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>zakelstorm</author>
      <guid isPermaLink="true">https://zakelstorm.tistory.com/189</guid>
      <comments>https://zakelstorm.tistory.com/189#entry189comment</comments>
      <pubDate>Mon, 8 Dec 2025 14:41:30 +0900</pubDate>
    </item>
    <item>
      <title>private npm(Verdaccio)에 msw toolkit publish 하기</title>
      <link>https://zakelstorm.tistory.com/180</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;monorepo&lt;/b&gt; 환경에서 사용하기 좋다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;polyrepo&lt;/b&gt;&lt;span&gt;로 구성할 경우&lt;span&gt;&amp;nbsp;프로젝트간&lt;/span&gt;&lt;/span&gt;&amp;nbsp;공용 컴포넌트에 대한 코드가 중복으로 늘어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI, 기능이 같은 컴포넌트를 하나의 repository에서 구성하고 싶은 경우 Monorepo로 구성하는 것이 이득이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Private npm&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 npm package는 public npm에서 제공하는 package를 install에서 사용하는 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 package를 private하게 관리해야 한다면 외부로 노출되지 않도록 npm registry를 구성해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 사내에서 재사용할 수 있는 모듈을 패키지화해서 공유하기 위해 private npm을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private npm 서비스의 종류로는 Verdaccio, Gihub packages, npm pro 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 회사는 node package의 버전 관리 용이성을위해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app1은 private npm의 package@0.1.0을 보는데 app2는 privatge npm의 package@0.1.1을 볼수있도록 설정.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Verdaccio vs Github Packages&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Github Packages&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;github action 연동 가능. webhook 지원. =&amp;gt; publishing 할때 혹은 publishing 이후 workflow customize 가능하다.&lt;/li&gt;
&lt;li&gt;microsoft가 지원하기 때문에 유지보수가 계속된다.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Verdaccio&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.devh.kr/2020/Host-Publish-and-Manage-Private-npm-Packages-with/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.devh.kr/2020/Host-Publish-and-Manage-Private-npm-Packages-with/&lt;/a&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;verdaccio 설치&lt;/li&gt;
&lt;li&gt;verdaccio 실행&lt;/li&gt;
&lt;li&gt;verdaccio 사용자 등록&lt;/li&gt;
&lt;li&gt;package 업데이트 정보 있을 시 npm publish&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Best Practice&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;public package와 private package 네이밍 구분
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다음의 경우 @my-company/ 와 local- prefix를 가지는 package는 priavate package 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1675761979054&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;packages:
   '@my-company/*':
     access: $authenticated // import packages할때 authenticated 되어야한다. 
     publish: $authenticated
    'local-*':
     access: $all
     publish: $authenticated
   '@*/*':
     access: $all
     publish: $authenticated
   '**':
     access: $all
     publish: $authenticated&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;private repository -&amp;gt; public repository(uplink) -&amp;gt; cache 순서로 보기때문에 이를 이용한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 public repository package가 마음에 안든다면 같은 이름으로 override한다.&lt;/li&gt;
&lt;li&gt;private package의 proxy설정을 해제한다. npmjs.org에서 가져오지 않게하기 위함이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Verdaccio + Docker&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;verdaccio private repository 자체를 docker image로 생성하여 어느 서버에서도 띄울 수 있도록 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1675762775176&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker pull verdaccio/verdaccio:4
docker run -it --rm --name verdaccio -p 4873:4873 verdaccio/verdaccio&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Verdaccio + Environment Variables&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;verdaccio는 permission이나 port 수정을 위해 환경변수를 사용한다.&lt;/p&gt;</description>
      <author>zakelstorm</author>
      <guid isPermaLink="true">https://zakelstorm.tistory.com/180</guid>
      <comments>https://zakelstorm.tistory.com/180#entry180comment</comments>
      <pubDate>Thu, 9 Feb 2023 02:16:50 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머, 열정을 말하다</title>
      <link>https://zakelstorm.tistory.com/174</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우리의 일과는 50% 이상이 일하는데 소비된다. 삶이 일로써 소비된다면 일을 사랑하는 것이 삶을 사랑하는 가장 중요한 열쇠다. &lt;b&gt;일에 도전하고 동기를 부여하고 보상을 얻는 것은 아침에 일어나고 싶게 만든다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 하는 사람이 되는 방법은 잘 하는 사람들이 모입 집단에서 한명이 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신만의 계획을 세워야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 할지 이 책에서 차근차근 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 자신의 시장을 선택하라. 집중할 기술과 사업 분야를 신중하게 골라야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 자신에게 투자하라. 지식과 기술은 자신이라는 상품의 기반이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 실행하라. 뛰어난 기술만 갖춘 직원만으로는 회사 성과가 나오지 않는다. 직원은 기술을 바탕으로 가치를 만들어야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 마케팅하라. 자신을 전혀 알리지 않고 어떻게 인정받을수 있을지 알아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자신의 시장을 선택하라&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우연히 돌아가는 프로그램을 만들어서는 안된다. 어떻게 돌아가는지 이해해야한다.&lt;/li&gt;
&lt;li&gt;기술만 알아서는 안된다. 내가 속한(또는 속하려고 하는) 사업에 대해서도 철저한 이해가 필요하다.(ex. 제조, 금융 지식)&lt;/li&gt;
&lt;li&gt;내가 제일 못하더라도 잘하는 사람들 사이에 있어라. 그곳에서 보통만 해도 뛰어난 개발자가 될 수 있다.&lt;/li&gt;
&lt;li&gt;비주류 기술을 좋아할 수 있다는 것은 당신이 찐 개발자라는 증거가 될 수 있다. 이것 자체가 채용시 장점이 되는 것이고... 비주류 기술을 공부하면 기존에 알고있는 기술에 대해 더 깊게 알 수 있는 기회가 될 수도 있다.(ex Rescript)&lt;/li&gt;
&lt;li&gt;단순 테스터나 단순 코더가 되지말자. 코딩, 테스팅, 아키텍트 다재다능한 사람이 되자. 그러면서 깊이 있게 공부하여 전문가가 되자.&lt;/li&gt;
&lt;li&gt;작은 프로젝트를 두 번 정도 해보자. 한번은 내가 잘 아는 기술로, 한번은 경쟁 기술로 하되 그 기술의 독특한 방식을 사용해보자.&lt;/li&gt;
&lt;li&gt;당신이 IT분야에서 왜 일하나? 우연히 있게된 것인가? 보수가 좋아서? 부모가 권해서? 내 분야에 열정을 쏟을 수 없다면 나쁜 결과를 가져올 수 있다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;2주간 일어나 일을 시작할 때마다 신나는 정도를 1부터 10으로 점수를 매기자. 급상승하는 부분이 있었나? 경향이 있었나? 항상 낮거나 항상 높았나? 평점은 얼마인가? 그 다음 2주간은 매일 아침에 10점으로 만들 계획을 짜라.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;나 자신을 기술로 정의하지 말자. 내가 한 일과 하고 싶은 일로 정의하자. 기술은 성공에 이르는 한 방법일 뿐이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자신에게 투자하라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경력을 위한 투자 전략 &amp;amp; 기술, 실력, 자신에게 투자하는 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;하나의 기술에 대해 '어떻게' 와 '왜'라는 질문을 계속 던져라&lt;/b&gt;. 이는 스스로 찾아야한다.&lt;/li&gt;
&lt;li&gt;무엇인가 정말 배우고 싶다면 내 자신이 멘토가 되어보라. &lt;b&gt;사람은 가르치면서 배우게된다&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;근무 시간을 연습 시간으로 삼지말자. 시간을 별도로 투자해야한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;몸에 익히기: 개발 환경의 여러 도구를 손에 익혀라. 개발자들이 활용할 수 있는 도구(ex. 정규식, 스트림 라이브러리, 컬렉션이나 리스트를 처리할 수 있는 유틸리티) 전체 를 습득해라.&lt;/li&gt;
&lt;li&gt;악보 읽기: &lt;b&gt;오픈소스를 읽고 기여해보아라&lt;/b&gt;. 중요한 것은 내가 보고 있는 것을 빠르게 이해하는 것이다.&lt;/li&gt;
&lt;li&gt;즉흥 연주: 사고를 날카롭게하고 즉흥 코딩 솜씨를 향상시킬 멋진 방법으로 스스로 제한 조건을 두고 연습하라.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특정 문제에 대한 해결 방법을 찾는 것보다 &lt;b&gt;기존 코드를 자신의 스타일과 능력을 점검하는 확대경으로 삼아보자&lt;/b&gt;. 코드를 읽으면서 전에 해본적 없는 것들과 결코 생각해 보지도 못했던 것을 발견할 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실행&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt; &lt;b&gt;파킨슨의 법칙: 끝내는데 필요한 시간에 맞추어 작업이 늘어난다.&lt;/b&gt;&lt;br /&gt;1달 완료 예정인 프로젝트를 1주일 안에 끝내보려고 해보라. 할 수 있다.&lt;/li&gt;
&lt;li&gt;상사에게 보고할 수준의 일을 매일 목표로 삼자. 간단하게 일간/주간/월간 목표를 세우고 성과를 추적하자.&lt;/li&gt;
&lt;li&gt;나는 회사의 좋은 투자 대상인가? 급여 인상을 당연하게 생각하지 말아라.&lt;/li&gt;
&lt;li&gt;회사에서 결코 편해지지 말자. 언제나 겸손해야한다. 매일 일어나면서 현재 지위에서 떨어질 수 있음을 상기하자.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;성공하면 성공할 수록 치명적인 실수를 저지를 수 있는데, 자기 결정에 의심을 하지 않는다는 것이다.&lt;/li&gt;
&lt;li&gt;나를 대신할 개발자가 언제나 있다고 생각하자. 이는 결국 내가 떠날수도 있다는 얘기가된다.&lt;br /&gt;나만 이해할 수 있는 코드, 나만이 유일하게 할수 있는 일이라 생각하는 것은 리팩토링과 문서화를 진행한다. 이러한 일들은 목록에서 없어져야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;유지보수를 꾸준히 하자. 버그를 고치고 사소한 기능 요구를 구현하고 계속 동작하게 만들라.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;오래된 웹 서비스를 현대적인 웹 브라우저의 기능을 이용할 수 있게 바꾼다.&lt;/li&gt;
&lt;li&gt;서비스의 품질을 점점 향상 시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;8시간밖에 없어! 부지런히 일해야지!! 라는 생각으로 일을 한다. 추가시간 없이 일하려하자. 열중하기의 힘은 대단하다.&lt;/li&gt;
&lt;li&gt;실수(버그)가 발생했을때 처리하는 법을 배우자.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;문제를 알게 되자마자 숨기지말고 드러내자&lt;/li&gt;
&lt;li&gt;빠른 해결을 위해 책임을 떠넘기지 말고 책임을 져라.&lt;/li&gt;
&lt;li&gt;해결책을 제시하라.&lt;/li&gt;
&lt;li&gt;도움을 구하라.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;계획을 아주 잘 세웠어도 삶은 위기와 재난의 연속이다. 당황&amp;amp;부정적인 대응으로 얻는 이익은 없다. 당황은 나의 능력을 떨어뜨린다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;당황하지 않는 방법은 문제가 터져도 제3자 관점에서 상황을 분석하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;오후에 시간을 내서 다음날 하고 싶은&amp;nbsp; 일을 모두 목록을 만들어둔다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;그 날 하지 못한 일은 다음날 옮기고 과정을 계속한다. 계획하고 착수하는 리듬이 생기기 시작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마케팅은 높으신 분들만 하는게 아니다&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 한 일을 윗사람이 알게 해라. 표현하지 않으면 모른다&lt;/li&gt;
&lt;li&gt;의사소통, 글쓰기를 위한 의사소통 능력을 기르자.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;매일 어떤 개발을 했는지 기록하고 설계의 타당성을 증명하고 어려운 기술적 또는 전문적 결정을 자세히 조사하라.&lt;br /&gt;글쓰기 연습이다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;직장 사람들이 나에대해 물을 수 있는 최악의 질문은 &quot;그 사람은 뭘 하나요?&quot; 이다. 영향력을 남기자.&lt;/li&gt;
&lt;li&gt;나를 다른 개발자로부터 남달라지게 만들 수 있는 것은 오픈 소스 소프트웨어 발표, 책과 기사쓰기, 컨퍼런스 발표이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자신의 강점을 유지보수하라&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연구, 투자, 실행, 마케팅의 루프를 돌텐데 너무 한 부분에 시간을 많이 쓰면 시대에 뒤쳐질 수 있음을 명시하자.&lt;/li&gt;
&lt;li&gt;나 자신을 측정하기 쉬운 방법은 신뢰할 만한 제3자를 이용하는 것이다. 멘토나 가까운 동료를 이용하자.&lt;/li&gt;
&lt;li&gt;내 경력 관리를 폭포수 모델로 관리하지 마라.(경력 사전설계X) 목표는 크게 세우더라도 도중에 꾸준히 수정하라. 목표를 바꿔나가는 것이다.&lt;/li&gt;
&lt;li&gt;오늘의 나는 어제의 나보다만 더 잘해지게 성장하면 된다. 조급해하지 말자.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Books</category>
      <author>zakelstorm</author>
      <guid isPermaLink="true">https://zakelstorm.tistory.com/174</guid>
      <comments>https://zakelstorm.tistory.com/174#entry174comment</comments>
      <pubDate>Mon, 26 Dec 2022 21:27:17 +0900</pubDate>
    </item>
    <item>
      <title>Vue3 google SEO 등록하기</title>
      <link>https://zakelstorm.tistory.com/154</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SEO의 기본 세팅은 다음과 같이 설정한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;페이지마다 캐노니컬 태그를 설정&lt;/b&gt;&lt;br /&gt;
&lt;pre id=&quot;code_1649856622309&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;link rel=&quot;canonical&quot; href=&quot;https://example.com/page.php&quot; /&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
...
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;태그와 메타 설정&lt;/b&gt;&lt;br /&gt;이미지 태그에 alt, head에 메타데이터, title은 페이지마다 달라야 하며 link에 alt도 붙여야한다.&lt;br /&gt;apple.com은 SEO를 위해 다음과 같이 meta tag를 설정해 놓았다&lt;br /&gt;&lt;b&gt;- apple home(www.apple.com)&lt;/b&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bR6iZy/btrzhqSUlDk/skpKce1mFow0Y4Wh3nGL81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bR6iZy/btrzhqSUlDk/skpKce1mFow0Y4Wh3nGL81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bR6iZy/btrzhqSUlDk/skpKce1mFow0Y4Wh3nGL81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbR6iZy%2FbtrzhqSUlDk%2FskpKce1mFow0Y4Wh3nGL81%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;910&quot; height=&quot;245&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br /&gt;&lt;b&gt;- apple watch(www.apple.com/watch)&lt;/b&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IhGrP/btrzhqegYhq/hmnBYG3JNuD8ulC3FVCUKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IhGrP/btrzhqegYhq/hmnBYG3JNuD8ulC3FVCUKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IhGrP/btrzhqegYhq/hmnBYG3JNuD8ulC3FVCUKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIhGrP%2FbtrzhqegYhq%2FhmnBYG3JNuD8ulC3FVCUKk%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;850&quot; height=&quot;278&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;페이지 속도 개선&lt;br /&gt;&lt;/b&gt;PageSpeedInsights에서 웹페이지 속도 개선에 대해 리포팅을 받아볼 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구조화&lt;br /&gt;&lt;/b&gt;HTML5의 tag들이 sementic 하게 맞춰 설계되어야한다. &lt;a href=&quot;https://search.google.com/test/rich-results&quot;&gt;https://search.google.com/test/rich-results&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사이트맵의 정기적인 관리&lt;br /&gt;&lt;/b&gt;&amp;nbsp;사이트맵을 주기적으로 갱신한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue 프로젝트를 google SEO에 등록하기 위해서는 크게 2가지 방법이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;vue-meta 이용하여 페이지별 meta tag 작성&lt;/li&gt;
&lt;li&gt;prerender-spa-plugin 이용하여 SSR 페이지 만들기&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 방법이 보다 편하므로 페이지별 meta tag를 이용하여 Vue 프로젝트의 SEO를 구현토록하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;vue-meta 라이브러리를 프로젝트에 설치한다.&lt;/b&gt;&lt;br /&gt;현시점 vue3에 적용가능한 vue-meta 라이브러리는 &lt;span style=&quot;color: #000000;&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;/span&gt;&lt;i&gt;3.0.0-alpha.10&lt;/i&gt; 까지 나와있다&lt;br /&gt;
&lt;pre id=&quot;code_1649834297422&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i --save vue-meta@alpha&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;vue가 vue-meta 플러그인을 사용할 수 있도록 해당 플러그인 라이브러리를 등록한다&lt;/b&gt;&lt;br /&gt;
&lt;pre id=&quot;code_1649834786566&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//main.ts
import { createApp } from 'vue';
import { createMetaManager } from 'vue-meta';
import App from './App.vue';

app
  .use(createMetaManager())
  .mount('#app');&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;default meta tag를 설정한다&lt;/b&gt;&lt;br /&gt;- index.html 설정&lt;br /&gt;og란: open graph의 약자로&amp;nbsp; 어떤 HTML 문서의 메타정보를 쉽게 표기하기 위해서 메타정보에 해당하는 제목, 설명, 문서타입, 대표 URL등을&amp;nbsp; 정의할 수 있게해주는 프로토콜.&lt;br /&gt;
&lt;pre id=&quot;code_1649838695096&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;utf-8&quot;&amp;gt;
    &amp;lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot;&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1.0&quot;&amp;gt;
    &amp;lt;meta name=&quot;robots&quot; content=&quot;ALL&quot;&amp;gt;
    &amp;lt;link rel=&quot;icon&quot; href=&quot;&amp;lt;%= BASE_URL %&amp;gt;favicon.ico&quot;&amp;gt;
    &amp;lt;title&amp;gt;식물의언어 : 식집사를 위한 식물 정보 플랫폼&amp;lt;/title&amp;gt;

    &amp;lt;!--og, meta--&amp;gt;
    &amp;lt;!--기본적으로 웹에 설정해줘야하는 og 메타태그 및 Naver 블로그, 카카오톡 미리보기 설정--&amp;gt;
    &amp;lt;meta property=&quot;og:type&quot; content=&quot;website&quot;&amp;gt;
    &amp;lt;!-- &amp;lt;meta property=&quot;og:url&quot; content=&quot;https://www.plantslang.com/&quot;&amp;gt; --&amp;gt;
    &amp;lt;meta id=&quot;meta_og_title&quot; property=&quot;og:title&quot; content=&quot;식물의언어&quot;&amp;gt;
    &amp;lt;meta id=&quot;meta_og_image&quot; property=&quot;og:image&quot; content=&quot;opengraph.webp&quot;&amp;gt;
    &amp;lt;!-- &amp;lt;meta property=&quot;og:description&quot; content=&quot;식집사를 위한 식물 정보 플랫폼, 식물의언어를 찾아오세요&quot;&amp;gt; --&amp;gt;
    &amp;lt;meta property=&quot;og:site_name&quot; content=&quot;식물의언어&quot;&amp;gt;
    &amp;lt;meta property=&quot;og:locale&quot; content=&quot;ko_KR&quot;&amp;gt;
    &amp;lt;meta property=&quot;og:width&quot; content=&quot;1200&quot;&amp;gt;
    &amp;lt;meta property=&quot;og:height&quot; content=&quot;630&quot;&amp;gt;


    &amp;lt;!-- &amp;lt;meta name=&quot;description&quot; content=&quot;식집사를 위한 식물 정보 플랫폼, 식물의언어를 찾아오세요&quot;&amp;gt; --&amp;gt;
    &amp;lt;!-- &amp;lt;meta name=&quot;keywords&quot; content=&quot;식물의언어, 식물의언어, 식물, plantslang&quot;&amp;gt; --&amp;gt;

    &amp;lt;!--phone 설정--&amp;gt;
    &amp;lt;meta name=&quot;theme-color&quot; content=&quot;#365650&quot;&amp;gt;
    &amp;lt;meta name=&quot;msapplication-navbutton-color&quot; content=&quot;#365650&quot;&amp;gt;
    &amp;lt;meta name=&quot;apple-mobile-web-app-status-bar-style&quot; content=&quot;black-translucent&quot;&amp;gt;
    &amp;lt;meta name=&quot;application-name&quot; content=&quot;식물의언어&quot;&amp;gt;
    &amp;lt;meta name=&quot;msapplication-tooltip&quot; content=&quot;식물의언어&quot;&amp;gt;
    

  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;noscript&amp;gt;
      &amp;lt;strong&amp;gt;We're sorry but &amp;lt;%= htmlWebpackPlugin.options.title %&amp;gt; doesn't work properly without JavaScript enabled. Please enable it to continue.&amp;lt;/strong&amp;gt;
    &amp;lt;/noscript&amp;gt;
    &amp;lt;div id=&quot;app&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;!-- built files will be auto injected --&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;img src=&quot;https://blog.kakaocdn.net/dn/KN1WI/btrzjpdLYkF/XYo7IjBuWOo4FwJQRKjQ4k/img.png&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/KN1WI/btrzjpdLYkF/XYo7IjBuWOo4FwJQRKjQ4k/img.png&quot; data-origin-width=&quot;443&quot; data-origin-height=&quot;223&quot; /&gt;&lt;br /&gt;만약 description의 주석처리를 해제한다면 뒤에 vue-meta로 설정한 description과 중복이 발생하게 된다.&lt;br /&gt;중복이 발생하면 두개의 description을 이어 붙인다. 즉, 모든 페이지 별로 description이 중복되는 현상이 발생된다.&lt;br /&gt;&lt;a href=&quot;https://www.searchenginejournal.com/google-on-how-it-handles-extra-meta-descriptions-and-title-tags/368600/#close&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.searchenginejournal.com/google-on-how-it-handles-extra-meta-descriptions-and-title-tags/368600/#close&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;페이지(route)별 meta tag를 설정한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>VueJS</category>
      <author>zakelstorm</author>
      <guid isPermaLink="true">https://zakelstorm.tistory.com/154</guid>
      <comments>https://zakelstorm.tistory.com/154#entry154comment</comments>
      <pubDate>Fri, 15 Apr 2022 17:27:01 +0900</pubDate>
    </item>
    <item>
      <title>React 18 변경 사항</title>
      <link>https://zakelstorm.tistory.com/151</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;17에 비해 코드상으로 수정해야할 부분이 많지는 않다는 것이 다행이다. React18은 React 17에서 어떤점이 바꼈는지 알아보자&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;nbsp;html과 js를 연결해주는 main.js안에서 사용하는 함수가 바꼈다.&lt;br /&gt;
&lt;pre id=&quot;code_1649653082168&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ReactDOM from 'react-dom'
import App from './App'

//17
//ReactDOM.render(&amp;lt;App /&amp;gt;, document.getElementById('root'));

//18
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(&amp;lt;App /&amp;gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Concurrent Rendering&lt;br /&gt;동시에 일어난 다수의 상태 업데이트 처리하는 기법으로 urgent한 update는 오래 걸리거나 급박하지않은 update보다 우선되어진다.&lt;br /&gt;만약 엄청나게 많은 component가 동시에 변해야하는 상황이라면&amp;nbsp;&lt;br /&gt;&lt;b&gt;React 17&lt;/b&gt;에서는 동시에 state 변화가 일어난다고 해도 먼저 일어난 것 순서대로 순차적으로 처리되는 반면&lt;br /&gt;&lt;b&gt;React 18&lt;/b&gt;에서는 동시에 state 변화가 일어나야할 시 우선도를 보고 urgent한 update가 우선되어지고 urgent하지 않거나 오래걸리는 update는 background에서 수행된다.&lt;br /&gt;&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ck8AGT/btryU942cyF/SBfKgUpAEuebnFJA6iZ50k/img.png&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/ck8AGT/btryU942cyF/SBfKgUpAEuebnFJA6iZ50k/img.png&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;251&quot; /&gt;우선도 구분은 다음 API를 사용하여 우선도가 높은 상태변화와 낮은 상태변화를 구분한다.&amp;nbsp;&lt;br /&gt;- useTransition( )&lt;br /&gt;- startTransition( )&lt;br /&gt;- useDeferredValue( )&lt;br /&gt;&lt;br /&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100.367%; height: 146px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 24px;&quot;&gt;
&lt;td style=&quot;width: 32.8813%; height: 24px; text-align: center;&quot;&gt;&lt;b&gt;useTransition&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.015%; height: 24px; text-align: center;&quot;&gt;&lt;b&gt;startTransition&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 37.1037%; text-align: center; height: 24px;&quot;&gt;&lt;b&gt;useDefferedValue&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 50px;&quot;&gt;
&lt;td style=&quot;width: 62.8963%; height: 50px;&quot; colspan=&quot;2&quot;&gt;React에게 lower priority의&amp;nbsp; state update를 알려준다&lt;/td&gt;
&lt;td style=&quot;width: 37.1037%; height: 100px;&quot; rowspan=&quot;3&quot;&gt;- React에게 갱신된 값이 준비될때까지 이전값을 UI상 display 해야한다는 것을 알려준다&lt;br /&gt;- 2개의 value 중 하나는 업데이트가 빠르고 나머지 하나는 업데이트가 느린경우 느린 업데이트의 value에 대해서는 update전 값을 보여준다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 32.8813%; height: 25px;&quot;&gt;함수형 컴포넌트에서 사용한다&lt;/td&gt;
&lt;td style=&quot;width: 30.015%; height: 25px;&quot;&gt;hook이 사용될 수 없는 환경에서 사용한다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 32.8813%; height: 25px;&quot;&gt;[isPending, startTransition] 반환&lt;br /&gt;//isPending은 현재 background에서 state가 변화하고 있음을 알려준다&lt;/td&gt;
&lt;td style=&quot;width: 30.015%; height: 25px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 62.8963%;&quot; colspan=&quot;2&quot;&gt;startTransition(()=&amp;gt;setUser(user));&lt;/td&gt;
&lt;td style=&quot;width: 37.1037%; height: 22px;&quot;&gt;const deferredVal=useDeferredValue(value);&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre id=&quot;code_1649663018735&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useTransition } from 'react';
    
const [isPending, startTransition] = useTransition();
// Urgent
setInputValue(input);

// Mark any state updates inside as transitions
startTransition(() =&amp;gt; {
  // Transition
  setSearchQuery(input);
})
{isPending &amp;amp;&amp;amp; &amp;lt;Spinner /&amp;gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1649663254828&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useDeferredValue } from &quot;react&quot;;
    
function App() {
  const [input, setInput] = useState(&quot;&quot;);
  const deferredValue = useDeferredValue(text, { timeoutMs: 3000 }); 

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input value={input} onChange={handleChange} /&amp;gt;
      &amp;lt;MyList text={deferredValue} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
 }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;State Batching 개선&lt;br /&gt;State Batching: 다수의 state 업데이트가 함께 실행된다. 그러므로 컴포넌트가 한번만 업데이트된다.&lt;br /&gt;
&lt;pre id=&quot;code_1649663613097&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function increaseCouterHandler() {
  //Two state update calls batched together
  // +3 씩 이뤄진다
  setCounter((curCounter)=&amp;gt;curCounter+1);
  setCounter((curCounter)=&amp;gt;curCounter+2);
}

function increaseCouterAsyncHandler() {
  setTimeout(()=&amp;gt;{
    //Two state update calls not batched when using react 17
    //+3 씩 이뤄지는데 오직 asynchronous 환경에서만 이뤄졌다
    setCounter((curCounter)=&amp;gt;curCounter+1);
    setCounter((curCounter)=&amp;gt;curCounter+2);
  })
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Suspense 개선&lt;br /&gt;Suspense: Code(or data) fetching과 관련된 UI updates를 도와주는 component. 주로 lazy loading할때 사용한다.&lt;br /&gt;React 18에서는 Server side rendering 환경에서도 Suspense를 사용할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/ol&gt;</description>
      <category>React</category>
      <author>zakelstorm</author>
      <guid isPermaLink="true">https://zakelstorm.tistory.com/151</guid>
      <comments>https://zakelstorm.tistory.com/151#entry151comment</comments>
      <pubDate>Mon, 11 Apr 2022 17:05:55 +0900</pubDate>
    </item>
    <item>
      <title>Docker #2- Docker file</title>
      <link>https://zakelstorm.tistory.com/146</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 React Application 을 실행시키기 위해서는 다음과 같은 프로세스를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nodejs설치 -&amp;gt; npm install -&amp;gt; npm run build -&amp;gt; server 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 일련의 과정을 Docker 는 이미지로 생성해 순서대로 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이런 이미지 설정파일을 Dockerfile에서 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dockerfile의 생김새는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;# parent image
FROM node:17-alpine as build-stage

# Run Command (ex. COPY, npm install, ...)이 작업되는 directory 선언
WORKDIR /app

# all source code copy , COPY &amp;lt;src&amp;gt; &amp;lt;dest&amp;gt;
COPY . .

# dependencies
RUN npm install

# container가 어떤 포트를 사용할지 선언한다. app.js에서 4000포트 사용중이므로 4000포트를 expose 한다.
EXPOSE 4000

# 다음 명령어가 안되는 이유: image는 container의 blueprint이지 application을 실행하는 곳이 아니다. container는 image의 running instance이다.
#RUN node app.js

# container가 RUN을 시작한 후 서버가 작동하게 하기 위해서 다음과 같이 CMD 명령어를 쓴다.
CMD [&quot;node&quot;, &quot;app.js&quot;]

# 빌드하는 명령어 myapp: image 이름, . : dockerfile build하는 장소로부터의 상대 경로
#docker build -t myapp .&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parent image 설정 -&amp;gt; work directory 설정 -&amp;gt; work directory에 source code 복사 -&amp;gt; package dependency 설치 -&amp;gt; 포트 설정 -&amp;gt; 서버 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 순서대로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주의해야할 것이 2가지가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WORKDIR
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업이 이뤄지는 경로로서 WORKDIR이 선언된 이후 모든 작업은 선언된 directory에서만 이뤄진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;EXPOSE
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호스트와 연결할 포트 번호 설정.&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;db: 
  image: mysql:latest 
  expose: 
    - &quot;3306&quot; 

node: 
  image: node:latest&lt;/code&gt;&lt;/pre&gt;
위의 경우 node는 mysql에 3306포트로 접근할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RUN vs CMD
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 명령어 모두 Command를 실행시켜주는 명령어이다.&lt;br /&gt;하지만 RUN은 image를 구성하는데 사용하는 명령어로 image는 container의 blueprint이지 application을 실행하는 역할이 아니다. 왜냐하면 container는 image의 running instance이기 때문이다.&lt;br /&gt;CMD는 모든 image구성이 끝난 후 실행할 Command를 실행시켜주기 위해 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>zakelstorm</author>
      <guid isPermaLink="true">https://zakelstorm.tistory.com/146</guid>
      <comments>https://zakelstorm.tistory.com/146#entry146comment</comments>
      <pubDate>Sun, 3 Apr 2022 04:41:31 +0900</pubDate>
    </item>
    <item>
      <title>Docker #1- Image &amp;amp; container</title>
      <link>https://zakelstorm.tistory.com/145</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Docker Image&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;container의 설계. Application이 작동하기 위해서 필요한 요소들을 의미한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Image 구성&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;196&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gs4nL/btrxTOtNAoV/FT3ETdQuUYssVirEdawLH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gs4nL/btrxTOtNAoV/FT3ETdQuUYssVirEdawLH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gs4nL/btrxTOtNAoV/FT3ETdQuUYssVirEdawLH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGs4nL%2FbtrxTOtNAoV%2FFT3ETdQuUYssVirEdawLH1%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;196&quot; height=&quot;207&quot; data-origin-width=&quot;196&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Parent Image : OS 또는 runtime environment 를 의미 (ex. node, python, jdk...)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a title=&quot;docker hub&quot; href=&quot;https://hub.docker.com/search?q=image&amp;amp;type=image&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;docker hub&lt;/a&gt;에서 parent image를 찾을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Docker Container&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 이미지들을 소유한 실행가능한 인스턴스. Application을 실행시켜준다. &lt;br /&gt;즉, Image들이 실행되면 Container가 생성되면서 application이 동작한다. &lt;br /&gt;하나의 독립적인 process라 보면 편하다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작동 원리&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLimiP/btrxVj7Tb4y/Uvs5wniNuOVZpHI3rkeIlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLimiP/btrxVj7Tb4y/Uvs5wniNuOVZpHI3rkeIlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLimiP/btrxVj7Tb4y/Uvs5wniNuOVZpHI3rkeIlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLimiP%2FbtrxVj7Tb4y%2FUvs5wniNuOVZpHI3rkeIlk%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;603&quot; height=&quot;275&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;275&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 구성의 이미지를 가진 Container는 모두 같은 방식으로 작동한다.&lt;/li&gt;
&lt;li&gt;Server에 어떤 환경이 구성되어 있던지 Container는 Image의 환경을 따라간다. (Isolated)&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zakelstorm</author>
      <guid isPermaLink="true">https://zakelstorm.tistory.com/145</guid>
      <comments>https://zakelstorm.tistory.com/145#entry145comment</comments>
      <pubDate>Wed, 30 Mar 2022 02:52:10 +0900</pubDate>
    </item>
    <item>
      <title>React Storybook - 1. Storybook 시작하기</title>
      <link>https://zakelstorm.tistory.com/143</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Storybook 이란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UI component를 위한 개발 환경과 Playground&lt;/li&gt;
&lt;li&gt;컴포넌트를 독립적으로 생성해준다.&lt;/li&gt;
&lt;li&gt;격리된 개발 환경에서 이러한 컴포넌트를 대화식으로 보여준다. =&amp;gt; react 어플리케이션의 외부에서 돌아간다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발된 컴포넌트들이 어떻게 다른 Props를 가지는지 보여준다.&lt;/li&gt;
&lt;li&gt;동적으로 Props나 Accessibility score를 바꿔준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Storybook 시작하기&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;npx create-react-app react-story-v6&lt;/li&gt;
&lt;li&gt;npx sb init =&amp;gt; Add Storybook&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btJeKo/btrtwrWBIAw/mOQcQgzmbeITo2xELHX4q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btJeKo/btrtwrWBIAw/mOQcQgzmbeITo2xELHX4q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btJeKo/btrtwrWBIAw/mOQcQgzmbeITo2xELHX4q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtJeKo%2FbtrtwrWBIAw%2FmOQcQgzmbeITo2xELHX4q1%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;580&quot; height=&quot;491&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
- npm run storybook / npm build-storybook : development mode에서 storybook 실행/ storybook 빌드&lt;br /&gt;- main.js : storybook의 configuration file&lt;br /&gt;- preview.js : 내가 작성한 story의 configuration file&lt;br /&gt;- stories/~.stories.mdx: storybook의 landing page&lt;br /&gt;- stories/~.js : component&lt;br /&gt;- stories/~.stories : component와 연관이 있는 story를 모아놓은 파일. story를 export 한다.&lt;/li&gt;
&lt;li&gt;npm run storybook : component의 landing page들을 볼 수 있다.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egFXnR/btrtwpYRmh7/cJoOglH6T0QGjRcgCsBOP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egFXnR/btrtwpYRmh7/cJoOglH6T0QGjRcgCsBOP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egFXnR/btrtwpYRmh7/cJoOglH6T0QGjRcgCsBOP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegFXnR%2FbtrtwpYRmh7%2FcJoOglH6T0QGjRcgCsBOP0%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;616&quot; height=&quot;627&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;970&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>zakelstorm</author>
      <guid isPermaLink="true">https://zakelstorm.tistory.com/143</guid>
      <comments>https://zakelstorm.tistory.com/143#entry143comment</comments>
      <pubDate>Thu, 17 Feb 2022 01:19:08 +0900</pubDate>
    </item>
    <item>
      <title>AWS 에 Jenkins와 Nginx 이용하여 vue project 올리기</title>
      <link>https://zakelstorm.tistory.com/133</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Overview&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에 Jenkins와 Nginx를 이용하여 vue project를 올리는 방법에 대하여 알아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 git repository에 project commit이 push 될때 자동으로 빌드가 이루어지도록한다 (CD)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;EC2에 linux (or window) 인스턴스 생성&lt;/li&gt;
&lt;li&gt;EC2 인스턴스에 Security Group 설정&lt;/li&gt;
&lt;li&gt;git webhook 설정&lt;/li&gt;
&lt;li&gt;git credential token 발급&lt;/li&gt;
&lt;li&gt;EC2에 에 Jenkins 및 daemonize 문제 해결&lt;/li&gt;
&lt;li&gt;Jenkins 설정 변경 (git credential, item 생성)&lt;/li&gt;
&lt;li&gt;EC2에 nginx 설치&lt;/li&gt;
&lt;li&gt;nginx 설정 변경(conf.d)&lt;/li&gt;
&lt;li&gt;빌드!&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. EC2에 linux 인스턴스 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 내용은 쉽고 넷상에 많이 나오므로 링크로 생략&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://victorydntmd.tistory.com/61&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://victorydntmd.tistory.com/61&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1641059191777&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;[AWS] EC2 (1) - EC2 인스턴스 생성하기&quot; data-og-description=&quot;2020.02.06 수정 1. 서비스 소개 AWS EC2는 간단하게 가상서버라고 생각할 수 있으며, 몇 가지 주요 특징은 다음과 같습니다. 클릭 몇 번 만으로&amp;nbsp;가상 컴퓨팅 환경을 제공받을 수 있음 원하는 만큼 가&quot; data-og-host=&quot;victorydntmd.tistory.com&quot; data-og-source-url=&quot;https://victorydntmd.tistory.com/61&quot; data-og-url=&quot;https://victorydntmd.tistory.com/61&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fNamg/hyMWoE2cOv/VSMZDLMC3QhIBRE2zszT01/img.png?width=800&amp;amp;height=409&amp;amp;face=0_0_800_409,https://scrap.kakaocdn.net/dn/cDxj2I/hyMU1SeEOa/dlfIVKJQ87zaSAphwZC9S1/img.png?width=800&amp;amp;height=409&amp;amp;face=0_0_800_409,https://scrap.kakaocdn.net/dn/vi6AL/hyMU2ji20t/HPf7wnKowbtfScxK6Gm7mk/img.png?width=900&amp;amp;height=461&amp;amp;face=0_0_900_461&quot;&gt;&lt;a href=&quot;https://victorydntmd.tistory.com/61&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://victorydntmd.tistory.com/61&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fNamg/hyMWoE2cOv/VSMZDLMC3QhIBRE2zszT01/img.png?width=800&amp;amp;height=409&amp;amp;face=0_0_800_409,https://scrap.kakaocdn.net/dn/cDxj2I/hyMU1SeEOa/dlfIVKJQ87zaSAphwZC9S1/img.png?width=800&amp;amp;height=409&amp;amp;face=0_0_800_409,https://scrap.kakaocdn.net/dn/vi6AL/hyMU2ji20t/HPf7wnKowbtfScxK6Gm7mk/img.png?width=900&amp;amp;height=461&amp;amp;face=0_0_900_461');&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;[AWS] EC2 (1) - EC2 인스턴스 생성하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2020.02.06 수정 1. 서비스 소개 AWS EC2는 간단하게 가상서버라고 생각할 수 있으며, 몇 가지 주요 특징은 다음과 같습니다. 클릭 몇 번 만으로&amp;nbsp;가상 컴퓨팅 환경을 제공받을 수 있음 원하는 만큼 가&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;victorydntmd.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. EC2 인스턴스에 Security Group 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aws console EC2 &amp;gt; 사용할 인스턴스 Detail로 페이지 &amp;gt; Security tab &amp;gt; secity groups 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1683&quot; data-origin-height=&quot;877&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciVHdY/btrpxxsj9eR/lNQiCqBKCQGklVFX7ok1Y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciVHdY/btrpxxsj9eR/lNQiCqBKCQGklVFX7ok1Y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciVHdY/btrpxxsj9eR/lNQiCqBKCQGklVFX7ok1Y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciVHdY%2Fbtrpxxsj9eR%2FlNQiCqBKCQGklVFX7ok1Y1%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;860&quot; height=&quot;448&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1683&quot; data-origin-height=&quot;877&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Edit inbound rules &amp;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;1736&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJgeV2/btrplgTgyVC/toBBzq04BSc6bK8mq1aUF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJgeV2/btrplgTgyVC/toBBzq04BSc6bK8mq1aUF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJgeV2/btrplgTgyVC/toBBzq04BSc6bK8mq1aUF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJgeV2%2FbtrplgTgyVC%2FtoBBzq04BSc6bK8mq1aUF1%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;1736&quot; height=&quot;576&quot; data-origin-width=&quot;1736&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8080은 Jenkins가 작동할 포트이다. Jenkins 설정 변경을 위해 접속할 일이 있으니 열어두도록하자. 단, 진짜 서비스의 경우 개방 범위를 0.0.0.0/0으로 하면 모든 사람이 들어올 수 있으니 이 때는 제한하도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트 3000이나 80 둘중 하나만 열어도 된다. 웹서버 nginx가 web application을 띄울 포트를 열도록한다. 모든 사람이 접속할 수 있어야 하므로 개방 범위를 0.0.0.0/0으로 설정하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. github webhook 설정 및 developer authentication 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GitHub Webhook&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Webhook을 사용하면 GitHub Apps 또는 OAuth Apps와 같은 integration을 빌드하거나 설정할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이러한 이벤트(예: commit code) 중 하나가 트리거되면 HTTP POST Payload를 webhook에 설정된 URL로 보낸다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;webhook을 이용해 CI 빌드를 트리거하거나 production 서버에 배포할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vue project 의 repository &amp;gt; setting 탭 &amp;gt; webhook 탭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;697&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zS4FF/btrpu11NVqX/8KfpxqiEo3FnAqu0GwKSfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zS4FF/btrpu11NVqX/8KfpxqiEo3FnAqu0GwKSfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zS4FF/btrpu11NVqX/8KfpxqiEo3FnAqu0GwKSfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzS4FF%2Fbtrpu11NVqX%2F8KfpxqiEo3FnAqu0GwKSfK%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;1008&quot; height=&quot;697&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Payload URL : &lt;b&gt;&quot;EC2에서 Jenkins 서비스 URL&quot;/github-webhook/&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;webhook POST request를 받을 서버의 URL 정의. jenkins의 경우 뒤에 github-webhook/ path를 명시해줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Content type: &lt;b&gt;application/json&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;webhoook을 어떤 형태로 받을지에 대한 정의. 여기서는 json으로 받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Just the push event
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;push event가 발생했을 시에만 webhook이 trigger되도록 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. git personal access token 발급&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 프로필 &amp;gt; settings &amp;gt; Developer settings 탭 &amp;gt; Personal access tokens 탭 &amp;gt; Generate new token &amp;gt; repo 체크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발급된 personal access token를 꼭 기억하도록 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D6PZh/btrpu63sSsE/ZMeCl2LVnEZXj9C0xojLt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D6PZh/btrpu63sSsE/ZMeCl2LVnEZXj9C0xojLt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D6PZh/btrpu63sSsE/ZMeCl2LVnEZXj9C0xojLt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD6PZh%2Fbtrpu63sSsE%2FZMeCl2LVnEZXj9C0xojLt1%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;659&quot; height=&quot;213&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;213&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;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git에서의 setting이 모두 끝났다. 이제 Jenkins 설치 및 설정 과정에대해 알아보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. EC2에 Jenkins 및 Git 설치&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Jenkins 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins 설치 방법은 넷상에 널려 있으니 이 또한 링크로 대체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://bokyung.dev/2021/03/17/jenkins-install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://bokyung.dev/2021/03/17/jenkins-install/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1641061757023&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;Amazon Linux2에 Jenkins 설치하기&quot; data-og-description=&quot;로컬 도커환경에서 Jenkins를 테스트 하던 중에 에러가 나는 경우가 있어서 검증을 위해서 Amazon Linux2에 Jenkins를 설치 해 보았습니다. 설치한 Jenkins 환경 Amazon Linux2 AMI ID : ami-0f27d081df46f326c AMI 이름 :&quot; data-og-host=&quot;bokyung.dev&quot; data-og-source-url=&quot;https://bokyung.dev/2021/03/17/jenkins-install/&quot; data-og-url=&quot;https://bokyung.dev/2021/03/17/jenkins-install/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://bokyung.dev/2021/03/17/jenkins-install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bokyung.dev/2021/03/17/jenkins-install/&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;Amazon Linux2에 Jenkins 설치하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;로컬 도커환경에서 Jenkins를 테스트 하던 중에 에러가 나는 경우가 있어서 검증을 위해서 Amazon Linux2에 Jenkins를 설치 해 보았습니다. 설치한 Jenkins 환경 Amazon Linux2 AMI ID : ami-0f27d081df46f326c AMI 이름 :&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bokyung.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 설치 중간에 다음의 에러가 발생한다면&lt;/p&gt;
&lt;pre id=&quot;code_1641061889317&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo yum install jenkins
...
Error: Package: jenkins-2.319.1-1.1.noarch (jenkins)
           Requires: daemonize
 You could try using --skip-broken to work around the problem
 You could try running: rpm -Va --nofiles --nodigest&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음의 명령어를 실행 시킨후 jenkins를 설치한다&lt;/p&gt;
&lt;pre id=&quot;code_1641064892480&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo amazon-linux-extras install epel -y
$ sudo yum install jenkins&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Git 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gamoo12.tistory.com/205&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gamoo12.tistory.com/205&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1641066973116&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;[AWS] EC2 인스턴스에서 git 설치 방법&quot; data-og-description=&quot;AWS EC2에서 git 설치 AWS의 EC2 인스턴스 중 Amazon Linux 2에 git을 설치하는 방법을 알아보았습니다. Yum을 이용하면 쉽게 git 설치가 가능합니다. Yum은 Yellow dog Updater, Modified의 약자로 RPM 기반의..&quot; data-og-host=&quot;gamoo12.tistory.com&quot; data-og-source-url=&quot;https://gamoo12.tistory.com/205&quot; data-og-url=&quot;https://gamoo12.tistory.com/205&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cco6wq/hyMVbN6kSw/4cnDTX5m8KlquMOgQawyS0/img.png?width=800&amp;amp;height=364&amp;amp;face=0_0_800_364,https://scrap.kakaocdn.net/dn/cz92wo/hyMU9bGRvM/pfkIMg3lZJqEEUO6OB9Cn1/img.png?width=800&amp;amp;height=364&amp;amp;face=0_0_800_364,https://scrap.kakaocdn.net/dn/b1BNiM/hyMWwb1iN1/xRPUGMk0PjlbjOC8dKr8ZK/img.png?width=1000&amp;amp;height=455&amp;amp;face=0_0_1000_455&quot;&gt;&lt;a href=&quot;https://gamoo12.tistory.com/205&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gamoo12.tistory.com/205&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cco6wq/hyMVbN6kSw/4cnDTX5m8KlquMOgQawyS0/img.png?width=800&amp;amp;height=364&amp;amp;face=0_0_800_364,https://scrap.kakaocdn.net/dn/cz92wo/hyMU9bGRvM/pfkIMg3lZJqEEUO6OB9Cn1/img.png?width=800&amp;amp;height=364&amp;amp;face=0_0_800_364,https://scrap.kakaocdn.net/dn/b1BNiM/hyMWwb1iN1/xRPUGMk0PjlbjOC8dKr8ZK/img.png?width=1000&amp;amp;height=455&amp;amp;face=0_0_1000_455');&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;[AWS] EC2 인스턴스에서 git 설치 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AWS EC2에서 git 설치 AWS의 EC2 인스턴스 중 Amazon Linux 2에 git을 설치하는 방법을 알아보았습니다. Yum을 이용하면 쉽게 git 설치가 가능합니다. Yum은 Yellow dog Updater, Modified의 약자로 RPM 기반의..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gamoo12.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6.&amp;nbsp;Jenkins git 연결 및 item 생성 (git credential, item 생성)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;nodejs 환경 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작하기 전 nodejs 환경을 만들어주기 위해 플러그인을 설치해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jenkins 관리 &amp;gt; 플러그인 관리 &amp;gt; 설치 가능 탭 &amp;gt; nodejs 검색&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVxXdT/btrplhklAkV/iAD2UZ0pJKKyGsa6L4DXr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVxXdT/btrplhklAkV/iAD2UZ0pJKKyGsa6L4DXr0/img.png&quot; data-alt=&quot;설치 후 뒤늦은 캡처를 해버렸다.. 하핳..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVxXdT/btrplhklAkV/iAD2UZ0pJKKyGsa6L4DXr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVxXdT%2FbtrplhklAkV%2FiAD2UZ0pJKKyGsa6L4DXr0%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;1924&quot; height=&quot;582&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;582&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;설치가 완료되었다면 (jenkins service restart까지) jenkins 관리 &amp;gt; global tool configuration &amp;gt; NodeJs 환경 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caYVci/btrpjxOuGrA/n6LNlgMsU5hm5ajqbzquD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caYVci/btrpjxOuGrA/n6LNlgMsU5hm5ajqbzquD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caYVci/btrpjxOuGrA/n6LNlgMsU5hm5ajqbzquD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaYVci%2FbtrpjxOuGrA%2Fn6LNlgMsU5hm5ajqbzquD1%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;449&quot; height=&quot;564&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;828&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;git personal access token credential 저장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 설정했던 github webhook을 설정하기 위해 jenkins에 git personal access token credential을 저장하도록하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jenkins 관리 &amp;gt; Manage Credentials &amp;gt; (global) &amp;gt; Add Credentials &amp;gt; 내용 입력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;647&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzde91/btrpthdkzaN/vLsdPS3Ffjde4F2hAPdNaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzde91/btrpthdkzaN/vLsdPS3Ffjde4F2hAPdNaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzde91/btrpthdkzaN/vLsdPS3Ffjde4F2hAPdNaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzde91%2FbtrpthdkzaN%2FvLsdPS3Ffjde4F2hAPdNaK%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;451&quot; height=&quot;416&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;647&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kind:&amp;nbsp; Username with password&lt;/li&gt;
&lt;li&gt;Scope : Global&lt;/li&gt;
&lt;li&gt;Username : git username&lt;/li&gt;
&lt;li&gt;password : 이전에 발급받은 git personal access token&lt;/li&gt;
&lt;li&gt;ID : 임의로 지은 식별자&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Item 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 Item &amp;gt; 아이템 이름 지정 및 Freestyle project 선택 &amp;gt; 소스코드 관리에서 Git radio button 체크 &amp;gt; 다음 내용 입력&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cl5o6D/btrpvSDVBaW/bECgfZWo6MkxqPTOKdA6r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl5o6D/btrpvSDVBaW/bECgfZWo6MkxqPTOKdA6r1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl5o6D/btrpvSDVBaW/bECgfZWo6MkxqPTOKdA6r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl5o6D%2FbtrpvSDVBaW%2FbECgfZWo6MkxqPTOKdA6r1%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;633&quot; height=&quot;563&quot; data-origin-width=&quot;959&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Repository URL : vue project git HTTPS 주소&lt;/li&gt;
&lt;li&gt;Credential : 이전에 생성한 git personal access token credentail 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub hook trigger for GITScm polling 체크 (webhook trigger) &amp;gt; Provide Node &amp;amp; npm bin/ folder to PATH 체크 &amp;gt; 이전에 만든 nodejs 환경 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;703&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIWiDE/btrpu6WItTI/bfWukJihfgGQfkdQRzBfQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIWiDE/btrpu6WItTI/bfWukJihfgGQfkdQRzBfQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIWiDE/btrpu6WItTI/bfWukJihfgGQfkdQRzBfQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIWiDE%2Fbtrpu6WItTI%2FbfWukJihfgGQfkdQRzBfQ1%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;411&quot; height=&quot;540&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;703&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Build Execute shell 선택 및 해당 내용 입력&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/de4mWA/btrputR8Trk/JNwLXLNQBoBCKa1P0dclXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/de4mWA/btrputR8Trk/JNwLXLNQBoBCKa1P0dclXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/de4mWA/btrputR8Trk/JNwLXLNQBoBCKa1P0dclXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fde4mWA%2FbtrputR8Trk%2FJNwLXLNQBoBCKa1P0dclXk%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;433&quot; height=&quot;162&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;asdfeeeee 는 내가 만든 Jenkins Item 이름이다. 각자의 이름에 맞게 변경토록하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm run build는 만약 build 명령어가 다르다면 해당 명령어로 입력하면 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. nginx 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ant-now/AWS-%EC%84%9C%EB%B2%84%EC%97%90-Nginx-%EC%84%A4%EC%B9%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@ant-now/AWS-%EC%84%9C%EB%B2%84%EC%97%90-Nginx-%EC%84%A4%EC%B9%98&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1641067151238&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;AWS 서버에 AMI Nginx 설치&quot; data-og-description=&quot;sudo yum install nginx 로 안됨. 왜냐면 아마존 리눅스에서 사용하는 엔진엑스가 따로 있기 때문sudo amazon-linux-extras install nginx1 로 설치하자.Amazon Linux 2 FAQs(https://aws.amaz&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@ant-now/AWS-%EC%84%9C%EB%B2%84%EC%97%90-Nginx-%EC%84%A4%EC%B9%98&quot; data-og-url=&quot;https://velog.io/@ant-now/AWS-서버에-Nginx-설치&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/se6sD/hyMUZGUiCV/Pg81HMTCky5EKehjqlkdY1/img.png?width=768&amp;amp;height=275&amp;amp;face=0_0_768_275,https://scrap.kakaocdn.net/dn/NEj31/hyMUXI4nEn/sYASEgGjeizk7XtTG7hGO1/img.png?width=2878&amp;amp;height=1032&amp;amp;face=0_0_2878_1032&quot;&gt;&lt;a href=&quot;https://velog.io/@ant-now/AWS-%EC%84%9C%EB%B2%84%EC%97%90-Nginx-%EC%84%A4%EC%B9%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@ant-now/AWS-%EC%84%9C%EB%B2%84%EC%97%90-Nginx-%EC%84%A4%EC%B9%98&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/se6sD/hyMUZGUiCV/Pg81HMTCky5EKehjqlkdY1/img.png?width=768&amp;amp;height=275&amp;amp;face=0_0_768_275,https://scrap.kakaocdn.net/dn/NEj31/hyMUXI4nEn/sYASEgGjeizk7XtTG7hGO1/img.png?width=2878&amp;amp;height=1032&amp;amp;face=0_0_2878_1032');&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;AWS 서버에 AMI Nginx 설치&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;sudo yum install nginx 로 안됨. 왜냐면 아마존 리눅스에서 사용하는 엔진엑스가 따로 있기 때문sudo amazon-linux-extras install nginx1 로 설치하자.Amazon Linux 2 FAQs(https://aws.amaz&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. nginx build file 경로 지정 및 서비스 포트 설정&lt;/h2&gt;
&lt;pre id=&quot;code_1641067290081&quot; class=&quot;java&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cd /etc/nginx/conf.d
$ vim default.conf //아무 이름이나 만들어도 된다.

// default.conf에 다음내용을 입력한다.

server {
        listen 80       default_server;
        listen  [::]:80 default_server;

        root    /var/lib/jenkins/workspace/asdfeeeee/dist;
        index   index.html      index.htm;
        server_name     _;

        location/{
                try_files	$uri    $uri/   /index.html;
        }
}

//설정 완료후
$ sudo service nginx restart&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 페이지를 80번 포트에서 서비스 하기위해 80번 포트를 열었다.&lt;/li&gt;
&lt;li&gt;root의 경로를 build 시 build 파일이 생성되는 &quot;jenkins item 경로&quot;/dist 로 지정했다.&lt;/li&gt;
&lt;li&gt;입력 한 conf값 저장후 nginx 웹서버를 재시작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;※만약 wq! 로 저장이 안된다면 현재 ec2-user linux 계정이 저장할 권한이 없는 것이다. 미리 sudo su 로 권한을 root로 변경하던지 저장할때 wq! 대신 w !sudo tee % &amp;gt; /dev/null 로 저장한다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 public ip로 접속하면 만들어진 화면이 나옴을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/neIOt/btrptgyKDuY/iSkKoSmpHNJLOXkws9akgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/neIOt/btrptgyKDuY/iSkKoSmpHNJLOXkws9akgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/neIOt/btrptgyKDuY/iSkKoSmpHNJLOXkws9akgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FneIOt%2FbtrptgyKDuY%2FiSkKoSmpHNJLOXkws9akgK%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;1920&quot; height=&quot;958&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>VueJS</category>
      <author>zakelstorm</author>
      <guid isPermaLink="true">https://zakelstorm.tistory.com/133</guid>
      <comments>https://zakelstorm.tistory.com/133#entry133comment</comments>
      <pubDate>Sun, 2 Jan 2022 05:20:12 +0900</pubDate>
    </item>
    <item>
      <title>크로스 브라우징</title>
      <link>https://zakelstorm.tistory.com/115</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모던 웹 브라우저(크롬, 사파리, 엣지 등)에서 크로스 브라우징이 가능해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크로스 브라우징이란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어떤 환경에서 접근하여도 동등하게 작동되는것을 의미.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 브라우저나 OS마다 보여지는 모습이 다른 경우가 많은데, 여러 환경에 호환성을 유지할 수 있도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크로스 브라우징 작업이 필요한 원인은 무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 브라우저마다 랜더링 엔진이 다르기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 작동되지 않는 HTML5, Javascript 코드가 존재&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 해석하지 못하는 CSS 코드 존재&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 브라우저 버그들이 존재&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 브라우저마다 자체적인 CSS 스타일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크로스 브라우징을 하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 수동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 크로스 브라우징을 하는 방법으로 가장 좋은 방법으로는 각 브라우저를 직접 들어가는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 체크목록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 크롬&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 웨일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - IE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - FireFox&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - Edge&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - Opera&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - Safari&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-자동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 셀레니엄 같은 자동화 도구를 사용하여 전문 QA가 작성한 스크립트로 다양한 브라우저에서 테스트 시나리오를 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바벨 (for script)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 크로스 브라우징 이슈를 해결하기 위해 생겨난 툴이 바벨이다. ES6+ 버전의 자바스크립트나 타입스크립트, JSX등 다른 언어로 분류되는 언어들에 대해서도 모든 브라우저에서 동작할 수 있도록 호환성을 지켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>browser</category>
      <author>zakelstorm</author>
      <guid isPermaLink="true">https://zakelstorm.tistory.com/115</guid>
      <comments>https://zakelstorm.tistory.com/115#entry115comment</comments>
      <pubDate>Sat, 21 Aug 2021 10:44:01 +0900</pubDate>
    </item>
  </channel>
</rss>