<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Sol Lee</title>
    <description>The latest articles on Forem by Sol Lee (@solleedata).</description>
    <link>https://forem.com/solleedata</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F705160%2F4ba356a1-48a4-4390-9bb3-e7b28c78b576.jpeg</url>
      <title>Forem: Sol Lee</title>
      <link>https://forem.com/solleedata</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/solleedata"/>
    <language>en</language>
    <item>
      <title>기업에서 원하는 개발자</title>
      <dc:creator>Sol Lee</dc:creator>
      <pubDate>Sun, 30 Mar 2025 13:07:14 +0000</pubDate>
      <link>https://forem.com/solleedata/gieobeseo-weonhaneun-gaebalja-2lf8</link>
      <guid>https://forem.com/solleedata/gieobeseo-weonhaneun-gaebalja-2lf8</guid>
      <description>&lt;p&gt;최근 여러 면접을 보면서 나 자신에 대해 돌아볼 수 있는 시간을 가졌다. 나는 어떤 개발자인가? 그리고 나는 어떤 가치를 회사에 제공할 수 있을까?&lt;/p&gt;

&lt;p&gt;나는 돈을 버는 것을 좋아한다. 단순히 돈 자체를 좋아한다기보다는, 내가 한 일이 경제적인 가치를 만들어내고, 그것이 숫자로 명확하게 확인될 때 가장 큰 보람을 느낀다. 그래서 주말에도 약속이나 일정이 없으면 자전거로 배달 부업을 뛰며 직접적인 수익을 창출한다. 이는 단순히 부수입을 얻는다는 개념을 넘어, 내가 직접 노력한 결과가 경제적인 가치로 이어지는 과정 자체를 즐긴다는 의미다.&lt;/p&gt;

&lt;p&gt;회사에서도 마찬가지였다. 가장 보람을 느낀 순간은 직접 모바일 광고를 적용하고 나서 매일 대시보드에서 매출 변화를 확인할 때였다. 단순한 기능 개발이 아니라, 실제로 비즈니스에 영향을 미치는 결과를 만들었다는 점에서 큰 만족감을 느꼈다. 내가 만든 기능이 회사의 매출에 기여하고, 이를 통해 비즈니스가 성장하는 것을 보는 것은 개발자로서 매우 뜻깊은 경험이었다.&lt;/p&gt;

&lt;p&gt;이러한 경험이 쌓이다 보니, 새로운 회사를 찾을 때도 자연스럽게 내가 얼마나 기여할 수 있을지, 그리고 그 기여가 얼마나 직접적인 경제적 가치를 창출할 수 있을지를 고민하게 되었다. 단순히 기술적으로 뛰어난 개발자가 아니라, 문제 해결을 통해 비즈니스적으로도 의미 있는 성과를 낼 수 있는 개발자가 되고 싶다는 생각이 들었다.&lt;/p&gt;

&lt;p&gt;결국 기업에서 원하는 개발자도 이런 사람이 아닐까? 단순히 코드를 잘 짜는 것이 아니라, 문제 해결을 통해 비즈니스적 가치를 창출할 수 있는 사람. 개발자의 역할은 단순한 기능 구현을 넘어, 회사가 성장하는 데 실질적인 기여를 할 수 있는 방향으로 나아가야 한다고 생각한다. 나 역시 앞으로도 이런 개발자가 되기 위해 끊임없이 고민하고 노력할 것이다.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Playwright E2E 테스트 실행 속도를 높이는 전략</title>
      <dc:creator>Sol Lee</dc:creator>
      <pubDate>Sun, 09 Feb 2025 14:56:11 +0000</pubDate>
      <link>https://forem.com/solleedata/playwright-e2e-teseuteu-silhaeng-sogdoreul-nopineun-jeonryag-4gcp</link>
      <guid>https://forem.com/solleedata/playwright-e2e-teseuteu-silhaeng-sogdoreul-nopineun-jeonryag-4gcp</guid>
      <description>&lt;p&gt;Playwright는 강력한 E2E 테스트 도구이지만, 테스트 수가 많아지면 실행 속도가 느려지는 문제가 발생할 수 있다. 이를 해결하기 위해 몇 가지 최적화 전략을 적용할 수 있는데, 이번 글을 통해 공유해보려고 한다.&lt;/p&gt;

&lt;p&gt;실제로 필자가 재직중인 회사에서 &lt;strong&gt;동료 개발자들이 테스트 시간이 오래 걸린다는 푸념&lt;/strong&gt;을 하고 있던 상황이었고, 나는 리서치를 통해 개선 사항들을 정리하고 실제로 적용해본 경험이 있다. &lt;/p&gt;

&lt;h2&gt;
  
  
  1. 더 많은 워커(Worker) 실행하기
&lt;/h2&gt;

&lt;p&gt;Playwright는 기본적으로 병렬 실행을 지원하며, --workers 옵션을 조정하면 더 많은 테스트를 동시에 실행할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx playwright test --workers=4&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;테스트 환경과 머신 리소스에 따라 최적의 워커 수를 조정하면 실행 속도를 높일 수 있다.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 가능한 경우 목(Mocking) 활용하기
&lt;/h2&gt;

&lt;p&gt;실 API 호출을 최소화하면 테스트 속도를 크게 향상할 수 있다. Playwright의 request API를 사용해 API 응답을 목(mock) 처리하면 네트워크 요청 대기 시간을 줄일 수 있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;page.route('**/api/data', async (route) =&amp;gt; {
  await route.fulfill({
    status: 200,
    body: JSON.stringify({ data: 'mocked response' }),
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;API 호출이 꼭 필요하지 않은 경우, 목 데이터를 활용해 실행 속도를 최적해보자.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. 컴포넌트 테스트 고려하기
&lt;/h2&gt;

&lt;p&gt;전체적인 E2E 테스트를 실행하기 전에, Playwright의 컴포넌트 테스트를 활용하면 특정 UI 컴포넌트만 빠르게 검증할 수 있다. 이렇게 하면 불필요한 전체적인 UI 테스트 없이도 주요 기능을 검증할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx playwright test --config=playwright.component.config.ts&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. 서브셋 테스트 실행하기 (스모크 테스트)
&lt;/h2&gt;

&lt;p&gt;모든 테스트를 매번 실행하는 대신, 가장 중요한 기능을 검증하는 스모크 테스트(smoke test) 세트를 정의하면 실행 시간을 절약할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx playwright test --grep @smoke&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;테스트 케이스에 &lt;a class="mentioned-user" href="https://dev.to/smoke"&gt;@smoke&lt;/a&gt; 태그를 추가하고, 주요 기능만 실행하도록 설정하면 빠르게 주요 시나리오를 검증할 수 있다.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 변경된 코드 기반으로 테스트 실행하기
&lt;/h2&gt;

&lt;p&gt;모든 테스트를 실행하는 대신, 변경된 코드와 관련된 테스트만 실행하면 시간을 단축할 수 있다.&lt;/p&gt;

&lt;p&gt;예를 들어, &lt;code&gt;git diff&lt;/code&gt;를 활용해 변경된 파일을 확인하고, 관련 테스트만 실행하는 스크립트를 만들 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git diff --name-only HEAD~1 | grep "src/" | xargs -I {} npx playwright test --grep="{}"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;이 방법을 CI/CD 환경에서 적용하면 테스트 실행 시간을 더욱 단축할 수 있다.&lt;/p&gt;

&lt;h2&gt;
  
  
  실무 프로젝트에 적용 결과
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;테스트 개수: 10개&lt;/li&gt;
&lt;li&gt;기본 실행 환경: 로컬 개발 환경에서 실행&lt;/li&gt;
&lt;li&gt;단일 워커(Worker)로 실행 시 평균 소요 시간: 10분 (1개당 1분 소요)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  최적화 적용 전후 비교
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;적용 전략&lt;/th&gt;
&lt;th&gt;AS-IS&lt;/th&gt;
&lt;th&gt;TO-BE&lt;/th&gt;
&lt;th&gt;기대 효과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;멀티 워커 사용 (4 workers)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10분&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;약 3분&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;병렬 실행으로 테스트 분산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API Mocking 적용&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10분&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;약 6~7분&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;네트워크 대기 시간 단축&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;컴포넌트 테스트 활용&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10분&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5분 이하&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;전체 UI 테스트 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;스모크 테스트 실행 (3개만 실행)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10분&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;약 3분&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;핵심 기능만 검증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;변경 코드 기반 테스트 실행&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10분&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;약 2~5분&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;관련 테스트만 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;결론적으로, 최적화 전략을 조합하면 10분 → 2~3분 수준으로 단축할 수 있었다.&lt;br&gt;
특히 멀티 워커 실행 + API Mocking + 스모크 테스트를 함께 적용하면 3배 이상 빠르게 테스트를 완료할 수 있다.&lt;/p&gt;

&lt;h2&gt;
  
  
  마무리하며
&lt;/h2&gt;

&lt;p&gt;Playwright의 E2E 테스트 실행 속도를 최적화하려면 병렬 실행을 활용하고, 목(mock) 처리 및 컴포넌트 테스트를 적극적으로 활용해야 한다. 또한, 스모크 테스트 전략과 변경된 코드 기반 테스트 실행을 조합하면 더욱 빠르고 효율적인 테스트 환경을 구축할 수 있다.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>e2e</category>
      <category>performance</category>
    </item>
    <item>
      <title>React Native vs Flutter: 어떤 것을 선택해야 할까?</title>
      <dc:creator>Sol Lee</dc:creator>
      <pubDate>Fri, 07 Feb 2025 05:29:11 +0000</pubDate>
      <link>https://forem.com/solleedata/react-native-vs-flutter-eoddeon-geoseul-seontaeghaeya-halgga-12cn</link>
      <guid>https://forem.com/solleedata/react-native-vs-flutter-eoddeon-geoseul-seontaeghaeya-halgga-12cn</guid>
      <description>&lt;p&gt;모바일 앱을 개발할 때 React Native와 Flutter 중 어떤 것을 선택해야 할지 고민하는 개발자들과 회사들이 많습니다. 개발 커뮤니티에 등장하는 단골 주제이기도 하지요. 두 프레임워크 모두 크로스 플랫폼 개발을 지원하며, 각각의 장점과 단점이 존재합니다. 이번 글에서는 &lt;strong&gt;학습 곡선, 성능, 디버깅, 패키지, 기술적 성숙함 등 다양한 측면에서 두 프레임워크를 비교&lt;/strong&gt;해보겠습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. 학습 곡선
&lt;/h2&gt;

&lt;p&gt;React Native: JavaScript(또는 TypeScript)를 사용하므로 웹 개발 경험이 있다면 빠르게 적응할 수 있습니다.&lt;/p&gt;

&lt;p&gt;Flutter: Dart라는 새로운 언어를 배워야 하지만, UI 구성 방식이 React와 유사하여 비교적 익숙할 수 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. CLI
&lt;/h2&gt;

&lt;p&gt;React Native: react-native-cli, expo, ignite 등 다양한 CLI 도구를 사용할 수 있습니다. &lt;/p&gt;

&lt;p&gt;React Native CLI는 React Native 프로젝트를 생성하고 관리하는 기본 도구입니다. 이 도구는 개발자에게 더 많은 제어권을 제공하지만, 초기 설정이 복잡할 수 있습니다.&lt;/p&gt;

&lt;p&gt;Expo CLI는 React Native 개발을 더욱 간소화합니다. 주요 특징은:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;빠르고 쉬운 프로젝트 생성&lt;/li&gt;
&lt;li&gt;플랫폼별 IDE 없이 시뮬레이터 사용 가능&lt;/li&gt;
&lt;li&gt;초보자에게 친화적인 환경 제공&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;하지만 Expo는 네이티브 파일 제어에 제한이 있고, 사용 가능한 라이브러리가 제한적입니다.&lt;/p&gt;

&lt;p&gt;Ignite는 React Native 프로젝트를 위한 보일러플레이트 생성기로, 미리 구성된 컴포넌트와 best practices를 포함합니다.&lt;/p&gt;

&lt;p&gt;Flutter: 공식 CLI를 제공하며, 프로젝트 생성부터 빌드까지 간편하게 수행할 수 있습니다&lt;/p&gt;

&lt;h2&gt;
  
  
  3. 성능
&lt;/h2&gt;

&lt;p&gt;React Native: JavaScript Bridge를 사용하기 때문에 네이티브보다 성능이 떨어질 수 있으나, 최신 Fabric과 TurboModules로 개선 중입니다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;React Native는 JavaScript Bridge를 사용하여 네이티브 컴포넌트와 통신합니다. 이로 인해 다음과 같은 성능 문제가 발생할 수 있습니다:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;비동기 통신으로 인한 이벤트 실행 지연&lt;/li&gt;
&lt;li&gt;사용자 입력에 대한 빠른 응답 제공의 어려움&lt;/li&gt;
&lt;li&gt;JSON 데이터 변환으로 인한 오버헤드&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Flutter: 네이티브 코드를 직접 실행하므로 더 높은 성능을 기대할 수 있습니다. 다음 특징들이 있습니다:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;네이티브 ARM 코드로 직접 컴파일되어 네이티브에 가까운 성능 제공&lt;/li&gt;
&lt;li&gt;Skia 그래픽 엔진을 사용하여 부드러운 애니메이션과 렌더링 가능&lt;/li&gt;
&lt;li&gt;복잡하고 애니메이션이 많은 앱에서 더 나은 성능 제공&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;결론: React Native는 UI가 단순하고 애니메이션이 덜 복잡한 앱에서 우수한 성능을 보이고, Flutter는 그래픽 집약적인 애플리케이션에서 더 나은 성능을 제공합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. 디버깅
&lt;/h2&gt;

&lt;p&gt;React Native: React Native Debugger, Flipper 등을 사용할 수 있지만, 네이티브 디버깅은 복잡할 수 있습니다.&lt;/p&gt;

&lt;p&gt;Flutter: Flutter DevTools를 활용하여 일관된 환경에서 디버깅이 가능합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 패키지(라이브러리) 생태계
&lt;/h2&gt;

&lt;p&gt;React Native: NPM 기반으로 웹 개발 생태계를 적극 활용할 수 있습니다.&lt;/p&gt;

&lt;p&gt;Flutter: pub.dev를 통해 다양한 패키지를 제공하지만, 일부 네이티브 기능은 직접 구현해야 할 수도 있습니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. 문서와 커뮤니티
&lt;/h2&gt;

&lt;p&gt;React Native: 공식 문서가 잘 정리되어 있으며, Stack Overflow와 같은 개발자 커뮤니티에서 많은 자료를 찾을 수 있습니다.&lt;/p&gt;

&lt;p&gt;Flutter: 공식 문서가 체계적으로 구성되어 있으며, 샘플 코드가 많아 학습하기 용이합니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. 앱 용량
&lt;/h2&gt;

&lt;p&gt;React Native: 기본 프로젝트 크기가 상대적으로 작습니다(10~15MB).&lt;/p&gt;

&lt;p&gt;Flutter: 네이티브 엔진을 포함하기 때문에 프로젝트 용량이 더 큽니다(20MB 이상).&lt;/p&gt;

&lt;h2&gt;
  
  
  8. 핫 리로드
&lt;/h2&gt;

&lt;p&gt;React Native: Fast Refresh를 지원하지만, 일부 네이티브 변경 사항 반영 시 앱을 재시작해야 합니다. (너무 싫은 pod install.. 🫢)&lt;/p&gt;

&lt;p&gt;Flutter: Hot Reload 기능이 강력하고 빠릅니다. Flutter도 iOS 개발 시 CocoaPods를 사용하지만, React Native에 비해 pod install 과정이 덜 빈번하고 더 간단합니다. 대부분의 경우 Flutter CLI가 자동으로 필요한 의존성을 관리합니다.&lt;/p&gt;

&lt;h1&gt;
  
  
  결론: 어떤 것을 선택해야 할까?
&lt;/h1&gt;

&lt;p&gt;React Native와 Flutter 모두 훌륭한 선택지이지만, 개발자의 배경과 프로젝트 특성에 따라 적절한 선택이 달라질 수 있습니다.&lt;/p&gt;

&lt;p&gt;React Native가 적합한 경우&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JavaScript/TypeScript 경험이 많고 빠르게 개발하고 싶을 때&lt;/li&gt;
&lt;li&gt;웹 생태계를 적극 활용하고 싶을 때&lt;/li&gt;
&lt;li&gt;다양한 라이브러리와 플러그인을 활용해야 할 때&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Flutter가 적합한 경우&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;성능이 중요한 애플리케이션을 개발할 때&lt;/li&gt;
&lt;li&gt;일관된 UI/UX를 유지해야 할 때&lt;/li&gt;
&lt;li&gt;네이티브 성능에 가까운 경험이 필요한 경우&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;제가 속한 회사에서는 기존 개발자들이 JS 개발 경험이 있었기 때문에 React Native를 선호하였고 빠른 개발이 요구되었기 때문에 비교적으로 라이브러리 생태계가 큰 React Native를 채택하였습니다.&lt;/p&gt;

</description>
      <category>react</category>
      <category>flutter</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>페이지 컴포넌트를 합성 컴포넌트로 재구조화 시키기</title>
      <dc:creator>Sol Lee</dc:creator>
      <pubDate>Mon, 03 Feb 2025 11:02:57 +0000</pubDate>
      <link>https://forem.com/solleedata/peiji-keomponeonteureul-habseong-keomponeonteuro-jaegujohwa-sikigi-282</link>
      <guid>https://forem.com/solleedata/peiji-keomponeonteureul-habseong-keomponeonteuro-jaegujohwa-sikigi-282</guid>
      <description>&lt;h2&gt;
  
  
  한 줄 요약
&lt;/h2&gt;

&lt;p&gt;거대한 페이지 컴포넌트를 합성 컴포넌트로 리팩토링했던 경험 공유&lt;/p&gt;

&lt;h2&gt;
  
  
  한 페이지 안에서 중복 코드가 너무 많다
&lt;/h2&gt;

&lt;p&gt;최근 회사 코드(Svelte)를 보다가 한 페이지에서 반복되는 코드가 너무 많다는 걸 발견했다. 회사 코드를 그대로 갖다 붙이면 큰일나기 때문에, 예시를 통해 알아보자. 예를 들어, 같은 스타일과 로직을 가진 카드 UI가 여러 개 있었는데, 각각 따로 구현되어 있어서 유지보수가 어려웠다. 나는 이 문제를 해결하기 위해 합성(Composable) 컴포넌트 방식으로 리팩토링하기로 했다.&lt;/p&gt;

&lt;h2&gt;
  
  
  중복을 제거하고 더 재사용 가능한 구조로 만들기
&lt;/h2&gt;

&lt;p&gt;내 목표는 다음과 같았다.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;중복되는 UI와 로직을 컴포넌트로 분리한다.&lt;/li&gt;
&lt;li&gt;다양한 데이터와 함께 재사용할 수 있도록 유연한 구조를 만든다.&lt;/li&gt;
&lt;li&gt;Svelte 코드와 React 코드로 비교하면서 더 나은 방식이 무엇인지 분석한다.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Svelte와 React 코드 비교하며 리팩토링하기
&lt;/h2&gt;

&lt;p&gt;기존 Svelte 코드 (문제점이 있는 코드)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;메뉴1&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Card&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;Card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;메뉴2&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Card&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;Card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title3&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description3&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick3&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;메뉴3&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Card&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;위 코드의 문제점은 단순하다.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;handleClick&lt;/code&gt;이 각각 다른 변수로 관리되고 있다.&lt;br&gt;
카드의 레이아웃과 스타일이 동일한데도 불구하고, 같은 구조가 반복되고 있다.&lt;/p&gt;

&lt;p&gt;리팩토링한 Svelte 코드 (합성 컴포넌트 적용)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;Card&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;자세히 보기&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Card&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Card&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onClick=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Card&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onClick=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Card&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title3&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description3&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onClick=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick3&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이제 &lt;code&gt;&amp;lt;Card&amp;gt;&lt;/code&gt; 컴포넌트는 &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;onClick&lt;/code&gt;을 props로 받아 재사용할 수 있게 되었다.&lt;/p&gt;

&lt;p&gt;React 코드로 같은 구조를 적용하면?&lt;br&gt;
Svelte에서 적용한 방식을 React에서도 똑같이 적용할 수 있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"p-4 border rounded-lg"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;자세히 보기&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"제목1"&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"설명1"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;클릭1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"제목2"&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"설명2"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;클릭2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"제목3"&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"설명3"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;클릭3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React에서는 props를 활용해서 같은 방식으로 합성 컴포넌트를 만들 수 있다.&lt;br&gt;
여기서 Card 컴포넌트를 단순히 title, description, onClick을 받아서 렌더링하는 형태로 만들었다.&lt;/p&gt;

&lt;p&gt;Result: 유지보수성이 높아지고, 코드가 간결해졌다&lt;br&gt;
리팩토링 전에는 같은 UI를 여러 번 반복해서 작성해야 했지만, 리팩토링 후에는 Card 컴포넌트 하나만 만들어서 여러 곳에서 사용할 수 있게 되었다. 이 방식이 가지는 장점은 명확하다.&lt;/p&gt;

&lt;p&gt;중복 코드 제거 → 한 번만 정의하면 어디서든 재사용 가능&lt;br&gt;
유지보수 용이 → 디자인 변경이 생겨도 Card 컴포넌트만 수정하면 됨&lt;br&gt;
더 나은 가독성 → 불필요한 반복이 줄어들어 읽기 쉬워짐&lt;/p&gt;

&lt;h2&gt;
  
  
  마무리
&lt;/h2&gt;

&lt;p&gt;Svelte와 React는 문법적인 차이는 있지만, 합성 컴포넌트 개념을 적용하는 방식은 거의 비슷하다. 공통적으로 중복 코드를 최소화하고, 재사용성을 높이는 것이 중요하다. 실제로 이 방식으로 리팩토링한 이후, 코드 변경이 있을 때 수정해야 할 부분이 줄어들면서 개발 속도가 빨라졌다.&lt;/p&gt;

&lt;p&gt;결국, 페이지 컴포넌트를 잘게 나누고, 필요한 데이터만 props로 넘겨주는 방식이 유지보수성과 확장성 측면에서 더 낫다는 걸 다시 한 번 깨닫게 되었다.&lt;/p&gt;

</description>
      <category>svelte</category>
      <category>react</category>
      <category>refactoring</category>
    </item>
    <item>
      <title>앱-웹뷰-아이프레임 데이터 공유 로직 개선 경험</title>
      <dc:creator>Sol Lee</dc:creator>
      <pubDate>Fri, 31 Jan 2025 03:12:32 +0000</pubDate>
      <link>https://forem.com/solleedata/aeb-webbyu-aipeureim-deiteo-gongyu-rojig-gaeseon-gyeongheom-iep</link>
      <guid>https://forem.com/solleedata/aeb-webbyu-aipeureim-deiteo-gongyu-rojig-gaeseon-gyeongheom-iep</guid>
      <description>&lt;h2&gt;
  
  
  한줄요약
&lt;/h2&gt;

&lt;p&gt;모바일 앱 내 웹뷰, 그리고 웹뷰 내 아이프레임 간 메시지 포스트 로직 개선 경험 공유&lt;/p&gt;

&lt;h2&gt;
  
  
  프로젝트 구조
&lt;/h2&gt;

&lt;p&gt;Pixels Guide Assistant(줄여서 "PGA")는 Pixels라는 해외 온라인 게임의 데이터를 제공하는 웹브라우저 익스텐션 및 모바일 앱 형태의 사내 프로젝트이다. 모바일 앱은 현 시점, 구글 플레이 스토어에만 출시가 된 상태이며, 총 사용자 수는 10만 명을 찍었다. &lt;/p&gt;

&lt;p&gt;나는 익스텐션 개발을 고도화 단계까지 참여하고 최근에는 모바일 앱 개발을 리드하고 있다. 앱은 React Native로 개발되었으며, 내부에는 두 개의 웹뷰가 띄워져 있다 (회사 프로젝트라서 구체적으로 어떤 웹뷰인지는 함구..🫢). 그 웹뷰 중 하나는 내부에 또 iframe을 포함한다. 요약하면 다음 그림과 같다:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcohuryzn059q8lbi3ir5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcohuryzn059q8lbi3ir5.png" alt="Image description" width="704" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  요구 사항
&lt;/h2&gt;

&lt;p&gt;모바일 앱에 애드몹 광고를 적용해달라는 요구 사항을 받았다.&lt;/p&gt;

&lt;p&gt;까다로웠던 점은 (예상했겠지만) 사용자가 iframe의 "광고 보기"라는 UI를 누르면 웹뷰로 이 이벤트를 전달하고, 웹뷰에서 다시 모바일앱으로 전달, 그리고 광고를 띄운 후에는 다시 반대로 iframe까지 이벤트를 응답해줘야 했다. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxs9o7imibqhwl7dv74kl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxs9o7imibqhwl7dv74kl.png" alt="Image description" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;iframe 웹사이트는 다른 동료 개발자분이 작업하고 계셔서 협업을 통해 응답 json값 인터페이스 등 스펙을 맞춰야 했다.&lt;/p&gt;

&lt;p&gt;그리고 아래와 같은 형식으로 표준화하기로 하였다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNativeWebView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;protocol&lt;/span&gt;
          &lt;span class="nx"&gt;func&lt;/span&gt;
          &lt;span class="nx"&gt;args&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  코드 개선
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;우선 위 postMessage이 여러 번 반복되어 &lt;code&gt;responseFunc&lt;/code&gt;라는 함수로 추출하여 추상화 하였다. 중복이 3번 이상이면 공통 로직으로 빼내는데, 충분히 충족되었기 때문이다. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;postMessage 인자 객체 중 direction이 있는데, 이는 위 도식에서 방향을 의미한다. 예를들어 "app-to-webview"와 비슷한 형식의 문자열이다. 우리는 이러한 매직스트링을 상수로 관리하기로 하였다:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const DIRECTION = {
  APP_TO_WEBVIEW: 'app-to-webview',
  ...
  ...
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  성과
&lt;/h2&gt;

&lt;p&gt;약 이틀만에 성공적으로 애드몹을 적용하고 배포까지 완료하였다. &lt;/p&gt;

&lt;p&gt;물론 중간에 뜻대로 되지 않던 상황들도 있었다. 예를들어 테스트용 광고 아이디로는 광고 송출이 정상적이었지만, 실제 광고 아이디는 광고가 뜨지 않았던 것이다. 대쉬보드에 들어가서 직접 확인해보니 회사 서류가 아직 제출되지 않았던 모양이다. 담당자에게 이 사실을 알리고, 다행스럽게 바로 다음날 이슈가 해결되었다. &lt;/p&gt;

&lt;h2&gt;
  
  
  아쉬웠던 점
&lt;/h2&gt;

&lt;p&gt;현재 중간 웹뷰는 단지 proxy로서, iframe과 모바일 앱 사이에서 다리 역할을 할 뿐이다. 중간 매개체없이 곧바로 이 둘 사이를 연결해볼 수는 없었을까? 제품의 아키텍쳐에 대한 이해도를 더 파악하고, 보다 쉬운 기술로 이 문제를 해결할 수 있도록 고민이 필요해보인다.&lt;/p&gt;

&lt;h2&gt;
  
  
  앞으로
&lt;/h2&gt;

&lt;p&gt;react-native-webview 라이브러리의 도움을 많이 받아서 비교적 빠르게 기능을 구현할 수 있었다. 웹뷰의 내부 구현 로직을 더 탐구해보고 가능하면 코드 기여도 시도해 볼 예정이다.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>지우기 쉬운 코드 (feat. Next.js 페이지 라우팅)</title>
      <dc:creator>Sol Lee</dc:creator>
      <pubDate>Wed, 29 Jan 2025 11:55:14 +0000</pubDate>
      <link>https://forem.com/solleedata/peijibyeolro-keomponeonteu-yutil-hug-taib-moaseo-gwanrihagi-feat-nextjs-peiji-rauting-2hcn</link>
      <guid>https://forem.com/solleedata/peijibyeolro-keomponeonteu-yutil-hug-taib-moaseo-gwanrihagi-feat-nextjs-peiji-rauting-2hcn</guid>
      <description>&lt;h2&gt;
  
  
  한줄요약
&lt;/h2&gt;

&lt;p&gt;하나의 페이지에서만 사용되는 컴포넌트나 함수라면 해당 페이지 폴더안에 모아놓자&lt;/p&gt;

&lt;h2&gt;
  
  
  좋은 코드란 무엇인가
&lt;/h2&gt;

&lt;p&gt;개발자마다 기준이 다를 수 있겠지만, 나는 최근에 좋은 코드에 대한 새로운 기준이 하나 생겼다. &lt;/p&gt;

&lt;p&gt;바로 "지우기 쉬운 코드"이다. &lt;/p&gt;

&lt;p&gt;예를들어, 이벤트 프로모션 페이지 구현을 요구 사항으로 받는다고 생각해보자. 물론 해당 페이지는 재활용할 수도 있겠지만, 비즈니스 요구 사항에 따라 몇 일 후면 영원히 삭제해야 될 수도 있다. &lt;/p&gt;

&lt;p&gt;후자를 위해 지우기 쉬운 코드를 작성해야 한다는 뜻이다.&lt;/p&gt;

&lt;h2&gt;
  
  
  지우기 쉬운 코드
&lt;/h2&gt;

&lt;p&gt;그렇다면 지우기 쉬운 코드란 무엇인가?&lt;/p&gt;

&lt;p&gt;흔히 말하는 "결합도"가 낮은 모듈을 만듦으로서 지우기 쉬운 코드를 만들 수 있다고 생각한다.&lt;/p&gt;

&lt;p&gt;극단적으로 말하면 페이지 디렉토리를 싹 다 날려버려도 괜찮을 만큼 결합도를 줄일 수 있다. &lt;/p&gt;

&lt;p&gt;Next.js &amp;gt; 페이지 라우팅을 기준으로, 이벤트 페이지에 해당하는 &lt;code&gt;page&lt;/code&gt; 폴더 안에 &lt;code&gt;/components&lt;/code&gt;, &lt;code&gt;/types&lt;/code&gt;, &lt;code&gt;/utils&lt;/code&gt; 등 해당 페이지에서만 사용되는 모듈들을 모아 놓는것이다. &lt;/p&gt;

&lt;p&gt;한마디로, 사용하는 쪽 가까이에 배치하는 것이다. 영어로는 "feature-based folder"라고 하는 듯 하다.&lt;/p&gt;

&lt;h2&gt;
  
  
  실제로 적용해보기
&lt;/h2&gt;

&lt;p&gt;최근 진행 중인 영화 스트리밍 웹사이트 프로젝트에서 이를 적용해보았다.&lt;/p&gt;

&lt;p&gt;문제는 next.js의 &lt;code&gt;pages&lt;/code&gt; 디렉토리에는 "페이지" 컴포넌트만 있어야 한다는 것.&lt;/p&gt;

&lt;p&gt;개발 서버 localhost에서 실행할 때는 문제가 되지 않았지만, production 배포 시 다음 에러와 함께 배포가 중단된다:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build optimization failed: found pages without a React Component as default export in&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;물론 각 컴포넌트, 타입, 훅들을 pages 폴더 밖으로 꺼낼 수도 있었다.&lt;/p&gt;

&lt;p&gt;하지만 domain-specific한 모듈을을 한 군데 보관하고, 사용하는 곳 근처에서 관리하는게 유지보수 이점이 있을 것으로 판단하였다.&lt;/p&gt;

&lt;p&gt;🍀 해결: next.config.js에 간단히 다음 설정을 추가하였다.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;pageExtensions: ['page.tsx', 'page.ts']&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;그리고 실제 "페이지"로 렌더링 되어야 할 파일들을 위 포맷으로 바꾸어 주었다.&lt;/p&gt;

&lt;p&gt;여기서 주의할 점은 _app.tsx, index.tsx 등도 바꿔줘야 한다 (_app.page.tsx, index.page.tsx).&lt;/p&gt;

&lt;p&gt;그리고 tailwind도 바로 먹히질 않아서 한 줄 더 추가해줘야 한다:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tailwind.config.js&lt;/span&gt;
 &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/*.page.{js,ts,jsx,tsx,mdx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="nx"&gt;추가&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/**/*.{js,ts,jsx,tsx,mdx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./components/**/*.{js,ts,jsx,tsx,mdx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app/**/*.{js,ts,jsx,tsx,mdx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Displaying different UI for mobile and desktop screens</title>
      <dc:creator>Sol Lee</dc:creator>
      <pubDate>Tue, 28 Jan 2025 02:56:47 +0000</pubDate>
      <link>https://forem.com/solleedata/displaying-different-ui-for-mobile-and-desktop-screens-1d06</link>
      <guid>https://forem.com/solleedata/displaying-different-ui-for-mobile-and-desktop-screens-1d06</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;&amp;lt;Responsive /&amp;gt;&lt;/code&gt; component to display different UI components for mobile and desktop screens&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;As frontend devs, we often need to display different UI components for small screens (mobile) and larger screens (tablet, PC..). &lt;/p&gt;

&lt;p&gt;For example, we may be required to render a bottomsheet for mobile, but a modal for PCs.&lt;/p&gt;

&lt;p&gt;We can use &lt;code&gt;media-query&lt;/code&gt; to set the breakpoint for each device and use a flag variable such as &lt;code&gt;isMobile&lt;/code&gt; to render each component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Responsive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isMobile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMediaQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(max-width: 768px)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Breakpoint for mobile&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isMobile&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MobileComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DesktopComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What are the limitations of this code?&lt;/p&gt;

&lt;p&gt;Let's see it from the maintanance point of view. &lt;/p&gt;

&lt;p&gt;For now, we are just required to handle with two types: mobile and non-mobile. However, what if in the future we are required to display different UIs for watch or tvOS? We'll probably have to use &lt;code&gt;switch-case&lt;/code&gt;, which is totally fine but is there any simpler solution?&lt;/p&gt;

&lt;p&gt;Also, the &lt;code&gt;&amp;lt;Responsive/&amp;gt;&lt;/code&gt; component described above is not so fit for reusability. In other words, what if we want to use components other than  MobileComponent and DesktopComponent?&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a universal component for responsiveness
&lt;/h2&gt;

&lt;p&gt;After some ideation, we can rewrite our code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Responsive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isMobile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMediaQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(max-width: 768px)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Breakpoint for mobile&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isMobile&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;mobile&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;desktop&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Responsive&lt;/span&gt; 
  &lt;span class="na"&gt;mobile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MobileComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
  &lt;span class="na"&gt;desktop&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DesktopComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, we can reuse the &lt;code&gt;&amp;lt;Responsive /&amp;gt;&lt;/code&gt; component by simply passing the respective component as props (mobile, desktop).&lt;/p&gt;

&lt;p&gt;Also, &lt;code&gt;isMobile ? mobile : desktop&lt;/code&gt; looks much simpler than &lt;code&gt;isMobile ? &amp;lt;MobileComponent /&amp;gt; : &amp;lt;DesktopComponent /&amp;gt;&lt;/code&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>반응형 컴포넌트 깔쌈하게 만들기</title>
      <dc:creator>Sol Lee</dc:creator>
      <pubDate>Mon, 27 Jan 2025 09:37:09 +0000</pubDate>
      <link>https://forem.com/solleedata/baneunghyeong-keomponeonteu-ggalggeumhage-mandeulgi-1n1c</link>
      <guid>https://forem.com/solleedata/baneunghyeong-keomponeonteu-ggalggeumhage-mandeulgi-1n1c</guid>
      <description>&lt;h2&gt;
  
  
  한 줄 요약
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;모바일, 데스크탑 등 브라우저의 화면 크기에 따라 서로 다른 컴포넌트를 보여줘야 할 때, &lt;code&gt;&amp;lt;Responsive /&amp;gt;&lt;/code&gt; 컴포넌트를 만들어 깔끔한 코드를 유지해보자&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  분기 처리의 한계
&lt;/h2&gt;

&lt;p&gt;프론트엔드 개발자라면 모바일 화면과 태블릿 그리고 데스크탑 화면까지 고려해야 하는 경우가 많다. &lt;/p&gt;

&lt;p&gt;흔히 미디어쿼리(media query) 기법을 활용하여 분기 처리를 통해 각각의 화면에 서로 다른 컴포넌트를 보여주는 것도 방법이 될 수 있다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Responsive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isMobile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMediaQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(max-width: 768px)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Breakpoint for mobile&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isMobile&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MobileComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DesktopComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;하지만 위 코드의 문제점은 무엇일까?&lt;/p&gt;

&lt;p&gt;유지 보수 측면에서 생각해보자.&lt;/p&gt;

&lt;p&gt;지금 당장은 모바일 여부만 분기 처리하면 되지만, 추후에 각종 디바이스에 대한 요구 사항이 들어온다면? 예를들어, watch나 tvOS에도 서포트해야 한다고 하면 분기 처리가 꽤 복잡해질 수 있다. 가독성이 나빠지고, 버그가 발생할 수 있으며, 개발자의 공수가 들어갈 것이다.&lt;/p&gt;

&lt;p&gt;또한 위 &lt;code&gt;&amp;lt;Responsive /&amp;gt;&lt;/code&gt; 컴포넌트는 현재 구현상으로는 재활용이 어렵다. &lt;code&gt;MobileComponent&lt;/code&gt;와 &lt;code&gt;DesktopComponent&lt;/code&gt; 말고 다른 컴포넌트를 보여주고 싶어지지 않을까? 이들을 외부로 주입받으면 되지 않을까?&lt;/p&gt;

&lt;h2&gt;
  
  
  반응형 분기처리를 위한 전용 컴포넌트 만들기
&lt;/h2&gt;

&lt;p&gt;이런 고민 끝에 다음과 같이 개선해볼 수 있다&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Responsive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isMobile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMediaQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(max-width: 768px)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Breakpoint for mobile&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isMobile&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;mobile&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;desktop&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⭐️사용처:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Responsive&lt;/span&gt; 
      &lt;span class="na"&gt;mobile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MobileComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
      &lt;span class="na"&gt;desktop&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DesktopComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;이로서 사용하는 곳에서는 원하는 컴포넌트를 각각의 prop에 주입시켜 주면서 무제한으로 재활용이 가능해진다. 확장성을 챙긴 것이다.&lt;/p&gt;

&lt;p&gt;분기 처리 또한 기존 코드보다 한결 간결해진 느낌이다. &lt;/p&gt;

&lt;h2&gt;
  
  
  실제로 적용해보기
&lt;/h2&gt;

&lt;p&gt;최근 사이드 프로젝트로 진행 중인 프로젝트에 적용해보았다(아래 스크린샷은 개인 프로젝트):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8q24d6srddoi02mkkiow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8q24d6srddoi02mkkiow.png" alt="Image description" width="800" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;이것저것 때려박은 코드 블럭이 단 한줄로 표현할 수 있을만큼의 효과를 얻었다.&lt;/p&gt;

</description>
      <category>반응형</category>
      <category>컴포넌트</category>
    </item>
    <item>
      <title>Why Should We Avoid `object` Props in React</title>
      <dc:creator>Sol Lee</dc:creator>
      <pubDate>Sat, 25 Jan 2025 10:58:40 +0000</pubDate>
      <link>https://forem.com/solleedata/why-should-we-avoid-object-props-in-react-3571</link>
      <guid>https://forem.com/solleedata/why-should-we-avoid-object-props-in-react-3571</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;In react, passing &lt;code&gt;object&lt;/code&gt; to props triggers rerendering (we need to use &lt;code&gt;useMemo&lt;/code&gt; to avoid this)&lt;/li&gt;
&lt;li&gt;If possible, pass primitive values to props&lt;/li&gt;
&lt;li&gt;Divide the component that is passing too many props into several smaller components&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  How React detects changes in &lt;code&gt;props&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;React uses the so-called "shallow comparison" to detect any changes in the props and states. Specifically, it is known that the javascript syntax &lt;code&gt;Object.is()&lt;/code&gt; is used for the comparison. Then, guess what the following code will result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;world },
  { hello: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;world&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The answer is... false!&lt;/p&gt;

&lt;p&gt;Both look the same. However, javascript &lt;code&gt;objects&lt;/code&gt; are passed by reference, meaning that even if they look the same, they are not the same: they have different allocations in the memory. &lt;/p&gt;

&lt;p&gt;Having said that, the &lt;code&gt;Child&lt;/code&gt; component below will be rerendered even if it is memoized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Child gets rerendered&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Child 렌더링&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We may avoid unnecessary rerendering by using the appropriate react hook: &lt;code&gt;useMemo&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, it is known that both &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt; are recommended to be used for "expensive" calculations. In our example, an object with only two keys and string values is not that expensive. We need another solution.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pass primitive values as props
&lt;/h1&gt;

&lt;p&gt;If possible, it is better to pass the primitive values as props. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Child&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case it wasn't a laborious task to pass each key to Child's props. However, sometimes we need to deal with huge objects with more than 10 key-values. &lt;/p&gt;

&lt;h1&gt;
  
  
  Create another component (single resposibility principle)
&lt;/h1&gt;

&lt;p&gt;If we stick to the SOLID principles, we may think about creating several smaller components that deal with each prop. Or, at least, distribute the object's key-values to several components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Child1&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Child2&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>react</category>
      <category>performance</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How We Reduced 700mb of Images Load</title>
      <dc:creator>Sol Lee</dc:creator>
      <pubDate>Sun, 01 Dec 2024 12:40:58 +0000</pubDate>
      <link>https://forem.com/solleedata/how-we-reduced-700mb-of-images-load-58kd</link>
      <guid>https://forem.com/solleedata/how-we-reduced-700mb-of-images-load-58kd</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Originally from : &lt;a href="https://techblog.woowahan.com/20228/" rel="noopener noreferrer"&gt;https://techblog.woowahan.com/20228/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;This post is a translation from above article, no AI-translation used&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  We'll See How To
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Reduce image load size with &lt;strong&gt;IntersectionObserver API&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;state&lt;/strong&gt; to conditionally load images&lt;/li&gt;
&lt;li&gt;And other useful techniques (webp, lazy loading, ...)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When my co-worker sent me a message reporting the image loading took long, I didn't realize the magnitude of its importance. &lt;/p&gt;

&lt;p&gt;In fact, I just guessed it must be a WI-FI issue or something similar. &lt;/p&gt;

&lt;p&gt;However, after investigating the dev tools, the images were total 700mb! This was way larger than expected.&lt;/p&gt;

&lt;p&gt;The service that downloads these images is a feed in which users can see the actual images of the food from local restaurants and reviews. &lt;/p&gt;

&lt;p&gt;My first guess was the hidden images from the screen  that were in HTML getting unnecessarily loaded.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0z3w8m5mk53r2pcwt5jj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0z3w8m5mk53r2pcwt5jj.png" alt="Image description" width="540" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The guess was correct. &lt;strong&gt;The gray squares from above screenshot were also being loaded&lt;/strong&gt;, even though not yet visible for the user.&lt;/p&gt;

&lt;p&gt;To improve this, we used the &lt;code&gt;IntersectionObserver API&lt;/code&gt; so that they get loaded only they get exposed to the screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ObserverImageProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;className&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** 이미지가 로드되기 전에 표시할 플레이스홀더 이미지 */&lt;/span&gt;
  &lt;span class="nl"&gt;placeholderSrc&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** 이미지가 뷰포트에 들어왔을 때 실행될 콜백 */&lt;/span&gt;
  &lt;span class="nl"&gt;onIntersect&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ObserverImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;placeholderSrc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1x1 투명 GIF&lt;/span&gt;
  &lt;span class="nx"&gt;onIntersect&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ObserverImageProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;imageSrc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setImageSrc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;placeholderSrc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoaded&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imgRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLImageElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// IntersectionObserver 인스턴스 생성&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// 이미지가 뷰포트에 들어왔을 때&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setImageSrc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;onIntersect&lt;/span&gt;&lt;span class="p"&gt;?.();&lt;/span&gt;
            &lt;span class="c1"&gt;// 한 번 로드된 후에는 더 이상 관찰할 필요가 없으므로 해제&lt;/span&gt;
            &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unobserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 옵저버 옵션&lt;/span&gt;
        &lt;span class="na"&gt;rootMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;50px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 뷰포트 기준 50px 여유를 두고 미리 로드&lt;/span&gt;
        &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="c1"&gt;// 10%만 보여도 로드 시작&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 현재 이미지 요소 관찰 시작&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imgRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imgRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 컴포넌트 언마운트 시 옵저버 해제&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onIntersect&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;
      &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;imgRef&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;imageSrc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isLoaded&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;opacity-0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;opacity-100&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;onLoad&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIsLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
      &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
        &lt;span class="na"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;opacity 0.3s ease-in-out&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isLoaded&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blur(10px)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
      &lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;ObserverImage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  But We Needed To Take More Action
&lt;/h3&gt;

&lt;p&gt;I thought the amount of download would decrease by large percentage, but it did not. So I had to further investigate. &lt;/p&gt;

&lt;p&gt;In the feed, there are 5 "themes" and each one of them displayed 3 contents, which had maximum 10 images. Therefore, we would have 5 x 3 x 10 = 150 images maximum. Even supposing each image is 1mb, we would have 150mb, not the absurd amount of 700mb.&lt;/p&gt;

&lt;p&gt;In the end, it turned out that ALL the images in the feed were added to HTML, including those at the bottom of the feed. &lt;/p&gt;

&lt;p&gt;Also, the UI for the detailed images was a "swipe" to the left and right. Thise swipe could have maximum 30 images. &lt;strong&gt;All of them were being loaded (unnecessarily!).&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Result of Improvement: 90% Decrease in Download Size
&lt;/h3&gt;

&lt;p&gt;Now that we spotted the reasons, let's fix them. First of all, we &lt;strong&gt;added an &lt;code&gt;open&lt;/code&gt; state in order to only load the images when user sees them:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Images&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, we *&lt;em&gt;applied the above-mentioned &lt;code&gt;IntersectionObserver API&lt;/code&gt; to make the image get loaded when it appears on the screen. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These improved approximately 90% of images load. A huge improvement, in fact. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft3krfjru4swlj9ch29yf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft3krfjru4swlj9ch29yf.png" alt="Image description" width="512" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  This Is Not The End. Let's Reduce Hidden Image Sizes
&lt;/h3&gt;

&lt;p&gt;A few days later, the images were heavier again. We were getting total 60mb of images alone. Now the reason was the increased amount of contents, which naturally increased the total of images size. We had to find more ways to reduce it. The following is a list of technique we took:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From the fourth image load the images when they appear on the screen&lt;/li&gt;
&lt;li&gt;Load the restaurant's image when user clicks its details page&lt;/li&gt;
&lt;li&gt;Load the profile image when it appears on the screen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These techniques led to 5 aditional mb saving 🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  Still More Room For Improvements?
&lt;/h3&gt;

&lt;p&gt;Currently, the images uploaded by the users get through a resizing process. In the feed we may use smaller images. &lt;/p&gt;

&lt;p&gt;Also, if we convert the image format to &lt;code&gt;webp&lt;/code&gt; we are expected to reduce the image size by 25 to 35%. &lt;/p&gt;

&lt;p&gt;In summary, &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use smaller image size for thumbnails&lt;/li&gt;
&lt;li&gt;use &lt;code&gt;webp&lt;/code&gt; format&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Details for Better User Experience
&lt;/h3&gt;

&lt;p&gt;I was able to get some lessons from this performance improvement experience. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The data usage amount is a very important element in user experience, especially for mobile environments. &lt;/li&gt;
&lt;li&gt;In order to detect performance issues in invisible areas we need to keep looking at developer tools. &lt;/li&gt;
&lt;li&gt;Sometimes simple solutions work better (like lazy loading in this case)&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>web</category>
      <category>frontend</category>
      <category>performance</category>
      <category>webp</category>
    </item>
    <item>
      <title>Develop safer web services with integrated testing</title>
      <dc:creator>Sol Lee</dc:creator>
      <pubDate>Tue, 12 Nov 2024 09:33:39 +0000</pubDate>
      <link>https://forem.com/solleedata/develop-safer-web-services-with-integrated-testing-3026</link>
      <guid>https://forem.com/solleedata/develop-safer-web-services-with-integrated-testing-3026</guid>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Originally from &lt;a href="https://techblog.woowahan.com/19509/" rel="noopener noreferrer"&gt;https://techblog.woowahan.com/19509/&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;I try not to use AI or translator&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  You'll learn
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;What's a unit test?&lt;/li&gt;
&lt;li&gt;What's an integration test? and why we need to use it.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;msw&lt;/code&gt; to mock the server response&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getByText&lt;/code&gt; and &lt;code&gt;getByTestId&lt;/code&gt;, which is more preferred?&lt;/li&gt;
&lt;li&gt;Differences between &lt;code&gt;userEvent&lt;/code&gt; and &lt;code&gt;fireEvent&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Why is it important to have separated, smaller tests instead of a huge, complex test code&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  How much do you trust your test code?
&lt;/h1&gt;

&lt;p&gt;Today, with the growth of web services, the importance of UI/UX is becoming more emphasize. This leads to increasing business logic to be handled in frontend. As a result, the importance of frontend testing is naturally growing too. Developers are applying various test methods to ensure the reliability of the code. Among them, the commonly written test method is &lt;strong&gt;Unit Testing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Below is an example of a **test code that verifies the behavior of the Terms and Conditions check box **and a function that returns whether the Terms and Conditions agree.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// checkAllRequiredTerms.test.ts&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkAllRequiredTerms()&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should return true only if all the checkboxes are checked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;terms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Term&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Term and conditions 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isChecked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Term and conditions 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isChecked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Term and conditions 3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isChecked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;checkAllRequiredTerms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;terms&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should return false is none of the checkboxes is checked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should return true if there is no required terms and conditions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// TermCheckBox.test.tsx&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;TermCheckBox /&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should call onChange and the check state should change if the checkbox is clicked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;isChecked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
    &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TermCheckBox&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;isChecked&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;term 4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(required) term 4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkBox&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeChecked&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// click the checkbox&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkBox&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkBox&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeChecked&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, in practice, it is very difficult to verify the behavior of the entire application with unit tests alone. Web applications operate with dozens of components and hundreds of functions. Therefore, even if the integrity of the function and single components is guaranteed, it is difficult to &lt;strong&gt;predict whether each unit performs the intended operation when combined and working together.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The following code is a component that activates the "Order" button once all the required terms and conditions downloaded from the server have been checked. It is difficult to be sure that the Order Button Section component is working properly using the previously written tests alone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The button is enabled if all the required terms and conditions are checked&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OrderButtonSection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Get API response and have it mapped &lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;terms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateTerms&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useFetchTermQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isCheckedAllRequiredTerms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;checkAllRequiredTerms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;terms&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;footer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TermList&lt;/span&gt; &lt;span class="nx"&gt;terms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;terms&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;updateTerms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;updateTerms&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;disabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isCheckedAllRequiredTerms&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/footer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My team used to focus on unit testing or verifying the operation of individual components. The verification of component and module interworking was conducted by the QA team or simply delayed. To enhance this, we decided to increase the reliability of automated tests by creating &lt;strong&gt;integrated tests that were closer to the flow of applications than just the operation of individual components.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  What is integration test?
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;An integration test is a test that verifies the way multiple modules or components of an application work together.&lt;/strong&gt; If the unit test verified the individual component or module, the integration test focuses on ensuring that all or some systems work together as expected.&lt;/p&gt;

&lt;p&gt;The code below is an integration test that tests the behavior of the OrderButtonSection component. The following test can verify that the "Order" button is activated when all the required terms and conditions downloaded from the server have been checked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// OrderButtonSection.test.tsx&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;OrderButtonSection /&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order button should be enabled if all required checkboxes are checked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;mockFetchTermsAPI&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;term 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;term 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;term 3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OrderButtonSection&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orderButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderButton&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeDisabled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// click the required checkboxes&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requiredCheckboxs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAllByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checkbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;checkBox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;checkBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkbox&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;requiredCheckboxs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkbox&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderButton&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeDisabled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integration tests are located between unit tests and End-to-End tests(E2E). Integration tests may have longer run times than unit tests, but they can validate a wider range of interactions. &lt;/p&gt;

&lt;p&gt;Integration tests may be somewhat less reliable than E2E tests because they do not cover all real-world scenarios, but they provide faster feedback in the early stages of development, and help us greatly in detecting problems between modules.&lt;/p&gt;

&lt;p&gt;The main goals of integration tests are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure that each function, component, and hooks are working together properly&lt;/li&gt;
&lt;li&gt;Verify that the API response are reflected in the application and that the application works correctly based on different server response cases (e.g., success, error, loading).&lt;/li&gt;
&lt;li&gt;Verifies that the application is working correctly when the user takes actions such as clicking or inputting a text. It not only tests the changes in the components that have interacted with the user, but also the overall scenario that occurs thereafter to verify the operation close to the actual environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Writing integration tests
&lt;/h1&gt;

&lt;p&gt;Let's have a look at examples of integration tests.&lt;/p&gt;

&lt;p&gt;The following is the &lt;code&gt;PointSection&lt;/code&gt; components, which is in charge of the points system. It calculates the available points through &lt;code&gt;getAvailablePoint&lt;/code&gt; function, which in turn uses the global state and the response from the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PointSection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;orderAmount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useOrderContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pointDiscountAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPointDiscountAmount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;usePointContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;couponDiscountAmount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCouponContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;point&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fetchPointBalance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderAmount&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Loader&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;availablePoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAvailablePoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;orderAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;couponDiscountAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pointBalance&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Points&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h3&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pointDiscountAmount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;availablePoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PointInput&lt;/span&gt;
        &lt;span class="nx"&gt;availablePoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;availablePoint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onChangePoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setPointDiscountAmount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the individual components and functions that compose &lt;code&gt;PointSection&lt;/code&gt; can easily be tested through unit tests, we should use integration tests to ensure its correctness in the actual application. In detail, this component has the following flow: &lt;code&gt;order amount&lt;/code&gt; -&amp;gt; &lt;code&gt;get the points&lt;/code&gt; -&amp;gt; &lt;code&gt;calculate the available points&lt;/code&gt; -&amp;gt; &lt;code&gt;render UI&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// getAvailablePoint.test.ts&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;getAvailablePoint()&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user has enough points: available points = order amount - coupon amount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;couponDiscountAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pointBalance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;availablePoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;getAvailablePoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paymentAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;couponDiscountAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pointBalance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;availablePoint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user has NO enough points&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// PointInput.test.tsx&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;PointInput /&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should be enabled if no enough points&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PointInput&lt;/span&gt; &lt;span class="nx"&gt;availablePoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeDisabled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should only accept numbers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should remove all input values if reset button is clicked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should not input number greater than the available points&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this test code, let's test several cases depending on properties and server responses.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does it get the global state correctly?&lt;/li&gt;
&lt;li&gt;Is the available points from the server response applied correctly?&lt;/li&gt;
&lt;li&gt;Are the order amount and the server response correctly reflected on the UI?&lt;/li&gt;
&lt;li&gt;Is the point discount amount updated when the user types the points to use?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's test the case in which the order amount is zero. We mock the &lt;code&gt;fetchPointBalance&lt;/code&gt;, which is the function that calls the API for the availbale points. Here, we'll use &lt;code&gt;jest.mock&lt;/code&gt;. Also, this will be place at the top of the test case so that the mocked function returns the mocked response at the beginning of the simulation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PointSection.test.tsx&lt;/span&gt;
&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./apis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requireActual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./apis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;fetchPointBalance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;pointBalance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="cm"&gt;/* imports */&lt;/span&gt;
&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;PointSection /&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should display nothing if the order amount is zero&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PointSection&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;QueryClientProvider&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MyContextProviders&lt;/span&gt; &lt;span class="nx"&gt;orderAmount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/MyContextProviders&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/QueryClientProvider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// check nothing is displayed&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChild&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you look at the &lt;code&gt;render&lt;/code&gt; method, there is a second parameter, a wrapper. This contains the providers that the &lt;code&gt;PointsSection&lt;/code&gt; component requires in the actual code: the &lt;code&gt;QueryClientProvider&lt;/code&gt; and the &lt;code&gt;MyContextProvider&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;If you don't prefer to have these providers in every render method, there is a workaround. We can use some kind of custom renderer that can be reused:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QueryClientProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@testing-library/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MyContextProviders&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./MyContextProviders&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RenderOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CustomOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;orderAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customRender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;orderAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;CustomOptions&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;RenderOptions&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;QueryClientProvider&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;queryOptions&lt;/span&gt; &lt;span class="p"&gt;})}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MyContextProviders&lt;/span&gt; &lt;span class="nx"&gt;orderAmount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;orderAmount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/MyContextProviders&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/QueryClientProvider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's test if &lt;code&gt;react-query&lt;/code&gt; works properly. We should take into consideration that the purpose of integration tests is testing the expected behavior of the application when handling the data from the API response, rather than testing the web API server itself. Therefore, &lt;strong&gt;we do not need to have the actual server running in the testing environment&lt;/strong&gt;. Instead, &lt;strong&gt;we can simply mock the server responses&lt;/strong&gt;. We'll use MSW (Mock Service Worker) for this purpose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PointSection.test.tsx&lt;/span&gt;

&lt;span class="c1"&gt;// mock the server response&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setupServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/point&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;pointBalance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resetHandlers&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nf"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order amount is zero&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PointsSection is not rendered if the order amount is zero&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;customRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PointSection&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;orderAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChild&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API response testing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;renders the points if points API fetching succeeded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;customRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PointSection&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;orderAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;사용 가능 포인트: 1,000원&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pro tip: In &lt;code&gt;testing-library&lt;/code&gt;, there is a list of preferences to be used. This is inline with the philosophy that it should be as close as possible to the actual user's behaviors:&lt;/li&gt;
&lt;li&gt;getByRole, getByLabelText, getByPlaceholderText, getByText, getByDisplayValue&lt;/li&gt;
&lt;li&gt;getByAltText, getByTitle&lt;/li&gt;
&lt;li&gt;getByTestId&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By using &lt;code&gt;server.use&lt;/code&gt;, we can overide the previous response and test several hypothtical cases. The following is the failed test case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;renders error message if points fetching is failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/point&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Internal Server Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;customRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PointSection&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;orderAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pro tip: If the global state or the mocked server response change and not initialized, other tests may get affected (and fail). If some tests fail due to unknown reasons, try to run the specific tests with &lt;code&gt;only&lt;/code&gt;. By resetting the global state or the mocked server response we may fix this problem. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's write test for user typing the points and if the component reacts properly. We can use either &lt;code&gt;userEvent&lt;/code&gt; or &lt;code&gt;fireEvent&lt;/code&gt; for this purpose. In my team, we use &lt;code&gt;userEvent&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should update the points amount when the user types the points&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;setupServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/point&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;pointBalance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;customRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PointSection&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;orderAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;available points: 1,000원&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;points to use: 0원&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;500&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;points to use: 500원&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pro tip: what's the difference between &lt;code&gt;fireEvent&lt;/code&gt; and &lt;code&gt;userEvent&lt;/code&gt;? &lt;code&gt;fireEvent&lt;/code&gt; produces forced events, while &lt;code&gt;userEvent&lt;/code&gt; imitates the user's action. The latter is closer to the actual user's behavior, and it is recommended by the &lt;code&gt;testing-library&lt;/code&gt; too.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should call onChangeHandler with fireEvent even if the input is disabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onChangeHandler&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;fireEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByPlaceholderText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onChangeHandler&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBecalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should not cal onChangeHandler if used with userEvent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onChangeHandler&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByPlaceholderText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onChangeHandler&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBecalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The Importance of Separating Tests
&lt;/h1&gt;

&lt;p&gt;Not every action can be tested at the highest accuracy (close to the real user's action), but we need some degree of intermediate tradeoff.&lt;/p&gt;

&lt;p&gt;As we've seen in the previous examples, &lt;code&gt;DiscountMethods&lt;/code&gt; required more mocking than &lt;code&gt;PointsSection&lt;/code&gt;. We will need much more server responses and external module mocking when testing the entire &lt;code&gt;OrderPage&lt;/code&gt;. As you can see, the more complex the component the more dependent to external libraries or system, and, therefore, the more complex and time-consuming the test code becomes. &lt;/p&gt;

&lt;p&gt;There are some ways to solve this problem. First, we can remove (refactor) repetitive tests. We can also separate them into smaller unit tests. By doing this we can leverage the effectiveness of the unit tests and the reliability of the integration test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// DO&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;CouponSection /&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;displays the right coupon amount, () =&amp;gt; { /* ... */ });
});

describe(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PointSection&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, () =&amp;gt; {
  it(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nx"&gt;displays&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;calculated&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, () =&amp;gt; { /* ... */ });
});

describe(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nf"&gt;getAvailablePoint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, () =&amp;gt; {
  it(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;enough&lt;/span&gt; &lt;span class="na"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;coupon&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, () =&amp;gt; { /* ... */ });
  it(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;enough&lt;/span&gt; &lt;span class="na"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, () =&amp;gt; { /* ... */ });
});
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the advantages of integration tests is that we can find **the side effects from altering the test code **relatively easily. Complex component tests, on the other hand, usually depend on external modules, so they can be harder to spot the side effects. For example, if we were to change the test code for &lt;code&gt;getAvailablePoints&lt;/code&gt; function, and something goes wrong with this, then other tests that use this function may get affected too. &lt;/p&gt;

&lt;h1&gt;
  
  
  Be loyal to the goal of the tests
&lt;/h1&gt;

&lt;p&gt;We've been through some aspects of the integration test. This kind of tests is ideal for verifying the correct functioning of one or more components together. They also help find the possible side effects and where they occured. &lt;/p&gt;

&lt;p&gt;However, having just one type of tests may not be ideal for testing the entire application. It is important to identify the testing strategies for each module or layer within the project. For this, I recommend to try out the different types of tests (unit tests, E2E, integration..) for each case. Don't forget the end goal of testing is to ensure the quality of the application. What's the best testing method for each situation is the question we should have in mind. &lt;/p&gt;

</description>
      <category>qa</category>
      <category>testing</category>
      <category>web</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Optimizing +200 Pipelines of a Monorepo</title>
      <dc:creator>Sol Lee</dc:creator>
      <pubDate>Sat, 21 Sep 2024 10:48:27 +0000</pubDate>
      <link>https://forem.com/solleedata/optimizing-200-pipelines-of-a-monorepo-1mgd</link>
      <guid>https://forem.com/solleedata/optimizing-200-pipelines-of-a-monorepo-1mgd</guid>
      <description>&lt;ul&gt;
&lt;li&gt;This article is the translation from the original article: &lt;a href="https://toss.tech/article/monorepo-pipeline" rel="noopener noreferrer"&gt;https://toss.tech/article/monorepo-pipeline&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  The Frontend Monorepo
&lt;/h1&gt;

&lt;p&gt;What's a monorepo? Mono + repository, &lt;strong&gt;monorepo in short, it is a single repository that manages multiple projects or libraries in one&lt;/strong&gt;. This makes it easier for code sharing and dependency management, and gives many developers a consistent development experience. Also, it is convenient to take CI/CD settings together because all projects are managed in a single repository.&lt;/p&gt;

&lt;p&gt;Toss frontend chapter manages over 200 services in a single monorepo. Contributors to this repo are about 50-60 devs and the average daily merged PR is over 60!&lt;/p&gt;

&lt;p&gt;With so many services being managed, monorepo became bigger and bigger, and in the end, it was impossible to simply clone repos. That's why I often use &lt;code&gt;filter=blob:none&lt;/code&gt; so that I don't download blob until I need it.&lt;/p&gt;

&lt;p&gt;At Toss, it takes only about 5 minutes from &lt;code&gt;git push&lt;/code&gt; to deployment even in such a big monorepo, and here's how we achieved it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secret 1: Parellel CI/CD
&lt;/h2&gt;

&lt;p&gt;In a monorepo, multiple services are often changed at the same time, and the service is built multiple times instead of once. At  Toss frontend chapter, &lt;strong&gt;&lt;code&gt;development&lt;/code&gt;, &lt;code&gt;staging&lt;/code&gt;, and &lt;code&gt;live&lt;/code&gt; environment builds are all done separately&lt;/strong&gt;. Therefore, we need (the number of changed services) x (the number of environments that need to be built) times.&lt;/p&gt;

&lt;p&gt;Here's a real example. If there are changes to three services:  shopping, transfer, and pedometer, we will build a total of nine (3 x 3). Assuming that each service takes 5 minutes to build on CI/CD, it takes 45 minutes in total to build sequentially. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;3 services x 3 environments x 5 minutes build time = 45 minutes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The more services that change like this, the longer it takes to build, and the more time the developer waits or does something else, the more context switching costs. 😢&lt;/p&gt;

&lt;p&gt;If you're using &lt;code&gt;yarn&lt;/code&gt;, you can run the build in parallel through the &lt;code&gt;yarn workspace signal run --jobs&lt;/code&gt; option. But &lt;strong&gt;if each service is built in a single computing environment, there's a problem of sharing the same computing resources as the CPU and memory&lt;/strong&gt;. &lt;strong&gt;In an environment with limited CPU and memory, increasing the number of parallels makes the process slower&lt;/strong&gt;. Eventually, if there are more build targets, it takes similar time as runnnign sequentially.&lt;/p&gt;

&lt;p&gt;If you set the pipeline to &lt;strong&gt;run all the builds in an independent environment&lt;/strong&gt;, you can root out these problems! At the frontend chapter, &lt;strong&gt;we used CircleCI's Dynamic Configuration.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ideally, no matter how many services there are, all services will be built in less than 6 minutes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;2 services: 1 minute (trigger pipeline) + 5 minutes (parallel pipeline)&lt;br&gt;
40 services: 1 minute (trigger pipeline) + 5 minutes (parallel pipeline)&lt;br&gt;
200 services: 1 minute (trigger pipeline) + 5 minutes (parallel pipeline)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you have unlimited amount of budget, you can save time indefinitely, but don't forget that realistically you need to control the maximum number of runners and construct a pipeline to meet the financial requirements.&lt;/p&gt;

&lt;p&gt;In the end, we can save about 5 times more time based on the two service changes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AS-IS: 2 services (6 distributed): 6 * 5 = 30 minutes&lt;br&gt;
TO-BE: 2 services (6 distributed): 1 + 5 = 6 minutes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can dynamically configure pipelines through Jenkins as well as CircleCI.&lt;/p&gt;

&lt;p&gt;With the capabilities of these various CI/CDs, it is most important to &lt;strong&gt;perform each pipeline in an independent computing environment.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Secret 2: Daily Docker Base Image
&lt;/h2&gt;

&lt;p&gt;Currently, the size of the monorepo is well over 40GB. It takes a very long time or a timeout error to check out to this repository every time on a runner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can we reduce checkout time without making an error in CI?&lt;br&gt;
**&lt;br&gt;
The way to do it is to **pre-replicate the monorepo&lt;/strong&gt;. If you start building in a pre-replicated environment, you won't have to download &lt;code&gt;git&lt;/code&gt; from the beginning, so you don't have to wait long! The contents of the monorepo are introduced into docker images in advance, and only the changed parts are newly received.&lt;/p&gt;

&lt;p&gt;This process can be done by writing &lt;code&gt;Dockerfile&lt;/code&gt; as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM docker.io/cimg/node:20.14.0
SHELL ["/bin/bash", "-c"]

WORKDIR ${HOME}/project

# `git clone` the 50 commit details first
RUN git clone --depth 50 $CIRCLE_REPOSITORY_URL

# Downloads up to 1000 commit history enough to calculate code changes
RUN git fetch --depth 1000 --force origin main

# Set the current environment to the main branch
RUN git checkout --force -B main

# Yarn install (optional)
RUN yarn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;We've booked a &lt;code&gt;Dockerfile&lt;/code&gt; like this to run every day at 7am&lt;/strong&gt;. Tt takes 36 minutes to complete the runner. This will show you that if you had to check out every time you build, it takes 36 more minutes each time!&lt;/p&gt;

&lt;p&gt;The docker image created in this way can be used using CircleCI's executor. CircleCI's executor refers to a setting where you can run a job with a specific image as below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: 2.1
excutors:
  my-executor:
    docker:
      - image: cimg/ruby:3.0.3-browsers
jobs:
  my-job:
    executor: my-executor
    steps:
      - run: echo "Hello executor!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At Toss, &lt;strong&gt;we manage Docker images using AWS ECR&lt;/strong&gt;, and &lt;strong&gt;to use this as a CircleCI executor, you can write it as follows.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;executors:
  toss_frontend_excutor:
    docker:
      - image: xxxxxx.dkr.ecr.ap-northeast-2.amazonaws.com/ci-base-image:latest
        aws_auth:
          aws_access_key_id: $AWS_ACCESS_KEY_ID
          aws_secret_access_key: $AWS_SECRET_ACCESS_KEY
        environment:
          TZ: 'Asia/Seoul'
          AWS_DEFAULT_REGION: ap-northeast-2
          ECR_REGISTRY: xxxxxx.dkr.ecr.ap-northeast-2.amazonaws.com
          ECR_REPOSITORY: ci-base-image
          ECR_LATEST_TAG: 'latest'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's use this executor in the jobs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  trigger-publish:
    executor: toss_frontend_excutor
    steps:
      - trigger-publish-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you check the time to download only the changed parts after the pre-received content, it has been shortened to 22 seconds.&lt;/p&gt;

&lt;p&gt;In this way, we were able to reduce the time required by 36 minutes to 22 seconds. For every job that needs a &lt;code&gt;git checkout&lt;/code&gt;, we could save about 36 minutes!&lt;/p&gt;

&lt;h2&gt;
  
  
  Secret 3: SSR Standalone Docker Image
&lt;/h2&gt;

&lt;p&gt;Lastly, the Standalone mode, which dramatically reduces SSR deployment time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Node File Trace allows you to extract only the dependencies you need for application runtime&lt;/strong&gt;. Therefore, when you create a build environment by gathering the minimum required JavaScript files, it becomes dramatically lighter, faster to deploy to Docker builds and K8S.&lt;/p&gt;

&lt;p&gt;By the way, Next.js offers this option as &lt;code&gt;output: 'standalone'&lt;/code&gt; in their next.config.js, too.&lt;/p&gt;

&lt;p&gt;However, this functionality was dependent on the &lt;code&gt;node_modules&lt;/code&gt; directory. Since we use &lt;code&gt;Yarn PnP&lt;/code&gt; at Toss, we couldn't use this functionality as it stored the deps in &lt;code&gt;.yarn/cache&lt;/code&gt; instead of &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;we used the Yarn PnP API to create a function with a role similar to the Next.js standalone function.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In reality, it's more complicated, but here's just pick of an idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function createSSRBundle(options: Options): Promise&amp;lt;SSRBundle&amp;gt; {
  const context = getSSRBundleContext(options);

  const [files, depFiles] = await Promise.all([
      getFilesForSSRBundle(context),
      getDependencyFiles(context),
  ]);

  const fileEntries = createFileEntries([ ...files, depFiles ], context);
  const pnpLoaderEntries = createPnPLoaderEntries(context);
  const pnpEntries = createPnPEntries([ depFiles ], context);

  const zip = new Zip();
  await addEntriesToZip(zip, [
    ...fileEntries,
    ...pnpLoaderEntries,
    ...pnpEntires
  ]);

  return zip.toStream();
}  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bundle.zip file created through the above function is available in the SSR Dockerfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:20.11.1-alpine3.19

WORKDIR /app
COPY ./bundle.zip ./bundle.zip

RUN unzip -i ./bundle.zip -d workspace

WORKDIR /app/workspace
CMD node -r ./.pnp.cjs --experimental-loader ./pnp.loader.mjs ./server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, with bundle.zip, you can run the SSR server anywhere, so you can run the server without any problems even without &lt;code&gt;git&lt;/code&gt; and &lt;code&gt;yarn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The size of the SSR Docker Image is also reduced from 4GB to about 200MB (optimized by 20 times).&lt;/p&gt;

&lt;p&gt;The reduced size of the SSR Docker Image also leads to higher speed of deployment, which reduces &lt;code&gt;PodInitialization&lt;/code&gt; time in Kubernetes. (PodInitialization refers to the process of pulling the docker image required to float the K8S pod)&lt;/p&gt;

</description>
      <category>monorepo</category>
      <category>optimization</category>
    </item>
  </channel>
</rss>
