<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>leave_a_comment.log</title>
        <link>https://velog.io/</link>
        <description>나도 성장하고파</description>
        <lastBuildDate>Sun, 25 Jan 2026 15:33:38 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>leave_a_comment.log</title>
            <url>https://velog.velcdn.com/images/leave_a_comment/profile/c7c5fe5f-d645-4ae7-9914-ffdae6c0004a/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. leave_a_comment.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/leave_a_comment" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[React Native] Sticky Header]]></title>
            <link>https://velog.io/@leave_a_comment/React-Native-Sticky-Header</link>
            <guid>https://velog.io/@leave_a_comment/React-Native-Sticky-Header</guid>
            <pubDate>Sun, 25 Jan 2026 15:33:38 GMT</pubDate>
            <description><![CDATA[<p>자사 서비스 개발을 하다 보니 Sticky Header UI를 구현할 일이 생각보다 많았다.
React Native에서는 성능 이슈가 쉽게 발생하는 만큼 리스트 출력에는 FlashList를 사용하며 최대한 최적화를 고려하고 있다.
이번 글에서는 FlashList + Sticky Header를 구현하면서 특히 애를 먹었던 부분들과, 그 과정에서 정리한 포인트들을 공유해보려고 한다.</p>
<h2 id="첫-번째-시도">첫 번째 시도</h2>
<p>FlashList에서 제공하는 <code>stickyHeaderIndices</code> 옵션을 활용해 Sticky Header를 구현해보았다.<br>하지만 예상과는 다른 문제가 발생했다.</p>
<h3 id="문제점">문제점</h3>
<p><code>stickyHeaderIndices</code>는 <code>renderItem</code>으로 렌더링되는 항목에만 적용되며<br><code>ListHeaderComponent</code>에는 사용할 수 없었다.</p>
<p>그 결과 리스트의 <strong>첫 번째 상품 아이템이 고정되는 현상</strong>이 발생했고<br>의도했던 Sticky Header와는 전혀 다른 <strong>어색한 UI</strong>가 만들어졌다.</p>
<h2 id="두번째-시도">두번째 시도</h2>
<p><strong><code>ListHeaderComponent</code>를 쓰지 말고 아예 Header를 <code>renderItem</code> 안으로 넣어버리자.</strong></p>
<p>이를 위해 리스트 데이터에 타입을 부여해 각 아이템이 어떤 역할을 하는지 구분하도록 설계했다.</p>
<ul>
<li><code>type: &#39;header&#39;</code> → Sticky Header로 사용될 영역</li>
<li><code>type: &#39;item&#39;</code> → 일반 상품 아이템</li>
</ul>
<p>이렇게 하면 <code>stickyHeaderIndices</code>를 적용할 수 있고 Header 역시 리스트 아이템처럼 다룰 수 있을 것이라 기대했다.</p>
<p>하지만 이 방식은 FlashList의 구조적 특성과 잘 맞지 않았다.</p>
<p>FlashList 입장에서는 Header 역시 일반 아이템과 동일하게 취급되기 때문에 </p>
<ul>
<li>재사용 대상이 되고</li>
<li>스크롤에 따라 unmount/mount가 발생할 수 있으며</li>
<li>Sticky 상태에서 불필요한 re-render가 발생</li>
</ul>
<p>결과적으로 FlashList의 <strong><code>가상화 장점을 일부 깨는 구조</code></strong>였다.</p>
<p>또한 성능 관점에서 보았을 때 이 방식이 최적은 아니라는 판단이 들었다.
FlashList는 기본적으로 아이템 가상화에 최적화된 리스트인데
stickyHeaderIndices는 레이아웃 계산, 오프스크린 아이템 유지,
스크롤 중 위치 보정 과정을 거치며 이로 인해 <strong>레이아웃 비용이 증가</strong>하기 때문이다.</p>
<h2 id="세번째-시도">세번째 시도</h2>
<h2 id="최종-구현-방식">최종 구현 방식</h2>
<p>FlashList에서 제공하는 <strong>스크롤 감지 옵션</strong>을 활용해 현재 스크롤 위치를 추적하도록 구현했다.</p>
<p>스크롤이 진행되면서 얻은 <strong>현재 Y축 위치</strong>를 기준으로 Header의 기준 위치와 비교하고<br><code>show / hide</code> 여부에 따라 Header의 <code>opacity</code>를 조정하는 방식이다.</p>
<p>이를 통해 Sticky Header를 실제로 고정시키는 대신<br>스크롤 위치에 따라 <strong>자연스럽게 나타나고 사라지는 UI</strong>를 구현할 수 있었다.</p>
<p>결과적으로 레이아웃 재계산을 발생시키지 않으면서
JS → UI 변경 비용을 최소화해
성능과 UX를 모두 만족시키는 구현이 가능했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[cache로 웹사이트 성능을 최적화하기]]></title>
            <link>https://velog.io/@leave_a_comment/cache%EB%A1%9C-%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%84%B1%EB%8A%A5%EC%9D%84-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@leave_a_comment/cache%EB%A1%9C-%EC%9B%B9%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%84%B1%EB%8A%A5%EC%9D%84-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 08 Jan 2025 12:18:27 GMT</pubDate>
            <description><![CDATA[<h2 id="캐시란-무엇인가">캐시란 무엇인가?</h2>
<ul>
<li>캐싱은 자주 사용되는 데이터를 한 번 받아온 후에 그 데이터를 임시 저장소에 저장하여 동일한 데이터를 빠르게 불러와서 사용하는 기법을 말한다.</li>
<li>메모리 계층 구조에서 캐시는 디스크나 메인 메모리보다 더 빠르게 데이터를 불러와 사용해야 할 때 쓰인다.</li>
</ul>
<h2 id="캐싱의-종류">캐싱의 종류?</h2>
<ul>
<li>캐싱에는 여러 종류가 존재한다.</li>
</ul>
<h3 id="1-브라우저-캐시">1) 브라우저 캐시</h3>
<ul>
<li><p>변화가 적은 데이터라면 캐싱을 적용해 볼 수 있다. HTML, CSS, JavaScript, 이미지 등 웹 자원을 로컬 디스크에 저장해둔다. 이는 로컬 저장소에 캐시되기 때문에 단일 사용자를 대상으로 하며, 해당 사용자의 정보만을 저장한다.
ex) Etag와 Cache-Control은 브라우저 캐시와 관련된 HTTP 헤더로, 웹 브라우저가 서버에서 자원을 어떻게 캐시할지를 결정하는 데 사용된다.</p>
</li>
<li><p>HTTP 헤더의 <strong>ETag</strong>는 특정 버전의 리소스를 식별한다. 클라이언트는 데이터를 최초로 받은 이후 동일한 리소스를 요청할 때, 이전에 받은 Etag 값을 <strong>If-None-Match</strong> 헤더에 포함하여 요청을 보낸다.</p>
</li>
<li><p><em>If-Modified-Since*</em> 캐시된 리소스의 Last-Modified 값 이후에 서버 리소스가 수정되었는지 확인합니다.
서버는 이 Etag와 리소스의 현재 Etag를 비교하여, 리소스가 변경되지 않았다면 304 Not Modified 상태 코드와 함께 빈 응답을 보내고, 리소스가 변경되었으면 새로운 리소스를 반환한다.</p>
</li>
<li><p>HTTP 헤더의 Cache-Control은 클라이언트(브라우저)와 서버 간의 캐시 동작을 제어한다. </p>
<blockquote>
<p><strong>no-cache</strong>: 서버에서 데이터를 확인해야 하며, 이전에 캐시된 데이터를 사용하지 않도록 합니다.</p>
</blockquote>
</li>
<li><p><em>no-store*</em>: 데이터가 캐시되지 않도록 합니다.</p>
</li>
<li><p><em>max-age*</em>: 리소스가 캐시된 후 최대 몇 초 동안 유효한지 지정합니다.</p>
</li>
<li><p><em>public*</em>: 리소스가 모든 캐시에 저장될 수 있음을 의미합니다.</p>
</li>
<li><p><em>private*</em>: 리소스는 사용자별로 개인적인 캐시에 저장됩니다.</p>
</li>
<li><p><em>must-revalidate*</em>: 캐시된 리소스가 만료되었을 경우, 서버에서 리소스를 다시 확인해야 함을 의미합니다.</p>
</li>
<li><p><em>s-maxage*</em>: 중간 서버에서만 적용되는 max-age 값을 설정하기 위해 사용합니다. 예를 들어, Cache-Control 값을 s-maxage=31536000, max-age=0 과 같이 설정하면 CDN에서는 1년동안 캐시되지만 브라우저에서는 매번 재검증 요청을 보내도록 설정할 수 있습니다.</p>
</li>
</ul>
<pre><code>no-cache 속성은 캐시를 먼저 사용하기 이전에 서버에 해당 캐시를 사용해도 되는지에 관해 검증 요청을 보내는 속성이다. 
no-cache 속성이 없는 경우 캐시가 있다면 바로 캐시를 쓰지만(max-age=0), no-cache 속성이 있는 경우 캐시를 바로 쓰지 않고 서버에 이 캐시를 사용해도 되는지에 대한 허락을 맡기 때문에 요청에 대한 시간이 소요될 수 있다.

no-store는 개인정보 등 private 한 데이터가 있는 경우 이 속성을 사용할 수 있다. (캐시를 만들어서 저장조차 하지 않는다.)
private cache에 반대되는 shared cache 가 존재한다.

Cache-Control max-age 값 대신 Expires 헤더로 캐시 만료 시간을 정확히 지정할 수도 있습니다.</code></pre><ul>
<li><strong>Cache-Control</strong>은 리소스를 <strong>어떻게</strong> 캐시할지에 대한 지침을 제공하며, <strong>Etag</strong>는 리소스의 <strong>변경 여부</strong>를 확인하는 데 사용된다.  </li>
</ul>
<hr>
<h3 id="2-프록시-캐시">2) 프록시 캐시</h3>
<ul>
<li>공유 캐시(shared cache)로 한 명 이상의 사용자에 의해 재사용되는 응답을 저장한다. no-cache는 최신화가 필요한 stale 데이터를 사용하고 싶을 때 사용하고, 이러한 데이터를 사용해서 문제가 발생한다면 504 에러를 반환하는 must-revalidate를 사용하면 된다.</li>
</ul>
<hr>
<h3 id="3-cdn-캐시">3) CDN 캐시</h3>
<ul>
<li>CDN은 성능 향상을 위해 클라이언트의 요청이 같은 서버로 가는 것을 막는다. CDN 캐시는 웹 자원의 정적 파일을 캐시하여 빠른 응답을 제공한다.<blockquote>
<p>일반적으로 캐시를 없애기 위해서 CDN Invalidation을 수행한다고 한다. CDN에 저장되어 있는 캐시를 삭제한다는 뜻이다. 브라우저의 캐시는 다른 곳에 위치하기 때문에 CDN 캐시를 삭제한다고 해서 브라우저 캐시가 삭제되지는 않는다. </p>
</blockquote>
</li>
</ul>
<hr>
<h2 id="데이터-페칭-라이브러리에서는-캐싱을-어떻게-처리할까">데이터 페칭 라이브러리에서는 캐싱을 어떻게 처리할까?</h2>
<h4 id="react-query">React Query</h4>
<ul>
<li>서버에서 받은 데이터를 클라이언트의 로컬 메모리나 로컬 스토리지, 또는 IndexedDB에 저장하여 다음에 동일한 요청이 있을 경우 네트워크 요청을 생략하고 캐시된 데이터를 반환할 수 있습니다.</li>
<li>staleTime을 설정하여 데이터가 &quot;stale&quot; 상태(즉, 오래된 상태)가 되기까지의 시간을 지정할 수 있고, cacheTime을 통해 캐시된 데이터를 얼마나 오래 보존할지 설정할 수 있습니다.</li>
</ul>
<blockquote>
<p><strong>staleTime</strong>: 데이터를 얼마나 오래 캐시할지 지정합니다. 이 시간이 지나면 데이터는 &quot;stale&quot; 상태가 됩니다.
<strong>cacheTime</strong>: 캐시된 데이터를 언제까지 저장할지 지정합니다. 이 시간이 지나면 캐시된 데이터는 삭제됩니다.
<strong>refetchOnWindowFocus</strong>: 브라우저 창이 포커스를 받을 때 데이터를 자동으로 재페칭할지 여부를 설정합니다.</p>
</blockquote>
<p>ref) <a href="https://yozm.wishket.com/magazine/detail/2341/">https://yozm.wishket.com/magazine/detail/2341/</a>
ref) <a href="https://toss.tech/article/smart-web-service-cache">https://toss.tech/article/smart-web-service-cache</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 기타 등등 Hook]]></title>
            <link>https://velog.io/@leave_a_comment/React-%EA%B8%B0%ED%83%80-%EB%93%B1%EB%93%B1-Hook</link>
            <guid>https://velog.io/@leave_a_comment/React-%EA%B8%B0%ED%83%80-%EB%93%B1%EB%93%B1-Hook</guid>
            <pubDate>Fri, 29 Nov 2024 06:30:57 GMT</pubDate>
            <description><![CDATA[<h3 id="리액트-핵심-훅을-제외한-유용한-훅들-소개">리액트 핵심 훅을 제외한 유용한 훅들 소개</h3>
<h4 id="-useimperativehandle">* useImperativeHandle</h4>
<ul>
<li>부모 컴포넌트가 자식 컴포넌트의 특정 기능에 접근하도록 허용. 일반적으로 ref와 함께 사용되며, forwardRef가 필요함. 부모가 자식 컴포넌트의 특정 기능(예: focus, scroll)을 직접 호출.</li>
</ul>
<p>[<strong>사용 예시</strong>]</p>
<pre><code>const FancyInput = React.forwardRef((props, ref) =&gt; {
  const inputRef = React.useRef();
  React.useImperativeHandle(ref, () =&gt; ({
    focus: () =&gt; {
      inputRef.current.focus();
    },
  }));
  return &lt;input ref={inputRef} /&gt;;
});

function Parent() {
  const ref = React.useRef();

  return (
    &lt;div&gt;
      &lt;FancyInput ref={ref} /&gt;
      &lt;button onClick={() =&gt; ref.current.focus()}&gt;포커스&lt;/button&gt;
    &lt;/div&gt;
  );
}
</code></pre><h4 id="-uselayouteffect">* useLayoutEffect</h4>
<ul>
<li>DOM이 렌더링된 직후, 브라우저가 그리기 전에 동기적으로 실행. DOM 변경 후 바로 실행이 필요한 경우에 적합하다. useEffect와 비슷하지만 실행 시점이 다르다. </li>
</ul>
<p>[<strong>사용 시기</strong>]</p>
<p>DOM 요소의 위치, 크기 조정이 필요할 때.
애니메이션 효과나 스크롤 위치 설정.</p>
<p>[<strong>차이점</strong>]</p>
<p>useEffect: 렌더링 이후 실행 (<strong>비동기</strong>).
useLayoutEffect: 렌더링 직전에 실행 (<strong>동기</strong>).</p>
<pre><code>function LayoutEffectExample() {
  const divRef = React.useRef();

  React.useLayoutEffect(() =&gt; {
    console.log(divRef.current.offsetHeight);
  }, []);

  return &lt;div ref={divRef}&gt;레이아웃 확인&lt;/div&gt;;
}
</code></pre><h4 id="-usetransition">* useTransition</h4>
<ul>
<li><p>상태 업데이트를 긴급/비긴급으로 나누어 처리한다. 사용자 인터페이스가 부드럽게 유지. React 18의 Concurrent Mode에서 유용.</p>
<pre><code>function App() {
const [isPending, startTransition] = React.useTransition();
const [count, setCount] = React.useState(0);

const handleClick = () =&gt; {
  startTransition(() =&gt; {
    setCount((c) =&gt; c + 1);
  });
};

return (
  &lt;div&gt;
    &lt;button onClick={handleClick}&gt;클릭&lt;/button&gt;
    {isPending ? &lt;p&gt;로딩 중...&lt;/p&gt; : &lt;p&gt;결과: {count}&lt;/p&gt;}
  &lt;/div&gt;
);
}
</code></pre></li>
</ul>
<pre><code>#### * useId
- 고유한 ID를 생성해서 HTML에 사용한다. 나는 폼에 적용했었다. 컴포넌트간 충돌이 없어 유용하다.</code></pre><p>function Component() {
  const id = React.useId();</p>
<p>  return (
    <label htmlFor={id}>
      입력창
      <input id={id} />
    </label>
  );
}</p>
<pre><code>#### * useDefferedValue
- 부하가 큰 연산의 업데이트를 지연시켜 UI 성능 개선. UI가 느려지지 않도록 함.</code></pre><p>function App({ value }) {
  const deferredValue = React.useDeferredValue(value);</p>
<p>  return <div>{deferredValue}</div>;
}</p>
<pre><code>

#### * useActionState
- 비동기 액션의 상태를 더 명확하고 직관적으로 관리할 수 있다. (기존 useFormState 훅을 개선했다.)</code></pre><p>&quot;use client&quot;;</p>
<p>function App() {
    const [state, submitAction, isPending] = useActionState(customAction, initialState); // initialState like {error: null}</p>
<pre><code>return (
    &lt;form action={submitAction}&gt;
    &lt;button disabled={isPending}&gt;&lt;/button&gt;
    {state.error &amp;&amp; &lt;span&gt;error&lt;/span&gt;}
    &lt;/form&gt;
)</code></pre><p>}</p>
<p>```
리액트 훅이므로 클라이언트 컴포넌트에서 사용해야 한다. pending 상태와 useState, onSubmit 함수를 축약해 개인적으로 가독성과 데이터 응집도가 높아졌다고 생각한다.</p>
<blockquote>
<p>“form, input, button 요소에 action 및 formAction props로 Server Action을 전달하면 JavaScript가 비활성화되었거나 코드가 로드되기 전에도 사용자가 폼을 제출하고, 오류를 표시할 수 있게 되었다.”</p>
</blockquote>
<p>JavaScript가 비활성화되거나 코드가 로드되기 전에 사용자가 form을 제출할 수 있으면 뭐가 좋은걸까?</p>
<p>사용자의 인터넷 연결이 느리거나, 장치 성능이 낮거나, JavaScript가 비활성화된 환경에서도 폼 제출 기능이 정상적으로 작동하도록 보장해줄 수 있다. 이로써 웹사이트의 접근성을 높여 모든 사용자에게 일관된 경험을 제공할 수 있게 되고, 결과적으로 사용자가 겪을 수 있는 잠재적인 문제를 줄이고 더 많은 사용자가 서비스를 이용할 수 있게 하는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redux 도입기]]></title>
            <link>https://velog.io/@leave_a_comment/Redux-%EB%8F%84%EC%9E%85%EA%B8%B0</link>
            <guid>https://velog.io/@leave_a_comment/Redux-%EB%8F%84%EC%9E%85%EA%B8%B0</guid>
            <pubDate>Wed, 11 Sep 2024 10:08:59 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ul>
<li>리액트 상태 관리 라이브러리들 (feat. 장단점)</li>
<li>상태 관리에 Redux를 선택하게 된 계기</li>
<li>Redux 셋팅하기</li>
<li>Next.js에 Redux를 적용하며 마주하게 된 사사로운 오류사항들</li>
<li>번외</li>
</ul>
<p><br><br></p>
<hr>
<p><br><br></p>
<h2 id="🛠️-리액트-상태-관리-라이브러리들-feat-장단점">🛠️ 리액트 상태 관리 라이브러리들 (feat. 장단점)</h2>
<br>

<p>리액트 상태 관리 라이브러리에는 다양한 종류가 있다. 익숙한 것들만 간단히 나열해보자면 Recoil, Redux, Jotai, Mobx, zustand 등등 ...</p>
<p>우리가 사용할 기술을 선정할 때 고려해야 할 것이 몇가지 있다. 각 기술의 특성과 장단점을 무조건 파악을 해야 한다는 거다. </p>
<blockquote>
<p>제일 주의해야하는 건 무작정 &#39;남들이 다 사용하니까&#39; 선택하는 일이다. </p>
</blockquote>
<p>그렇게 되면 내가 만들 프로젝트와 그 기술의 성격이 맞지 않을 수가 있고, 몸집이 커진 프로젝트를 다시 싹 다 갈아 엎어야 하는 대참사가 발생할 수도 있다. </p>
<br>
<br>


<h2 id="상태관리에-redux를-선택하게-된-계기">상태관리에 Redux를 선택하게 된 계기</h2>
<br>


<p>위와 같은 다양한 라이브러리 중 나는 zustand 또는 Redux 사이에서 고민하게 되었다.</p>
<br>

<p>나의 상황을 적어보자면</p>
<p>1) 온전히 혼자 작업을 해야 한다.
2) 최대한 빠르게 작업을 마무리 하면 좋다.
3) 두 라이브러리 모두 사용해 본 경험이 없거나 적다.</p>
<br>




<p>위 같은 상황에서 빠르고 쉽게 작업할 수 있는 zustand 를 선택하는게 더 효율적이었겠지만, 나는 redux를 선택했다!</p>
<br>




<p>왜냐면 redux는 대규모 프로젝트에 더 적합하기 때문이다. (나는 런칭해서 운영까지, 즉 유지보수까지 해보고 싶었기 때문이다.)
그리고 &#39;쉬운걸 먼저 적용해버리면 나중에 더 큰 어려움을 마주하게 되지 않을까? 그러니까 어려운 것부터 먼저 깊게 다뤄보자!&#39; 싶은 마음이었다.</p>
<br>

<p>따라서 redux를 next.js(App Route) + typescript 에 적용하게 되는데 ,,, </p>
<br>
<br>


<h2 id="redux-셋팅하기">Redux 셋팅하기</h2>
<br>



<p>자 차근차근 적용해보도록 하자.</p>
<br>


<p>우선, redux와 관련 패키지를 설치해야 한다.</p>
<p><br><br></p>
<p><strong>Redux 설치</strong>
<br><br></p>
<pre><code>npm install @reduxjs/toolkit react-redux
</code></pre><p><br><br></p>
<p><strong>Redux 설정</strong>
<br><br></p>
<p>일단, store를 만든다. 이때 store는 redux의 모든 상태들을 관리하게 된다. 
<br></p>
<pre><code>// store.ts

import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import rootReducer from &quot;./rootReducer&quot;;

export const makeStore = () =&gt; {
  return configureStore({
    reducer: rootReducer,
  });
};


export type AppStore = ReturnType&lt;typeof makeStore&gt;;
export type RootState = ReturnType&lt;AppStore[&quot;getState&quot;]&gt;;
export type AppDispatch = AppStore[&quot;dispatch&quot;];
</code></pre><p><br><br></p>
<p><strong>slice 생성</strong>
<br></p>
<p>slice는 액션 및 리듀서를 포함한다.
<br></p>
<pre><code>import { createSlice } from &quot;@reduxjs/toolkit&quot;;

const userAuthSlices = createSlice({
  name: &quot;userAuth&quot;, // slice 식별
  initialState: { name: &quot;subeen&quot; },
  reducers: {
    setUserAuth: (state, action) =&gt; {
      state.name = action.payload;
    },
  },
});

export const { setUserAuth } = userAuthSlices.actions;

export default userAuthSlices.reducer;
</code></pre><br>

<p>이때, name에 따라 slice가 식별되니 직관적이게 작명하도록 유의한다. </p>
<p><br><br></p>
<p><strong>Provider 설정</strong>
<br></p>
<p>Provider를 설정해 모든 페이지에서 Redux 상태에 접근할 수 있도록 한다. </p>
<p>Next.js App Router 구조에서는 app/layout.tsx 파일에서 설정한다. </p>
<br>

<pre><code>&quot;use client&quot;;

import React from &quot;react&quot;;
import { Provider } from &quot;react-redux&quot;;
import { makeStore } from &quot;./lib/store&quot;;

const store = makeStore(); // makeStore 함수를 호출하여 스토어 객체를 생성합니다.

const ClientLayout: React.FC&lt;{ children: React.ReactNode }&gt; = ({
  children,
}) =&gt; {
  return (
    &lt;Provider store={store}&gt;
      {/* store 객체를 Provider에 전달합니다. */}
      &lt;main&gt;{children}&lt;/main&gt;
    &lt;/Provider&gt;
  );
};

export default ClientLayout;
</code></pre><pre><code>import Footer from &quot;@/components/Footer&quot;;
import Header from &quot;@/components/Header&quot;;
import type { Metadata, Viewport } from &quot;next&quot;;
import { Inter } from &quot;next/font/google&quot;;
import ClientLayout from &quot;./ClientLayout&quot;;
import &quot;./globals.css&quot;;

const inter = Inter({ subsets: [&quot;latin&quot;] });

export const metadata: Metadata = {
  title: &quot;Create Next App&quot;,
  description: &quot;Generated by create next app&quot;,
};

export const viewport: Viewport = {
  initialScale: 1,
  width: &quot;device-width&quot;,
};

const RootLayout = ({
  children,
}: Readonly&lt;{
  children: React.ReactNode;
}&gt;) =&gt; {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;body className={inter.className}&gt;
        &lt;ClientLayout&gt;
          &lt;Header /&gt;
          {children}
        &lt;/ClientLayout&gt;
        &lt;Footer /&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
};

export default RootLayout;
</code></pre><br>


<p>위의 코드를 보면 바로 Provider로 감싸는 대신 ClientLayout으로 감쌌는데, 이는 Redux가 클라이언트 컴포넌트에서만 작동하는 훅을 사용해야하기 때문이다. (useSelector, useDispatch)</p>
<br>


<p><em>여기서 잠깐</em></p>
<br>

<p>Redux는 클라이언트 사이드 상태 관리 (브라우저 환경에서 동작하는 상태 관리 도구) 라이브러리이기 때문에 &quot;use client&quot; 지시어가 필요하다. </p>
<p>(&quot;use client&quot; 지시어는 초기 페이지 로딩 속도나 사용자 경험에 부정적인 영향을 미칠 가능성이 있다. 번들 크기가 커져 페이지 로드 시간이 증가하는 단점도 있다.)</p>
<p><br><br></p>
<p>보통 그래서 next-redux-wrapper 라이브러리를 사용한다. </p>
<br>


<p><br><br></p>
<p><strong>Redux 사용 예시</strong></p>
<br>

<p>컴포넌트에서 redux 상태와 액션 사용 방법이다. 
useSelector와 useDispatch 훅을 이용하여 상태를 조회하고, 액션을 디스패치 할 수 있다. </p>
<br>

<pre><code>// app/login/page.tsx
&#39;use client&#39;;

import { useState } from &#39;react&#39;;
import { useDispatch } from &#39;react-redux&#39;;
import { login } from &#39;../../features/userSlice&#39;;
import { useRouter } from &#39;next/navigation&#39;;

const LoginPage = () =&gt; {
  const [email, setEmail] = useState(&#39;&#39;);
  const [password, setPassword] = useState(&#39;&#39;);
  const dispatch = useDispatch();
  const router = useRouter();

  const handleLogin = async (e: React.FormEvent) =&gt; {
    e.preventDefault();

    // 실제로는 백엔드에서 로그인 API 호출
    const userData = {
      id: &#39;123&#39;,
      name: &#39;John Doe&#39;,
      email: email,
    };

    // 로그인 성공 시 Redux에 사용자 정보를 저장
    dispatch(login(userData));

    // 대시보드로 리디렉션
    router.push(&#39;/dashboard&#39;);
  };

  return (
    &lt;form onSubmit={handleLogin}&gt;
      &lt;div&gt;
        &lt;label&gt;Email&lt;/label&gt;
        &lt;input
          type=&quot;email&quot;
          value={email}
          onChange={(e) =&gt; setEmail(e.target.value)}
          required
        /&gt;
      &lt;/div&gt;
      &lt;div&gt;
        &lt;label&gt;Password&lt;/label&gt;
        &lt;input
          type=&quot;password&quot;
          value={password}
          onChange={(e) =&gt; setPassword(e.target.value)}
          required
        /&gt;
      &lt;/div&gt;
      &lt;button type=&quot;submit&quot;&gt;Login&lt;/button&gt;
    &lt;/form&gt;
  );
};

export default LoginPage;
</code></pre><br>






<br>
<br>

<h2 id="nextjs에-redux를-적용하며-마주하게-된-사사로운-오류사항들">Next.js에 Redux를 적용하며 마주하게 된 사사로운 오류사항들</h2>
<br>



<p><strong>가장 많이 언급되는 불편함: 새로고침 하면 날라가요 ,,,</strong>
<br></p>
<p>이를 해결하기 위한 몇가지 방법이 있다. </p>
<br>



<p>아. 일단 왜 이런 문제가 발생하냐면, 브라우저를 새로고침 하면 페이지 전체를 다시 로드하기 때문에 Redux 상태가 초기화된다.</p>
<br>




<p><strong>첫번째 해결방법) redux-persist 라이브러리 사용</strong></p>
<br>
간편해서 많이 사용하는 방법!
<br><br>

<p>간략한 사용법 설명</p>
<br>
1. 설치

<pre><code>npm install redux-persist
</code></pre><br>

<ol start="2">
<li>스토어 설정<pre><code>// lib/store.ts
import { configureStore } from &#39;@reduxjs/toolkit&#39;;
import { persistStore, persistReducer } from &#39;redux-persist&#39;;
import storage from &#39;redux-persist/lib/storage&#39;; // 기본적으로 localStorage 사용
import rootReducer from &#39;./rootReducer&#39;; // 모든 리듀서를 합친 rootReducer
</code></pre></li>
</ol>
<p>const persistConfig = {
  key: &#39;root&#39;,
  storage, // localStorage에 저장
};</p>
<p>const persistedReducer = persistReducer(persistConfig, rootReducer);</p>
<p>export const store = configureStore({
  reducer: persistedReducer,
});</p>
<p>export const persistor = persistStore(store);</p>
<pre><code>

&lt;br&gt;

3. Provider 설정
&lt;br&gt;</code></pre><p>// app/layout.tsx
&#39;use client&#39;;</p>
<p>import &#39;./globals.css&#39;;
import { Provider } from &#39;react-redux&#39;;
import { store, persistor } from &#39;../lib/store&#39;;
import { PersistGate } from &#39;redux-persist/integration/react&#39;;
import Header from &#39;../components/Header&#39;;</p>
<p>export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Provider store={store}>
          <PersistGate loading={null} persistor={persistor}>
            <Header />
            <main>{children}</main>
          </PersistGate>
        </Provider>
      </body>
    </html>
  );
}</p>
<pre><code>
&lt;br&gt;


redux-persist의 PersistGate를 사용하여, 스토어가 복원되기 전까지 앱을 지연시킬 수 있다.

&lt;br&gt;



만약 세션 중에만 상태를 유지하고 싶다면 redux-persist/lib/storage/session을 사용할 수 있다.

&lt;br&gt;


__단점__
- 스토리지의 크기가 제한적이고, 복잡한 상태 관리에는 다소 비효율적일 수 있습니다.
상태가 커질수록 브라우저 로딩 시간이 길어질 수 있음.








&lt;br&gt;&lt;br&gt;

**두번째 해결방법) localStorage나 sessionStorage 직접 사용**
&lt;br&gt;


1. store가 업데이트 될 때마다 localStorage에 상태 저장
&lt;br&gt;</code></pre><p>// lib/store.ts
import { configureStore } from &#39;@reduxjs/toolkit&#39;;
import rootReducer from &#39;./rootReducer&#39;;</p>
<p>const loadState = () =&gt; {
  try {
    const serializedState = localStorage.getItem(&#39;reduxState&#39;);
    if (serializedState === null) {
      return undefined;
    }
    return JSON.parse(serializedState);
  } catch (e) {
    console.error(&#39;Could not load state&#39;, e);
    return undefined;
  }
};</p>
<p>const saveState = (state: RootState) =&gt; {
  try {
    const serializedState = JSON.stringify(state);
    localStorage.setItem(&#39;reduxState&#39;, serializedState);
  } catch (e) {
    console.error(&#39;Could not save state&#39;, e);
  }
};</p>
<p>const preloadedState = loadState();</p>
<p>const store = configureStore({
  reducer: rootReducer,
  preloadedState, // 저장된 상태를 미리 로드
});</p>
<p>store.subscribe(() =&gt; {
  saveState(store.getState());
});</p>
<p>export default store;</p>
<pre><code>
&lt;br&gt;


__장점__
- redux-persist보다 가볍고, 직접 제어 가능.
- 상태가 단순한 경우 빠르게 적용 가능.
&lt;br&gt;
__단점__
- 수동으로 상태 직렬화/역직렬화를 관리해야 하므로 코드 복잡도가 약간 증가.
- 상태가 클 경우 저장 성능에 영향을 줄 수 있음.






&lt;br&gt;
&lt;br&gt;


**세번째 해결방법) 쿠키 사용**

&lt;br&gt;

쿠키를 사용하여 상태를 저장할 수 있지만, 쿠키는 브라우저가 서버에 전송하는 용도로 주로 사용되므로 큰 데이터를 저장하는 데는 부적합하다. 그러나 인증 정보나 작은 상태값을 저장하는 데에는 적합.

&lt;br&gt;

</code></pre><p>import { parseCookies, setCookie } from &#39;nookies&#39;; // nookies 라이브러리 사용
import { configureStore } from &#39;@reduxjs/toolkit&#39;;
import rootReducer from &#39;./rootReducer&#39;;</p>
<p>const loadStateFromCookies = () =&gt; {
  const cookies = parseCookies();
  return cookies.reduxState ? JSON.parse(cookies.reduxState) : undefined;
};</p>
<p>const saveStateToCookies = (state: RootState) =&gt; {
  setCookie(null, &#39;reduxState&#39;, JSON.stringify(state), {
    maxAge: 30 * 24 * 60 * 60,
    path: &#39;/&#39;,
  });
};</p>
<p>const preloadedState = loadStateFromCookies();</p>
<p>const store = configureStore({
  reducer: rootReducer,
  preloadedState,
});</p>
<p>store.subscribe(() =&gt; {
  saveStateToCookies(store.getState());
});</p>
<p>export default store;</p>
<pre><code>&lt;br&gt;



__장점__
- 인증 정보나 작은 상태 저장에 적합.
- 서버 사이드 렌더링(SSR)과 쉽게 연동 가능.
&lt;br&gt;
__단점__
- 쿠키는 용량 제한이 있으므로 큰 데이터를 저장할 수 없음.
- 보안 이슈가 있을 수 있으며, 민감한 정보는 적절히 암호화 필요.





&lt;br&gt;&lt;br&gt;





**네번째 해결방법) 서버 사이드에서 상태 저장**

&lt;br&gt;

Next.js의 API Routes를 활용하여 서버에 상태를 저장하고, 필요할 때 API를 통해 불러오는 방법.


&lt;br&gt;

</code></pre><p>// pages/api/saveState.ts
import type { NextApiRequest, NextApiResponse } from &#39;next&#39;;</p>
<p>export default function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === &#39;POST&#39;) {
    // 상태를 데이터베이스나 파일 시스템에 저장
    // 예: db.save(req.body)
    res.status(200).json({ message: &#39;State saved&#39; });
  } else {
    res.status(405).json({ message: &#39;Method not allowed&#39; });
  }
}</p>
<pre><code>


&lt;br&gt;

새로고침 시 상태가 서버로부터 복원될 수 있도록 useEffect로 API를 호출하여 상태를 불러오고, 저장할 때는 API로 상태를 보낸다. 

&lt;br&gt;


__장점__
- 큰 데이터를 저장하거나, 보안이 필요한 상태 관리에 적합.
- 서버에서 관리되므로 클라이언트 측의 저장 용량 한계를 걱정할 필요 없음.
&lt;br&gt;
__단점__
- 상태 저장 및 불러오는 로직이 복잡해질 수 있음.
- 상태 저장/복원 시 네트워크 지연이 발생할 수 있음.


&lt;br&gt;



&lt;br&gt;

**나는 위 방법들 중에 localStorage 직접 설정을 택했다.**

&lt;br&gt;

redux-persist 는 나중에 사용해보도록 하자 ~

&lt;br&gt;&lt;br&gt;




#### localStorage에 직접 설정하기
&lt;br&gt;
</code></pre><p>import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import rootReducer from &quot;./rootReducer&quot;;</p>
<p>const loadState = () =&gt; {
  try {
    const serializedState = localStorage.getItem(&quot;reduxState&quot;);
    if (serializedState === null) {
      return undefined;
    }
    return JSON.parse(serializedState);
  } catch (e) {
    console.log(&quot;Could not load state&quot;, e);
    return undefined;
  }
};</p>
<p>const saveState = (state: RootState) =&gt; {
  try {
    const serializedState = JSON.stringify(state);
    localStorage.setItem(&quot;reduxState&quot;, serializedState);
  } catch (e) {
    console.error(&quot;Could not save state&quot;, e);
  }
};</p>
<p>const preloadedState = loadState();</p>
<p>export const makeStore = () =&gt; {
  return configureStore({
    reducer: rootReducer,
    preloadedState,
  });
};</p>
<p>makeStore.subscribe(() =&gt; {
    saveState(makeStore.getState());
  });</p>
<p>export type AppStore = ReturnType<typeof store>;</p>
<p>export type RootState = ReturnType&lt;AppStore[&quot;getState&quot;]&gt;;
export type AppDispatch = AppStore[&quot;dispatch&quot;];</p>
<pre><code>&lt;br&gt;

이런식으로 작성했더니 타입 오류가 났다.

&lt;br&gt;


makeStore 함수에 직접적으로 subscribe 메서드를 호출하려고 했기 때문이다. 

makeStore 함수는 store를 반환하는 함수이지 store 자체가 아니다.

(makeStore 함수가 호출된 후에 반환된 store 인스턴스를 사용해야 함)



&lt;br&gt;




코드를 수정해준 후, 디버깅을 해보았는데 store를 참조하고 있지 않았다.

(그래서 자꾸 데이터가 보존되지 않고 날아감)


&lt;br&gt;


각 store는 name에 따라 식별되고 있는데, localStorage에서 가져온 이 데이터가 어느 store에 대한 데이터인지 매칭이 안되고 있었다. 



&lt;br&gt;

따라서 아래와 같이 수정해주었다. 
</code></pre><p>import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import rootReducer from &quot;./rootReducer&quot;;</p>
<p>const loadState = () =&gt; {
  try {
    const serializedState = localStorage.getItem(&quot;reduxState&quot;);
    if (serializedState === null) {
      return undefined;
    }</p>
<pre><code>const parsedState = JSON.parse(serializedState);

// 로컬 스토리지의 상태를 rootReducer의 구조에 맞게 변환
return {
  userAuth: parsedState, // userAuth로 감싸기
};</code></pre><p>  } catch (e) {
    console.log(&quot;Could not load state&quot;, e);
    return undefined;
  }
};</p>
<p>const saveState = (state: RootState) =&gt; {
  try {
    const serializedState = JSON.stringify(state.userAuth); // userAuth만 저장
    localStorage.setItem(&quot;reduxState&quot;, serializedState);
  } catch (e) {
    console.log(&quot;Could not save state&quot;, e);
    return undefined;
  }
};</p>
<p>const preloadedState = loadState();</p>
<p>export const makeStore = () =&gt; {
  return configureStore({
    reducer: rootReducer,
    preloadedState,
  });
};</p>
<p>// Create store instance
const store = makeStore();</p>
<p>// 아래와 같이 하면 타입 오류가 발생
// const store =  configureStore({
//  reducer: rootReducer,
//  preloadedState
// })</p>
<p>// Subscribe to store updates and save state to localStorage
store.subscribe(() =&gt; {
  const state = store.getState();
  saveState(state);
});</p>
<p>export type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType&lt;AppStore[&quot;getState&quot;]&gt;;
export type AppDispatch = AppStore[&quot;dispatch&quot;];</p>
<pre><code>

&lt;br&gt;&lt;br&gt;


이상으로 Redux 적용기 1탄을 마무리해보도록 하겠다. 




&lt;br&gt;&lt;br&gt;


~~velog 예쁘고 가독성 있게 작성하는거 왜케 어려워 ...~~












</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[잊지 않기 위한 기록]]></title>
            <link>https://velog.io/@leave_a_comment/%EC%9E%8A%EC%A7%80-%EC%95%8A%EA%B8%B0-%EC%9C%84%ED%95%9C-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@leave_a_comment/%EC%9E%8A%EC%A7%80-%EC%95%8A%EA%B8%B0-%EC%9C%84%ED%95%9C-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Tue, 09 Jul 2024 03:34:23 GMT</pubDate>
            <description><![CDATA[<h4 id="도커"><strong>도커</strong></h4>
<ul>
<li>도커는 컨테이너 기반의 가상화 플랫폼.</li>
<li>vm ware는 무겁기 때문에 docker를 많이 사용한다.</li>
<li>서비스 운영에 필요한 서버 프로그램, 코드 및 라이브러리, 컴파일된 실행 파일 등을 묶는 형태를 도커 이미지라고 한다. 즉 컨테이너 생성(실행)에 필요한 모든 파일과 환경을 가진 것.
ex) 우분투 이미지는 우분투를 실행하기 위한 모든 파일과 dependency 들을 포함한다.</li>
<li>도커 이미지를 실행하면 도커 컨테이너가 된다고 볼 수 있다.</li>
</ul>
<h4 id="서버"><strong>서버</strong></h4>
<ul>
<li>netstat
현재 서버가 다른 시스템과 어떤 서비스/포트로 연결되어 있는지 확인하는 명령어</li>
<li>netstat -an
연결되었던/연결을 기다리는 목록을 아이피 주소로 바꾸어서 보여줌</li>
<li>서버 주소/hello-world 로 서버가 죽어있는지 확인한다.</li>
</ul>
<h4 id="graphql"><strong>graphQL</strong></h4>
<ul>
<li>유연하게 확장하기 위해 사용</li>
<li>Restful API 는 형태와 데이터 요청 방법이 연결되어 있으나, graphQL은 완전히 분리되어 있음.</li>
<li>REST는 리소스 형태와 크기를 서버에서 결정하나 graphQL은 클라이언트 단에서 요청시 결정한다.</li>
<li>REST는 여러 리소스에 접근 시 여러 번 요청을 하나 graphQL은 한번의 요청을 한다.</li>
</ul>
<h4 id="react"><strong>React</strong></h4>
<ul>
<li>React DOM은 JSX에 삽입된 모든 값을 렌더링하기 전에 이스케이프 하므로, 애플리케이션에서 명시적으로 작성되지 않은 내용은 주입되지 않는다. 모든 항목은 렌더링 되기 전에 문자열로 변환된다. 이런 특성으로 인해 XSS 공격을 방지할 수 있다.</li>
<li>strictMode에서 useEffect의 double call 특성 : 개발시에만 활성화 되고 실제 프로덕션 빌드 할 때는 알아서 꺼진다. 이 모드에서는 컴포넌트를 2번씩 렌더링해서 deprecated된 생명주기 메서드의 사용, 예상치 못한 부작용, 레거시 문자열 ref 등을 확인하는 용도로 사용된다. 개발 과정에서 잠재적인 문제를 조기에 발견하고 수정하는 용도로 사용된다.</li>
</ul>
<h4 id="웹-성능-최적화"><strong>웹 성능 최적화</strong></h4>
<ul>
<li>이미지 최적화</li>
<li>리소스 압축</li>
<li>렌더링 최적화</li>
<li>CDN 사용</li>
<li>캐시 설정</li>
</ul>
<h4 id="각-언어별-프레임워크"><strong>각 언어별 프레임워크</strong></h4>
<ul>
<li>Python: Django, Flask</li>
<li>Node.js: Express.js, Nest.js</li>
<li>Java: Spring, Spring Boot</li>
<li>Ruby: Ruby on Rail</li>
<li>Golang: Gin</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React와 Next 배포 (1)]]></title>
            <link>https://velog.io/@leave_a_comment/React%EC%99%80-Next-%EB%B0%B0%ED%8F%AC-1</link>
            <guid>https://velog.io/@leave_a_comment/React%EC%99%80-Next-%EB%B0%B0%ED%8F%AC-1</guid>
            <pubDate>Thu, 22 Feb 2024 13:08:12 GMT</pubDate>
            <description><![CDATA[<p>블로그가 다 만들어지면 CI/CD 자동화를 구현해보고자 한다.</p>
<p>React는 정적 사이트 배포(bundle.js로 빈 html트리에 채워넣는다.)이지만 Next는 SSR 방식이기 때문에 React와 Next 배포 방식에는 차이가 있다.</p>
<p>그래서 Next를 사용할 때에는 Docker+S3+EC2와 같은 환경에서의 배포를 사용하고 있다. (Github + Jenkins + S3)</p>
<p>Vercel은 서버리스 방식이라고 하여 Vercel로 내가 만든 블로그를 배포해보려고 한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next로 블로그 구현하기 (1)]]></title>
            <link>https://velog.io/@leave_a_comment/Next%EB%A1%9C-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@leave_a_comment/Next%EB%A1%9C-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Wed, 21 Feb 2024 13:35:09 GMT</pubDate>
            <description><![CDATA[<p>오늘 To Do list</p>
<ul>
<li>markdown 에 필요한 라이브러리 서칭 및 설정</li>
<li>html로 markdown하는 기능의 api 작성</li>
<li>포스트 불러오는 api 작성</li>
</ul>
<p>서버리스 이므로 md파일로 post를 저장해놓은 다음에 post내용을 읽어온다.
join 함수로 파일 확장자까지 붙여 정확한 경로를 만든다.
fs 모듈의 readdirSync 메소드 사용하여 경로에 있는 파일명들을 받아온다.</p>
<ul>
<li>fs 모듈은 파일 시스템에 접근하고 파일과 폴더를 다룰 수 있는 기능을 제공하는 내장 모듈입니다. fs 모듈은 Node.js 환경에서 사용되며, 브라우저에서는 사용할 수 없습니다.</li>
<li>path 모듈은 파일 경로와 관련된 유틸리티 기능을 제공하는 내장 모듈입니다. path 모듈은 파일 경로를 조작하고 파싱하는 데 사용됩니다. 
markdown 언어를 해석해주는 matter 함수를 사용한다.</li>
<li>Gray-matter는 JavaScript 기반의 라이브러리로, Markdown 파일과 YAML, TOML, JSON 등의 프론트매터(front matter)를 파싱하는 기능을 제공합니다. 프론트매터는 문서의 메타데이터를 포함하는 머리말 부분으로, 일반적으로 YAML, TOML 또는 JSON 형식으로 작성됩니다.</li>
</ul>
<p>dangerouslySetInnerHTML로 React에서 직접 html을 설정할 수 있다.</p>
<ul>
<li>신뢰할 수 없는 소스에서 가져온 HTML 문자열은 보안상 위험이 있을 수 있기 때문이다.
따라서 이 속성을 사용할 때에는 신뢰할 수 있는 소스로부터의 HTML 문자열만 사용해야 하며, 공격을 방지하기 위해 적절한 XSS 방어 메커니즘을 구현하는 것이 좋다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[RSC (React Server Compoment)]]></title>
            <link>https://velog.io/@leave_a_comment/RSC-React-Server-Compoment</link>
            <guid>https://velog.io/@leave_a_comment/RSC-React-Server-Compoment</guid>
            <pubDate>Tue, 12 Sep 2023 12:02:42 GMT</pubDate>
            <description><![CDATA[<p>리액트 서버 컴포넌트는 react 18에 나온 아주 강력한 기능 중 하나이다.</p>
<p><a href="https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#react-server-components">https://react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023#react-server-components</a></p>
<p>Server side rendering을 상호보완해주는 기술인데, Next.js 서버 컴포넌트도 RSC에 기반한다고 한다.</p>
<p>Next에서 권장하는 방법론 !</p>
<ol>
<li><p>server component를 이용하여 서버측에서 데이터를 fetching 합니다.</p>
</li>
<li><p>data fetching을 병렬적으로 수행하는것이 좋습니다.</p>
</li>
<li><p>layout 및 page의 경우 데이터를 사용하는 곳에서 데이터를 가져오는것이 좋습니다.</p>
</li>
<li><p>Suspense, Loading UI를 이용해 점진적으로 렌더링을 수행하는것이 좋습니다.</p>
</li>
</ol>
<p>revalidate는 뭐지 ?? 하다가 ISR과 관련된거라고 알게되었따
특정한 간격마다 캐시된 데이터를 재검증하기 위해 리소스의 캐시 수명을 설정하는 것이다.</p>
<p><a href="https://velog.io/@seungchan__y/NextJS%EC%99%80-ISR">https://velog.io/@seungchan__y/NextJS%EC%99%80-ISR</a></p>
<p>리액트 쿼리를 SSR 환경에서 사용하기 위해서</p>
<p>공식문서에서 추천하는 방법이 있습니다.</p>
<p>두가지 방법이 존재하는데</p>
<p>initialData 속성을 이용한 방법과</p>
<p>Hydration을 이용한 방법이 있습니다.</p>
<p>튜토리얼은 요기</p>
<p><a href="https://codevoweb.com/setup-react-query-in-nextjs-13-app-directory/">https://codevoweb.com/setup-react-query-in-nextjs-13-app-directory/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next는 url 직접 접근이 안돼요..]]></title>
            <link>https://velog.io/@leave_a_comment/Next%EB%8A%94-url-%EC%A7%81%EC%A0%91-%EC%A0%91%EA%B7%BC%EC%9D%B4-%EC%95%88%EB%8F%BC%EC%9A%94</link>
            <guid>https://velog.io/@leave_a_comment/Next%EB%8A%94-url-%EC%A7%81%EC%A0%91-%EC%A0%91%EA%B7%BC%EC%9D%B4-%EC%95%88%EB%8F%BC%EC%9A%94</guid>
            <pubDate>Tue, 05 Sep 2023 12:49:30 GMT</pubDate>
            <description><![CDATA[<p>url로 데이터를 전달할 때 시크릿창에서 직접 접근하면 데이터가 없다.</p>
<p>이를 해결하기 위해선 초기화된 배열로 예외 처리를 해주거나  -&gt; 클라이언트 처리)</p>
<p>getServerSideProps를 통해 pre-rendering 되도록 한다. -&gt; 서버에서 처리 (SEO최적화가 필요하거나, 유저에게 로딩을 보여주고 싶지 않으면)</p>
<p>컴포넌트 내부에서 router를 사용하면, 이는 클라이언트 사이드(프론트)에서만 실행된다.</p>
<p>이 주제 얘기하다보니까 s3에서 직접접근할때 index.html 파일 처리해줘야하는 문제점이 생각났다 ....... </p>
<p>배포할때마다 html 확장자를 제거해줘야하는데, 만들어지는 html 파일이 많아질수록 번거로워지니 이 부분도 처리를 미리미리 해놓아야겠다! </p>
<p>(함수를 추가하면 된다던데...)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[getServerSideProps vs getStaticProps 알고가기]]></title>
            <link>https://velog.io/@leave_a_comment/getServerSideProps-vs-getStaticProps-%EC%95%8C%EA%B3%A0%EA%B0%80%EA%B8%B0</link>
            <guid>https://velog.io/@leave_a_comment/getServerSideProps-vs-getStaticProps-%EC%95%8C%EA%B3%A0%EA%B0%80%EA%B8%B0</guid>
            <pubDate>Tue, 05 Sep 2023 12:26:53 GMT</pubDate>
            <description><![CDATA[<h3 id="getserversideprops-vs-getstaticprops">getServerSideProps vs getStaticProps</h3>
<p>getStaticProps =&gt; SSG
getServerSideProps =&gt; SSR</p>
<p>SSG는 빌드된 시점에 fetch를 하고, SSR은 매 요청시마다 refetch를 한다는 차이점이 있다. SSR은 SSG보다는 효율이 떨어지지만 언제든지 내용을 수정가능하다.</p>
<p>url 내 쿼리를 활용하여 api 요청 후 해당 데이터를 props로 넘겨줄 경우 getServerSideProps를 사용한다.</p>
<p>getServerSideProps를 사용하면 Context Object가 파라미터로 해당 함수에 들어오는데, key로는 아래와 같은게 있다. (함수는 클라이언트가 아닌 서버에서 돌아간다.)</p>
<blockquote>
</blockquote>
<p>params: If this page uses a dynamic route, params contains the route parameters. If the page name is [id].js , then - params will look like { id: ... }.
req: The HTTP IncomingMessage object.
res: The HTTP response object.
query: An object representing the query string.
preview: preview is true if the page is in the Preview Mode and false otherwise.
previewData: The preview data set by setPreviewData.
resolvedUrl: A normalized version of the request URL that strips the _next/data prefix for client transitions and - includes original query values.</p>
<p>예제 코드</p>
<pre><code>export const getServerSideProps: GetServerSideProps = async (context) =&gt; {
  try {
    const { menuId } = context.query;
    const response = await axios.get&lt;CategoryType[]&gt;(
      &quot;http://localhost:8080/category/get/menu&quot;,
      {
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
          &quot;Access-Control-Allow-Origin&quot;: &quot;*&quot;,
        },
        params: {
          menuId: menuId,
        },
      }
    );
    const data = response.data;
    console.log(data);
    return {
      props: {
        CategoryData: data,
      },
    };
  } catch (err) {
    console.log(err);
    return {
      props: {},
    };
  }
};</code></pre><p>참고 레퍼런스 : <a href="https://velog.io/@hhhminme/Next.js%EC%97%90%EC%84%9C-SSR%EB%A1%9C-url-query-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0feat.-typescript">https://velog.io/@hhhminme/Next.js%EC%97%90%EC%84%9C-SSR%EB%A1%9C-url-query-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0feat.-typescript</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA['JavaScript heap out of memory' 에러 핸들링]]></title>
            <link>https://velog.io/@leave_a_comment/JavaScript-heap-out-of-memory-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81-ntqxgp5y</link>
            <guid>https://velog.io/@leave_a_comment/JavaScript-heap-out-of-memory-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81-ntqxgp5y</guid>
            <pubDate>Tue, 05 Sep 2023 11:43:58 GMT</pubDate>
            <description><![CDATA[<p>xcode랑 안드로이드 스튜디오 설치하고 나니까 잘만 되던 빌드가 잘 안되더라 ,,, 상사분께 여쭤보니까 위 프로그램 설치하고는 무관하다는데 그럼 내가 마지막 빌드 이후에 설치한 라이브러리 때문일까 싶다 ..!</p>
<p>JavaScript heap out of memory 에러를 뱉어냈는데,
검색해보니 해결방법으로는</p>
<p>맥 사용 유저는 </p>
<pre><code>export NODE_OPTIONS=&quot;--max-old-space-size=8192&quot;</code></pre><p>를 입력하고 빌드를 하면 된다 ! 
주의) 저 숫자 아무거나 치면 안됨,,</p>
<p>그런데 빌드할때마다 매번 타이핑을 해주는건 번거로우니 package.json 파일에 cross-env 처리를 하면 된다.</p>
<p>공용 브랜치에 함부로 package.json 파일을 건드는건 위험하니 일단 할때마다 타이핑 해주고 있다 ㅠ.ㅠ</p>
<p>&#39;cross-env를 보니 스크립트뿐만 아니라 환경변수 및 여러 가지 설정값들을 js 파일로 들고 있다가 적용하는 방법도 있는 것 같던데&#39; 라고 다른 블로그에서 보았다 ! 이 부분도 나중에 알아봐야지 !!!!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[server component, client component를 알고가자 !]]></title>
            <link>https://velog.io/@leave_a_comment/server-component-client-component%EB%A5%BC-%EC%95%8C%EA%B3%A0%EA%B0%80%EC%9E%90</link>
            <guid>https://velog.io/@leave_a_comment/server-component-client-component%EB%A5%BC-%EC%95%8C%EA%B3%A0%EA%B0%80%EC%9E%90</guid>
            <pubDate>Thu, 31 Aug 2023 13:06:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="server-component와-client-component가-무엇일까---">Server component와 Client component가 무엇일까 ...  ?</h3>
</blockquote>
<p>Server  side rendering이 핫해지며 서버 컴포넌트와 클라이언트 컴포넌트 개념을 자주 접하게 되었다 !</p>
<p>나는 이런 개념들 정확히 아는걸 좋아하니까 정리해보자 <del>~</del></p>
<p>일반적으로 리액트에선 모든게 클라이언트 단에서 이루어진다. (CSR) </p>
<p>next는 클라이언트 단에서 모든 것을 부담하지 않아도 되게끔 개선했다!
보안을 강화시켜주고, 번들링 될 자바스크립트 양도 줄여주고, data fetching과 렌더링을 동일한 환경에서 수행할 수 있는 점 등등 장점이 꽤나 많은 것 같다ㅏ 
그런데 또 단점은 이렇게 서버 단에서 생성된 html에 결국 클라이언트 단에서 hydrate 작업을 진행해줘야 한다는 것이다. 즉 추가적인 자바스크립트 코드가 필요하다는 소리 ...!</p>
<p>서버사이드 렌더링은 정적으로도, 동적으로도 구현할 수 있다고 한다.</p>
<p>SSG: 빌드된 시점에 데이터 fetch
SSR: 매 요청시마다 데이터 refetch (할때마다 서버사이드에서 페이지 생성)</p>
<blockquote>
<p> 정적(static): 서버 단에서 서버 컴포넌트와 클라이언트 컴포넌트 모두 빌드 시에 미리 렌더링될 수 있다. 일단 요청에 따른 응답 결과를 캐싱해두고, 뒤이은 요청에는 그 캐싱해둔 결과를 재사용하는 방식이다. SSG, ISR 방식이 이에 해당한다.
동적(dynamic): 서버 컴포넌트와 클라이언트 컴포넌트가 매 요청 시 렌더링되며, 응답 결과는 캐싱되지 않는다. SSR 방식이 이에 해당한다.</p>
</blockquote>
<ul>
<li>data fetching이 필요한 경우 👉 서버 컴포넌트</li>
<li>백엔드 자원에 접근해야 하는 경우 👉 서버 컴포넌트</li>
<li>클라이언트에 드러내면 안 되는 민감한 정보가 있을 때 👉 서버 컴포넌트</li>
<li>자바스크립트 코드를 줄여야 할 때 👉 서버 컴포넌트</li>
<li>click, change 리스너 등을 사용하여 대화형(상호작용) 컨텐츠를 구현하려는 경우 👉 클라이언트 컴포넌트</li>
<li>&#39;상태(state)&#39;을 활용하는 경우 👉 클라이언트 컴포넌트</li>
<li>브라우저 상에서만 지원하는 API(예: local storage와 같은 웹 스토리지를 다루는 API)를 사용하는 경우 👉 클라이언트 컴포넌트</li>
</ul>
<p>Next.js에 따르면 가능한 경우에는 서버 컴포넌트로 만들고 따로 클라이언트 컴포넌트로 구현이 필요한 부분들만 추출하는 편이 좋다고 한다.</p>
<h3 id="1-server-component-란-">1. server component 란 ?</h3>
<p>- </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 12, 13]]></title>
            <link>https://velog.io/@leave_a_comment/Next.js-12-13</link>
            <guid>https://velog.io/@leave_a_comment/Next.js-12-13</guid>
            <pubDate>Wed, 30 Aug 2023 12:48:42 GMT</pubDate>
            <description><![CDATA[<p>요즘 Next.js에 관심이 많아졌는데, next12 버전의 페이지 라우터와 next13의 앱 라우터 모두 찍먹해보았다 !</p>
<p>12버전에서 13버전으로 릴리즈되면서 폴더 구조가 많이 바뀌었는데, 처음에 아주 헷갈렸다 ! (이 부분도 추후에 작성해놓아야겠다.)</p>
<p>또 12버전의 getServerSideProps와 getStaticProps 함수도 완벽하게 정복 못한채로 13버전을 맞이하게 되었다 .......</p>
<p>13버전에서는 사용하지 않는다고 하던데, 그래도 정확한 개념은 알고 넘어가야 할 것 같아서 기록해 놓으려 한다 !</p>
<p>next13에서는 data fetching을 할 때 저 두 함수 대신  fetch API 를 사용하면 된다고 한다. </p>
<pre><code>// 직접 무효화 하기 전까지는 이 request는 캐싱됨.
// `getStaticProps`와 비슷! (즉, 빌드 시점에 fetch)
// `force-cache`가 디폴트 값이므로 생략 가능
fetch(URL, { cache: &#39;force-cache&#39; });

// 매번 요청 때마다 refetch 됨.
// `getServerSideProps`와 비슷!
fetch(URL, { cache: &#39;no-store&#39; });

// 이 request는 10초동안 캐싱됨.
// This request should be cached with a lifetime of 10 seconds.
// `revalidate` 옵션을 지정한 `getStaticProps`와 비슷!
fetch(URL, { next: { revalidate: 10 } });</code></pre><p>훨씬 직관적이어진 것 같다.
또 공식 문서에서 서버 컴포넌트니, 클라이언트 컴포넌트니 구분하여 작성하는걸 권장한다고 하던데 이 부분도 추후에 개념 정리를 해놓아야겠다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React query 알고 사용하자 !! (1) - 장점, 필요한 상황]]></title>
            <link>https://velog.io/@leave_a_comment/React-query-%EC%95%8C%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90-1-%EC%9E%A5%EC%A0%90-%ED%95%84%EC%9A%94%ED%95%9C-%EC%83%81%ED%99%A9</link>
            <guid>https://velog.io/@leave_a_comment/React-query-%EC%95%8C%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90-1-%EC%9E%A5%EC%A0%90-%ED%95%84%EC%9A%94%ED%95%9C-%EC%83%81%ED%99%A9</guid>
            <pubDate>Wed, 30 Aug 2023 12:25:38 GMT</pubDate>
            <description><![CDATA[<p>요즘 핫하다는 스택들,,중 React query가 빠지지 않는 것 같아 오늘은 React query에 대해 간단하게 포스팅하려고 한다!</p>
<p>무지성으로 사용하기 전에, React query의 장단점을 알고 사용하는게 좋겠다 !</p>
<h2 id="react-query란-">React query란 ?</h2>
<ul>
<li>공식 문서에선 <strong>fetching, caching, 서버 데이터와의 동기화를 지원해주는 라이브러리</strong> 라고 한다 .</li>
</ul>
<h3 id="--특장점">- 특장점</h3>
<p><strong>데이터를 캐싱</strong>한다.
캐싱이란 특정 데이터의 복사본을 저장하여 이후 동일한 데이터의 재접근 속도를 높이는 것을 말한다.</p>
<p>같은 내용을 반복적으로 불러오는 불필요한 호출을 방지하여 서버에 대한 부하를 줄이는 것이다 !!</p>
<p>즉 필요한 상황에 적절하게 데이터를 갱신할 수 있다는 소리다 !
_(최신 데이터인지(fresh) 기존 데이터(stale)인지도 판단할 수 있다) _</p>
<h3 id="--그래서-언제-사용하냐-">- 그래서 언제 사용하냐 ???</h3>
<p>다른 블로그 글을 보면 일반적인 data fetching 솔루션에선 필요하지 않을 수 있다고 한다. 더 까다로운 사용사례를 구현할 때, 예를 들면 무한 스크롤 ??? 유용하다고 한다 :)</p>
]]></description>
        </item>
    </channel>
</rss>