<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>yu_and_hip_0.log</title>
        <link>https://velog.io/</link>
        <description>🍡 힙하게 개발하고 싶은 사람 💻</description>
        <lastBuildDate>Thu, 05 Feb 2026 15:00:59 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>yu_and_hip_0.log</title>
            <url>https://velog.velcdn.com/images/yu_and_hip_0/profile/604511ab-9879-49d2-9837-139446501257/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. yu_and_hip_0.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/yu_and_hip_0" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[컴포넌트화의 중요성 ]]></title>
            <link>https://velog.io/@yu_and_hip_0/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%ED%99%94%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1</link>
            <guid>https://velog.io/@yu_and_hip_0/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%ED%99%94%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1</guid>
            <pubDate>Thu, 05 Feb 2026 15:00:59 GMT</pubDate>
            <description><![CDATA[<h2 id="컴포넌트화">&quot;컴포넌트화&quot;</h2>
<p>아마 대부분의 프론트엔드, 아니 모든 프론트엔드가 필수적으로 알아야 할 요소라고 생각한다.</p>
<p>처음 팀원들과 프로젝트를 할 때, 이를 고려하지 않은 채로 코딩을 시작했고
그 결과 오류가 발생하면 몇 백줄에 달하는 컴포넌트를 찾아야 하는 아찔하고도 절망적인 상황을 겪어야만 했다.</p>
<p>이후로 나는 <strong>컴포넌트화</strong>의 중요성을 절실히 깨닫게 되었다.</p>
<p>그래서 컴포넌트화를 주제로 작성을 해볼려고 한다.</p>
<hr>
<blockquote>
<p><strong>컴포넌트화</strong>란?</p>
</blockquote>
<ul>
<li>UI와 그 동작을 독립적인 단위로 나누어 재사용, 유지보수, 확장이 가능하게 만드는 설계 방식이다.</li>
<li>즉, 어떤 기준으로 나누고 그 조각들을 독립적으로 관리할 수 있는 것이다.</li>
</ul>
<hr>
<blockquote>
<p>그렇다면 컴포넌트화를 하면 어떤 이점이 있는가?</p>
</blockquote>
<ul>
<li>우선 유지보수가 훨씬 쉬워진다.</li>
</ul>
<p>만약 모든 코드가 들어있는 컴포넌트에 문제가 생겨서 몇몇 코드를 건드린다면 여기저기서 난리가 날 것이다.</p>
<p>하지만, 컴포넌트를 했다면? 
문제가 있는 해당 컴포넌트 안에서만 수정할 수 있다.</p>
<ul>
<li>재사용성이 가능하다</li>
</ul>
<p>어떤 버튼이 있다고 가정해보자.</p>
<p>이 버튼은 메인 화면, 로그인 화면, 마이 페이지 화면 등등의 수많은 페이지에 사용해야한다.</p>
<p>그래서 중복으로 작성하기보다는 따로 버튼 컴포넌트를 만들어서 필요한 페이지에 import를 한다면 편리하게 버튼을 넣을 수 있고 
오류가 터져도 해당 버튼 컴포넌트만 건드리면 되기 때문에 효율적이다.</p>
<ul>
<li>각각의 역할이 분명해진다.</li>
</ul>
<p>아래 컴포넌트들을 살펴보자</p>
<pre><code>// 컴포넌트화 안한 컴포넌트

return &lt;div&gt;
        &lt;div&gt;
            &lt;div&gt;
                헤더에 관한 UI
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div&gt;
            &lt;div&gt;
                컨텐츠에 관한 UI
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div&gt;
            &lt;div&gt;
                푸터에 관한 UI
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;


// 컴포넌트화를 적용한 컴포넌트    
return &lt;div&gt;
        &lt;Header /&gt;
        &lt;Content /&gt;
        &lt;Footer /&gt;
    &lt;/div&gt;</code></pre><p>누가봐도 아래 코드가 깔끔하고, 페이지 안에 어떤 컨텐츠들이 있는지 한눈에 알아볼 수 있다.</p>
<p>이 처럼 컴포넌트화를 하면 페이지안에 어떤 역할을 맡고 있는지 확실하게 알아 볼 수 있다.</p>
<hr>
<blockquote>
<p>그렇다면... 컴포넌트화를 하는 방법이 여러가지인가?</p>
</blockquote>
<p>(이미지로 대체)
<img src="https://velog.velcdn.com/images/yu_and_hip_0/post/3eb1c2eb-548a-49c9-9eff-65139749ace4/image.jpg" alt=""></p>
<h3 id="1-atomic-design">1. Atomic Design</h3>
<ul>
<li>원자, 원소처럼 잘게 쪼개진 UI를 합쳐서 큰 단위로 만드는 디자인 시스템 기반으로 한 컴포넌트화</li>
</ul>
<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/9ada3ed2-c58a-41de-af67-91207385bce7/image.png" alt=""></p>
<ul>
<li><p>atom이 모여 Molecules가 되고, 이게 모여서 Organisms이 되고 이걸 계속해서 모으다 보면 하나의 페이지가 되는 것이다.</p>
</li>
<li><p>주로 디자인 시스템이나 공동 UI가 빈번하게 많이 사용하는 경우에 사용한다.</p>
<br>
<br>
### 2. Presentational & Container 패턴</li>
<li><p>로직을 위한 컴포넌트, UI를 위한 View 컴포넌트로 분리된 패턴.</p>
<pre><code>MainPage
├─ MainPage.container.tsx
└─ MainPage.view.tsx
</code></pre></li>
</ul>
<pre><code>- UI와 로직의 명확한 분류가 필요할 때 사용하고,container 내에 데이터를 props를 통해 각기 다른 컴포넌트에 전달할 수 있다.
- 동일한 마크업, 컨테이너 레이아웃 (header, footer)는 반복해서 작성하지 않아도 this.props.children 구현 할 수 있다.
&lt;br&gt;
&lt;br&gt;
### 3. 기능 기준 컴포넌트화
- 하나의 기능을 하나의 독립 단위로 만드는 것이다.</code></pre><p>features
 └─ login
     ├─ LoginPage.tsx
     ├─ LoginForm.tsx
     ├─ useLogin.ts
     ├─ api.ts
     └─ types.ts</p>
<pre><code>- 관련된 코드가 한 파일에 모여있기 때문에 다른 기능과의 의존성을 최소화하고 유지보수 &amp; 확장에 매우 유리한 장점을 가지고 있다.

이밖에도 레이아웃 기준, 재사용성 기준 컴포넌트화 등등의 여러 방법이 있다.

---

여러분들도 프로젝트 크기, 복잡도 등의 여러 요소를 고려하여 효율적인 컴포넌트화를 하길 바랍니다!

#### 참고 사이트

- https://velog.io/@inwoong100/React-디자인패턴-Presentational-Container-VAC패턴


</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Zustand 미들웨어]]></title>
            <link>https://velog.io/@yu_and_hip_0/Zustand-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4</link>
            <guid>https://velog.io/@yu_and_hip_0/Zustand-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4</guid>
            <pubDate>Fri, 30 Jan 2026 14:51:55 GMT</pubDate>
            <description><![CDATA[<p>이전 시간에는 기본적인 zustand 문법들을 알아보았다.</p>
<p>그럼 지금부터는 <code>Zustand middleware</code>에 대해 알아보도록 하겠다.</p>
<h3 id="zustand-middleware이란">Zustand Middleware이란?</h3>
<ul>
<li>store를 생성할 때, 기능을 추가해주는 <strong>확장 기능</strong></li>
<li>쉽게 말하자면, 크롬의 익스텐션 기능이라고 보면 된다.
<img src="https://velog.velcdn.com/images/yu_and_hip_0/post/9a9b71e6-09be-4b89-9f9e-67b26f27d25b/image.webp" alt="">
(또다른 예시로는 빅맥에 패티 추가라고 볼 수 있다!)</li>
</ul>
<p>그럼 본격적으로 어떤 미들웨어가 있는지 알아보자!</p>
<hr>
<h3 id="1-combine">1. Combine</h3>
<ul>
<li>새로운 저장소를 생성할 때 기존에 했던 state, action 함수를 다 통으로 한꺼번에 넣는 객체에 넣는 방식이 아닌, <strong>state는 state 끼리, action 함수는 actions 함수끼리 서로 분리해서 작성한 다음에 결합시키는 방식으로 스토어를 정의</strong>하는 것이다.</li>
</ul>
<pre><code>import { create } from &#39;zustand&#39;;
import { combine } from &quot;zustand/middleware&quot;;

const useCountStore = create(
    combine({ count: 0 }, (set, get) =&gt; ({
        actions: {
            increase: () =&gt; {
                set((state) =&gt; ({
                    count: state.count + 1 
            }));
        },
            decrease: () =&gt; {
                set((state) =&gt; ({
                    count: state.count - 1
            }));
            },
        },
    })), 
);</code></pre><ul>
<li>이렇게 작성할 경우, 코드가 굉장히 명확해지는 것을 볼 수 있다.</li>
</ul>
<h3 id="그렇다면-왜-state와-액션-함수들을-분리해서-store를-생성하는-이유가-무엇인가">그렇다면... 왜 state와 액션 함수들을 분리해서 store를 생성하는 이유가 무엇인가?</h3>
<ul>
<li>그건 첫 번째 인수로 전달한 <strong>state 타입이 자동으로 추론</strong>되기 때문이다.</li>
<li>이렇게 말하면 어려울까봐 전에 파트에서 다뤘던 코드를 가져와서 비교해보겠다.<pre><code>import { create } from &#39;zustand&#39;;
</code></pre></li>
</ul>
<p>type Store = {
    count : number;
    increase: () =&gt; void;
    decrease: () =&gt; void;
}</p>
<p>const useCountStore = create((set, get) =&gt; ({
    count: 0,
    increase: () =&gt; {
        const count = get().count; 
        set({ count: count + 1 }) 
        set((store) =&gt; ({
                count: store.count + 1 
        }));
    },
    decrease: () =&gt; {
        set((store) =&gt; ({
            count: store.count - 1
        }))
    },
})</p>
<pre><code>보시다시피 `type Store`으로 타입을 설정하고 있다.
하지만 combine을 하게되면 따로 입력할 필요없이 자동으로 타입을 추론해준다! **(제너릭을 사용하기 때문이다.)**

![](https://velog.velcdn.com/images/yu_and_hip_0/post/a27c0ccc-02ff-47e3-9078-e6e0b2b98fbe/image.jpeg)

#### 주의사항!
- **첫 번째 인수로 전달한 state값만 포함하는 타입만 추론!**

---

### 2. Immer
- 변경될 값을 포함한 아예 새로운 객체를 만들어서 전달을 하는 방식으로 업데이트 하는 것을 **불변성 관리**라고 한다.
- 만약 업데이트 해야하는 데이터의 구조가 중첩이 되던가 하는 복잡해지면 굉장히 코드도 복잡해진다.
![](https://velog.velcdn.com/images/yu_and_hip_0/post/076c1e88-fe27-446a-ba29-5fe7bd55c9cf/image.jpg)
➡️ 그래서 이걸 해결해주는 것이 바로 `Immer`!

#### 예시
</code></pre><p>import { create } from &#39;zustand&#39;;
import { combine } from &quot;zustand/middleware&quot;;
import { immer } from &quot;&quot;</p>
<p>const useCountStore = create(
    immer(combine({ count: 0 }, (set, get) =&gt; ({
        actions: {
            increase: () =&gt; {
                set((state) =&gt; {
                    state.count += 1
            });
        },
        decrease: () =&gt; {
            set((state) =&gt; {
                state.count -= 1
                    });
                },
            },
        })), 
    )
);</p>
<pre><code>- 만약 위의 코드에서 action이 복잡하고 `immer`이 없었다면, `count`의 업데이트 값도 복잡하게 설정 했을 것이다.

---

### 3. subscribeWithSelector
- **selector 함수를 통해서 스토어의 특정 값을 구독함으로써 해당 값이 변경될 때마다 어떠한 기능을 추가로 수행하도록 만들어주는 역할**이다.
- **React의 `useEffect`랑 비슷하다고 보면 된다.**
</code></pre><p>import { create } from &#39;zustand&#39;;
import { combine, subscribeWithSelector } from &quot;zustand/middleware&quot;;
import { immer } from &quot;&quot;</p>
<p>const useCountStore = create(
    subscribeWithSelector(
        immer(combine({ count: 0 }, (set, get) =&gt; ({
                actions: {
                    increase: () =&gt; {
                        set((state) =&gt; {
                            state.count += 1
                    });
                },
                decrease: () =&gt; {
                    set((state) =&gt; {
                        state.count -= 1
                            });
                        },
                    },
                })), 
            ),
        ),
    );</p>
<p>useCountStore.subscribe(
    (store) =&gt; store.count, // count 값이 변경될 때마다 
    (count, preCount) =&gt; { // 1번째 매개변수는 값이 변경될 때마다 업데이트된 count의 값, 두 번째 매개 변수는 이전의 count 값
        // Listner
        console.log(count, prevCount); // 로그가 출력됨</p>
<pre><code>    const store = useCountStore.getState(); // store 값을 반환
    useCountStore.setState((store) =&gt; ({ count : 10 })) // store 업데이트
}</code></pre><p>)</p>
<pre><code>---

### 4. persist
- **현재 스토어의 값을 브라우저 스토리지, 로컬 스토리지 또는 세션 스토리지에 보관하는 미들웨어**.</code></pre><p>import { create } from &#39;zustand&#39;;
import { combine, subscribeWithSelector, persist, createJSONStorage } from &quot;zustand/middleware&quot;;
import { immer } from &quot;&quot;</p>
<p>const useCountStore = create(
    persist(
        subscribeWithSelector(
            immer(combine({ count: 0 }, (set, get) =&gt; ({
                    actions: {
                        increase: () =&gt; {
                            set((state) =&gt; {
                                state.count += 1
                        });
                    },
                    decrease: () =&gt; {
                        set((state) =&gt; {
                            state.count -= 1
                                });
                            },
                        },
                    })), 
                ),
            ),
            {
                name : &quot;countStore&quot; // 어떤 이름으로 스토어를 보관할 건지 설정
                partialize: (store) =&gt; ({
                    count: store.count, // 어떤 것을 스토리지에 저장할 건지 명시
                }),
                storage: createJSONStorage(() =&gt; sessionStorage());) // 세션 스토리지에 저장
            }
        ),
    );</p>
<pre><code>- 여기서 Actions 객체는 빈 객체로만 저장되는 것을 볼 수 있는데, 로컬 스토리지에 어떠한 값을 보관할 때는 **JSON이라는 형태로 파싱을 해서 보관을 하는데 반해 자바스크립트 함수는 파싱이 이루어지지 않기 때문이다.** → 동작이 제대로 이루어지지 않을 수 있음
#### 만약 로컬 스토리지가 아닌, 세션 스토리지에 저장하고 싶다면!!?
- 위 코드처럼 **createJSONStorage**을 사용하면 된다.

---

### 5. devtools
- 개발자 도구를 통해서 스토어를 디버깅할 수 있도록 도와주는 미들웨어</code></pre><p>import { create } from &#39;zustand&#39;;
import { combine, subscribeWithSelector, persist, createJSONStorage, devtools } from &quot;zustand/middleware&quot;;</p>
<p>const useCountStore = create(
    devtools(
        persist(
            subscribeWithSelector(
                immer(combine({ count: 0 }, (set, get) =&gt; ({
                        actions: {
                            increase: () =&gt; {
                                set((state) =&gt; {
                                    state.count += 1
                            });
                        },
                        decrease: () =&gt; {
                            set((state) =&gt; {
                                state.count -= 1
                                    });
                                },
                            },
                        })), 
                    ),
                ),
                {
                    name : &quot;countStore&quot; // 어떤 이름으로 스토어를 보관할 건지 설정
                    partialize: (store) =&gt; ({
                        count: store.count, // 어떤 것을 스토리지에 저장할 건지 명시
                    }),
                    storage: createJSONStorage(() =&gt; sessionStorage());) // 세션 스토리지에 저장
                }
            ),
            {
                name: &quot;counterStore&quot;,
            }
        ),
    );</p>
<pre><code>--- 

## 주의사항
- 미들웨어를 감쌀 때, 순서를 지키면서 사용해야합니다.
- 예를 들어 persist 바깥에 combine으로 감싸면 절대 불가능이다!!
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Zustand를 복습하며..]]></title>
            <link>https://velog.io/@yu_and_hip_0/Zustand%EB%A5%BC-%EB%B3%B5%EC%8A%B5%ED%95%98%EB%A9%B0</link>
            <guid>https://velog.io/@yu_and_hip_0/Zustand%EB%A5%BC-%EB%B3%B5%EC%8A%B5%ED%95%98%EB%A9%B0</guid>
            <pubDate>Thu, 29 Jan 2026 13:16:57 GMT</pubDate>
            <description><![CDATA[<p>처음 프론트앤드로써 프로젝트를 썼을 때, 상태 관리 라이브러리로 Redux를 사용하였다.
아무래도 근본 중의 근본이기도 하고 그만큼 다운로드 수도 많기 때문에 사용했다.</p>
<p>그러나 급격한 러닝 커브, 상대적으로 무거운 특성 때문에 제대로 활용하지 못했다.</p>
<p>이후 다음 프로젝트를 위해 사용할 상태관리 라이브러리를 찾던 중에 Redux 못지않게 
많이 사용하는 Zustand를 알게되었다.</p>
<p>그리고 사용법을 익혀보고 적용해봤는데...</p>
<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/8bb562f0-09be-4c38-92ba-2a15a9d5b7c6/image.png" alt=""></p>
<p>훨씬 가벼웠고, 사용하기에도 어렵지 않았다!</p>
<p>(그렇다고 Redux가 안좋다는 것이 아닙니다!)</p>
<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/1a774357-d31e-4983-ab00-523141066470/image.jpg" alt=""></p>
<p>자 이제 복습겸 여러분에게도 zustand에 대해 알려주도록 하겠다!</p>
<hr>
<h2 id="1-create">1. Create</h2>
<ul>
<li>state, action 함수를 포함하는 객체인 store을 생성한다.</li>
</ul>
<p>아래 코드를 살펴보자.</p>
<pre><code>import { create } from &#39;zustand&#39;;

type Store = {
    count : number;
    increase: () =&gt; void;
    decrease: () =&gt; void;
}

const useCountStore = create((set, get) =&gt; ({
    count: 0,
    increase: () =&gt; {
        const count = get().count; // count가 나옴
        set({ count: count + 1 }) // count에 +1
        set((store) =&gt; ({
                count: store.count + 1 // 보통 이런 형태로 많이 사용
        }));
    },
    decrease: () =&gt; {
        set((store) =&gt; ({
            count: store.count - 1
        }))
    },
})</code></pre><ul>
<li><p>여기서 store는 <code>count</code>라는 state와 <code>increase</code>, <code>decrease</code>라는 action을 담고있다.</p>
<ul>
<li>비유하자면 store는 레스토랑 state는 현재 주문량 action은 주문을 조작하는 것이라고 볼 수 있다.</li>
</ul>
</li>
<li><p>코드를 자세히 보면 <code>get</code>, <code>set</code>이 보이는데 get의 경우 객체 형태의 스토어를 그대로 반환하는 역할이다.</p>
</li>
<li><p>set는 인수를 전달한 값으로 현재 스토어를 업데이트 시켜주는 함수이다. (함수형으로도 지원 가능)</p>
</li>
</ul>
<h3 id="자-이제-store도-만들었겠다-어떻게-적용시키냐">자 이제 Store도 만들었겠다! 어떻게 적용시키냐?</h3>
<ul>
<li>원하는 곳에 다음과 같이 입력한다.<pre><code>const { count, increase, decrease} = useCountStore()
</code></pre></li>
</ul>
<p>// p태그에 이용할 경우</p>
<p>두쫀쿠 개수 : {count}</p>

<p>// button 태그에 이용할 경우
<button onClick={decrease} >두쫀쿠 판매</button>
<button onClick={increase} >두쫀쿠 생산</button></p>
<pre><code>- 보다시피 매우 간단하게 적용이 가능하다.

### 주의사항
- Zustand는 컴포넌트에서 불러온 스토의 값들 중에 하나라도 업데이트가 되면 해당 컴포넌트를 자동으로 리렌더링을 한다.
- 즉, 컴포넌트화를 해서 부분적으로 사용할지라도 나머지도 리렌더링이 진행된다는 것이다.
- 예시처럼 가벼울 경우에는 영향은 미미하지만 컴포넌트가 무거워지면 불필요한 리렌더링이 누적되어 성능 저하에 영향을 준다.


#### 그럼 이걸 어떻게 해결해?
- 간단하다. `selecter 함수`로 설정하면 된다!
</code></pre><p>const { increase } = useCountStore((store) =&gt; store.increasse);
const { decrease } = useCountStore((store) =&gt; store.decreasse);</p>
<pre><code>이렇게 만들경우 한 가지 불편한 점이 있다.
만약 store에 7가지의 액션을 써야한다고 가정했을 때 위 코드처럼 한땀한땀 작성해야 한다.
그러면 우리는 다음과 같은 느낌을 받을 것이다.
![](https://velog.velcdn.com/images/yu_and_hip_0/post/474fd6dc-eebf-4bff-9212-cc44be4eb332/image.webp)

그래서 store를 다음과 같이 수정한다.</code></pre><p>import { create } from &#39;zustand&#39;;</p>
<p>type Store = {
    count : number;
    actions : {
        increase: () =&gt; void;
        decrease: () =&gt; void;
    }
}</p>
<p>const useCountStore<Store> = create((set, get) =&gt; ({
    count: 0,
    actions: {
            increase: () =&gt; {
            const count = get().count; // count가 나옴
            set({ count: count + 1 }) // count에 +1
            set((store) =&gt; ({
                    count: store.count + 1 // 보통 이런 형태로 많이 사용
            }));
        },
        decrease: () =&gt; {
            set((store) =&gt; ({
                count: store.count - 1
            }))
        },
    }</p>
<p>})</p>
<pre><code>그리고 적용할때 다음과 같이 사용한다.</code></pre><p>const { increase, decrease } = useCountStore((store) =&gt; store.actions);</p>
<pre><code>하지만 이런 방식은 유지보수하기가 힘들어진다.
![](https://velog.velcdn.com/images/yu_and_hip_0/post/e113ca02-1bc7-4738-b415-9c6240c7b0ad/image.jpg)

그래서 최종적으로 이런 방식을 사용하면 된다.</code></pre><p>// store에 추가</p>
<p>export const useCount = () =&gt; {
    const count = useCountStore((store) =&gt; store.count);
    return count;
};</p>
<p>export const useIncrease = () =&gt; {
    const increase = useCountStore((store) =&gt; store.actions.increaseOne);
    return increase;
};</p>
<p>export const useDecrease = () =&gt; {
    const decrease = useCountStore((store) =&gt; store.actions.decreaseOne);
    return decrease;
};</p>
<p>// 사용법</p>
<p>const increase = useIcrease();
const decrease = useDecrease();</p>
<pre><code>---

* 다음 포스트에서는 Zustand의 미들웨어에 대해 알아보겠다!




</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[상태 관리 라이브러리] 조타이, 넌 누구냐?]]></title>
            <link>https://velog.io/@yu_and_hip_0/Jotai-%EC%A1%B0%ED%83%80%EC%9D%B4-%EB%84%8C-%EB%88%84%EA%B5%AC%EB%83%90</link>
            <guid>https://velog.io/@yu_and_hip_0/Jotai-%EC%A1%B0%ED%83%80%EC%9D%B4-%EB%84%8C-%EB%88%84%EA%B5%AC%EB%83%90</guid>
            <pubDate>Mon, 10 Nov 2025 14:25:42 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/ef8b7c35-52a8-4d3e-abf9-23f359df663e/image.png" alt=""></p>
<p>react나 next.js로 프로젝트를 할 때, 항상 빠지지 않는 것이 있다.
바로 &quot;상태 관리 라이브러리&quot;</p>
<p>만약 상태 관리 라이브러리를 사용하지 않고 순수하게 한다고 했을 때는 
부모가 자식에게 내려주는 props를 개념을 이용하여 사용할 것이다.
하지만 부모에서 부모의 자식의 자식의 자식의... 구조거나 관리해야 하는 상태가 많다면 매우 복잡해질 수 있다.</p>
<p>(props drilling이 대표적인 문제 중 하나이다.)</p>
<p>그래서 등장한 것이 바로 상태 관리 라이브러리들이다.</p>
<p>상태 관리 라이브러리는 여러가지가 존재한다. 대표적으로 context API, redux, recoil, zustand 등등이 있다.
그 중에서 나는 이번 개인 프로젝트에 적용한 jotai에 대해 설명할려고 한다.</p>
<blockquote>
<p>Jotai란?</p>
</blockquote>
<p>Jotai는 앞서 말했다시피 상태 관리 라이브러리 중 하나이다.
일본어로 &quot;状態&quot; 상태를 뜻한다. (직관적인 뜻이 마음에 든다.) 그리고 react-spring을 개발한 Pmndrs 팀에서 만든 것이다.</p>
<blockquote>
<p>Jotai의 특징</p>
</blockquote>
<ol>
<li><p>원자 기반 상태관리 </p>
<p>Jotai는 atom이라는 것을 사용한다.
atom... 학창시절 우리가 배웠던 그 원자가 맞다.
Jotai는 이러한 느낌으로 쪼개서 관리를 한다.</p>
</li>
<li><p>간단하고 가벼움
또한 Jotai는 사용법을 보면 알 수 있듯이, atom으로 정의하고   useAtom으로 사용하는 매우 쉽고 간단하게 사용할 수 있다.</p>
</li>
</ol>
<blockquote>
<p>Jotai의 사용법</p>
</blockquote>
<h2 id="설치">설치</h2>
<pre><code>npm install jotai

yarn add jotai
</code></pre><h2 id="가장-기본적인-형태">가장 기본적인 형태</h2>
<pre><code>import { atom, useAtom } from &#39;jotai&#39;;

const countAtom = atom(0)

function BasicStep() {
    const [count, setCount] = useAtom(countAtom);

    return (

        &lt;div&gt;
            &lt;p&gt;{count}&lt;/p&gt;
            &lt;button onClick={() =&gt; setCount(count + 1)}&gt;플러스&lt;/button&gt;
        &lt;/div&gt;
    );
}</code></pre><p>여기서 atom안에 초기값을 넣어서 상태를 만든다.
그리고 useAtom(atom)을 이용해서 atom을 읽거나 업데이트 하도록 한다.</p>
<h3 id="읽기-전용-atom">읽기 전용 atom</h3>
<pre><code>const countAtom = atom(10);
const doubleAtom = atom((get) =&gt; get(countAtom) * 2);</code></pre><pre><code>const [doublescore] = useAtom(doubleAtom);
console.log(doublescore); // 20</code></pre><p>여기서 get()은 다른 atom값을 읽을 때 사용한다.
그리고 코드안의 doubleAtom은 읽기 전용이다. (즉, set 불가)
여기서 get(countAtom)으로 새로운 형태로 만들 수 있다.</p>
<h3 id="쓰기-전용-atom">쓰기 전용 atom</h3>
<pre><code>const countAtom = atom(10);
const plusAtom = atom(
  null, // 읽기 없음
  (get, set) =&gt; set(countAtom, get(countAtom) + 1)
);
</code></pre><p>첫번째 인자는 읽기이지만 null을 넣었으므로 여기에는 읽기 기능이 없다.
두번째 인자를 보면 (get, set)이 보이는데 이건 다른 atom을 읽고 수정이 가능하다.</p>
<pre><code>const [, plus] = useAtom(incrementAtom);
plus(); // count + 1
</code></pre><h3 id="읽기--쓰기-모두-가능한-atom">읽기 + 쓰기 모두 가능한 atom</h3>
<pre><code>const count = atom(0);

const double = atom(
    (get) =&gt; get(count) * 2,
    (get, set, newValue) =&gt; set(count, newValue / 2)
);</code></pre><p>읽을 때는 두배값을 반환하고 쓸때는 새로운 값의 절반을 count에 저장한다.</p>
<h3 id="비동기에서의-atom">비동기에서의 atom</h3>
<pre><code>const fetchAtom = atom(async() =&gt; {
    const res = await fetch(&quot;...&quot;);
    const data = await res.json();
});</code></pre><pre><code>function User() {
    const [user] = useAtom(fetchAtom);

    return &lt;div&gt;{user.name}&lt;/div&gt;;
};</code></pre><p>여기서 주의할 점은 suspense로 감싸줘야 한다.</p>
<pre><code>&lt;Suspense fallback={&lt;p&gt;로딩중&lt;/p&gt;}&gt;
  &lt;User /&gt;
&lt;/Suspense&gt;
</code></pre><h3 id="atom-파생">atom 파생</h3>
<p>다른 atom들을 가지고 파생 상태를 만들어 줄 수 있다.</p>
<pre><code>const one = atom(1);
const anotherone = atom(2);

const total = atom((get) =&gt; get(one) * get(anotherone));</code></pre><p>여기서 <code>one</code> 또는 <code>anthorone</code>의 값이 바뀌면 <code>total</code>이 자동으로 갱신된다.</p>
<h3 id="provider">Provider</h3>
<p>Provider는 atom들이 공유되는 범위를 결정한다.
예를 들면 layout.tsx에 <code>&lt;Provider&gt;&lt;/Provider&gt;</code>로 감싼다면 그 안에 있는 것은 서로 공유가 된다.</p>
<p>그렇다면 아예 Provider를 안쓰면 어떻게 될까?
놀랍게도 기본 Provider이 자동 생성되어 작동이 된다. 
(그 대신 내가 직접 제어할 수 없는 단점이 존재한다.)</p>
<p>그럼 또 다른 생각이 들것이다.
&quot;나는 이 하나의 값을 독립적으로 관리하고 싶다.&quot;
그렇다면 개별적으로 provider을 만들어주면 된다.</p>
<p>쉽게 예를 들자면 <code>const thisIsBool = atom(true);</code>가 있다고 가정해보자. 
만약 provider를 사용하고 있지않거나 공통으로 provider로 묶었다면 어느 컴포넌트 안에 있는 atom 상태가 변경되면 동일한 atom을 사용하고 있는 곳도 같이 변경된다.
이걸 막기 위해서는 하나로 묶지말고 개별적으로 묶어주면 된다.</p>
<pre><code>import { Provider, createStore, atom, useAtom } from &#39;jotai&#39;;

const booleanJotai = atom(false);
const store1 = createStore():
const store2 = createStore();

function Bool() {
    const [isBoolean, setIsBoolean] = useAtom(booleanJotai);
    return (
        &lt;div&gt;
            &lt;p&gt;{isBoolean}&lt;/p&gt;
        &lt;/div&gt;
        )
 };</code></pre><pre><code>export default function App() {
    return (
        &lt;div&gt;
            &lt;Provider store={store1} &gt;
                &lt;Bool /&gt;
            &lt;/Provider&gt;

            &lt;Provider store={store2} &gt;
                &lt;Bool /&gt;
            &lt;/Provider&gt;
        &lt;/div&gt;
    )};</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[PKCE란 무엇일까?]]></title>
            <link>https://velog.io/@yu_and_hip_0/PKCE%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@yu_and_hip_0/PKCE%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Tue, 28 Oct 2025 08:20:43 GMT</pubDate>
            <description><![CDATA[<p>일단 픽시 인증방식을 이야기하기 전에 먼저 <strong>OAuth 2.0</strong>을 알아야한다.</p>
<blockquote>
<p>OAuth 2.0 이란, &quot;<strong>서드파티 애플리케이션이 사용자 비밀번호를 직접 받지 않고, 사용자의 자원에 제한된 접근 권한을 얻도록 허용하는 표준 인증 프로토콜</strong>&quot;</p>
</blockquote>
<p>좀 더 쉽게 말하면, _<strong>나의 계정정보를 안줘도 앱이 내 데이터 일부를 쓸 수 있게 허락</strong>_해주는 것이다.</p>
<p>예를 들어, </p>
<p>구글 계정으로 로그인 / 카톡 계정으로 로그인 / 스포티파이 계정으로 로그인
위 예시는 비밀번호를 직접 주지 않아도 다른 앱이 권한만 위임을 받는 방식이다.
이것들이 모두 OAuth 방식을 이용한 것이다!</p>
<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/f333e4b1-754e-4822-9950-5002290e48eb/image.png" alt=""></p>
<blockquote>
<p>하지만 여기서 <strong>문제</strong>가 발생한다.</p>
</blockquote>
<p>기존의 OAuth에서는 클라이언트 시크릿(client secret) 이라는 비밀키를 써서 인증을 한다.
물론 서버에서 안전하게 숨겨둘 때는 좋다.</p>
<p>But, 모바일 앱 / 브라우저 같은 환경에서는 숨길 수가 없다.
그냥 코드까면 다 나오니까!!
그래서 항상 해킹에 위험에 도사리고 있는 것은 덤이다.</p>
<blockquote>
<p>그래서 이 문제를 해결하기 위해서 등장한 것이 오늘 설명할 주제인 PKCE이다.</p>
</blockquote>
<p>그렇다면 PKCE란 무엇일까?
풀어서 보면 <em>Proof Key for Code Exchange</em> 이다.
말그대로 &quot;코드 교환시 증명키를 사용한다.&quot;라는 것이다.</p>
<blockquote>
<p>다음은 PKCE가 동작하는 원리를 설명할 것이다.</p>
</blockquote>
<p>PKCE는 2개의 비밀 문자열을 만든다.</p>
<ol>
<li><strong>Code Verifier</strong> : 진짜 비밀키 (앱이 임시로 생성)</li>
<li><strong>Code Challenge</strong> : Code Verifier를 해쉬(sha-256)한 값</li>
</ol>
<p>즉 몇번꼬아서 만든 버전으로 주는 것이라고 생각하면 편하다.</p>
<p>그 다음 사용자가 로그인을 시도할 경우, 앱은 Code Challenge를 함께 서버로 보낸다.</p>
<p>그리고 로그인에 성공했을 경우 서버는 임시 권한 코드를 앱에 돌려준다.</p>
<p>이제 앱이 accesstoken을 받으려면 내가 가진 Code Verifier를 내면, 서버는 Code Challenge와 비교하여 같으면 accesstoken을 발급해준다.</p>
<blockquote>
<p>그런데 굳이... 이렇게 까지 하는 이유가 뭐지?</p>
</blockquote>
<p>바로 가로채도 쓸모 없게 만들기 위함이다.
만약 누군가 임시 권한 코드를 가로채도 code_verifier라는 열쇠가 있어야 하는데, 
그건 클라이언트만 알고 있기 때문에 안전하게 관리할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] Skeleton UI]]></title>
            <link>https://velog.io/@yu_and_hip_0/Next.js-Skeleton-UI</link>
            <guid>https://velog.io/@yu_and_hip_0/Next.js-Skeleton-UI</guid>
            <pubDate>Mon, 06 Oct 2025 13:12:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/90b1d773-0f26-4544-bc09-3476b3730e9f/image.png" alt=""></p>
<h2 id="skeleton-ui-이란">Skeleton UI 이란?</h2>
<p>콘텐츠가 아직 준비되지 않은 상태에서 실제 레이아웃을 닮은 뼈대 형태의 화면을 미리 보여주는 UI 패턴. </p>
<p>마치 완성된 화면의 스케치 버전을 보여주는 것이다.
아래 사진들을 보자.</p>
<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/dd9c45d3-a971-4b43-858a-2a7dbc004f88/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/88e31b95-ea65-4357-8df2-916dc07467fa/image.png" alt=""></p>
<p>위 사진은 페이스북, 아래 사진은 유튜브의 스켈레톤 UI이다.
이 처럼 주변에 스켈레톤 UI를 많이 사용하는 것을 볼 수 있다.</p>
<h2 id="그럼-왜-사용하는-걸까">그럼 왜 사용하는 걸까?</h2>
<br/>

<p>사용자에게 편안함을 느끼게 해주고, 화면이 매끄럽게 만들기 위해서다.</p>
<p>만약 스켈레톤 UI이 아닌, 빈 화면 또는 화면 가운데에 로딩 스피너만 있다면 보는 사용자는 심리적으로 불안감과 불편함을 느낄 것이다.</p>
<h2 id="nextjs에-적용하는-방법">Next.js에 적용하는 방법</h2>
<br/>

<p>먼저 적용하고자 하는 페이지를 참고하여 그 컴포넌트에 대한 스켈레톤 UI 스타일을 만들어준다. (css는 생략)</p>
<pre><code>// card.tsx

import style from &#39;./card.module.css&#39;

export default function Card() {
    return &lt;div className={style.container}&gt;
        &lt;div className={style.cardimage}&gt;&lt;/div&gt;
        &lt;div className={style.cardinfo}&gt;
            &lt;div className={style.cardtitle}&gt;&lt;/div&gt;
            &lt;div className={style.cardauthor}&gt;&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;;
}
</code></pre><br/>

<p>그리고나서 스트리밍을 위해 작성했던 Suspense의 fallback에 추가해준다.</p>
<pre><code>examplecomponent.tsx

import { Suspense } from &quot;react&quot;;
import Card from &quot;./card&quot;;         
import SkeletonCard from &quot;./skeleton-card&quot;;

export default function ExampleComponent() {
  return (
    &lt;Suspense fallback={&lt;SkeletonCard /&gt;}&gt;
      &lt;Card/&gt;
    &lt;/Suspense&gt;
  );
}</code></pre><p>이렇게 진행했으면 데이터를 부르는데 시간이 걸리는 컨텐츠는 스켈레톤 UI로 표시되는 것을 볼 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스트리밍 ]]></title>
            <link>https://velog.io/@yu_and_hip_0/%EC%8A%A4%ED%8A%B8%EB%A6%AC%EB%B0%8D</link>
            <guid>https://velog.io/@yu_and_hip_0/%EC%8A%A4%ED%8A%B8%EB%A6%AC%EB%B0%8D</guid>
            <pubDate>Thu, 02 Oct 2025 17:34:35 GMT</pubDate>
            <description><![CDATA[<h2 id="스트리밍이란">스트리밍이란?</h2>
<p>서버가 데이터를 조각 단위로 생성되는 대로 실시간 전송하여 브라우저가 부분적으로 먼저 렌더링할 수 있게 해주는 기능이다.</p>
<h3 id="그럼-굳이-이것을-하는-이유">그럼 굳이 이것을 하는 이유?</h3>
<p>넷플릭스를 예로 들어보자.
우리가 보고싶은 영화나 드라마가 있으면 해당 페이지에 존재하는 시청 버튼을 누를 것이다.
그 후 서버에서 영상을 불러와 클라이언트까지 넘어올 것이다.</p>
<p>그러나 동영상은 용량이 매우 크기 때문에 불러오기에는 시간이 많이 소요될 것이고, 결과적으로 사용자도 영상을 보기까지 시간이 오래 소요될 것이다.</p>
<p>그렇다면 사용자에게 빨리 영상을 보여주는 방법은 어떤것이 있을까?
바로 동영상을 조각조각 나누어서 사용자에게 보내주는 것이다.
그렇게하면 영상의 불러오는 시간을 줄이고, 끊김없이 볼 수 있다.</p>
<h3 id="nextjs에서-스트리밍은">Next.js에서 스트리밍은?</h3>
<p>Next.js에서도 이 스트리밍을 적용할 수가 있다.
만약 백엔드로부터 받는 데이터가 크다면, 사용자는 앞서 설명한 넷플릭스의 경우처럼 오랜 시간을 기다려야 한다.</p>
<p>그래서 데이터를 여러조각으로 나누어서 보내주는 것 처럼, 레이아웃 같은 비교적 빠르게 불러올 수 있는 컨텐츠는 먼저 보여주고 오래 걸릴 것 같은 컨텐츠의 경우에는 스켈레톤 UI와 같은 로딩화면을 이용해서 나중에 보여주도록 하는 것이다.</p>
<p>쉽게 설명하자면 레이아웃과 같은 빠르게 불러올 수 있는 컨텐츠를 에피타이저, 오래 걸릴 것 같은 컨텐츠는 에피타이저가 나오는 동안 조리되는 메인 디시라고 보면 된다.</p>
<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/ebbcf880-4abe-48c3-b6fa-ffdc1b67330d/image.png" alt=""></p>
<h3 id="nextjs-스트리밍-적용-방법">Next.js 스트리밍 적용 방법</h3>
<p>2가지 방식이 있다.</p>
<h3 id="1-loadingtsx-이용-라우트-단위">1. Loading.tsx 이용 (라우트 단위)</h3>
<p>먼저 내가 스트리밍을 적용하고자 하는 Page.tsx와 같은 위치 <strong>Loading.tsx</strong>를 만들어준다. </p>
<p>그 다음 로딩화면 코드를 작성해주면 된다.</p>
<br/>

<pre><code>// loading.tsx

export default function Loading() {
  return &lt;div&gt;... 로딩중입니다.&lt;div&gt;;
}
</code></pre><p>여기서 이 로딩 컴포넌트를 적용하기 위한 총 4가지 주의사항이 있다.</p>
<h4 id="1-페이지-컴포넌트이어야-할-것">1. 페이지 컴포넌트이어야 할 것.</h4>
<h4 id="2-반드시-async-함수일-필요는-없지만-fetch-같은-비동기-동작이-있을-때-로딩이-표시된다">2. 반드시 async 함수일 필요는 없지만, fetch 같은 비동기 동작이 있을 때 로딩이 표시된다.</h4>
<h4 id="3-쿼리스트링이-변경될-경우에-페이지-자체가-재렌더링이-되지-않아서-적용이-잘-안보일-수-있다">3. 쿼리스트링이 변경될 경우에 페이지 자체가 재렌더링이 되지 않아서 적용이 잘 안보일 수 있다.</h4>
<h4 id="4-하위-파일도-같이-적용된다">4. 하위 파일도 같이 적용된다.</h4>
<p>그러나 어떤 사람들은 쿼리스트링이 갱신되는 중에 스트리밍을 넣고 싶고,Page.tsx말고 다른 컴포넌트에 적용시키고 싶을 것이다.</p>
<p>그런 경우, 아래 방법을 이용하면 된다.</p>
<h3 id="2-suspense-이용-컴포넌트-단위">2. Suspense 이용 (컴포넌트 단위)</h3>
<p>다음과 같이 적용해주면 끝이다.</p>
<pre><code>&lt;Suspense key={} fallback={}&gt;
    &lt;스트리밍 적용하고 싶은 컴포넌트 /&gt;
&lt;Suspense /&gt;</code></pre><p>여기서 fallback은 로딩 상태일 때, 어떤 것을 보여줄지 하는 것이다.
그래서 보통 유튜브, 페이스북과 같이 스켈레톤 UI를 사용한다.</p>
<p>그리고 Suspense는 기본적으로 한 번 로딩이 끝나면 fallback을 다시 안 보여주기 때문에 key값을 넣어줌으로써, key 값이 변하면 새로운 컴포넌트로 인식하여 fallback을 다시 실행한다.</p>
<p>즉, 쿼리스트링이 변화되는 페이지일 경우 사용한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CSR , SSR , SSG , ISR]]></title>
            <link>https://velog.io/@yu_and_hip_0/CSR-SSR-SSG-ISR</link>
            <guid>https://velog.io/@yu_and_hip_0/CSR-SSR-SSG-ISR</guid>
            <pubDate>Thu, 25 Sep 2025 17:36:13 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/c0709250-6db0-4164-9247-a91ad0b40181/image.png" alt=""></p>
<h2 id="1-client-side-rendering-csr">1. Client-Side-Rendering (CSR)</h2>
<blockquote>
<p>빈 화면을 사용자의 브라우저에서 JavaScript가 직접 그리는 방식</p>
</blockquote>
<p>사용자가 페이지를 요청할 때, 먼저 서버가 비어있는 HTML을 브라우저 전달하고 사용자에게 보여준다. 그 뒤에 서버에서 JS Bundle을 브라우저에 전달하고 실행해서 화면에 그려준다.</p>
<p>대표적인 장점은 빠른 페이지 전환 가능, 서버 부하 감소가 있다.</p>
<p>반면 단점은 받아오는 데이터량이 많을 경우에는 초기 로딩 시간(First Contentful Paint)이 오래 걸린다.</p>
<br/>

<h2 id="2-server-side-rendering-ssr">2. Server-Side Rendering (SSR)</h2>
<blockquote>
<p>사용자가 웹페이지를 요청하면 서버가 미리 HTML을 완전히 구성해 브라우저로 전달하고, 브라우저는 이를 바로 표시하는 렌더링 방식.</p>
</blockquote>
<p>자세히 설명하자면, 사용자가 접속 요청을 보내면 서버가 HTML을 사전 렌더링을 진행하여 즉각적으로 브라우저로 전달한다. 하지만, 이 것은 이벤트와 같은 상호작용이 없는 상태이다. 그러므로 서버는 JS 번들을 브라우저에 전달하고 HTML에 Hydration을 통해 클라이언트에서 인터렉션한다.</p>
<p>장점은 앞서 설명했던 CSR의 단점인 초기 로딩 시간(First Contentful Paint)을 효과적으로 줄일 수 있다.</p>
<p>단점은 수많은 데이터를 불러올 때는 시간이 오래 걸린다는 것이다.</p>
<br/>

<h2 id="3-static-site-generation-ssg">3. Static Site Generation (SSG)</h2>
<blockquote>
<p>서버(또는 빌드 서버)가 빌드 시점에 미리 HTML을 만들어 놓고,
사용자 요청 시 서버는 정적 파일 그대로 전달하는 렌더링 방식.</p>
</blockquote>
<p>사용자 요청 전에 빌드 도구(Next.js, Nuxt 등)가 데이터를 미리 가져와 페이지별 HTML을 미리 생성 함(빌드 타임) 그 뒤에 사용자 요청 시, 미리 만든 HTML을 제공하고 SSR과 동일하게 JS번들등을 브라우저에 제공하여 HTML에 Hydration을 통해 클라이언트에서 인터렉션한다.</p>
<p>장점은 빌드 타임을 통해 미리 대량의 데이터를 받기 때문에 서버로 대량의 데이터를 받아와도 로딩 시간이 오래 걸리지 않는다.</p>
<p>단점은 아무리 데이터가 새롭게 업데이트해도 최신버전으로 반영이 안된다는 것이다.</p>
<br/>

<h2 id="4-incremental-static-regeneration-isr">4. Incremental Static Regeneration (ISR)</h2>
<blockquote>
<p>SSG의 확장 개념으로 SSG처럼 빌드 타임에 HTML을 미리 생성 특정 주기에 따라 요청 시, 서버에서 다시 HTML을 재생성해 점진적으로 업데이트를 하는 방식</p>
</blockquote>
<p>초기에는 SSG와 동일하게 정적 HTML로 먼저 빌드한다. 그 다음 사용자가 페이지를 접속하면 기존에 캐싱된 HTML을 그대로 응답한다. 그러나 만약 재생성 주기(revalidate)를 초과한 이후에 접속을 했다면 백그라운드에서 새 HTML을 저장하고, 다음 요청부터는 새로 제공된 HTML을 제공한다.</p>
<p>장점은 SSG처럼 빠르게 대량의 데이터를 받을 수 있으면서도, SSG의 단점인 최신 버전으로 반영되지 않는 점을 보완할 수 있다.</p>
<p>반면 단점은 설정한 주기에 따라서 업데이트가 되기 때문에 게시글 등록과 같은 즉각적인 데이터 반영이 필요한 페이지에서는 적합하지 않는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] 페이지 별 개별 레이아웃 설정하기]]></title>
            <link>https://velog.io/@yu_and_hip_0/Next.js-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%B3%84-%EA%B0%9C%EB%B3%84-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yu_and_hip_0/Next.js-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%B3%84-%EA%B0%9C%EB%B3%84-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 09 Sep 2025 09:58:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🚨 이 글은 Next.js 14 버전으로 설명하고 있어, 최신 버전과 다를 수 있습니다. </p>
</blockquote>
<p>어느 홈페이지를 보면 어디 페이지에서는 특정 레이아웃이 있는데, 또 다른 페이지에는 특정 레이아웃이 없는 것을 확인할 수 있다.</p>
<p>우리도 한번 개별 레이아웃을 적용해보자.</p>
<p>먼저 레이아웃 컴포넌트를 만들어보자.
나는 검색바 레이아웃을 만들어 보았다.</p>
<pre><code>// searchBar.tsx

export default function SearchableLayout({
  children,
}: {
  children: ReactNode;
}) {

  return (
    &lt;div&gt;
      &lt;div className={style.searchbar_container}&gt;
        &lt;input placeholder=&quot;검색어를 입력하세요 ...&quot;/&gt;
        &lt;button&gt;검색&lt;/button&gt;
      &lt;/div&gt;
      {children}
    &lt;/div&gt;
  );
}
</code></pre><br/>

<p>그 다음 이 검색바를 넣을 컴포넌트에 다음과 같이 작성을 한다.
나는 메인 페이지의 역할을 하는 Home에 넣어보기로 했다.</p>
<pre><code>import SearchableLayout from &quot;@/components/searchable-layout&quot;;
import style from &quot;./index.module.css&quot;;
import { ReactNode } from &quot;react&quot;;

export default function Home() {
  return (
    &lt;&gt;
      &lt;div&gt;여기는 메인화면 입니다.&lt;/div&gt;
    &lt;/&gt;
  );
}

Home.getLayout = (page : ReactNode) =&gt; {
  return &lt;SearchableLayout&gt;{page}&lt;/SearchableLayout&gt;;
};
</code></pre><p>여기서, <code>getLayout</code> 라는 메서드를 넣을 수 있는 이유는 <em><strong>컴포넌트도 하나의 객체</strong></em> 이기 때문이다. </p>
<p>그 다음, _app.tsx로 들어가 다음과 같이 작성해준다.</p>
<pre><code>import GlobalLayout from &quot;@/components/global-layout&quot;;
import &quot;@/styles/globals.css&quot;;
import { NextPage } from &quot;next&quot;;
import type { AppProps } from &quot;next/app&quot;;
import { ReactNode } from &quot;react&quot;;

type NextPageWithLayout = NextPage &amp; {
  getLayout?: (page: ReactNode) =&gt; ReactNode;
};

export default function App({
  Component,
  pageProps,
}: AppProps &amp; { Component: NextPageWithLayout }) {
  const getLayout = Component.getLayout ?? ((page: ReactNode) =&gt; page);

  return &lt;GlobalLayout&gt;{getLayout(&lt;Component {...pageProps} /&gt;)}&lt;/GlobalLayout&gt;;
}</code></pre><br/>

<p>여기서 <code>getLayout</code> 를 보자면 개별 레이아웃이 지정하지 않는 페이지가 있을 경우 undefined 값이 나올 수 있으므로 <code>((page: ReactNode) =&gt; page)</code> 를 작성해 그대로 페이지가 나오도록 한다.</p>
<p>그 다음<code>getLayout</code> 는 임의로 만들어진 것 즉, 기본 타입에는 없기 때문에 _app 컴포넌트에는 인식할 수가 없다. 따라서 이 메서드를 알려주기 위해, type 형태의 <code>NextPageWithLayout</code> 를 만들고, <code>Component: NextPageWithLayout</code> 를 작성하여 <code>AppProps</code> 의 타입을 확장시켰다.</p>
<p>그리고 실행해보면...</p>
<hr>
<p>검색바 레이아웃이 적용된 페이지
<img src="https://velog.velcdn.com/images/yu_and_hip_0/post/0a357974-de43-451a-baa2-f092c42a1da4/image.png" alt="검색바 레이아웃이 적용된 페이지"></p>
<hr>
<p>검색바 레이아웃이 적용되지 않은 페이지
<img src="https://velog.velcdn.com/images/yu_and_hip_0/post/560c0998-e1fd-4f34-bc32-084fe207bbc3/image.png" alt=""></p>
<hr>
<p>이렇게 원하는 페이지에 개별 레이아웃을 지정했지만 한 가지 궁금증이 생긴다.
&quot;그냥 리액트처럼 하면 되는거 아닌가?&quot;
아래 코드처럼 말이다.</p>
<pre><code>import SearchableLayout from &quot;@/components/searchable-layout&quot;;
import style from &quot;./index.module.css&quot;;

export default function Home() {
  return (
    &lt;&gt;
      &lt;div&gt;여기는 메인화면 입니다.&lt;/div&gt;
      &lt;searchBar/&gt;
    &lt;/&gt;
  );
}</code></pre><p>물론 이 방법도 가능하다.
하지만 여기서 치명적인 단점이 하나 존재하는데, 그것은 페이지 이동시 레이아웃 컴포넌트가 무조건 언마운트가 된다는 점이다.</p>
<p>이 말은 즉슨 동일한 레이아웃을 사용하고 있는 페이지로 이동할지라도 무조건 레이아웃이 언마운트 되었다가 다시 마운트를 하기 때문에 불필요한 렌더링 리소스를 낭비하게 된다.</p>
<p>(이에 따라, 레이아웃에 존재하는 state도 초기화가 된다.)</p>
<p>하지만 위와 같이 getLayout를 사용할 경우에는 이러한 문제가 발생하지 않는다. 
이유는 페이지 이동이 일어나도 App 컴포넌트는 언마운트가 되는 것이 아닌, 업데이트만 하기 때문이다.
즉, 페이지 이동간 적은 리소스 렌더링으로만 처리할 수 있고, state 초기화까지 방지할 수 있다.</p>
<hr>
<p>📖 참고 문헌</p>
<p><a href="https://www.inflearn.com/courses/lecture?courseId=333250&amp;type=LECTURE&amp;unitId=237807&amp;subtitleLanguage=ko&amp;tab=QnA&amp;category=questionDetail&amp;q=1360976">왜 개별 레이아웃을 사용하는 이유</a></p>
<p><a href="https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-nextjs">한 입 크기로 잘라먹는 Next.js</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] 렉시컬 환경 (+ 클로저)]]></title>
            <link>https://velog.io/@yu_and_hip_0/JS-%EB%A0%89%EC%8B%9C%EC%BB%AC-%ED%99%98%EA%B2%BD</link>
            <guid>https://velog.io/@yu_and_hip_0/JS-%EB%A0%89%EC%8B%9C%EC%BB%AC-%ED%99%98%EA%B2%BD</guid>
            <pubDate>Tue, 27 May 2025 03:44:41 GMT</pubDate>
            <description><![CDATA[<p>나는 자바 스크립트를 공부하면서, 특정 구간마다 벽을 느꼈는데 그 중 하나가 바로 이 렉시컬 환경 (+ 클로저)였다.</p>
<blockquote>
<p>렉시컬 환경(Lexical Environment)??</p>
</blockquote>
<p>렉시컬 환경은 변수 또는 함수가 선언되는 위치에 따라서 범위가 결정되는 것을 말한다.</p>
<p>이해를 쉽게 하기 위해서 아래 케이스들을 살펴 보겠다.</p>
<hr>
<h2 id="케이스-1--변수">케이스 1 : 변수</h2>
<p>설명하기 전에 실행중인 함수/코드 블록/스크립트 전체는 렉시컬 환경을 가지게 된다.</p>
<p>이 렉시컬 환경은 2가지로 나누어지는데,</p>
<ol>
<li><p>환경 레코드 (Environment Record)
&ensp; 모든 지역변수를 프로퍼티로 저장하고 있는 객체</p>
</li>
<li><p>외부 렉시컬 환경 (Outer Lexical Environment) 
&ensp; 자신의 바깥 스코프를 접근할 수 있는 곳 </p>
</li>
</ol>
<p>먼저 변수의 경우, <em><strong>환경 레코드의 단순한 프로퍼티</strong></em> 이다.</p>
<p>이와 같은 이유로 변수를 가져오거나 변경하는 행위는 환경 레코드의 프로퍼티를 가져오는 것 또는 변경하는 것과 같은 의미라고 할 수 있다.</p>
<p>아래 코드를 살펴 보겠다.</p>
<pre><code>const name = potato;

alert(&#39;name&#39;);</code></pre><p>앞서 말했듯이 이 코드에는 환경 레코드, 외부 렉시컬 환경이 존재한다.</p>
<p>(참고로 이 코드는 스크립트 전체와 관련되어 있는데, 이것을 <strong>전역 렉시컬 환경(global Lexical Environment)</strong>이라고 부른다.)</p>
<p>여기서 환경 레코드에는 &#39;name : potato&#39;라고 저장될 것이다.
반면, 외부 렉시컬 환경은 외부 참조를 가지고 있지 않기 때문에 
null을 가리킬 것이다.</p>
<hr>
<p>이번에는 다른 코드로 예를 들어보겠다.</p>
<pre><code>js execution start

let name;

name : Tom;

name : potato;
</code></pre><p>먼저 스크립트가 실행하는 순간, 스크립트 내에서 선언한 변수 전체가 렉시컬 환경에 올라가게 된다.</p>
<p>그 다음 &#39;let name&#39;을 만나게 되는데, 아직 값을 할당하지 않았기 때문에 프로퍼티 값은 undefined이다. (name : undefined)</p>
<p>바로 아래에 값이 할당되고, 이에 따라 프로퍼티 값도 할당이 된다.
(name : Tom)</p>
<p>마지막으로 potato로 값이 변경되는데 역시나 프로퍼티 값도 동일하게 변경된다. (name : potato)</p>
<h3 id="요약하자면">요약하자면...</h3>
<ol>
<li>변수는 환경 레코드의 프로퍼티라고 생각하면 된다. </li>
<li>변수의 값을 변경하거나, 할당하면 환경 레코드의 프로퍼티도 변경된다.</li>
</ol>
<hr>
<h2 id="케이스-2--함수-선언문">케이스 2 : 함수 선언문</h2>
<p>함수도 변수와 마찬가지로 값이다.</p>
<p>그러나 함수 선언문 (function...) 으로 선언한 함수는 변수와 달리 바로 초기화가 된다는 점에서 차이점이 있다.</p>
<p>let을 만나 선언이 되기 전까지는 사용할 수 없는 변수와 달리, 함수 선언문으로 사용한 함수는 렉시컬 환경이 만들어지는 동시에 사용할 수 있기 때문이다.</p>
<p>쉽게 이해하기위해 아래 코드를 살펴보자 </p>
<pre><code>let name : potato;

function declare(say) {
    alert(`${name}, ${say}`);
 }</code></pre><p>스크립트가 실행되는 순간, 전역 렉시컬 환경 상태는 
함수는 &#39;declare : function&#39;의 프로퍼티 형태로 저장된다.</p>
<p>(여기서 주의할 점은 &#39;let declare = function(say)...와 같이 <strong>함수를 변수에 할당하는 함수 표현식</strong>에는 포함되지 않는다.)</p>
<hr>
<h2 id="케이스-3--내부--외부-렉시컬-환경">케이스 3 : 내부 &amp; 외부 렉시컬 환경</h2>
<p>함수를 호출하여 실행하면 새로운 렉시컬 환경이 자동으로 만들어지고,
이 렉시컬 환경에 함수 호출 시 넘겨받은 매개변수 및 함수의 지역 변수가 저장된다. </p>
<p>아래 코드를 살펴보자</p>
<pre><code>let animal = bird;

function feed(food) {
    alert (`${animal}가 ${food}을 먹습니다.`);
}

feed(nut); // bird가 nut을 먹습니다.</code></pre><p>함수가 호출 중엔 호출중의 함수를 위한 <strong>내부 렉시컬 환경</strong>, <strong>외부 렉시컬 환경</strong>을 갖게된다.</p>
<p>내부 렉시컬 환경에서는 <strong>&#39;food : nut&#39;</strong> 라는 프로퍼티가 존재한다.</p>
<p>외부 렉시컬 환경에서는 함수와 변수의 프로퍼티를 갖게 된다.
<strong>(&#39;feed : function&#39;, &#39;animal : bird&#39;)</strong></p>
<p>(내부 렉시컬 환경은 외부 렉시컬 환경에 대한 참조를 갖게된다.)</p>
<p>이를 통해 알 수 있는 것은 <strong>코드에서 변수를 접근할 때, 우선 내부 렉시컬 환경을 검색 범위로 찾아보고 만약 원하는 변수를 찾지 못했을 때는 범위를 내부 렉시컬 환경이 참조하는 외부 렉시컬 환경까지 확장을 시킨다.</strong></p>
<p><strong>이러한 과정은 검색 범위가 전역 렉시컬 환경으로 확장 될 때까지 지속된다.</strong>
(쉽게 설명하면, 내부 렉시컬 환경에서 변수를 찾는데 없으면 계속해서 외부로 넓혀간다고 생각하면 이해하기 쉬울 것이다.)</p>
<p>즉, 위의 코드를 자세히 정리하자면</p>
<p>함수 feed 내부의 alert에서 변수 &#39;food&#39;를 접근할 때 먼저 내부 렉시컬 환경에서 변수 food를 찾는다.</p>
<p>그 다음, alert에서 변수 &#39;animal&#39;에 접근하려는데 내부 렉시컬 환경에는 프로퍼티가 존재하지 않는다. 
따라서 외부 렉시컬까지 검색 범위를 확대해서 animal을 찾는다. </p>
<hr>
<h2 id="케이스-4--함수를-반환하는-함수">케이스 4 : 함수를 반환하는 함수</h2>
<br>

<pre><code>function counts() {
    let count = 0;

    return function() {
        return count++;
    };
}

let counter = counts();

alert(counter());
</code></pre><p>위에서 살펴본 케이스들과 같이 내부 렉시컬 환경과 외부 렉시컬 환경이 생성이 된다.</p>
<p>실행 흐름을 살펴보자면 우선, 중첩 함수의 본문으로 넘어오면 &#39;count&#39;라는 변수가 필요한데 먼저 자체 렉시컬 환경에서 찾는다. 하지만 해당 지역 변수가 존재하지 않기 때문에 렉시컬 환경은 비어있는 상태가 되어 counter()의 렉시컬 환경이 참조하는 외부 렉시컬 환경까지 검색 범위를 확대한다. </p>
<p>그러면 counts() 안에 존재하는 count를 찾게되고, 중첩함수안에 있는 count++ 가 실행되면서 count 값이 1씩 증가하는데, <strong>변수 값 갱신은 변수가 저장된 렉시컬 환경에서 이뤄진다.</strong></p>
<p>( 모든 함수는 [[Environment]]라 불리는 숨김 프로퍼티를 갖는데, 여기에 함수가 만들어진 곳의 렉시컬 환경에 대한 참조가 저장된 곳이다. 이것은 [[Environment]]는 함수가 생성될 때, 딱 한번만 값이 세팅되고 절대 변하지 않는다.)</p>
<hr>
<p>이제 렉시컬 환경과 관련이 깊은 클로저에 대해 알아보자.</p>
<blockquote>
<p>클로저(closure)란?</p>
</blockquote>
<p>클로저는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수이다.
즉, 내부 함수가 외부 함수에 접근할 수 있고 함수가 선언된 당시의 스코프를 기억하는 것을 말한다.</p>
<p>➡️ 핵심은 스코프를 이용하여, 변수의 접근 범위를 닫는 것에 있다.
(외부 함수에서 내부 함수로의 접근은 불가능하지만, 내부 함수에서 외부 함수로의 접근은 가능하다. 따라서 내부 함수는 외부 함수에 정의된 변수에 접근이 가능하다.)</p>
<h3 id="클로저-함수의-장점">클로저 함수의 장점</h3>
<br>

<p>1️⃣ <strong>모듈화에 유리하다.</strong></p>
<p>클로저 함수를 각각의 변수에 할당하면 각자 독립적으로 값을 사용하고 보존할 수 있다.
이와 같이 함수의 재사용성을 극대화 함수 하나를 독립적인 부품의 형태로 분리하는 것을 모듈화라고한다.
클로저를 통해 데이터와 메소드를 묶어다닐 수 있기에 클로저는 모듈화에 유리하다.</p>
<br>

<p>2️⃣ 클로저는 특정 데이터를 스코프 안에 가두어 둔 채로 계속 사용할 수 있게하는 폐쇄성을 갖고 있기 때문에 <strong>데이터를 보존할 수 있다.</strong></p>
<br>

<p>3️⃣ <strong>정보의 접근 제한 (캡슐화)</strong></p>
<p>클로저 모듈 패턴을 사용해 객체에 담아 여러 개의 함수를 리턴하도록 만든다.
이러한 정보의 접근을 제한하는 것을 캡슐화라고 한다.</p>
<hr>
<h3 id="클로저-함수의-단점">클로저 함수의 단점</h3>
<br>

<p>1️⃣ 과도하게 사용하면 <strong>가독성이 떨어질 수 있음</strong>.</p>
<br>

<p>2️⃣ <strong>클로저 함수는 계속해서 외부 함수의 변수를 참조하기 때문에, 해당 변수가 가비지 컬렉션이 되지 않는다. 이로 인해 메모리 사용량이 증가</strong>하게 되고, 과도하게 사용될 경우에는 <strong>애플리케이션 성능 저하를 초래</strong>할 수 있음.</p>
<hr>
<h4 id="출처">출처</h4>
<ul>
<li><p><a href="https://hanamon.kr/javascript-%ED%81%B4%EB%A1%9C%EC%A0%80/">출처1</a></p>
</li>
<li><p><a href="https://ko.javascript.info/closure">출처2</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[크로스 브라우징 (cross browsing)]]></title>
            <link>https://velog.io/@yu_and_hip_0/%ED%81%AC%EB%A1%9C%EC%8A%A4-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A7%95-cross-browsing</link>
            <guid>https://velog.io/@yu_and_hip_0/%ED%81%AC%EB%A1%9C%EC%8A%A4-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A7%95-cross-browsing</guid>
            <pubDate>Wed, 21 May 2025 05:44:44 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>크로스 브라우징(cross browsing) </p>
</blockquote>
<p>우선 아래 사진을 살펴보자.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/b548b012-f243-4e6f-b621-9ba6328fa776/image.png" alt=""></p>
<hr>
<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/e4cf4e45-d937-43c6-81b3-9e7e97e1aed0/image.png" alt=""></p>
<hr>
<p>위 사진은 동일한 리액트 파일을 크롬으로 실행한 화면, 아래는 사파리로 실행한 화면이다.
아래 사진과 달리, 위 카테고리 안의 글자가 text-align:center가 적용되지 않아, 중앙이 아닌 왼쪽으로 치우쳐진 모습을 볼 수 있다.</p>
<p>이것이 바로 크로스 브라우징을 고려하지 않은 사례이다.</p>
<p>여기서 _<strong>크로스 브라우징 (cross-browsing)</strong>_이란, 웹 페이지 제작 시, 다양한 브라우저에서 개발자가 의도한 대로 올바르게 나타나는 것을 말한다.</p>
<blockquote>
<p>그렇다면 크로스 브라우징을 위해 어떻게 설정을 해야할까?</p>
</blockquote>
<h2 id="1-css-reset">1. CSS Reset</h2>
<p>css reset이란, 브라우저마다 기본적으로 제공되는 고유 css를 초기화하는 작업을 말한다. </p>
<p>이를 통해, 각 브라우저마다 공통의 기본 값을 제공할 수 있다.
(깨끗한 상태로 만든다고 보면 된다.)</p>
<h3 id="1-cdn-이용하기">1. CDN 이용하기</h3>
<br>

<pre><code>&lt;link
      rel=&quot;stylesheet&quot;
      href=&quot;https://cdn.jsdelivr.net/npm/reset-css@5.0.2/reset.min.css&quot;
    /&gt;</code></pre><h3 id="2-사이트를-통해-이용하기">2. 사이트를 통해 이용하기</h3>
<br>

<p>아래 사이트에 들어가서 코드 복사</p>
<ul>
<li><a href="https://meyerweb.com/eric/tools/css/reset/">https://meyerweb.com/eric/tools/css/reset/</a></li>
</ul>
<p>그 다음, reset.css 생성 후 복사했던 코드를 붙여 넣기 후 
app.css에 import해주면 된다.</p>
<pre><code>@import &quot;./reset.css&quot;</code></pre><h2 id="2-벤더-프리픽스-vendor-prefix">2. 벤더 프리픽스 (Vendor Prefix)</h2>
<p>벤더 프리픽스는 각 브라우저별 접두사를 이용하여 css를 동일하게 하는 방법을 말한다.</p>
<table>
<thead>
<tr>
<th>벤더</th>
<th>프리픽스</th>
<th>사용하는 브라우저</th>
</tr>
</thead>
<tbody><tr>
<td>-webkit-</td>
<td>-webkit-</td>
<td>Chrome, Safari</td>
</tr>
<tr>
<td>-moz-</td>
<td>-moz-</td>
<td>Firefox</td>
</tr>
<tr>
<td>-ms-</td>
<td>-ms-</td>
<td>Internet Explorer</td>
</tr>
<tr>
<td>-o-</td>
<td>-o-</td>
<td>Opera</td>
</tr>
</tbody></table>
<br>

<h2 id="3-브라우저-테스트-사이트-이용">3. 브라우저 테스트 사이트 이용</h2>
<p>사이트를 이용하여 브라우저 테스트를 하는 방법이다.
이를 이용하여 다양한 브라우저 및 버전 그리고 운영체제에서 웹사이트가 어떻게 보이는지 확인할 수 있다.</p>
<p>사이트는 다음과 같다.</p>
<ul>
<li><p><a href="https://www.browserstack.com">https://www.browserstack.com</a></p>
</li>
<li><p><a href="https://saucelabs.com">https://saucelabs.com</a></p>
</li>
<li><p><a href="https://www.lambdatest.com">https://www.lambdatest.com</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 생성 시 , 에러에 관한 해결 법(Feat. MacBook)]]></title>
            <link>https://velog.io/@yu_and_hip_0/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%83%9D%EC%84%B1-%EC%8B%9C-%EC%97%90%EB%9F%AC%EC%97%90-%EA%B4%80%ED%95%9C-%ED%95%B4%EA%B2%B0-%EB%B2%95Feat.-MAC</link>
            <guid>https://velog.io/@yu_and_hip_0/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%83%9D%EC%84%B1-%EC%8B%9C-%EC%97%90%EB%9F%AC%EC%97%90-%EA%B4%80%ED%95%9C-%ED%95%B4%EA%B2%B0-%EB%B2%95Feat.-MAC</guid>
            <pubDate>Fri, 09 May 2025 07:29:35 GMT</pubDate>
            <description><![CDATA[<p>리액트 관련 프로젝트를 만들기 위해, Vite로 React를 생성 할려는데...
<br></p>
<pre><code> npm create vite@latest &lt;파일이름&gt; -- --template react</code></pre><br>

<p><strong>여러줄의 npm err와 함께, 다음과 같은 에러가 발생했다.</strong></p>
<pre><code>Your cache folder contains root-owned files, 
due to a bug in npm error previous versions of npm which has since been addressed.

To permanently fix this problem, please run:
sudo chown -R 501:20 &quot;/Users/사용자이름/.npm&quot; </code></pre><br>
이것을 보고 캐시 폴더에 대한 권한 문제라 확신하고<br>
아래 문구에서 추천해주는 명령어를 이용하여 문제를 해결했다.
<br>

<pre><code>sudo chown -R 501:20 &quot;/Users/{사용자이름}/.npm&quot;
</code></pre><br>

<p>위의 명령어는 &quot;.npm 폴더와 그 안의 파일의 소유권을 현재 사용자에게 넘겨준다.&quot;
라는 뜻이다.</p>
<p>이 밖에 명령어 앞에 sudo를 붙여서 하는 방법도 존재한다.
<br></p>
<pre><code>sudo npm create vite@latest &lt;파일이름&gt; -- --template react</code></pre><br>
명령어 입력 -> 맥북 자체 비밀번호를 입력의 심플하고 쉬운 방법이다.

<p><img src="https://velog.velcdn.com/images/yu_and_hip_0/post/26f653c5-b13d-493c-a184-20963fc38cf6/image.jpeg" alt=""> 
<br>
‼️ 하지만 이건 별로 추천하지 않는 방법인데, 이유는 npm이 설치하는 
파일 및 디렉토리가 root 사용자 권한으로 되어 있어 파일을 수정 및 삭제가 불가능하기 때문이다. 
<br></p>
<h2 id="결론-웬만하면-sudo를-쓰지말자">결론: 웬만하면 sudo를 쓰지말자!</h2>
]]></description>
        </item>
    </channel>
</rss>