<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>gazero</title>
        <link>https://velog.io/</link>
        <description>`나는  ${job} 개발자`</description>
        <lastBuildDate>Tue, 20 Jan 2026 01:38:34 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>gazero</title>
            <url>https://velog.velcdn.com/images/gazero_/profile/7021c609-51bf-47a7-bec8-ca26e73713c9/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. gazero. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/gazero_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[성능이 뭐가 중요하다니요???]]></title>
            <link>https://velog.io/@gazero_/%EC%84%B1%EB%8A%A5%EC%9D%B4-%EB%AD%90%EA%B0%80-%EC%A4%91%EC%9A%94%ED%95%98%EB%8B%A4%EB%8B%88%EC%9A%94</link>
            <guid>https://velog.io/@gazero_/%EC%84%B1%EB%8A%A5%EC%9D%B4-%EB%AD%90%EA%B0%80-%EC%A4%91%EC%9A%94%ED%95%98%EB%8B%A4%EB%8B%88%EC%9A%94</guid>
            <pubDate>Tue, 20 Jan 2026 01:38:34 GMT</pubDate>
            <description><![CDATA[<h3 id="아니-성능은-다-필요없고-일단-되면-끝이라고">아니, 성능은 다 필요없고 일단 되면 끝이라고?</h3>
<p>&#39;지금 잘 쓰고 있잖아, 뭐하러 더 만져?&#39;
<img src="https://velog.velcdn.com/images/gazero_/post/1200428a-1b43-43a4-b146-a0cd6c0ee484/image.jpeg" alt=""></p>
<p>사내 서비스에서 외주 프리랜서에 맡겼다가 인계를 받은 프로젝트가 있다.
처음 인수인계를 받고 코드를 분석하면서
<del>1. 유지보수 어렵다.</del>
<del>2. 새로운 기능 추가 어렵다.</del>
<del>3. 느리다.</del>
<img src="https://velog.velcdn.com/images/gazero_/post/5417cc0c-91bd-4747-b073-7d1f36409f36/image.png" alt=""></p>
<p>총제적 난국이었고, 프론트엔드만의 문제가 이정도인데 백엔드와 DB 그리고 서버까지 모두 엉망인 상태였다.</p>
<p>상태관리가 엉망이었고, 스타일 라이브러리를 여러가지 중복으로 사용하고 있다.
상태관리가 중복이더라도 적재적소에 사용하면 시너지를 일으킬 수 있다.(하지만 이 프로젝트는 아니었다.)
그래 그럴 수 있지, 이유가 있을꺼야 하고 백번양보해도
Styled-components, MUI, module css, inline 등등 왜이렇게 했는지 이해할 수 없다.
불필요한 객체지향 구조 남발로 로컬에서 실행시 엄청나게 느렸다. 
(가끔 지금 뭐하고 사는지 그 사람들 이름을 구글에 검색해본다.)</p>
<p><img src="https://velog.velcdn.com/images/gazero_/post/0a8224c2-fe59-4c7e-b218-b7efa2f151db/image.png" alt=""></p>
<p>이 엄청난 프로젝트를 내가 받았고,
이 프로젝트의 유일한 백엔드와 DB를 맡아주셨던 분의 육아휴직으로 
최대한 프론트에서 해결, 안되면 백엔드 api 생성(혹은 유지보수), 그래도 안되면 DB 만지기로
어찌저찌 <code>2025.09.01</code>에 성공적으로 오픈을 했다(추가 유지보수 건 오픈은 10월 중순).</p>
<p>오픈하고 나서도 대민서비스 였기 때문에 조마조마 했다. 
갑자기 와르르 무너질 것 같은 서비스였기 때문이다. </p>
<p>오픈 전에 당시 신입이었던 동료 선임님(현재 내 최애), 담당 프로젝트 관련 선임님(현재 책임님)과 엄청난 테스트를 진행했었고, 다행스럽게도 프로그램에 관련된 CS는 현재까지도 0건이다.</p>
<p>우리(a.k.a 나)는 이렇게 개발을 진행했다.
<strong>&#39;새로운 페이지 개발은 기존 컨벤션을 따르되, 최대한 유연하게 컴포넌트를 구성하자&#39;</strong>
기존 코드를 완전히 무시할 수 없는 구조였기 때문에
최소한의 컨벤션을 따르기로 결정했다.
그리고
<strong>&#39;기존 사용불가능 하던 페이지의 유지보수는, 기존 컨벤션 구조에서 리팩토링과 함께 진행하자&#39;</strong>
프리랜서들이 버리고 간 화면은 객체지향으로 엮여있는 코드, 컨벤션을 따르지 않고 무지성으로 작성된 코드, 퍼블리셔가 퍼블리싱만 한 하드코딩 코드 이렇게 3가지 였고, 최대한 우리가 유지보수하게 되는 화면에 대해서는 해당 규칙을 적용해서 작업했다.</p>
<p>그리고 최근 회사에서 해당 프로젝트가 확장될 것이라는 사업계획을 전달 받았고,
현재 상태로는 확장이 불가능할거라는 파트원들의 판단이 있었다.
그 사이 팀이 분리되면서 나는 또 우리팀의 유일한 프론트엔드가 되었다.</p>
<p>우선 다시 코드분석에 들어갔고, 
성능개선이 우선이라는 생각이 들었다.</p>
<h3 id="초기-상태">초기 상태</h3>
<p><img src="https://velog.velcdn.com/images/gazero_/post/77ad5387-574e-4ba4-9155-ffc059671b34/image.png" alt=""></p>
<ul>
<li>모든 페이지가 체감상 매우 느림(예시 이미지 자료는 강력 새로고침하면서 그나마 잘 나온 수치였음)</li>
<li>새로고침 시 네트워크 요청 다수</li>
<li>Lighthouse / Web Vitals:<ul>
<li>LCP 수 초 ~ 수십 초!</li>
<li>CLS 지속 발생</li>
<li>INP 비교적 양호</li>
</ul>
</li>
</ul>
<blockquote>
<h4 id="프론트-구조-문제로-확정">프론트 구조 문제로 확정</h4>
</blockquote>
<h4 id="providers-중첩-구조-문제-발견">Providers 중첩 구조 문제 발견</h4>
<ul>
<li><code>Providers</code> 가 <ul>
<li>app/layout</li>
<li>route별 layout</li>
<li>page내부에 중복 선언   </li>
</ul>
</li>
<li>Recoil, React Query, MUI, Init Provider가 페이지 진입 시마다 초기화
-&gt; hydration 비용 증가
-&gt; 불필요한 re-render 폭증</li>
<li>조치: Providers를 최상위 app/layout.tsx 하나로 통합(하위에서는 Providers 제거)</li>
</ul>
<h4 id="api-호출-과다--반복-호출-구조">API 호출 과다 + 반복 호출 구조</h4>
<ul>
<li>테이블 공통 구조에서 <ul>
<li>usePageInfo(Recoil)</li>
<li>QueryLoader</li>
<li>usePagingQuery
가 서로 상태 변경 -&gt; 재호출 루프를 형성</li>
</ul>
</li>
<li>같은 API가 pagination 변경, filter 변경, mount시 마다 동일한 API 호출</li>
<li>조치: 불필요한 effect dependency 제거, page 진입 시 자동 reload 로직 정리, 페이지 로딩 시 무조건 fetch 하던 패턴 제거</li>
</ul>
<blockquote>
<p>API 호출 수 감소
네트워크 타임라인 정리
그런데, 여전히 LCP 나쁨</p>
</blockquote>
<h4 id="api-문제가-아니다">API 문제가 아니다!</h4>
<p><img src="https://velog.velcdn.com/images/gazero_/post/474314a5-fa20-4677-a6dc-08b7e1bf8b66/image.png" alt=""></p>
<ul>
<li>API 응답 자체는 수십~수백 ms</li>
<li>Network waterfall에서 요청이 빨리 끝나는데, <code>화면이 늦게 완성</code><blockquote>
<h4 id="렌더링-문제">렌더링 문제</h4>
</blockquote>
</li>
</ul>
<h4 id="구조-전면-재검토">구조 전면 재검토</h4>
<ul>
<li><p>예시 페이지: MyPage</p>
<ul>
<li>page.tsx가<ul>
<li>use client</li>
<li>useEffect로 getUser함수 호출</li>
<li>user 로딩 전까지 전체화면 screenLoading -&gt; 초기 HTML 아무 내용도 없음</li>
</ul>
</li>
</ul>
</li>
<li><p>조치: Server + client 분리 구조로 변경
1) <code>page.tsx</code>(서버컴포넌트 역할)</p>
<ul>
<li>getUser() 서버에서 호출</li>
<li>초기 HTML에 사용자 정보 포함
2)<code>MyPageClient.tsx</code>(클라이언트 컴포넌트 역할)<ul>
<li>탭 전환 interaction만 담당</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>결과
<img src="https://velog.velcdn.com/images/gazero_/post/9f0d5ff4-cd04-46b4-b26e-c692a9310176/image.png" alt="">
<img src="https://velog.velcdn.com/images/gazero_/post/c06db397-edd2-40dc-ad7b-c2d68f2d28da/image.png" alt=""></p>
<p>진짜 화가 나는건
이렇게 보이지 않는 그러니깐 성과가 체감으로 느껴지지 않는걸 왜하냐는 상사의 지적이었다.</p>
<p>마치 이건 
<strong><em>&#39;코드리뷰 받으면서 그냥 기능은 작동하는데 머지해주시면 안돼요?&#39;</em></strong>
의 과거의 나를 보는 느낌이었다.</p>
<p>예전에 api호출이 9번이 있었던 기억이 있다.
잊어버리지 않는 이유는 이 현상을 개선하기 위해 거의 3일을 썼고, 팀 리더의 도움을 받아서 해결을 했던 경험이 있기 때문이다.</p>
<p>이때는 빡센 QA가 너무 힘들었는데, 이때의 경험이 지금은 정말 소중하다.</p>
<p><span style="color: transparent;">떠날 때가 됐다. 저새끼랑 도저히 일 못하겠다.</span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿨하게 완성된 브랜치 날린 사람(해결한 사람도 나)]]></title>
            <link>https://velog.io/@gazero_/%EC%BF%A8%ED%95%98%EA%B2%8C-%EC%99%84%EC%84%B1%EB%90%9C-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EB%82%A0%EB%A6%B0-%EC%82%AC%EB%9E%8C%ED%95%B4%EA%B2%B0%ED%95%9C-%EC%82%AC%EB%9E%8C%EB%8F%84-%EB%82%98</link>
            <guid>https://velog.io/@gazero_/%EC%BF%A8%ED%95%98%EA%B2%8C-%EC%99%84%EC%84%B1%EB%90%9C-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EB%82%A0%EB%A6%B0-%EC%82%AC%EB%9E%8C%ED%95%B4%EA%B2%B0%ED%95%9C-%EC%82%AC%EB%9E%8C%EB%8F%84-%EB%82%98</guid>
            <pubDate>Sun, 26 Oct 2025 22:54:11 GMT</pubDate>
            <description><![CDATA[<h2 id="사건의-시작">사건의 시작</h2>
<p><img src="https://velog.velcdn.com/images/gazero_/post/245e8505-d0a9-466d-b460-c189b1fe59dd/image.png" alt=""></p>
<p>프로젝트 중간에 브랜치를 정리하기로 했고, 한 명이 정리하는게 편하겠다 생각해서 삭제가 필요한 브랜치 목록을 받았다.</p>
<h2 id="사건의-발생">사건의 발생</h2>
<p><img src="https://velog.velcdn.com/images/gazero_/post/c6509ccc-7ef6-4a72-a473-cb7eec3d3dc9/image.png" alt="">
<img src="https://velog.velcdn.com/images/gazero_/post/5a616d25-553d-4706-8f15-4b51656b6adc/image.png" alt=""></p>
<p>작업은 완료했는데, 아직 개발에 올라가지 않은 브랜치를 날렸다.
그리고 그 다음날 퇴근 직전에 뭔가 쎄함이 느껴졌다.
<img src="https://velog.velcdn.com/images/gazero_/post/7681b2a8-81d3-4402-888a-c1da89388784/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/gazero_/post/40a2f219-01bd-4b8d-a2c4-edb69eaf3176/image.png" alt=""></p>
<h2 id="사건-해결">사건 해결</h2>
<p>reflog를 해서 확인</p>
<pre><code>git fsck --no-reflogs --lost-found</code></pre><p>이거로는 확인이 어려움 커밋 아이디를 모르기 때문</p>
<pre><code>git reflog</code></pre><p>커밋메세지를 확인하자! (과거의 내가 커밋메세지를 잘 써놨길 믿자)
<img src="https://velog.velcdn.com/images/gazero_/post/a66a78e8-dce5-4493-b954-8bb4dc909a80/image.png" alt="">
역시 과거의 나는 커밋메세지를 잘 써놨다.
그럼 이제 복구만 하면된다. </p>
<pre><code>main 브랜치에서
git checkout -b gy0912_file {commitID}</code></pre><p>사실 이상한 커밋로그로 돌아가서 몇번의 시행착오가 있긴 했는데
쉽게 커밋메세지를 찾은 것 같지만 저 브랜치에서 17번의 커밋메세지가 있는 것을 복구 후에 나중에 확인했다.
어디가 마지막 커밋인지 확실하지 않아서
중간중간 계속 멘붕이었다.
이미 위에 명령어로 특정 커밋상태로 돌아갔는데, 아니다? <code>reset</code>으로 바꾸면 됨</p>
<pre><code>git reset --hard {commitID}</code></pre><p>아무튼 성공했다. 
오늘은 월요일이고, 금요일 퇴근(퇴근 4시) 직전에 저걸 발견해서 찝찝한 주말을 보냈지만 복구했다.
<img src="https://velog.velcdn.com/images/gazero_/post/a7f24928-979f-45a8-b6e9-02b50e052b6d/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[502 오류 해결 여정] 까먹지 말자고 작성]]></title>
            <link>https://velog.io/@gazero_/502-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EC%97%AC%EC%A0%95-%EA%B9%8C%EB%A8%B9%EC%A7%80-%EB%A7%90%EC%9E%90%EA%B3%A0-%EC%9E%91%EC%84%B1</link>
            <guid>https://velog.io/@gazero_/502-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EC%97%AC%EC%A0%95-%EA%B9%8C%EB%A8%B9%EC%A7%80-%EB%A7%90%EC%9E%90%EA%B3%A0-%EC%9E%91%EC%84%B1</guid>
            <pubDate>Mon, 02 Jun 2025 06:34:14 GMT</pubDate>
            <description><![CDATA[<p>현재 화면은</p>
<ul>
<li>로컬</li>
<li>개발(dev)</li>
<li>운영(production)
아직 오픈되지 않은 서비스라서 <code>로컬</code>에서 개발하고, <code>개발</code>에 배포해서 진행중이었음. 
오픈 준비 완료되었고, <code>운영</code>서버에 배포 했더니 일부 데이터 호출에서 502오류 발생
<img src="https://velog.velcdn.com/images/gazero_/post/380034bb-b14c-4eda-98fe-54b8ec0046d6/image.png" alt=""></li>
</ul>
<p>일단 코드 문제는 아니라고 판단
왜냐, 로컬에서도 잘 됐고, 개발에서도 문제가 없었음.</p>
<p>그래서 파트장님한테 도움을 바로 요청했고!
간단하게 확인함.</p>
<p>terminus에서 
<img src="https://velog.velcdn.com/images/gazero_/post/3376d4b6-755b-4178-acaf-3b59717feab4/image.png" alt=""></p>
<pre><code>docker-compose logs -f 실행프로젝트</code></pre><p>로그를 봤더니
<img src="https://velog.velcdn.com/images/gazero_/post/a5e1c801-08fd-4e17-a4e0-751450237185/image.png" alt=""></p>
<p>어?
일단 <strong>DB문제</strong>임을 확인 왜냐하면, 실제로 해당 디비에 해당 칼럼이 없기 때문
왜 없지? 우리는 지금 개발DB와 운영DB를 분리해서 만들었는데
개발 DB에서 작업하면서 칼럼 값 등 달라질 수 있음. 그런데 그 수정된 부분이 반영이 안 됨
그래서 최신 DB(개발DB)로 반영을 해줌</p>
<p>만약 이게 오픈된 서비스에서는 적용이 안될 수도 있음.
왜냐하면 오픈된 서비스의 DB는 함부로 수정해서는 안 됨 &gt;&gt; 따라서 그럴 경우 운영에 따라가는 게 맞음 혹은 정말 필요하다면 운영을 수정해서 개발에 반영하는게 맞음</p>
<p><img src="https://velog.velcdn.com/images/gazero_/post/f590bcfb-1aec-4697-9188-78a51ca12a98/image.png" alt=""></p>
<p>해결했음.</p>
<p>*로그 보는 방법을 알려준 이유: 오늘 비슷한 문제로 로그인이 안되고 있었음
분명 프론트 + 백에서 로컬스토리지에 권한에 관한 부분을 만들어서 담아놨고, 그런데 운영에 배포를 했더니 미친듯이 오류가 발생하는 것. 심지어 로그인이 안 됨
그래서 로그를 확인했더니 이 또한 
<img src="https://velog.velcdn.com/images/gazero_/post/41b88f07-d34c-4392-895e-4edbc8602edf/image.png" alt=""></p>
<p>비슷한 문제였음. 권한에 관한 DB 테이블 작업을 최근에 했기 때문에 운영에는 반영이 되지 않았다는 사실을 확인할 수 있었음</p>
<blockquote>
<p>오늘의 교훈: 에러 로그를 유심히 확인하자.</p>
</blockquote>
<p>추가
<img src="https://velog.velcdn.com/images/gazero_/post/3ba2487b-16b3-49a3-823d-8ef596239c5f/image.png" alt="">
파일 다운로드 기능이 존재
그런데 get호출에서 또 502발생 
<img src="https://velog.velcdn.com/images/gazero_/post/6b61dfc1-0d9f-4b6d-91bb-977b323b55ae/image.png" alt="">
파일 업로드에서 잘못된(개발DB)경로로 업로드 됨
해결방법
<code>SFTP</code>에서 개발서버에 잘못올라간 파일을 운영서버로 옮겨줌
<img src="https://velog.velcdn.com/images/gazero_/post/43262223-1d88-4ab3-89a4-7b3e1584c745/image.png" alt="">
<img src="https://velog.velcdn.com/images/gazero_/post/2d8eaec2-eeca-4054-b8f1-6b4ece7eb50d/image.png" alt=""></p>
<p>초록불이 잘 뜸!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파일업로드(+서버로 파일 저장하기)]]></title>
            <link>https://velog.io/@gazero_/%ED%8C%8C%EC%9D%BC%EC%97%85%EB%A1%9C%EB%93%9C%EC%84%9C%EB%B2%84%EB%A1%9C-%ED%8C%8C%EC%9D%BC-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@gazero_/%ED%8C%8C%EC%9D%BC%EC%97%85%EB%A1%9C%EB%93%9C%EC%84%9C%EB%B2%84%EB%A1%9C-%ED%8C%8C%EC%9D%BC-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 27 Jan 2025 15:06:53 GMT</pubDate>
            <description><![CDATA[<h3 id="요구사항">요구사항</h3>
<p>첫번째, 파일을 업로드 하기
두번째, 업로드 한 파일의 제목을 화면에 노출하기
세번째, 저장버튼을 누르면 파일(및 다른 입력 데이터들)을 저장하기</p>
<h3 id="조건">조건</h3>
<p>첫번째, 파일 외에는 모두 json형태로 전송할 것
두번째, 프론트 데이터 구조는 총 3가지의 테이블의 데이터를 하나로 합쳐서 저장해야 함(ex. 입찰내역 테이블의 데이터, erp 테이블의 데이터, 입찰결과 테이블의 데이터)</p>
<h3 id="스웨거데이터-구조">스웨거(데이터 구조)</h3>
<pre><code>bidData = {
    &quot;bidId&quot; : string,
    &quot;bidNo&quot; : string,
    ...
    erpItems = {
        &quot;erpId&quot; : string,
        ...
    }
 }
 grpType001
 grpType002
 grpType003</code></pre><ul>
<li>입찰내역, erp, 입찰결과 테이블의 데이터는 bidData안에 들어가 있으며, json구조로 전송해야함</li>
<li>grpType001 ... 은 파일업로드 데이터임</li>
</ul>
<h4 id="문제점나의-문제점">문제점(나의 문제점)</h4>
<p>첫번째, 파일을 통째로 어떻게 전달하지?
두번째, bidData만 어떻게 json형식으로 보내란거지?</p>
<h4 id="해결하기">해결하기</h4>
<p>첫번째, 백엔드 선임님한테 물어봄
두번째, 백엔드 선임님과 함께 GPT에 물어봄
세번째, formData를 사용하라는 힌트를 얻음</p>
<ul>
<li>formData 메서드사용하기</li>
<li>나머지 데이터는 json 형식으로 전달하기(formData에 추가)</li>
<li>body형식은 formData로 전달하기</li>
</ul>
<pre><code>const formData = new FormData();</code></pre><pre><code>formData.append(&quot;bidData&quot;, JSON.stringify(payload));</code></pre><pre><code>Object.entries(files).forEach(([key, file]) =&gt; {
      if (file) {
        formData.append(key, file);
      } else {
        console.warn(`파일이 없습니다: Key=${key}`);
      }
    });</code></pre><pre><code> const response = await fetch(`${baseUrl}/...`,         {
        method: &quot;POST&quot;,
        body: formData,
      });
</code></pre><ul>
<li>이렇게 하면 bidData는 json 형식을 formData에 담에서 전달되고, 파일을 통째로 formData에 담겨서 전달됨</li>
</ul>
<h4 id="회고">회고</h4>
<ul>
<li>해보니깐 별거 아닌데 별거다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[우당탕탕 백엔드 입문기록(번외) - 머지완(ㅋ)]]></title>
            <link>https://velog.io/@gazero_/%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%9E%85%EB%AC%B8%EA%B8%B0%EB%A1%9D%EB%B2%88%EC%99%B8-%EB%A8%B8%EC%A7%80%EC%99%84%E3%85%8B</link>
            <guid>https://velog.io/@gazero_/%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%9E%85%EB%AC%B8%EA%B8%B0%EB%A1%9D%EB%B2%88%EC%99%B8-%EB%A8%B8%EC%A7%80%EC%99%84%E3%85%8B</guid>
            <pubDate>Tue, 03 Dec 2024 06:48:11 GMT</pubDate>
            <description><![CDATA[<h3 id="api-만들면서-깨달은-순서정답-아님">API 만들면서 깨달은 순서(정답 아님)</h3>
<ol>
<li>Entity를 작성한다.</li>
<li>DTO를 작성한다.</li>
<li>Service를 작성하면서 Repository도 함께 작성한다. </li>
<li>ontroller를 작성한다.</li>
</ol>
<ul>
<li>repository는 데이터를 조회할 로직을 구현</li>
<li>service는 조회한데이터를 보여줄 로직을 구현
<img src="https://velog.velcdn.com/images/gazero_/post/2cde06d5-7db4-4106-89a6-ab1f44204e9a/image.png" alt=""><h3 id="무아지경">무아지경</h3>
<img src="https://velog.velcdn.com/images/gazero_/post/f84a97e2-1075-4efe-a7fb-39a6b5b4b028/image.png" alt=""></li>
</ul>
<p><img src="https://velog.velcdn.com/images/gazero_/post/c158c273-93ef-4d09-b1d0-a216e9bfa1a9/image.png" alt=""></p>
<p>머지 완. 꺄륵</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우당탕탕 백엔드 입문기록(번외) - 저는 모르겠어요(일하는 중)]]></title>
            <link>https://velog.io/@gazero_/%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%9E%85%EB%AC%B8%EA%B8%B0%EB%A1%9D%EB%B2%88-%EC%A0%80%EB%8A%94-%EB%AA%A8%EB%A5%B4%EA%B2%A0%EC%96%B4%EC%9A%94%EC%8B%A4%EB%AC%B4%EC%A4%91</link>
            <guid>https://velog.io/@gazero_/%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%9E%85%EB%AC%B8%EA%B8%B0%EB%A1%9D%EB%B2%88-%EC%A0%80%EB%8A%94-%EB%AA%A8%EB%A5%B4%EA%B2%A0%EC%96%B4%EC%9A%94%EC%8B%A4%EB%AC%B4%EC%A4%91</guid>
            <pubDate>Thu, 28 Nov 2024 00:33:36 GMT</pubDate>
            <description><![CDATA[<p><strong>나의 Nest 지식 수준</strong></p>
<ol>
<li>3일 동안 기존 프로젝트 및 신규 프로젝트(내가 해야 할) 구조를 봤음</li>
<li>이해가 안되는 부분 검색해서 블로그 찾아봄</li>
<li>공식문서도 정독하면서 겨우 겨우 신규 프로젝트 구조를 파악</li>
<li>유툽 강의를 들으면서 신기한 기능도 발견</li>
<li>혼자 실습하면서 프로젝트 생성 - 단지 컨트롤러에서 api 만들고(서비스 이런거 아무것도 모르는 상태), 썬더클라이언트로 확인까지만 해봄</li>
</ol>
<p>🐣일단 시키니까 <strong>맨땅에 헤딩</strong>🐣</p>
<h4 id="나의-문제점-파악하기👍🏼">나의 문제점 파악하기👍🏼</h4>
<p><img src="https://velog.velcdn.com/images/gazero_/post/6c2f3996-f688-463b-a834-8701e026d1c9/image.jpg" alt=""></p>
<h5 id="1-회사-프로젝트-구조기본">1. <strong>회사 프로젝트 구조(기본)</strong></h5>
<p> module - controller - service의 기본구조</p>
<h5 id="2-dto">2. <strong>dto</strong></h5>
<ul>
<li>respose
응답시 필요한 api property를 정의하는 곳인가?</li>
<li>request
요청시 필요한 api property를 정의하는 곳인가?<h5 id="3-entities">3. <strong>entities</strong></h5>
</li>
<li>본격 DB와 연동해주는 부분인가?</li>
<li>DB 정의도 해줌<h5 id="4-repositories">4. <strong>repositories</strong></h5>
</li>
<li>DB 쿼리를 작성해주는 곳인가?</li>
</ul>
<p>🔒<strong>종합</strong>) 
<img src="https://velog.velcdn.com/images/gazero_/post/02b8b910-105a-4674-a86e-f761ef2a8270/image.jpg" alt=""></p>
<p><code>컨트롤러</code>에서는 어떤 방식으로 데이터를 요청할 것인지 정하고, 어떤 값으로 반환받을 것인지 정의하는 중(서비스에서 받아온 함수를 뿌려줄 예정)</p>
<p><code>서비스</code>에서 데이터를 받아올 함수를 정의하고 있음</p>
<p>1) 비동기 함수가 실행하고, 성공하면 dto에 정의된 부분을 반환
2) 일단 데이터 조회하는데 reposities에 있는 메서드를 호출, 호출하는 메서드에는 entities에 있는 데이터를 조회하게 되어있음</p>
<p>🔐<strong>진짜 종합</strong>) 내가 이해하는건
<img src="https://velog.velcdn.com/images/gazero_/post/31c4f068-f343-466b-84cd-4708fe5a62bd/image.jpg" alt=""></p>
<ul>
<li><code>컨트롤러</code>에서 api 요청과 결과값을 도와주는 것이 서비스</li>
<li><code>서비스</code>에서 데이터를 조회하게 도와주는 것이 <code>repositories</code></li>
<li>그런 <code>repository</code>에서 조회할 데이터를 정의한 곳이 <code>entities</code></li>
<li>이 과정을 거친 서비스가 함수 실행에 성공하면 보여줄 데이터가 <code>dto</code></li>
</ul>
<p>이 정도로 이해하고 있는데, 
<strong>아니?</strong>🙂‍↔️ 이해하려고 노력하고 있음</p>
<p><del>(지금부터 평정성 잃음 주의🥹)</del></p>
<p>난 진쨔 정말 모르겠어.
실행해봤거든?</p>
<pre><code>npm run start:dev</code></pre><p><img src="https://velog.velcdn.com/images/gazero_/post/a4b8b326-ba65-4c92-ad7b-5e40c3e8ceeb/image.png" alt=""></p>
<p>근데 module에서 오류가 나는거야. 일단 무서워 <em><strong>빨갛잖아</strong></em>? 
<img src="https://velog.velcdn.com/images/gazero_/post/eacc6e41-fbfa-468c-839b-7747841a0abe/image.jpeg" alt=""></p>
<p>그래서 내가 만든 API가 제대로 된건지 확인도 못해봤어.
분명히 혼자 집에서 실습할때는 재밌었거든? </p>
<p><strong>그냥 리액트만 하게 해달라고 간절하게 부탁해볼까?🫸</strong>
ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ</p>
<h4 id="내가-파악한-나의-문제점-회고"><em>내가 파악한 나의 <strong>문제점</strong> 회고</em></h4>
<blockquote>
<ul>
<li>구조를 대충 이해하고 있음</li>
</ul>
</blockquote>
<ul>
<li>module에서 imports, exports, providers의 기능을 제대로가 아니라 아예 이해 못하고 있음</li>
<li>공통 모듈(선임님이 만들어준)의 역할과 기능도 모름</li>
</ul>
<h4 id="일단-해결했음">일단 해결했음</h4>
<blockquote>
<p>원인 1. module파일에서 꼬임
원인 2. repository에서 꼬임</p>
</blockquote>
<p>애초에 우리는 <code>EntityManager</code>방식으로 세팅이 되어있었는데, 나는 계속 <code>repository</code>형식으로 코드를 만들고 있었던 것임. </p>
<pre><code>import * as Repositories from &#39;../repositories/total-repository.index&#39;;

@Module({
  imports: [,
  providers: [ ...Object.values(Repositories)],
  controllers: [],
})
</code></pre><p>이런식으로 불러오는 것이었음! 
🔅<strong>핵심</strong>은 providers에서 repository를 인식하지 못하는게 문제였고, 그걸 인식하게 세팅을 한 것임!</p>
<h3 id="이제-짠-하고-만들어둔-api가-나올줄-알았음">이제, 짠! 하고 만들어둔 api가 나올줄 알았음</h3>
<p>그런데,
<img src="https://velog.velcdn.com/images/gazero_/post/5ea8edb7-03e5-4420-873a-bac682860e9b/image.png" alt=""></p>
<h4 id="❌500에러가-발생했고-no-medatata-for-어쩌고">❌500에러가 발생했고, No medatata for ...어쩌고</h4>
<p>이번엔 entity를 인식하지 못하는 오류
구글링을 했더니</p>
<ul>
<li>typeorm 버전을 낮춰서 해결했다는 사람(하지만 다른 get방식으로 요청한 데이터는 잘 나옴)</li>
<li>moodule에 imports에서 제대로 해당 entity를 추가해주지 않아서 추가 했다는 사람(나는 몹시 잘 추가 되어있음)</li>
<li><strong>config에서 entities 설정의 문제일거란 사람</strong>❗️ - 이게 가장 유력했다.</li>
</ul>
<p><strong>🔖문제 인식에 가장 key paper가 된 블로그</strong>
<a href="https://blockprogram.tistory.com/entry/Nestjs-TypeORM-EntityMetadataNotFoundError">https://blockprogram.tistory.com/entry/Nestjs-TypeORM-EntityMetadataNotFoundError</a></p>
<p>그래서 GPT에 도움으로</p>
<pre><code>❌entities: [__dirname + &#39;/../**/*.entity{.js,.ts}&#39;],</code></pre><p>이렇게 추가했다. 그랬더니 안된다.
그리고 우리는 total-entity.index.ts로 공통으로 모아서 관리하고 있으니 혹시 index를 추가해줘야 하는게 아닐까? 하고</p>
<pre><code>❌entities: [__dirname + &#39;/../**/*.entity.index.{.js,.ts}&#39;],</code></pre><p>이렇게 해보았다.
그렇지만 해결되지 않았다. 그래서 선임님한테 조용히가서
<img src="https://velog.velcdn.com/images/gazero_/post/a854f999-4999-46b6-bc35-e8f36c772663/image.jpeg" alt=""></p>
<p>&#39;<strong>도와주세요</strong>...&#39;했다</p>
<ol>
<li>maria.config에서의 entities<pre><code>entities: Object.values(Entities),</code></pre></li>
<li>database.module에서의 entities<pre><code>entities: [__dirname + &#39;/../**/*.entity{.ts,.js}&#39;],</code></pre></li>
</ol>
<p>아무튼 <code>typeorm</code> 형식의 문제였다. 이렇게 하고! 요청을 해보니깐
<img src="https://velog.velcdn.com/images/gazero_/post/7134100b-eed0-412f-920f-066e67f0d23d/image.png" alt=""></p>
<p>혹시 <strong>천재</strong>세요?
<img src="https://velog.velcdn.com/images/gazero_/post/b6b6f3aa-5e99-4b09-b189-3cd53f75b18c/image.png" alt=""></p>
<p>✨오늘의 나. <strong>스웨거 업데이트 하는 프론트엔드 어떤데?</strong> 좀 멋지잖아?🌟
<img src="https://velog.velcdn.com/images/gazero_/post/7c5a9b43-2ab3-4324-a571-bb7ea8775735/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/gazero_/post/30213a38-be77-4437-b824-4ff1389fc313/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우당탕탕 백엔드 입문기록(2) - api 그거 어떻게 만드는 건데?]]></title>
            <link>https://velog.io/@gazero_/%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%9E%85%EB%AC%B8%EA%B8%B0%EB%A1%9D2-api-%EA%B7%B8%EA%B1%B0-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%A7%8C%EB%93%9C%EB%8A%94-%EA%B1%B4%EB%8D%B0</link>
            <guid>https://velog.io/@gazero_/%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%9E%85%EB%AC%B8%EA%B8%B0%EB%A1%9D2-api-%EA%B7%B8%EA%B1%B0-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%A7%8C%EB%93%9C%EB%8A%94-%EA%B1%B4%EB%8D%B0</guid>
            <pubDate>Wed, 27 Nov 2024 11:52:39 GMT</pubDate>
            <description><![CDATA[<h2 id="crud-api를-만들어보자">CRUD API를 만들어보자.</h2>
<h3 id="지난-번에-만들었던-userscontrollerts에서-작업-시작">지난 번에 만들었던, users.controller.ts에서 작업 시작</h3>
<blockquote>
<p>  /*
  GET /users
  GET /users/:id
  POST /users
  PATCH /users/:id
  DELETE /users/:id
  */</p>
</blockquote>
<ol>
<li>일반적인 GET 요청
요청사항: 유저의 정보를 불러오기
<code>@Get</code> 데코레이터 사용<pre><code>@Get() // GET /users
findAll() {
return [];
}</code></pre><img src="https://velog.velcdn.com/images/gazero_/post/b3940824-2d80-4152-9c7f-ef772a19dbde/image.png" alt=""></li>
</ol>
<ol start="2">
<li><p>중첩경로 GET 요청</p>
<pre><code>@Get(&#39;interns&#39;) // GET /users/interns
findAllInterns() {
 return [&#39;GET방식, endpoint는 /user/interns 입니다.&#39;];
}</code></pre><p><img src="https://velog.velcdn.com/images/gazero_/post/393b0852-b8de-4748-ac9e-525dc625b416/image.png" alt=""></p>
</li>
<li><p>동적경로 GET 요청</p>
<pre><code>@Get(&#39;:id&#39;) // GET /users/:id
findOne(@Param(&#39;id&#39;) id: string) {
 return `GET방식, 동적경로 /users/${id}`;
}</code></pre><p><img src="https://velog.velcdn.com/images/gazero_/post/5f60b96a-ae82-413a-b689-92a402773030/image.png" alt=""></p>
</li>
<li><p>일반적인 POST 요청
<code>@Post</code> 데코레이터 사용
파라미터가 들어갈 <code>@Body</code>데코레이터 사용</p>
<pre><code>@Post() // POST /users
create(@Body() user: {}) {
 return user;
}</code></pre><p><img src="https://velog.velcdn.com/images/gazero_/post/97e50e82-dbc6-4e31-8caa-f134982d90eb/image.png" alt=""></p>
</li>
</ol>
<p><strong>오류발생</strong>
왜냐하면! JSON 객체의 마지막 항목 뒤에 쉼표 때문
<img src="https://velog.velcdn.com/images/gazero_/post/37a4e335-3326-4f71-8123-7be059abcbc4/image.png" alt="">
제거하고 요청했더니 성공!</p>
<ol start="5">
<li>PATCH요청
<code>@Patch</code> 데코레이터 사용
특정 id를 동적으로 업데이트 하는 기능을 하기 때문에 <code>@Param</code> 데코레이터 사용, 그리고 반환이 될 데이터가 있기 때문에 <code>@Body</code>데코레이터 사용<pre><code>@Patch(&#39;:id&#39;) // PATCH /users/:id
update(@Param(&#39;id&#39;) id: string, @Body() userUpdate: {}) {
 return { id, ...userUpdate };
}</code></pre><img src="https://velog.velcdn.com/images/gazero_/post/7da1fd24-8f6d-44a8-8821-eef010d5927f/image.png" alt=""></li>
</ol>
<ul>
<li>동적으로 id 값을 경로에 넣어주지 않으면 404오류 발생
<img src="https://velog.velcdn.com/images/gazero_/post/e8d064e1-3764-4ba8-93a6-394dc25fb104/image.png" alt=""></li>
<li>경로에 담은 id 값을 담아서 userUpdate에 포함시켜 출력됨</li>
</ul>
<ol start="6">
<li><p>DELETE 요청
<code>@Delete</code>데코레이터 사용
특정 id를 동적으로 삭제하기 때문에 <code>@Param</code>데코레이터를 사용</p>
<pre><code>@Delete(&#39;:id&#39;) // DELETE /users/:id
delete(@Param(&#39;id&#39;) id: string) {
 return { id };
}</code></pre><p><img src="https://velog.velcdn.com/images/gazero_/post/8e1a037e-0be6-41c3-96db-46f6c4afdb39/image.png" alt=""></p>
</li>
<li><p>쿼리를 사용해서 요청
<code>@Query</code> 데코레이터 사용</p>
<pre><code>@Get() // GET /users or /users?role=value
findAll(@Query(&#39;role&#39;) role?: &#39;INTERN&#39; | &#39;ENGINEER&#39; | &#39;ADMIN&#39;) {
 return [];
}</code></pre><p><img src="https://velog.velcdn.com/images/gazero_/post/779d5bc1-0cd1-464e-b6ac-e2c7db9f386c/image.png" alt=""></p>
</li>
</ol>
<ul>
<li>이렇게 요청했더니 200이 중요한게 아니라, Query 부분에 role과 ADMIN이 생겨남</li>
</ul>
<h4 id="신기하고-어렵고-두렵고-이게-맞나-싶고-그냥-하는거지-뭐">신기하고, 어렵고, 두렵고, 이게 맞나 싶고, 그냥 하는거지 뭐.</h4>
<p><img src="https://velog.velcdn.com/images/gazero_/post/8d16194d-17e9-478c-805e-7cd2a7dd47ca/image.jpeg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[우당탕탕 백엔드 입문기록(1) - nest 프로젝트 생성하기!]]></title>
            <link>https://velog.io/@gazero_/%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%9E%85%EB%AC%B8%EA%B8%B0%EB%A1%9D1-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@gazero_/%EC%9A%B0%EB%8B%B9%ED%83%95%ED%83%95-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%9E%85%EB%AC%B8%EA%B8%B0%EB%A1%9D1-nest-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 26 Nov 2024 13:31:01 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs-아니고-nestjs-맞습니다">NextJS 아니고, NestJS 맞습니다.</h1>
<p><img src="https://velog.velcdn.com/images/gazero_/post/4e698694-ad93-41fe-8235-ddce7505a6d2/image.jpeg" alt="">
어차피 백엔드 배우고 싶었음. 오히려 좋아 럭키비키쟈나</p>
<p>프로젝트 만드는 것부터 차근차근 시작해 보겠슴.
<strong>참고</strong>) 구조 이해를 프론트적 사고로 할 수 있음 주의</p>
<h2 id="프로젝트-생성cli">프로젝트 생성(CLI)</h2>
<pre><code>$ npm install -g @nestjs/cli</code></pre><pre><code>$ nest new my-nest-project
$ cd my-nest-project
$ npm run start:dev</code></pre><p>이렇게 하면 프로젝트는 성공
구조를 간단히 설명하면</p>
<h4 id="maints">main.ts</h4>
<pre><code>import { NestFactory } from &#39;@nestjs/core&#39;;
import { AppModule } from &#39;./app.module&#39;;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
</code></pre><ul>
<li>부트스트랩 함수가 있고</li>
<li>부트스트랩을 마지막에 호출함</li>
<li>부트스트랩 내부에는 AppModule을 불러와서 NestFactory가 애플리케이션을 생성 그리고 3000 포트로 HTTP 요청을 받고 있음</li>
</ul>
<p>참고) <strong>NestFactory</strong></p>
<ul>
<li>Nest 애플리케이션 인스턴스를 생성 클래스</li>
<li>create()메서드는 인터페이스를 충족하는 애플리케이션 객체를 반환</li>
</ul>
<h4 id="appmodulets">app.module.ts</h4>
<pre><code>import { Module } from &#39;@nestjs/common&#39;;
import { AppController } from &#39;./app.controller&#39;;
import { AppService } from &#39;./app.service&#39;;
import { UsersModule } from &#39;./users/users.module&#39;;

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
</code></pre><ul>
<li>main.ts 내부에 불러오고 있는 AppModule이 있는 곳!</li>
<li>Module에 Provider를 등록해야 다른 곳에서 사용이 가능함(= Provider에 있는 함수를 다른 곳에 제공하는 역할)</li>
<li>Providers안에는 Service(함수를 구현 하는 장소)를 넣고, Controllers안에는 Controller를 넣음</li>
</ul>
<h4 id="appcontrollerts">app.controller.ts</h4>
<pre><code>import { Controller, Get } from &#39;@nestjs/common&#39;;
import { AppService } from &#39;./app.service&#39;;

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
</code></pre><ul>
<li><code>@Controller()</code> 데코레이터를 호출: 컨트롤러로 인식!</li>
<li>클래스 내의 각 메서드에는 <code>@Get()</code>, <code>@Post()</code>, <code>@Delete()</code>와 같은 HTTP 방식(method)에 해당하는 데코레이터를 명시해서 사용</li>
<li>이러한 데코레이터들은 URL 경로를 나타내는 문자열을 인자로 받음</li>
<li>따라서 endpoint는 여기에서 만들어짐</li>
<li>컨트롤러는 들어오는 <strong>요청을 처리</strong> &amp; 클라이언트에 <strong>응답을 반환</strong></li>
</ul>
<h4 id="appservicets">app.service.ts</h4>
<pre><code>import { Injectable } from &#39;@nestjs/common&#39;;

@Injectable()
export class AppService {
  getHello(): string {
    return &#39;Hello World!&#39;;
  }
}
</code></pre><ul>
<li><code>@Injectable()</code> 데코레이터: 다른 클래스에 생성자를 통해서 주입해줄 수 있음 </li>
<li>이게 있어야 controller에서 constructor를 통해 의존 선언을 할 수 있음</li>
</ul>
<h3 id="의존성">의존성?</h3>
<blockquote>
<p>Nest의 핵심일지도!</p>
</blockquote>
<h4 id="controller와-service의-관계">controller와 service의 관계</h4>
<blockquote>
<p>Controller는 Service를 의존한다.</p>
</blockquote>
<ul>
<li><code>컨트롤러는 서비스에서 함수를 가져다 쓰기 때문</code>에 의존한다고 봄 - 따라서 서비스가 변하면 컨트롤러도 영향을 받음</li>
<li>의존성을 주입하는 방법: 의존받을 class constructor 내부에서 외부의 class를 사용하는 것으로 구현 가능<pre><code>@Controller(&#39;pur&#39;)
export class PurController {
constructor(private readonly purServices: PurService) {}
}
</code></pre></li>
</ul>
<pre><code>- 컨트롤러에서 ‘나는 PurService에게 의존 하겠다!’라고 `constructor` 내부에 선언
- 만약 의존하지 않고, controller내부에 함수를 구현했다면? 재사용 하기가 어렵다!

그럼 일단 이 정도하고, 설치가 잘 됐는지 **확인**부터 해보자!
![](https://velog.velcdn.com/images/gazero_/post/0ef2fc0a-6c43-4151-bcd8-f99a52519d95/image.png)
응답 값을 잘 받아오고 있음을 확인!



### 오, 근데 그냥 신기한 거 발견</code></pre><p>nest g module users</p>
<pre><code>- 모듈을 만들어주는 명령어!
- src/users/users.module.ts를 만들어줌!</code></pre><p>import { Module } from &#39;@nestjs/common&#39;;</p>
<p>@Module({})
export class UsersModule {}</p>
<pre><code>- 그리고 루트 모듈 imports에도 자동으로 추가되어 있음!</code></pre><p>import { Module } from &#39;@nestjs/common&#39;;
import { AppController } from &#39;./app.controller&#39;;
import { AppService } from &#39;./app.service&#39;;
import { UsersModule } from &#39;./users/users.module&#39;;</p>
<p>@Module({
  imports: [UsersModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}</p>
<pre><code>- 그니깐 users module을 root module에 연동시킴!
</code></pre><p>nest g controller users</p>
<pre><code></code></pre><p>nest g service users</p>
<pre><code>컨트롤러와 서비스도 다 만들 수 있음!
싱기방기 뿡뿡방기........
- user Module은 이렇게 자동으로 바뀜!</code></pre><p>import { Module } from &#39;@nestjs/common&#39;;
import { UsersController } from &#39;./users.controller&#39;;
import { UsersService } from &#39;./users.service&#39;;</p>
<p>@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}</p>
<pre><code>
## 실행방법</code></pre><p>npm run start</p>
<pre><code>
## lint와 prettier
- 프로젝트 생성시 자동으로 설정되어 있음</code></pre><h1 id="lint-and-autofix-with-eslint">Lint and autofix with eslint</h1>
<p>$ npm run lint</p>
<h1 id="format-with-prettier">Format with prettier</h1>
<p>$ npm run format
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[어쩌다 마주친 Unexpected end of json input오류. 다신 마주치지 말자. ]]></title>
            <link>https://velog.io/@gazero_/%EC%96%B4%EC%A9%8C%EB%8B%A4-%EB%A7%88%EC%A3%BC%EC%B9%9C-Unexpected-end-of-json-input%EC%98%A4%EB%A5%98.-%EB%8B%A4%EC%8B%A0-%EB%A7%88%EC%A3%BC%EC%B9%98%EC%A7%80-%EB%A7%90%EC%9E%90</link>
            <guid>https://velog.io/@gazero_/%EC%96%B4%EC%A9%8C%EB%8B%A4-%EB%A7%88%EC%A3%BC%EC%B9%9C-Unexpected-end-of-json-input%EC%98%A4%EB%A5%98.-%EB%8B%A4%EC%8B%A0-%EB%A7%88%EC%A3%BC%EC%B9%98%EC%A7%80-%EB%A7%90%EC%9E%90</guid>
            <pubDate>Wed, 20 Nov 2024 05:25:23 GMT</pubDate>
            <description><![CDATA[<p>갑자기 찾아온 &#39;<strong>Unexpected end of json input</strong>&#39; 빌드 오류</p>
<p>어제였다.</p>
<p>현재 <strong>NextJS app route</strong>를 사용하고 있는데, 배포된 화면에서는 이미지가 나오지 않는 것. 하지만 로컬에는 잘 나온다.
(<code>참고</code>: 프론트 프로젝트는 셋팅부터 내가 했음, 자랑하는게 아니라 이미지가 안나오는 걸 발견한게 이미지를 배포해본 적이 없는 새삥 프로젝트 때문임을 강조하기 위함.)</p>
<p><strong>프로젝트 구조</strong>는 루트경로에 app과 public이 있다.
my-nextjs-app/
├── app/
├── public/
│   ├── images/
│   └── ... </p>
<p>일단 문제는 배포될 때, public이 포함되지 않았던 것.
확인했더니(배포 도메인/images/파일이름 으로 검색했을 때, 이미지가 안나오면 배포안된 것. 왜냐 로컬에서는 localhost:3000/images/파일이름 하면 이미지가 나옴) 확실히 배포될 때 해당 경로가 포함되지 않은 걸 확인했음</p>
<p>나의 대처 방법(Dockerfile에 경로 포함시켜서 배포되도록 추가) </p>
<pre><code>COPY --from=builder /app/public ./public</code></pre><p>문제는 지금부터였다. 적용해서 빌드하고 배포하는데
<img src="https://velog.velcdn.com/images/gazero_/post/56b57e3b-9782-4fb3-866f-7404c00a18d5/image.png" alt=""></p>
<p><code>Unexpected end of json input</code> 왜죠</p>
<ol>
<li>아직 백엔드 서버에서 api를 받아오고 있지 않음</li>
<li>일단 각각 파일에는 json형식이 없음</li>
<li>.json 확장자 파일에 문제 없음</li>
<li>캐시삭제 해봄</li>
<li>npm 다시 설치해봄
<img src="https://velog.velcdn.com/images/gazero_/post/1faf4608-f0bb-4bff-b653-003a676b5fe5/image.png" alt=""></li>
<li>그냥 해보라는 거 다 해봤음</li>
</ol>
<p>검색도 해봤음
결국 해결하지 못하고 <strong>퇴사</strong> 아니고 퇴근함ㅋㄷ</p>
<p>아침에 출근했음 2시간 동안 계속 해결방안을 찾아보기 위해 노력함
<img src="https://velog.velcdn.com/images/gazero_/post/fab24b41-78b8-4cfd-a3f0-c058f89cd582/image.png" alt="">
(참고: 노력한 흔적들)</p>
<p>구글링 시작해봄
어????분명 어제까지는 없던 나와 같은 문제를 가진 사람을 찾음</p>
<ol>
<li><a href="https://stackoverflow.com/questions/79201586/react-build-error-unexpected-end-of-json-input">https://stackoverflow.com/questions/79201586/react-build-error-unexpected-end-of-json-input</a></li>
<li><a href="https://stackoverflow.com/questions/79196464/react-js-failed-to-compile-unexpected-end-of-json-input-error">https://stackoverflow.com/questions/79196464/react-js-failed-to-compile-unexpected-end-of-json-input-error</a>
<img src="https://velog.velcdn.com/images/gazero_/post/60d9fe57-14c8-4689-a293-545f0776fc9d/image.png" alt=""></li>
</ol>
<p><strong>문제</strong>: 저는 예전과 다름없이 쓰고 있는데 오늘 빌드하려니 갑자기 오류가 발생하고 있어요!
<strong>대답</strong>: 노드 버그같아. 노드 버전의 문제이니 너는 노드의 버전을 LTS로 낮춰보겠니? 라는 답변이었다.</p>
<p>근데 저는뇨, 20 버전을 쓰고 있었거든요? 쨌든 노드 문제일 것 이라는 부푼 희망을 안고(내 잘못 아님) 선임님께 문의를 드려쬬.</p>
<p><img src="https://velog.velcdn.com/images/gazero_/post/067ffaa1-b1f4-4f36-8af6-267defe88397/image.png" alt=""></p>
<p>ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ아무튼 선임님은 그라데이션 분노였찌만!
지금은 어떻게 되었냐하면 쨔쟈쟌!
<img src="https://velog.velcdn.com/images/gazero_/post/bb8b6944-fb04-4aa8-ae69-c316c61f0cfc/image.png" alt=""></p>
<p>해당 <code>Unexpected end of json input</code>오류는 선임님이 해결했음.
어떻게 해결했냐하면, 빌드에 쓰이는 모든 node버전을 통일하셨다고 함(node 18버전으로 통일함)</p>
<ul>
<li>Dockerfile</li>
<li>.gitlab-ci.yml</li>
<li>package.json</li>
</ul>
<p>모든 파일에 node 버전을 18로 낮춘상태닷!
내가 마지막으로 수정한 건 버전을 낮추면서 기존에 없었던 타입 오류가 미친듯이 발생했고 모든 파일의 타입 오류를 고쳤다.</p>
<p><strong>회고</strong></p>
<blockquote>
<ol>
<li>일단 nextjs의 문제는 아니라는 점.</li>
<li>오류 발생한 사람들의 공통점은 react를 사용하고 있었다는 점.</li>
<li>진짜 노드 버전을 수정하니 해결이 되었다는 점</li>
</ol>
</blockquote>
<p>자칫 잘못했다가
<img src="https://velog.velcdn.com/images/gazero_/post/a2882bc3-c0eb-44f4-9103-072b067e3d42/image.jpeg" alt=""></p>
<p>또 vue 할 뻔...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next JS에서 react-query 적용기 1탄.]]></title>
            <link>https://velog.io/@gazero_/Next-JS%EC%97%90%EC%84%9C-react-query-%EC%A0%81%EC%9A%A9%EA%B8%B0-1%ED%83%84</link>
            <guid>https://velog.io/@gazero_/Next-JS%EC%97%90%EC%84%9C-react-query-%EC%A0%81%EC%9A%A9%EA%B8%B0-1%ED%83%84</guid>
            <pubDate>Tue, 19 Nov 2024 00:26:32 GMT</pubDate>
            <description><![CDATA[<h1 id="next-js에서-react-query-적용기-1탄">Next JS에서 react-query 적용기 1탄.</h1>
<blockquote>
<p>&#39;<strong>NextJS에서 별도의 상태관리가 필요한 가?</strong>&#39;에 대한 고민이 계속 됐다.</p>
</blockquote>
<p>그러던 중 회사 프로젝트에서 유지보수 해야할 업무(외주에서 진행하던)를 맡았고, 코드를 보는데 NextJS에서 React-query를 사용하고 있었다.
<img src="https://velog.velcdn.com/images/gazero_/post/a2836313-f00e-4ffc-b56d-23e0eaec3dca/image.jpeg" alt=""></p>
<p>그동안 상태관리는 Redux, Recoil, jotai 정도만 접해봤고, react-query를 써볼 기회가 없었다(취업한 후에는 내 의지가 아니라고 할 수 있다. 하지만 사이드 프로젝트에서도 안써본 거는 내 의지 이긴하지만 그냥 내탓이 아니었으면 좋겠다는 변명....)</p>
<p>어쨌든 해당 업무 말고 원래 진행중이던 NextJS 프로젝트에서도 React-query를 사용해볼 겸, 유지보수 해야 할 코드도 볼 겸 공부를 시작한다.</p>
<p>오늘은 <strong>설치</strong>부터 필요한 <strong>각종 메서드와 옵션</strong>을 공부할 것이다! 뚜둔(강한 의지)</p>
<h3 id="nextjs--app-route에-react-query-더하기">NextJS + app route에 react-query 더하기!</h3>
<h4 id="설치">설치</h4>
<pre><code class="language-js">npm install @tanstack/react-query</code></pre>
<h4 id="react-query-설정">React Query 설정</h4>
<ul>
<li><code>app/layout.tsx</code></li>
<li><em>참고*</em>) 반드시 여기에 이 경로에 하라는 것은 아님 주의! 다른 경로의 layout.tsx에 하면 경로 이하에 전역적으로 상태관리가 가능해 지는 것임!</li>
</ul>
<pre><code>import React from &#39;react&#39;;
import { QueryClient, QueryClientProvider } from &#39;@tanstack/react-query&#39;;

const queryClient = new QueryClient();

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        &lt;html lang=&quot;en&quot;&gt;
            &lt;body&gt;
                &lt;QueryClientProvider client={queryClient}&gt;
                    {children}
                &lt;/QueryClientProvider&gt;
            &lt;/body&gt;
        &lt;/html&gt;
    );
}
</code></pre><ul>
<li><strong>queryClient</strong> / <strong>queryClientProvider</strong> 설정<pre><code>const queryClient = new QueryClient();</code></pre></li>
<li><strong>QueryClient</strong> 인스턴스는 모든 쿼리와 뮤테이션의 상태를 관리하며, 캐시와 관련된 설정을 포함한다.</li>
<li>애플리케이션에서 데이터를 가져오고, 캐시를 관리하고, 쿼리의 생명주기를 제어하는 데 사용</li>
</ul>
<pre><code>&lt;QueryClientProvider client={queryClient}&gt;</code></pre><p>-<strong>QueryClientProvider</strong>는 React Context API를 사용하여 애플리케이션의 하위 컴포넌트에 queryClient 인스턴스를 제공
-이 컴포넌트로 감싸진 모든 하위 컴포넌트는 useQuery, useMutation 등과 같은 React Query 훅을 사용할 수 있다.</p>
<h4 id="데이터-페칭-함수-만들기예시">데이터 페칭 함수 만들기(예시)</h4>
<ul>
<li>app/api/posts.ts<pre><code>export const fetchPosts = async () =&gt; {
  const response = await fetch(&#39;https://jsonplaceholder.typicode.com/posts&#39;);
  if (!response.ok) {
      throw new Error(&#39;Network response was not ok&#39;);
  }
  return response.json();
};</code></pre></li>
</ul>
<h4 id="react-query로-데이터-가져오기">React Query로 데이터 가져오기</h4>
<pre><code>import React from &#39;react&#39;;
import { useQuery } from &#39;@tanstack/react-query&#39;;
import { fetchPosts } from &#39;../api/posts&#39;;

const PostsPage = () =&gt; {
    const { data, error, isLoading } = useQuery([&#39;posts&#39;], fetchPosts);

    if (isLoading) {
        return &lt;div&gt;Loading...&lt;/div&gt;;
    }

    if (error instanceof Error) {
        return &lt;div&gt;Error: {error.message}&lt;/div&gt;;
    }

    return (
        &lt;div&gt;
            &lt;h1&gt;Posts&lt;/h1&gt;
            &lt;ul&gt;
                {data.map((post: { id: number; title: string }) =&gt; (
                    &lt;li key={post.id}&gt;{post.title}&lt;/li&gt;
                ))}
            &lt;/ul&gt;
        &lt;/div&gt;
    );
};

export default PostsPage;
</code></pre><p><img src="https://velog.velcdn.com/images/gazero_/post/d99bf41a-69c7-4792-8362-e336ff5f425b/image.jpeg" alt=""></p>
<p><em><strong>그래서?</strong></em></p>
<p>첫번째, 메서드와 그 메서드의 사용방법을 먼저 확인해보자.</p>
<h4 id="1-usequery서버에서-데이터를-가져오는-가장-기본적인-훅">1. useQuery(서버에서 데이터를 가져오는 가장 기본적인 훅)</h4>
<pre><code>import { useQuery } from &#39;@tanstack/react-query&#39;;
import { fetchPosts } from &#39;../api/posts&#39;;

const Posts = () =&gt; {
    const { data, error, isLoading } = useQuery([&#39;posts&#39;], fetchPosts);

    if (isLoading) return &lt;div&gt;Loading...&lt;/div&gt;;
    if (error instanceof Error) return &lt;div&gt;Error: {error.message}&lt;/div&gt;;

    return (
        &lt;ul&gt;
            {data.map((post: { id: number; title: string }) =&gt; (
                &lt;li key={post.id}&gt;{post.title}&lt;/li&gt;
            ))}
        &lt;/ul&gt;
    );
};
</code></pre><ul>
<li>첫번째 매개변수 <code>query key(고유식별자)</code></li>
<li>두번째 매개변수 <code>쿼리함수</code>(React Query가 호출하여 데이터를 가져오고, 성공적으로 데이터를 가져오면 해당 데이터를 캐시에 저장)</li>
<li>그러니깐 쿼리함수를 통해 받아온 데이터를 쿼리 키를 통해 구분하겠다는 의미</li>
</ul>
<h4 id="2-usemutation서버에-데이터를-추가-업데이트-또는-삭제">2. useMutation(서버에 데이터를 추가, 업데이트 또는 삭제)</h4>
<pre><code>import { useMutation, useQueryClient } from &#39;@tanstack/react-query&#39;;
import { createPost } from &#39;../api/posts&#39;;

const CreatePost = () =&gt; {
    const queryClient = useQueryClient();
    const mutation = useMutation(createPost, {
        onSuccess: () =&gt; {
            // 데이터가 성공적으로 추가된 후 쿼리 재요청
            queryClient.invalidateQueries([&#39;posts&#39;]);
        },
    });

    const handleSubmit = async (event: React.FormEvent) =&gt; {
        event.preventDefault();
        const data = { title: event.currentTarget.title.value };
        mutation.mutate(data);
    };

    return (
        &lt;form onSubmit={handleSubmit}&gt;
            &lt;input name=&quot;title&quot; required /&gt;
            &lt;button type=&quot;submit&quot;&gt;Create Post&lt;/button&gt;
        &lt;/form&gt;
    );
};
</code></pre><ul>
<li><p>매개변수(ex. createPost) 서버에 데이터를 <code>추가, 수정 또는 삭제</code>하는 비동기 작업을 정의
cf ) createPost 함수</p>
<pre><code>export const createPost = async (newPost: { title: string }) =&gt; {
  const response = await fetch(&#39;https://jsonplaceholder.typicode.com/posts&#39;, {
      method: &#39;POST&#39;,
      headers: {
          &#39;Content-Type&#39;: &#39;application/json&#39;,
      },
      body: JSON.stringify(newPost),
  });

  if (!response.ok) {
      throw new Error(&#39;Network response was not ok&#39;);
  }

  return response.json(); // 서버에서 반환된 새 게시물 데이터
};</code></pre></li>
<li><p><em>useMutation의 옵션*</em></p>
</li>
<li><p><code>onSuccess</code>: 뮤테이션이 성공적으로 완료되었을 때 호출되는 콜백 함수</p>
</li>
<li><p><code>onError</code>: 뮤테이션이 실패했을 때 호출되는 콜백 함수</p>
</li>
<li><p><code>onSettled</code>: 뮤테이션이 성공하거나 실패한 후 호출되는 콜백 함수</p>
</li>
<li><p><code>onMutate</code>: 뮤테이션이 시작되기 전에 호출되는 콜백 함수</p>
</li>
<li><p>그러니깐, try(onSuccess), catch(onError), finally(onSettled)의 역할을 한다고 이해하면 됨!</p>
</li>
</ul>
<p><strong>사용예시</strong></p>
<pre><code>import { useMutation, useQueryClient } from &#39;@tanstack/react-query&#39;;
import { createPost } from &#39;../api/posts&#39;;

const CreatePost = () =&gt; {
    const queryClient = useQueryClient();

    const mutation = useMutation(createPost, {
        onMutate: () =&gt; {
            // 뮤테이션이 시작되기 전에 호출
            console.log(&#39;Mutation started&#39;);
        },
        onSuccess: (data) =&gt; {
            console.log(&#39;Post created successfully!&#39;, data);
            queryClient.invalidateQueries([&#39;posts&#39;]);
        },
        onError: (error) =&gt; {
            console.error(&#39;Error creating post:&#39;, error);
        },
        onSettled: () =&gt; {
            // 성공 여부와 관계없이 호출
            console.log(&#39;Mutation settled&#39;);
        },
    });

    const handleSubmit = async (event: React.FormEvent) =&gt; {
        event.preventDefault();
        const data = { title: event.currentTarget.title.value };
        //currentTarget: 이벤트가 바인딩된 요소를 참조, 이벤트가 발생한 폼 요소 자체를 가리
        // const data = { title: event.target.title.value };
        // event.target: 이벤트가 실제로 발생한 요소, 일반적으로 currentTarget과 같지만, 
        // 이벤트가 버블링되거나 캡처링되는 경우에는 다를 수 있음
        mutation.mutate(data); // 뮤테이션 실행
    };

    return (
        &lt;form onSubmit={handleSubmit}&gt;
            &lt;input name=&quot;title&quot; required /&gt;
            &lt;button type=&quot;submit&quot;&gt;Create Post&lt;/button&gt;
        &lt;/form&gt;
    );
};
</code></pre><p>어? <code>useQueryClient</code>는 뭔데?</p>
<h4 id="3-usequeryclientqueryclient-인스턴스에-접근할-수-있는-훅">3. useQueryClient(QueryClient 인스턴스에 접근할 수 있는 훅)</h4>
<ul>
<li>데이터 무효화, 재요청 등의 작업<pre><code>import { useQueryClient } from &#39;@tanstack/react-query&#39;;
</code></pre></li>
</ul>
<p>const SomeComponent = () =&gt; {
    const queryClient = useQueryClient();</p>
<pre><code>const handleRefresh = () =&gt; {
    queryClient.invalidateQueries([&#39;posts&#39;]);
};

return &lt;button onClick={handleRefresh}&gt;Refresh Posts&lt;/button&gt;;</code></pre><p>};</p>
<pre><code>- 그래가지고 위 예시에서는 뮤테이션 작업이 성공적으로 완료된 후, 기존의 게시물 데이터를 새로 갱신하기 위해 useQueryClient의 invalidateQueries 메소드를 사용(새로운 게시물이 추가된 후, UI에서 항상 최신 상태의 데이터를 보여주기 위해 쿼리를 무효화)

#### 4. useInfiniteQuery(무한 스크롤을 구현)
- 페이지네이션된 데이터를 가져오는 데 유용</code></pre><p>import { useInfiniteQuery } from &#39;@tanstack/react-query&#39;;
import { fetchPosts } from &#39;../api/posts&#39;;</p>
<p>const Posts = () =&gt; {
    const {
        data,
        fetchNextPage,
        hasNextPage,
        isLoading,
    } = useInfiniteQuery([&#39;posts&#39;], fetchPosts, {
        getNextPageParam: (lastPage) =&gt; lastPage.nextCursor,
    });</p>
<pre><code>return (
    &lt;div&gt;
        {isLoading &amp;&amp; &lt;div&gt;Loading...&lt;/div&gt;}
        {data?.pages.map((page) =&gt;
            page.posts.map((post) =&gt; &lt;div key={post.id}&gt;{post.title}&lt;/div&gt;)
        )}
        {hasNextPage &amp;&amp; &lt;button onClick={fetchNextPage}&gt;Load More&lt;/button&gt;}
    &lt;/div&gt;
);</code></pre><p>};</p>
<pre><code>- 약간 useNavigation의 느낌이랄까? 그래서 해당 매개변수를 알아두면 유용할 것 같다.

**useInfiniteQuery의 매개변수**
- `getNextPageParam`: 다음 페이지를 요청하는 데 필요한 매개변수를 정의</code></pre><p>getNextPageParam: (lastPage, pages) =&gt; {
    return lastPage.nextCursor; // lastPage에서 다음 커서를 가져옴
}</p>
<pre><code>- `getPreviousPageParam`: 이전 페이지를 요청하는 데 필요한 매개변수를 정의</code></pre><p>getPreviousPageParam: (firstPage, pages) =&gt; {
    return firstPage.previousCursor; // 첫 페이지에서 이전 커서를 가져옴
}</p>
<pre><code>- `staleTime`: 쿼리 데이터가 신선하다고 간주되는 시간(밀리초). 이 시간 동안 쿼리는 재요청되지 않음
- `cacheTime`: 쿼리 데이터가 캐시에 남아 있는 시간(밀리초). 이 시간이 지나면 데이터는 garbage collection에 의해 제거
- `refetchOnWindowFocus`: 브라우저 창이 포커스를 받을 때 쿼리를 자동으로 다시 요청할지를 결정
- `refetchOnReconnect`: 네트워크가 다시 연결될 때 쿼리를 자동으로 다시 요청할지를 결정
- `enabled`: 쿼리의 활성화 여부를 결정하는 불리언 값. true일 경우에만 쿼리가 실행

![](https://velog.velcdn.com/images/gazero_/post/d8a9b1a8-8f32-4fc7-8e23-5e065ac2e202/image.png)

너무 많아서 이쯤에서 _**포기할까?**_ 라는 생각 한 번 정도 해줘야함

#### 5. useIsFetching(현재 쿼리가 진행 중인지 여부를 확인)</code></pre><p>import { useIsFetching } from &#39;@tanstack/react-query&#39;;</p>
<p>const LoadingIndicator = () =&gt; {
    const isFetching = useIsFetching();</p>
<pre><code>return isFetching ? &lt;div&gt;Loading...&lt;/div&gt; : null;</code></pre><p>};</p>
<pre><code>- 전역 로딩 인디케이터, 조건부 렌더링, 상태 기반 스타일링 등이 필요할 때 주로 사용

#### 6. useHydrate(서버 사이드 렌더링(SSR)과 클라이언트 사이드에서의 데이터 동기화를 도와주는 훅)</code></pre><p>import { Hydrate, QueryClient, QueryClientProvider } from &#39;@tanstack/react-query&#39;;</p>
<p>const App = ({ dehydratedState }) =&gt; {
    const queryClient = new QueryClient();</p>
<pre><code>return (
    &lt;QueryClientProvider client={queryClient}&gt;
        &lt;Hydrate state={dehydratedState}&gt;
            {/* Your application components */}
        &lt;/Hydrate&gt;
    &lt;/QueryClientProvider&gt;
);</code></pre><p>};</p>
<pre><code>- Hydrate 컴포넌트는 서버에서 미리 가져온 데이터를 클라이언트의 캐시에 주입하는 역할
- 서버 사이드 렌더링(SSR) 또는 정적 사이트 생성(SSG) 시, 서버에서 미리 데이터를 가져와 클라이언트에서 다시 요청하지 않고도 렌더링할 수 있음

중요한건 꺾이지 않는 마음이 아니라, 꺾여도 하는 마음이라고 누가 그랬는데 뭔소리야.
![](https://velog.velcdn.com/images/gazero_/post/354dd6b8-4e3c-454b-8dcd-2b059bbd7b10/image.jpg)
이미 하기 싫어 죽겠어가지고 아니 NextJS는 기본적으로 SSR이잖아? 그럼 useHydrate는 없어도 되는거 아녔어???라고 하나 정도 줄여보려고 했는데 **아님**.

### 나, NextJS 마스터 다! 손. 그럼 이 부분은 넘기셔도 됨
#### 서버 사이드 렌더링(SSR) 예시</code></pre><p>// pages/index.js
import { Hydrate, QueryClient, QueryClientProvider, useQuery } from &#39;@tanstack/react-query&#39;;</p>
<p>const fetchPosts = async () =&gt; {
    const res = await fetch(&#39;<a href="https://jsonplaceholder.typicode.com/posts&#39;">https://jsonplaceholder.typicode.com/posts&#39;</a>);
    if (!res.ok) throw new Error(&#39;Network response was not ok&#39;);
    return res.json();
};</p>
<p>const PostsList = () =&gt; {
    const { data, error, isLoading } = useQuery(&#39;posts&#39;, fetchPosts);</p>
<pre><code>if (isLoading) return &lt;div&gt;Loading...&lt;/div&gt;;
if (error) return &lt;div&gt;Error: {error.message}&lt;/div&gt;;

return (
    &lt;ul&gt;
        {data.map((post) =&gt; (
            &lt;li key={post.id}&gt;{post.title}&lt;/li&gt;
        ))}
    &lt;/ul&gt;
);</code></pre><p>};</p>
<p>const App = ({ dehydratedState }) =&gt; {
    const queryClient = new QueryClient();</p>
<pre><code>return (
    &lt;QueryClientProvider client={queryClient}&gt;
        &lt;Hydrate state={dehydratedState}&gt;
            &lt;PostsList /&gt;
        &lt;/Hydrate&gt;
    &lt;/QueryClientProvider&gt;
);</code></pre><p>};</p>
<p>// 서버 사이드 렌더링을 위한 getServerSideProps
export async function getServerSideProps() {
    const queryClient = new QueryClient();</p>
<pre><code>// 데이터 미리 가져오기
await queryClient.prefetchQuery(&#39;posts&#39;, fetchPosts);

return {
    props: {
        dehydratedState: dehydrate(queryClient), // hydrate에 사용할 상태
    },
};</code></pre><p>}</p>
<p>export default App;</p>
<pre><code>서버 사이드 렌더링을 위한 **getServerSideProps**
- `getServerSideProps`는 Next.js에서 서버 사이드 렌더링(SSR)을 구현하기 위한 함수
- 페이지 요청 시 서버에서 실행되며, 데이터를 **미리 가져와서 페이지를 렌더링**하는 데 필요한 props를 반환

#### 정적 사이트 생성(SSG) 예시</code></pre><p>// pages/index.js
import { Hydrate, QueryClient, QueryClientProvider, useQuery } from &#39;@tanstack/react-query&#39;;
import { dehydrate } from &#39;@tanstack/react-query&#39;;</p>
<p>const fetchPosts = async () =&gt; {
    const res = await fetch(&#39;<a href="https://jsonplaceholder.typicode.com/posts&#39;">https://jsonplaceholder.typicode.com/posts&#39;</a>);
    if (!res.ok) throw new Error(&#39;Network response was not ok&#39;);
    return res.json();
};</p>
<p>const PostsList = () =&gt; {
    const { data, error, isLoading } = useQuery(&#39;posts&#39;, fetchPosts);</p>
<pre><code>if (isLoading) return &lt;div&gt;Loading...&lt;/div&gt;;
if (error) return &lt;div&gt;Error: {error.message}&lt;/div&gt;;

return (
    &lt;ul&gt;
        {data.map((post) =&gt; (
            &lt;li key={post.id}&gt;{post.title}&lt;/li&gt;
        ))}
    &lt;/ul&gt;
);</code></pre><p>};</p>
<p>const App = ({ dehydratedState }) =&gt; {
    const queryClient = new QueryClient();</p>
<pre><code>return (
    &lt;QueryClientProvider client={queryClient}&gt;
        &lt;Hydrate state={dehydratedState}&gt;
            &lt;PostsList /&gt;
        &lt;/Hydrate&gt;
    &lt;/QueryClientProvider&gt;
);</code></pre><p>};</p>
<p>// 정적 사이트 생성을 위한 getStaticProps
export async function getStaticProps() {
    const queryClient = new QueryClient();</p>
<pre><code>// 데이터 미리 가져오기
await queryClient.prefetchQuery(&#39;posts&#39;, fetchPosts);

return {
    props: {
        dehydratedState: dehydrate(queryClient), // hydrate에 사용할 상태
    },
    revalidate: 10, // 10초마다 재생성
};</code></pre><p>}</p>
<p>export default App;</p>
<pre><code>
이 부분에 대해서는 따로 정리한 것이 있음. 아직 업로드를 하지 않은 것임. 그래서 그냥 이렇게 쓰이기 때문에 useHydrate가 NextJS에서도 사용되는거구나 정도로만 이해하고 넘어가세오.

혹시 눈치 챈 사람 있나?
지나오면서 `QueryClient` 설명을 했을때, `invalidateQueries` 메서드를 사용했다는 부분.

#### invalidateQueries의 주요 메서드(이것또 정리했찌 이게 바로 나.)
`fetchQuery`

```jsx
const data = await queryClient.fetchQuery(&#39;posts&#39;, fetchPosts);
</code></pre><ul>
<li>지정한 쿼리 키와 쿼리 함수를 사용하여 데이터를 즉시 가져옴</li>
<li>쿼리를 실행하고, 결과를 반환</li>
<li>쿼리는 캐시되지 않으며, 항상 최신 데이터를 가져옴</li>
</ul>
<p><code>prefetchQuery</code></p>
<pre><code class="language-jsx">await queryClient.prefetchQuery(&#39;posts&#39;, fetchPosts);
</code></pre>
<ul>
<li>prefetchQuery는 지정한 쿼리 키와 쿼리 함수를 사용하여 데이터를 미리 가져옴</li>
<li>미리 가져온 데이터는 캐시에 저장</li>
<li>데이터를 캐시에 저장하므로, 다음 번에 해당 쿼리를 요청할 때 캐시된 데이터를 즉시 사용가능</li>
</ul>
<p><code>invalidateQueries</code></p>
<pre><code class="language-jsx">queryClient.invalidateQueries(&#39;posts&#39;);
</code></pre>
<ul>
<li>invalidateQueries는 지정한 쿼리 키에 대한 캐시를 무효화</li>
<li>다음 렌더링 시 해당 쿼리를 다시 요청</li>
</ul>
<p><code>setQueryData</code></p>
<pre><code class="language-jsx">queryClient.setQueryData(&#39;posts&#39;, newData);
</code></pre>
<ul>
<li>setQueryData는 특정 쿼리 키에 대한 데이터를 수동으로 설정</li>
<li>캐시된 데이터를 업데이트할 때 사용</li>
<li>새로운 데이터로 기존 데이터를 대체</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gazero_/post/dea46516-a500-443f-9da4-718c7388587f/image.jpg" alt=""></p>
<p>자, 이제 <strong>끝</strong>이 아니라. <strong>시작</strong>임 아직 아무것도 하지 않았음. 나는 메서드만 봤을 뿐임. 하지만 내 맘은 아까 꺾임.
그렇지만 또 안할 수 없는 현실이 싫을 뿐 다음에는 작은 예시 혹은 프로젝트에 직접 적용해보겠씀. 이만 춍춍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(번역)Next.js 앱 라우터 마스터링: 애플리케이션 구조화를 위한 모범 사례]]></title>
            <link>https://velog.io/@gazero_/%EB%B2%88%EC%97%ADNext.js-%EC%95%B1-%EB%9D%BC%EC%9A%B0%ED%84%B0-%EB%A7%88%EC%8A%A4%ED%84%B0%EB%A7%81-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B5%AC%EC%A1%B0%ED%99%94%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%AA%A8%EB%B2%94-%EC%82%AC%EB%A1%80</link>
            <guid>https://velog.io/@gazero_/%EB%B2%88%EC%97%ADNext.js-%EC%95%B1-%EB%9D%BC%EC%9A%B0%ED%84%B0-%EB%A7%88%EC%8A%A4%ED%84%B0%EB%A7%81-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B5%AC%EC%A1%B0%ED%99%94%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%AA%A8%EB%B2%94-%EC%82%AC%EB%A1%80</guid>
            <pubDate>Mon, 18 Nov 2024 00:40:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>원문: <a href="https://thiraphat-ps-dev.medium.com/mastering-next-js-app-router-best-practices-for-structuring-your-application-3f8cf0c76580">https://thiraphat-ps-dev.medium.com/mastering-next-js-app-router-best-practices-for-structuring-your-application-3f8cf0c76580</a></p>
</blockquote>
<p> Next.js는 선도적인 React 프레임워크 중 하나로서 입지를 굳건히 했으며, 개발자에게 <strong>성능, 유연성, 사용 편의성</strong>을 매끄럽게 결합했다.  App Router가 출시되면서 Next.js는 또 다른 도약을 이루어 더욱 직관적이고 강력한 라우팅 메커니즘을 제공했다. 개인 블로그를 구축하든 대규모 엔터프라이즈 애플리케이션을 구축하든 Next.js 프로젝트를 효과적으로 구조화하는 것은 <strong>유지 관리, 확장성, 개발자 경험</strong>에 매우 중요하다.</p>
<p> 이 문서에서는 App Router를 사용하여 Next.js 애플리케이션을 구조화하는 모범 사례를 살펴보고자 한다. 이를 통해 프로젝트가 체계적이고 효율적이며 탐색하기 쉬운 상태를 유지할 수 있다.</p>
<h1 id="목차"><strong>목차</strong></h1>
<ol>
<li>Next.js 앱 라우터 이해</li>
<li>프로젝트 구조 개요</li>
<li>페이지 및 경로 구성</li>
<li>구성 요소를 효과적으로 관리하기</li>
<li>스타일링 전략</li>
<li>상태 관리 모범 사례</li>
<li>성능 최적화</li>
<li>테스트 및 품질 보증</li>
<li>배포 고려 사항</li>
<li>결론</li>
</ol>
<h1 id="nextjs-앱-라우터-이해"><strong>Next.js 앱 라우터 이해</strong></h1>
<p>구조를 먼저 보기 전에 App Router가 무엇을 제공하는지 파악하는 것이 중요하다. Next.js의 App Router는 다음과 같은 기능을 도입하여 라우팅 기능을 향상시킨다.</p>
<ul>
<li><p><strong>중첩 라우팅</strong></p>
<p>  : 중첩된 레이아웃으로 복잡한 경로 계층을 지원</p>
</li>
<li><p><strong>동적 경로</strong></p>
<p>  : 동적 URL 생성을 간소화</p>
</li>
<li><p><strong>서버 구성 요소</strong></p>
<p>  : 더 나은 성능을 위해 서버에서 구성 요소를 렌더링</p>
</li>
<li><p><strong>향상된 데이터 가져오기</strong></p>
<p>  : 클라이언트 측 부하를 줄여서 데이터를 가져오는 더 나은 방법을 제공</p>
</li>
</ul>
<p>이러한 기능을 이해하는 것은 프로젝트 구조에서 App Router를 효과적으로 활용하는 데 필수적이다.</p>
<h2 id="프로젝트-구조-개요">프로젝트 구조 개요</h2>
<p>잘 구성된 프로젝트 구조는 <strong>가독성, 유지 관리성, 확장성</strong>을 향상시킨다. App Router를 사용하는 Next.js 애플리케이션에 권장되는 디렉토리 레이아웃은 다음과 같다.</p>
<pre><code class="language-jsx">my-nextjs-app/
├── app/
│   ├── layout.js
│   ├── page.js
│   ├── dashboard/
│   │   ├── layout.js
│   │   ├── page.js
│   │   └── settings/
│   │       └── page.js
│   └── blog/
│       ├── layout.js
│       ├── page.js
│       └── [slug]/
│           └── page.js
├── components/
│   ├── Header.js
│   ├── Footer.js
│   └── ... 
├── styles/
│   ├── globals.css
│   └── ...
├── public/
│   ├── images/
│   └── ... 
├── lib/
│   ├── api.js
│   └── ... 
├── hooks/
│   ├── useAuth.js
│   └── ... 
├── tests/
│   ├── components/
│   └── ... 
├── package.json
├── next.config.js
└── ...</code></pre>
<h1 id="주요-디렉토리-및-파일"><strong>주요 디렉토리 및 파일</strong></h1>
<ul>
<li><p><strong>app/</strong></p>
<p>  : App Router를 활용하는 모든 경로와 레이아웃을 포함</p>
</li>
<li><p><strong>components/</strong></p>
<p>  : 재사용 가능한 UI 구성요소</p>
</li>
<li><p><strong>styles/</strong></p>
<p>  : 글로벌 및 구성 요소별 스타일</p>
</li>
<li><p><strong>public/</strong></p>
<p>  : 이미지, 글꼴 등과 같은 정적 자산</p>
</li>
<li><p><strong>lib/</strong></p>
<p>  : 유틸리티 함수 및 라이브러리</p>
</li>
<li><p><strong>hooks/</strong></p>
<p>  : 사용자 정의 React hooks</p>
</li>
<li><p><strong>tests/</strong></p>
<p>  : 애플리케이션의 다양한 부분에 대한 테스트 모음</p>
</li>
</ul>
<h1 id="페이지-및-경로-구성"><strong>페이지 및 경로 구성</strong></h1>
<p>App Router를 통해 Next.js는 디렉토리 구조가 URL 구조를 반영하는 파일 기반 라우팅 시스템을 장려한다. 페이지와 경로를 효과적으로 구성하는 방법은 다음과 같다.</p>
<h1 id="중첩된-경로-및-레이아웃"><strong>중첩된 경로 및 레이아웃</strong></h1>
<p>중첩 라우팅을 활용하여 계층적 레이아웃을 만든다. 예를 들어, 대시보드 섹션에는 주 애플리케이션 레이아웃과 별도로 자체 레이아웃이 있을 수 있다.</p>
<pre><code class="language-jsx">app/
├── layout.js           // Main application layout
├── page.js             // Home page
├── dashboard/
│   ├── layout.js       // Dashboard-specific layout
│   ├── page.js         // Dashboard home
│   └── settings/
│       └── page.js     // Dashboard settings</code></pre>
<h1 id="동적-경로"><strong>동적 경로</strong></h1>
<p>대괄호 표기법으로 폴더를 만들어 동적 콘텐츠를 처리한다. 예를 들어, 블로그 게시물은 <code>/blog/[slug]</code> 을 통해 액세스할 수 있다.</p>
<pre><code class="language-jsx">app/
└── blog/
    └── [slug]/
        └── page.js</code></pre>
<p><code>page.js</code> 파일 내부에는:</p>
<pre><code class="language-jsx">export default function BlogPost({ params }) {
  const { slug } = params;
  // Fetch and render blog post based on slug
}</code></pre>
<h1 id="catch-all-routes캐치올-경로"><strong>Catch-All Routes(캐치올 경로)</strong></h1>
<p>여러 구간을 캡처해야 하는 경로의 경우 <code>[...param]</code>구문을 사용한다.</p>
<pre><code class="language-jsx">app/
└── docs/
    └── [...slug]/
        └── page.js</code></pre>
<p>이 설정은 다음과 같은 URL을 처리할 수 있다. <code>/docs/intro/getting-started</code>.</p>
<h1 id="구성-요소를-효과적으로-관리하기"><strong>구성 요소를 효과적으로 관리하기</strong></h1>
<p>구성요소를 구성하는 것은 재사용성과 유지관리에 필수적이다.</p>
<h1 id="atomic-design-principles"><strong>Atomic Design Principles</strong></h1>
<p>원자 설계 원칙을 채택하여 구성 요소를 분류</p>
<ul>
<li><p><strong>Atoms(원자)</strong></p>
<p>  : 버튼, 입력과 같은 기본적인 구성 요소</p>
</li>
<li><p><strong>Molecules(분자)</strong></p>
<p>  : 기능적 단위를 형성하는 원자의 조합</p>
</li>
<li><p><strong>Organisms(유기체)</strong></p>
<p>  : 분자와 원자로 구성된 복잡한 UI 섹션</p>
</li>
<li><p><strong>Templates(템플릿)</strong></p>
<p>  : 페이지 수준 구조.</p>
</li>
<li><p><strong>Pages(페이지)</strong></p>
<p>  : 템플릿과 데이터를 결합한 특정 인스턴스</p>
</li>
</ul>
<p>디렉토리 구조:</p>
<pre><code class="language-jsx">components/
├── atoms/
│   ├── Button.js
│   └── Input.js
├── molecules/
│   ├── FormGroup.js
│   └── Card.js
├── organisms/
│   ├── Header.js
│   └── Footer.js
└── ...</code></pre>
<h1 id="구성-요소-명명-및-파일-구조"><strong>구성 요소 명명 및 파일 구조</strong></h1>
<p>예를 들어, <code>Button.js</code>구성 요소와 <code>Button.module.css</code>해당 스타일에 대해 명확하고 일관된 명명 규칙을 사용한다.</p>
<pre><code class="language-jsx">// components/atoms/Button.js
import styles from &#39;./Button.module.css&#39;;

export default function Button({ children, onClick }) {
  return (
    &lt;button className={styles.button} onClick={onClick}&gt;
      {children}
    &lt;/button&gt;
  );
}</code></pre>
<h1 id="스타일링-전략"><strong>스타일링 전략</strong></h1>
<p>올바른 스타일링 방식을 선택하면 개발 워크플로와 애플리케이션 성능에 영향을 미칠 수 있다.</p>
<h1 id="css-모듈"><strong>CSS 모듈</strong></h1>
<p>Next.js는 기본적으로 CSS 모듈을 지원하여 범위가 지정되고 모듈화된 CSS를 사용할 수 있다.</p>
<pre><code class="language-jsx">components/
└── atoms/
    └── Button.module.css</code></pre>
<h1 id="스타일이-적용된-구성-요소-또는-css-in-js"><strong>스타일이 적용된 구성 요소 또는 CSS-in-JS</strong></h1>
<p>동적인 스타일과 테마를 적용하려면 Styled Components나 Emotion과 같은 라이브러리를 고려해야 한다.</p>
<pre><code class="language-jsx">// components/atoms/Button.js
import styled from &#39;styled-components&#39;;

const StyledButton = styled.button`
  background-color: #0070f3;
  color: white;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
`;
export default function Button({ children, onClick }) {
  return &lt;StyledButton onClick={onClick}&gt;{children}&lt;/StyledButton&gt;;
}</code></pre>
<h1 id="테일윈드-css"><strong>테일윈드 CSS</strong></h1>
<p>Tailwind는 HTML을 벗어나지 않고도 빠르게 스타일을 지정할 수 있는 유틸리티 중심의 CSS 클래스를 제공한다.</p>
<pre><code class="language-jsx">// components/atoms/Button.js
export default function Button({ children, onClick }) {
  return (
    &lt;button
      className=&quot;bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600&quot;
      onClick={onClick}
    &gt;
      {children}
    &lt;/button&gt;
  );
}</code></pre>
<h1 id="글로벌-스타일"><strong>글로벌 스타일</strong></h1>
<p><code>globals.css</code>기본 스타일과 글로벌 구성에 이 파일을 사용하면 된다.</p>
<pre><code class="language-jsx">/* styles/globals.css */
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, Oxygen,
    Ubuntu, Cantarell, &#39;Open Sans&#39;, &#39;Helvetica Neue&#39;, sans-serif;
}</code></pre>
<p>루트 레이아웃으로 가져오는 경우:</p>
<pre><code class="language-jsx">// app/layout.js
import &#39;../styles/globals.css&#39;;

export default function RootLayout({ children }) {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;body&gt;{children}&lt;/body&gt;
    &lt;/html&gt;
  );
}</code></pre>
<h1 id="상태-관리-모범-사례"><strong>상태 관리 모범 사례</strong></h1>
<p>효과적인 상태 관리를 통해 애플리케이션이 예측 가능하고 디버깅하기 쉬운 상태를 유지할 수 있다.</p>
<h1 id="react-hooks를-사용한-로컬-상태"><strong>React Hooks를 사용한 로컬 상태</strong></h1>
<p>컴포넌트 수준 상태를 위해 React의 기본 제공 Hooks인 <code>useState</code>및 <code>useReducer</code>를 사용한다.</p>
<pre><code class="language-jsx">import { useState } from &#39;react&#39;;

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    &lt;div&gt;
      &lt;p&gt;{count}&lt;/p&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;Increment&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h1 id="context-api를-사용한-글로벌-상태"><strong>Context API를 사용한 글로벌 상태</strong></h1>
<p>여러 구성 요소에서 상태를 공유하려면 React의 Context API를 활용하면 된다.</p>
<pre><code class="language-jsx">// context/AuthContext.js
import { createContext, useState } from &#39;react&#39;;

export const AuthContext = createContext();
export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  return (
    &lt;AuthContext.Provider value={{ user, setUser }}&gt;
      {children}
    &lt;/AuthContext.Provider&gt;
  );
}</code></pre>
<p>provider와 함께 애플리케이션을 감싸면:</p>
<pre><code class="language-jsx">// app/layout.js
import { AuthProvider } from &#39;../context/AuthContext&#39;;

export default function RootLayout({ children }) {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;body&gt;
        &lt;AuthProvider&gt;
          {children}
        &lt;/AuthProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}</code></pre>
<h1 id="전역-관리-라이브러리"><strong>전역 관리 라이브러리</strong></h1>
<p>복잡한 상태 요구 사항에는 Redux, Zustand, Recoil과 같은 라이브러리를 고려할 필요가 있다.</p>
<pre><code class="language-jsx">// Using Zustand
import create from &#39;zustand&#39;;

const useStore = create(set =&gt; ({
  user: null,
  setUser: (user) =&gt; set({ user }),
}));
export default useStore;</code></pre>
<h1 id="성능-최적화"><strong>성능 최적화</strong></h1>
<p>성능 최적화는 원활한 사용자 경험과 더 나은 SEO 순위를 보장한다.</p>
<h1 id="코드-분할-및-지연-로딩"><strong>코드 분할 및 지연 로딩</strong></h1>
<p>Next.js는 자동으로 코드를 분할하지만, 동적 가져오기를 사용하여 추가로 최적화 할 수 있다.</p>
<pre><code class="language-jsx">import dynamic from &#39;next/dynamic&#39;;

const HeavyComponent = dynamic(() =&gt; import(&#39;../components/HeavyComponent&#39;), {
  loading: () =&gt; &lt;p&gt;Loading...&lt;/p&gt;,
});</code></pre>
<h1 id="이미지-최적화"><strong>이미지 최적화</strong></h1>
<p>최적화된 이미지 로딩을 위해 Next.js의 <code>Image</code>컴포넌트를 사용해야 한다.</p>
<pre><code class="language-jsx">import Image from &#39;next/image&#39;;

export default function Profile() {
  return (
    &lt;Image
      src=&quot;/images/profile.jpg&quot;
      alt=&quot;Profile Picture&quot;
      width={200}
      height={200}
    /&gt;
  );
}</code></pre>
<h1 id="캐싱-및-cdn"><strong>캐싱 및 CDN</strong></h1>
<p>캐싱 전략과 콘텐츠 전송 네트워크(CDN)를 활용하여 정적 자산을 효율적으로 제공한다.</p>
<h1 id="서버-사이드-렌더링ssr-및-정적-사이트-생성ssg"><strong>서버 사이드 렌더링(SSR) 및 정적 사이트 생성(SSG)</strong></h1>
<p>콘텐츠의 동적 특성에 따라 SSR과 SSG 중에서 선택하여 성능과 최신성 사이의 균형을 유지해라.</p>
<pre><code class="language-jsx">// pages/index.js
export async function getStaticProps() {
  const data = await fetchData();
  return {
    props: { data },
    revalidate: 60, // Revalidate every 60 seconds
  };
}</code></pre>
<h1 id="테스트-및-품질-보증"><strong>테스트 및 품질 보증</strong></h1>
<p>테스트를 구현하면 애플리케이션이 강력하게 유지되고 회귀 현상이 발생하지 않게 된다.</p>
<h1 id="단위-테스트"><strong>단위 테스트</strong></h1>
<p>단위 테스트에는 Jest와 React Testing Library와 같은 프레임워크를 사용해라.</p>
<pre><code class="language-jsx">// tests/components/Button.test.js
import { render, screen, fireEvent } from &#39;@testing-library/react&#39;;
import Button from &#39;../../components/atoms/Button&#39;;

test(&#39;renders button and handles click&#39;, () =&gt; {
  const handleClick = jest.fn();
  render(&lt;Button onClick={handleClick}&gt;Click Me&lt;/Button&gt;);

  const button = screen.getByText(&#39;Click Me&#39;);
  fireEvent.click(button);

  expect(handleClick).toHaveBeenCalledTimes(1);
});</code></pre>
<h1 id="통합-테스트"><strong>통합 테스트</strong></h1>
<p>애플리케이션의 다양한 부분이 어떻게 함께 작동하는지 테스트한다.javascript</p>
<pre><code class="language-jsx">// tests/pages/HomePage.test.js
import { render, screen } from &#39;@testing-library/react&#39;;
import HomePage from &#39;../../app/page&#39;;

test(&#39;renders home page with header and footer&#39;, () =&gt; {
  render(&lt;HomePage /&gt;);
  expect(screen.getByText(&#39;Welcome&#39;)).toBeInTheDocument();
  expect(screen.getByText(&#39;Footer&#39;)).toBeInTheDocument();
});</code></pre>
<h1 id="엔드투엔드e2e-테스트"><strong>엔드투엔드(E2E) 테스트</strong></h1>
<p>Cypress나 Playwright와 같은 도구를 사용하여 E2E 테스트를 실시하여 사용자 상호작용을 시뮬레이션한다.</p>
<pre><code class="language-jsx">// cypress/integration/home.spec.js
describe(&#39;Home Page&#39;, () =&gt; {
  it(&#39;loads successfully&#39;, () =&gt; {
    cy.visit(&#39;/&#39;);
    cy.contains(&#39;Welcome&#39;).should(&#39;be.visible&#39;);
  });
});</code></pre>
<h1 id="배포-고려-사항"><strong>배포 고려 사항</strong></h1>
<p>Next.js 애플리케이션을 효율적으로 배포하면 다운타임을 최소화하고 최적의 성능을 보장할 수 있다.</p>
<h1 id="vercel">vercel</h1>
<p>Next.js를 개발한 팀이 만든 Vercel은 최적화된 성능으로 원활한 배포를 제공한다.</p>
<h1 id="다른-플랫폼"><strong>다른 플랫폼</strong></h1>
<p>Netlify, AWS 또는 DigitalOcean과 같은 플랫폼에도 배포할 수 있다.  SSR 및 API 경로가 올바르게 구성되었는지 확인햐러,`</p>
<h1 id="환경-변수"><strong>환경 변수</strong></h1>
<p>환경 변수를 사용하여 민감한 데이터를 관리합니다.</p>
<pre><code class="language-jsx"># .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
SECRET_KEY=your-secret-key</code></pre>
<p>당신의 application을 액세스하면:</p>
<pre><code class="language-jsx">const apiUrl = process.env.NEXT_PUBLIC_API_URL;</code></pre>
<h1 id="지속적인-통합-및-배포cicd"><strong>지속적인 통합 및 배포(CI/CD)</strong></h1>
<p>GitHub Actions, GitLab CI 또는 기타 도구를 사용하여 CI/CD 파이프라인을 구현하여 테스트 및 배포를 자동화한다.</p>
<pre><code class="language-jsx"># .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [ main ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install Dependencies
        run: npm install
      - name: Run Tests
        run: npm test
      - name: Build
        run: npm run build</code></pre>
<h1 id="결론"><strong>결론</strong></h1>
<p>App Router를 사용하여 Next.js 애플리케이션을 효과적으로 구조화하는 것은 확장 가능하고 유지 관리가 가능하며 고성능 웹 애플리케이션을 만드는 데 매우 중요하다. 이 가이드에 설명된 모범 사례를 준수하면(프로젝트 구조 구성, 구성 요소 관리, 성능 최적화, 강력한 테스트 구현) Next.js의 모든 잠재력을 활용하고 뛰어난 사용자 경험을 제공할 수 있다.</p>
<p>이러한 관행을 도입하여 개발 워크플로를 간소화하고, 팀 협업을 용이하게 하고, 끊임없이 변화하는 웹 환경에서 애플리케이션이 시간의 시험을 견뎌낼 수 있도록 하길 바란다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[그거 아세요?(2탄) 개발자 도구에서 Toggle Device Toolbar로 반응형 화면이 확인이 안되는 거]]></title>
            <link>https://velog.io/@gazero_/%EA%B7%B8%EA%B1%B0-%EC%95%84%EC%84%B8%EC%9A%942%ED%83%84-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%8F%84%EA%B5%AC%EC%97%90%EC%84%9C-Toggle-Device-Toolbar%EB%A1%9C-%EB%B0%98%EC%9D%91%ED%98%95-%ED%99%94%EB%A9%B4%EC%9D%B4-%ED%99%95%EC%9D%B8%EC%9D%B4-%EC%95%88%EB%90%98%EB%8A%94-%EA%B1%B0</link>
            <guid>https://velog.io/@gazero_/%EA%B7%B8%EA%B1%B0-%EC%95%84%EC%84%B8%EC%9A%942%ED%83%84-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%8F%84%EA%B5%AC%EC%97%90%EC%84%9C-Toggle-Device-Toolbar%EB%A1%9C-%EB%B0%98%EC%9D%91%ED%98%95-%ED%99%94%EB%A9%B4%EC%9D%B4-%ED%99%95%EC%9D%B8%EC%9D%B4-%EC%95%88%EB%90%98%EB%8A%94-%EA%B1%B0</guid>
            <pubDate>Thu, 14 Nov 2024 06:02:55 GMT</pubDate>
            <description><![CDATA[<p>지난 번 _<strong>그거 아세요 1탄</strong>_을 해결하고 뿌듯한 마음에 자수를 했다.</p>
<p><img src="https://velog.velcdn.com/images/gazero_/post/5d5bb76c-bbce-4935-90b3-8008054b888d/image.png" alt="">
이게 무슨말이냐면</p>
<ul>
<li>반응형(모바일)화면 개발을 동시에 해야했다.</li>
<li>그래서 media 쿼리를 적용했다.</li>
<li>Toggle Device Toolbar로 반응형이 적용되지 않음을 확인했다.</li>
<li>하지만, 임의로 화면을 줄이면 어느 시점(내가 media 쿼리로 지정하지 않은 크기)에서 반응형이 적용된다.</li>
</ul>
<p>차라리 반응형이 적용안되면 그러려니 했을텐데 아무튼 이상했다.(그러려니 하는 것도 웃김ㅋㅋ)
<img src="https://velog.velcdn.com/images/gazero_/post/19d95086-c9f5-4e9f-831c-99d97fa77014/image.png" alt=""></p>
<p><strong>퇴사할까?</strong> 하다가 
결국 주변 프론트엔드 동료 및 선임분들께 도움을 요청했다. 모르면 물어봐야 한다.</p>
<p>그러던 중 단서를 발견한 단서
<img src="https://velog.velcdn.com/images/gazero_/post/d391729b-5d80-44b7-9315-5955878f57d8/image.jpeg" alt=""></p>
<p>&#39;기본 width가 계속 <code>980</code> 비율로 맞춰져 있음&#39;을 발견!
아직 해결 못하고, 다시 구글링</p>
<p><img src="https://velog.velcdn.com/images/gazero_/post/57d36be7-2fd5-4b34-9564-4d3b1924e7ca/image.png" alt=""></p>
<p><a href="https://nykim.work/84">웹 뚝딱 만드시는 분의 블로그</a>를 발견</p>
<pre><code>뷰포트(viewport)란, 웹 페이지에서 사용자의 보이는 영역(visible area)를 말합니다. 따라서 이는 기기별로 달라지게 됩니다.

같은 페이지라 하더라도 모바일과 태블릿은 화면 크기가 다르기 때문에 사용자가 볼 수 있는 범위가 다르니까요.

즉, 이 태그는 뷰포트를 지정해주는 역할을 합니다.</code></pre><pre><code>뷰포트를 정하지 않으면 width는 기본값인 980이 됩니다. 그럼, width:100%를 지정한 요소는 그냥 980px 값을 가지게 되겠죠.</code></pre><p>댑악....그래가지고 큰 깨달음을 얻는 나는</p>
<pre><code>&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1&quot;&gt;</code></pre><p>이 부분을 <code>head</code>안에 추가했다.
<img src="https://velog.velcdn.com/images/gazero_/post/8e48bd72-17de-411e-8ae5-c71735381055/image.png" alt=""></p>
<p>바로(6시간 걸려서) 해결
<img src="https://velog.velcdn.com/images/gazero_/post/682ec497-4d7d-40de-915c-dde6fbc7f512/image.jpeg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[그거 아세요? 크롬에서 폰트 스타일이 적용되지 않을 때 해결책.]]></title>
            <link>https://velog.io/@gazero_/%EA%B7%B8%EA%B1%B0-%EC%95%84%EC%84%B8%EC%9A%94-%ED%81%AC%EB%A1%AC%EC%97%90%EC%84%9C-%ED%8F%B0%ED%8A%B8-%EC%8A%A4%ED%83%80%EC%9D%BC%EC%9D%B4-%EC%A0%81%EC%9A%A9%EB%90%98%EC%A7%80-%EC%95%8A%EC%9D%84-%EB%95%8C-%ED%95%B4%EA%B2%B0%EC%B1%85</link>
            <guid>https://velog.io/@gazero_/%EA%B7%B8%EA%B1%B0-%EC%95%84%EC%84%B8%EC%9A%94-%ED%81%AC%EB%A1%AC%EC%97%90%EC%84%9C-%ED%8F%B0%ED%8A%B8-%EC%8A%A4%ED%83%80%EC%9D%BC%EC%9D%B4-%EC%A0%81%EC%9A%A9%EB%90%98%EC%A7%80-%EC%95%8A%EC%9D%84-%EB%95%8C-%ED%95%B4%EA%B2%B0%EC%B1%85</guid>
            <pubDate>Thu, 14 Nov 2024 05:46:09 GMT</pubDate>
            <description><![CDATA[<p><strong>프론트엔드를 선택한 이유</strong></p>
<ol>
<li>화면을 구현하는게 재밌었다.</li>
<li>화면을 구현하는게 자신있었다.</li>
</ol>
<p>하지만 화면 마크업을 하다 보면 CSS와 HTML이 전부가 아닐 때가 있다. 혼자 프로젝트를 하면 나와 타협해서 넘어갈 수 있지만, 그게 협업이고 업무일 때는 얘기가 달라진다.</p>
<p>해결이 안되니깐 화면을 구현하는게 재미없고, 자신감도 없어졌다.</p>
<p>&#39;아니, <strong><code>크롬</code>에서만 <code>폰트</code> 스타일 적용 안되는건 왜 그런건가요?</strong>&#39;
<img src="https://velog.velcdn.com/images/gazero_/post/c9257f23-b49c-48de-bdfe-24e721b407ad/image.jpeg" alt=""></p>
<p>심지어 내 컴퓨터는 맥북도 아니야! 바로 바로 크롬을 확인하기 어려웟!
<img src="https://velog.velcdn.com/images/gazero_/post/99eb065c-3169-4002-ac2b-b24c2c1b70e2/image.jpg" alt=""></p>
<p>내 화면에서는 잘 보였다. 그래서 디자이너님께 디자인 QA를 요청드렸고, 디자인 <strong>QA FAIL</strong> </p>
<p>구글링을 시작했다. 1시간이 지나고, 해결하지 못했다.
<img src="https://velog.velcdn.com/images/gazero_/post/ead190c4-a972-4bcb-a9f0-a4b6be57e849/image.png" alt=""></p>
<p>&#39;그냥 지금 퇴사한다고 할까...?&#39; 이거 때문인게 너무 티나서 안되겠다.
다시 구글링을 시작했다.
<img src="https://velog.velcdn.com/images/gazero_/post/830eab21-fa6d-4af5-b326-43c9a05e21b5/image.png" alt="">
나랑 같은 현상인 사람을 발견! (너모 너모 반가웠)
코딩애플님의 답변에서 힌트를 얻었다.
&#39;<code>구글폰트</code>&#39;....?</p>
<p><strong>기존</strong></p>
<pre><code> @font-face {
            font-family: &#39;Pretendard&#39;;
            src: url(&#39;https://cdn.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff&#39;) format(&#39;woff&#39;);
            font-weight: 400;
            font-style: normal;
            font-display: swap;
        }</code></pre><p><strong>수정 후</strong></p>
<pre><code> @import url(&#39;https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css&#39;);</code></pre><p><img src="https://velog.velcdn.com/images/gazero_/post/2857fb74-bbe3-4416-b9da-c03629b3ac15/image.png" alt=""></p>
<p>4시간 만에 수정을 완료했고, 디자인 QA를 마쳤다.
<img src="https://velog.velcdn.com/images/gazero_/post/db7ccc4b-b3ae-4744-818e-e351fc67fd94/image.jpeg" alt=""></p>
<p><strong>Happy Ending</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 내가 그 날 toRefs를 사용한 이유를 찾아서.]]></title>
            <link>https://velog.io/@gazero_/%ED%9A%8C%EA%B3%A0-%EB%82%B4%EA%B0%80-%EA%B7%B8-%EB%82%A0-toRefs%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9D%B4%EC%9C%A0%EB%A5%BC-%EC%B0%BE%EC%95%84%EC%84%9C</link>
            <guid>https://velog.io/@gazero_/%ED%9A%8C%EA%B3%A0-%EB%82%B4%EA%B0%80-%EA%B7%B8-%EB%82%A0-toRefs%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%9D%B4%EC%9C%A0%EB%A5%BC-%EC%B0%BE%EC%95%84%EC%84%9C</guid>
            <pubDate>Tue, 09 Apr 2024 13:22:58 GMT</pubDate>
            <description><![CDATA[<h3 id="🖥-문제의-코드">🖥 문제의 코드</h3>
<pre><code class="language-js">const { expiredAt } = toRefs(reactive({ expiredAt: ref(null) }));
const { clientId } = toRefs(reactive({ clientId: ref(null) }));
</code></pre>
<h3 id="📌-사건의-경위">📌 사건의 경위</h3>
<p><img src="https://velog.velcdn.com/images/gazero_/post/1465d9fd-d72b-47f0-a50f-b8010f614a46/image.png" alt=""></p>
<h4 id="💡-흔적을-발견했다">💡 흔적을 발견했다.</h4>
<p><img src="https://velog.velcdn.com/images/gazero_/post/93352b8e-c743-4ed4-b1c1-a584bfcceb32/image.png" alt=""></p>
<h4 id="과거의-나-무슨-생각을-했을까생각을-했을까라는-질문이-더-적절할지도">과거의 나. 무슨 생각을 했을까?(생각을 했을까?라는 질문이 더 적절할지도?)</h4>
<p><img src="https://velog.velcdn.com/images/gazero_/post/c246ca41-fdbc-44aa-a64a-435f2e3c9b8c/image.png" alt=""></p>
<ol>
<li>api로 받아온 값 계속 <code>바뀌는 값</code>이라고 생각했다.</li>
<li>받아온 데이터 중 &#39;만료일&#39; 값은 <code>객체로 이루어진 데이터 중 1개</code> 라고 생각했다.</li>
</ol>
<h1 id="일단-vue의-반응성에-대해서-논의하고자-함">일단. vue의 반응성에 대해서 논의하고자 함</h1>
<h2 id="⭐-반응성reactivity이란">⭐ 반응성(Reactivity)이란?</h2>
<ul>
<li>컴포넌트의 상태(state)가 바뀌면 <code>자동</code>으로 컴포넌트의 DOM을 변경하는 것</li>
</ul>
<p><strong>자동으로 DOM을 변경하는게 어떻게 가능할까?</strong>
<img src="https://velog.velcdn.com/images/gazero_/post/e320e1f8-0d30-4b50-a768-132da09caf32/image.jpeg" alt=""></p>
<p><strong>전제조건</strong>: 자바스크립트의 프록시와 getter, setter
<del>cf)** Proxy란**? 다른 객체에 대해 수행되는 기본 작업을 수정하고 사용자 정의할 수 있는 객체 생성을 허용하는 기능</del>
<strong>Proxy란?</strong> Proxy의 역할을 직역하자면 &#39;대리자&#39;라고 할 수 있는데, 이때 한정적인 무언가만을 대리하는 것이 아닌 그냥 무엇이든 대리할 수 있는 역할을 하는 말그대로 대리자임</p>
<ul>
<li>자바스크립트에서 속성 접근을 가로채는 방법 getter/setter 그리고 Proxy(그러니깐 다른 오브젝트를 감싸 연산을 가로챔)</li>
<li>get은 프로퍼티 접근 로그를 남김</li>
<li>set은 추가하여 해당 연산이 일어날 때마다 개발자가 입력한 추가 작업이 수행되도록 하는 방식</li>
<li>특히 vue3에서 프록시는 반응형 객체에 사용됨</li>
<li>Proxy를 통해서 Object의 get()/set()메소드를 재정의</li>
<li>원하는 동작으로!</li>
</ul>
<blockquote>
<p>반응형 상태의 객체를 Proxy 객체로 만들어서 값이 바뀌는 동작이 발생하는 경우에는 set()이 호출, set()은 DOM을 업데이트하게 함</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/gazero_/post/bdc4f1ec-f359-47f9-89f7-5e4531eabeeb/image.png" alt=""></p>
<ul>
<li>Vue의 인스턴스에 자바스크립트 객체를 data 옵션으로 전달</li>
<li>getter/setter로 변환되어 존재</li>
<li>getter/setter는 보이는 것은 아니지만 속성에 접근하거나 수정할 때 종속성 추적 및 변경 알림을 수행할 수 있음</li>
<li>모든 컴포넌트 인스턴스에는 watcher 인스턴스(컴포넌트가 종속적으로 렌더링 되는 동안, 수정된 모든 속성을 기록)가 존재</li>
<li>추후에 setter가 트리거되면 watcher에 알리고 컴포넌트가 리렌더 됨</li>
</ul>
<p>정.리.하.자.면</p>
<blockquote>
<p>Proxy를 통해 대상 객체의 set함수를 Vue에서 만든 반응형 handler로 교체해주는 작업</p>
</blockquote>
<ul>
<li>대상 객체를 직접 조작X -&gt; Proxy를 통해 상태를 관리하도록 함</li>
<li>Proxy 객체의 값을 변화 발생 -&gt; set handler에서 DOM을 업데이트</li>
<li>그러니깐 vue에서는 proxy를 통해 반응성을 확보하는 것! <strong>&#39;값이 바뀔 때마다 watcher에게 값이 바꼈다고 알려줘!&#39;</strong></li>
</ul>
<h2 id="대표적으로-ref와-reactive">대표적으로 ref와 reactive</h2>
<h4 id="1-ref">1) ref</h4>
<ul>
<li>ref를 호출하면? <code>.value</code>속성을 가지는 객체를 리턴</li>
<li>이 과정을 자동으로 동작하게 해주는 역할</li>
<li>해당 함수의 인자로는 모든 타입이 올 수 있음</li>
</ul>
<h4 id="2-reactive">2) reactive</h4>
<ul>
<li>역할은 ref와 동일</li>
<li>reactive 함수의 인자로는 객체와 배열 정도가 주로 쓰임</li>
</ul>
<p><strong>그러면 ref만 있으면 되지 않나😨?</strong> 어차피 ref를 쓰면 객체, 배열 다 되지 않냐는 말이지!</p>
<p>아차차! <strong>인자의 타입만 차이점이 아니었음</strong>
<img src="https://velog.velcdn.com/images/gazero_/post/013bf393-812b-4794-9297-ff73b5544ea5/image.jpeg" alt=""></p>
<ul>
<li>reactive를 사용하는 경우 객체의 접근 방법이 다름<pre><code class="language-js">&lt;script setup&gt;
import { ref, reactive } from &#39;vue&#39;
</code></pre>
</li>
</ul>
<p>const refState = ref({ count: 0 });
const reactiveState = reactive({ count: 0 });</p>
<p>function refPlus() {
  refState.value.count++;
}</p>
<p>function reactivePlus() {
  reactive.count++;
}</p>
<pre><code>- reactive일 때 객체에게 접근하는 경우 `.value`속성을 붙이지 않아도 됨! 바로 접근이 가능해짐
- 왜? ref는 단일 값을 파라미터로 갖는데 내부적으로는 value라는 키 값에 파라미터를 매핑하는 객체로, 따라서 실제 값에 접근하기 위해서는 .value를 통해 한 단계 더 들어가서 접근할 수 있음
- 주의할 점! .value의 접근은 자바스크립트 영역에서만 필수임(그러니깐 html / template에서는 사용하지 않음)


### 💡원점으로 toRefs 왜 쓴거지 도대체?
#### toRefs()에 관한 고찰
- toRefs()는 객체의 속성을 하나씩 분리 -&gt; ref로 만듦
- reactive의 모든 프로퍼티에 대해 toRef를 적용해 반환
cf) toRef? 단일 reactive객체 프로퍼티를 ref로!
&gt; 반응성을 가진 객체를 받아 해당 객체의 속성들을 반응성을 가진 개별적인 **ref로 변환**

- 구조분해할당에서의 toRefs
```js
// reactive로 구조분해 할당 하는 경우
import { reactive } from &#39;vue&#39;;

const { count } = reactive({
  count: 0
});

console.log(count);</code></pre><p>count는 더이상 리액티브 하지 않음!(구조분해가 반응성 시스템을 깨뜨림) 사실 이 문제를 해결하기 위해 toRefs가 두둥 등장!</p>
<h4 id="torefs는-구조분해할당-하는-경우를-대비해서-등장한-기능반응성-절대-지켜">toRefs는 구조분해할당 하는 경우를 대비해서 등장한 기능(반응성 절대 지켜!)</h4>
<p>cf)** 구조분해 할당이란**? 배열이나 객체에서 속성을 추출하여 보다 간결한 방식으로 변수에 할당할 수 있는 구분</p>
<pre><code class="language-js">// 1. 간결하고 가독성이 좋다 &amp; 반복적인 코드를 방지
const closet = { category: &#39;shirts&#39;, color: &#39;red&#39; };
// 구조분해 할당이 없는 경우 객체에 접근하는 방법
const category = closet.category;
const color = closet.color;
// 구조분해 할당을 하는 경우
const { category, color } = closet;

// 2. 유연성: 필요한 속성이나 요소만 선택하고 나머지는 버릴 수 있음
const closet = { category: &#39;shirts&#39;, color: &#39;red&#39;, price:20000 };
const { category, price } = closet;</code></pre>
<p>그러니깐 사실상 일반적으로는 이런식으로 쓰이면 쓰임이 맞다고 볼 여지가 어느 정도 있는 편(나 자신과 진짜 최대한 타협했을 경우임)</p>
<pre><code class="language-js">const toRefObject = reactive({
  expiredAt: null,
  clientId: null
});

const {expiredAt, clientId} = toRefs(toRefObject);</code></pre>
<ul>
<li>expiredAt은 toRefObject.expiredAt을 참조하는 반응형 ref가 됨</li>
<li>그렇다면 그냥 ref를 쓴다면?<pre><code class="language-js">const expiredAt = ref(&#39;null&#39;);
const clientId = ref(&#39;null&#39;);</code></pre>
</li>
<li>똑.같.다ㅎ</li>
</ul>
<p>이쯤에서 다시 한 번 나의 문제 코드는..... 그래도 해석을 해보자면</p>
<pre><code class="language-js">const { expiredAt } = toRefs(reactive({ expiredAt: ref(null) }));
const { clientId } = toRefs(reactive({ expiredAt: ref(null) }));</code></pre>
<ul>
<li>expiredAt은 expiredAt을 참조하는 반응형 ref가 됨</li>
<li>코드에서 reactive 함수는 객체를 반응형으로 만들고</li>
<li>ref함수는 null 값을 갖는 반응형 ref를 생성</li>
<li>그리고 toRefs 함수는 객체를 분해해서 반응형 ref로 만듦</li>
</ul>
<h4 id="결론">결론</h4>
<ul>
<li>toRefs 함수는 주어진 객체의 각 속성을 ref로 변환하는 데 사용 됨</li>
<li>하지만 내가 작성했던 코드에서는 각 변수에 대해 toRefs를 호출할 필요가 없었음</li>
<li>따라서 성능적인 부분에서도 문제가 발생할 수 있음(객체 생성하는 과정에서 불필요한 메모리 할당 및 객체 초기화가 있음, 또한 ref 객체로 변환하기 위해 toRefs에 두 개의 함수를 별도로 호출하고 있음)</li>
</ul>
<h4 id="나의-반성">나의 반성</h4>
<p><img src="https://velog.velcdn.com/images/gazero_/post/84e02862-2ab1-40b7-986c-37af28a31170/image.jpeg" alt=""></p>
<p><strong>첫 번째</strong>. &#39;일단 되니깐 끝&#39;이라는 생각으로 끝내면 안 된다.
<strong>두 번째</strong>. 성능에 관해서도 관심을 갖자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머의 뇌를 다 읽고, 난 후의 내 뇌를 돌아보는 시간]]></title>
            <link>https://velog.io/@gazero_/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EB%87%8C%EB%A5%BC-%EB%8B%A4-%EC%9D%BD%EA%B3%A0-%EB%82%9C-%ED%9B%84%EC%9D%98-%EB%82%B4-%EB%87%8C%EB%A5%BC-%EB%8F%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EC%8B%9C%EA%B0%84</link>
            <guid>https://velog.io/@gazero_/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EB%87%8C%EB%A5%BC-%EB%8B%A4-%EC%9D%BD%EA%B3%A0-%EB%82%9C-%ED%9B%84%EC%9D%98-%EB%82%B4-%EB%87%8C%EB%A5%BC-%EB%8F%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EC%8B%9C%EA%B0%84</guid>
            <pubDate>Fri, 19 Jan 2024 06:35:32 GMT</pubDate>
            <description><![CDATA[<p>입사 후 사내 개발자들과 책 1권을 정해서 일정 분량을 읽고, 그 생각을 교류하는 시간을 가져왔다(스터디라 쓰고, 친목이라 읽음).</p>
<p>첫번째 책은 <code>프로그래머의 뇌</code> 이걸 다 읽으면 내 뇌도 프로그래머의 뇌가 될 수 있겠지? 라는 기대로 읽었고, 마지막 챕터를 읽을 때 까지도 나는 그런 기대를 했다. </p>
<p>다 읽고 난 후, 내 뇌는?
그냥 <code>가영이의 뇌</code>다.</p>
<p>책이 별로였나? 아님. 내가 별로라고 생각함. </p>
<h3 id="스터디-방식">스터디 방식</h3>
<blockquote>
<p>일정분량을 각자 읽고 깃에 자유롭게 이슈를 남긴다. 그리고 일주일에 한 번 읽었던 내용에 대해 토론한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/gazero_/post/f6826310-7363-4971-a3ef-fa0845c3e405/image.png" alt=""></p>
<p>감상문의 형식은 자유로웠다. 나는 어렸을 때 방학숙제로 쓰던 감상문 형식에 독후감을 썼고, 다른 스터디원들은 짧게 내용을 요약하기도 했다.</p>
<p>지금 다시 내가 이 책을 읽으며 쓴 감상문을 순서대로 읽어보는데, 저 당시에 내가 느끼던 고민과 좌절 그리고 자기합리화(이게 제일 많음)가 다시금 떠올랐다. 그리고 챕터 마지막으로 갈수록 나는 반성문을 쓰고있었다. </p>
<p><img src="https://velog.velcdn.com/images/gazero_/post/90534a9d-bb93-435c-93d0-41e09f81e15d/image.jpg" alt=""></p>
<p><code>챕터 01</code>
첫번째 챕터에서 다른 사람의 코드를 보고 &#39;어? 잠깐.. 이 부분은 뭐지?&#39;라고 했던 내가 &#39;몰라서&#39;가 아닐 수도 있다는 생각을 했고, 약간의 마음의 위안이 됐다. 왜냐하면 개념을 몰라서(지식의 부족, 정보의 부족)가 아니라, 이해하려고 노력할 때(처리 능력의 부족) 발생할 수 있었던 혼란 이었을지도 모르기 때문이다. 처리 능력의 부족이 잘했다는 것은 아니지만 코드를 작성한 사람이 이해하기 어렵게 코드를 짰을 것이다....라는 그런 합리화를 했다. 그리고 이 책에서도 언급하고 있듯이 최근에는 라이브러리, 모듈, 패키지 등이 다양하기 때문에 모든걸 다 아는 개발자는 있을 수 없다고 생각한다. 아무튼 그렇기 때문에 LTM(장기기억장치)와 STM(단기기억장치)을 적절히 활용해서 코드를 보는 능력을 키우면 좋겠지만 그게 되려나 모르겠음</p>
<p><code>챕터 02</code>
두번째 챕터에서 앞서 느꼈던 내용이 등장했다. &#39;개발 경험이 많더라도 코드를 빠르게 이해하기 어렵다.&#39; 첫번째 챕터에서 느꼈던대로 원인을 미리 찾아보자면 첫번째, 라이브러리, 모듈 등이 다양해졌기 때문에, 두번째, 코드를 작성한 사람이 자기만 알아보게 코드를 작성했기 때문에 정도로 생각했고, 정확히 일치했다. &quot;프로그램은 사람이 읽을 수 있도록 작성해야 한다.&quot;, 특히 협업의 경우 필수라고 생각한다. 그래서 보통 협업에서는 본격적으로 개발에 앞서 변수명을 정할때 규칙부터 정해놓는 것이 일반적이다. 그러면 코드를 읽는 속도가 빨라질 수 있다. 예를 들어 규칙대로 만든 변수명만 보더라고 &#39;아 이거는 어떤 목적을 위해 만들어진 변수구나&#39;라고 넘어갈 수 있기 때문이다(이것이 바로 LTM을 활용하는 방법). 이밖에도 이 챕터에서는 주석을 다는 방법, 디자인패턴을 만드는 방법, 표식을 하는 방법 등을 소개하고 있다.</p>
<p><code>챕터 03</code>
지난 챕터 1, 2에서는 코드 이해가 어려운 이유에 대한 합리화 시간이었다면, 이번 챕터3에서는 반성의 시간이었다. 마치 나를 CCTV로 지켜보고 있다가 문제점을 책으로 써낸 기분이었다. 예를 들어, 새로운 코드를 처음봤을때는 당연히 모를 수 있다. 하지만 다음에 또 봤을 때는 개선된 모습이 보여야 한다. 그 개선을 위한 노력이 필요하단 의미다. 이 챕터에서도 지적하듯이 &#39;구글에 검색&#39;이 너무나 생활화 되어있다는 생각이 들었다. 어쩌다 한 번 쓰거나 앞으로도 쓸 일이 없지만 현재 업무나 과제를 위해 필요한 경우를 제외하고는 늘 사용하는 기능은 검색을 하지 않고도 잘 알고 있어야 한다. 그냥 언젠가 익숙해 지겠지~라고 생각한게 벌써 오늘이다..</p>
<p><code>챕터 04</code>
이번 챕터는 소제목부터 혹하게 만들었다. 뭔가 &#39;이 챕터를 읽으면 복잡한 코드를 읽는 노하우를 배울 수 있겠지?&#39;라고 생각했다. 제시된 방법 중 하나, 하나를 나라면 할 수 있을까? 라고 대입해 보았다. 아니, 안되겠다. 특히 의존 그래프 생성에서는 깜짝 놀랐다. 마치 영어 독해를 하는 느낌이었다. 또 처음 자바로 알고리즘을 공부할 때 알고리즘의 예상 결과를 하나씩 그리면서 다음 코드를 한 줄, 한 줄 적어본 적이 있고, sql을 공부하면서도 데이터의 결과를 하나 하나 적어가며 작성해 본 기억이 있다. 이런 것도 하나의 방법이 될 수 있을까?라는 생각을 잠시 하긴했다.</p>
<p><code>챕터 05</code>
이번 챕터 5는 쉽지 않은 여정이었다. 와 닿는 문장은 &#39;기억은 따로 저장되는 것이 아니라 다른 기억들과 연결되어 있다.&#39; 이 부분이었다. 뇌가 그렇게 구조화 된 것 같다. 그래서 외우려고만 하지말고 연상기억법을 이용하라고 했던 말도 생각난다. 아 그리고 사야니에미가 정의한 11개의 역할을 보고 개발자는 코드를 짤때 그냥 로직만 생각해야 하는게 아닌게 몸소 와닿았다. 왜 코드를 짜다가 갑자기 화면을 뚫어지게 쳐다보는지에 대한 이유가 이러한 과정때문이 아닐까 조심스럽게 생각해보았다.</p>
<p><code>챕터 06</code>
역시 호락호락 하지 않았다. 코드를 작성할 때 고려해야 할 요인이 너무 많다는 대목에서는 공감했다. 그렇지만 코드는 완벽한 완성이 없기 때문에 지속적인 유지보수가 늘 필요하다. 얼마전에 나도 진짜 완벽하게 모듈을 짰다고 생각했다. 하지만 갑자기 여러가지 변수가 나타나면서 내가 생각했던게 엉망이 되었다. 이 책에서는 사용의 용이함, 성능, 변경 가능성 중 어느것에 비중을 더 둬야 하는지를 고민하지만, 아마 대부분의 개발자는 코드를 짜면서 이 세가지 요소를 한꺼번에 고민하 것이다. 재사용을 하는 것도 &#39;미래의 나&#39;, 성능 최적화도 &#39;미래의 나&#39;, 유지보수 역시 &#39;미래의 나&#39;의 몫이기 때문이다. 그리고 희망적인 것은 이 책이 전반적으로 뭘 중요시 하는지 눈치챘다는 것이다. 이 책은 &#39;관계&#39;에 중점을 두고 있다. 그러니까 코드를 빠르고 쉽게 이해하려면? 코드안 요소간의 관계를 중점적으로 보라는 얘기를 하는 것 같다. 그냥 나만의 생각일 수 있지만 그런 느낌을 강하게 받다가 갑자기 챕터 6이 끝나버렸다.</p>
<p><code>챕터 07</code>
이번에는 뭔가 와닿는 부분이 조금 있었다. 특히 두번째로 배우는 언어가 더 쉽게 느껴진다는 부분, 나는 지금까지 만나는 사람들에게 &#39;자바가 세상에서 제일 어렵다.&#39;라고 했다. 자바는 내가 개발을 함에 있어서 처음으로 배웠던 프로그래밍 언어다. 이후 다뤘던 자바스크립트도 어려웠지만 자바에 비하면 별거 아니라는 생각을 했고, 파이썬은 지금까지 다뤘던 언어중에 제일 쉽게 느껴졌다. 이게 바로....전이?아닐까? 여기 까지가 전이의 순기능 이라면, 역기능은 아마도 &#39;편견&#39;이 아닐까 생각해 보았다. 프로그래밍 언어는 유사하지만 자세히 보면 다른 부분이 어마무시하게 많다. 예를 들어 R에서는 배열의 인덱스가 1부터 시작하는데 반해, 자바스크립트 등등의 배열 인덱스 시작은 0부터 시작하는 것 처럼 ! 애초에 자바스크립트를 배우고 R을 배우는 사람은 별거 아닌 인덱스 시작 순서에 생각보다 혼란스러워 할 수 있기 때문이다. 그렇지만 이 챕터에서 &#39;이미 알고 있다는 것은 저주인가, 축복인가?&#39;라는 질문에 나는 그래도 모르는 것 보다 하나라도 아는게 더 낫다고 생각해버림 하하</p>
<p><code>챕터 08</code>
변수명을 짓는 것이 최대 난제이기도 하고, 최고의 고민거리다. 진짜 변수명을 지어주는 기계가 있었으면 좋겠다고 생각할 때가 한 두번이 아니다. 이번 챕터에서는 정말 당연하고 옳은 말 뿐이었다. 이 책에서 만큼은 아니더라도 비스무리하게 변수명은 이렇게 지어야해 ! 라는 사실은 언뜻 알고 있었다. 하지만 막상 닥치면 그게 생각대로 와 미쳤는데? 나 이거 변수명 엄청 잘 지은듯? 이런건 없었다. 언제나 변수명을 짓고 다음 코드를 써내려 가면서 찝찝함이 함께 였다. 이론적으로 알더라도 그 이론이 내제화되어 나 스스로가 변수명을 짓는 기계가 되기까지 그동안 수 많은 찝찝함을 거쳐야 할 것 같다.</p>
<p><code>챕터 09</code>
9번째 챕터를 읽으면서 처음부터 끝까지 &#39;오...아...오ㅏ..&#39;를 멈출 수 없었다. 내가 항상 추구하는건 나도 쉽고, 다른 사람이 봤을때도 그냥 읽히는 코드 였으면 좋겠다는게 나의 바람이다. 그런데 나의 코드는 이 챕터에서 설명하는 코드 스멜이 넘쳐난다. &quot;작동은 하지만 개선의 여지가 있는 코드&quot; 앞서 다른 챕터를 읽을 때에도 &#39;세상에 완벽한 코드가 어디 있겠어.&#39; 라는 생각을 했지만 내가 그동안 작성했던 코드는 합리화하면서 흐린눈 하기엔 너무 코드 스멜이 굉장히 많다. 물론 머리로는 &#39;지금 이게 그때 그 로직이나 코드랑 비슷하니까 이거를 이렇게 해서 이렇게 하고 저렇게 리팩토링 해야지!&#39;라고 생각한다. 하지만 코드를 계속 작성하다보면 그런 상황의 연속이다. 아무튼 이거는 나의 문제점이고 다시 본론으로 넘어가자면 파울러가 정한 22가지 코드 스멜에 하나도 빠짐 없이 내가 포함되는 기분이라 책을 빠르게 읽을 수 없었다. 나아가 현재 이 책에서 설명하는 언어적 안티패턴 역시 이름을 잘 지어야 한다는 또 다시 죄책감이 들게 했다.</p>
<p><code>챕터 10</code>
10번째 챕터 제목은 &#39;복잡한 문제 해결을 더 잘 하려면&#39;이다. 나는 제목을 읽고 &#39;또.....제목으로 플러팅하는구나&#39; 생각했다. 그러다가 LTM 학습법 부분에서 왜 이 책은 계속 맞는 말만 하는데, 왜 거부감이 잔뜩 들었는지 이유를 찾았다. 첫번째, 나는 정보가 들어왔을때, 혹은 활용할 때 뇌가 하는 일이 궁금하지 않았다. 두번째, 나는 노력없이 문제를 쉽게 해결하거나 쉽게 코드를 작성하거나 쉽게 이름을 짓고 싶었다. 하지만 여기에서는 &#39;노력해야 한다. 꾸준히 학습해야 한다.&#39;라고 하기 때문에 나는 스스로 거리를 둔 것이다. 그러던 와중에 &#39;자...동...화??&#39;이게 가능할까? 라는 생각을하며 이 부분을 읽었고, 엄청 큰 공감을 했다. 일전에 이 책에서도 소개되었던 에빙하우스의 망각곡선에서도 처음에는 30분만에 다음에는 4시간만에 뭐 이런식으로 간격을 두고 같은 것을 반복하면 무슨일이 있어도 잊지 않는다는 것이 이 원리이다. 나도 혼자 공부할 때 다들 리덕스가 필수라고 하니, 해보긴 해봐야겠고, 과거 리덕스를 써봤다고 면접에서 얘기했더니 리덕스에 대한 프로세스를 설명해달라는 질문을 받은적 있었기 때문에 어쩔 수 없이 혼자서 작은 프로젝트에(리덕스 상태관리가 전혀 필요없는)리덕스로 상태관리를 했었다. 내재화 될 정도로 여러번 했었다. 근데 막상 리덕스를 쓸 기회가 없었고 지금은 &#39;그치 이런게 있었어. 맞아맞아 거기에 넣고 뭐 보관하고 그랬었지&#39; 이 정도의 스치는 기억이 되었다. 이 외에도 api 통신을 해봐야 하는데, 하면서 온갖 공공데이터 포털에서 공공api로 혼자 통신해보고 그랬던 기억이 있다(어찌보면 암시적 기억 개선 행위이기도). 하지만 이 또한 늘 반복되는 일이 아니라 자동화시키지 못했다. 아무튼 두가지 경험 다 자동화 실패사례닼ㅋㅋㅋ하핳 하지만 인수분해 예시를 보고 기억나는거 보면 우리나라 교육 좀 하네?라고 생각했다.</p>
<p><code>챕터 11</code>
이 챕터에서는 뇌가 프로그래밍을 &#39;더&#39; 잘하게, 그러니깐 더 잘 기억해내고, 더 잘 인지해 낼 수 있도록 여러 단계의 방법을 설명하고 있었다. 예를 들어 노트를 하면서 하던지, 혹은 주석을 적는 등의 방법을 설명한다. 며칠간 이 방법처럼 노트를 하기도했고, 주석을 달아 보기도 했다. 확실히 노트를 하면 마치 todo 리스트를 작성한 것인가? 라는 생각을 하기도 했지만 어쨌든 안하는 것보다 낫다는 생각을 했다. 물론 이번 챕터와 논외의 얘기지만 중간에 &#39;리팩터링&#39;이라는 키워드가 나왔고, 갑자기 리팩터링의 객관적인 기준이 있을까? 라는 생각도 스치듯이 해봤다. 아무튼 본론으로 돌아와서 며칠간 이 책에서 문제를 더 깊이 생각할 수 있는 마음의 여유를 확보할 수 있다는 얘기에 설계의 방향과 결정 사항을 노트에 적어내려 갔었고,�그래도 굉장히 만족스러웠다고 스스로 평가할 수 있었다. 하지만 마음의 여유는 아쉽게도 확보하지 못했다.. 더 인상 깊었던 내용은 &#39;업무 중단&#39;이다. 뭔가 사장님이 보면 좋아할 것 같은 주제였다고 생각한다. 그 이상 그 이하도 아니었고, 일단 공감하지 못해서 아쉽다. 공감할 때가 된다면 내가 사장이 되었을 때가 아닐까...? 크흠</p>
<p><code>챕터 12</code>
역시나 &#39;일관성&#39; 함수를 표시할 때, &#39;함수명()&#39;이런 형태면 함수다! 라는 우리 끼리의 암묵적인 합의 같은 것들이라고 할 수 있는 부분이다. 이건 모든 개발자들(아마도..?)과의 약속이지만 협업을 할 때는 우리끼리만의 이러한 일관성 있는 코드 형식이 중요하다는 사실을 마지막까지 강조하는 부분이 아닐까 싶었다. 또 숨겨진 의존성......이 부분에서 의존성을 채택하고 &#39;문서화&#39;얘기가 나와서 조금 소름이 돋았다. 또 공감할만한 이야기는 IDE의 색상 기능이 아닐까 싶다. 그냥 메모장에 코드를 작성하거나 cmd창에 코드를 작성했다면 아마도 나는 코딩을 시작도 하지 않았을 것이라고 단언한다. 익숙해서 지금은 느끼지 못하지만 진짜 코드를 작성하기에 엄청 편리한 기능이 아닐 수 없다. 이 챕터도 마냥 읽어 내려가다가 공감하지 못할 소제목이 있었다. &#39;힘든 정신 활동&#39;...내가 이해한 바로는 시스템을 이해하기 위해 많은 생각할 해야 한다는 표현을 힘든 정신 활동이라고 할 것일까? 그렇다면 공감해보겠다. &#39;아무튼 많은 시간과 노력없이도 코드를 단번에 쉽게 알아 볼 수 있는 방법(그것에 IDE 익스텐션도 포함)으로 코드를 짜자! 는 것이 이 챕터의 핵심으로 생각된다.</p>
<p><code>챕터 13</code>
마지막 챕터. 새로운 개발자 팀원의 적응 지원. 이 부분에 대해서는 아직 먼 미래라 생각되어 그냥 마음속에 저장만 해둘 생각이다. 제일 처음 개발자로 취업을 했을때, 전체 서비스 코드를 받았고, 이 서비스가 어떤 구조로 되어있는지, 어떤 언어를 사용하는지 모든걸 스스로 혼자 파악하고 파일 구조도 혼자 다 확인했어야 했다. 만약에 처음 회사에서 선임 개발자가 이 책에서 말하는 정도의 무슨 라이브러리 인지 어떤 프레임워크고 데이터 구조는 어떠했는지를 좀 더 친절하게 지원해줬다면 현재도 그 회사에 다니고 있지 않았을까? 뭐 그런 생각을 하게 하는 마지막 챕터였다.</p>
<h4 id="그리고-나의-결말">그리고 나의 결말</h4>
<p><img src="https://velog.velcdn.com/images/gazero_/post/666272b9-3735-4af1-a292-731f8adf9258/image.png" alt="">
책의 내용은 아주 어려웠다. 그리고 현재 우리는 여전히 아주 완벽한 변수명을 지으려고 노력중이고, 서로 변수명이 그게 뭐냐며 놀리는 중이다. 이 책을 다 읽고 난 후 탈출한 기분이어서 너무나 행복했다. </p>
<p><img src="https://velog.velcdn.com/images/gazero_/post/a65174a8-14ec-4d54-a457-62e357370bb7/image.png" alt=""></p>
<p><strong>아 이게 아니라..</strong>
<img src="https://velog.velcdn.com/images/gazero_/post/3694b332-18b5-43ab-ab92-e8a7b368d2fd/image.jpg" alt="">
<strong>이거임:)</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 도둑을 잡았다.]]></title>
            <link>https://velog.io/@gazero_/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%8F%84%EB%91%91%EC%9D%84-%EC%9E%A1%EC%95%98%EB%8B%A4</link>
            <guid>https://velog.io/@gazero_/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%8F%84%EB%91%91%EC%9D%84-%EC%9E%A1%EC%95%98%EB%8B%A4</guid>
            <pubDate>Tue, 09 Jan 2024 05:16:11 GMT</pubDate>
            <description><![CDATA[<p>일단 나: <code>사람을 잘 믿는 편임</code>, <code>정이 많음</code> (좋은 사람이라는 뜻..), 하지만 내 기준에** &#39;아니다&#39; 싶을 땐 확실히 아니라고 말하는 편**이다.  </p>
<p>그래서 개인적으로 연락을해서 얘기할까? 한참을 고민했다.
얼마전에 내 생일이었고 그걸 계기로 같이 교육받던 사람에게 안부인사 겸 연락이 왔다. 우선 고마웠고, 반가웠다. 그렇게 안부를 묻고 갑자기 어떻게 살고 있는지 궁금했다. 그래서 그 사람 Git Repo에 들어가봤다.</p>
<p>별게 없었다. 그냥 프로젝트도 별로 없었다. 프라이빗일 수도 있지만 내가 직접 확인할 수 있는건 없었다.
그 중 눈에 띈건 내가 지금으로 부터 5달 전에 완성했다는 <code>mbti 프로젝트</code>와 같은 프로젝트 레포</p>
<p><img src="https://velog.velcdn.com/images/gazero_/post/16d4b933-9ef4-4a67-a737-d0cc56e934da/image.png" alt="">
<strong>(이미지 자료는 본인 프로젝트 repository임, 손민수꺼는 배려차원에서 여기에 넣지 않음)</strong></p>
<p>나랑 같은 프로젝트를 포폴에 넣으려고 했구나? 라는 생각이 들었고, 레포를 봤는데 main 브랜치에 아무것도 없었다. 그리고 다른 브랜치에서 진행중인가?라는 합리적 의심으로 눌러봤더니 다른 브랜치가 있었고 그 브랜치에서 살짝 소름이 돋았고, 충격을 받았다.</p>
<p><code>같은 프로젝트</code>를 만들 수 있고, <code>같은 라이브러리</code>를 사용할 수 있다고 생각한다. 충분히 가능성있다.
<code>파일 구조가 같은</code> 것도 그럴 수 있다고 생각했다.</p>
<p>그런데, 내가 만든 파일에서 <code>CamelCase가 지켜지지 않은 오타</code> 부분, 그리고 <code>변수명</code>이 같은 부분, 더미데이터 안에 <code>key값</code> 마저 같은 부분에서 이상하다는 생각이 들었다.</p>
<p>그치만 <code>주석</code>마저 같은건....그리고 커밋 흔적도 봤다. 끝까지 아닐 수 있잖아? 커밋 흔적이 없다(커밋1: 초기 CRA 생성, 커밋2: 플젝 완성).</p>
<p>물론 그 안에는 일부 그 사람만의 기능도 있었다. </p>
<p>내가 마지막으로 하고 싶은 말은 포트폴리오에 이 프로젝트 하나 더 있다고 서류에 합격할지도 의문이고, 면접에서 대답을 잘 할지도 의문이지만 어찌됐든 취업을 했다니 축하합니다. 회사분들이 의문의 1패를 하신듯 :) 혹시 모르니 제가 저 프로젝트를 하면서 겪었던 시행착오를 적었던 과정이 이 블로그에 있으니 살면서 참고하세요!</p>
<p><strong>앗차차! 이거 챙겨가야지</strong>
<img src="https://velog.velcdn.com/images/gazero_/post/41f6a0f2-c403-4702-9715-fe03ac26d5a5/image.jpg" alt=""></p>
<p><a href="https://velog.io/@gazero_/%EC%98%AC%EA%B2%8C-%EC%99%94%EB%8B%A4.-Redux-%EB%8B%A4.-%EC%A7%88%EB%AC%B8%EC%9D%80-%EA%B1%B0%EC%A0%88%ED%95%9C%EB%8B%A4">https://velog.io/@gazero_/올게-왔다.-Redux-다.-질문은-거절한다</a>
<a href="https://velog.io/@gazero_/Redux-%EA%B3%B5%EB%B6%80%EB%8A%94-%EB%82%B4%EA%B0%80-%ED%96%88%EB%8A%94%EB%8D%B0-%EB%AF%B8%EB%8B%88-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%8A%94-%EB%88%84%EA%B0%80%ED%95%A0%EB%9E%98">https://velog.io/@gazero_/Redux-공부는-내가-했는데-미니-프로젝트는-누가할래</a>
<a href="https://velog.io/@gazero_/Redux%EB%A1%9C-MBTI%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0">https://velog.io/@gazero_/Redux로-MBTI테스트-만들기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[서버 사이드 렌더링(SSR: Server Side Rendering)은 뭐죠!]]></title>
            <link>https://velog.io/@gazero_/%EC%84%9C%EB%B2%84-%EC%82%AC%EC%9D%B4%EB%93%9C-%EB%A0%8C%EB%8D%94%EB%A7%81SSR-Server-Side-Rendering%EC%9D%80-%EB%AD%90%EC%A3%A0</link>
            <guid>https://velog.io/@gazero_/%EC%84%9C%EB%B2%84-%EC%82%AC%EC%9D%B4%EB%93%9C-%EB%A0%8C%EB%8D%94%EB%A7%81SSR-Server-Side-Rendering%EC%9D%80-%EB%AD%90%EC%A3%A0</guid>
            <pubDate>Wed, 11 Oct 2023 07:53:03 GMT</pubDate>
            <description><![CDATA[<h1 id="서버-사이드-렌더링ssr-server-side-rendering">서버 사이드 렌더링(SSR: Server Side Rendering)</h1>
<blockquote>
<h3 id="서버-측에서-페이지를-렌더링-하는-것">서버 측에서 페이지를 렌더링 하는 것</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/gazero_/post/9fd59cbb-4804-40f7-9b5a-a915631f5987/image.png" alt=""></p>
<ol>
<li><p>Client는 원하는 페이지를 Server에 요청</p>
</li>
<li><p>Server는 요청받은 페이지의 데이터를 불러옴 </p>
</li>
<li><p>그리고 요청받은 페이지의 완성된 .html을 생성</p>
</li>
<li><p>반환</p>
</li>
<li><p>브라우저는 _<code>렌더링</code>이 완료된 페이지_를 화면에 <code>렌더링</code></p>
<h4 id="렌더링이-완료된-페이지">렌더링이 완료된 페이지?</h4>
<p><img src="https://velog.velcdn.com/images/gazero_/post/d1e4b4c8-4ad2-4a15-b555-71823029d9c3/image.png" alt=""></p>
</li>
</ol>
<ul>
<li><p>리액트 컴포넌트와 같이 자바스크립트로 작성되어 있는 페이지를 실제 HTML 코드로 변환하는 과정</p>
<h4 id="화면에-렌더링">화면에 렌더링?</h4>
<p><img src="https://velog.velcdn.com/images/gazero_/post/b6a106a6-4335-4c71-9807-507956866dde/image.png" alt=""></p>
</li>
<li><p>HTML을 화면에 실제로 그려내는 것</p>
</li>
</ul>
<ol start="6">
<li>서버는 JavaScript Bundle 파일을 브라우저에 전달</li>
<li>클라이언트는 자바스크립트 코드와 HTML 요소들을 결합</li>
</ol>
<h2 id="next-js의-렌더링-특징">NEXT JS의 렌더링 특징</h2>
<blockquote>
<p>페이지별로 다른 렌더링 전략 적용 가능</p>
</blockquote>
<ol>
<li>SSR(Server Side 렌더링)</li>
<li>CSR(Client Side 렌더링)</li>
<li>SSG(정적 사이트 생성)</li>
</ol>
<h3 id="ssr-방식-적용해보기">SSR 방식 적용해보기</h3>
<pre><code class="language-js">export default function Home({ name }) {
  return &lt;div&gt;{name}&lt;/div&gt;;
}

export const getServerSideProps = async () =&gt; {
  // ssr을 위해 서버측에서 페이지 컴포넌트에게 전달할 데이터를 설정하는 함수

  return {
    props: {
      name: &quot;KOREA&quot;,
    },
  };
};
</code></pre>
<ul>
<li><code>getServerSideProps</code>함수를 비동기(async)로 만들어줌</li>
<li>반드시 <code>객체</code>를 반환</li>
<li>객체 안에는 <code>props</code>라는 프로퍼티가 반드시 있어야 함</li>
<li>프로퍼티의 값은 무조건 <code>객체</code>로 생성되어야 함</li>
<li>객체 안의 값은 모두 Home 컴포넌트에 전달됨</li>
<li>따라서 구조분해 할당 방식으로 받아옴</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gazero_/post/d7950ba3-2250-4bad-b9fa-3e9cb41c9a0c/image.png" alt=""></p>
<ul>
<li>name을 잘 받아온 것을 확인할 수 있음</li>
</ul>
<ol>
<li><code>&quot;/&quot;</code>이 경로에 접속하기 위해 요청을 보내면</li>
<li>서버는 <code>getServerSideProps</code>함수를 실행</li>
<li>그러면 index 페이지에서 렌더링 하기 위한 데이터가 뭔지 함수안에서 계산을 하고, props를 포함하는 객체로 반환</li>
<li>서버는 페이지를 렌더링 하기 위해 <code>Home</code> 컴포넌트를 호출</li>
<li>이때 props로 전달되는 값(name)이 getServerSideProps함수의 반환값에 있는 객체가 전달됨</li>
<li>props로 전달된 값을 포함한 Home 컴포넌트가 html로 렌더링이 됨</li>
<li>브라우저(클라이언트)로 전달</li>
</ol>
<h4 id="getserversideprops-함수는-서버에서만-실행됨">getServerSideProps 함수는 서버에서만 실행됨</h4>
<p>따라서 함수 내에 콘솔을 확인하면
<img src="https://velog.velcdn.com/images/gazero_/post/8e1d1027-5fb2-472b-acc9-5eeb030d9605/image.gif" alt=""></p>
<ul>
<li>브라우저 콘솔에는 아무리 새로고침해도 콘솔 내용이 나타나지 않음</li>
<li>서버 터미널에서 보임</li>
</ul>
<h4 id="그럼-home-컴포넌트-내부는">그럼 Home 컴포넌트 내부는?</h4>
<ul>
<li>Home 컴포넌트는 1차 서버에서 렌더링 되어 실행, 2차 브라우저 하이드레이션 이후에도 실행
<img src="https://velog.velcdn.com/images/gazero_/post/a1dc2f19-905c-461d-96bf-76ea2d12e524/image.gif" alt=""></li>
<li>그래서 서버에서 실행되고, 브라우저에서도 실행됨</li>
</ul>
<h4 id="싫어-나는-브라우저에서만-실행되게-하고싶어">싫어. 나는 브라우저에서만 실행되게 하고싶어</h4>
<p><img src="https://velog.velcdn.com/images/gazero_/post/6a5099ed-0239-422a-be9d-ec943e78f6c2/image.gif" alt=""></p>
<ul>
<li>useEffect로 조절가능</li>
<li>useEffect의 컴포넌트 마운트는 서버에서 실행되지 않는 이벤트이기 때문</li>
</ul>
<h2 id="api-적용">API 적용</h2>
<ul>
<li>getServerSideProps 함수 내에 api 실행 코드를 넣었음</li>
<li>즉 브라우저에서는 실행되지 않음</li>
<li>서버에서 다 실행시켜놓고 만들어진 것을 보내주기 때문임</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gazero_/post/ef2a00b3-2d60-41da-9444-598c380fc2df/image.png" alt=""></p>
<ul>
<li>api 호출을 통해 불러온 데이터가 서버측에서 HTML로 잘 렌더링 되어서 전달</li>
<li>즉 서버에서 만들어서 한 번에 보내줌</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gazero_/post/44dd82b5-a7f3-4866-bb50-22812b7ede41/image.png" alt=""></p>
<ul>
<li>브라우저에서는 api를 호출하지 않음</li>
</ul>
<p><strong>(비교)</strong> 리액트에서 api를 호출했을 때(CSR 방식)
<img src="https://velog.velcdn.com/images/gazero_/post/3ae308d6-7c41-4a98-be84-f12656c42756/image.png" alt=""></p>
<ul>
<li>브라우저에서 api를 호출하고 있음
<img src="https://velog.velcdn.com/images/gazero_/post/87a59617-49ca-4051-bbee-b1b7b7902367/image.png" alt=""></li>
<li>그리고 초기 요청시 빈화면만 보여짐(초기 렌더링 되지 않음)</li>
</ul>
<h4 id="query-string이-있는-경우">query string이 있는 경우</h4>
<pre><code class="language-js">import SubLayout from &quot;@/components/SubLayout&quot;;

export default function Search() {
  return &lt;div&gt;Search Page&lt;/div&gt;;
}

Search.Layout = SubLayout;

export const getServerSideProps = async (context) =&gt; {
  console.log(context);
  return {
    props: {},
  };
};
</code></pre>
<ul>
<li>context를 매개변수로 요청</li>
<li>콘솔로 실행해 보면
<img src="https://velog.velcdn.com/images/gazero_/post/59883a59-f226-4965-baab-b925a9bbd645/image.png" alt=""></li>
<li><code>query</code> 객체가 존재<pre><code class="language-js">  const { q } = context.query;</code></pre>
</li>
<li>쿼리 스트링을 꺼내옴
<img src="https://velog.velcdn.com/images/gazero_/post/c5bc0146-db50-431f-9e74-846c9f14b123/image.png" alt="">
<img src="https://velog.velcdn.com/images/gazero_/post/ff2c001d-e9df-420f-852c-d6792d2330e5/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NEXT JS] 레이아웃 설정하는 방법]]></title>
            <link>https://velog.io/@gazero_/NEXT-JS-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%84%A4%EC%A0%95%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@gazero_/NEXT-JS-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%84%A4%EC%A0%95%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Wed, 11 Oct 2023 02:54:58 GMT</pubDate>
            <description><![CDATA[<h1 id="전체-레이아웃-설정">전체 레이아웃 설정</h1>
<h3 id="_appjs-파일"><code>_app.js</code> 파일</h3>
<ul>
<li>이 파일에서 전체적으로 반복되는 화면 구성을 설정</li>
<li>예를 들면, 네비바 혹은 푸터 같은 것들 <pre><code class="language-js">export default function App({ Component, pageProps }) {
return (
  &lt;div&gt;
    &lt;header&gt;NATIONS&lt;/header&gt;
    &lt;Component {...pageProps} /&gt;
  &lt;/div&gt;
);
}</code></pre>
<img src="https://velog.velcdn.com/images/gazero_/post/3b874346-dcfc-4ebf-96da-aff41f3fa4d2/image.gif" alt=""></li>
</ul>
<h4 id="모듈화하는-방법">모듈화하는 방법</h4>
<p><code>_app.js</code></p>
<pre><code class="language-js">import &quot;@/styles/globals.css&quot;;
import Layout from &quot;@/components/Layout&quot;;

export default function App({ Component, pageProps }) {
  return (
    &lt;Layout&gt;
      &lt;Component {...pageProps} /&gt;
    &lt;/Layout&gt;
  );
}
</code></pre>
<p><code>Layout.js</code></p>
<pre><code class="language-js">import React from &quot;react&quot;;

const Layout = ({ children }) =&gt; {
  return &lt;div className=&quot;Layout&quot;&gt;{children}&lt;/div&gt;;
};

export default Layout;
</code></pre>
<p><img src="https://velog.velcdn.com/images/gazero_/post/a135d58b-2c89-43f7-aec1-6af1a8187b41/image.png" alt=""></p>
<ul>
<li><code>{children}</code> 으로 페이지 역할을 하는 컴포넌트들을 <code>main 태그</code>에 렌더링 시켜줌</li>
<li>Layout 아래에 페이지들이 잘 렌더링 되는 것을 확인 가능</li>
</ul>
<h1 id="개별-페이지-레이아웃-설정">개별 페이지 레이아웃 설정</h1>
<h4 id="search--country-페이지에만-특정-레이아웃을-설정하고-싶다면">search / country 페이지에만 특정 레이아웃을 설정하고 싶다면</h4>
<ol>
<li>레이아웃 컴포넌트 생성<pre><code class="language-js">const SubLayout = ({ children }) =&gt; {
return &lt;div className=&quot;SubLayout&quot;&gt;{children}&lt;/div&gt;;
};
</code></pre>
</li>
</ol>
<p>export default SubLayout;</p>
<pre><code>2. 적용하고자 하는 페이지에 설정
```js
import SubLayout from &quot;@/components/SubLayout&quot;;

export default function Search() {
  return &lt;div&gt;Search Page&lt;/div&gt;;
}

Search.Layout = SubLayout;
</code></pre><ul>
<li><code>Search</code>는 함수이지만 객체로도 활용이 가능</li>
<li>그래서 <code>Layout</code>이라는 프로퍼티를 추가</li>
<li>그 값을 <code>SubLayout</code>에 담아 둠</li>
</ul>
<ol start="3">
<li><code>_app.js</code>에 해당 Layout을 감싸줌<pre><code class="language-js">import &quot;@/styles/globals.css&quot;;
import Layout from &quot;@/components/Layout&quot;;
</code></pre>
</li>
</ol>
<p>export default function App({ Component, pageProps }) {
  return (
    <Layout>
      &lt;Component.Layout&gt;
        &lt;Component {...pageProps} /&gt;
      &lt;/Component.Layout&gt;
    </Layout>
  );
}</p>
<pre><code>![](https://velog.velcdn.com/images/gazero_/post/1f57ed03-1600-4148-83b6-31d5a6844f61/image.gif)

- SubLayout이 잘 담겨 오는 것을 확인
- 즉 메인페이지에서는 개별 적용을 하지 않았기 때문에 콘솔이 찍히지 않음

#### 적용된 화면
- main화면에는 푸터가 없음
- search/country에만 푸터를 적용

![](https://velog.velcdn.com/images/gazero_/post/fadc2823-593e-4fac-9bd7-ff3961f91084/image.gif)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[NEXT JS] 라우팅 기능에 사용되는 내장 훅]]></title>
            <link>https://velog.io/@gazero_/NEXT-JS-%EB%9D%BC%EC%9A%B0%ED%8C%85-%EA%B8%B0%EB%8A%A5%EC%97%90-%EC%82%AC%EC%9A%A9%EB%90%98%EB%8A%94-%EB%82%B4%EC%9E%A5-%ED%9B%85</link>
            <guid>https://velog.io/@gazero_/NEXT-JS-%EB%9D%BC%EC%9A%B0%ED%8C%85-%EA%B8%B0%EB%8A%A5%EC%97%90-%EC%82%AC%EC%9A%A9%EB%90%98%EB%8A%94-%EB%82%B4%EC%9E%A5-%ED%9B%85</guid>
            <pubDate>Wed, 11 Oct 2023 02:17:30 GMT</pubDate>
            <description><![CDATA[<h1 id="userouter">useRouter?</h1>
<blockquote>
<h3 id="next-js에서-제공하는-내장-훅-주로-라우팅에-사용됨">next js에서 제공하는 내장 훅, 주로 라우팅에 사용됨</h3>
</blockquote>
<h2 id="동적-라우팅과-쿼리-파라미터-처리">동적 라우팅과 쿼리 파라미터 처리</h2>
<h4 id="리액트에서-유사한-쓰임">리액트에서 유사한 쓰임</h4>
<ul>
<li><code>react</code>에서는 라우팅 관련 라이브러리를 설치</li>
<li><code>react-router-dom</code>을 예로 들자면 <code>useParams</code> 등의 훅을 사용했음<h4 id="next-js">NEXT JS</h4>
</li>
<li>동적 라우팅: URL 일부를 매개변수로 사용하여 동적으로 페이지를 생성하는 기능</li>
<li>쿼리 파라미터: URL에 추가 정보를 전달하기 위해 사용
<img src="https://velog.velcdn.com/images/gazero_/post/d1228d37-c938-4ffe-87d7-82300bf7c070/image.png" alt=""></li>
<li>함수를 통한 페이지 이동 기능<pre><code class="language-js">import { useRouter } from &quot;next/router&quot;;
</code></pre>
</li>
</ul>
<p>export default function Home() {
  const router = useRouter();</p>
<p>  const onClickButton = () =&gt; {
    router.push(&quot;/search&quot;);
  };
  return (
    <div>
      HOME PAGE
      <div>
        <button onClick={onClickButton}>Search 페이지로 이동</button>
      </div>
    </div>
  );
}</p>
<pre><code>![](https://velog.velcdn.com/images/gazero_/post/0cd1c588-0669-4ff0-ad49-fa26ac3eec6d/image.gif)


# Link?
&gt; ### Client Side Rendering(CSR), 서버에 필요한 요청을 보내지 않고, 클라이언트 자체에서 페이지를 이동시킬 수 있게 도와주는 컴포넌트

#### 리액트의 `Link`와 유사한 기능이지만 차이점
- 리액트에서는 Link `to`로 경로를 전달함
```js
      &lt;div&gt;
        &lt;Link to={&quot;/&quot;}&gt;Home&lt;/Link&gt;
        &lt;Link to={&quot;/search&quot;}&gt;Search&lt;/Link&gt;
        &lt;Link to={&quot;/country&quot;}&gt;Country&lt;/Link&gt;
      &lt;/div&gt;</code></pre><p><img src="https://velog.velcdn.com/images/gazero_/post/c5588263-f3f6-4607-8a63-a5b7cfa67f72/image.gif" alt=""></p>
<ul>
<li>넥스트에서는 Link <code>href</code>로 경로를 전달함</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gazero_/post/6cac2973-1b39-4642-8e55-79cc68b640e7/image.gif" alt=""></p>
<pre><code class="language-js">import Link from &quot;next/link&quot;;

export default function Home() {
  return (
    &lt;div&gt;
      HOME PAGE
      &lt;div&gt;
        &lt;Link href={&quot;/search&quot;}&gt;Search 페이지로 이동&lt;/Link&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<ul>
<li>동적으로 이동하는 경로(방법 1 - 문자열 명시)<pre><code class="language-js">import Link from &quot;next/link&quot;;
</code></pre>
</li>
</ul>
<p>export default function Home() {
  const code = &quot;KOR&quot;;
  return (
    <div>
      HOME PAGE</p>
<pre><code>  &lt;div&gt;
    &lt;Link href={`/country/${code}`}&gt;{code} 페이지로 이동&lt;/Link&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre><p>  );
}</p>
<pre><code>- 동적으로 이동하는 경로(방법 2 - url object로 구조화시킴)
```js
         &lt;Link
          href={{
            pathname: &quot;/country/[code]&quot;,
            query: { code: code },
          }}
        &gt;
          {code} 페이지로 이동
        &lt;/Link&gt;</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[NextJS에서 페이지 라우팅 어떻게 하나요?(Page Router)]]></title>
            <link>https://velog.io/@gazero_/NextJS%EC%97%90%EC%84%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%9D%BC%EC%9A%B0%ED%8C%85-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%98%EB%82%98%EC%9A%94Page-Router</link>
            <guid>https://velog.io/@gazero_/NextJS%EC%97%90%EC%84%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%9D%BC%EC%9A%B0%ED%8C%85-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%98%EB%82%98%EC%9A%94Page-Router</guid>
            <pubDate>Tue, 10 Oct 2023 13:42:29 GMT</pubDate>
            <description><![CDATA[<h1 id="page-router">Page Router</h1>
<blockquote>
<h2 id="폴더-구조를-기반으로-라우팅을-제공">폴더 구조를 기반으로 라우팅을 제공</h2>
</blockquote>
<p><img src="https://velog.velcdn.com/images/gazero_/post/536cc590-0f3e-451d-803b-cd991175c3fa/image.png" alt=""></p>
<h3 id="무슨-소리야">무슨 소리야?</h3>
<ol>
<li><code>&#39;/&#39;</code> 경로로 요청이 들어오면?
<img src="https://velog.velcdn.com/images/gazero_/post/33dfae2b-c218-4065-840e-5e74ef65e4ae/image.png" alt=""></li>
</ol>
<ul>
<li>pages의 index.js에 작성된 컴포넌트가 렌더링 됨</li>
</ul>
<ol start="2">
<li><code>&#39;/about&#39;</code> 경로로 요청이 들어오면?
<img src="https://velog.velcdn.com/images/gazero_/post/23ac8410-1f80-4805-9514-9f95d22b84d8/image.png" alt=""></li>
</ol>
<ul>
<li>pages이하의 about.js에 작성된 컴포넌트가 렌더링 됨</li>
</ul>
<ol start="3">
<li><code>&#39;/post&#39;</code> 경로로 요청이 들어오면?
<img src="https://velog.velcdn.com/images/gazero_/post/712b32e3-e2da-42a0-9a8a-560182822b70/image.png" alt=""></li>
</ol>
<ul>
<li>post폴더 이하의 index.js에 작성된 컴포넌트가 렌더링 됨</li>
</ul>
<ol start="4">
<li><code>&#39;/post/{포스트아이디}&#39;</code> 경로로 요청이 들어오면?
<img src="https://velog.velcdn.com/images/gazero_/post/b7498b1d-a8b7-40be-a90b-fd47967044f4/image.png" alt=""></li>
</ol>
<ul>
<li>URL 파라미터(동적경로)</li>
<li><code>[id].js</code> 동적 경로(ex. ~/post/1234)에 대응하는 동적 라우팅의 기능</li>
</ul>
<ol start="5">
<li><code>&#39;/none&#39;</code> 경로로 요청이 들어오면?</li>
</ol>
<ul>
<li>404 NOT FOUND</li>
</ul>
<p><strong>따라서 <code>NEXT JS</code>에서는 React JS에서의 라우팅을 별도로 작업해 줄 필요 없음, 폴더 구조를 통해 자동으로 라우터 기능을 제공하기 때문</strong></p>
<h2 id="파일-구조를-파악해보자">파일 구조를 파악해보자</h2>
<h3 id="_appjs-파일">_app.js 파일</h3>
<p><img src="https://velog.velcdn.com/images/gazero_/post/772ceee7-8884-4922-b2d7-428f07efd568/image.png" alt=""></p>
<ul>
<li><code>_app.js</code>는 pages 폴더에 있는 js 파일을 렌더시키는 역할이라고 생각하자.</li>
<li>지금 여기에서는 index.js의 경로가 <code>&#39;/&#39;</code>이므로
<img src="https://velog.velcdn.com/images/gazero_/post/5bce5053-5818-433e-b9af-13e2f2369730/image.png" alt=""></li>
<li>이 코드에 해당하는 페이지가 기본 페이지로 띄워지는데</li>
<li>이 페이지는 localhost:3000에 해당하는 페이지임
<img src="https://velog.velcdn.com/images/gazero_/post/80c6b075-2703-4093-98c3-7132830b96a6/image.png" alt=""></li>
</ul>
<p><strong>무슨말이냐면</strong>
<img src="https://velog.velcdn.com/images/gazero_/post/71138cf2-997d-432c-8315-50d41840487a/image.png" alt=""></p>
<ul>
<li>pages폴더 아래에 about.js 파일을 만들고
<img src="https://velog.velcdn.com/images/gazero_/post/5c263c96-a676-4a61-9b94-e731a4acdeb9/image.png" alt=""></li>
<li><code>/about</code>경로로 이동하면, 해당 페이지가 라우팅되는 것을 확인할 수 있음</li>
<li>즉, <code>_app.js</code>에서 prop으로 전달받는 Component로 요청받은 페이지를 렌더해줌
<img src="https://velog.velcdn.com/images/gazero_/post/cfd96ecf-500d-4456-afd5-6f62bb26c9b1/image.png" alt=""></li>
<li>App 컴포넌트로 AboutPage를 전달받고 있는 것을 확인할 수 있음</li>
</ul>
<p><strong>정리하자면</strong>
<code>_app.js</code>에는 공통으로 들어갈 컴포넌트를 작성해주면 됨!</p>
<h3 id="_documentjs">_document.js</h3>
<p><img src="https://velog.velcdn.com/images/gazero_/post/06990cfd-ef4a-4f7b-9767-6c239cd8316a/image.png" alt=""></p>
<blockquote>
<p>공통적으로 적용되어야 할 html 코드를 작성하는 컴포넌트</p>
</blockquote>
<ul>
<li>리액트에서 index.html과 유사</li>
</ul>
<h4 id="라우터-실습은-다음-포스팅에-계속">라우터 실습은 다음 포스팅에 계속....</h4>
]]></description>
        </item>
    </channel>
</rss>