<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>seojaeyeong-051.log</title>
        <link>https://velog.io/</link>
        <description>.</description>
        <lastBuildDate>Wed, 28 Jan 2026 06:41:33 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>seojaeyeong-051.log</title>
            <url>https://velog.velcdn.com/images/seojaeyeong-051/profile/6681568e-1a8c-4079-bce3-db918ae72612/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. seojaeyeong-051.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/seojaeyeong-051" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[잡것]]></title>
            <link>https://velog.io/@seojaeyeong-051/%EC%9E%A1%EA%B2%83</link>
            <guid>https://velog.io/@seojaeyeong-051/%EC%9E%A1%EA%B2%83</guid>
            <pubDate>Wed, 28 Jan 2026 06:41:33 GMT</pubDate>
            <description><![CDATA[<p>application. 에서</p>
<p>yml
properties
차이를 몰랐음. 찾아보니 문법의 차이고 성능 측면에서는 상관없음</p>
<pre><code class="language-yml">  jpa:
    hibernate:
      ddl-auto: validate
    properties:
      hibernate:
        format_sql: true
    show-sql: true</code></pre>
<pre><code class="language-js">    spring.jpa.hibernate.ddl-auto = validate
    spring.jpa.properties.hibernate.format_sql = true
    spring.jpa.show-sql = true</code></pre>
<ol>
<li>ddl-auto: validate</li>
</ol>
<p>“엔티티(Entity)와 실제 DB 테이블 구조가 일치하는지만 검사”</p>
<p>옵션별 의미
값    의미
none    아무 것도 안 함
validate    구조만 검사 (지금 추천)
update    엔티티 기준으로 테이블 수정
create    실행 시 테이블 전부 삭제 후 생성
create-drop    실행 시 생성, 종료 시 삭제</p>
<ol start="2">
<li>spring.jpa.properties.hibernate</li>
</ol>
<ul>
<li>Hibernate 전용 세부 옵션</li>
</ul>
<p>spring JPA는 내부적으로 Hibernate를 쓰기 때문에
Hibernate 설정을 이렇게 &quot;넘겨서&quot; 쓰는 구조</p>
<ol start="3">
<li>format_sql: true</li>
</ol>
<ul>
<li>SQL을 보기 좋게 정렬해서 출력</li>
</ul>
<p>예시
기본</p>
<pre><code class="language-sql">select user0_.id as id1_0_,user0_.email as email2_0_ ...</code></pre>
<pre><code class="language-format_sql">select
    user0_.id as id1_0_,
    user0_.email as email2_0_
from
    user user0</code></pre>
<p>디버깅에 능함</p>
<ol start="4">
<li>spring.jpa.show-sql: true</li>
</ol>
<ul>
<li>Hibernate가 실행하는 SQL을 콘솔에 출력</li>
</ul>
<p>실제로 어떤 SQL이 DB에 날아가는지 확인 가능</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 뿌수기 #7]]></title>
            <link>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%BF%8C%EC%88%98%EA%B8%B0-7</link>
            <guid>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%BF%8C%EC%88%98%EA%B8%B0-7</guid>
            <pubDate>Wed, 14 Jan 2026 11:25:14 GMT</pubDate>
            <description><![CDATA[<h1 id="day-07--이벤트-루프--비동기-event-loop">DAY 07 — 이벤트 루프 &amp; 비동기 (Event Loop)</h1>
<h2 id="🎯-목표">🎯 목표</h2>
<ul>
<li>JavaScript가 왜 한 번에 하나만 실행되는지 이해한다</li>
<li>비동기 코드의 실행 순서를 예측할 수 있다</li>
<li>Promise와 setTimeout의 실행 순서 차이를 설명한다</li>
</ul>
<hr>
<h2 id="javascript는-싱글-스레드다">JavaScript는 싱글 스레드다</h2>
<blockquote>
<p><strong>JavaScript는 한 번에 하나의 작업만 실행한다</strong></p>
</blockquote>
<ul>
<li>동시에 여러 줄 실행 ❌</li>
<li>대신 <strong>순서를 관리</strong>해서 비동기처럼 보이게 만든다</li>
</ul>
<p>이 순서를 관리하는 시스템이 <strong>이벤트 루프(Event Loop)</strong> 다.</p>
<hr>
<h2 id="이벤트-루프-핵심-구성요소">이벤트 루프 핵심 구성요소</h2>
<h3 id="call-stack">Call Stack</h3>
<blockquote>
<p><strong>현재 실행 중인 동기 코드(함수 호출 기록)</strong></p>
</blockquote>
<ul>
<li>함수 호출 시 스택에 쌓임</li>
<li>실행이 끝나면 스택에서 제거</li>
<li>항상 스택의 맨 위가 실행 중</li>
</ul>
<hr>
<h3 id="heap">Heap</h3>
<blockquote>
<p><strong>객체, 배열, 함수 같은 참조 타입 데이터가 저장되는 공간</strong></p>
</blockquote>
<ul>
<li>실제 데이터는 Heap에 저장</li>
<li>변수에는 Heap을 가리키는 참조값이 들어 있음</li>
<li>가비지 컬렉터(GC)가 관리</li>
</ul>
<hr>
<h3 id="web-apis-host-environment">Web APIs (Host Environment)</h3>
<blockquote>
<p><strong>비동기 작업을 대신 처리해주는 영역</strong></p>
</blockquote>
<ul>
<li><code>setTimeout</code></li>
<li>네트워크 요청(fetch)</li>
<li>DOM 이벤트</li>
</ul>
<p>JS 엔진 밖에서 동작하며, 작업 완료 후 큐로 전달됨.</p>
<hr>
<h3 id="microtask-queue">Microtask Queue</h3>
<blockquote>
<p><strong>동기 코드가 끝난 직후 가장 먼저 실행되는 대기열</strong></p>
</blockquote>
<p>대표 예시:</p>
<ul>
<li><code>Promise.then</code></li>
<li><code>catch</code></li>
<li><code>finally</code></li>
<li><code>await</code> 이후 코드</li>
</ul>
<hr>
<h3 id="task-queue-macrotask-queue">Task Queue (Macrotask Queue)</h3>
<blockquote>
<p><strong>일반적인 비동기 콜백이 대기하는 큐</strong></p>
</blockquote>
<p>대표 예시:</p>
<ul>
<li><code>setTimeout</code></li>
<li><code>setInterval</code></li>
<li>DOM 이벤트 콜백</li>
</ul>
<hr>
<h3 id="event-loop">Event Loop</h3>
<blockquote>
<p><strong>Call Stack이 비었을 때<br>Microtask → Task 순서로 실행을 조율하는 관리자</strong></p>
</blockquote>
<hr>
<h2 id="실행-순서-핵심-규칙-가장-중요">실행 순서 핵심 규칙 (가장 중요)</h2>
<blockquote>
<p><strong>Call Stack이 비면<br>1️⃣ Microtask Queue를 전부 비우고<br>2️⃣ Task Queue에서 하나를 실행한다</strong></p>
</blockquote>
<p>이 과정이 반복된다.</p>
<hr>
<h2 id="settimeout이-0초여도-늦는-이유">setTimeout이 0초여도 늦는 이유</h2>
<pre><code class="language-js">console.log(&quot;A&quot;);

setTimeout(() =&gt; console.log(&quot;B&quot;), 0);

console.log(&quot;C&quot;);</code></pre>
<p>출력</p>
<pre><code class="language-css">A
C
B</code></pre>
<p>setTimeout은 즉시 실행 ❌</p>
<p>Web API → Task Queue → Event Loop를 거침</p>
<p>Promise가 더 먼저 실행되는 이유</p>
<pre><code class="language-js">console.log(&quot;A&quot;);

Promise.resolve().then(() =&gt; console.log(&quot;B&quot;));

console.log(&quot;C&quot;);</code></pre>
<p>출력</p>
<pre><code class="language-css">A
C
B</code></pre>
<p>then 콜백은 Microtask Queue</p>
<p>Task보다 우선 실행됨</p>
<p>Promise vs setTimeout 함께 쓰는 경우</p>
<pre><code class="language-js">console.log(&quot;A&quot;);

setTimeout(() =&gt; console.log(&quot;B&quot;), 0);

Promise.resolve().then(() =&gt; console.log(&quot;C&quot;));

console.log(&quot;D&quot;);</code></pre>
<p>출력</p>
<pre><code class="language-css">A
D
C
B</code></pre>
<p>async / await 실행 흐름</p>
<pre><code class="language-js">async function test() {
  console.log(1);
  await Promise.resolve();
  console.log(2);
}

console.log(3);
test();
console.log(4);</code></pre>
<p>출력</p>
<pre><code>3
1
4
2</code></pre><p>이유
await 뒤 코드는 Promise.then</p>
<p>즉, Microtask로 예약</p>
<p>중요한 포인트 (헷갈림 방지)
Promise.then은 즉시 실행 ❌</p>
<p>예약(Microtask) 개념</p>
<p>Microtask 안에서 setTimeout을 등록하면
기존 Task보다 뒤에 실행됨</p>
<p>이벤트 루프 한 줄 요약
JavaScript는 동기 코드를 Call Stack에서 처리하고,
콜스택이 비면 Microtask를 먼저 전부 실행한 뒤,
Task를 하나씩 실행한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 뿌수기 #6]]></title>
            <link>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%BF%8C%EC%88%98%EA%B8%B0-6</link>
            <guid>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%BF%8C%EC%88%98%EA%B8%B0-6</guid>
            <pubDate>Tue, 13 Jan 2026 09:40:53 GMT</pubDate>
            <description><![CDATA[<h1 id="day-06--this--바인딩-call--apply--bind">DAY 06 — this &amp; 바인딩 (call / apply / bind)</h1>
<h2 id="🎯-목표">🎯 목표</h2>
<ul>
<li><code>this</code>가 언제, 어떻게 결정되는지 이해한다</li>
<li>일반 함수 / 메서드 / 화살표 함수의 <code>this</code> 차이를 설명할 수 있다</li>
<li><code>call / apply / bind</code>의 역할과 차이를 명확히 구분한다</li>
</ul>
<hr>
<h2 id="this란">this란?</h2>
<blockquote>
<p><strong><code>this</code>는 함수가 “어디서 선언됐는지”가 아니라<br>“어떻게 호출됐는지”에 따라 결정된다</strong></p>
</blockquote>
<p>⚠️ 주의  </p>
<ul>
<li>스코프 → 선언 위치 기준  </li>
<li><code>this</code> → <strong>호출 방식 기준</strong></li>
</ul>
<hr>
<h2 id="this-결정-규칙-핵심-4가지">this 결정 규칙 (핵심 4가지)</h2>
<h3 id="1️⃣-전역에서의-this">1️⃣ 전역에서의 this</h3>
<pre><code class="language-js">console.log(this);</code></pre>
<p>브라우저: window</p>
<p>2️⃣ 일반 함수 호출</p>
<pre><code class="language-js">function foo() {
  console.log(this);
}

foo();</code></pre>
<p>브라우저: window</p>
<p>strict mode: undefined</p>
<p>👉 그냥 호출하면 전역 this</p>
<p>3️⃣ 메서드 호출</p>
<pre><code class="language-js">const user = {
  name: &quot;철수&quot;,
  sayName() {
    console.log(this.name);
  }
};

user.sayName(); // 철수</code></pre>
<p>👉 . 앞의 객체가 this</p>
<p>4️⃣ 화살표 함수</p>
<pre><code class="language-js">const user = {
  name: &quot;철수&quot;,
  sayName: () =&gt; {
    console.log(this.name);
  }
};

user.sayName(); // undefined</code></pre>
<p>화살표 함수는 자기 this가 없음</p>
<p>자신이 선언된 스코프의 this를 그대로 사용</p>
<p>화살표 함수 this 한 줄 정리</p>
<p>화살표 함수의 this는
“자기가 태어난 곳의 this”다</p>
<p>this가 깨지는 대표적인 상황</p>
<pre><code class="language-js">const user = {
  name: &quot;철수&quot;,
  sayName() {
    setTimeout(function () {
      console.log(this.name);
    }, 0);
  }
};

user.sayName(); // undefined</code></pre>
<p>이유</p>
<p>setTimeout 안의 함수는 일반 함수</p>
<p>호출 주체 없음 → 전역 this</p>
<p>해결 방법 3가지
1️⃣ 화살표 함수 (가장 많이 씀)</p>
<pre><code class="language-js">setTimeout(() =&gt; {
  console.log(this.name);
}, 0);</code></pre>
<p>2️⃣ bind 사용</p>
<pre><code class="language-js">setTimeout(function () {
  console.log(this.name);
}.bind(this), 0);</code></pre>
<p>3️⃣ 변수에 저장 (구식)</p>
<pre><code class="language-js">const self = this;

call / apply / bind란?

함수의 this를 강제로 지정하는 방법

call
func.call(thisArg, arg1, arg2);</code></pre>
<p>즉시 실행</p>
<p>인자 나열</p>
<p>greet.call({ name: &quot;철수&quot; }, &quot;안녕&quot;);
// 안녕 철수</p>
<p>apply</p>
<pre><code class="language-js">func.apply(thisArg, [arg1, arg2]);</code></pre>
<p>즉시 실행</p>
<p>인자를 배열로 전달</p>
<pre><code class="language-js">greet.apply({ name: &quot;영희&quot; }, [&quot;안녕&quot;]);
// 안녕 영희</code></pre>
<p>bind (⭐ 가장 중요)</p>
<pre><code class="language-js">const boundFn = func.bind(thisArg);</code></pre>
<p>실행하지 않음</p>
<p>this가 고정된 새 함수 반환</p>
<p>이후 call / apply로도 this 변경 불가</p>
<pre><code class="language-js">const fn = foo.bind(obj);
fn.call(other); // this는 여전히 obj</code></pre>
<p>바인딩(binding)이란?</p>
<p>this를 특정 객체에 연결(고정)하는 것</p>
<p>bind는 함수 자체를 고정하는 게 아님</p>
<p>함수 안에서 사용되는 this만 고정</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 뿌수기 #5]]></title>
            <link>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%BF%8C%EC%88%98%EA%B8%B0-5</link>
            <guid>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%BF%8C%EC%88%98%EA%B8%B0-5</guid>
            <pubDate>Tue, 13 Jan 2026 08:00:20 GMT</pubDate>
            <description><![CDATA[<h1 id="day-05--실행-컨텍스트--호이스팅-hoisting-tdz">DAY 05 — 실행 컨텍스트 &amp; 호이스팅 (Hoisting, TDZ)</h1>
<h2 id="🎯-목표">🎯 목표</h2>
<ul>
<li>JavaScript가 <strong>코드를 실행하기 전에 무엇을 준비하는지</strong> 이해한다.</li>
<li>호이스팅을 “코드 이동”이 아닌 <strong>실행 컨텍스트 생성 과정</strong>으로 설명한다.</li>
<li><code>var / let / const</code> 차이를 <strong>TDZ와 실행 컨텍스트 관점</strong>에서 이해한다.</li>
</ul>
<hr>
<h2 id="실행-컨텍스트execution-context란">실행 컨텍스트(Execution Context)란?</h2>
<blockquote>
<p><strong>JavaScript 코드를 실행하기 위해<br>JS 엔진이 만드는 실행 환경</strong></p>
</blockquote>
<p>JS는 코드를 바로 실행하지 않는다.<br><strong>먼저 준비 → 그 다음 실행</strong>한다.</p>
<hr>
<h2 id="실행-컨텍스트의-2단계">실행 컨텍스트의 2단계</h2>
<h3 id="1️⃣-생성-단계-creation-phase">1️⃣ 생성 단계 (Creation Phase)</h3>
<ul>
<li>변수 이름 수집</li>
<li>함수 선언 등록</li>
<li>스코프 체인 구성</li>
<li><code>this</code> 결정</li>
</ul>
<h3 id="2️⃣-실행-단계-execution-phase">2️⃣ 실행 단계 (Execution Phase)</h3>
<ul>
<li>코드 한 줄씩 실행</li>
<li>값 할당 및 연산 수행</li>
</ul>
<p>👉 <strong>호이스팅은 생성 단계에서 발생하는 현상</strong></p>
<hr>
<h2 id="호이스팅hoisting이란">호이스팅(Hoisting)이란?</h2>
<blockquote>
<p><strong>선언이 코드 위로 끌어올려진 것처럼 보이는 현상</strong></p>
</blockquote>
<p>실제로 이동하는 게 아니라<br>👉 <strong>실행 전에 선언을 미리 등록</strong>하는 것이다.</p>
<hr>
<h2 id="var의-호이스팅">var의 호이스팅</h2>
<pre><code class="language-js">console.log(a); // undefined
var a = 10;



내부 동작
// 생성 단계
var a = undefined;

// 실행 단계
console.log(a);
a = 10;</code></pre>
<p>선언 + 접근 허용</p>
<p>값만 나중에 할당</p>
<p>👉 에러 ❌, 대신 undefined</p>
<pre><code class="language-js">let / const의 호이스팅과 TDZ
console.log(b); // ReferenceError
let b = 10;</code></pre>
<p>특징</p>
<p>선언은 수집됨 ⭕</p>
<p>하지만 초기화 전까지 접근 금지</p>
<p>이 구간을 TDZ(Temporal Dead Zone) 라고 한다.</p>
<p>TDZ(Temporal Dead Zone)</p>
<p>선언은 되었지만
값이 할당되기 전까지 접근이 금지된 구간</p>
<pre><code class="language-js">{
  console.log(x); // ReferenceError
  let x = 10;
}</code></pre>
<p>존재는 알고 있음</p>
<p>접근하면 즉시 ReferenceError</p>
<p>👉 값이 undefined인 상태 ❌
👉 접근 자체가 불가능한 상태 ⭕</p>
<p>var vs let / const 차이 요약
구분    var    let    const
선언 수집    ⭕    ⭕    ⭕
초기화    즉시(undefined)    ❌    ❌
TDZ    ❌    ⭕    ⭕
재대입    ⭕    ⭕    ❌
블록 스코프    ❌    ⭕    ⭕
함수 호이스팅
함수 선언문</p>
<pre><code>foo();

function foo() {
  console.log(&quot;foo&quot;);
}</code></pre><p>생성 단계에서 함수 전체가 등록</p>
<p>선언 전 호출 가능</p>
<p>함수 표현식</p>
<pre><code class="language-js">bar(); // TypeError

var bar = function () {
  console.log(&quot;bar&quot;);
};</code></pre>
<p>변수만 호이스팅</p>
<p>함수는 아직 없음</p>
<p>실행 컨텍스트 스택 (Call Stack)</p>
<p>실행 컨텍스트는 스택 구조로 관리된다</p>
<p>Global Context
→ 함수 A 실행
→ 함수 B 실행
→ B 종료
→ A 종료</p>
<p>가장 나중에 실행된 함수가 먼저 종료 (LIFO)</p>
<p>재귀, 콜스택 에러의 원인</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 뿌수기 #4]]></title>
            <link>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8-%EB%BF%8C%EC%88%98%EA%B8%B0-4</link>
            <guid>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8-%EB%BF%8C%EC%88%98%EA%B8%B0-4</guid>
            <pubDate>Tue, 13 Jan 2026 05:48:11 GMT</pubDate>
            <description><![CDATA[<h1 id="day-04--함수-스코프-클로저-개념-정리">DAY 04 — 함수, 스코프, 클로저 (개념 정리)</h1>
<h2 id="🎯-목표">🎯 목표</h2>
<ul>
<li>JavaScript에서 <strong>변수를 어떻게 찾는지</strong> 이해한다.</li>
<li>스코프 체인이 왜 “선언 위치 기준”인지 설명할 수 있다.</li>
<li>클로저를 메모리 개념이 아닌 <strong>접근 규칙 관점</strong>에서 이해한다.</li>
</ul>
<hr>
<h2 id="스코프scope란">스코프(Scope)란?</h2>
<blockquote>
<p><strong>스코프는 값이 저장된 공간이 아니라<br>변수를 어디서 접근할 수 있는지에 대한 규칙이다.</strong></p>
</blockquote>
<ul>
<li>메모리: 값이 저장되는 장소</li>
<li>스코프: 그 값에 <strong>접근할 수 있는 범위와 규칙</strong></li>
</ul>
<hr>
<h2 id="스코프-체인의-핵심-원칙">스코프 체인의 핵심 원칙</h2>
<blockquote>
<p><strong>JavaScript는 변수를<br>“어디서 실행됐는지”가 아니라<br>“어디서 선언됐는지”를 기준으로 찾는다.</strong></p>
</blockquote>
<p>변수를 찾을 때의 순서:</p>
<ol>
<li>현재 스코프</li>
<li>자신을 감싸는 상위 스코프</li>
<li>전역 스코프</li>
</ol>
<p>👉 가까운 곳부터, 없으면 바깥으로 올라간다.</p>
<hr>
<h2 id="선언-위치-vs-호출-위치">선언 위치 vs 호출 위치</h2>
<pre><code class="language-js">let x = 10;

function foo() {
  console.log(x);
}

function bar() {
  let x = 20;
  foo();
}

bar(); // 10</code></pre>
<p>foo는 전역에서 선언됨</p>
<p>따라서 foo 안에서 x를 찾을 때</p>
<p>bar의 x는 고려되지 않는다</p>
<p>👉 함수는 자신이 선언된 스코프만 본다</p>
<p>var / let 차이 (핵심만)
var는 반복문 전체에서 하나의 변수를 공유하고
let은 반복문 한 바퀴마다 새로운 변수를 만든다</p>
<pre><code class="language-js">// var
for (var i = 0; i &lt; 3; i++) {
  setTimeout(() =&gt; console.log(i), 0);
}
// 3, 3, 3

// let
for (let i = 0; i &lt; 3; i++) {
  setTimeout(() =&gt; console.log(i), 0);
}
// 0, 1, 2</code></pre>
<p>클로저(Closure)란?
❌ 외운 정의</p>
<p>외부 함수의 변수를 참조하는 내부 함수</p>
<p>⭕ 이해용 정의</p>
<p>함수가 자신이 선언된 스코프를 기억하고
그 스코프에 계속 접근할 수 있는 현상</p>
<p>클로저가 생기는 이유</p>
<pre><code class="language-js">function outer() {
  let a = 1;
  return function inner() {
    console.log(a);
  };
}

const fn = outer();
fn(); // 1</code></pre>
<p>outer 실행은 끝났지만</p>
<p>inner 함수가 a에 접근하고 있음</p>
<p>JS 엔진은 해당 변수를 제거하지 않음</p>
<p>👉 값이 메모리에 남아서가 아니라
접근 경로가 살아 있기 때문</p>
<p>클로저와 메모리의 관계
개념    의미
메모리    값이 저장되는 장소
스코프    변수를 찾는 규칙
클로저    함수가 스코프 접근을 유지하는 현상</p>
<p>클로저는 변수를 저장하는 것이 아니라
변수를 참조할 수 있는 상태를 유지한다.</p>
<p>클로저 예제 한 줄 설명</p>
<pre><code class="language-js">function makeCounter() {
  let count = 0;
  return function () {
    count++;
    return count;
  };
}</code></pre>
<p>내부 함수는
자신이 선언된 스코프의 count를 기억하기 때문에
호출할 때마다 이전 상태를 유지할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 뿌수기 #3]]></title>
            <link>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%BF%8C%EC%88%98%EA%B8%B0-3</link>
            <guid>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%BF%8C%EC%88%98%EA%B8%B0-3</guid>
            <pubDate>Wed, 07 Jan 2026 11:28:27 GMT</pubDate>
            <description><![CDATA[<h1 id="day-03--얕은-복사shallow-copy-vs-깊은-복사deep-copy">DAY 03 — 얕은 복사(Shallow Copy) vs 깊은 복사(Deep Copy)</h1>
<h2 id="🎯-목표">🎯 목표</h2>
<ul>
<li>JavaScript에서 <strong>얕은 복사와 깊은 복사의 차이</strong>를 이해한다.</li>
<li>객체 복사 시 왜 예상치 못한 버그가 발생하는지 설명할 수 있다.</li>
<li>React에서 state를 직접 수정하면 안 되는 이유를 연결해서 이해한다.</li>
</ul>
<hr>
<h2 id="복사가-왜-문제일까">복사가 왜 문제일까?</h2>
<pre><code class="language-js">const a = { n: 1 };
const b = a;

b.n = 2;

console.log(a.n); // 2</code></pre>
<p>객체를 복사했다고 생각했지만,
실제로는 같은 객체를 공유하고 있어서
한쪽을 수정하면 다른 쪽도 함께 변경된다.</p>
<p>👉 이 경우는 복사가 아니다 (참조 공유)</p>
<p>얕은 복사 (Shallow Copy)
정의</p>
<p>겉 객체만 새로 만들고,
내부 객체는 그대로 공유하는 복사</p>
<p>얕은 복사 예시
const a = { n: 1 };
const b = { ...a };</p>
<p>b.n = 2;</p>
<p>console.log(a.n); // 1</p>
<p>겉 객체는 새로 생성</p>
<p>n은 원시 타입이라 안전</p>
<pre><code class="language-js">⚠️ 얕은 복사의 함정 (중첩 객체)
const a = { n: { x: 1 } };
const b = { ...a };

b.n.x = 2;

console.log(a.n.x); // 2</code></pre>
<p>n은 객체</p>
<p>내부 객체를 공유</p>
<p>속성을 수정하면 같이 바뀜</p>
<p>👉 겉만 복사했기 때문</p>
<pre><code class="language-js">얕은 복사지만 안전한 경우
const a = { n: { x: 1 } };
const b = { ...a };

b.n = { x: 2 };

console.log(a.n.x); // 1</code></pre>
<p>공유 중인 객체의 속성 수정 ❌</p>
<p>객체 자체를 새로 교체 ⭕</p>
<p>📌 차이 포인트</p>
<p>속성을 바꾸면 공유 유지,
객체를 교체하면 분리</p>
<p>깊은 복사 (Deep Copy)
정의</p>
<p>겉 객체와 내부 객체까지
모두 새로 만드는 복사</p>
<pre><code class="language-js">structuredClone 사용
const a = { n: { x: 1 } };
const b = structuredClone(a);

b.n.x = 2;

console.log(a.n.x); // 1</code></pre>
<p>내부 객체까지 완전히 분리</p>
<p>서로 영향 없음</p>
<p>복사 방식 한눈에 정리
방식    결과</p>
<pre><code class="language-js">b = a    ❌ 복사 아님 (공유)
{ ...a }    ⭕ 얕은 복사
structuredClone(a)    ⭕ 깊은 복사
React와 연결 (중요)
❌ 잘못된 상태 변경
state.user.name = &quot;영희&quot;;
setState(state);</code></pre>
<p>참조 유지</p>
<p>React는 변경을 감지하지 못함</p>
<p>리렌더링 ❌</p>
<p>⭕ 올바른 패턴</p>
<pre><code class="language-js">setState({
  ...state,
  user: {
    ...state.user,
    name: &quot;영희&quot;
  }
});</code></pre>
<p>필요한 깊이까지만 새 객체 생성</p>
<p>불변성 유지</p>
<p>핵심 정리</p>
<p>복사 아님: 같은 객체를 공유</p>
<p>얕은 복사: 겉만 새로, 속은 공유될 수 있음</p>
<p>깊은 복사: 겉도 속도 전부 새로</p>
<p>중첩 객체에서는 얕은 복사에 주의해야 한다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 뿌수기 #2]]></title>
            <link>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%BF%8C%EC%88%98%EA%B8%B0-2</link>
            <guid>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%BF%8C%EC%88%98%EA%B8%B0-2</guid>
            <pubDate>Fri, 02 Jan 2026 05:47:13 GMT</pubDate>
            <description><![CDATA[<h1 id="day-02--변수--자료형-primitive--reference">DAY 02 — 변수 &amp; 자료형 (Primitive / Reference)</h1>
<h2 id="🎯-목표">🎯 목표</h2>
<ul>
<li>JavaScript에서 <strong>원시 타입(Primitive)</strong> 과 <strong>참조 타입(Reference)</strong> 의 차이를 이해한다.</li>
<li><code>const</code> 인데도 객체 값이 바뀌는 이유를 설명할 수 있다.</li>
<li>함수에 객체를 넘겼을 때 <strong>원본이 바뀌는 경우 / 안 바뀌는 경우</strong>를 구분한다.</li>
</ul>
<hr>
<h2 id="js-자료형-큰-분류">JS 자료형 큰 분류</h2>
<p>JavaScript의 자료형은 크게 두 가지로 나뉜다.</p>
<p>Primitive Type (원시 타입)
Reference Type (참조 타입)</p>
<hr>
<h2 id="primitive-type-원시-타입">Primitive Type (원시 타입)</h2>
<h3 id="정의">정의</h3>
<blockquote>
<p><strong>값 그 자체가 데이터인 타입</strong></p>
</blockquote>
<h3 id="종류">종류</h3>
<ul>
<li>number</li>
<li>string</li>
<li>boolean</li>
<li>null</li>
<li>undefined</li>
<li>symbol</li>
<li>bigint</li>
</ul>
<h3 id="특징">특징</h3>
<ul>
<li>값 자체가 복사됨</li>
<li>서로 영향을 주지 않음</li>
</ul>
<pre><code class="language-js">let a = 10;
let b = a;

b = 20;

console.log(a, b); // 10, 20</code></pre>
<p>Reference Type (참조 타입)
정의
객체를 가리키는 참조값을 데이터로 가지는 타입</p>
<p>종류
object</p>
<p>array</p>
<p>function</p>
<p>특징
참조값이 복사됨</p>
<p>여러 변수가 같은 객체를 가리킬 수 있음</p>
<pre><code class="language-js">const a = { value: 1 };
const b = a;

b.value = 2;

console.log(a.value); // 2</code></pre>
<p>Primitive vs Reference 차이
구분    Primitive    Reference
저장    값 자체    객체에 대한 참조값
복사    값 복사    참조값 복사
영향    없음    있음</p>
<p>const인데 왜 값이 바뀔까?
오해
const는 값이 바뀌면 안 된다 ❌</p>
<p>실제 의미
const는 재할당만 금지한다</p>
<pre><code class="language-js">
const user = { name: &quot;철수&quot; };

user.name = &quot;영희&quot;; // ⭕ 가능 (객체 내부 수정)
user = { name: &quot;민수&quot; }; // ❌ 에러 (재할당)</code></pre>
<p>객체 비교의 함정</p>
<pre><code class="language-js">const a = { value: 1 };
const b = { value: 1 };

console.log(a === b); // false</code></pre>
<p>객체 비교는 내용 비교가 아니다</p>
<p>서로 다른 객체이므로 참조값이 다르다</p>
<p>함수와 참조 타입 (중요)
객체를 수정하는 경우</p>
<pre><code class="language-js">function change(user) {
  user.name = &quot;영희&quot;;
}

const person = { name: &quot;철수&quot; };
change(person);

console.log(person.name); // &quot;영희&quot;</code></pre>
<p>함수는 객체를 가리키는 참조값을 받는다</p>
<p>객체 내부를 수정하면 원본도 변경된다</p>
<p>참조를 재할당하는 경우</p>
<pre><code class="language-js">function update(user) {
  user = { name: &quot;영희&quot; };
}

const person = { name: &quot;철수&quot; };
update(person);

console.log(person.name); // &quot;철수&quot;</code></pre>
<p>함수 내부에서 참조를 바꿔도</p>
<p>원본 객체에는 영향이 없다</p>
<p>핵심 정리
모든 변수는 값을 가진다</p>
<p>Primitive는 값 자체</p>
<p>Reference는 객체를 가리키는 참조값</p>
<p>객체를 수정하면 공유된 모든 참조에 영향</p>
<p>참조를 재할당해도 원본 변수는 변하지 않는다</p>
<p>한 줄 정리
JavaScript에서 버그의 대부분은
참조 타입을 값처럼 다룰 때 발생한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 뿌수기]]></title>
            <link>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%BF%8C%EC%88%98%EA%B8%B0</link>
            <guid>https://velog.io/@seojaeyeong-051/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%BF%8C%EC%88%98%EA%B8%B0</guid>
            <pubDate>Tue, 30 Dec 2025 06:41:37 GMT</pubDate>
            <description><![CDATA[<h1 id="브라우저-렌더링-흐름-dom--cssom--render-tree">브라우저 렌더링 흐름, DOM / CSSOM / Render Tree</h1>
<h2 id="🎯-목표">🎯 목표</h2>
<ul>
<li>브라우저에서 <strong>JS가 왜, 언제 실행되는지</strong> 설명할 수 있도록 한다.  </li>
<li>HTML / CSS / JS를 브라우저가 어떻게 처리해서 화면을 그리는지 정리한다.</li>
</ul>
<hr>
<h2 id="브라우저-렌더링-흐름">브라우저 렌더링 흐름</h2>
<p>HTML 파싱
→ DOM 생성
→ CSS 파싱
→ CSSOM 생성
→ Render Tree
→ Layout
→ Paint</p>
<hr>
<h2 id="파싱parsing이란">파싱(parsing)이란?</h2>
<p><strong>문자열로 된 문서를<br>브라우저가 이해할 수 있는 의미 있는 객체 구조로 바꾸는 과정</strong></p>
<hr>
<h3 id="html-파싱-예시">HTML 파싱 예시</h3>
<pre><code class="language-html">&lt;button&gt;클릭&lt;/button&gt;
이 상태에서 브라우저 입장에서는

버튼 ❌

구조 ❌

의미 ❌

👉 단순한 문자 덩어리

브라우저는 HTML을 위에서부터 읽으면서 다음과 같이 해석한다.

&quot;&lt;button&gt;&quot;  → Button 노드 생성
&quot;클릭&quot;      → Text 노드 생성
&quot;&lt;/button&gt;&quot; → 닫힘 처리
그 결과 만들어지는 구조는 다음과 같다.</code></pre>
<pre><code class="language-css">
Document
 └─ body
    └─ button
        └─ &quot;클릭&quot;
이렇게 생성된 트리가 DOM 트리이다.

DOM / CSSOM / Render Tree
HTML → DOM

CSS → CSSOM

DOM + CSSOM → Render Tree</code></pre>
<p>Render Tree란?
Render Tree는 DOM과 CSSOM을 결합해서
실제로 화면에 그릴 요소만 선별한 트리 구조이다.</p>
<p>백엔드로 따졌을 때 DTO 포지션과 비슷하다.
받아올 때 선언한 변수들을 전부 사용하지 않고,
필요한 것들만 뽑아서 사용하는 느낌이다.</p>
<h3 id="파싱의-핵심">파싱의 핵심</h3>
<p>파싱은 이것이 무엇인지 이해하는 단계이다.
화면을 그리는 단계는 아니다.</p>
<h2 id="layout-과-reflow">Layout 과 Reflow</h2>
<pre><code>Layout
Render Tree를 기준으로
각 요소의 위치와 크기를 계산하는 단계

Reflow
Layout을 다시 계산하는 작업
DOM 구조 변경이나 요소의 크기 / 위치가 변경되면 발생한다.

Paint와의 차이
색 변경 → Paint

크기 / 위치 변경 → Reflow</code></pre><pre><code class="language-js">
box.style.color = &quot;red&quot;;    // Paint
box.style.width = &quot;200px&quot;; // Reflow
색상 변경은 Layout에 영향을 주지 않기 때문에
Reflow가 아니라 Paint만 발생한다.

JS가 DOM을 바꾸면 성능이 나빠지는 이유
JS로 DOM을 변경하면
기존 Render Tree와 Layout 정보가 무효화된다.</code></pre>
<p>그 결과,</p>
<pre><code class="language-scss">
DOM 변경
→ Render Tree 무효화
→ Layout(Reflow)
→ Paint
이 과정이 여러 번 반복되면
브라우저가 레이아웃을 계속 다시 계산하게 되어
성능이 저하된다.</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[## 11. 공유 링크 생성 로직 변경 후 발생한 오류]]></title>
            <link>https://velog.io/@seojaeyeong-051/11.-%EA%B3%B5%EC%9C%A0-%EB%A7%81%ED%81%AC-%EC%83%9D%EC%84%B1-%EB%A1%9C%EC%A7%81-%EB%B3%80%EA%B2%BD-%ED%9B%84-%EB%B0%9C%EC%83%9D%ED%95%9C-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@seojaeyeong-051/11.-%EA%B3%B5%EC%9C%A0-%EB%A7%81%ED%81%AC-%EC%83%9D%EC%84%B1-%EB%A1%9C%EC%A7%81-%EB%B3%80%EA%B2%BD-%ED%9B%84-%EB%B0%9C%EC%83%9D%ED%95%9C-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Mon, 15 Dec 2025 08:21:47 GMT</pubDate>
            <description><![CDATA[<h2 id="공유-링크-404-이슈--프론트에서-url을-생성하도록-구조-단순화">공유 링크 404 이슈 — 프론트에서 URL을 생성하도록 구조 단순화</h2>
<h3 id="📝-문제-상황">📝 문제 상황</h3>
<p>서비스에 <strong>공유하기 기능</strong>이 있었는데,<br>공유 링크를 복사해 브라우저에 붙여넣으면 <strong>404(Not Found)</strong> 오류가 발생했다.</p>
<p>기능을 확인하는 과정에서 한 가지 의문이 들었다.</p>
<blockquote>
<p>“공유하기는 보통 <strong>현재 페이지 주소(URL)만 복사하면 되는 기능</strong>인데,<br>왜 백엔드에 공유 링크를 생성하는 로직이 존재하지?”</p>
</blockquote>
<p>해당 기능을 구현한 팀원은 발표용 PPT 준비로 여유가 없는 상황이었고,<br>내가 대신 원인을 정리하고 수정 방향을 제안하게 되었다.</p>
<hr>
<h3 id="🔍-원인-분석">🔍 원인 분석</h3>
<p>이번 이슈의 핵심은 <strong>공유 링크 생성 책임이 애매하게 분산되어 있었다는 점</strong>이었다.</p>
<ul>
<li>기존 구조에서는<ul>
<li>방 등록 시점에 <strong>백엔드가 shareLink를 생성</strong></li>
<li>프론트는 해당 값을 그대로 사용</li>
</ul>
</li>
<li>하지만<ul>
<li>프론트 라우팅 규칙과 실제 생성된 링크가 맞지 않으면서</li>
<li>복사한 링크로 접근 시 404가 발생했다.</li>
</ul>
</li>
</ul>
<p>또한 공유 링크는:</p>
<ul>
<li>서버 상태나 DB와 강하게 연결될 필요가 없고</li>
<li><strong>프론트 라우팅 규칙만 알면 충분히 생성 가능한 값</strong>이었다.</li>
</ul>
<hr>
<h3 id="🛠-해결-방법">🛠 해결 방법</h3>
<p>팀원과 상의 후, 다음과 같은 방향으로 구조를 단순화했다.</p>
<h4 id="✅-1-백엔드-공유-링크-자동-생성-로직-제거">✅ 1) 백엔드 공유 링크 자동 생성 로직 제거</h4>
<ul>
<li>방 등록 시 shareLink를 생성하던 백엔드 로직은 <strong>주석 처리</strong></li>
<li>발표 일정상 리스크를 줄이기 위해<ul>
<li>백엔드 변경은 최소화</li>
<li>프론트에서 책임을 가져가는 방향 선택</li>
</ul>
</li>
</ul>
<h4 id="✅-2-공유-링크-생성-책임을-프론트로-이동">✅ 2) 공유 링크 생성 책임을 프론트로 이동</h4>
<ul>
<li>프론트에서 방 ID를 기준으로 <strong>공유 URL을 직접 생성</strong></li>
<li>서버 응답에 의존하지 않도록 변경</li>
</ul>
<pre><code class="language-ts">// room.ts
const shareLinkUrl =
  roomId ? `${window.location.origin}/rooms/${roomId}` : undefined;



3) 공유 버튼 클릭 시 클립보드 복사만 수행

공유 버튼은 링크를 생성하지 않고

이미 프론트에서 계산된 URL을 복사하는 역할만 담당

const handleShareLink = async (room: RoomSummary) =&gt; {
  const link = room.shareLinkUrl;

  if (!link) {
    alert(&quot;공유 링크가 준비되지 않았습니다. 관리자에게 문의해 주세요.&quot;);
    return;
  }

  try {
    if (navigator.clipboard?.writeText) {
      await navigator.clipboard.writeText(link);
      alert(&quot;공유 링크가 클립보드에 복사되었습니다.&quot;);
    } else {
      // 구형 브라우저 대비
      window.prompt(&quot;이 링크를 복사해 주세요.&quot;, link);
    }
  } catch {
    window.prompt(&quot;이 링크를 복사해 주세요.&quot;, link);
  }
};</code></pre>
<p>✅ 결과</p>
<p>공유 링크 복사 → 붙여넣기 시 발생하던 404 문제가 해결되었고</p>
<p>공유 기능의 책임이 명확해졌다:</p>
<p>URL 생성 → 프론트</p>
<p>복사 동작 → 공유 버튼</p>
<p>발표(PPT) 준비 일정에도 영향을 주지 않고 안정적으로 기능을 마무리할 수 있었다.</p>
<p>📌 배운 점</p>
<p>단순한 기능일수록 책임을 한 곳에 모으는 것이 유지보수에 유리하다.</p>
<p>공유 링크처럼</p>
<p>라우팅 규칙에만 의존하는 값은</p>
<p>굳이 백엔드에서 관리하지 않아도 된다.</p>
<p>협업 상황에서는</p>
<p>“누가 만들었는가”보다</p>
<p>현재 상황에 맞는 가장 단순하고 안전한 해결책을 선택하는 것이 중요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[## 10. Git 충돌 / fast-forward 오류]]></title>
            <link>https://velog.io/@seojaeyeong-051/10.-Git-%EC%B6%A9%EB%8F%8C-fast-forward-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@seojaeyeong-051/10.-Git-%EC%B6%A9%EB%8F%8C-fast-forward-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Mon, 15 Dec 2025 08:21:24 GMT</pubDate>
            <description><![CDATA[<h3 id="📝-문제-상황">📝 문제 상황</h3>
<ul>
<li>팀원이 <code>main</code> 브랜치에 push</li>
<li>로컬에서 <code>git pull</code>을 했는데 오류 발생</li>
<li>다음과 같은 메시지를 보게 됨</li>
</ul>
<pre><code class="language-bash">fatal: Not possible to fast-forward, aborting.</code></pre>
<p>또는</p>
<p>pull은 했는데 코드가 꼬이거나</p>
<p>브랜치 상태가 예상과 다르게 변함</p>
<p>즉,</p>
<p>“팀원이 올린 코드를 받아오려 했는데, Git이 거부한 상황”</p>
<p>🔍 원인 정리
이 문제는 대부분 로컬 브랜치와 원격 브랜치의 커밋 히스토리가 갈라졌을 때(diverge) 발생한다.</p>
<p>1️⃣ 로컬 커밋과 원격 커밋이 서로 다른 방향으로 진행됨
로컬 main 브랜치에서 커밋을 한 상태</p>
<p>동시에 원격 origin/main에도 다른 커밋이 추가됨</p>
<p>이 경우 Git은:</p>
<p>“어느 쪽이 기준인지 모르겠다”
→ fast-forward 불가</p>
<p>2️⃣ fast-forward 이란?</p>
<p>히스토리가 갈라지지 않았을 때,
병합 커밋 없이 브랜치가 직선으로 이어지는 상태를 말한다.</p>
<p>팀 프로젝트에서는
각자 브랜치에서 작업한 결과를 main에 병합하기 때문에
fast-forward가 되지 않는 구조가 오히려 자연스럽다.</p>
<p>반대로 개인 프로젝트에서는
히스토리가 갈라질 일이 거의 없으므로
fast-forward가 더 단순하고 안전하다.</p>
<pre><code class="language-text">
A -- B -- C  (origin/main)
          \
           D  (local main)</code></pre>
<p>이 구조에서는:</p>
<p>단순히 앞으로 당길 수 없음</p>
<p>반드시 merge 또는 rebase가 필요</p>
<p>3️⃣ pull.rebase 옵션의 영향
환경에 따라 Git 설정이 다음과 같을 수 있다.</p>
<pre><code class="language-bash">git config --global pull.rebase true</code></pre>
<p>이 경우:</p>
<pre><code>git pull = fetch + rebase</code></pre><p>rebase란 내 커밋들을 다른 브랜치의 최신 커밋 뒤로 “다시 얹는” 작업이다.</p>
<p>충돌 발생 시 더 복잡해 보일 수 있음</p>
<p>원인을 모르면 “왜 갑자기 안 되지?”라는 느낌을 받게 됨</p>
<p>🛠 해결 방법
✅ 1) 무조건 git fetch부터</p>
<pre><code class="language-bash">git fetch origin</code></pre>
<p>원격 변경사항을 작업 트리에 반영하지 않고</p>
<p>상태만 먼저 확인</p>
<pre><code class="language-bash">git log --oneline --graph --all</code></pre>
<p>→ 브랜치가 어떻게 갈라졌는지 시각적으로 확인 가능</p>
<p>✅ 2) merge로 충돌을 직접 해결</p>
<pre><code class="language-bash">git merge origin/main</code></pre>
<p>충돌 발생 시</p>
<p>파일 열어서 직접 수정</p>
<pre><code>git add

git commit</code></pre><p>👉 팀 프로젝트에서는 가장 안전한 방식</p>
<p>✅ 3) 로컬 작업이 필요 없을 경우</p>
<pre><code class="language-bash">git reset --hard origin/main</code></pre>
<p>로컬 변경사항 전부 폐기</p>
<p>원격 상태와 완전히 동일하게 맞춤</p>
<p>⚠️ 주의: 협업 중에는 신중히 사용</p>
<p>✅ 4) 브랜치 전략 재정의
문제의 근본 원인은 브랜치 운영 규칙 부재인 경우가 많다.</p>
<p>main 직접 작업 ❌</p>
<p>기능 단위로 feature 브랜치 사용</p>
<p>PR 기반으로 merge</p>
<p>예:</p>
<pre><code class="language-text">feature/login
feature/oauth
feature/ban</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[## 8. Nginx + HTTPS 구성 오류]]></title>
            <link>https://velog.io/@seojaeyeong-051/8.-Nginx-HTTPS-%EA%B5%AC%EC%84%B1-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@seojaeyeong-051/8.-Nginx-HTTPS-%EA%B5%AC%EC%84%B1-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Mon, 15 Dec 2025 08:21:03 GMT</pubDate>
            <description><![CDATA[<h2 id="8-nginx--https-구성-오류">8. Nginx + HTTPS 구성 오류</h2>
<p>📝 문제 상황</p>
<p>HTTPS 적용 후 프론트/백엔드 연결이 갑자기 안 됨</p>
<p>또는 redirect 루프 발생</p>
<p>EC2 + Nginx + Spring Boot 조합에서 흔한 상황</p>
<p>🔍 원인</p>
<p>80 → 443 redirect 설정 오류</p>
<p>프록시 경로(/api) mismatch</p>
<p>Nginx에서 CORS or host header 제대로 안 넘김</p>
<p>Certbot SSL 설정 파일 충돌</p>
<p>🛠 해결 방법</p>
<p>/etc/nginx/sites-enabled/default 구조 정리</p>
<p>proxy_pass 정확하게 / or /api 구분</p>
<p>React dev server와 Nginx path 충돌 해결</p>
<p>certbot이 만든 파일 중복 제거</p>
<p>📌 배운 점</p>
<p>HTTPS는 “SSL 설정 + proxy 설정 + redirect 설정” 세 가지가 모두 맞아야 돌아간다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[## 6. 좋아요(찜) 기능이 작동하지 않는 문제]]></title>
            <link>https://velog.io/@seojaeyeong-051/6.-%EC%A2%8B%EC%95%84%EC%9A%94%EC%B0%9C-%EA%B8%B0%EB%8A%A5%EC%9D%B4-%EC%9E%91%EB%8F%99%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@seojaeyeong-051/6.-%EC%A2%8B%EC%95%84%EC%9A%94%EC%B0%9C-%EA%B8%B0%EB%8A%A5%EC%9D%B4-%EC%9E%91%EB%8F%99%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Mon, 15 Dec 2025 08:20:42 GMT</pubDate>
            <description><![CDATA[<h2 id="6-좋아요찜-기능이-작동하지-않는-문제">6. 좋아요(찜) 기능이 작동하지 않는 문제</h2>
<p>📝 문제 상황</p>
<p>클릭하면 UI에서는 반응하는 것처럼 보이는데</p>
<p>실제 DB 저장도 안 되고, 프로필 화면에서도 반영 안 됨.</p>
<p>isFavorite 상태가 꼬여서 UI만 업데이트됨.</p>
<p>🔍 원인</p>
<p>배열 내부에 isFavorite 값을 넣는 방식 자체가 불안정</p>
<p>Optimistic UI 적용이 중간에 실패</p>
<p>favorites Set 기반 구조와 충돌</p>
<p>🛠 해결 방법</p>
<p>rooms 배열에서 isFavorite 프로퍼티 제거</p>
<p>좋아요 여부는 favorites Set 기반으로만 렌더링</p>
<p>toggleFavorite에서 UI 선반영 제거</p>
<p>fetchFavoriteRooms로 초기 상태 보장</p>
<p>📌 배운 점</p>
<p>“좋아요 기능”은 global state가 불안정하면 쉽게 꼬인다 → Set 기반 관리가 가장 안정적.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[## 5. React + MUI 타입 오류 — “{} is not assignable to ReactNode”]]></title>
            <link>https://velog.io/@seojaeyeong-051/5.-React-MUI-%ED%83%80%EC%9E%85-%EC%98%A4%EB%A5%98-is-not-assignable-to-ReactNode</link>
            <guid>https://velog.io/@seojaeyeong-051/5.-React-MUI-%ED%83%80%EC%9E%85-%EC%98%A4%EB%A5%98-is-not-assignable-to-ReactNode</guid>
            <pubDate>Mon, 15 Dec 2025 08:20:21 GMT</pubDate>
            <description><![CDATA[<h2 id="5-react--mui-타입-오류---is-not-assignable-to-reactnode">5. React + MUI 타입 오류 — “{} is not assignable to ReactNode”</h2>
<h3 id="📝-문제-상황">📝 문제 상황</h3>
<p>MUI 컴포넌트(<code>Box</code> 등)를 사용하는 과정에서 다음과 같은 TypeScript 오류가 발생했다.</p>
<p>{} 형식은 ReactNode에 할당할 수 없습니다.</p>
<p>yaml
코드 복사</p>
<p>렌더링 과정에서 JSX의 <code>children</code> 위치에<br>TypeScript가 허용하지 않는 타입이 전달되며 오류가 발생했다.</p>
<hr>
<h3 id="🔍-원인">🔍 원인</h3>
<p>React에서 <code>children</code>으로 전달될 수 있는 값은 <code>ReactNode</code>로 제한된다.</p>
<ul>
<li>문자열, 숫자</li>
<li>JSX 요소</li>
<li><code>null</code>, <code>undefined</code>, <code>false</code></li>
<li>위 타입들의 배열</li>
</ul>
<p>반면 <strong>객체(Object)는 렌더링 대상이 아니기 때문에</strong><br>JSX에서 그대로 출력하려 할 경우 타입 오류가 발생한다.</p>
<p>이 오류는 MUI <code>Box</code>처럼 <code>children: ReactNode</code>를 그대로 사용하는 컴포넌트에서
특히 자주 드러난다.</p>
<hr>
<h3 id="🛠-해결-방법">🛠 해결 방법</h3>
<p>JSX에서 객체를 직접 렌더링하지 않고,<br>실제로 화면에 표시할 수 있는 값만 추출해 컴포넌트로 감싸도록 수정했다.</p>
<pre><code class="language-tsx">{value &amp;&amp; &lt;Badge&gt;{value}&lt;/Badge&gt;}
또한 조건부 렌더링을 통해
렌더링 가능한 경우에만 컴포넌트를 출력하도록 방어 로직을 추가했다.

📌 배운 점
JSX의 children에는 반드시 ReactNode만 전달되어야 한다.

TypeScript는 이러한 문제를 런타임 이전에 잡아준다.

MUI + TypeScript 조합에서는
“이 값이 실제로 화면에 그려질 수 있는가?” 를 항상 의식해야 한다.

yaml
코드 복사</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[## 4. OAuth2 Redirect 경로 문제 — 로그인 후 원하는 페이지로 이동하지 않는 문제]]></title>
            <link>https://velog.io/@seojaeyeong-051/4.-OAuth2-Redirect-%EA%B2%BD%EB%A1%9C-%EB%AC%B8%EC%A0%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%9B%84-%EC%9B%90%ED%95%98%EB%8A%94-%ED%8E%98%EC%9D%B4%EC%A7%80%EB%A1%9C-%EC%9D%B4%EB%8F%99%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@seojaeyeong-051/4.-OAuth2-Redirect-%EA%B2%BD%EB%A1%9C-%EB%AC%B8%EC%A0%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%9B%84-%EC%9B%90%ED%95%98%EB%8A%94-%ED%8E%98%EC%9D%B4%EC%A7%80%EB%A1%9C-%EC%9D%B4%EB%8F%99%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Mon, 15 Dec 2025 08:19:54 GMT</pubDate>
            <description><![CDATA[<h3 id="📝-문제-상황">📝 문제 상황</h3>
<ul>
<li>Google OAuth2 로그인은 <strong>성공</strong>함</li>
<li>하지만 로그인 이후 항상 <code>/</code>(루트)로만 이동</li>
<li>실제로는 <code>/home</code> 또는 특정 페이지로 이동시키고 싶었음</li>
<li><code>redirect_uri</code>를 넘기거나 설정을 바꿔도 <strong>무시되는 것처럼 보였음</strong></li>
</ul>
<p>즉,</p>
<blockquote>
<p><strong>“OAuth2 로그인은 됐는데, 리다이렉트 제어가 전혀 안 되는 상황”</strong></p>
</blockquote>
<hr>
<h3 id="🔍-원인-정리">🔍 원인 정리</h3>
<p>이 문제의 핵심은 <strong>Spring Security OAuth2의 기본 redirect 동작이 매우 강력</strong>하다는 점이다.<br>의도적으로 override 하지 않으면, 개발자가 설정한 값이 적용되지 않을 수 있다.</p>
<hr>
<h4 id="1️⃣-customoauth2successhandler가-실제로-적용되지-않음">1️⃣ <code>CustomOAuth2SuccessHandler</code>가 실제로 적용되지 않음</h4>
<p>Spring Security는 SuccessHandler를 명시하지 않으면 기본 핸들러를 사용한다.</p>
<ul>
<li><code>SavedRequestAwareAuthenticationSuccessHandler</code></li>
<li>또는 <code>defaultSuccessUrl(&quot;/&quot;)</code></li>
</ul>
<p>즉,</p>
<ul>
<li><code>CustomOAuth2SuccessHandler</code>를 구현했더라도</li>
<li><code>SecurityConfig</code>에 <strong>명확히 등록하지 않으면</strong></li>
<li>기본 SuccessHandler가 그대로 실행됨</li>
</ul>
<p>➡ 그 결과, 항상 <code>/</code> 또는 기본 URL로 이동</p>
<hr>
<h4 id="2️⃣-frontend-redirect_uri와-backend-처리-방식-불일치">2️⃣ Frontend <code>redirect_uri</code>와 Backend 처리 방식 불일치</h4>
<ul>
<li>프론트에서는 <code>redirect_uri</code>를 전달했지만</li>
<li>백엔드에서는 이를:<ul>
<li>읽지 않거나</li>
<li>다른 파라미터 이름으로 기대하거나</li>
<li>아예 사용하지 않았을 가능성</li>
</ul>
</li>
</ul>
<p>OAuth2에서는<br><strong>“프론트가 보내는 redirect 정보”와<br>“백엔드가 실제로 사용하는 redirect 기준”이 정확히 일치해야 한다.</strong></p>
<hr>
<h4 id="3️⃣-defaultsuccessurl-또는-oauth2-기본-설정이-우선-적용됨">3️⃣ <code>defaultSuccessUrl()</code> 또는 OAuth2 기본 설정이 우선 적용됨</h4>
<p>다음과 같은 설정이 존재하면:</p>
<pre><code class="language-java">.formLogin()
  .defaultSuccessUrl(&quot;/&quot;)</code></pre>
<p>이 설정이 CustomSuccessHandler보다 우선 적용되어
직접 구현한 redirect 로직이 무력화될 수 있다.</p>
<h3 id="문제-해결-방법">문제 해결 방법</h3>
<p>2) SecurityConfig에서 SuccessHandler를 명확히 지정</p>
<pre><code class="language-java">.oauth2Login(oauth -&gt; oauth
    .userInfoEndpoint(userInfo -&gt; userInfo.userService(customUserService))
    .successHandler(customSuccessHandler)
)</code></pre>
<p>➡ “OAuth2 로그인 성공 시 이 핸들러를 사용하라”고
Security 설정에서 명시적으로 선언</p>
<p>✅ 3) defaultSuccessUrl() 제거 또는 OAuth2 영역에서 사용하지 않기</p>
<p>OAuth2를 커스터마이징하는 경우</p>
<p>defaultSuccessUrl()은 혼선을 유발할 수 있음</p>
<p>redirect 로직은 SuccessHandler 하나로 통일</p>
<p>✅ 4) 프론트 ↔ 백엔드 redirect 책임 분리</p>
<p>프론트가 redirect를 제어할지</p>
<p>백엔드가 redirect를 전담할지</p>
<p>👉 둘 중 하나만 책임지도록 명확히 정해야 한다.
(혼합하면 현재와 같은 문제가 반복됨)</p>
<p>OAuth2 로그인 성공 시 커스텀 redirect 로직이 확실히 적용되도록
CustomOAuth2SuccessHandler를 SecurityConfig에 명시적으로 등록했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인증 구조 불일치가 만들어낸 문제들: 쿠키 vs sessionStorage 충돌 해결기]]></title>
            <link>https://velog.io/@seojaeyeong-051/%EC%9D%B8%EC%A6%9D-%EA%B5%AC%EC%A1%B0-%EB%B6%88%EC%9D%BC%EC%B9%98%EA%B0%80-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%82%B8-%EB%AC%B8%EC%A0%9C%EB%93%A4-%EC%BF%A0%ED%82%A4-vs-sessionStorage-%EC%B6%A9%EB%8F%8C-%ED%95%B4%EA%B2%B0%EA%B8%B0</link>
            <guid>https://velog.io/@seojaeyeong-051/%EC%9D%B8%EC%A6%9D-%EA%B5%AC%EC%A1%B0-%EB%B6%88%EC%9D%BC%EC%B9%98%EA%B0%80-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%82%B8-%EB%AC%B8%EC%A0%9C%EB%93%A4-%EC%BF%A0%ED%82%A4-vs-sessionStorage-%EC%B6%A9%EB%8F%8C-%ED%95%B4%EA%B2%B0%EA%B8%B0</guid>
            <pubDate>Mon, 15 Dec 2025 08:11:28 GMT</pubDate>
            <description><![CDATA[<h1 id="oauth2--jwt--cookie-전환-시-발생한-오류-유형별-정리">OAuth2 + JWT + Cookie 전환 시 발생한 오류 유형별 정리</h1>
<p>쿠키 기반 인증을 도입하는 과정에서
React와 Spring Security(OAuth2, JWT) 사이에 인증 방식 불일치로
총 4가지 대표적인 오류가 발생했다.</p>
<h3 id="🟥-오류-유형-1--쿠키-기반-인증으로-바꿨는데-로그인-유지가-안-되는-문제">🟥 오류 유형 1 — 쿠키 기반 인증으로 바꿨는데 로그인 유지가 안 되는 문제</h3>
<p>📌 문제 상황</p>
<p>OAuth2 로그인 성공 후 백엔드에서 HttpOnly 쿠키로 토큰을 전달했음.</p>
<p>하지만 프론트는 로그인 상태를 sessionStorage 기반으로 판단하고 있었음.</p>
<p>그 결과:</p>
<p>쿠키에는 토큰이 존재함</p>
<p>sessionStorage에는 없음
→ 프론트는 계속 “로그인 안 됨”으로 판단
→ 홈 화면 또는 로그인 화면으로만 리다이렉트됨</p>
<p>📌 원인</p>
<p>백엔드는 “쿠키 기반 인증”, 프론트는 “sessionStorage 기반 인증”
→ 인증 판단 기준이 완전히 달라졌기 때문.</p>
<p>📌 발생 조건</p>
<p>OAuth2SuccessHandler에서 쿠키 적용 시작</p>
<p>/api/me 기반 인증 로직이 없음</p>
<p>프론트가 sessionStorage만 읽고 상태 판단</p>
<p>📌 해결 방법</p>
<p>인증 판단을 sessionStorage에서 쿠키 기반 + /api/me 조회 방식으로 전환</p>
<p>axios 요청에 withCredentials: true 설정</p>
<p>로그인 여부는 sessionStorage가 아니라 백엔드 검증 기반으로 판단</p>
<h2 id="🟥-오류-유형-2--httponly-쿠키가-js에서-읽히지-않는-문제">🟥 오류 유형 2 — HttpOnly 쿠키가 JS에서 읽히지 않는 문제</h2>
<p>📌 문제 상황</p>
<p>React에서 쿠키 값을 확인하려 했지만 결과값이 계속 undefined.</p>
<p>📌 원인</p>
<p>HttpOnly 쿠키는 원래 JS에서 읽을 수 없음.
이 속성은 보안(XSS 차단)을 위해 존재하는 것.</p>
<p>즉, JS에서 읽히지 않는 것이 “정상 동작”임.</p>
<p>📌 발생 조건</p>
<p>React에서 document.cookie 또는 cookie-parser로 접근하려고 함</p>
<p>HttpOnly 옵션이 적용된 쿠키였음</p>
<p>📌 해결 방법</p>
<p>쿠키를 직접 읽지 말고 /api/me 같은 사용자 정보 API에서 받은 응답으로 인증 여부 판단</p>
<p>모든 요청에 withCredentials: true 추가</p>
<p>핵심: “쿠키를 읽는다”가 아니라 “쿠키를 서버에 전송해서 인증한다”</p>
<h2 id="🟥-오류-유형-3--react에서-인증-여부-판단-불일치-→-무한-리다이렉트--로그인-안-됨">🟥 오류 유형 3 — React에서 인증 여부 판단 불일치 → 무한 리다이렉트 / 로그인 안 됨</h2>
<p>📌 문제 상황</p>
<p>다음과 같은 코드가 존재했음:</p>
<p>if (!sessionStorage.getItem(&quot;username&quot;)) {
  navigate(&quot;/&quot;);
}</p>
<p>하지만 백엔드는 쿠키 기반 인증이라
sessionStorage에는 아무것도 없었음.</p>
<p>→ 프론트가 계속 “로그인 안 됨”이라고 오판
→ 홈 화면으로 강제 이동 또는 무한 redirect 발생</p>
<p>📌 원인</p>
<p>React 인증 상태 판단이 sessionStorage에 의존하고 있었음.</p>
<p>백엔드 인증 방식이 변경되었음에도 이를 반영하지 않은 구조적 문제.</p>
<p>📌 발생 조건</p>
<p>쿠키 기반 인증 도입</p>
<p>sessionStorage 사용 코드 삭제되지 않음</p>
<p>/api/me 호출 구조 미비</p>
<p>📌 해결 방법</p>
<p>로그인 여부를 sessionStorage → /api/me 방식으로 전환</p>
<p>sessionStorage 기반 인증 로직 제거</p>
<p>AuthContext에서 쿠키 기반 인증으로 변경</p>
<h2 id="🟥-오류-유형-4--useeffect에서-인증-체크가-잘못되어-무한-호출--redirect-반복">🟥 오류 유형 4 — useEffect에서 인증 체크가 잘못되어 무한 호출 / redirect 반복</h2>
<p>📌 문제 상황</p>
<p>다음과 같은 useEffect 로직이 존재했음:</p>
<p>useEffect(() =&gt; {
  if (!sessionStorage.getItem(&quot;username&quot;)) {
    navigate(&quot;/&quot;);
  }
}, []);</p>
<p>Cookie → sessionStorage 불일치 때문에 해당 로직이 계속 발동해</p>
<p>로그인 성공 후에도 계속 &quot;/&quot;로 이동</p>
<p>useEffect 내부에서 navigate 반복 → 무한 루프처럼 보임</p>
<p>📌 원인</p>
<p>인증 판단 로직이 쿠키 구조를 반영하지 않고 sessionStorage만 검사했기 때문.
즉, useEffect는 “정상적으로 잘못된 조건”을 반복적으로 만족하게 된 것.</p>
<p>📌 발생 조건</p>
<p>쿠키 기반 인증으로 전환</p>
<p>sessionStorage 값 없음</p>
<p>useEffect가 최초 렌더링 때 항상 조건 만족 → redirect 실행</p>
<p>📌 해결 방법</p>
<p>useEffect의 인증 판단 로직을 /api/me 기반으로 변경</p>
<p>쿠키 기반 인증 여부를 비동기 로드 후 루트 경로 이동 여부 결정</p>
<p>sessionStorage 활용 코드 제거</p>
<h1 id="📌-총정리">📌 총정리</h1>
<p>이 네 가지 문제는 모두 같은 뿌리에서 시작됨:</p>
<p>“백엔드는 쿠키 기반 인증으로 전환했는데
프론트는 여전히 sessionStorage 기반 인증을 유지하고 있었다.”</p>
<p>그 결과:</p>
<p>로그인 유지 안 됨</p>
<p>리다이렉트 반복</p>
<p>쿠키 읽기 불가</p>
<p>OAuth2 정상동작 안 함</p>
<p>등 여러 문제가 다른 증상처럼 보였지만 하나의 근본 원인에서 발생했다.</p>
<p>공통 해결 전략 — 인증 판단 기준을 /api/me로 통일</p>
<p>위 4가지 오류의 공통점은 모두 동일했다.</p>
<p>백엔드는 쿠키 기반 인증을 사용하고 있는데,
프론트는 여전히 sessionStorage 기준으로 로그인 여부를 판단하고 있었다.</p>
<p>그래서 설계적으로는 다음 전략이 가장 깔끔했다.</p>
<p>/api/me 호출로 서버에서 로그인 상태 검증</p>
<p>axios 요청에 withCredentials: true 적용</p>
<p>React 인증 상태(AuthContext)를 /api/me 응답으로 초기화</p>
<p>sessionStorage 기반 인증 로직 제거</p>
<p>🔁 현실적인 선택 — /api/me 대신 Redirect Token 방식으로 Rollback
📌 배경</p>
<p>설계적으로는 /api/me 기반 인증 구조가 가장 이상적이었다.
하지만 팀 프로젝트 환경에서는 현실적인 제약이 존재했다.</p>
<p>⚠️ 현실적인 문제</p>
<p>로그인 프론트엔드를 다른 팀원이 전담</p>
<p>해당 코드 구조를 즉시 파악하거나 수정하기 어려운 상황</p>
<p>/api/me 전환 시:</p>
<p>AuthContext 구조 변경</p>
<p>초기 렌더링 로직 수정</p>
<p>로그인/로그아웃 흐름 전반 수정 필요</p>
<p>👉 일정과 리스크를 고려했을 때 즉각적인 구조 변경이 어려웠다.</p>
<p>🔄 선택한 대응</p>
<p>최종적으로는 다음과 같은 rollback 전략을 선택했다.</p>
<p>OAuth2 로그인 성공 시
→ 기존 방식대로 redirect URL에 accessToken / refreshToken 포함</p>
<p>프론트는
→ 이미 구현된 sessionStorage 기반 로그인 로직 유지</p>
<p>Ban 사용자만 예외적으로
→ ?error=banned_user 쿼리 파라미터로 redirect 처리</p>
<p>즉,</p>
<p>설계적으로는 /api/me가 정답이지만,
팀 협업 상황과 프로젝트 안정성을 고려해 기존 방식으로 rollback했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[오류 해결 # 1]]></title>
            <link>https://velog.io/@seojaeyeong-051/%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-1</link>
            <guid>https://velog.io/@seojaeyeong-051/%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-1</guid>
            <pubDate>Mon, 15 Dec 2025 07:53:19 GMT</pubDate>
            <description><![CDATA[<p>🚨 1. 오류 원인 #1 — 쿠키 기반 인증 적용 시 로그인 실패 &amp; 홈 화면으로만 리다이렉트되는 문제
📌 문제 상황</p>
<p>원래는 sessionStorage 에 저장된 값(토큰/username 등)을 기반으로 로그인 여부를 판단하고 있었음.</p>
<p>그러나 백엔드에서 OAuth2 성공 후
➡ 쿠키(HttpOnly) 로 토큰을 전달하는 방식으로 바꾸면서
프론트는 sessionStorage만 보고 있었기 때문에:</p>
<p>쿠키에는 토큰이 있음</p>
<p>sessionStorage에는 없음
→ 프론트는 “로그인 안 됨”으로 판단함
→ 홈 화면으로 계속 리다이렉트되는 현상 발생</p>
<p>📌 원인 정리</p>
<p>“백엔드는 쿠키 기반 인증으로 변경했는데, 프론트는 여전히 sessionStorage 기준으로 인증 상태를 판단한 것”</p>
<p>즉, 백과 프론트의 인증 저장 방식 불일치가 원인.</p>
<p>📌 발생 조건</p>
<p>OAuth2 로그인 성공 후 토큰을 쿠키에 저장하는 구조로 전환</p>
<p>프론트는 sessionStorage만 검사</p>
<p>/api/me 호출 또는 쿠키 기반 인증 검증 로직이 없었음</p>
<p>📌 해결 방법</p>
<p>프론트는 쿠키 기반 인증 방식으로 업데이트</p>
<p>로그인 여부를 판단할 때 sessionStorage 대신
→ /api/me 호출
혹은
→ 쿠키를 포함한 axios 요청(withCredentials: true) 사용</p>
<p>로컬 로그인도 동일하게</p>
<p>쿠키 세팅 유지</p>
<p>인증 검증은 동일하게 /api/me에서 처리</p>
<p>백엔드: OAuth2SuccessHandler → redirect 유지</p>
<p>프론트: sessionStorage 기반 인증 제거</p>
<p>📌 정리 문장(벨로그용)</p>
<p>“쿠키 기반 인증을 도입했지만 프론트가 계속 sessionStorage를 기준으로 로그인 여부를 판단하고 있었기 때문에 인증이 정상적으로 완료되어도 로그인 처리되지 않고 홈 화면으로만 리다이렉트되는 문제가 발생했다. 이는 인증 저장 방식 불일치에서 기인한 문제였다.”</p>
<p>🚨 2. 오류 원인 #2 — Ban 처리 시 특정 경우 로그인되는 문제 (Local/OAuth2 처리 불일치)
📌 문제 상황</p>
<p>Ban된 사용자라면:</p>
<p>로컬 로그인 → 403 Forbidden</p>
<p>OAuth2 로그인 → redirect + error=banned_user</p>
<p>이렇게 모두 막혀야 정상이다.</p>
<p>하지만 실제로는 다음 현상이 발생함:</p>
<p>로컬 로그인에서는 정상적으로 ban 처리됨</p>
<p>OAuth2(구글) 로그인에서는 ban 사용자가 로그인 성공처럼 동작</p>
<p>또는 그 반대로 OAuth2에서는 막히지만 로컬에서는 토큰 발급됨</p>
<p>즉, 둘 중 하나의 ban 처리 로직이 씹혀서 적용되지 않는 문제</p>
<p>📌 원인 정리</p>
<p>Ban 검사가 다음 3곳에 분산되어 있었기 때문:</p>
<p>UserDetailsService (로컬 로그인)</p>
<p>OAuth2SuccessHandler</p>
<p>JwtAuthenticationFilter</p>
<p>이 세 곳에서 ban 체크 기준이 다르고 처리 방식도 달랐음.</p>
<p>그 결과:</p>
<p>OAuth2에서는 redirect만 이루어지고 ban 체크 누락</p>
<p>JwtAuthenticationFilter에서는 ban 여부 확인하지 않음</p>
<p>UserDetailsService는 로컬 로그인에서만 동작</p>
<p>➡ 결국 ban 사용자라도 로그인 성공 처리되는 시나리오가 발생했다.</p>
<p>📌 발생 조건</p>
<p>OAuth2SuccessHandler에서 ban 체크가 빠진 상태</p>
<p>JWT 인증 필터가 ban 토큰을 검증하지 않음</p>
<p>UserDetailsService는 로컬 로그인만 커버
→ 세 로직이 “각자 따로 놀면서” 누락 발생</p>
<p>📌 해결 방법</p>
<p>해결 핵심 포인트는 “ban 처리를 모든 인증 경로에서 일관되게 적용하는 것”</p>
<p>Local 로그인 (UserDetailsService)</p>
<p>ban → 403 banned_user JSON 응답</p>
<p>OAuth2 로그인 (CustomSuccessHandler)</p>
<p>ban → sendRedirect(...?error=banned_user)</p>
<p>JWT 인증 (JwtAuthenticationFilter)</p>
<p>ban 사용자의 토큰 → 403 banned_user</p>
<p>프론트는 local/OAuth2 모두 같은 방식으로 경고</p>
<p>axios catch에서 403 처리</p>
<p>OAuth2 redirect param 확인</p>
<p>📌 정리 문장(벨로그용)</p>
<p>“Ban 처리가 UserDetailsService, OAuth2SuccessHandler, JwtAuthenticationFilter로 분산되어 있었고, 각각의 로직이 일관되지 않아 특정 경우에는 ban된 사용자도 로그인되는 문제가 발생했다. 이를 모든 인증 경로(Local/OAuth2/JWT)에 대해 동일한 기준으로 ban 검증 로직을 통일함으로써 해결하였다.”</p>
<p>✨ 최종 — 벨로그에 두 오류를 나누어 설명할 때의 추천 구조</p>
<h1 id="🔥-oauth2--jwt--cookie-기반-인증-통합-중-발생한-문제-원인-정리">🔥 OAuth2 + JWT + Cookie 기반 인증 통합 중 발생한 문제 원인 정리</h1>
<hr>
<h2 id="1-오류-원인-1--쿠키-기반-인증-도입-후-로그인-실패--홈-화면-리다이렉트-문제">1. 오류 원인 #1 — 쿠키 기반 인증 도입 후 로그인 실패 &amp; 홈 화면 리다이렉트 문제</h2>
<p>(설명 + 원인 + 해결 + 정리문장)</p>
<hr>
<h2 id="2-오류-원인-2--ban-처리-불일치로-특정-사용자가-로그인되는-문제">2. 오류 원인 #2 — Ban 처리 불일치로 특정 사용자가 로그인되는 문제</h2>
<p>(설명 + 원인 + 해결 + 정리문장)</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>두 문제는 모두 &quot;백/프론트 인증 흐름의 불일치&quot;와<br>&quot;ban 처리 책임이 분산된 구조&quot;에서 비롯되었으며<br>리팩토링 후 인증 경로를 일원화함으로써 해결되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[### 14편. React 실전 프로젝트 구조 설계]]></title>
            <link>https://velog.io/@seojaeyeong-051/14%ED%8E%B8.-React-%EC%8B%A4%EC%A0%84-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B5%AC%EC%A1%B0-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@seojaeyeong-051/14%ED%8E%B8.-React-%EC%8B%A4%EC%A0%84-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B5%AC%EC%A1%B0-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Fri, 21 Nov 2025 06:49:42 GMT</pubDate>
            <description><![CDATA[<h3 id="14편-react-실전-프로젝트-구조-설계">14편. React 실전 프로젝트 구조 설계</h3>
<p>🟦 포함 내용</p>
<p>폴더 구조 설계</p>
<p>컴포넌트/페이지 분리 기준</p>
<p>API layer 설계</p>
<p>공통 컴포넌트 구조</p>
<p>재사용 가능한 UI 설계</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[### 13편. 최적화 개념 — useMemo / useCallback / memo
]]></title>
            <link>https://velog.io/@seojaeyeong-051/13%ED%8E%B8.-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B0%9C%EB%85%90-useMemo-useCallback-memo</link>
            <guid>https://velog.io/@seojaeyeong-051/13%ED%8E%B8.-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B0%9C%EB%85%90-useMemo-useCallback-memo</guid>
            <pubDate>Fri, 21 Nov 2025 06:49:23 GMT</pubDate>
            <description><![CDATA[<h3 id="13편-최적화-개념--usememo--usecallback--memo">13편. 최적화 개념 — useMemo / useCallback / memo</h3>
<p>🟦 포함 내용</p>
<p>불필요 렌더링이 왜 발생하는가</p>
<p>각 훅의 사용 목적</p>
<p>실전 기준 언제 써야 하는가</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[### 12편. Context API vs Zustand vs Redux
]]></title>
            <link>https://velog.io/@seojaeyeong-051/12%ED%8E%B8.-Context-API-vs-Zustand-vs-Redux</link>
            <guid>https://velog.io/@seojaeyeong-051/12%ED%8E%B8.-Context-API-vs-Zustand-vs-Redux</guid>
            <pubDate>Fri, 21 Nov 2025 06:49:01 GMT</pubDate>
            <description><![CDATA[<h3 id="12편-context-api-vs-zustand-vs-redux">12편. Context API vs Zustand vs Redux</h3>
<p>🟦 필수 개념</p>
<p>전역 상태의 필요성</p>
<p>Context API 한계</p>
<p>Zustand 실전 예</p>
<p>Redux Toolkit 소개</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[### 11편. API 연동 패턴 (axios 기반 실전)]]></title>
            <link>https://velog.io/@seojaeyeong-051/11%ED%8E%B8.-API-%EC%97%B0%EB%8F%99-%ED%8C%A8%ED%84%B4-axios-%EA%B8%B0%EB%B0%98-%EC%8B%A4%EC%A0%84</link>
            <guid>https://velog.io/@seojaeyeong-051/11%ED%8E%B8.-API-%EC%97%B0%EB%8F%99-%ED%8C%A8%ED%84%B4-axios-%EA%B8%B0%EB%B0%98-%EC%8B%A4%EC%A0%84</guid>
            <pubDate>Fri, 21 Nov 2025 06:48:41 GMT</pubDate>
            <description><![CDATA[<h3 id="11편-api-연동-패턴-axios-기반-실전">11편. API 연동 패턴 (axios 기반 실전)</h3>
<p>🟦 핵심 내용</p>
<p>axios 설치 및 기본 셋팅</p>
<p>GET / POST 실전</p>
<p>에러 핸들링</p>
<p>API 요청 구조 설계</p>
<p>React Query 도입 여부</p>
]]></description>
        </item>
    </channel>
</rss>