<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Ressi  </title>
    <link>https://hini7.tistory.com/</link>
    <description>Ressi의 성장기  </description>
    <language>ko</language>
    <pubDate>Sat, 11 Jul 2026 11:42:14 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>희은w</managingEditor>
    <image>
      <title>Ressi  </title>
      <url>https://tistory1.daumcdn.net/tistory/3611365/attach/b851f3033f0e40d6b8b2a8c0567a8f7f</url>
      <link>https://hini7.tistory.com</link>
    </image>
    <item>
      <title>iOS Safari(WebView) z-index stacking 이슈</title>
      <link>https://hini7.tistory.com/277</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;iOS Safari 기반 WebView 환경에서는 transform 속성이 적용된 요소가 &lt;b&gt;별도의 compositing layer(GPU layer)&lt;/b&gt; 로 승격되면서, 기존의 DOM 기반 stacking context와 다른 렌더링 계층을 형성하는 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;z-index 값이 낮은 요소라도&lt;/li&gt;
&lt;li&gt;transform이 적용된 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 일반 레이어보다 &lt;b&gt;상위에 렌더링되는 현상&lt;/b&gt;이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;transform, opacity, will-change 등의 CSS 속성은 브라우저에서 &lt;b&gt;GPU compositing layer 생성 트리거&lt;/b&gt;로 동작&lt;/li&gt;
&lt;li&gt;iOS Safari(WebKit)는 이 compositing layer를 &lt;b&gt;독립적인 stacking context보다 우선적으로 처리하는 경향&lt;/b&gt;이 있음&lt;/li&gt;
&lt;li&gt;결과적으로 DOM 상의 z-index 우선순위가 깨지는 것처럼 보이는 현상 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 관련 요소를 동일한 compositing layer 전략으로 맞추어 &lt;b&gt;렌더링 계층을 균일화&lt;/b&gt;한다:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;.target {
  transform:translateZ(0);/* or translate3d(0,0,0) */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주요 레이어(겹침 관계가 있는 요소들)에 의도적으로 transform을 적용하여&lt;/li&gt;
&lt;li&gt;모든 요소를 GPU layer로 승격&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 브라우저가 동일한 기준에서 stacking을 수행하도록 유도&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주의사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;불필요한 GPU layer 증가로 인해 &lt;b&gt;메모리 사용량 및 성능 저하&lt;/b&gt; 가능&lt;/li&gt;
&lt;li&gt;특히 애니메이션이 많거나 요소가 많은 경우 &lt;b&gt;렌더링 비용 증가&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;가능한 경우 구조적으로 stacking context를 재설계하는 것이 더 바람직&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>웹 (WEB)/에러해결</category>
      <category>composition layer</category>
      <category>iOS z-index</category>
      <category>Safari</category>
      <category>safari compositing</category>
      <category>safari z-index</category>
      <category>Stacking</category>
      <category>webview</category>
      <category>z-index</category>
      <category>사파리</category>
      <category>웹뷰</category>
      <author>희은w</author>
      <guid isPermaLink="true">https://hini7.tistory.com/277</guid>
      <comments>https://hini7.tistory.com/277#entry277comment</comments>
      <pubDate>Mon, 27 Apr 2026 11:07:19 +0900</pubDate>
    </item>
    <item>
      <title>ChatGPT 로 React 상태 관리 공부해보기</title>
      <link>https://hini7.tistory.com/276</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;동료분이 공유주신 XL8 배휘동님의 세미나 공유용 PPT 를 보고, 상태 관리 관련해서 내가 어느 정도 알고 있는지 모르는 건 없는지 보고 싶은마음에 ChatGPT 를 이용해서 공부를 해봤다. 우선 아래는 4개의 이미지를 붙여넣고 예시 코드를 달라고 해서 정리한 내용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKMING/btsPAAkeNhs/zPIov00H6YJHKKMJFqe291/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKMING/btsPAAkeNhs/zPIov00H6YJHKKMJFqe291/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKMING/btsPAAkeNhs/zPIov00H6YJHKKMJFqe291/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKMING%2FbtsPAAkeNhs%2FzPIov00H6YJHKKMJFqe291%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;380&quot; height=&quot;324&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리한 내용&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;useState 수련생&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;useState 사용해 상태 업데이트&lt;/li&gt;
&lt;li&gt;props 전달 위주로 구성&lt;/li&gt;
&lt;li&gt;간단한 이벤트 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Counter() {
  const [count, setCount] = useState(0);

  return (
    &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;
      Count: {count}
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;의사결정 포인트&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;이건 상태로 관리해야 해?&quot;&lt;/li&gt;
&lt;li&gt;&quot;그냥 변수여도 되는 거 아닌가?&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;자주 겪는 문제&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;불필요한 상태 선언&lt;/li&gt;
&lt;li&gt;동기/비동기 상태 업데이트 개념 부족&lt;/li&gt;
&lt;li&gt;props vs state 혼동&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Props &amp;amp; 상태 탐색가&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태 끌어올리기 (lifting state)&lt;/li&gt;
&lt;li&gt;컴포넌트 간 상태 공유 고민&lt;/li&gt;
&lt;li&gt;복잡한 이벤트 조합 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;function Parent() {
  const [name, setName] = useState(&quot;&quot;);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Input name={name} onChange={setName} /&amp;gt;
      &amp;lt;Preview name={name} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

function Input({ name, onChange }: { name: string; onChange: (v: string) =&amp;gt; void }) {
  return &amp;lt;input value={name} onChange={(e) =&amp;gt; onChange(e.target.value)} /&amp;gt;;
}

function Preview({ name }: { name: string }) {
  return &amp;lt;p&amp;gt;Preview: {name}&amp;lt;/p&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;의사결정 포인트&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;이 상태는 어디에 있어야 할까?&quot;&lt;/li&gt;
&lt;li&gt;&quot;Props로 줄까, Context로 공유할까?&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;자주 겪는 문제&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;과도한 props drilling&lt;/li&gt;
&lt;li&gt;리렌더링 병목 발생&lt;/li&gt;
&lt;li&gt;컴포넌트 간 경계 불분명&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;상태 흐름 설계자&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Context, useReducer 사용 시작&lt;/li&gt;
&lt;li&gt;상태 흐름 구조화&lt;/li&gt;
&lt;li&gt;memo, useMemo, useCallback 시작&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const CounterContext = React.createContext(null);

function CounterProvider({ children }) {
  const [count, dispatch] = useReducer((state, action) =&amp;gt; {
    switch (action.type) {
      case 'INC':
        return state + 1;
      default:
        return state;
    }
  }, 0);

  return (
    &amp;lt;CounterContext.Provider value={{ count, dispatch }}&amp;gt;
      {children}
    &amp;lt;/CounterContext.Provider&amp;gt;
  );
}

function Child() {
  const { count, dispatch } = useContext(CounterContext);
  return (
    &amp;lt;button onClick={() =&amp;gt; dispatch({ type: 'INC' })}&amp;gt;
      {count}
    &amp;lt;/button&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;의사결정 포인트&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;Prop drilling이 너무 심해. Context로 풀까?&quot;&lt;/li&gt;
&lt;li&gt;&quot;이건 복잡한 로직이니 useReducer 써야 할까?&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;자주 겪는 문제&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성급한 성능 최적화 (memo 남용)&lt;/li&gt;
&lt;li&gt;Context 남용 (전역처럼 사용)&lt;/li&gt;
&lt;li&gt;로컬로 충분한 상태도 전역화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본기 마스터&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 상태 관리 도구 숙련 (useState, useReducer, Context, Recoil, Zustand 등)&lt;/li&gt;
&lt;li&gt;목적에 맞는 도구 선택&lt;/li&gt;
&lt;li&gt;성능 최적화, 리팩토링 설계 능력&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;// 상태를 slice별로 쪼개서 useContext, useMemo, useReducer 조합
// 또는 필요에 따라 Zustand 등의 외부 상태 라이브러리 활용

// ex: Zustand
import { create } from 'zustand';

const useStore = create((set) =&amp;gt; ({
  user: null,
  setUser: (user) =&amp;gt; set({ user }),
}));

function Profile() {
  const user = useStore((state) =&amp;gt; state.user);
  const setUser = useStore((state) =&amp;gt; state.setUser);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{user?.name}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setUser({ name: '렛시' })}&amp;gt;Set User&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;의사결정 포인트&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;어떤 접근 방식이 이 시나리오에 가장 적절한가?&quot;&lt;/li&gt;
&lt;li&gt;&quot;지금 memoization은 실질적인 이득이 있는가?&quot;&lt;/li&gt;
&lt;li&gt;&quot;유지보수성과 성능 사이에서 최적 구조는?&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;자주 겪는 문제&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오버엔지니어링&lt;/li&gt;
&lt;li&gt;상태 구조/결정 문서화 부족&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 내용을 바탕으로 문제를 내달라고 부탁했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKnnv5/btsPAlOrEGd/OKlPUBWzwJMY9kK8oQjRKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKnnv5/btsPAlOrEGd/OKlPUBWzwJMY9kK8oQjRKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKnnv5/btsPAlOrEGd/OKlPUBWzwJMY9kK8oQjRKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKnnv5%2FbtsPAlOrEGd%2FOKlPUBWzwJMY9kK8oQjRKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;428&quot; height=&quot;127&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  문제: 유저 대시보드 앱&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너는 대시보드 화면을 개발 중이야. 구조는 아래와 같아:&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;&amp;lt;Dashboard&amp;gt;
├── &amp;lt;UserProfile /&amp;gt;         // 유저 이름, 프로필 이미지
├── &amp;lt;UserStats /&amp;gt;           // 유저 포인트, 활동 통계 등
└── &amp;lt;UserPosts /&amp;gt;           // 유저가 작성한 게시글 리스트
       └── &amp;lt;PostItem /&amp;gt;     // 각각의 게시글 (좋아요, 댓글 등 포함)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  조건&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;페이지에 진입하면 &lt;b&gt;/api/me&lt;/b&gt; 에서 유저 정보(id, name, profileImage)를 fetch해서 사용함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;/api/posts?userId=xxx&lt;/b&gt; 에서 해당 유저의 게시글 목록을 fetch.&lt;/li&gt;
&lt;li&gt;UserProfile, UserStats, UserPosts는 각각 별도 파일로 분리된 컴포넌트.&lt;/li&gt;
&lt;li&gt;PostItem에서 유저 이름을 표시할 일이 있다. (작성자: 렛시 이런 느낌)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;❓질문: 상태를 어떻게 관리하는 게 가장 효율적일까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유저 정보 상태와 게시글 상태를 어디서 관리할래?&lt;/li&gt;
&lt;li&gt;어떤 상태 관리 도구를 쓸래? (useState, Context, 외부 라이브러리 등 중 선택)&lt;/li&gt;
&lt;li&gt;어떤 방식이 성능, 유지보수, 재사용 측면에서 효율적이라고 생각해?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  네가 답할 때 참고하면 좋은 포인트&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;User 정보는 자주 바뀌지 않아&quot; &amp;rarr; 어떤 상태 관리 전략이 적합할까?&lt;/li&gt;
&lt;li&gt;&quot;PostItem까지 유저 이름이 필요해&quot; &amp;rarr; props로 넘길지, 전역 상태로 둘지?&lt;/li&gt;
&lt;li&gt;&quot;컴포넌트가 서로 다른 파일에 흩어져 있어&quot; &amp;rarr; 정보 공유는 어떻게?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이 문제를 보고 적어본 나의 답변&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. 유저 정보 상태와 게시글 상태를 어디서 관리할래?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;게시물 리스트에서 api 호출 시 userId 가 필요하기 때문에, 유저 정보는 Dashboard 컴포넌트에서 UserContextProvider 를 만들어서 사용하면 좋을 것 같아. 유저 정보를 받아오는 건 인증이 필요해서 쿠키에 담긴 인증정보를 사용할 것으로 예상되는데, ServerSide Rendering 을 통해서 들어오는 요청의 브라우저 req.headers 를 참조해서 미리 유저 정보를 받아둘 수 있으면 다른 api 들을 호출하거나 ui 를 구성하는데에 걸리는 요소가 없을거야. 유저 정보는 유저의 인터랙션에 의해 쉽게 변하는 정보도 아니기 때문에 상위 컴포넌트의 Context 로 구성해도 크게 문제 되지 않을 것 같아.&lt;br /&gt;&lt;br /&gt;게시글 상태는 UserPosts 에서 다룰 것 같아. 다른 컴포넌트와는 상관이 없어서. 각각의 게시글을 나타내는 PostItem 이 props 로 게시글 관련 정보를 내려받는 구조가 될 거 같아. depth 가 한번 타고들어가는거라 props driling도 심하지 않아. 다만, PostItem 에서 좋아요, 댓글 을 눌러서 게시글의 정보를 업데이트 해야 할 때, api 를 요청하고 시간이 걸리니까 낙관적 업데이트를 하는게 좋을 것 같아서 게시글 각각에서 내려받은 좋아요나 댓글 정보를 state 의 initial 값으로 받아서 표시하고, 인터랙션 후에는 즉시 state 를 업데이트하고 api 를 요청하는 식으로 구현할거야. api 의 응답이 오면 이 state 를 유지할 지 / 오류를 표시할 지 결정해야 할 것 같아. 만약 react-query 같은 도구를 사용하고 있으면, 낙관적 업데이트로 하지 않고 게시글 리스트를 불러오는 api 를 호출하는 곳에서 사용하는 key 를 가지고 좋아요나 댓글 상태를 특정 id 의 게시글에서 업데이트 하는 방식을 적용할 수도 있을 것 같아. 뭐가 좋을지 고민이네.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. 어떤 상태 관리 도구를 쓸래?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;유저 정보는 Context. 나머지는 state 로 가능할 것 같아. react-query 를 쓰면 좋은데 꼭 쓸 필요는 없어.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3.&amp;nbsp; &quot;컴포넌트가 서로 다른 파일에 흩어져 있어&quot; &amp;rarr; 정보 공유는 어떻게?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Context 로 조합해서 묶거나 global 상태를 다루는 상태 관리 라이브러리를 사용할 수 있어.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GPT 씨의 피드백&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 좋았던 점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Context 위치와 유저 정보의 특성에 대한 판단&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;유저 정보는 자주 바뀌지 않고 상위 Context에 둬도 문제되지 않을 것 같다.&quot;&lt;/li&gt;
&lt;li&gt;정확해. 이런 식으로 불변에 가까운 데이터는 상위에 두고 Context로 배포하는 게 좋고, 렌더링 비용이 큰 데이터는 피하는 게 핵심이야.&lt;/li&gt;
&lt;li&gt;SSR에서 미리 유저 정보를 받아두는 전략까지 언급한 건 진짜  &lt;/li&gt;
&lt;li&gt;&amp;rarr; getServerSideProps or app/layout.tsx 수준에서 프리패칭하는 구조, 실무에서 매우 자주 씀.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 게시글은 로컬 상태로, props로 전달하는 전략&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;UserPosts 내에서 게시글 상태 관리, PostItem엔 props로 넘기기&quot;&lt;/li&gt;
&lt;li&gt;복잡도 최소화에 집중한 구조. depth 1이라 props로 충분하다는 판단도 합리적.&lt;/li&gt;
&lt;li&gt;굳이 Context나 글로벌 상태 안 쓰고 지역화 한 것도 잘했어.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 낙관적 업데이트 전략 고민&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;state 초기값으로 내려받고, optimistic update 적용 &amp;rarr; API 응답에 따라 rollback&quot;&lt;/li&gt;
&lt;li&gt;이건 확실히 상태 관리 고수만이 바로 떠올리는 방식이야.&lt;/li&gt;
&lt;li&gt;실전에서는 onMutate, onError, onSettled 패턴 (react-query) 이랑 딱 매칭됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 개선 포인트 &amp;amp; 대안 제안&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  PostItem 의 상태를 어디서 관리할지&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 구조도 좋지만, 다음도 고려 가능해:&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대안 1: PostItem 내부에서만 상태 변경 관리&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;tsx
복사편집
function PostItem({ post }: { post: Post }) {
  const [likes, setLikes] = useState(post.likes);
  const handleLike = () =&amp;gt; {
    setLikes((prev) =&amp;gt; prev + 1); // optimistic
    api.likePost(post.id).catch(() =&amp;gt; setLikes((prev) =&amp;gt; prev - 1)); // rollback
  };
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이건 지금 말한 방식이고 좋아.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대안 2: UserPosts에서 전체 posts 상태를 가진 뒤, PostItem은 콜백을 받아 업데이트만 요청&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;tsx
복사편집
function UserPosts() {
  const [posts, setPosts] = useState&amp;lt;Post[]&amp;gt;(...);

  const updatePost = (id, patch) =&amp;gt; {
    setPosts((prev) =&amp;gt;
      prev.map((p) =&amp;gt; (p.id === id ? { ...p, ...patch } : p))
    );
  };

  return posts.map((post) =&amp;gt; (
    &amp;lt;PostItem key={post.id} post={post} onUpdate={updatePost} /&amp;gt;
  ));
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 중앙에서 모든 게시글 상태를 관리&lt;/li&gt;
&lt;li&gt;단점: 조금 더 복잡해지고, 성능 이슈 있을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 결론&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체적인 구조 판단: 매우 훌륭해. 실전과도 90% 이상 일치.&lt;/li&gt;
&lt;li&gt;불변 데이터는 Context, UI에 민감한 상호작용은 지역 상태,&lt;/li&gt;
&lt;li&gt;이걸 자연스럽게 구분해냈다는 건 상태 관리 마스터에 거의 다 왔다는 뜻이야.&lt;/li&gt;
&lt;li&gt;react-query 언급도 좋았고, 꼭 안 써도 된다는 것도 상황 판단력 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피드백도 잘 주고 문제도 잘 내줘서 재밌었다. 다음에 또 이어서 해봐야지.&lt;/p&gt;</description>
      <category>웹 (WEB)/공부</category>
      <category>ChatGPT로공부</category>
      <category>ContextAPI</category>
      <category>react상태관리</category>
      <category>낙관적업데이트</category>
      <category>배휘동님상태관리</category>
      <category>상태관리라이브러리</category>
      <category>상태관리마스터</category>
      <author>희은w</author>
      <guid isPermaLink="true">https://hini7.tistory.com/276</guid>
      <comments>https://hini7.tistory.com/276#entry276comment</comments>
      <pubDate>Sun, 27 Jul 2025 07:45:13 +0900</pubDate>
    </item>
    <item>
      <title>우리 웹서비스는 어떻게 사용자에게 전달될까? (Frontend 가 보는 인프라)</title>
      <link>https://hini7.tistory.com/275</link>
      <description>&lt;div&gt;
&lt;div data-message-model-slug=&quot;gpt-4o&quot; data-message-id=&quot;472dcf50-6534-4cb9-8841-2b64594a885e&quot; data-message-author-role=&quot;assistant&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image 2025년 7월 26일 오후 11_44_51.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/euaVji/btsPBGjtNs2/aW29JCCOuKy1YRkXTfe6y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/euaVji/btsPBGjtNs2/aW29JCCOuKy1YRkXTfe6y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/euaVji/btsPBGjtNs2/aW29JCCOuKy1YRkXTfe6y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeuaVji%2FbtsPBGjtNs2%2FaW29JCCOuKy1YRkXTfe6y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;372&quot; height=&quot;372&quot; data-filename=&quot;ChatGPT Image 2025년 7월 26일 오후 11_44_51.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h2 data-end=&quot;158&quot; data-start=&quot;111&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;ALB, Ingress, Node.js, Kubernetes 구조 쉽게 이해하기&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;235&quot; data-start=&quot;160&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;최근 입사하신 동료분의 질문에 섞여있는 &quot;ALB&quot;, &quot;Ingress&quot;, &quot;ECR&quot;, &quot;Kubernetes&quot; 같은 단어들을 제대로 이해하지 못하고 있음을 깨닫고 정리해봅니다. 우리 웹 서비스가 사용자에게 도달하는 흐름을 이해하면 이 단어들의 의미가 자연스럽게 연결됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;235&quot; data-start=&quot;160&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이 글은 프론트엔드 코드가 어떻게 배포되고 사용자 브라우저에 전달되는지 전체 흐름을 쉽게 정리한 내용입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;402&quot; data-start=&quot;389&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;전체 흐름 요약&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;402&quot; data-start=&quot;389&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;프론트엔드 코드는 Docker로 빌드되어 AWS의 Kubernetes에 배포되고, 사용자는 ALB &amp;rarr; Ingress &amp;rarr; Node.js 서버를 거쳐 서비스를 전달받습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;402&quot; data-start=&quot;389&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;402&quot; data-start=&quot;389&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;551&quot; data-start=&quot;529&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;사용자 &amp;rarr; 서버까지 요청 흐름&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;사용자가 웹사이트에 접속하면, 요청은 다음과 같은 경로를 따라 전달됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;580&quot; data-start=&quot;553&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;1. ALB를 지나 Kubernetes로 진입&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;747&quot; data-start=&quot;613&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;675&quot; data-start=&quot;613&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;사용자의 요청은 먼저 AWS ALB (Application Load Balancer) 로 전달됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;747&quot; data-start=&quot;676&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;ALB는 도메인 및 경로 기반으로 라우팅을 수행하며, HTTPS 인증서 처리를 담당합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;747&quot; data-start=&quot;676&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;다수의 서버에 트래픽을 분산시키고, 장애가 발생한 서버를 자동으로 제외합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;841&quot; data-start=&quot;812&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;2. Ingress (Kubernetes 내부 라우터)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;958&quot; data-start=&quot;843&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;서비스가 Kubernetes 클러스터 위에서 운영된다면, ALB는 외부에서 클러스터로 들어오는 관문 역할을 하며 클러스터 내부에는 또 다른 라우터인 Ingress가 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1083&quot; data-start=&quot;960&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1014&quot; data-start=&quot;960&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Ingress는 Kubernetes 내부에서 요청을 어떤 서비스로 전달할지 결정합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1083&quot; data-start=&quot;1015&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;/api 요청은 backend-service로, / 요청은 frontend-service로 라우팅됩니다.&lt;/span&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;ALB와 Ingress는 모두 트래픽을 분기하는 역할을 하지만, ALB는 클러스터 외부, Ingress는 클러스터 내부에 존재하며 역할이 분리되어 있습니다. ALB는&amp;nbsp;Kubernetes&amp;nbsp;내부&amp;nbsp;구조를&amp;nbsp;알지&amp;nbsp;못하기&amp;nbsp;때문에,&amp;nbsp;Ingress와&amp;nbsp;함께&amp;nbsp;사용되어야&amp;nbsp;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot; data-start=&quot;812&quot; data-end=&quot;841&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;3. Node.js 서버&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Ingress가&amp;nbsp;/&amp;nbsp;요청을&amp;nbsp;frontend-service로&amp;nbsp;전달하면,&amp;nbsp;해당&amp;nbsp;서비스가&amp;nbsp;배포된&amp;nbsp;Pod(실행&amp;nbsp;단위)&amp;nbsp;중&amp;nbsp;하나인&amp;nbsp;Node.js&amp;nbsp;서버로&amp;nbsp;요청이&amp;nbsp;전달됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;배포된 Pod 은 Github Actions를 통해 자동 빌드되는 Docker 이미지로 구성되어 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1261&quot; data-start=&quot;1235&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이어지는 질문들&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;빌드 및 배포 흐름&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;일반적인 흐름은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;GitHub에 코드 Push&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;GitHub Actions 실행&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Docker 이미지 빌드&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;AWS ECR에 이미지 저장&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Kubernetes에 배포&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;새로운 Node.js 서버 Pod가 실행됨&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;1334&quot; data-start=&quot;1263&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-end=&quot;2085&quot; data-start=&quot;2061&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;정적 파일 서빙만 필요하다면?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;b&gt;1. Nginx&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;143&quot; data-start=&quot;90&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Nginx는 전통적으로 정적 파일 서빙과 리버스 프록시 역할에 특화된 웹 서버입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;221&quot; data-start=&quot;144&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;정적 파일을 빠르게 전달하며, 요청에 따라 다른 서버(예: Node.js)로 트래픽을 전달하는 리버스 프록시 역할도 수행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;334&quot; data-start=&quot;222&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;많은 서비스에서 Node.js 앞단에 Nginx를 두어&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;334&quot; data-start=&quot;263&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;289&quot; data-start=&quot;263&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;정적 자산은 Nginx가 직접 서빙하고,&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;334&quot; data-start=&quot;292&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;API 요청 등 동적 처리는 Node.js로 전달하는 구조를 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;424&quot; data-start=&quot;335&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Node.js 서버가 직접 SSR과 정적 파일 서빙까지 모두 처리한다면, Nginx를 별도로 두지 않을 수도 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;&lt;b&gt;2. S3 + CloudFront&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;721&quot; data-start=&quot;616&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;S3는 AWS가 제공하는 저렴하고 안정적인 파일 저장 공간입니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;721&quot; data-start=&quot;667&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;721&quot; data-start=&quot;667&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;빌드된 프론트엔드 정적 파일(HTML, JS, CSS, 이미지 등)을 저장하는 데 적합합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;839&quot; data-start=&quot;722&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;CloudFront는 AWS의 글로벌 CDN(콘텐츠 전송 네트워크) 서비스로,&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;839&quot; data-start=&quot;776&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;839&quot; data-start=&quot;776&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;S3에 저장된 정적 파일을 전 세계에 분산된 엣지 서버에 캐싱해 사용자가 더 빠르게 접근할 수 있도록 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;901&quot; data-start=&quot;840&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이 조합은 서버 운영 부담 없이, 고성능으로 정적 파일을 배포할 수 있는 일반적인 방법입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;954&quot; data-start=&quot;902&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;보통 SPA(Single Page Application)나 정적 사이트에 많이 사용됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100.349%; height: 171px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 16.8604%; height: 19px;&quot;&gt;방식&lt;/td&gt;
&lt;td style=&quot;width: 34.0795%; height: 19px;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 24.7512%; height: 19px;&quot;&gt;단점&lt;/td&gt;
&lt;td style=&quot;width: 24.6577%; height: 19px;&quot;&gt;활용 예시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 57px;&quot;&gt;
&lt;td style=&quot;width: 16.8604%; height: 57px;&quot;&gt;Nginx + Node.js&lt;/td&gt;
&lt;td style=&quot;width: 34.0795%; height: 57px;&quot;&gt;정적 파일과 API 요청을 효율적으로 분리 가능.&lt;br /&gt;유연한 리버스 프록시 설정&lt;/td&gt;
&lt;td style=&quot;width: 24.7512%; height: 57px;&quot;&gt;서버 운영과 관리 필요&lt;/td&gt;
&lt;td style=&quot;width: 24.6577%; height: 57px;&quot;&gt;SSR 앱 + 정적 파일 빠른 서빙이 필요할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 16.8604%; height: 38px;&quot;&gt;Node.js 단독 서버&lt;/td&gt;
&lt;td style=&quot;width: 34.0795%; height: 38px;&quot;&gt;구조 단순화, SSR과 정적 파일 모두 처리 가능&lt;/td&gt;
&lt;td style=&quot;width: 24.7512%; height: 38px;&quot;&gt;고부하 시 서버 리소스 부담 가능&lt;/td&gt;
&lt;td style=&quot;width: 24.6577%; height: 38px;&quot;&gt;SSR 중심 앱, 단일 서버로 간단히 운영할 때&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 57px;&quot;&gt;
&lt;td style=&quot;width: 16.8604%; height: 57px;&quot;&gt;S3 + CloudFront&lt;/td&gt;
&lt;td style=&quot;width: 34.0795%; height: 57px;&quot;&gt;비용 효율적, 글로벌 CDN으로 빠른 응답&lt;br /&gt;서버리스 구조로 관리 용이&lt;/td&gt;
&lt;td style=&quot;width: 24.7512%; height: 57px;&quot;&gt;SSR 불가, 정적 파일만 서빙 가능&lt;/td&gt;
&lt;td style=&quot;width: 24.6577%; height: 57px;&quot;&gt;정적 SPA, 블로그, 마케팅 사이트 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;2467&quot; data-start=&quot;2451&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;주요 구성 요소 정리&lt;/span&gt;&lt;/h4&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 133px;&quot; border=&quot;1&quot; data-end=&quot;2816&quot; data-start=&quot;2469&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;2816&quot; data-start=&quot;2505&quot;&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;2544&quot; data-start=&quot;2505&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2511&quot; data-start=&quot;2505&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;ALB&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;2544&quot; data-start=&quot;2511&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;외부 요청의 관문. HTTPS 처리 및 서비스 라우팅&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;2582&quot; data-start=&quot;2545&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2555&quot; data-start=&quot;2545&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Ingress&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;2582&quot; data-start=&quot;2555&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Kubernetes 내부 요청 라우팅 담당&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;2672&quot; data-start=&quot;2631&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2637&quot; data-start=&quot;2631&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Pod&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;2672&quot; data-start=&quot;2637&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;(Frontend 관점에서) Docker 이미지 기반으로 실행되는 Node.js 서버&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;2711&quot; data-start=&quot;2673&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2683&quot; data-start=&quot;2673&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Node.js&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;2711&quot; data-start=&quot;2683&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;SSR 렌더링 또는 HTML/JS 응답 생성&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;2745&quot; data-start=&quot;2712&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2729&quot; data-start=&quot;2712&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;GitHub Actions&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;2745&quot; data-start=&quot;2729&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;CI/CD 자동화 도구&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;2774&quot; data-start=&quot;2746&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2752&quot; data-start=&quot;2746&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;ECR&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;2774&quot; data-start=&quot;2752&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;AWS Docker 이미지 저장소&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;2816&quot; data-start=&quot;2775&quot;&gt;
&lt;td style=&quot;height: 19px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;2784&quot; data-start=&quot;2775&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Docker&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot; data-end=&quot;2816&quot; data-start=&quot;2784&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;Node.js 앱을 패키징하여 실행 가능하도록 이미지로 만드는 도구&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;2832&quot; data-start=&quot;2823&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;마치며&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;3053&quot; data-start=&quot;2968&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;이 글은 인프라 지식이 많지 않은 상태에서 개념을 하나씩 정리하며 작성한 내용입니다. 부족한 부분이 있다면 댓글로 제보주세요!&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3053&quot; data-start=&quot;2968&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;읽어주셔서 감사합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;thread-bottom-container&quot;&gt;
&lt;div id=&quot;thread-bottom&quot;&gt;
&lt;div data-testid=&quot;composer-trailing-actions&quot;&gt;
&lt;div&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot; data-state=&quot;closed&quot;&gt;&lt;/span&gt;
&lt;div data-testid=&quot;composer-speech-button-container&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>웹 (WEB)/공부</category>
      <category>ALB</category>
      <category>ECR</category>
      <category>frontend</category>
      <category>Infra</category>
      <category>INGRESS</category>
      <category>k8s</category>
      <category>nginx</category>
      <category>nodejs</category>
      <category>pod</category>
      <category>s3+cloudfront</category>
      <author>희은w</author>
      <guid isPermaLink="true">https://hini7.tistory.com/275</guid>
      <comments>https://hini7.tistory.com/275#entry275comment</comments>
      <pubDate>Sat, 26 Jul 2025 23:54:53 +0900</pubDate>
    </item>
    <item>
      <title>useQuery의 isLoading이 SSR에서도 true가 될 수 있을까?</title>
      <link>https://hini7.tistory.com/274</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 동료분이 &quot;SSR 페이지에서 미리 데이터를 받아오는 경우 useQuery 의 isLoading 이 true 인 경우가 있어요?&quot; 라는 질문을 던졌는데, 이 질문에 대한 대답을 생각하다가 여러 의문이 생겨 글로 정리해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;용어 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서버사이드 : 웹서버의 서버 쪽 코드 실행&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 클라이언트사이드 : 웹서버의 클라이언트 쪽 코드 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SSR : ServerSideRendering&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CSR : ClientSideRendering&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서버 : api 를 제공하는 실제 서비스 서버&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시&amp;nbsp;코드:&amp;nbsp;SSR&amp;nbsp;+&amp;nbsp;useQuery&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 아래와 같은 코드를 주로 사용하고 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1745633217319&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Next.js 의 페이지 컴포넌트

export const getServerSideProps = async () =&amp;gt; {
  const data = await fetchData();
  return { props: { dehydratedState: dehydrate(queryClient) } };
};

const Page = () =&amp;gt; {
  const { data, isLoading } = useQuery(['key'], fetchData);

  if (isLoading) {
    return &amp;lt;Spin /&amp;gt;;
  }

  return &amp;lt;div&amp;gt;{data}&amp;lt;/div&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getServerSideProps 내부 로직은 웹서버(서버사이드)에서 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버사이드에서 서버의 데이터를 미리 받아오고, dehydrate 를 통해 React Query 캐시에 넣어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버사이드에서 미리 데이터를 넣어주기 때문에, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;클라이언트에서는&lt;span&gt; &lt;/span&gt;&lt;/span&gt;useQuery 의 반환값인 isLoading 이 항상 false 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이 코드가 의미는 없었던걸까?  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;반면 CSR의 경우는?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSR 페이지에서는 서버사이드에서 내려오는 데이터가 없기 때문에, useQuery는 클라이언트에서 처음으로 fetching 을 수행한다. 따라서 서버로부터 데이터를 불러오기 전에는 isLoading 값이 true 이다. 즉, isLoading 을 가지고 로딩 UI 를 보여주는 코드가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;SSR 페이지 간 이동 시의 동작은?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 어플리케이션의 다른 페이지로 이동할 때, Next.js 에서는 보통 Next Link 나 Next Router를 사용해서 이동한다.&lt;/p&gt;
&lt;pre id=&quot;code_1745636789154&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const Home = () =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Home&amp;lt;/h1&amp;gt;
      &amp;lt;Link href=&quot;/about&quot; prefetch={true}&amp;gt;
        Go to About
      &amp;lt;/Link&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745636805331&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect } from 'react';
import { useRouter } from 'next/router';

export const Home = () =&amp;gt; {
  const router = useRouter();

  useEffect(() =&amp;gt; {
    router.prefetch('/about');
  }, [router]);

  const goToAbout = () =&amp;gt; {
    router.push('/about');
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Home&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={goToAbout}&amp;gt;Go to About&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, SSR 페이지로 이동 시 prefetch 동작이 Next 의 버전에 따라 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 76px;&quot; border=&quot;1&quot; data-end=&quot;1654&quot; data-start=&quot;1222&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px; width: 16.9767%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 40.5814%;&quot;&gt;Next.js 12 이하&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 42.3256%;&quot;&gt;Next.js 13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1447&quot; data-start=&quot;1373&quot;&gt;
&lt;td style=&quot;height: 19px; width: 16.9767%;&quot; data-end=&quot;1391&quot; data-start=&quot;1373&quot;&gt;prefetch 동작&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 40.5814%;&quot; data-end=&quot;1417&quot; data-start=&quot;1391&quot;&gt;_next/data JSON 미리 받음&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 42.3256%;&quot; data-end=&quot;1447&quot; data-start=&quot;1417&quot;&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot; data-end=&quot;1589&quot; data-start=&quot;1529&quot;&gt;
&lt;td style=&quot;height: 19px; width: 16.9767%;&quot; data-end=&quot;1542&quot; data-start=&quot;1529&quot;&gt;클릭 시 응답 방식&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 40.5814%;&quot; data-end=&quot;1565&quot; data-start=&quot;1542&quot;&gt;클라이언트에서 hydrate&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 42.3256%;&quot; data-end=&quot;1589&quot; data-start=&quot;1565&quot;&gt;서버가 HTML을 렌더링&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 SSR 페이지에 대한 Next Link 가 있다면, 미리 해당 페이지의 서버 사이드 로직을 실행하여 pageProps 받아두었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;사담으로 이로 인해 이슈를 겪은 적도 있는데, 배포를 하면 이 pageProps를 받아오는 hash 값이 바뀌게 되어, 유저가 보고 있던 페이지의 Next LInk prefetch 가 모두 404 에러로그로 뜨는 경우가 있었다. 이 이슈를 마주한 후 &quot;Next LInk 로 되어 있으면 서버 사이드에서 실행할 로직을 미리 실행해두는구나&quot; 하고 생각하고 있었는데 Next 13 버전 이후에 이 동작이 바뀌었다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;9버전 ~ 12버전 : &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;SSR 페이지에 대한 Next&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Link 가 있다면, 서버 사이드 로직을 미리 실행하는 식으로 prefetch가 동작한다. &lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://nextjs.org/docs/pages/api-reference/components/link&quot;&gt;공식문서&lt;/a&gt; &lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://nextjs.org/docs/messages/prefetch-true-deprecated&quot;&gt;공식문서2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;13버전 : 기본적으로 prefetch 하지 않고, SSR 페이지로 이동 시 서버 사이드가 동작하여 HTML을 렌더링한다. &lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#2-prefetching&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;prefetch 설정을 통해 켤 수 있다.&lt;/li&gt;
&lt;li&gt;개발모드에서는 prefetch 가 실행되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전에 따라 prefetch 의 동작이 다를 수는 있지만, 어떤 방식으로든 SSR 페이지들은 항상 데이터를 불러온 상태이므로 isLoading 이 false 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론적으로 useQuery의 isLoading은?&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2370&quot; data-start=&quot;2072&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 35%;&quot;&gt;상황&lt;/td&gt;
&lt;td style=&quot;width: 64.8837%;&quot;&gt;useQuery의 isLoading 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2259&quot; data-start=&quot;2197&quot;&gt;
&lt;td style=&quot;width: 35%;&quot; data-end=&quot;2229&quot; data-start=&quot;2197&quot;&gt;SSR 진입 (getServerSideProps)&lt;/td&gt;
&lt;td style=&quot;width: 64.8837%;&quot; data-end=&quot;2259&quot; data-start=&quot;2229&quot;&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2314&quot; data-start=&quot;2260&quot;&gt;
&lt;td style=&quot;width: 35%;&quot; data-end=&quot;2284&quot; data-start=&quot;2260&quot;&gt;CSR 진입 (클라이언트 렌더링)&lt;/td&gt;
&lt;td style=&quot;width: 64.8837%;&quot; data-end=&quot;2314&quot; data-start=&quot;2284&quot;&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2370&quot; data-start=&quot;2315&quot;&gt;
&lt;td style=&quot;width: 35%;&quot; data-end=&quot;2340&quot; data-start=&quot;2315&quot;&gt;SSR 페이지 간 이동 (13버전 이후)&lt;/td&gt;
&lt;td style=&quot;width: 64.8837%;&quot; data-end=&quot;2370&quot; data-start=&quot;2340&quot;&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR에서는 항상 false 이고, CSR 에서는 true 이다. 따라서, SSR 로 구성된 페이지에서 서버사이드에서 불러오는 서버 데이터의 경우 isLoading 코드를 작성하지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보면&amp;nbsp;당연한&amp;nbsp;사실이지만,&amp;nbsp;미처&amp;nbsp;의식하지&amp;nbsp;못했을&amp;nbsp;때는&amp;nbsp;관성적으로&amp;nbsp;isLoading에&amp;nbsp;대한&amp;nbsp;코드를&amp;nbsp;작성해왔던&amp;nbsp;것&amp;nbsp;같다.&lt;br /&gt;이번에 동료의 질문을 계기로 useQuery와 SSR의 관계를 다시 들여다보면서, 자연스럽게&amp;nbsp;Next.js의&amp;nbsp;prefetch&amp;nbsp;동작&amp;nbsp;변화까지&amp;nbsp;살펴보게&amp;nbsp;되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 (WEB)/공부</category>
      <category>isloading</category>
      <category>Next.js</category>
      <category>next.js 13버전</category>
      <category>next.js prefetch</category>
      <category>prefetch</category>
      <category>reactquery</category>
      <category>SSR</category>
      <category>ssr에서 isloading</category>
      <category>useQuery</category>
      <author>희은w</author>
      <guid isPermaLink="true">https://hini7.tistory.com/274</guid>
      <comments>https://hini7.tistory.com/274#entry274comment</comments>
      <pubDate>Sat, 26 Apr 2025 12:24:42 +0900</pubDate>
    </item>
    <item>
      <title>[브라우저] 인터넷 상의 클라이언트 - 서버 통신 과정</title>
      <link>https://hini7.tistory.com/273</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;내가 개발하는 코드가 어떻게 서버와 통신하고 그 결과를 받아오는 지에 대해 전체적인 그림을 그리지 못하고 지금껏 개발해온 것 같아요. &quot;브라우저는 HTTP 프로토콜 기반의 통신을 한다&quot;, &quot;TCP 통신은 3way handshake로 신뢰성을 확보한다.&quot;, &quot;브라우저는 TCP / IP 기반의 통신을 한다.&quot; 이런 말들 중 하나도 제대로 이해하고 있는 게 없더라구요. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;그래서 네트워크에 대해 잘 모르는 웹 개발자(&lt;s&gt;저&lt;/s&gt;)를 대상으로 클라이언트 - 서버 간의 통신 과정을 쉽게 이해하기 위한 글을 써보았어요 ✨&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;목차&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인터넷 상의 클라이언트 - 서버 통신 과정
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;DNS : 주소를 IP 주소로 변환하기&lt;/li&gt;
&lt;li&gt;HTTP 요청 : 요청 메시지 전송 과정&lt;/li&gt;
&lt;li&gt;HTTPS&lt;/li&gt;
&lt;li&gt;URL 구성&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 &quot;브라우저 설계&quot;와 &quot;Chrome 개발자도구 기능&quot; 등의  주제로 이어서 글을 작성해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네트워크 관점에서 클라이언트 - 서버 통신&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;795&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A2C3D/btsL4Ok7mtU/dBofxKI4HUQMldNVV0RGu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A2C3D/btsL4Ok7mtU/dBofxKI4HUQMldNVV0RGu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A2C3D/btsL4Ok7mtU/dBofxKI4HUQMldNVV0RGu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA2C3D%2FbtsL4Ok7mtU%2FdBofxKI4HUQMldNVV0RGu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;142&quot; height=&quot;128&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;795&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 컴퓨터로 어떤 웹 사이트를 접속할 때의 흐름을 천천히 들여다 보고자 합니다. chrome, safari 같은 &lt;i&gt;브라우저 &lt;/i&gt;라는 프로그램을 열면, 공통적으로 주소창 UI가 표시됩니다. 우리는 이 주소창에 사이트 주소를 입력하고 이동버튼이나 엔터를 눌러 해당 사이트로 이동합니다.그 후, 서버에서 응답을 받아오기까지의 과정을 네트워크 관점에서 먼저 살펴보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w8gt6/btsL4wLWUAG/2Qt8IkZUfnKAEFzosFphmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w8gt6/btsL4wLWUAG/2Qt8IkZUfnKAEFzosFphmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w8gt6/btsL4wLWUAG/2Qt8IkZUfnKAEFzosFphmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw8gt6%2FbtsL4wLWUAG%2F2Qt8IkZUfnKAEFzosFphmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;563&quot; height=&quot;388&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 서버 컴퓨터가 같은 공간에 있고 물리적인 케이블로 연결되어 있다면, 케이블을 통해 서버 컴퓨터에 있는 데이터를 내 컴퓨터로 바로 전송할 수 있을거에요. 하지만 실제로 서버 컴퓨터는 멀리 있기 때문에, 위의 그림과 같이 복잡한 과정으로 요청을 보내야 합니다. 이 과정을 하나씩 뜯어봅시다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5Is44/btsL5kYf5ue/4saGxqzz6yYfnFfkzB61lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5Is44/btsL5kYf5ue/4saGxqzz6yYfnFfkzB61lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5Is44/btsL5kYf5ue/4saGxqzz6yYfnFfkzB61lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5Is44%2FbtsL5kYf5ue%2F4saGxqzz6yYfnFfkzB61lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;349&quot; height=&quot;211&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;br /&gt;통신사에서 인터넷을 설치하러 오면 보통 2개의 기계를 제공하는데요. 하나는 라우터, 하나는 모뎀입니다. 라우터는 우리 집에 있는 여러 기기의 신호를 받아서 모뎀으로 전달해주는 역할을 합니다. 또한, 무선 통신의 기능도 제공해요. 모뎀만 있으면 오직 1개의 기기만 연결 가능하지만, 라우터가 있으면 여러 기기를 인터넷에 연결할 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIwFdW/btsL3zP79jJ/fg2BWWAsy0Q6ptYSYnawL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIwFdW/btsL3zP79jJ/fg2BWWAsy0Q6ptYSYnawL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIwFdW/btsL3zP79jJ/fg2BWWAsy0Q6ptYSYnawL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIwFdW%2FbtsL3zP79jJ%2Ffg2BWWAsy0Q6ptYSYnawL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;245&quot; height=&quot;161&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모뎀은 라우터를 통해 전달받은 디지털 신호를 아날로그 신호로 변환하는 역할을 합니다. 기존에 이렇게 변환된 신호는 전화선을 통해서 전송되었어요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RwaB2/btsL4wd6AoY/46Q6Pf29LpmxbLNYjLFJOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RwaB2/btsL4wd6AoY/46Q6Pf29LpmxbLNYjLFJOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RwaB2/btsL4wd6AoY/46Q6Pf29LpmxbLNYjLFJOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRwaB2%2FbtsL4wd6AoY%2F46Q6Pf29LpmxbLNYjLFJOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;254&quot; height=&quot;153&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 요즘에는 빛 신호로 변환해서 광케이블을 이용하여 전송하고 있답니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Q. 그럼 아주 먼 다른 국가는 어떻게 연결하고 있을까요?&lt;br /&gt;A. 궁금해서 찾아보니 대부분의 국가들이 해저 광케이블을 통해서 연결하고 있더라구요. (국제 인터넷 트래픽의 99% 이상) 해저 광케이블이 깔려있지 않은 국가나 인공위성끼리의 통신은 장거리 무선 통신을 이용하긴 하지만, 대부분은 물리적인 케이블이 깔려있다고 해요. 이유는 빛을 통한 데이터 전송이 전파보다 빠르고 안정적이기 때문이라네요.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIkx02/btsL3lEBO5W/HBkyXt5TmOTybtDKAVH9lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIkx02/btsL3lEBO5W/HBkyXt5TmOTybtDKAVH9lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIkx02/btsL3lEBO5W/HBkyXt5TmOTybtDKAVH9lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIkx02%2FbtsL3lEBO5W%2FHBkyXt5TmOTybtDKAVH9lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;372&quot; height=&quot;160&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;319&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;케이블을 통해 이동한 신호가 전달되는 ISP(Internet service provider)는 쉽게 말해 우리가 생각하는 &quot;통신사&quot; 입니다. 다른 통신사로 신호를 보내야 하기 때문에, ISP는 다른 ISP 로 신호를 보냅니다. ISP 에서는 다시 모뎀 - 라우터를 거쳐 서버 컴퓨터에 도달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지의 내용을 요약해보면, 내 컴퓨터에서 시작한 요청이 &quot;라우터&quot;, &quot;모뎀&quot; 이라는 물리적 장치를 거쳐서 ISP(통신사)로 이동한 뒤 다른 ISP(통신사)를 거쳐서 역순으로 다시 신호를 변환하면 서버 컴퓨터에 도달하게 되는 것이죠. 이러한 과정을 통해서 내 컴퓨터에서 내가 누른 주소창 입력 요청이 서버에까지 도달하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 흐름을 파악했으니 브라우저 프로그램이 요청을 보낼 때 발생하는 일을 자세히 설명해볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DNS : 주소를 IP 주소로 변환하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;주소창에 사이트 주소를 입력하고 엔터를 눌러 해당 사이트로 이동합니다.&quot; 에서 일어나는 일 중, 주소창에 입력한 사이트 주소를 이용해 실제 서버 컴퓨터 주소를 찾는 과정을 설명해보려고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buB1F3/btsL38YWBPF/ep9KkpPgkoRwx1cwdJZIQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buB1F3/btsL38YWBPF/ep9KkpPgkoRwx1cwdJZIQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buB1F3/btsL38YWBPF/ep9KkpPgkoRwx1cwdJZIQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuB1F3%2FbtsL38YWBPF%2Fep9KkpPgkoRwx1cwdJZIQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;361&quot; height=&quot;103&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;365&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 주소창에 입력된 사이트 주소를 네트워크 상의 주소인 IP 주소로 변환해야 합니다. IP 주소는 0~255 사이에 있는 숫자 4개와 dot(.) 3개의 조합으로 이루어져 있습니다. 문자를 숫자로 바꾸기 위해 브라우저는 다음과 같은 과정을 거칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 먼저 내 컴퓨터의 hosts 파일을 참조하여 도메인과 연결된 IP 주소가 저장되어 있나 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 종종 개발 예제에서 /etc/hosts 파일을 수정하여 &lt;i&gt;127.0.0.1 localhost&lt;/i&gt; 라인을 추가하면, 로컬에서 켠 서버를 켜기 위해 주소 창에 127.0.0.1 이 아닌 localhost 라는 문자열을 입력하면 된다는 안내를 보게 됩니다. 이 hosts 파일이 도메인 변환 시 가장 먼저 참조하게 되는 파일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 브라우저 캐시를 참조해서 도메인이 있나 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번에서 hosts 파일을 참조하였으나, 요청하고자 하는 도메인의 IP 정보가 없다면 브라우저 캐시를 참조해서 이전에 해당 도메인을 통해 이동한 적이 있나 확인합니다. 있다면 해당 IP 주소를 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg7Qqf/btsL3PFi8An/RX6zK3JI7KxUiKyi34bUe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg7Qqf/btsL3PFi8An/RX6zK3JI7KxUiKyi34bUe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg7Qqf/btsL3PFi8An/RX6zK3JI7KxUiKyi34bUe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg7Qqf%2FbtsL3PFi8An%2FRX6zK3JI7KxUiKyi34bUe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;633&quot; height=&quot;190&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;chrome 브라우저를 사용하고 있다면, 주소창에 chrome://net-internals/#dn 를 치고 들어가면 브라우저에 캐싱되어 있는 도메인의 IP 주소를 확인할 수 있어요.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. localDNS (ISP의 DNS)를 참조해서 도메인이 있나 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ISP(통신사)들은 각자 Domain Name System 을 가지고 있습니다. 이 시스템을 이용하여 현재 입력한 도메인이 있는지 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 재귀적으로 Root DNS 를 찾아갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(Root)&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;┌───────┼────────┐&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;br /&gt;&amp;nbsp; .com&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;.org&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;.net&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;br /&gt;&amp;nbsp;google.com&amp;nbsp; &amp;nbsp; &amp;nbsp;wikipedia.org&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;example.net&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;br /&gt;&amp;nbsp;&lt;a href=&quot;http://www.google.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://www.google.com&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS는 계층적인 tree 구조로 되어 있습니다. 도메인의 IP 정보를 발견하지 못했다면, 계속 상위 DNS 로 올라가면서 도메인의 정보를 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 브라우저는 서버의 인터넷 상 주소를 획득하여 어디로 요청을 보내야 할지 파악했습니다. 그럼 이제 이 요청을 어떤 형태로 어떻게 보낼 건지에 대해 알아봅시다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;HTTP 요청 : &amp;nbsp;요청 메시지 전송 과정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 서버에 보내는 요청은 어떤 형태로 서버에 전송될까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CaI9E/btsMbsQZblB/JkARKtllg5Q70fJyuzZQkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CaI9E/btsMbsQZblB/JkARKtllg5Q70fJyuzZQkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CaI9E/btsMbsQZblB/JkARKtllg5Q70fJyuzZQkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCaI9E%2FbtsMbsQZblB%2FJkARKtllg5Q70fJyuzZQkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;443&quot; height=&quot;119&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 가능한 통신의 형식은 위와 같이 다양합니다. 하지만 주로 HTTP 방식을 사용하기 때문에 HTTP 통신에 대해서 살펴보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2774&quot; data-origin-height=&quot;1232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vic93/btsMcrXNM1N/FN49Gk3DaYFzW6rKe4DR5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vic93/btsMcrXNM1N/FN49Gk3DaYFzW6rKe4DR5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vic93/btsMcrXNM1N/FN49Gk3DaYFzW6rKe4DR5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvic93%2FbtsMcrXNM1N%2FFN49Gk3DaYFzW6rKe4DR5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;741&quot; height=&quot;329&quot; data-origin-width=&quot;2774&quot; data-origin-height=&quot;1232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 브라우저는 보내고 싶은 데이터를 HTTP 요청의 형태로 만듭니다. 그리고는 운영체제 프로그램이 다루는 네트워크 서비스를 이용하기 위해 운영체제가 제공하는 &lt;b&gt;소켓의 인터페이스를 사용&lt;/b&gt;합니다. 브라우저는 운영체제에서 제공하는 소켓 인터페이스 중 send 라는 함수를 호출해서 만들어둔 HTTP 요청을 운영체제로 보냅니다. 운영체제의 네트워크 파트 중 TCP 스택이 소켓으로부터 해당 요청을 전달받습니다. TCP 스택은 전달받은 HTTP 요청 데이터에 다른 정보를 조금 더 추가해서 캡슐화 한 다음 IP 스택으로 보냅니다. IP 스택에서는 전달받은 데이터에 또 한번 다른 정보를 추가해서 캡슐화 한 다음 NIC(Network Interface Controller) 라는 물리적인 통신 장치로 요청을 보냅니다. NIC 는 이제 라우터로 신호를 전송하고 그 다음부터는 앞서 배웠던 흐름으로 신호가 전달됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 에서의 캡슐화를 먼저 살펴봅시다. TCP 는 데이터의 &lt;b&gt;신뢰성을 보장&lt;/b&gt;하면서 데이터를 쪼개고 헤더를 붙여 세그먼트라는 단위로 만듭니다.&amp;nbsp;IP는 세그먼트를 받아 헤더를 붙이고 패킷이라는 단위로 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpwAYC/btsL4MViF8K/FPbDKZ51pYXnw2BERkqj5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpwAYC/btsL4MViF8K/FPbDKZ51pYXnw2BERkqj5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpwAYC/btsL4MViF8K/FPbDKZ51pYXnw2BERkqj5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpwAYC%2FbtsL4MViF8K%2FFPbDKZ51pYXnw2BERkqj5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;229&quot; height=&quot;146&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 IP 스택에 넘어 온 데이터가 하나의 패킷에 넣을 수 있는 데이터 단위인 MTU (Maximum Transmission Unit) 보다 크다면 어떻게 할까요? 우선 IP 스택은 패킷을 만들어서 보내려고 하고, 라우터 측에서 Fragment (패킷을 조각내는 것) 을 진행합니다.  따라서 라우터는 패킷을 전달하는 것 뿐만 아니라 계산을 추가로 해야하는 부담이 발생하고, 쪼개진 패킷 중 첫번째 패킷에 포트 정보가 담겨 있기 때문에 해당 패킷이 손실되면 전체 패킷을 다시 보내야 합니다. 이렇게 IP 계층은 신뢰성을 보장하지 않기 때문에, TCP 계층에서 이를 해결해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b42hXB/btsMcSgqFKd/dgBGI837IrqNZkIGcIxmuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b42hXB/btsMcSgqFKd/dgBGI837IrqNZkIGcIxmuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b42hXB/btsMcSgqFKd/dgBGI837IrqNZkIGcIxmuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb42hXB%2FbtsMcSgqFKd%2FdgBGI837IrqNZkIGcIxmuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;611&quot; height=&quot;351&quot; data-origin-width=&quot;1588&quot; data-origin-height=&quot;912&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 가 미리 MTU 에 들어갈 만한 크기로 데이터를 쪼개는 작업을 합니다. 이 때, MSS (Maximum Segment Size) 라는 최대 세그먼트 크기는 IP 헤더와 TCP 헤더의 크기를 모두 고려해서 MTU 가 넘지 않도록 데이터 크기를 조정하여 결정합니다. 미리 IP 계층에서 fragment를 하지 않을 법한 크기로 Data 를 쪼개서 세그먼트화 하는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2428&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt8nh6/btsMcGAoGSE/NiIZvW1si6AJhY8jZ5GMIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt8nh6/btsMcGAoGSE/NiIZvW1si6AJhY8jZ5GMIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt8nh6/btsMcGAoGSE/NiIZvW1si6AJhY8jZ5GMIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt8nh6%2FbtsMcGAoGSE%2FNiIZvW1si6AJhY8jZ5GMIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2428&quot; height=&quot;792&quot; data-origin-width=&quot;2428&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 에는 이런 헤더들을 붙여서 세그먼트를 만듭니다. 주요한 정보들만 표시해보았는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 요청을 보내는 출발지 주소와 요청이 도착해야 할 도착지 주소를 표시하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 보내는 세그먼트가 같은 요청의 몇번째 세그먼트인지를 나타내는 index를 표시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 설명할 3way handshake 라는 과정에 사용되는 flag bit 를 저장하는 공간도 있구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 한번에 받을 수 있는 데이터 크기를 알려주기 위한 데이터도 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 손상없이 왔는지를 체크할 수 있는 데이터도 추가해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;1158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/skmYA/btsMbaCRVyl/Z0g7xK9BsgAXokrVeyTnEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/skmYA/btsMbaCRVyl/Z0g7xK9BsgAXokrVeyTnEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/skmYA/btsMbaCRVyl/Z0g7xK9BsgAXokrVeyTnEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FskmYA%2FbtsMbaCRVyl%2FZ0g7xK9BsgAXokrVeyTnEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;603&quot; height=&quot;453&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;1158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3way handshake는 전송된 데이터가 목적지까지 도달할 수 있을지에 관한 신뢰성을 보장하기 위한 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 SYN는 해줘. ACK는 확인. 이라고 생각하면 되는데요. 다음과 같은 과정을 거칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 클라이언트는 서버에 SYN, Seq = X 라는 요청을 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 연결을 요청하고, 데이터는 X 부터 시작할거라는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 서버는 클라이언트에 SYN, Seq = Y | ACK, Ack = X+1 을 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; X 부터 시작할거라는 연결 요청은 잘 받았고 (ACK), 나도 연결 요청하는데 데이터는 Y부터 시작할거 SYN)라는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 클라이언트는 서버에 ACK, Ack = Y + 1 을 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; Y 부터 시작할거라는 연결 요청 잘 받았어 라는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 3번의 요청을 주고 받으면 연결이 성사되고 데이터가 목적지까지 도달할 수 있다는 신뢰성을 확보하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;HTTPS&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS 는 HTTP + Secure(보안) 를 의미합니다. 여기서 보안이란 TLS(Transport Layer Security) 가 추가된 것으로,&lt;br /&gt;예전에는 SSL(Secure Socket Layer) 을 사용했다가 보안상 취약점이 발견되어 현재는 TLS 가 최신 보안 표준입니다. TLS 암호화는 TCP계층에 의해 데이터가 세그먼트화 되기 전에 이루어집니다. 대칭키 암호화의 과정을 거치나, 이 글에서 자세히 다룰 정도로 중요한 내용은 아닌 것 같아서 따로 참고할 글을 첨부해둡니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.tosspayments.com/resources/glossary/tls&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.tosspayments.com/resources/glossary/tls&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;URL 구성&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCF4Mt/btsL30zXTzG/3nxKOjzQxwrDbFXz5S7F0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCF4Mt/btsL30zXTzG/3nxKOjzQxwrDbFXz5S7F0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCF4Mt/btsL30zXTzG/3nxKOjzQxwrDbFXz5S7F0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCF4Mt%2FbtsL30zXTzG%2F3nxKOjzQxwrDbFXz5S7F0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;624&quot; height=&quot;175&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로토콜 : 통신 규약 (e.g.&amp;nbsp;http,&amp;nbsp;https,&amp;nbsp;ftp,&amp;nbsp;file,&amp;nbsp;mailto&amp;nbsp;등)&lt;/li&gt;
&lt;li&gt;호스트 : 서버&amp;nbsp;컴퓨터&amp;nbsp;주소&lt;/li&gt;
&lt;li&gt;경로 : 서버&amp;nbsp;컴퓨터&amp;nbsp;상의&amp;nbsp;리소스&amp;nbsp;위치&lt;/li&gt;
&lt;li&gt;파라미터 : 추가적인&amp;nbsp;정보를&amp;nbsp;주기&amp;nbsp;위한&amp;nbsp;key=value&amp;nbsp;형태의&amp;nbsp;파라미터&lt;/li&gt;
&lt;li&gt;fragment&amp;nbsp;(=anchor,&amp;nbsp;hash)&amp;nbsp;:&amp;nbsp;특정&amp;nbsp;위치로&amp;nbsp;이동하기&amp;nbsp;위한&amp;nbsp;책갈피&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Appendix&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://algoroot.tistory.com/110&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://algoroot.tistory.com/110&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@jeonbyeongmin/HTTP-%EB%81%9D%EC%9E%A5%EB%82%B4%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@jeonbyeongmin/HTTP-%EB%81%9D%EC%9E%A5%EB%82%B4%EA%B8%B0&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;긴 글 끝까지 읽어주셔서 감사합니다. &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;틀린 내용이 있다면 가감없이 지적해주세요  &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>웹 (WEB)/공부</category>
      <category>browser network</category>
      <category>DNS</category>
      <category>HTTP요청</category>
      <category>TCP IP</category>
      <category>라우터</category>
      <category>모뎀</category>
      <category>브라우저</category>
      <category>브라우저네트워크</category>
      <category>브라우저네트워크요청</category>
      <category>클라이언트서버</category>
      <author>희은w</author>
      <guid isPermaLink="true">https://hini7.tistory.com/273</guid>
      <comments>https://hini7.tistory.com/273#entry273comment</comments>
      <pubDate>Sun, 9 Feb 2025 15:56:30 +0900</pubDate>
    </item>
    <item>
      <title>playwright 테스트 전 필요한 것들 setup하기 (feat. beforeMount 사용)</title>
      <link>https://hini7.tistory.com/272</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBnvTW/btsJhTb2U5i/xW2hNeJgeqlBP3Cfk4IGbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBnvTW/btsJhTb2U5i/xW2hNeJgeqlBP3Cfk4IGbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBnvTW/btsJhTb2U5i/xW2hNeJgeqlBP3Cfk4IGbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBnvTW%2FbtsJhTb2U5i%2FxW2hNeJgeqlBP3Cfk4IGbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;260&quot; height=&quot;260&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;에러 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-router-dom를 사용하는 코드의 playwright 테스트 중 아래와 같이 useNavigate 관련 에러가 발생했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;882&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjeh5a/btsJhM5aRxs/ryhdji9cVH6F3k0A4GD7rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjeh5a/btsJhM5aRxs/ryhdji9cVH6F3k0A4GD7rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjeh5a/btsJhM5aRxs/ryhdji9cVH6F3k0A4GD7rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbjeh5a%2FbtsJhM5aRxs%2Fryhdji9cVH6F3k0A4GD7rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;686&quot; height=&quot;162&quot; data-origin-width=&quot;882&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단순 해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useNavigate는 react-router-dom 의 훅으로 서칭해보니 MemoryRouter 로 감싸주면 된다고 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1724803993468&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const component = await mount&amp;lt;HooksConfig&amp;gt;(
    &amp;lt;MemoryRouter initialEntries={['/']}&amp;gt;
      &amp;lt;Component /&amp;gt;
    &amp;lt;/MemoryRouter&amp;gt;,
    { hooksConfig }
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 mount 시에 단순히 감싸는 식으로 해결할 수도 있겠지만, 이 부분을 공통화 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공통화 시도 1&lt;/h3&gt;
&lt;pre id=&quot;code_1724804119203&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { MemoryRouter } from 'react-router-dom';
import { AuthContext } from '@contexts';

export const TEST_CT_USER_ID = 1;

export const testCtRender = (
  children: React.ReactElement,
  initialPath?: string
): React.ReactElement =&amp;gt; {
  return (
    &amp;lt;MemoryRouter initialEntries={[initialPath ?? '/']}&amp;gt;
      &amp;lt;AuthContext.Provider
        value={{
          user: {
            id: TEST_CT_USER_ID,
          },
          isLoading: false,
          isError: false,
          logout: () =&amp;gt; {},
        }}
      &amp;gt;
        {children}
      &amp;lt;/AuthContext.Provider&amp;gt;
    &amp;lt;/MemoryRouter&amp;gt;
  );
};

// 사용처
const component = await mount&amp;lt;HooksConfig&amp;gt;(
    testCtRender(&amp;lt;Component /&amp;gt;, '/some-path'),
    { hooksConfig }
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 단순히 render 해주는 함수를 별도로 구성하여 공통화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;testCtRender 함수는 MemoryRouter 로 현재 path 를 세팅하고, 필요한 Context 를 제공해주는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공통화 시도 2&lt;/h3&gt;
&lt;pre id=&quot;code_1724804279608&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { beforeMount as beforeMount17 } from '@playwright/experimental-ct-react17/hooks';
import { beforeMount as beforeMount18 } from '@playwright/experimental-ct-react/hooks';

interface HooksConfig {
  initialPath?: string;
}

function customBeforeMount(originalSetupApp: SetupAppCallback): void {
  beforeMount&amp;lt;HooksConfig&amp;gt;(async ({ App, hooksConfig }) =&amp;gt; {
    const { initialPath } = hooksConfig;
    return (
        &amp;lt;MemoryRouter initialEntries={[initialPath ?? '/']}&amp;gt;
          &amp;lt;AuthContext.Provider
            value={{
              user: {
                id: TEST_CT_USER_ID,
              },
              isLoading: false,
              isError: false,
              logout: () =&amp;gt; {},
            }}
          &amp;gt;
            &amp;lt;App /&amp;gt;
          &amp;lt;/AuthContext.Provider&amp;gt;
        &amp;lt;/MemoryRouter&amp;gt;
    );
  });
}

// playwright/index.tsx 에서 실행
customBeforeMount();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;playwright 의 beforeMount 기능을 이용하면 공통함수를 정의하지 않고도 모든 테스트 코드에 똑같은 코드를 적용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 : &lt;a href=&quot;https://playwright.dev/docs/test-components&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://playwright.dev/docs/test-components&lt;/a&gt;&lt;/p&gt;</description>
      <category>웹 (WEB)/에러해결</category>
      <category>beforemount</category>
      <category>error</category>
      <category>initialpath</category>
      <category>memoryrouter</category>
      <category>memoryrouter사용</category>
      <category>Playwright</category>
      <category>playwright에러</category>
      <category>react-router-dom</category>
      <category>useNavigate</category>
      <category>usenavigate에러</category>
      <author>희은w</author>
      <guid isPermaLink="true">https://hini7.tistory.com/272</guid>
      <comments>https://hini7.tistory.com/272#entry272comment</comments>
      <pubDate>Wed, 28 Aug 2024 09:28:48 +0900</pubDate>
    </item>
    <item>
      <title>vite 프로젝트에서 playwright 설정 시 type 관련 오류 해결</title>
      <link>https://hini7.tistory.com/271</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;vite 프로젝트에서 playwright 를 설정했을 때, typecheck 를 돌리면 spec.tsx 에서 타입 관련 에러가 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때는 `@playwright/test` 패키지를 설치해서 tsconfig의 compilerOptions.types 필드에 추가해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1722871501492&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;types&quot;: [&quot;node&quot;, &quot;vite/client&quot;, &quot;jest&quot;, &quot;@testing-library/jest-dom&quot;, &quot;@playwright/test&quot;],
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 (WEB)/에러해결</category>
      <category>@playwright/test</category>
      <category>Playwright</category>
      <category>playwright/test</category>
      <category>Type Error</category>
      <category>type 에러</category>
      <category>typecheck</category>
      <category>VITE</category>
      <author>희은w</author>
      <guid isPermaLink="true">https://hini7.tistory.com/271</guid>
      <comments>https://hini7.tistory.com/271#entry271comment</comments>
      <pubDate>Tue, 6 Aug 2024 00:25:39 +0900</pubDate>
    </item>
    <item>
      <title>[Git] revert commit is ignored when merge branch</title>
      <link>https://hini7.tistory.com/270</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;branchA 에 revert commit 이 포함되어 있고 main 에는 없을 때, branchA 에서 `git merge main` 을 수행하면 main 브랜치의 상태와 동일하게 revert commit 이 없는 것처럼 무시하는 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;merge recursive 전략&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git 에서는 merge 명령어 사용 시 기본 전략으로 recursive 를 사용하는데, 이 전략은 다음과 같이 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 두 커밋의 공통 조상 커밋을 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 각 브랜치의 변경 사항을 조합해 새로운 병합 커밋을 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 2번에서 충돌이 나면 사용자에게 해결을 위임합니다. (recursive 전략)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이슈 상황 자세히&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2022&quot; data-origin-height=&quot;1152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXfwMa/btsIbTkkIPI/pbu9TcOIM9FjKAbp65VMh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXfwMa/btsIbTkkIPI/pbu9TcOIM9FjKAbp65VMh0/img.png&quot; data-alt=&quot;브랜치 상황&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXfwMa/btsIbTkkIPI/pbu9TcOIM9FjKAbp65VMh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXfwMa%2FbtsIbTkkIPI%2Fpbu9TcOIM9FjKAbp65VMh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;331&quot; data-origin-width=&quot;2022&quot; data-origin-height=&quot;1152&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;브랜치 상황&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이슈가 생겼던 상황을 그대로 가져와봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main branch 에서 24.12.0 브랜치를 딴 후 react-query commit 을 포함하여 main 에 다시 머지했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 24.12.2 브랜치를 따고 해당 commit 을 revert 했는데, 이 때 24.12.2 브랜치를 어느 브랜치에서 따느냐에 따라서 결과가 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 24.12.0 브랜치에서 땄을 때.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main에서는 24.12.0 브랜치를 머지할 때 squash merge 로 머지하기 때문에 react-query commit 이 공통 조상 커밋이 될 수 없습니다. 따라서 main과 24.12.2 브랜치의 공통 조상 커밋은 react-query 코드 삽입 이전의 코드를 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 24.12.0 가 머지된 main 브랜치에서 땄을 때.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main과 24.12.2 브랜치의 공통 조상 커밋은 24.12.0 브랜치의 머지 커밋 이후가 될 것입니다. 따라서 공통 조상 커밋은 react-query 코드 삽입 이전의 코드를 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 차이로 24.12.2 브랜치에서 `git merge main` 수행 시 1)의 상황에서는 react-query 코드가 반영된 상태, 2)의 상황에서는 react-query 코드가 revert 된 상태 가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. `git merge -s ours main`으로 -s ours 옵션을 주면 병합 충돌을 무시하고 현재 브랜치의 내용을 우선시하여 병합할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이러한 충돌이 나지 않게 squash merge 가 완료된 브랜치에서 새로운 브랜치를 따야 revert 시 revert 커밋이 반영되지 않는 상황을 막을 수 있습니다.&lt;/p&gt;</description>
      <category>웹 (WEB)/에러해결</category>
      <category>-s ours</category>
      <category>git merge -s ours</category>
      <category>git merge recursive</category>
      <category>merge</category>
      <category>revert</category>
      <category>revert commit ignored</category>
      <category>revert commit is gone</category>
      <category>revert commit issue</category>
      <category>revert is ignored</category>
      <category>revert is not applied</category>
      <author>희은w</author>
      <guid isPermaLink="true">https://hini7.tistory.com/270</guid>
      <comments>https://hini7.tistory.com/270#entry270comment</comments>
      <pubDate>Wed, 26 Jun 2024 01:00:38 +0900</pubDate>
    </item>
    <item>
      <title>require() of ES Module someFile.js from someFile2.js not supported 이슈 해결</title>
      <link>https://hini7.tistory.com/269</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;에러&lt;/h3&gt;
&lt;pre id=&quot;code_1719130000081&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Error [ERR_REQUIRE_ESM]: require() of ES Module /app/node_modules/packageA/node_modules/packageB/dist/index.cjs.js from /app/node_modules/packageA/dist/cjs/hooks.js not supported.
index.cjs.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains &quot;type&quot;: &quot;module&quot; which declares all .js files in that package scope as ES modules.
Instead either rename index.cjs.js to end in .cjs, change the requiring code to use dynamic import() which is available in all CommonJS modules, or change &quot;type&quot;: &quot;module&quot; to &quot;type&quot;: &quot;commonjs&quot; in /app/node_modules/packageA/node_modules/packageB/package.json to treat all .js files as CommonJS (using .mjs for all ES modules instead).&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;packageA의 hooks.js 파일에서 packageB를 import 해서 사용하고 있고, 이 에러는 별도 저장소에서 packageA 를 import 해서 사용할 때 발생하였습니다. 에러를 해석해보면 &quot;packageA 내부의 node_modules에 있는 packageB 파일 중 index.cjs.js 는 ES Module 이라 require 문을 사용할 수 없다&quot;고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 파악 / 해결법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 .cjs.js 파일이면 common js 인데 왜 이 파일을 ES Module 파일이라고 하는걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 packageB가 type: module 로 정의되어 있기 때문이다. type: module 을 package.json 에 정의하면 해당 패키지의 모든 .js 파일은 esm 으로 처리되는데, cjs 로 빌드된 파일이기 때문에 require 문법이 파일 안에 들어있고 에러가 발생하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결하기 위해서 node에서 제공한 방법은 (위의 에러문의 2, 3번째 줄) packageB 를 빌드할 때 .cjs.js 가 아닌 .cjs 확장자로 빌드 파일을 저장하라고 한다&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(esm 은 .mjs로)&lt;/span&gt;. 이렇게 하게 되면 default export 와 named export 가 혼동되어 es module interupt 에러가 발생하는 경우가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째로는 type: module 을 제거하여 더이상 .js 파일들을 esm 으로 인식하지 않게 만드는 방식을 사용할 수 있다. 우선 첫번째 방식을 시도해보고 이슈가 생기면 두번째 방식으로 해결하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 (WEB)/에러해결</category>
      <category>esmodule is not supported</category>
      <category>index.cjs.js esmodule</category>
      <category>module package esm</category>
      <category>require is not supported</category>
      <category>require() of es module</category>
      <category>require() of es module somefile.js from somefile2.js not supported</category>
      <category>type:module</category>
      <author>희은w</author>
      <guid isPermaLink="true">https://hini7.tistory.com/269</guid>
      <comments>https://hini7.tistory.com/269#entry269comment</comments>
      <pubDate>Sun, 23 Jun 2024 17:17:23 +0900</pubDate>
    </item>
    <item>
      <title>어느 3년차 개발자의 회고록</title>
      <link>https://hini7.tistory.com/268</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_4181.JPG&quot; data-origin-width=&quot;2204&quot; data-origin-height=&quot;2204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZNhb8/btsHTjClpzG/HVOqFIaUdmlk15fUH1dVl1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZNhb8/btsHTjClpzG/HVOqFIaUdmlk15fUH1dVl1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZNhb8/btsHTjClpzG/HVOqFIaUdmlk15fUH1dVl1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZNhb8%2FbtsHTjClpzG%2FHVOqFIaUdmlk15fUH1dVl1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;313&quot; height=&quot;313&quot; data-filename=&quot;IMG_4181.JPG&quot; data-origin-width=&quot;2204&quot; data-origin-height=&quot;2204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼마 전 친한 동료 분이 블로그에서 저의 회고 글을 봤다고 말씀해주신 게 계기가 되어 오랜만에 회고를 작성해 봅니다. 그러다 보니 제목이 필요해서 자연스럽게 &amp;ldquo;3년차 개발자의 회고록&amp;rdquo; 이라고 써버렸는데, 언제 3년차가 되어 버린 건가 싶어요. 어쨌든 스스로 부담 주지 않고 가볍게 저에 대해서, 그리고 앞으로 어떻게 하고싶은지에 대해서 써보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반성의 시간&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;나의 자만에 대하여&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사족이지만 올해 연초에 토정비결을 봤었는데 첫 문장이 &amp;ldquo;분수에 맞게 살라&amp;rdquo; 였어요. 지금껏 제 실력보다 운이 좋아서 많은 일들이 잘 풀려왔는데, 그로 인해 제가 대단한 개발자인양 착각하고 있었던 것 같습니다. 일을 할 때 저의 말투나 질문 습관, 그 안에 숨어있는 생각들이 점점 자만으로 물들어가는 걸 느끼던 차였어요. 그만하라고 말해줄 무언가가 언젠가는 필요했을 거라고 생각하니, 올해 운세에서 본 말이 도리어 행운입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 단점보다 장점을 더 많이 생각하려고 하는 경향이 있어서 가끔 이렇게 단점을 써놓고 직시해야 밸런스가 맞춰저요. 스스로에게 너무 관대합니다. 신입 때 들었던 &amp;ldquo;신입임에도 리더십이 있다&amp;rdquo;는 칭찬은 저의 책임감을 키웠지만, 실력이 부족함에도 리더가 되고 싶은 그래서 실력이 뛰어난 사람에게 하게 되는 못난 질투심도 함께 키운 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 생각을 적기 부끄럽고 누군가 안 좋게 볼 까봐 걱정도 되지만 고치고 싶어 적어봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;말그릇&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 질문할 때 안 좋은 습관이 있어요. 진심으로 궁금해서 보다는 상대방에게 조언하기 위해서, 나올 법한 답을 이야기 흐름에 이용하기 위해서 질문들을 합니다. 이런 습관이 생기니 저도 저와 대화하기 싫더라구요. 그래서 괜히 주변 사람들과 거리를 두기도 했습니다. 누군가와 얘기를 나누고 싶다가도 상처를 줄까봐 말하기를 주저하는 사람이 되었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 의도를 가지고 말을 하면 대화가 힘들어지는 것 같아요. &amp;ldquo;선의 최대의 적은 선의이다.&amp;rdquo; 라는 말이 있듯 도움을 주려 하지 말고 그냥 대화를 해야겠다고 생각했습니다. 그러다보면 그 말이 누군가를 살릴 수도 있을까 하고 생각하면서요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;혼자서 개발할 수 없는 개발자 렛시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 내용 들에서는 개발자로서가 아닌 인간으로서 저에 대한 회고를 써보았다면, 이번에는 개발자 렛시의 단점을 써보려 합니다. 아직도 신입 개발자처럼 팀원들의 리뷰에 의존하고 있습니다. 이제 어느 정도 완전한 코드를 작성하고 내 생각에 대한 설득력까지 갖추어야 하는 단계라고 생각하는데, 쉽지 않네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 작성할 때는 물론이고, 논의 중에 입장이 명확히 정리가 되지 않아요. 실력은 대부분 경험에 따른 직관 이라는 말을 어디선가 본 적이 있는데 아직 경험이 부족해서 일까요? 의견을 주장하면서도 제가 저를 설득하기 위해 말이 길어짐을 느낍니다. 최고의 설득은 확신 인 것처럼, 스스로 확신을 가지고 주장을 할 줄 아는 사람이 되고 싶어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더해서 스스로 해결하기 어려워 보이는 문제를 만나면 잘하는 동료분들께 곧바로 의지해버리는 것도 더 깊게 공부할 수 있는 기회를 막아버리는 듯 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자잘한 단점들도 많지만 정말로 고치고 싶은 3가지만 적어봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다독임의 시간&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;나보다는 우리가 중요한&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혼자서만 알고 있는 것들을 떠안게 되는 상황을 몇 번 겪어보니 &amp;ldquo;본인만 아는 일이 없게 하자&amp;rdquo;는 목표가 생겼어요. 다른 사람과 알고 있는 지식수준을 맞춘다는게 당시에는 시간이 더 소모되는 것처럼 보이겠지만 결국엔 미래의 내가 휴가를 갈 수 있게, 아플 때 쉴수 있게 돕는 길인 것 같아요. 그래서 업무를 마치고 몰랐던 점이 있으면 문서화하거나 회의에서 공유하는 걸 놓치지 않으려고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;급해보이는 일이 있으면 동료를 도와주고 문제를 함께 보는 습관은 오늘의집에서 만든 것 같아요. 처음 입사하고 아무것도 모를 때 올린 질문 스레드에 댓글이 80여개가 달리는 순간부터 감사함과 동시에 소속감이 들었고 저도 누군가에게 도움이 될 수 있는 개발자가 되었습니다. 다른 사람들이 필요로 할 때 도움을 줄 수 있게 빠르게 일을 처리하고 여유로운 시간들을 남겨두려고 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가슴이 시키는 일을 하는 나&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년 앱 개발에 도전해봤을 때를 떠올려보면, 한동안 비슷한 일들을 하면서 지루해하고 있었던 것 같아요. 새로운 걸 해보고 싶다고 리더분께 여러 차례 찡찡댄 후에 저에게 기회가 왔습니다 ㅋㅋㅋ 저는 &amp;ldquo;어려울까?&amp;rdquo; 라는 생각을 하기 전에 &amp;ldquo;재밌을까?&amp;rdquo; 라는 질문이 먼저인 사람이라는 걸 이번 일로 깨닫게 되었습니다. 이렇게 고생하고서도 또 비슷한 기회가 찾아오면 별 고민없이 &amp;ldquo;할래요!&amp;rdquo; 라고 대답할 것 같거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;빠른 적응력&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그간 다른 팀에 잠시 파견을 가거나, 다른 지역에서 일을 하거나, 새로운 분야를 개발하는 등 환경이 바뀐 적이 종종 있었습니다. 이런 상황들을 겪으면서 저는 바뀐 환경에 꽤 빠르게 적응하는 사람이라는 걸 알게 되었어요. 애초에 지루한 걸 잘 못 견뎌서, 다양한 것들을 시도하는 것에 익숙합니다. 새로운 분야에 도착해서 내가 진짜 아무것도 모른다는 걸(내가 누구나 인정하는 초보란 것을) 인정하고 나면 편하게 모른다고 하고 질문할 수 있어서 오히려 마음이 편해요. 이런 성격이 절 더 도전적으로 만드는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자라는 직업을 가진 사람으로서 &amp;ldquo;새로운 일을 즐기면서 빠르게 적응해나가는&amp;rdquo; 라는 특징을 가진 건 자랑할 만한 일인 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어떤 개발자가 되고 싶은지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장단점을 나열해봤는데 사실 단점 중에는 그다지 고치고 싶지 않은 것도 있고, 꼭 고치고 싶은 것도 있습니다. 장점 중에서도 크게 장점이라고 생각되지 않는 것도 있고 계속 가지고 가야겠다고 생각되는 것도 있어요. 필요한 말들만 고르고 골라 적어볼게요. 이 글의 3문단 요약입니다 ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 욕심 이겠지만, 제 말에 아무도 상처받지 않았으면 좋겠어요. 사람을 살리고 희망을 주고 도움을 주는 좋은 말을 하고 싶습니다. 반대하는 말을 해도 어떠한 불순한 의도가 있지 않고 다같이 잘되기 위해서 라는 걸 모두가 느낄 수 있게, 일에서 만큼은 누구에게도 감정 섞인 말을 하지 않는 사람이 되고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 의견을 내거나 코드를 작성하고 나서 누구에게 질문이나 피드백을 받아도 당당하고 싶습니다. 물론 단시간에 의견을 내야 할 때는 어려울 수 있을 것 같아요, 그 부분에서는 주눅들지 않으려 합니다. 하지만 시간을 내어 말을 정리하거나 코드를 첫 작성부터 어느정도 완성도 있게 만드는 연습은 꼭 필요해보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 앞으로도 제가 하고싶은 일을 할 겁니다. 주변에서 누가 힘들다고 걱정해도 그냥 재밌어 보이면 할거에요. 지금까지 그런 일을 하면서 후회한 적이 한번도 없거든요 (하는 도중에는 잠시 후회합니다 ㅎㅎ). 힘든 만큼 성장할 거라고 믿으면서 또 힘듦을 즐기기 까지 하다니 놓치고 싶지 않은 장점이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;글을 마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 어떤 장단점을 가진 개발자일까? 지금 내가 그리고 있는 성장 그래프는 어느 정도 기울기를 가지고 있을까? 너무 궁금하지만 적어도 10년은 개발자로 살아봐야 이 질문들에 명확히 답할 수 있을 것 같아요 ㅋㅋㅋ 아직 반도 오지 않았네요. 아직 개발이 재밌어서 다행이라고 생각하면서 이만 줄입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>끄적이는 글/개발자로서 끄적이는 글</category>
      <category>개발자</category>
      <category>개발자글</category>
      <category>개발자회고</category>
      <category>개발회고</category>
      <category>버킷플레이스</category>
      <category>오늘의집</category>
      <category>회고</category>
      <category>회고글</category>
      <category>회고록</category>
      <author>희은w</author>
      <guid isPermaLink="true">https://hini7.tistory.com/268</guid>
      <comments>https://hini7.tistory.com/268#entry268comment</comments>
      <pubDate>Sat, 8 Jun 2024 17:04:34 +0900</pubDate>
    </item>
  </channel>
</rss>