<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>씅로그 :)</title>
        <link>https://velog.io/</link>
        <description>Front-end</description>
        <lastBuildDate>Thu, 23 Oct 2025 05:01:51 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>씅로그 :)</title>
            <url>https://velog.velcdn.com/images/sseung-i/profile/bdb0202a-a49a-4115-a3aa-4485aa9d1999/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 씅로그 :). All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sseung-i" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[프론트엔드 공부, 신규강의로 달려봅시다  — ‘한입 시리즈’ SNS편]]></title>
            <link>https://velog.io/@sseung-i/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B3%B5%EB%B6%80-%EC%8B%A0%EA%B7%9C%EA%B0%95%EC%9D%98%EB%A1%9C-%EB%8B%AC%EB%A0%A4%EB%B4%85%EC%8B%9C%EB%8B%A4-%ED%95%9C%EC%9E%85-%EC%8B%9C%EB%A6%AC%EC%A6%88-SNS%ED%8E%B8</link>
            <guid>https://velog.io/@sseung-i/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B3%B5%EB%B6%80-%EC%8B%A0%EA%B7%9C%EA%B0%95%EC%9D%98%EB%A1%9C-%EB%8B%AC%EB%A0%A4%EB%B4%85%EC%8B%9C%EB%8B%A4-%ED%95%9C%EC%9E%85-%EC%8B%9C%EB%A6%AC%EC%A6%88-SNS%ED%8E%B8</guid>
            <pubDate>Thu, 23 Oct 2025 05:01:51 GMT</pubDate>
            <description><![CDATA[<p>이 링크를 통해 구매하시면 제가 수익을 받을 수 있어요. 🤗</p>
<h2 id="한-입-크기로-잘라먹는-실전-프로젝트---sns-편">한 입 크기로 잘라먹는 실전 프로젝트 - SNS 편</h2>
<p>프론트엔드 공부를 처음 시작했을 때,
저는 리액트로 입문하면서 정말 많은 시행착오를 겪었어요.
그때 처음 들었던 강의가 바로 이정환 님의 ‘한입 시리즈’!
쉽고 친절하게 개념을 풀어주는 설명 덕분에
프로젝트만들어보면서 자신감을 얻었죠 😊</p>
<p>요즘 현생에 치여서 뭔가를 시작하지 못하였는데,
뭐든 결제해두면 하게되는 마법 있잖아요? ㅋㅋ</p>
<p>이번에 ‘한 입 크기로 잘라먹는 실전 프로젝트 - SNS편’ 신규 강의가 나왔어요!
👉 <a href="https://inf.run/yoASN">강의 바로가기</a></p>
<p><img src="https://velog.velcdn.com/images/sseung-i/post/b665d842-9fb9-4c32-9981-d363c0c676cc/image.png" alt="">
저는 이전에 ‘한입 리액트’, ‘한입 타입스크립트’도 수강했는데,
이정환 님의 강의는 정말 믿.강(믿고 듣는 강의)이에요 😎</p>
<p>강의 템포도 적당하고, 초보자 눈높이에 딱 맞춰서 설명해주셔서
리액트를 처음 배우는 분들도 쉽게 따라올 수 있고
저도 실무하면서 틈틈히 다시 보기도 하고있어요.</p>
<p>최근 Supabase로 혼자 데이터 작업까지 해서 프로젝트를 진행해보고싶었는데
이런저런 이유로 쉽지않더라구요
그치만, 이번기회에 달려보려고합니다!!</p>
<h3 id="📚-이번-강의에서는-이런-걸-배워요">📚 이번 강의에서는 이런 걸 배워요</h3>
<ul>
<li>React.js + TypeScript + Supabase를 활용한 실무급 SNS 프로젝트 제작</li>
<li>회원가입 &amp; 로그인 인증/인가 기능 구현</li>
<li>무한 스크롤, 이미지 업로드, 좋아요, 무한 중첩 댓글, 다크모드까지!</li>
<li>Zustand + Tanstack Query로 서버 &amp; 전역 상태 관리</li>
</ul>
<p>단순한 예제 강의가 아니라,
실제로 배포 가능한 수준의 SNS를 직접 만드는 실전형 강의라서
혼자힘으로 플젝 하나 완성하면 그 뿌듯함... 뭔지 아시죠 ?</p>
<p>강의 페이지에는 실제로 작동되는 <strong>데모 사이트</strong>도 있어서
어떤 결과물이 나오는지 미리 확인할 수 있어요 👀</p>
<h3 id="🎁-할인-정보--수강-혜택">🎁 할인 정보 &amp; 수강 혜택</h3>
<p><img src="https://velog.velcdn.com/images/sseung-i/post/77f8d769-2510-44a7-ad5b-d1886207780f/image.png" alt=""></p>
<h4 id="현재-오픈-기념으로-얼리버드-35-할인-중">현재 오픈 기념으로 얼리버드 35% 할인 중!</h4>
<p>(저는 사전 등록 이벤트 덕분에 50% 쿠폰을 받았어요 🙌)</p>
<p><strong>인프런은 강의 할인할 때 미리 구매해두는 게 진리죠.</strong>
할인 기간 놓치면 나중에 꼭 후회하더라구요 😂</p>
<p>또한 수강생 전용 오픈채팅방 &amp; 카페 커뮤니티가 있어서
질문이나 에러가 생겼을 때도 빠르게 도움 받을 수 있어요.
실제 수강생들끼리 코드 리뷰도 하고, 정보 공유도 활발합니다.</p>
<p>저는 이번 강의로 Supabase를 제대로 익혀보려구요.
혼자 하려니 막히던 부분이 많았는데,
이번엔 제대로 완강 도전 💪</p>
<p>프론트엔드 실력을 확실히 키우고 싶은 분이라면
이번 강의로 함께 성장해보세요!</p>
<p>👇👇👇
<a href="https://inf.run/yoASN">한 입 크기로 잘라먹는 실전 프로젝트 - SNS편 수강하러 가기</a></p>
<h3 id="👇👇👇-한-입-시리즈-추천">👇👇👇 한 입 시리즈 추천!!</h3>
<p><a href="https://inf.run/221rn">한 입 크기로 잘라먹는 리액트(기초부터 실전까지) 수강하러 가기</a>
<a href="https://inf.run/Xk6fc">한 입 크기로 잘라먹는 타입스크립트 수강하러 가기</a>
<a href="https://inf.run/neFGb">한 입 크기로 잘라먹는 Next.js(v15) 수강하러 가기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[nextjs] #425 #418 #423 뭐때문에 이런 에러가 나는가?! ]]></title>
            <link>https://velog.io/@sseung-i/nextjs-425-418-423-%EB%AD%90%EB%95%8C%EB%AC%B8%EC%97%90-%EC%9D%B4%EB%9F%B0-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%82%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@sseung-i/nextjs-425-418-423-%EB%AD%90%EB%95%8C%EB%AC%B8%EC%97%90-%EC%9D%B4%EB%9F%B0-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%82%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Tue, 25 Mar 2025 07:51:03 GMT</pubDate>
            <description><![CDATA[<p>어드민 프로젝트이다보니 날짜, 시간 포멧팅 사용이 많아 date-fns를 사용하였다.</p>
<p>로컬 작업시에는 찍히지 않던 에러가 배포하고나니 초기 렌더링 시에 와장창 뜨는것이 아닌가...</p>
<p><img src="https://velog.velcdn.com/images/sseung-i/post/7ca7afb4-da23-4bf2-adfc-e1ec1da5b146/image.png" alt=""></p>
<p>에러도 종류별로 나는것같아 보여서 에러 내의 리액트 링크를 타고 들어갔더니 </p>
<blockquote>
<p>Text content does not match server-rendered HTML.</p>
</blockquote>
<blockquote>
<p>Hydration failed because the server rendered HTML didn&#39;t match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:</p>
</blockquote>
<ul>
<li>A server/client branch <code>if (typeof window !== &#39;undefined&#39;)</code>.</li>
<li>Variable input such as <code>Date.now()</code> or <code>Math.random()</code> which changes each time it&#39;s called.</li>
<li>Date formatting in a user&#39;s locale which doesn&#39;t match the server.</li>
<li>External changing data without sending a snapshot of it along with the HTML.</li>
<li>Invalid HTML tag nesting.<blockquote>
</blockquote>
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.<blockquote>
</blockquote>
<a href="https://react.dev/link/hydration-mismatch%5Bmissing">https://react.dev/link/hydration-mismatch[missing</a> argument]</li>
</ul>
<blockquote>
<p>There was an error while hydrating but React was able to recover by instead client rendering the entire root.</p>
</blockquote>
<p>이러한 내용들이었다.</p>
<p>서버에서 렌더링 한 내용과 클라이언트에서 렌더링 한 내용이 맞지 않아서 생긴 문제인가싶어 코드를 다 찾아봤지만 다르게 렌더링 될 만한 부분이 보이지 않는데 ..?</p>
<p>페이지와 컴포넌트 단위로 가려가며 배포 해서 확인해보기로 하였더니 날짜를 쓰던 곳을 가렸을 때에는 에러가 나지않는것을 확인!
다시 검색을 두드려본 결과 이와 동일한 문제의 <a href="https://github.com/vercel/next.js/issues/37489">이슈내용</a>이 있었다.</p>
<p>date-fns의 문제인가싶어 getDate() 처럼 직접 뜯어서 포멧팅을 해보았지만, 동일한 에러가 나는것을 보고 Date 객체 자체가 문제가 있는것 같다는것을 직감하였다...</p>
<p>다시 코멘트를 하나하나 읽고 내 문제일까 싶은것을 찾던 중 발견한 글!</p>
<p><img src="https://velog.velcdn.com/images/sseung-i/post/3c623bc5-e57e-4825-ab62-59fcc4291369/image.png" alt=""></p>
<blockquote>
<p>서버의 Data 객체는 기본적으로 UTC 기준
브라우저는 자동으로 현지 시간으로 변환</p>
</blockquote>
<p>UTC != KST 가 되어버린 것이다.</p>
<h2 id="해결">해결</h2>
<ol>
<li>우리는 백에서 받아올 때 UTC로 받는다</li>
<li>한국에서만 사용한다</li>
</ol>
<p>그렇다면 KST로 고정을 해주면 되지않을까?</p>
<p>date-fns에서 타임존을 셋팅해주려면 &quot;<a href="https://www.npmjs.com/package/date-fns-tz">date-fns-tz</a>&quot;를 확장 아리브러리를 추가로 install 해줘야 한다.</p>
<pre><code class="language-js">export const KST = &quot;Asia/Seoul&quot;;
const zonedDate = toZonedTime(date, KST);

format(zonedDate, &quot;yyyy.MM.dd&quot;, { timeZone: KST })
</code></pre>
<p>KST를 사용한 Date로 바꿔주고,  이 타임존을 사용해서 포멧팅 해줘! 라고 이중으로 알려줘야한다.</p>
<p>이렇게 싹 바꿔준 이후 배포 시 에러 없이 깔-끔</p>
<p>리액트 에러내용처럼 Date와 random 같이 환경이나 실행 시점에 따라 다르게 보여질 수 있는부분은 미리 인지를 할 수 있어야 할 것 같다.</p>
<p>로컬에서 잘된다고 방심 노노!</p>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[시스템콜, 커널 (js/Node.js 예시)]]></title>
            <link>https://velog.io/@sseung-i/%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%BD%9C-%EC%BB%A4%EB%84%90-jsNode.js-%EC%98%88%EC%8B%9C</link>
            <guid>https://velog.io/@sseung-i/%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%BD%9C-%EC%BB%A4%EB%84%90-jsNode.js-%EC%98%88%EC%8B%9C</guid>
            <pubDate>Tue, 18 Mar 2025 13:48:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>cs 공부하며 시스템 콜, 커널에 대해 이해가도록 찾아보며 js와 Node.js로 예시로 정리해보았다.</p>
</blockquote>
<h2 id="커널">커널</h2>
<ul>
<li>커널(Kernel)은 운영체제(OS)의 핵심 요소</li>
<li>하드웨어와 응용 프로그램 사이에서 중재자 역할을 한다.
즉, 프로그램이 CPU, 메모리, 디스크, 네트워크 등의 하드웨어를 직접 제어하지 않고, 커널을 통해서만 접근할 수 있도록 한다.</li>
</ul>
<p>📌 커널 = 운영체제의 핵심, 하드웨어와 소프트웨어를 이어주는 다리</p>
<h2 id="시스템-콜system-call이란">시스템 콜(System Call)이란?</h2>
<p>사용자 프로그램이 운영체제(OS)의 커널에게 직접 접근할 수 없으므로, 커널에게 요청을 보내는 방식</p>
<h3 id="시스템-콜의-동작-과정">시스템 콜의 동작 과정</h3>
<p>1️⃣ 응용 프로그램이 시스템 콜 요청 (예: 파일 열기)
2️⃣ 시스템 콜이 커널 모드로 전환 (유저 모드 → 커널 모드)
3️⃣ 커널이 요청된 작업 수행 (예: 파일을 읽음)
4️⃣ 작업 완료 후, 유저 모드로 복귀 (커널 모드 → 유저 모드)
5️⃣ 응용 프로그램이 결과를 받음</p>
<h4 id="커널-모드kernel-mode란">커널 모드(Kernel Mode)란?</h4>
<p>운영체제의 핵심인 커널이 실행되는 모드로, 하드웨어(메모리, 디스크, 네트워크 등)에 직접 접근할 수 있는 권한을 가짐</p>
<p>일반 프로그램(유저 모드)은 직접 하드웨어에 접근할 수 없음
프로그램이 커널의 기능이 필요할 때 시스템 콜을 요청하면 커널 모드에서 실행됨</p>
<hr>
<h2 id="nodejs-환경에서는-시스템-콜을-간접적으로-사용할-수-있다">Node.js 환경에서는 시스템 콜을 간접적으로 사용할 수 있다?</h2>
<p>Dart나 node.js 코드를 보다보면 파일을 다루기 위해 File이나 fs를 사용하는것이 생각나서 내가 이해할 수 있는 예제들로 더 찾아보게 되었다.</p>
<ul>
<li>js는 원래 브라우저에서 실행되도록 만들어졌으며, 이후에 node.js 런타임으로 브라우저가 아닌 환경(서버, 로컬 PC 등)에서도 실행할 수 있게 된것</li>
<li>js자체는 보안때문에 운영체제 접근이 불가능하여 시스템 콜 사용이 불가능 하지만 node.js에서는 내부적으로 사용 가능하다.</li>
</ul>
<h3 id="그렇다면-내가아는-백엔드로-기기를-제어할-수-있나">그렇다면 내가아는 백엔드로 기기를 제어할 수 있나??</h3>
<p>Node.js는 서버이므로, 원격으로 클라이언트 PC를 직접 조작할 수 없지만
웹 브라우저 ↔ 백엔드(Node.js) 간 실시간 양방향 통신을 통해 기기 제어(하드웨어 조작) 가능하고,
이게 IoT(사물인터넷) 프로젝트에서 많이 사용하는 방법이라고 한다.</p>
<p>💡 즉, WebSocket, API, 클라이언트 애플리케이션을 활용해 <strong>간접적으로 제어 가능</strong>!</p>
<hr>
<h2 id="nextjs에서-ssr서버사이드렌더링환경은">Nextjs에서 SSR(서버사이드렌더링)환경은?</h2>
<p>Next.js의 SSR은 기본적으로 Node.js 환경에서 실행되지만
Vercel, Cloudflare Workers, Deno, Edge Functions 같은 서버리스 환경에서도 실행 가능하다.
API Routes도 마찬가지이다.</p>
<p>📌 즉, Next.js의 SSR을 실행하는 서버가 꼭 Node.js일 필요는 없고, 실행 환경에 따라 다를 수 있다!</p>
<p>서버리스 환경에서는 Edge Runtime을 선택할 수도 있다고 한다.</p>
<pre><code class="language-js">export const runtime = &#39;edge&#39;;  // Edge Runtime (Deno 기반)

export async function GET(req) {
    return new Response(&quot;Hello from Edge Runtime!&quot;, { status: 200 });
}</code></pre>
<p>✅ 이 코드가 실행되면 Node.js가 아니라 V8 + Deno 환경에서 동작하게 됨
✅ 즉, Next.js의 서버는 실행 환경에 따라 Node.js가 아닐 수도 있음</p>
<hr>
<h3 id="nodejs와-edge-runtime">Node.js와 Edge Runtime</h3>
<h4 id="nodejs">Node.js</h4>
<ul>
<li>서버에서 JavaScript를 실행할 수 있는 런타임 (백엔드 개발에 주로 사용)</li>
<li>파일 시스템, 네트워크, 데이터베이스 접근 가능 → 브라우저에서 할 수 없는 작업을 실행</li>
<li>JavaScript를 실행하는 독립적인 프로세스를 만듦 (즉, 하나의 서버 프로세스가 계속 실행되면서 요청을 받고 응답을 반환)</li>
</ul>
<h4 id="edge-runtime">Edge Runtime</h4>
<ul>
<li>Edge 네트워크에서 실행되는 가볍고 빠른 런타임 (서버리스 환경)</li>
<li>파일 시스템 접근 불가능 → 보안이 강화된 환경</li>
<li>요청이 들어올 때마다 새로운 인스턴스가 실행되고, 응답을 보내면 자동으로 종료됨.(즉, 항상 실행되는 서버(Node.js)와 다르게, Edge Functions는 필요할 때만 실행)</li>
</ul>
<h4 id="nodejs와-edge-runtime의-차이점">Node.js와 Edge Runtime의 차이점</h4>
<img src="https://velog.velcdn.com/images/sseung-i/post/26d299fb-42cf-44d5-a92d-a471d6863f6e/image.png"/>

<p>✅ Node.js는 항상 실행되는 서버 기반 환경
✅ Edge Runtime은 요청이 있을 때만 실행되는 서버리스 환경</p>
<hr>
<h2 id="그래서-정리를-해보자면">그래서, 정리를 해보자면</h2>
<p>Node.js가 파일 시스템, 네트워크, 데이터베이스를 접근하는 과정은 시스템 콜(System Call)을 사용하며, 이때 커널 모드에서 작업이 수행된다.
하지만 Node.js 자체가 커널 모드에서 실행되는 것은 아니고, 시스템 콜을 통해 커널이 대신 실행하는 방식.</p>
<h3 id="nodejs에서-시스템-콜-사용-예시">Node.js에서 시스템 콜 사용 예시</h3>
<h4 id="1-파일시스템-접근-fs모듈">1. 파일시스템 접근 (fs모듈)</h4>
<pre><code class="language-js">const fs = require(&quot;fs&quot;);

fs.readFile(&quot;test.txt&quot;, &quot;utf8&quot;, (err, data) =&gt; {
    if (err) {
        console.error(&quot;파일 읽기 실패:&quot;, err);
        return;
    }
    console.log(&quot;파일 내용:&quot;, data);
});</code></pre>
<p>📌 내부 동작 과정
1️⃣ fs.readFile()이 호출됨 (유저 모드)
2️⃣ Node.js가 내부적으로 시스템 콜 open()을 호출하여 파일을 엶
3️⃣ 파일을 읽기 위해 read() 시스템 콜이 호출됨
4️⃣ 커널 모드에서 파일을 읽고, 데이터를 유저 모드로 반환
5️⃣ Node.js가 데이터를 받아서 JavaScript 코드에서 사용할 수 있도록 제공</p>
<p>💡 즉, Node.js의 fs 모듈을 사용하면 내부적으로 시스템 콜을 호출하여 커널이 파일 시스템을 처리함!</p>
<h4 id="2-네트워크-요청-http모듈">2. 네트워크 요청 (http모듈)</h4>
<pre><code class="language-js">const http = require(&quot;http&quot;);

http.get(&quot;http://example.com&quot;, (res) =&gt; {
    console.log(`응답 코드: ${res.statusCode}`);
    res.on(&quot;data&quot;, (chunk) =&gt; {
        console.log(`응답 데이터: ${chunk}`);
    });
});</code></pre>
<p>📌 내부 동작 과정
1️⃣ http.get()이 호출됨 (유저 모드)
2️⃣ Node.js가 내부적으로 시스템 콜 socket()을 호출하여 네트워크 연결을 생성
3️⃣ 연결이 완료되면 connect() 시스템 콜을 호출하여 서버와 연결
4️⃣ 데이터를 보내기 위해 send() 시스템 콜 실행
5️⃣ 서버에서 응답을 받으면 recv() 시스템 콜을 실행하여 데이터 수신
6️⃣ 받은 데이터를 Node.js에서 JavaScript 코드로 처리</p>
<p>💡 즉, Node.js에서 네트워크 요청을 보내면 내부적으로 커널이 네트워크 소켓을 관리하고 데이터를 처리함!</p>
<h4 id="3-데이터베이스-접근">3. 데이터베이스 접근</h4>
<pre><code class="language-js">const mysql = require(&quot;mysql&quot;);

const connection = mysql.createConnection({
    host: &quot;localhost&quot;,
    user: &quot;root&quot;,
    password: &quot;password&quot;,
    database: &quot;test_db&quot;
});

connection.connect((err) =&gt; {
    if (err) {
        console.error(&quot;데이터베이스 연결 실패:&quot;, err);
        return;
    }
    console.log(&quot;데이터베이스 연결 성공!&quot;);
});</code></pre>
<p>📌 내부 동작 과정
1️⃣ mysql.createConnection()이 호출됨 (유저 모드)
2️⃣ Node.js가 내부적으로 시스템 콜 socket()을 호출하여 데이터베이스 서버와 연결
3️⃣ connect() 시스템 콜을 호출하여 연결을 설정
4️⃣ SQL 쿼리를 보내기 위해 send() 시스템 콜을 실행
5️⃣ 응답을 받으면 recv() 시스템 콜을 실행하여 데이터 가져오기
6️⃣ 받은 데이터를 JavaScript에서 사용할 수 있도록 반환</p>
<p>💡 즉, 데이터베이스와의 네트워크 통신도 시스템 콜을 통해 이루어짐!</p>
<hr>
<h2 id="정리">정리</h2>
<ul>
<li><strong>웹(JavaScript, 브라우저)</strong> → 완전한 <strong>사용자 모드(User Mode)</strong>에서 실행됨. 시스템 콜을 직접 호출할 수 없음.</li>
<li><strong>Node.js 기반 데스크탑 앱 (Electron 등)</strong> → <strong>사용자 모드(User Mode)</strong>에서 실행되지만, <strong>시스템 콜을 사용</strong>하여 OS의 기능(파일 시스템, 네트워크 등)에 접근 가능.
즉, Node.js 기반 애플리케이션은 사용자 모드이지만, 시스템 콜을 사용하여 기기 제어가 가능.</li>
<li><strong>Node.js 기반의 백엔드 서버</strong> -&gt; 파일 시스템(fs 모듈)이나 네트워크 소켓(net 모듈) 같은 기능을 사용할 때, 시스템 콜(System Call)을 사용해 커널에게 요청
즉, 웹과 통신하는 백엔드 서버도 파일을 읽거나, 데이터베이스 접근 등 네트워크 요청을 처리하는 과정에서 시스템 콜을 사용한다고 볼 수 있다</li>
</ul>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS S3, presignedUrl로 파일업로드 개선하기]]></title>
            <link>https://velog.io/@sseung-i/AWS-S3-presignedUrl%EB%A1%9C-%ED%8C%8C%EC%9D%BC%EC%97%85%EB%A1%9C%EB%93%9C-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sseung-i/AWS-S3-presignedUrl%EB%A1%9C-%ED%8C%8C%EC%9D%BC%EC%97%85%EB%A1%9C%EB%93%9C-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 10 Dec 2024 05:48:18 GMT</pubDate>
            <description><![CDATA[<p>그동안 다양한 파일을들 업로드 하며 formData로만 사용해봤었다.</p>
<p>다양했어봤자 보통 이미지나 pdf정도였어서 큰 데이터 처리를 해본 적은 없었는데, 이번 사이드 작업을 하며 영상파일을 다루게 되었다.</p>
<p>동일하게 formData로 작업을 했었는데, 개발단계에서 30초 내의 영상으로 테스트할 때에는 문제가없었으나 QA를 하며 1분 넘는 영상을 사용하였는데 갑자기 오류가 나서 보니 서버가 터져버렸다.💣💣</p>
<p>처음엔 그냥 오류인줄알고 한번더 테스트 해보니 이것은, 영상업로드 문제라는것을 캐치하고 확인해보니 영상 업로드 시점에 CPU가 100%를 찍은것..</p>
<p>우선 강의영상을 다뤄야하다보니 CPU를 늘리는것으로 해결은 되었지만, 백엔드분과 추가적으로 presignedUrl을 적용해보기로 하였고 결과적으로는 적용 하였다.</p>
<p>presignedUrl에 대한 자세한 설명과 백엔드 설정방법은 참고한 블로그로 대체! (글 하단 참고자료)</p>
<hr>
<h3 id="작업-플로우를-간략하게-써보자면">작업 플로우를 간략하게 써보자면</h3>
<ul>
<li><strong>적용 전(formData)</strong>
영상데이터를 백엔드에 전송 -&gt; 백엔드는 영상데이터를 S3에 업로드 -&gt; 업로드 된 S3 url을 프론트로 전송 -&gt; 프론트는 받은 url을 등록 폼 내에 넣어서 post</li>
<li><strong>적용 후(presignedUrl)</strong>
영상데이터의 파일타입,파일이름을 백엔드에 전송 -&gt; 백엔드는 영상이 업로드 될 주소인 presignedUrl을 프론트에 전송 -&gt; 받은 Url로 프론트에서 바로 영상데이터 put -&gt; 결과값 200이면 받은 S3업로드 url 등록 폼내에 넣어서 post</li>
</ul>
<hr>
<p>백엔드분도 처음 처리해보는것이라 서로 찾아보고 맞춰가며 진행하였다.
처음엔 presignedUrl만 받았었어서, 등록 폼에 넣어줄 파일Url에도 넣었더니 정상 처리가 되지 않았다.
서치 해보니 url을 프론트에서 잘라쓰거나 백에서 presignedUrl과 함께 전달해준다고 하여, 백에서 전달받기로 하였다.</p>
<blockquote>
<p>처음엔 presignedUrl로 확인하니 나오지않아서 data로 url을 받아야 정상처리된건줄 알았는데, 찾아보니 data: &quot;&quot; 비어있는 형태에 Status: 200으로 오며, url은 알아서 잘라 써야하는것!</p>
</blockquote>
<p>프론트에서 직접 업로드 할때 사용할 presignedUrl은 s3파일url 뒤에 쿼리스트링으로 직접 업로드 할 수 있도록 열어주는 값이 붙어 있다..</p>
<pre><code>?X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&amp;X-Amz-Credential=AKIAYQYUA2LJPHE2C77H%2F20241209%2Fap-northeast-2%2Fs3%2Faws4_request&amp;X-Amz-Date=20241209T074627Z&amp;X-Amz-Expires=300&amp;X-Amz-Signature=ee580bfcec95a638d3efbcdafab51952d25535288f2bba5888df0fc2a68df580&amp;X-Amz-SignedHeaders=host&amp;x-id=PutObject</code></pre><p>백에서 해당 쿼리 제거 후 전달해 준 값으로 등록 post날렸더니 성공!!!</p>
<h3 id="그렇다면-개선여부-체크-들어가아죠">그렇다면? 개선여부 체크 들어가아죠</h3>
<blockquote>
<p>서버 들렸다 가던 formData 형식 파일 업로드
<img src="https://velog.velcdn.com/images/sseung-i/post/34d7d572-fee6-4f68-9883-f6bcf3f5edaa/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>presignedUrl로 바로 파일 업로드
<img src="https://velog.velcdn.com/images/sseung-i/post/78590627-e4fa-48fb-a047-434e2782203d/image.png" alt=""></p>
</blockquote>
<p>1시간내외의 영상으로 테스트하였고,
AWS의 CPU는 물론 위처럼 시간이 15.37s -&gt; 9.54s로 거의 2/3으로 줄어들어서 적용하길 잘한것 같다.</p>
<h3 id="cpu-부하를-줄이기-위해-시작하였으나-시간도-줄어서-뿌듯뿌듯">CPU 부하를 줄이기 위해 시작하였으나, 시간도 줄어서 뿌듯뿌듯</h3>
<hr>
<h3 id="reference">Reference</h3>
<p><a href="https://suloth.tistory.com/188">https://suloth.tistory.com/188</a>
<a href="https://velog.io/@mimi0905/Presigned-URL%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-S3%EB%A1%9C-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C">https://velog.io/@mimi0905/Presigned-URL%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-S3%EB%A1%9C-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C</a></p>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nextjs 다국어 적용하기 - 1]]></title>
            <link>https://velog.io/@sseung-i/%EC%96%B8%EC%96%B4%EB%B3%80%ED%99%98-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@sseung-i/%EC%96%B8%EC%96%B4%EB%B3%80%ED%99%98-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Tue, 12 Nov 2024 02:20:52 GMT</pubDate>
            <description><![CDATA[<p><code>Nextjs 14.1</code>, <code>AppRoute</code></p>
<p>두개의 프로젝트에서 조금 다르게 언어변환을 적용해 보았다.</p>
<h1 id="1-유저-선호언어를-위해-클라이언트를-거쳐야만-했다">1. 유저 선호언어를 위해 클라이언트를 거쳐야만 했다.</h1>
<p>기존에 언어변환을 적용했던 프로젝트가 있었다.
해당 프로젝트에서는 기본언어의 우선순위가 이랬다.</p>
<blockquote>
<ol>
<li>기존에 방문했던 유저라면 그당시 설정한 언어로</li>
<li>처음 방문시 유저의 브라우저 설정의 언어로</li>
</ol>
</blockquote>
<p>만약 1번만 했었더라면, 쿠키사용정도로 middleware에서만 해결했을수도 있을 것 같았는데....
2번을 하기위해 여러 방법을 써봤는데 결론은 실패.</p>
<p>로컬에서 할 때에는 <code>request.headers.get(&quot;accept-language&quot;)</code> 에서 확인이 가능하였으나, aws의 amplify로 배포하면 해당 accept-language가 없어지고 들어오지 않는것이 문제였다.</p>
<p>일단 롤백하고 3일정도를 amplify.yml수정도해보고 유저에이전트가 ‘Amazon CloudFront’여서 CloudFront도 건드려보았지만 답이 나오지않았고, 문서찾아봤을때 amplify에서는 유저 각각에 대한 정보들은 헤더에서 날려버린다는것 같았다.
CDN 캐싱을 사용하면서 공통된 정보 전달을 위해 그런건가..? 라는 생각을 해보며 aws쪽 공부를 언젠가는 꼭 해야겠다고 다짐하게 되었던 계기였다...
(이 부분에 대해 아시는분은 알려주시면 감사하겠습니다 ㅠㅠ)</p>
<p>더이상 시간을 끌 순 없다!
middleware(서버)와 cookies(서버+클라)와 web Api인 navigator(클라)를 섞어서 적용하였다.</p>
<h3 id="middleware">Middleware</h3>
<pre><code class="language-ts">export const locales = [&quot;ko&quot;, &quot;ja&quot;, &quot;en&quot;] as const;
export const DEFAULT_LOCALE = &quot;en&quot;;

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  const pathnameHasLocale = locales.some(
    (locale) =&gt; pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  );

  if (pathnameHasLocale) return;

  /** 패스에 로케일 정보가 없을 때
   * 쿠키 값이 있을 경우 : 쿠키를 넣어줌 (전에 방문 이력)
   * 쿠키 값이 없을 경우 : DEFAULT_LOCALE로 넣어줌 (이후 클라이언트에서 브라우저 선호로 바꿈)
   */
  const COOKIE_LOCALE = request.cookies.get(&quot;xxx_locale&quot;)?.value;

  request.nextUrl.pathname = `/${COOKIE_LOCALE || DEFAULT_LOCALE}${pathname}`;
  return Response.redirect(request.nextUrl);
}</code></pre>
<h3 id="rootlayout">RootLayout</h3>
<p>쿠키를 가져오고, setCookie라는 클라이언트측컴포넌트를 만들어 해당 컴포넌트 내에서 처리해주었다.</p>
<pre><code class="language-ts">const SetCookie = ({
  locale,
  cookieLocale,
}: {
  locale: string;
  cookieLocale: string | undefined;
}) =&gt; {
  const pathname = usePathname();

  useEffect(() =&gt; {
    if (!cookieLocale) {
      // 1 :: 쿠키가 없다면, 브라우저 선호 언어로 쿠키 생성
      const userLocale = getUserLocale();

      if (userLocale !== locale) {
        // 1-1 :: 브라우저 선호 언어가 현재 locale이 아니라면 리다이렉트
        setCookie(userLocale);
        redirect(changeLocaleHandler(pathname, userLocale));
      }
    } else if (cookieLocale !== locale) {
      // 2 :: 쿠키와 현재 패스의 언어가 다르다면, 현재 패스 언어로 쿠키 변경 (직접 주소창 입력으로 들어왔을 경우 &amp; lang선택 변경)
      setCookie(locale);
    }

    // 3 :: 쿠키와 패스가 같다면 통과!
  }, []);

  return &lt;&gt;&lt;/&gt;;
};

export const changeLocaleHandler = (pathname: string, lang: string) =&gt; {
  let setPath = pathname;
  locales.forEach((locale) =&gt; {
    if (pathname.startsWith(`/${locale}`)) {
      setPath = pathname.replace(`/${locale}`, `/${lang}`);
    }
  });
  return setPath;
};

export const getUserLocale = () =&gt; {
  return navigator.language.substring(0, 2);
};</code></pre>
<p>이렇게해서 적용은 되었지만,
쿠키가 없는 초기상태이면서 locale없는 메인도메인으로 왔을경우에만 default로 영문을 보여줬다가 유저선호랑 맞지않으면 바로 redirect 해주기때문에 깜빡이는것을 겪을수밖에 없어서 나중에 한번 리팩토링을 거쳐야할 것 같다.</p>
<h3 id="페이지에서-언어-사용하기">페이지에서 언어 사용하기</h3>
<pre><code class="language-ts">export type DicObjType = { [key: string]: any };
let dic: DicObjType = {};
let nowLocale: Locale;

// nav, footer와 또는 페이지마다 공통으로 들어가는 부분들
export const getCommonDictionary = async (locale: Locale) =&gt; {
  await import(`@/dictionaries/common/${locale}.json`).then((module) =&gt; {
    dic = { ...dic, ...module.default };
    return dic;
  });
};

export const getDictionary = async (path: string, locale: Locale) =&gt;
  await import(
    `@/dictionaries/${path === &quot;/&quot; ? &quot;main&quot; : path}/${locale}.json`
  ).then((module) =&gt; {
    dic = { ...dic, ...module.default };
    nowLocale = locale;
    return dic;
  });


export const t = (k: string) =&gt; dic[k];
export const lo = () =&gt; nowLocale;

export const isLocale = () =&gt; {
  const isKo = nowLocale === &quot;ko&quot;;
  const isJa = nowLocale === &quot;ja&quot;;
  const isEn = nowLocale === &quot;en&quot;;
  return { isKo, isJa, isEn };
};</code></pre>
<pre><code class="language-tsx">// 서버컴포넌트 Page에서 params로 가져와서 해당 페이지 dictionary호출
const Contact = async ({
  params: { lang },
}: {
  params: { lang: Locale };
}) =&gt; {
  await getDictionary(&quot;contact&quot;, lang);
  // ...


  const dic = t(&quot;contact&quot;)[&quot;partnership&quot;];
  &lt;p&gt;{dic[&quot;desc&quot;]}&lt;/p&gt;

  // 이미지도 언어별로 보여줄 때
  const { isKo, isJa } = isLocale();
  const localeImg = isKo ? Image_KO : isJa ? Image_JA : Image_EN;</code></pre>
<p>ex) /dictionaries/contact/ko.json</p>
<pre><code class="language-json">{
  &quot;contact&quot; : {
    &quot;title&quot;: &quot;문의하기&quot;,
    &quot;partnership&quot;: {
      &quot;desc&quot;: &quot;내용내용&quot;
    }
  }
}</code></pre>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] 특정 범위 배열 생성 효율]]></title>
            <link>https://velog.io/@sseung-i/JS-%ED%8A%B9%EC%A0%95-%EB%B2%94%EC%9C%84-%EB%B0%B0%EC%97%B4-%EC%83%9D%EC%84%B1-%ED%9A%A8%EC%9C%A8</link>
            <guid>https://velog.io/@sseung-i/JS-%ED%8A%B9%EC%A0%95-%EB%B2%94%EC%9C%84-%EB%B0%B0%EC%97%B4-%EC%83%9D%EC%84%B1-%ED%9A%A8%EC%9C%A8</guid>
            <pubDate>Fri, 30 Aug 2024 03:01:27 GMT</pubDate>
            <description><![CDATA[<p>특정 범위, 즉 요소의 개수가 정해진 배열에 내용을 채워넣는 것을 할 때에는
배열을 먼저 생성하여 메모리 할당을 한번만 하고, 배열의 크기가 고정된 상태에서 추가 작업을 진행하는것이 효율적일 수 있다.</p>
<p><strong>적은 데이터면 큰 차이는 없겠지만 배열의 길이가 커질수록 차이는 보일테니 앞으로는 최대한 적용할 수 있도록!!</strong></p>
<p>예를들면</p>
<p>start ~ end 의 값을 배열에 담고싶다.</p>
<pre><code class="language-js">let start = 3, end = 10;
Array.from({length: (end - start) + 1}, () =&gt; start++);</code></pre>
<p>-&gt; 배열을 만들고, 두번째 인수로 전달된 함수로 순서대로 값을 초기화 하는데 start++를 함으로 현재 요소는 start로 반환하고, start값을 증가시켜주어 다음번 불려지는 초기화 함수는 start + 1이 반환되며 반복작업이 이루어지는것이다.</p>
<p>++</p>
<p>아래처럼 for문을 사용하여서 하게되면 배열의 크기를 동적으로 증가시키며, 배열이 커질수록 메모리 할당이 반복적으로 이루어지게 된다. 또한 push 메서드를 여러 번 호출하기 때문에 위의 코드보다 실행 효율이 떨어질 수 있다.</p>
<pre><code class="language-js">const arr = [];
for(let i = start; i &lt;= end; i++) {
  arr.push(i);
}</code></pre>
<p>같은 동작을 하더라도, js문법을 알고 쓴다면 작은 차이가 큰 차이로 이어질 수 있음을 생각하자!</p>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] 이벤트 루프란..]]></title>
            <link>https://velog.io/@sseung-i/JS%EC%97%90%EC%84%9C-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84%EB%9E%80</link>
            <guid>https://velog.io/@sseung-i/JS%EC%97%90%EC%84%9C-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84%EB%9E%80</guid>
            <pubDate>Thu, 29 Aug 2024 01:53:12 GMT</pubDate>
            <description><![CDATA[<p>원티드 프론트엔드 프리온보딩 9월이 이벤트루프라 신청하였고,
사전 과제인 영상을 보며 내가 생각하던 이벤트 루프에 대해 정리를 해보기로 했다.</p>
<hr>
<p>싱글 스레드 런타임 = 한번에 하나만 실행 = 싱글 콜 스택</p>
<h2 id="스택">스택</h2>
<p>함수를 쌓아서 저장해둔다.
가장 위쪽에 있는 함수부터 꺼내면서 실행된다.</p>
<pre><code class="language-js">function foo() {
  console.log(&quot;here!&quot;);
};

function bar() {
  foo();
}

function baz() {
  bar();
}

baz();</code></pre>
<p>스택의 아래부터 위로 쌓이는 순서
main() -&gt; baz() -&gt; bar() -&gt; foo()
스택이 빠지는(실행) 순서는 그 반대로 위에부터
foo() -&gt; bar() -&gt; baz() -&gt; main()</p>
<pre><code class="language-js">function bar() {
  bar();
}</code></pre>
<p>이렇게 잘못 쓰는 순간 에러 뿜!
스택에 미친듯이 쌓이면서 브라우저에서 maximum call stack 에러를 보내주고 실행을 끊어준다.</p>
<h2 id="블로킹-blocking">블로킹 (blocking)</h2>
<p>느리게 동작하는 코드(네트워크요청, 이미지 프로세싱 등)가 스택에 남아있는 것을 의미</p>
<p>동기로 실행되는 코드가 3개라면 해당 코드 3개가 순서대로완료 될 때 까지 그 이후의 코드는 기다려야한다. 브라우저는 해당 코드가 완료될 때 까지(콜스택이 비워질때까지) 멈춰있게 되면서, 유저 입장에서 사이트가 멈췄다 볼 수 있는것!
즉, 전체 완료까지 시간이 소요되어 전체 프로세스의 성능이 저하될 수 있습니다.</p>
<blockquote>
<p><strong>동기와 비동기에 대한 나의 오해</strong>
사전적으로
동기: 동시에 일어난다
비동기: 동시에 일어나지 않는다</p>
</blockquote>
<p>나는 처음 개발을 공부할 때 이 사전적 의미만 보고 헷갈렸었다.
비동기라는 네트워크요청이 3개가 있다고 치면, 실제로 네트워크창을 확인해보면 결과를 기다리지않고 순서대로 실행이 되는것을 확인했었었다. 왜? 동시에 시작하는거 아닌가? 라는 생각이었다.</p>
<blockquote>
<p>내가 잘못 알고있던건 전체 작업에 대해서만 생각한것...</p>
<p><strong>그렇다면 동기/비동기란?</strong>
한 작업에 대해 요청과 결과를 처리하고 넘어가느냐(동기),
요청만하고 결과가 나오기 전에 다른 작업으로 넘어갈 수 있다(비동기) 였다.</p>
</blockquote>
<p>(비동기란, 작업을 독립적으로 백그라운드에서 수행하고 작업이 완료되면 콜백, 프로미스, async/await 등을 사용하여 결과를 처리할 수 있다.
그렇기때문에 &quot;비동기 처리&quot; = &quot;동기처럼 결과까지 처리하고 다음 작업으로 넘어간다&quot;로 이해했다)</p>
<h3 id="그렇다면-블로킹-어떻게-해결할까">그렇다면, 블로킹 어떻게 해결할까?</h3>
<p>WebAPIs가 이러한 부분을 도와준다.
대표적으로 비동기 콜백이 있다.</p>
<p>비동기코드 실행 순서
<code>stack -&gt; webAPIs(진행중...) -(완료가 되면)-&gt; task queue</code></p>
<p>여기서 javascript만의 영역은 stack이다.</p>
<p>이 진행되는 과정을 지켜보고 있는데 이벤트루프(event loop)이다.
task queue에 위의 결과가 쌓여졌다면,stack이 비었는지를 관찰하다가 비었을 때 해당 queue를 stack으로 옮겨 쌓아준다.</p>
<p>js에서 동기/비동기 처리만 잘 해도 성능상 많은 개선을 할 수 있을 것 같다.</p>
<hr>
<h3 id="reference">reference</h3>
<p><a href="https://www.youtube.com/watch?v=8aGhZQkoFbQ">https://www.youtube.com/watch?v=8aGhZQkoFbQ</a></p>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] TextField 한글입력 문제]]></title>
            <link>https://velog.io/@sseung-i/flutter-TextField-%ED%95%9C%EA%B8%80%EC%9E%85%EB%A0%A5-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@sseung-i/flutter-TextField-%ED%95%9C%EA%B8%80%EC%9E%85%EB%A0%A5-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Tue, 20 Aug 2024 04:40:44 GMT</pubDate>
            <description><![CDATA[<p>맥에서 개발하고 윈도우로 빌드하며 체크해보는 중 많은 문제가 보였다.
그 중 한개인 TextField 한글입력 문제</p>
<p>맥에서는 한글 입력시 깔-끔 하게 된다.
<img src="https://velog.velcdn.com/images/sseung-i/post/46e3e6f0-c5c2-4a97-979a-99b7ed0efc1e/image.png" alt=""></p>
<p>근데.... 윈도우에서 빌드하면 따라란
자모음 분리만 되는것도아니고 완전 난리가 났다... 또르륵</p>
<p><img src="https://velog.velcdn.com/images/sseung-i/post/78d5aef1-28a2-403f-a462-b15e6468527b/image.gif" alt=""></p>
<p>textController.text를 찍어봐도 변화가 없기에
BlocConsumer의 Listener에서 textController.text에 BlocState값을 넣어주고,
TextField의 Controller에 textController를 넣고, onChange로 value를 BlocState에 담아주고.... 위젯이 리렌더링되며 위의 과정을 반복</p>
<p>이런 말도 안되는 코드를 작성해뒀던 것.</p>
<hr>
<p>찾아보며 정리해 보았다.</p>
<h3 id="1-컨트롤러만-사용">1. 컨트롤러만 사용</h3>
<p>위젯 업데이트는 되지 않아 화면에서 바로 보여줄 수는 없지만(물론 BlocConsumer의 listener를 사용해서 값을 업데이트 시켜주거나 하는 방식으로는 가능하겠지만..굳이 ?), 버튼을 누르거나 어딘가에서 해당 컨트롤러의 값을 가져다 쓸 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/sseung-i/post/5b71c4f2-6abd-4c44-9d43-c7ef3168d0c5/image.gif" alt=""></p>
<pre><code>late TextEditingController textController;

@override
void initState() {
    super.initState();
    textController = TextEditingController();
}

@verride
void dispose() {
    textController.dispose();
    super.dispose();
}

...![](https://velog.velcdn.com/images/sseung-i/post/befcc2ef-1fe5-4408-baaa-1ab3ab7fadc9/image.gif)


TextField(
    controller: textController,
    decoration: ...,
)

</code></pre><h3 id="2-컨트롤러-없이-onchange-state-사용">2. 컨트롤러 없이 onChange, state 사용</h3>
<p>위젯이 업데이트되어 변동사항을 바로바로 보여줄 수 있다.</p>
<p>유효성검사의 시점에 따라서 선택이 다를 수도 있을것 같고,
위젯이 계속 업데이트 될 필요의 유무에 따라 선택해서 사용하면 좋을 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/sseung-i/post/5b3d0e8d-969e-4d43-a36d-2b75e94dc0a4/image.gif" alt=""></p>
<pre><code>TextField(
    decoration: ...,
    onChanged: (value) {
        context.read&lt;SettingBloc&gt;().add(UpdateTextValue(value));
    }
)</code></pre><h3 id="3-blocconsumer-사용">3. BlocConsumer 사용</h3>
<blockquote>
<p>BlocConsumer: BlocListener와 BlocBuilder를 결합한 위젯으로, 상태 변화에 반응하여 UI를 빌드하고, 동시에 사이드 이펙트를 처리하는 데 사용.</p>
</blockquote>
<p>리셋을 하면 빈값을 controller에 넣어줘야하고(또는 controller.clear()), 해당 onChanged value를 BlocState에 저장(또는 state 사용 시점에 controller.text로 가져오기)해줘야 한다.</p>
<p>다른 셋팅 값들도 BlocState에 저장하기도 하고, controller를 다른 위젯에서 불러올 수 있도록 하는 로직은 어떻게 구성할지 고민좀 해봐야할 것 같아 우선 BlocState에 저장하는쪽으로 해주었다.</p>
<ul>
<li>리셋을 위해 현재 BlocState를 controller.text에 넣어줘야한다.</li>
<li>필드 value 변경 시 마다 BlocState에 담아 두어야 한다.</li>
</ul>
<pre><code>final TextEditingController _controller = TextEditingController();

...

    BlocConsumer&lt;SettingBloc, SettingState&gt;(
      listener: (context, state) {
        if (_controller.text != state.taxtValue) {
          _controller.text = state.taxtValue;
        }
      },
      builder: (context, settingState) {
      ...

          TextField(
            controller: _controller,
            onChanged: (value) {
              if (value != context.read&lt;SettingBloc&gt;().state.taxtValue) {
                context.read&lt;SettingBloc&gt;().add(UpdateTextValue(value));
              }
            },
          ),
      },
    );</code></pre><hr>
<h3 id="reference">reference</h3>
<p><a href="https://luvris2.tistory.com/804">https://luvris2.tistory.com/804</a></p>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Bloc패턴]]></title>
            <link>https://velog.io/@sseung-i/Flutter-Bloc%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@sseung-i/Flutter-Bloc%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Tue, 13 Aug 2024 01:02:48 GMT</pubDate>
            <description><![CDATA[<p>처음에 Bloc패턴은 State 관리라고 해서, 값 저장해두고 Provider를 사용해 전역에서 사용하여 state를 구독하고있다가 변경사항이 생기면 업데이트 해주는줄알았다.</p>
<p>전역사용과 더불어 클래스의 확장(extends)을 통해 state의 타입을 정의할 수 있어서, 현재 이게 어떤 상태의 값이냐! 에 따라서 뷰를 다르게 보여주거나 액션을 취할 수 있다.</p>
<hr>
<h2 id="bloc패턴-이게맞나">Bloc패턴, 이게맞나?</h2>
<h3 id="business-logic-component">Business Logic Component</h3>
<p>나는 blocs라는 폴더 내에, 담당하는 부분에 따라 나눠 주었다.</p>
<ul>
<li><strong>loading Bloc</strong>
로딩중에는 다른 액션이나 작업을 하지 못하도록 Stack과 AbsorbPointer를 사용한 로딩 레이어를 최상단에 씌워주는 형식으로 bloc을 사용해 전역에서 토글식으로 사용하였다.</li>
<li>embed
임베딩 내에서도 파일, 셋팅, 진행, 결과 등... 각각의 컴포넌트에서 필요한 부분만 가져다 쓰기 위하여 담당에 따라 분리해주었다. (다른 페이지에서의 공용 사용을 위한 확장을 위해서라도!)<ul>
<li><strong>file Bloc</strong></li>
<li><strong>setting Bloc</strong> : 유저의 입력을 받거나, 기존 정의된 값들을 json파일을 repository를 통해 초기값으로 가져온다.</li>
<li><strong>inProgress Bloc</strong>: 실행 중단시 데이터 동기화를 위해 필요했다.</li>
<li><strong>result Bloc</strong> : state가 initial인지 change 인지에 따라 결과창을 보여주도록 하였다. (작업하다 알게되었다보니 다른 부분도 이 형식으로 리팩토링중..)
.</li>
</ul>
</li>
<li><strong>page Bloc</strong>
원래는 결과페이지도 별도의 페이지이동 형식으로 작업하려 페이지index를 전역사용하려 하였으나, navigator에는 표시하고싶지 않아 각 페이지에서 레이어로 사용하였고 로그인페이지처럼 nav에는 없지만 추가 페이지가 생길 예정이라 전역으로 유지</li>
</ul>
<pre><code class="language-dart">BlocBuilder&lt;ResultBloc, ResultState&gt;(
      builder: (resultContext, resultState) {
      if (resultState is ResultChange) {
          return ...
      }
},)</code></pre>
<p>이게 효율적인 방법인지는 솔직히 플러터 입문한지 한달에 첫 프로젝트의 첫 Bloc패턴이라 이래저래 찾아가며 더 나은 플로우를 위하여 작업과 리팩토링을 동시에 하고있는 상황이다.</p>
<hr>
<h2 id="페이지에서-초기값-셋팅">페이지에서 초기값 셋팅</h2>
<p>각 페이지에 접근할 때 마다 모든 값을 초기화 해야한다.
초기화 되어야 하는 Bloc들은 provider를 각 페이지 내에서 사용하며, create 시 initial과 repository를 주입시켜 주어 초기값으로 유지할 수 있도록 해주었다.</p>
<pre><code class="language-flutter">MultiBlocProvider(
    providers: [
        BlocProvider&lt;FileBloc&gt;(
          create: (_) =&gt; FileBloc(FileRepository()),
          ... 다른 Bloc,
    ]
)</code></pre>
<p>BlocBuild는 쪼개놓은 컴포넌트 내에서 필요에 따라 불러서 사용하였다.</p>
<hr>
<h2 id="blocprovider로-감싸준-하위-위젯에서-blocstate-사용법">BlocProvider로 감싸준 하위 위젯에서 BlocState 사용법</h2>
<h3 id="blocbuilder">BlocBuilder</h3>
<p>state 변경에 따른 UI 업데이트가 필요할 때 사용</p>
<pre><code>BlocBuilder&lt;MyBloc,MyState&gt;(
    builder: (context, state) {
        ....
    }
)</code></pre><blockquote>
<p>state가 0이었는데 + 버튼을 눌러 state가 1이 되었다.
UI에서 1로 변경이 되어야 한다.</p>
</blockquote>
<h3 id="contextread">context.read</h3>
<p>이벤트를 가져오거나, state를 변경하지만 UI는 변경하지 않을 때</p>
<pre><code>final myState = context.read&lt;MyBloc&gt;().state;</code></pre><blockquote>
<p>실시간으로 변경되는 값을 감지하지는 못하지만, 값을 변경해두었다가 사용가능하다.
나의 경우는 탭을 선택하면 탭에 해당하는 값을 저장해두었다가, 해당 값을 사용해야 하는부분에서 불러서 사용했다.</p>
</blockquote>
<hr>
<p>이또한 나~중에보면 냄새나는 코드일지언정!
기능 한개씩 할때마다 쾌감있는 도장뽀개기 느낌이랄까...
얼마만에 느끼는것인지</p>
<p>기존 웹 작업하는것과 큰 차이가 없는듯 하면서도 성장 기회가 부족했던 찰나에 공부할게 많아서 다시 커브가 올라가는 느낌이 들어 지금은 나름 뿌듯하다.</p>
<hr>
<h3 id="reference">reference</h3>
<p><a href="https://gr-st-dev.tistory.com/2895#google_vignette">https://gr-st-dev.tistory.com/2895#google_vignette</a>
<a href="https://blog.arong.info/flutter/2023/01/10/Flutter-BLoC-%ED%8C%A8%ED%84%B4-1.html">https://blog.arong.info/flutter/2023/01/10/Flutter-BLoC-%ED%8C%A8%ED%84%B4-1.html</a></p>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] widget, state, variable]]></title>
            <link>https://velog.io/@sseung-i/Flutter-widget-state</link>
            <guid>https://velog.io/@sseung-i/Flutter-widget-state</guid>
            <pubDate>Thu, 11 Jul 2024 02:03:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>자주쓰며 기본이 되는 내용 정리 페이지</strong></p>
</blockquote>
<p>프로젝트의 시작은 lib/main.dart로 시작한다.
초기 build까지의 필수 공통사항은</p>
<pre><code class="language-dart">import &#39;package:flutter/material.dart&#39;;

void main() {
  runApp(
    const MaterialApp(),
  );
}
</code></pre>
<p>build(){ } : 위젯 또는 중첩된 위젯 트리를 반환해야 한다.
MaterialApp() : 생성자 함수이며 위젯이다.</p>
<h2 id="widget">Widget</h2>
<p>뷰를 그릴때 필요한 단위</p>
<blockquote>
<ul>
<li>우리가 사용하는건 생성자 함수이며 class에 기반해 개체를 생성한다. 그렇기에 위젯도 class다.
그래서 우리가 컴포넌트 분리로 직접 만드는것도 위젯이며 class인것이다.</li>
</ul>
</blockquote>
<ul>
<li><p>앱 자체도위젯이고 컴포넌트도 위젯이라 불릴 수 있고
눈에보이지 않는 padding이나 column, center 등과 같은 Flutter 앱을 빌드할 때 사용되는 요소.</p>
</li>
<li><p>LayoutBuilder(builder: (context, constraints) {})</p>
<ul>
<li>builder콜백은 제약 조건이 변경될 때마다 호출 (ex. 앱의 창 크기 조절, 세로모드에서 가로모드로 회전 등), 반응형!</li>
<li>constraints : <code>BoxConstraints(w=866.0, h=494.0)</code> 컨테이너에 대한 값을 가지고 있기 때문에 <code>constraints.maxWidth &gt;= 600</code> 이런식으로 사용 가능</li>
</ul>
</li>
<li><p>scaffold() : AppBar, 기본 배너, 배경색을 제공 등 기본 구조를 위한 클랙스 API 제공하는 유용한 위젯</p>
</li>
<li><p>safeArea() : child에 들어가는 위젯을 하드웨어 노치나 상태 표시줄로 가려지지 않도록 해줌</p>
</li>
<li><p>NavigationRail() : 메뉴바를 쉽게 만들 수 있다.</p>
<ul>
<li>extended: boolean으로 메뉴의 label을 보여주었다 말았다를 정할 수 있다.</li>
<li>selectedIndex: 현재 선택된 메뉴의 Index</li>
<li>onDestinationSelected: 메뉴 클릭 시 처리할 콜백, 인자로 index를 준다.</li>
</ul>
</li>
<li><p>NavigationRailDestination() : 메뉴바의 메뉴 한개에 대한 요소라고 생각하면 될듯. icon과 label로 구성</p>
</li>
<li><p>Expanded() : css의 Flex-grow 느낌으로 (페이지 전체 - 지정된 영역) 후 나머지 남는 영역을 꽉채워서 표현 가능</p>
</li>
<li><p>Placeholder() : 영역 자리잡기 확인용으로 임시로 사용</p>
</li>
<li><p>button : 잘 쓰는 버튼 3가지만 정리 해보면</p>
<ul>
<li>ElevatedButton() : 클릭시 버튼 내에 그림자같은 효과가 적용</li>
</ul>
</li>
</ul>
<h3 id="class위젯-생성-시-참고">class(위젯) 생성 시 참고</h3>
<ul>
<li><p>key 설정 두가지 방법</p>
<pre><code class="language-dart">class GradientContiner extends StatelessWidget{
  // 풀어쓰기 : 생성자함수 뒤에 콜론(:)을 추가하고 그 후에 변수 초기화를 할 수 있다.
  // super()키워드를 통해 상속받은 부모 클래스(StatelessWidget)에 접근할 수 있다.
  // 부모의 key를 받아서 넣어준다.
  const GradientContainer({key}): super(key: key);

  // 간단하게 : 위와 동일한 내용을 다트에서 간단하게 사용할 수 있도록 도와준다.
  const GradientConntainer({super.key})
...
}</code></pre>
</li>
</ul>
<h3 id="위젯-속성">위젯 속성</h3>
<ul>
<li>mainAxisSize : row/column 같이 레이아웃 위젯의 범위(?)</li>
<li>gradirent : 그라데이션 컬러와 시작과 끝점을 정할 수 있다<pre><code class="language-dart"> gradient: LinearGradient(
    colors: [
        Color.fromARGB(255, 48, 79, 255),
        Color.fromARGB(255, 34, 255, 233)
    ],
    begin: Alignment.topLeft,
    end: Alignment.bottomRight,
),</code></pre>
</li>
</ul>
<h2 id="statelesswidget">StatelessWidget</h2>
<p>상태가 없는 위젯</p>
<ul>
<li>레이아웃처럼 어떠한 공유 값(context state)에 의해서 변경되거나 하는 뷰가 아닌, 고정된 뷰일때 사용</li>
</ul>
<h2 id="statefulwidget">StatefulWidget</h2>
<p>상태가 있는 위젯</p>
<ul>
<li>setState() : ChangeNotifier의 notifyListeners()와 같은 역할을 한다. 상태를 변경하여 UI를 업데이트</li>
</ul>
<h3 id="changenotifier">ChangeNotifier</h3>
<p>상태관리를 위해 사용하는 클래스</p>
<ul>
<li><p>notifyListeners() : ChangeNotifier 클래스의 메서드이며, 상태가 변경될 때 리스너들에게 변경 사항을 알리기 위해 사용(UI를 업데이트)</p>
<pre><code class="language-dart">class Counter with ChangeNotifier {
int _count = 0;

int get count =&gt; _count;

void increment() {
  _count++;
  notifyListeners();
}
}
// ChangeNotifier를 상속받아 count 상태를 관리</code></pre>
<blockquote>
<p>Dart에서는 변수나 메서드 이름 앞에 언더바(_)를 붙이는 것만으로도 해당 변수나 메서드가 프라이빗(private)으로 처리된다.
위의 예시로 보면 _count는 클래스 외부에서 직접 접근 불가하지만, getter나 메서드를 통해서 _count를 가져오거나 변경할 수 있다.</p>
</blockquote>
</li>
</ul>
<h2 id="변수">변수</h2>
<ul>
<li>var : 초기 값 지정 이후에 수정될 수 있을 경우</li>
<li>final : 초기 값 지정 이후에 값 수정 불가능,  런 타임에서 상수를 정의</li>
<li>const : 초기 값 지정 이후에 값 수정 불가능, 컴파일 타임에서 상수를 정의(상수변수나, 상수 데이터를 넣을 때 사용 가능)</li>
</ul>
<blockquote>
<p>final과 const의 차이점이라고 하면, 언제 정의를 하느냐인데 그렇다면 어떨때 사용..? 최 적 화 !</p>
</blockquote>
<ul>
<li>const의 경우 특정 화면이 리렌더링 된다고 했을 경우 항상 같은 내용을 표시 (리스트 추가 불가능)</li>
<li>final의 경우 특정 화면이 리렌더링 된다고 했을 경우 현재의 내용을 표시 (리스트의 경우 추가 가능)</li>
</ul>
<p>const로 지정된 위젯 안에서는 const변수를 사용할 수 있다.
이미 위젯이 고정일거라고 명시되어있기때문에.</p>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 윈도우 앱을위한 공부 시작]]></title>
            <link>https://velog.io/@sseung-i/Flutter</link>
            <guid>https://velog.io/@sseung-i/Flutter</guid>
            <pubDate>Mon, 08 Jul 2024 06:48:06 GMT</pubDate>
            <description><![CDATA[<p>회사에서 윈도우앱 겸 앱을 위해 flutter 해볼 의향을 물어봐,
겸사겸사 시작하게된 flutter 공부 기록</p>
<hr>
<p>flutter는 Dart라는 언어를 사용한다.</p>
<h3 id="타입이-중요한-언어">타입이 중요한 언어</h3>
<p>변수 지정 시 타입을 먼저 적고, 변수명 작성
<code>int price = 3000;</code></p>
<h3 id="클래스">클래스</h3>
<p>객체(클래스) = 인스턴스 = 확장 가능한 기본값이라고 생각하자
내부에 어떤 변수들을 가질지 타입&amp;변수명(멤버변수), 메서드 지정.
클래스는 반드시 생성자를 가져야하며, 생성자를 통해서 인스턴스를 만들어낼 수 있다.</p>
<p>또한 생성자 메서드 내에서, 값을 할당 받을 멤버변수앞에는 this라는 키워드를 붙여준다
-&gt; <strong>&quot;지금 내가(생성자 메서드) 들어있는 클래스의 이 멤버변수 한테 값을 할당해줘!&quot;</strong></p>
<pre><code class="language-dart">class A{
    int? price;
    ...
    CreateInstance(int price) {
        this.price = price
    }
}</code></pre>
<p>그치만 위처럼 쓰는것 말고 이렇게 쓰는것을 권장한다고 한다.</p>
<pre><code>class A{
    int? price;
    ...
    CreateInstance(this.price)
}</code></pre><h3 id="생성자">생성자</h3>
<p>인스턴스 초기화 메소드
인스턴스에 원하는 특징과 개성을 부여하기 위한 것
인스턴스를 생성할 때 인스턴스 내의 멤버변수에 대한 값은 생성자의 인수(argument)로 할당 가능하다.</p>
<p>직접 생성자를 만들어주지 않으면, 다트가 알아서 생성자를 클래스에 추가해준다.(기본옵션이라 눈에 보이진 않지만 사용 가능함)</p>
<p>해당 생성자 메서드를 호출하며 인스턴스를 생성할 수 있다.</p>
<h3 id="널-세이프티null-safety">널 세이프티(null safety)</h3>
<p>만약 인스턴스 내에 변수들이 고정값으로 되어있다면?
생성할때마다 해당 값으로 고정된다.
그렇기에 초기 인스턴스 내의 변수에 값을 할당하지 않고, 생성자를 통해 인스턴스가 만들어질 때 할당해주자.
그래서 생긴것이 널 세이프티(null safety)</p>
<p>널세이프티가 생기기 전에는 비어있는 변수지정이 가능했다.(ex. <code>int number;</code>)
but, &quot;요즘은 이렇게 작성하면 오류나요!&quot;</p>
<p>클래스 내에서 생성된 변수에는 값이 필요하니 변수를 지정해주려 했던것처럼, 나중에 값을 미입력하는 오류방지를 위해 생겨났다.
그렇다고 초기에 임의의값을 넣어주는것도... 재할당 안하고 넘어가거나 할 수도 있지 않을까</p>
<p>그래서 없을수도 있다고 널 세이프티를 사용한다.
<code>int price = 3000</code> -&gt; <code>int? price;</code>
(에러메세지 사라짐~)</p>
<p><strong>고정값이 아닌, 인스턴스 생성할때마다 할당해줘야하는 멤버변수들은 널 세이프티 사용하기</strong></p>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[nextjs14의 서버와 클라이언트 컴포넌트]]></title>
            <link>https://velog.io/@sseung-i/nextjs14%EC%9D%98-%EC%84%9C%EB%B2%84%EC%99%80-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@sseung-i/nextjs14%EC%9D%98-%EC%84%9C%EB%B2%84%EC%99%80-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Fri, 05 Jul 2024 04:44:21 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>next14에서 로직을 분리하면서 혼돈을 겪고있는 상황에 머릿속 정리겸 공부
내 언어로 풀어서 기록하며 기본부터 정리해보자...
(틀린건 댓글로 알려주시면 감사하겠습니다)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sseung-i/post/8aac8f15-b34f-4819-aa17-17eb09317771/image.png" alt=""></p>
<h2 id="클라이언트-컴포넌트"><strong>클라이언트 컴포넌트</strong></h2>
<ul>
<li>&quot;use client&quot; 명시해준 컴포넌트</li>
<li>뷰를 그리고 데이터 호출을 통해 추가 뷰를 그려야 한다면, 네트워크 워터폴이 생김으로 컴포넌트의 응답 지연되어 사용자 경험이 저하됨
<img src="https://velog.velcdn.com/images/sseung-i/post/0b0087bc-babe-455e-a3f9-170dd2effea1/image.png" alt=""></li>
<li><strong>정말 클라이언트</strong>에서만 실행을 하고 싶다면??
클라이언트컴포넌트를 import할 때 dynamic import를 사용하며, ssr을 false로 꺼준다.
그럼 pre-rendering을 비활성화 할 수 있다.
<code>const ComponentC = dynamic(() =&gt; import(&#39;../components/C&#39;), { ssr: false })</code></li>
</ul>
<h2 id="서버-컴포넌트"><strong>서버 컴포넌트</strong></h2>
<ul>
<li>page.tsx 또는 일반 컴포넌트 생성시 기본이 서버컴포넌트임(위처럼 클라이언트 명시해주거나 클라이언트 컴포넌트 내부에 위치하지 않는 다면)</li>
<li>자동 코드 분할을 지원하여 번들 크기를 줄여 앱의 성능을 향상시킴</li>
<li>서버에서 뷰 생성과 백엔드 접근이 가능해 데이터 호출을 한번에 해서 클라이언트로 넘겨주기 때문에(=SSR과 함께) 네트워크 워터폴을 피할 수 있다.</li>
<li><blockquote>
<p>서버에서는 모양(HTML)만 만들어 보내주고(=pre-rendering), 상호작용을 할 수 있도록 영혼을 불어넣어준다.(=hydration)
<img src="https://velog.velcdn.com/images/sseung-i/post/8420ff94-4c22-4559-a43c-25d333d45e02/image.png" alt=""></p>
</blockquote>
</li>
</ul>
<blockquote>
<p>pre-rendering : 사전렌더링
SSR : 서버에서 HTML을 클라이언트로 보내고, 이후에 클라이언트사이드 자바스크립트를 다운로드하여 상호작용 할 수 있도록 hydration 시켜주는 것 (초기 페이지를 빠르게 로드하는데 유용하게 해줌)</p>
<p>RSC(서버컴포넌트)와 SSR 둘다 서버라는 말을 사용하기때문에 같다고 생각할 수 있지만, &quot;서버에서 동작한다&quot; 는 것만 같고 역할이나 행동이 다름. (<a href="https://velog.io/@2ast/React-%EC%84%9C%EB%B2%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8React-Server-Component%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0">참고글</a>에 자세한 설명있기에 여기선 생략..)</p>
</blockquote>
<hr>
<h2 id="어떻게-나누는가">어떻게 나누는가?</h2>
<p>hooks나 onClick과 같은 이벤트를 사용 할 경우(=hydration을 해 줄 것들) 클라이언트 컴포넌트를 분리해주어 서버와 클라이언트 컴포넌트를 나눠줄 수 있다.</p>
<p>⭕️ <strong>서버컴포넌트 내에 클라이언트컴포넌트를 사용할 수 있다.</strong></p>
<pre><code class="language-jsx">const ServerCom = () =&gt; {
  // 여기서 서버 로직 사용 가능

    return &lt;ClientCom /&gt;
}</code></pre>
<p>🔺 <strong>클라이언트컴포넌트 내에 서버컴포넌트를 사용할 수 있다.</strong>
-&gt; 하지만 그 서버 컴포넌트는 클라이언트컴포넌트로 동작한다.</p>
<pre><code class="language-jsx">const ClientCom = () =&gt; {
  const [val, setVal] = useState(&quot;&quot;);

  const handleConfirm = () =&gt; {
    setVal(&quot;textttt&quot;)
  }
    return &lt;&gt;
          &lt;ServerCom val={val}/&gt;
          &lt;button onClick={handleConfirm}&gt;확인&lt;/button&gt;
      &lt;/&gt;
}</code></pre>
<p>⭕️ <strong>서버컴포넌트를 클라이언트컴포넌트로 감싸서 사용할 수 있다.</strong>
-&gt; 부모(page)가 렌더링 되는 시점에 서버컴포넌트가 렌더링 됨으로 서버와 클라이언트 컴포넌트 둘다 의도했던대로 작동한다.</p>
<pre><code class="language-jsx">const Page = () =&gt; {

  const handleConfirm = () =&gt; {
    setVal(&quot;textttt&quot;)
  }
    return &lt;&gt;
          &lt;ClientCom&gt;
          &lt;ServerCom /&gt;
          &lt;div&gt;page.tsx는 서버에용&lt;div&gt;
          &lt;/ClientCom&gt;
      &lt;/&gt;
}</code></pre>
<p>⭕️ <strong>클라이언트컴포넌트에서 server action을 사용 할 수 있다.</strong></p>
<pre><code class="language-js">// ex) action.ts (server action 파일 분리)
&quot;use server&quot;

export const handleSubmit = () =&gt; {
  // 뭐시기뭐시기
}</code></pre>
<pre><code class="language-jsx">import { handleSubmit } from &quot;./action.ts&quot;

const ClientCom = () =&gt; {

    return &lt;&gt;
            &lt;form action={handleSubmit}&gt;
              &lt;input name=&quot;id&quot; /&gt;
              &lt;input name=&quot;pw&quot;/&gt;
              &lt;button type=&quot;submit&quot;&gt;확인&lt;/button&gt;
            &lt;/form&gt;
      &lt;/&gt;
}</code></pre>
<hr>
<p>[reference]
<a href="https://velog.io/@ssonnni/Next.js14-03.Hydration">https://velog.io/@ssonnni/Next.js14-03.Hydration</a>
<a href="https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading#nextdynamic">https://nextjs.org/docs/app/building-your-application/optimizing/lazy-loading#nextdynamic</a>
<a href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#pending-states">https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#pending-states</a>
<a href="https://www.freecodecamp.org/korean/news/how-to-use-react-server-components/">https://www.freecodecamp.org/korean/news/how-to-use-react-server-components/</a>
<a href="https://velog.io/@2ast/React-%EC%84%9C%EB%B2%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8React-Server-Component%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0">https://velog.io/@2ast/React-%EC%84%9C%EB%B2%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8React-Server-Component%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[./gradlew build 
 JAVA_HOME 변수 경로설정 에러]]></title>
            <link>https://velog.io/@sseung-i/.gradlew-build-JAVAHOME-%EB%B3%80%EC%88%98-%EA%B2%BD%EB%A1%9C%EC%84%A4%EC%A0%95-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@sseung-i/.gradlew-build-JAVAHOME-%EB%B3%80%EC%88%98-%EA%B2%BD%EB%A1%9C%EC%84%A4%EC%A0%95-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Mon, 17 Jun 2024 04:55:31 GMT</pubDate>
            <description><![CDATA[<p>일을 할 때에도 백엔드 알아두면 좋을 것 같아 생각만 해오다 최근에 혼자 헤드리스CMS를 사용해 강의없이 쌩으로 만들어보며 todo list작업해보니, 기초부터 백엔드를 알아두면 좋을 것 같아서 spring 강의를 듣기 시작.</p>
<p>BUT.... 맥북으로 JAVA 17 설치하고 빌드해보는 초반부터 난관에 부딪혔다 🥲</p>
<pre><code>ERROR: JAVA_HOME is set to an invalid directory: /opt/homebrew/Cellar/openjdk@17/17.0.3/libexec/openjdk.jdk/Contents/Home

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation.</code></pre><p>전에 다른 버전으로 설치를 시도하다가 흐지부지 했었던 적이 있어서, 경로가 꼬여있었던 것 같다.</p>
<h2 id="검색-할-때에도-내가-사용하는-쉘-버전-경로-등등을-잘-체크하자">검색 할 때에도 내가 사용하는 쉘, 버전, 경로 등등을 잘 체크하자!</h2>
<p><strong>내 시스템에 설치된 자바 버전 확인</strong>
<code>java -version</code> 또는 <code>javac -version</code>
-&gt; javac 17.0.11로 나왔다</p>
<p><strong>JAVA_HOME 변수 확인</strong>
<code>echo $JAVA_HOME</code>
-&gt; <code>/opt/homebrew/Cellar/openjdk@17/17.0.3/libexec/openjdk.jdk/Contents/Home</code> 이렇게 버전이 다르게 나오는것을보니 잘못 되어있다는것</p>
<p><strong>맞는 경로인지를 확인하고 싶을 땐?</strong>
경로이동을 통해서 가넝~
<code>ls 위의 경로</code> -&gt; 역시나 안된다</p>
<p><strong>Java 설치 경로 확인 방법</strong>
<code>ls /opt/homebrew/Cellar/openjdk*</code> -&gt; <code>17.0.11</code> 내 버전이 뜬다. 해당 경로에 잘 깔려있다는거겠지</p>
<h3 id="이젠-수정할-차례">이젠 수정할 차례</h3>
<p><strong>쉘에 따라 파일 수정</strong></p>
<ol>
<li>수정</li>
</ol>
<ul>
<li>bash : <code>vi ~/.bash_profile</code></li>
<li>zsh : <code>vi ~/.zshrc</code><pre><code>export JAVA_HOME=/opt/homebrew/Cellar/openjdk/17.0.3/libexec/openjdk.jdk/Contents/Home
export PATH=$JAVA_HOME/bin:$PATH</code></pre></li>
</ul>
<ol start="2">
<li>적용</li>
</ol>
<ul>
<li>bash : <code>source ~/.bash_profile</code></li>
<li>zsh : <code>source ~/.zshrc</code></li>
</ul>
<ol start="3">
<li>확인
<code>echo $JAVA_HOME</code> -&gt; <code>/opt/homebrew/Cellar/openjdk@17/17.0.11/libexec/openjdk.jdk/Contents/Home</code></li>
</ol>
<p>버전 동일하게 적용 되었다!!</p>
<p>vscode를 완전히 종료 후 다시 켜주고, 프로젝트에서 <code>echo $JAVA_HOME</code>으로 한번 더 확인해줬을 때 정상적으로 나오면 <code>./gradlew build</code> 가능~</p>
<hr>
<p>[reference]</p>
<p><a href="https://hoft.tistory.com/entry/Mac-java%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0-open-jdk-17">java설치하기 Open jdk 17</a></p>
<hr>
<pre><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[nextauth (client_fetch_error)]]></title>
            <link>https://velog.io/@sseung-i/nextauth-clientfetcherror</link>
            <guid>https://velog.io/@sseung-i/nextauth-clientfetcherror</guid>
            <pubDate>Thu, 09 Nov 2023 08:50:43 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@sseung-i/next-i18n-ssr%EC%97%90%EC%84%9C-%EC%A0%81%EC%9A%A9-%EB%AC%B8%EC%A0%9C">전글</a>에서 썼던 이슈 중 두번째 이슈인 nextauth 해결하기</p>
<p>로컬, netlify배포시 둘다 잘 되었던 로그인이 server error를 뱉는다...
로컬에서도 NEXTAUTH_URL 설정으로인해 <a href="https://velog.io/@sseung-i/next-auth-error-CLIENTFETCHERROR">글을 쓴 적</a>이 있었는데, 또 얘가 말썽일줄이야</p>
<p>nextauth 관련해서는 환경 변수에 <strong>NEXTAUTH_URL</strong>과 <strong>NEXTAUTH_SECRET</strong>을 사용했었다.</p>
<pre><code class="language-ts">// [...nextauth].ts
export const authOptions:NextAuthOptions = {
  ...
  secret: process.env.NEXTAUTH_SECRET
}</code></pre>
<p>무슨 설정이 다르기에 안되는 것인가!!!!</p>
<p>검색 중 두가지를 찾게되었다.</p>
<ol>
<li><a href="https://stackoverflow.com/questions/73983022/nextauth-deployed-on-aws-amplify-next-autherrorclient-fetch-error">빌드 설정을 바꾼다</a><pre><code> build:
     commands:
         - echo &quot;NEXTAUTH_URL=$NEXTAUTH_URL&quot; &gt;&gt; .env
         - npm run build</code></pre>echo 부분을 추가 : 빌드 중에 .env(환경변수)에 액세스 하여 NEXTAUTH_URL을 사용한다는 것 같다.</li>
</ol>
<p>그런데도 안된다고 ?!  그래서 찾은</p>
<ol start="2">
<li><a href="https://velog.io/@seunghwa17/next-auth-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0">cloudWatch 확인하자!!!</a> &amp; <code>NEXT_PUBLIC_</code></li>
</ol>
<ul>
<li>기능을 제공을 해줘도 쓰지못하는 나...
이 글을 보자마자 아차! 싶어서 들어가서 확인하였더니 역시나역시나
<img src="https://velog.velcdn.com/images/sseung-i/post/60bfaf76-173a-4613-a15a-78353abf50db/image.png" alt=""></li>
</ul>
<ul>
<li>근데 왜 <code>[next-auth][error][NO_SECRET]</code> ????
&quot;NEXTAUTH_SECRET&quot;이라고 쓰지 않는 글을 발견
<a href="https://stackoverflow.com/questions/71600978/missingsecret-missingsecreterror-please-define-a-secret-in-production">https://stackoverflow.com/questions/71600978/missingsecret-missingsecreterror-please-define-a-secret-in-production</a>
설마설마하며 <a href="https://github.com/nextauthjs/next-auth/issues/3245#issuecomment-1027242031">관련이슈</a>를 찾아보니 정답이었다.</li>
</ul>
<p><code>NEXTAUTH_URL</code>은 그냥 써도되지만 <code>NEXTAUTH_SECRET</code>은 <code>NEXT_PUBLIC_</code> 을 붙여줘야 했다 ....</p>
<hr>
<p>확실히 빨라진 속도!!</p>
<p>동일한 플로우 그대로 perfomance 측정..</p>
<ul>
<li><p>netlify : 이미지 느려 터짐
<img src="https://velog.velcdn.com/images/sseung-i/post/3a8ed0c4-852f-4375-9424-b958412629f8/image.png" alt=""></p>
</li>
<li><p>amplify : 빠르다 빨라
<img src="https://velog.velcdn.com/images/sseung-i/post/074ace05-a614-4e4e-b5e0-ae60ea41e3b3/image.png" alt=""></p>
</li>
</ul>
<p>이젠 amplify 배포로 사용할 수 있을지?! 는
일단, amplify배포 사이트에서 QA하다 이슈가 없길 기도 할 수밖에 🥹</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[next-i18n ssr에서 적용 문제]]></title>
            <link>https://velog.io/@sseung-i/next-i18n-ssr%EC%97%90%EC%84%9C-%EC%A0%81%EC%9A%A9-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@sseung-i/next-i18n-ssr%EC%97%90%EC%84%9C-%EC%A0%81%EC%9A%A9-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Thu, 09 Nov 2023 06:01:17 GMT</pubDate>
            <description><![CDATA[<p>로컬과 netlify를 사용했을땐 잘 작동 되었었는데, amplify로 옮기면서 두가지 이슈가 생겼다.</p>
<ol>
<li><p>i18n이 깨진다</p>
<div>
<img src="https://velog.velcdn.com/images/sseung-i/post/1840aed4-2a5b-42ed-84ee-356e3f7c1d1b/image.png"/>
</div>
</li>
<li><p>next-auth로 소셜로그인 진행 시 Server error....</p>
<div>
<img src="https://velog.velcdn.com/images/sseung-i/post/861b67fd-b2f2-4d6b-acae-0b6f9ce4fe63/image.png"/>
</div>

</li>
</ol>
<p>이미지 뜨는게 느려서 옮길려했다가 ssr의 문제들로 고통을 받을줄이야... 
당장 QA돌려야하니 netlify로 돌아가고 amplify dev에서 이슈해결 시작</p>
<p>next12에서 i18n을 적용 하던 중 ssr페이지 이동시에만 t()내부에 써주는 값이 그대로 노출되는 이슈가 생겼다.</p>
<p>이슈들과 구글링을 통해 이것저것 적용 해 봤지만 해결이 되지않던 중,
빛한줄기와 같은 글을 발견</p>
<p><a href="https://github.com/i18next/next-i18next/issues/2192">https://github.com/i18next/next-i18next/issues/2192</a></p>
<p>문제상황 캡쳐까지 올려줘서 나랑 동일한 문제임을 확신하였고, 글의 링크를 타고 타고 들어가서 해결책 이슈를 찾았다!</p>
<p><a href="https://github.com/i18next/next-i18next/issues/1552">https://github.com/i18next/next-i18next/issues/1552</a></p>
<pre><code class="language-js">const path = require(&quot;path&quot;);

module.exports = {
  i18n :{
    locales: [&quot;ko&quot;,&quot;en&quot;],
    ...
  },
  localePath: path.resolve(&quot;./public/locales&quot;),
}</code></pre>
<h3 id="localepath--이것을-추가함으로-해결">localePath !!!! 이것을 추가함으로 해결</h3>
<p>ssr 페이지의 푸터도 깨지지 않고 잘~ 나온닷</p>
<div>
<img src="https://velog.velcdn.com/images/sseung-i/post/70cd9279-a0de-4841-898b-f2172a07f63a/image.png"/>
  </div>

<p>ssr, 뭔가 까다롭고 신경써줘야할게 많은 녀석같다는걸 다시 느끼고
nextauth 해결하러 출발...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[query사용 뒤로가기 시 모달 닫기]]></title>
            <link>https://velog.io/@sseung-i/query%EC%82%AC%EC%9A%A9-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0-%EC%8B%9C-%EB%AA%A8%EB%8B%AC-%EB%8B%AB%EA%B8%B0</link>
            <guid>https://velog.io/@sseung-i/query%EC%82%AC%EC%9A%A9-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0-%EC%8B%9C-%EB%AA%A8%EB%8B%AC-%EB%8B%AB%EA%B8%B0</guid>
            <pubDate>Mon, 12 Jun 2023 16:22:18 GMT</pubDate>
            <description><![CDATA[<p><strong>Next13에서 작업하며 고민한 흔적..(next가 아니어도 활용 가능)</strong></p>
<hr>
<h3 id="페이지컴포넌트-내에서-모달-열-때">페이지컴포넌트 내에서 모달 열 때</h3>
<p>그동안 모달을 작업 할 때에는 페이지 내에서 모달을 열고 닫고 할 일만 있었다.</p>
<ul>
<li>버튼을 누르면 열리고 dimm 또는 닫기 버튼을 두르면 닫힌다.</li>
<li>해당 페이지 내에서 모달컴포넌트가 동작하니 페이지 이동을하면 닫히는것처럼 보인다.</li>
<li><blockquote>
<p>즉, 모달이 열려있는 상태에서 뒤로가기를 할 경우 모달이 닫히기만 하는게 아닌 페이지가 뒤로 가지는 것
(모바일에서는 닫고싶거나 할 때 뒤로가기를 자주 누르는데, 나는 그저 팝업이 닫히길 바랬는데 페이지가 뒤로가지면 불편)</p>
</blockquote>
</li>
</ul>
<p>이번 작업에선 로그인을 공통헤더에서 모달형식으로 띄워주기를 하면서 어떻게 구현을 할지 고민이 많았다.</p>
<hr>
<h2 id="next13의-기능-사용">next13의 기능 사용?</h2>
<p>next13에서 제공하고있는 <a href="https://nextjs.org/docs/app/building-your-application/routing/parallel-routes">Parallel Routes</a>와 <a href="intercepting-routes">Intercepting Routes</a>를 사용하면 페이지이동처럼 히스토리에 &#39;/login&#39; 패스 추가하여 사용할 수 있다고 하여 작업을 해 보았다.</p>
<p>그 결과.. 홈(패스 = &#39;/&#39;)에서만 보여지는 header스타일이 있는데 패스가 &#39;/&#39;에서 &#39;/login&#39; 으로 바뀌다 보니 서브 header스타일로 적용이 되면서 홈에서 모달이 열리고 닫힐 때 뒤에 보이는 페이지 레이아웃이 무너지게 되었다.</p>
<p>어느 페이지에서나 모달을 열 수 있는것이기 때문에 패스로 처리하는건 적합하지 않다고 생각하여 다른방법을 찾아보았다.</p>
<h4 id="그렇담-어떻게-제어-할것인가">그렇담... 어떻게 제어 할것인가?</h4>
<hr>
<h2 id="뒤로가기-이벤트-제어">뒤로가기 이벤트 제어</h2>
<ul>
<li>모달이 열릴 때 현재페이지 히스토리를 push()</li>
<li>모달 내부에 뒤로가기 이벤트에서 원래 동작인 back()과 모달 닫히도록 setState변경</li>
</ul>
<p>혹시나 헤더에서 로그인버튼 클릭이 아닌 다른 경로로 로그인을 띄워야 할 경우가 있다면, 현재 헤더컴포넌트 안에 있는 로그인 모달을 제어하기위해 상태관리를 사용해야 하거나 또다른 처리를 해줘야 할 것 같았다.</p>
<p>그렇지만 쿼리를 사용한다면 어디서든 모달쿼리값을 push()해주면 헤더에서 바뀐 쿼리에 대한 처리를 해서 모달을 띄워 주지 않을까?</p>
<hr>
<h2 id="✅-쿼리-값으로-모달-컨트롤">✅ 쿼리 값으로 모달 컨트롤</h2>
<p>&#39;modal&#39;이란 쿼리 키로 모달을 제어 하였고, 쿼리의 유무로 열고닫기만 하는 것이 아닌 특정 모달이 열릴 수 있도록 해주었다.
(로그인 모달 내에서 회원가입 / 비번찾기 버튼을 누르면 해당 모달을 보여줘야했다.)</p>
<h3 id="nextnavigation">next/navigation</h3>
<pre><code class="language-js">import { useRouter, useSearchParams, usePathname } from &quot;next/navigation&quot;;

const router = useRouter();
const searchParams = useSearchParams();
const pathname = usePathname();</code></pre>
<ul>
<li>router : 히스토리 조작</li>
<li>searchParams : 쿼리 확인</li>
<li>pathname : 현재 페이지(path)에 쿼리를 덮어 쓰기 위해<br />

</li>
</ul>
<h3 id="핸들러-3개">핸들러 3개</h3>
<p>히스토리를 조작한다.</p>
<h4 id="1-히스토리를-쌓는-핸들러-처음-모달-열때-사용">1. 히스토리를 쌓는 핸들러 (처음 모달 열때 사용)</h4>
<pre><code class="language-js">  const handleOpenMadal = (name: string) =&gt; {
      router.push(`${pathname}?modal=${name}`);
  };</code></pre>
<p>  혹시나 나중에 재사용 하거나 모달내에서 다른 모달을 열었을 경우 순차적으로 뒤로 가야한다면 히스토리를 쌓아야 하기때문에 name을 받아 사용했다.
<br /></p>
<h4 id="2-닫기-또는-dimm-클릭-시-뒤로가기">2. 닫기 또는 dimm 클릭 시 뒤로가기</h4>
<pre><code class="language-js">  const handleCloseMadal = () =&gt; {
      router.back();
  };</code></pre>
<br />

<h4 id="3-히스토리-쌓는게-아닌-바꿔치기-모달-내에서-다른-모달로-교체할때-사용">3. 히스토리 쌓는게 아닌 바꿔치기 (모달 내에서 다른 모달로 교체할때 사용)</h4>
<pre><code class="language-js">  const handleChangeModal = (name: string) =&gt; {
      if (pathname === null) return;
      router.replace(`${pathname}?modal=${name}`);
  };</code></pre>
<p>  로그인 &gt; 회원가입 &gt; 로그인 왔다갔다 해도 뒤로가기 한번이면 모달 닫힘</p>
<h3 id="searchparams">searchParams</h3>
<p>현재 히스토리의 쿼리에 대한 모달상태를 보여준다.</p>
<pre><code class="language-js">const isModal = searchParams?.get(&quot;modal&quot;);

return (
  // ...
  {isModal &amp;&amp; (
   &lt;Modal&gt;
          {isModal === &quot;로그인&quot; &amp;&amp; &lt;로그인 /&gt;}
          {isModal === &quot;회원가입&quot; &amp;&amp; &lt;회원가입 /&gt;}
        {isModal === &quot;비번찾기&quot; &amp;&amp; &lt;비번찾기 /&gt;}
      &lt;/Modal&gt;
   )}
)</code></pre>
<p>isModal : &#39;modal&#39;이라는 쿼리키에대한 유무와 값
여기서 isModal의 타입은 <code>string | null | undefined</code>이 된다.</p>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[next13] Dynamic Routes의 Params]]></title>
            <link>https://velog.io/@sseung-i/next13-Dynamic-Routes%EC%9D%98-Params</link>
            <guid>https://velog.io/@sseung-i/next13-Dynamic-Routes%EC%9D%98-Params</guid>
            <pubDate>Fri, 02 Jun 2023 10:41:30 GMT</pubDate>
            <description><![CDATA[<h2 id="dynamic-routes의-params">Dynamic Routes의 Params</h2>
<h3 id="🔽-pages-사용-할-때">🔽 pages 사용 할 때</h3>
<h4 id="userouter">useRouter()</h4>
<p><code>dynamic param을 [slug]</code>로 지정을 했을 경우
useRouter를 사용하면 <code>.query.slug</code>를 통해 꺼내사용 할 수가 있었다.</p>
<pre><code class="language-js">import { useRouter } from &#39;next/router&#39;;

export default function Page() {
  const router = useRouter();
  return &lt;p&gt;Post: {router.query.slug}&lt;/p&gt;;
}</code></pre>
<h3 id="🔽-app-사용-할-때">🔽 app 사용 할 때</h3>
<p><a href="https://velog.io/@sseung-i/Next.js-13%EC%9C%BC%EB%A1%9C-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0">전에 썼던 글</a>에선 page.tsx에서 params로 받아와서 사용했었는데, 컴포넌트 내에서 받아 써야 할 일이 있었다.</p>
<p>useRouter로 동일하게 썼더니 오류가 났다.</p>
<blockquote>
<p>NextRouter was not mounted.
<img src="https://velog.velcdn.com/images/sseung-i/post/c03915e0-f954-417e-bb31-400ef0fbb5ff/image.png" alt=""></p>
</blockquote>
<p>client 컴포넌트 내에서 썼는데도 왜 오류가 나는가 싶어 에러 흰트 링크를 들어갔더니 <a href="https://www.npmjs.com/package/next-router-mock">Useful Links</a> .. 이방법 말고는 없을까 ??</p>
<h4 id="useparams">useParams</h4>
<p><code>next/navigation</code>에 들어있는 useParams를 사용하면 된다.</p>
<pre><code class="language-js">&#39;use client&#39;;

import { useParams } from &#39;next/navigation&#39;;

export default function ExampleClientComponent() {
  const params = useParams();

  // Route -&gt; /shop/[tag]/[item]
  // URL -&gt; /shop/shoes/nike-air-max-97
  // `params` -&gt; { tag: &#39;shoes&#39;, item: &#39;nike-air-max-97&#39; }
  console.log(params);

  return &lt;&gt;&lt;/&gt;;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/sseung-i/post/b9d2c5dd-ce16-474f-8b4d-37bef0b59d78/image.png" alt="">page 경로에서는 <code>next/navigation</code> 으로 사용하는 것들은 null이 리턴될수있다고하니 조심!</p>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[sass : 반복문 이용하여 템플릿 스타일링 하기]]></title>
            <link>https://velog.io/@sseung-i/sass-%EB%B0%98%EB%B3%B5%EB%AC%B8-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sseung-i/sass-%EB%B0%98%EB%B3%B5%EB%AC%B8-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 25 May 2023 15:21:26 GMT</pubDate>
            <description><![CDATA[<p>그동안은 prop으로 받아쓰기 편해서 스타일컴포넌트를 주로 이용하였었다.
이번에 사이드프로젝트를 하며 모듈css로 사용하기로 하며 그래도 css보단 sass가 편하니까 sass로 작업하기로!
(자주 사용하는 스타일을 mixin으로 사용해봤지만 다른것들은 잘 사용하지 않았었다.)</p>
<h2 id="하드로-풀어쓰면">하드로 풀어쓰면...</h2>
<p>단계별 카드 스타일을 주고, hover시에도 각 카드별 color를 적용해 주어야 했기에 처음 나열한 코드는 이랬다.</p>
<p>리스트의 단계는 a,b,c,d 가 있다고 한다면</p>
<pre><code class="language-scss">$--a--color : red;
$--b--color : blue;
$--c--color : green;
$--d--color : gray;

.card {

  &amp;.a {
    color: $--a-color;

    .price {
      background-color: $--a-color;
    }

    :hover &gt; .btn {
      background-color: $--a-color;
    }
  }

  &amp;.b {
    color: $--b-color;

    .price {
      background-color: $--b-color;
    }

    :hover &gt; .btn {
      background-color: $--b-color;
    }
  }

  &amp;.c {
    color: $--c-color;

    .price {
      background-color: $--c-color;
    }

    :hover &gt; .btn {
      background-color: $--c-color;
    }
  }

  &amp;.d {
    color: $--d-color;

    .price {
      background-color: $--d-color;
    }

    :hover &gt; .planBtn {
      background-color: $--d-color;
    }
  }
}</code></pre>
<p>같은 형식인데 공통으로 처리할 수 있을 것 같다는 생각이들어 찾아보았다.</p>
<h2 id="each">@each</h2>
<ol>
<li><p>객체 변수를 지정해 주고</p>
<pre><code class="language-scss">$step-color: (
 a: red,
 b: blue,
 c: green,
 d: gray,
);</code></pre>
<ol start="2">
<li><p>each를 이용해 반복문처럼 사용할 수 있다는 것!</p>
<pre><code class="language-scss">.card {
@each $key, $value in $step-color {
&amp;.#{$key} {
  color: $value;

  .price {
    background-color: $value;
  }
}

&amp;.#{$key}:hover .btn {
  background-color: $value;
}
}
}</code></pre>
</li>
</ol>
</li>
</ol>
<p>$step-color를 순회하면서 key,value를 사용하여 a,b,c,d 총 네가지의 스타일을 공통적용 해줄 수 있었다.</p>
<h2 id="map-get">map-get</h2>
<p>map-get($map,$key) 형식으로 줄 수 있다.
만약 color만이 아니고 다른 스타일속성도 함께 바꾸고 싶다면 map-get을 함께 사용하면 가능하다.</p>
<ol>
<li><p>map으로 객체 변수들을 담아준다.</p>
<pre><code class="language-scss">$step-color: (
a: (
 color: red,
 width: 10%,
),
b: (
 color: blue,
 width: 20%,
),
c: (
 color: green,
 width: 30%,
),
d: (
 color: gray,
 width: 40%,
),
);</code></pre>
</li>
<li><p>@each로 $step-color라는 map을 돌면서 map-get()을 이용해 원하는 값을 꺼내준다.</p>
<pre><code class="language-scss">@each $key, $map in $step-color {
 &amp;.#{$key} {
   width: map-get($map, width);
   color: map-get($map, color);

   .price {
     background-color: map-get($map, color);
   }

   &amp;.#{$key}:hover .btn {
     background-color: map-get($map, color);
   }
 }
}</code></pre>
<p>만약 $key가 a라면 <code>width: map-get(a:(color: red, width: 10%), width)</code> 가 된다.</p>
</li>
</ol>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[next-auth error : CLIENT_FETCH_ERROR]]></title>
            <link>https://velog.io/@sseung-i/next-auth-error-CLIENTFETCHERROR</link>
            <guid>https://velog.io/@sseung-i/next-auth-error-CLIENTFETCHERROR</guid>
            <pubDate>Wed, 26 Apr 2023 07:38:36 GMT</pubDate>
            <description><![CDATA[<h2 id="next-auth의-client_fetch_error">next-auth의 [CLIENT_FETCH_ERROR]</h2>
<h4 id="이-글은-로컬작업시의-에러이고-배포amplify이후-에러도-있다">이 글은 로컬작업시의 에러이고, <a href="https://velog.io/@sseung-i/nextauth-clientfetcherror">배포(amplify)이후</a> 에러도 있다...</h4>
<p>dev로 보면서 작업 하다가 build &amp; start를 해서 보면 google 로그인 버튼이 사라졌다..
터미널의 에러를 확인해보니</p>
<pre><code>[next-auth][error][CLIENT_FETCH_ERROR] 
https://next-auth.js.org/errors#client_fetch_error fetch failed {
  error: {
    message: &#39;fetch failed&#39;,
    stack: &#39;TypeError: fetch failed\n&#39; +
      &#39;    at Object.fetch (node:internal/deps/undici/undici:11413:11)\n&#39; +
      &#39;    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)&#39;,
    name: &#39;TypeError&#39;
  },
  url: &#39;http://localhost:3000/api/auth/providers&#39;,
  message: &#39;fetch failed&#39;
}</code></pre><p>이런식으로 나오며 주어진 에러확인 <a href="https://next-auth.js.org/errors#client_fetch_error">링크</a>를 타고 들어가보면 NEXTAUTH_URL 가 잘못 설정되어있다는 것 같다. 하지만 건드리질 않았는데 왜이러는건지..?
혹시 다른사람들도 이런 이슈가 있었을까 싶어 github 이슈를 찾아보니 여러 CLIENT_FETCH_ERROR 이슈가 있는것을 확인하였고, 그 중에 나와 같은 케이스를 찾았다.</p>
<h3 id="프로덕션에서-오류가-난다는-것">프로덕션에서 오류가 난다는 것!!</h3>
<p>고맙게도 해당 이슈의 <a href="https://github.com/nextauthjs/next-auth/issues/7142#issuecomment-1495355868">밑에 댓글</a>에 있는 방식으로 해보았더니 되었다.</p>
<p><strong>node 17+에서 IPV6에 유리한 도메인 확인 순서 지정 때문</strong>이란다. (내 node -v는 v18.15.0 였다)</p>
<ol>
<li>위 내용대로 .env <code>NEXTAUTH_URL=http://127.0.0.1:3000/</code> 로 수정</li>
</ol>
<p>-&gt; <a href="https://ko.wikipedia.org/wiki/Localhost">왜 127.0.0.1 인가?</a>
2. google cloude의 리디렉션 URI에 localhost:3000을 위 ip주소로 바꾸어서 추가</p>
<p>그러면 접속은 localhost던 ip로던 할 수는 있지만 로그인을 거치면 ip주소로 사용하면서 잘 동작 된다 :) , dev도 물론 잘 됨</p>
<blockquote>
<p>이유모를 에러가 날 때엔 공식문서와 github issues를 잘 확인하자!</p>
</blockquote>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Sanity]]></title>
            <link>https://velog.io/@sseung-i/Sanity</link>
            <guid>https://velog.io/@sseung-i/Sanity</guid>
            <pubDate>Wed, 12 Apr 2023 11:17:01 GMT</pubDate>
            <description><![CDATA[<h3 id="클라우드-기반-headless-cms">클라우드 기반 Headless CMS</h3>
<ul>
<li>스키마(데이터 모델) 생성하고 클라우드에 콘텐츠 저장할 수 있으며 API를 통해 콘텐츠에 액세스 가능</li>
<li>클라우드에는 스키마파일로 만든 구조의 DB가있고 Sanity Studio를 사용하여 쉽게 DB 관리할 수 있도록 도와줌</li>
</ul>
<p>API를 제공하여 웹에서 사용 가능하지만 프론트엔드에서 바로사용하지 말고 안전하게 서버에서 사용하는것이 좋다 (Sanity &lt;-&gt; Server &lt;-&gt; Frontend)</p>
<h3 id="sanity-studio">Sanity Studio</h3>
<ul>
<li>Sanity클라우드(Content Lake)와 Sanity Studio와 동기화되어 클라우드에 있는 데이터를 편집할 수 있게끔 보여주는 API 클라이언트 앱</li>
<li>스튜디오에서 보여지는 목록을 Javascript문법을 사용하여 커스터마이징 가능</li>
<li>React로 만들어져있어 기능을 추가하거나 라이브러리를 사용하는 등 확장시켜서 사용가능 (배포해서 어드민처럼 사용할 수도 있음)</li>
</ul>
<h3 id="프로젝트에-셋팅하기">프로젝트에 셋팅하기</h3>
<p>홈페이지에서 get-started 해서 필요한 정보 선택 후 나오는 CLI를 입력해주면 됨
-&gt; CLI 입력 루트를 프로젝트 폴더 안으로 하면 레포를 한개로 관리가 가능하다. (client / back 두개로 관리하면 두개의 레포를 사용하면 됨)</p>
<p><a href="http://localhost:3333/">http://localhost:3333/</a> 으로 지정이 되면 로그인하고 스키마 만들어 사용 가능</p>
<p>사용할 스키마 파일들은 /schemas/index 안에서 <code>schemaTypes</code>에 지정해줘야한다.</p>
<hr>
<p>reference
<a href="https://www.sanity.io/docs/create-a-sanity-project">공식문서</a>
<a href="https://blog.usefulparadigm.com/sanity%EB%A1%9C-gatsby-%EC%BD%98%ED%85%90%EC%B8%A0-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0-908327949ec0">Sanity로 Gatsby 콘텐츠 관리하기</a></p>
<hr>
<p><code>공부하며 정리&amp;기록하는 ._. 씅로그</code></p>
]]></description>
        </item>
    </channel>
</rss>