<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>parkMoen.log</title>
        <link>https://velog.io/</link>
        <description>게시글에 잘못된 부분이 있으면 댓글로 알려주시면 빠르게 수정 및 수용도 하겠습니다. 🥲</description>
        <lastBuildDate>Fri, 30 Jan 2026 16:37:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>parkMoen.log</title>
            <url>https://images.velog.io/images/park-moen/profile/55a9c80a-70f1-4815-bb4d-749032dbdba8/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. parkMoen.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/park-moen" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[OAuth2 쿠키 인증 트러블슈팅: Cross-Domain Cookie 접근 불가 이슈]]></title>
            <link>https://velog.io/@park-moen/OAuth2-%EC%BF%A0%ED%82%A4-%EC%9D%B8%EC%A6%9D-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-Cross-Domain-Cookie-%EC%A0%91%EA%B7%BC-%EB%B6%88%EA%B0%80-%EC%9D%B4%EC%8A%88%EC%B4%88%EC%95%88</link>
            <guid>https://velog.io/@park-moen/OAuth2-%EC%BF%A0%ED%82%A4-%EC%9D%B8%EC%A6%9D-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-Cross-Domain-Cookie-%EC%A0%91%EA%B7%BC-%EB%B6%88%EA%B0%80-%EC%9D%B4%EC%8A%88%EC%B4%88%EC%95%88</guid>
            <pubDate>Fri, 30 Jan 2026 16:37:58 GMT</pubDate>
            <description><![CDATA[<h2 id="1-문제-상황">1. 문제 상황</h2>
<h3 id="현재-아키텍처">현재 아키텍처</h3>
<ul>
<li><strong>Frontend</strong>: <code>localhost:3000</code> (Next.js)</li>
<li><strong>Backend</strong>: <code>unibusk.site</code> (Spring Boot)</li>
<li><strong>인증 방식</strong>: Kakao OAuth2 + HttpOnly Cookie</li>
</ul>
<h3 id="발생한-문제">발생한 문제</h3>
<p>OAuth2 로그인 성공 후 FE 콜백 페이지에서 인증 쿠키에 접근할 수 없어 인증 상태 확인 및 API 요청이 불가능한 상황</p>
<hr>
<h2 id="2-인증-플로우-be-oauth2-워크플로우-기반">2. 인증 플로우 (BE OAuth2 워크플로우 기반)</h2>
<pre><code>Step 1: 사용자 &quot;로그인&quot; 버튼 클릭

Step 2: FE - 로그인 버튼 클릭 시
└─ window.location.href = &quot;&lt;https://unibusk.site/api/auths/login?state=http%3A%2F%2Flocalhost%3A3000%2Fcallback&gt;&quot;
   → 브라우저가 BE 엔드포인트로 하드 리다이렉트 (FE 실행 종료)

Step 3: BE - RedirectUrlFilter에서 state 처리
└─ 내부적으로 /oauth2/authorization/kakao로 리다이렉트
   → state 쿠키에 FE callback URL 저장

Step 4: 사용자 카카오 로그인
└─ 카카오 인증 페이지에서 로그인 완료

Step 5: 카카오 → BE - 인증 코드 전달
└─ &lt;https://unibusk.site/api/auths/login?code=xxx&gt;

Step 6: BE - AuthService.handleLoginSuccess 호출
└─ 토큰 발급 (Access/Refresh Token)
   → TokenInjector를 통해 쿠키에 저장

Step 7: BE - OAuth2LoginSuccessHandler.redirectToSuccessUrl 호출
└─ HTTP/1.1 302 Found
   ├─ Set-Cookie: accessToken=xxx; Domain=unibusk.site; Secure; HttpOnly
   ├─ Set-Cookie: refreshToken=yyy; Domain=unibusk.site; Secure; HttpOnly
   └─ Location: &lt;http://localhost:3000/oauth-callback/kakao&gt;

   ❌ **문제 발생 지점**:
   브라우저가 쿠키를 unibusk.site 도메인에 저장한 후 localhost로 리다이렉트

Step 8: FE - /oauth-callback/kakao 페이지
└─ 쿠키 파라미터 및 쿠키 처리
   → 신규 사용자 → 회원가입 페이지
   → 기존 사용자 → 홈 화면

   ❌ localhost에서 unibusk.site 쿠키 접근 불가</code></pre><hr>
<h2 id="3-근본-원인-리다이렉트-시점의-쿠키-도메인-격리">3. 근본 원인: 리다이렉트 시점의 쿠키 도메인 격리</h2>
<h3 id="핵심-문제">핵심 문제</h3>
<p><code>window.location.href</code>로 OAuth2 로그인 플로우를 시작하면, <strong>BE가 로그인 성공 후 설정한 쿠키는 <code>unibusk.site</code> 도메인에 저장되지만, 리다이렉트로 <code>localhost</code>로 전환되는 순간 해당 쿠키에 접근할 수 있는 기술적 경로가 존재하지 않습니다</strong>.</p>
<h3 id="브라우저의-쿠키-저장-메커니즘">브라우저의 쿠키 저장 메커니즘</h3>
<h3 id="1-쿠키는-정상적으로-저장됨">1. 쿠키는 정상적으로 저장됨</h3>
<pre><code>BE 응답 (Step 7):
HTTP/1.1 302 Found
Set-Cookie: accessToken=xxx; Domain=unibusk.site; Secure; HttpOnly
Location: &lt;http://localhost:3000/oauth-callback/kakao&gt;

→ 브라우저는 Set-Cookie를 보고 자동으로 쿠키 저장
→ 그 후 Location 헤더를 따라 localhost로 이동</code></pre><p><strong>중요</strong>: <code>window.location.href</code> 사용 시 FE 코드는 이 응답을 가로챌 수 없습니다. 모든 처리는 브라우저가 직접 수행합니다.</p>
<h3 id="2-쿠키-저장소는-도메인별로-격리됨">2. 쿠키 저장소는 도메인별로 격리됨</h3>
<pre><code>브라우저 쿠키 저장소:

[&lt;https://unibusk.site&gt;]
├─ accessToken: eyJhbGc...
└─ refreshToken: eyJhbGc...

[&lt;http://localhost:3000&gt;]
└─ (비어있음)

→ 브라우저 보안 정책: 각 도메인은 자신의 저장소만 접근 가능
→ localhost 컨텍스트에서는 unibusk.site 저장소를 볼 수 없음</code></pre><h3 id="3-리다이렉트-후-상태">3. 리다이렉트 후 상태</h3>
<pre><code>Step 8: FE (localhost:3000/oauth-callback/kakao)
현재 실행 컨텍스트:
├─ document.domain: &quot;localhost&quot;
├─ 쿠키 저장 위치: unibusk.site (다른 도메인)
└─ 결과: JavaScript/API 어떤 방법으로도 쿠키 접근 불가

// 콘솔 확인
console.log(document.cookie);  // &quot;&quot; (빈 문자열)
console.log(document.domain);  // &quot;localhost&quot;</code></pre><h3 id="왜-fe가-개입할-수-없는가">왜 FE가 개입할 수 없는가?</h3>
<pre><code>[리다이렉트 체인]
FE (localhost)
    ↓ window.location.href 실행
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[브라우저가 직접 처리 - JS 코드 실행 권한 없음]
    ↓
BE (unibusk.site) OAuth 처리
    ↓
BE가 Set-Cookie + 302 Redirect 응답
    ↓
[브라우저가 직접 처리 - JS 코드 실행 권한 없음]
├─ 쿠키를 unibusk.site에 저장
└─ localhost:3000으로 이동
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ↓
FE (localhost) 페이지 렌더링 시작
└─ 이미 localhost 컨텍스트로 전환됨
   → unibusk.site 쿠키는 접근 불가능한 영역</code></pre><hr>
<h2 id="4-시도한-해결-방법-및-실패-원인">4. 시도한 해결 방법 및 실패 원인</h2>
<h3 id="41-nextjs-rewrites-transparent-proxy">4.1 Next.js Rewrites (Transparent Proxy)</h3>
<p><strong>시도한 방법</strong>:</p>
<pre><code class="language-jsx">// next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: &#39;/api/:path*&#39;,
        destination: &#39;&lt;https://unibusk.site/api/:path*&gt;&#39;,
      },
    ];
  },
};</code></pre>
<p><strong>실패 이유</strong>:</p>
<ul>
<li>Rewrites는 <strong>FE에서 시작한 fetch/axios 요청</strong>만 프록시</li>
<li><code>window.location.href</code>는 브라우저가 직접 처리하여 Next.js 서버 자체에 접근 ❌</li>
<li>OAuth 플로우 전체가 브라우저 레벨에서 발생하므로 프록시 개입 불가</li>
</ul>
<pre><code>실제 흐름:
브라우저 → unibusk.site (브라우저가 이동, 프록시 과정 생략)
         ↓
      OAuth 처리
         ↓
    쿠키 설정 + 리다이렉트
         ↓
브라우저 → localhost (브라우저가 이동, 프록시 과정 생략)

→ Next.js 서버는 이 과정에 전혀 관여하지 않음</code></pre><hr>
<h3 id="42-route-handler를-통한-쿠키-전달">4.2 Route Handler를 통한 쿠키 전달</h3>
<p><strong>시도한 방법</strong>:</p>
<pre><code class="language-tsx">// app/api/members/me/route.ts
export async function GET() {
  const cookieStore = await cookies();
  // cookieStore에는 어떠한 cookie 값이 존재하지 않음

  const response = await fetch(`${ENV.API_URL}/api/members/me`, {
    method: &#39;GET&#39;,
    headers: {
      Cookie: cookieStore.toString(),
    },
    cache: &#39;no-store&#39;,
  });

  const data = await response.json();

  return NextResponse.json(data, { status: 200 });
}</code></pre>
<p><strong>실패 이유</strong>:</p>
<ol>
<li><strong>Route Handler가 호출되지 않음</strong>:<ul>
<li>BE가 브라우저에게 <code>http://localhost:3000/oauth-callback</code>로 이동시킴 (Next 서버 자체가 실행되지 않음)</li>
<li>브라우저가 <strong>페이지를 직접 렌더링</strong> (API Route 건너뜀)</li>
<li>Route Handler는 클라이언트가 명시적으로 요청할 때만 실행</li>
</ul>
</li>
<li><strong>토큰 교환 API 부재</strong>:<ul>
<li>현재 BE는 리다이렉트 응답에만 쿠키를 포함</li>
<li>별도의 토큰을 조회할 수 있는 엔드포인트가 존재하지 않음</li>
</ul>
</li>
<li><strong>쿠키가 이미 없음</strong>:</li>
</ol>
<pre><code class="language-tsx">// Route Handler 내부에서
const cookies = request.cookies;  // 비어있음

// 이유: 브라우저가 localhost 페이지를 요청할 때
// unibusk.site 쿠키는 전송되지 않음</code></pre>
<hr>
<h3 id="43-cloudflare-tunnel">4.3 Cloudflare Tunnel</h3>
<p><strong>시도한 방법</strong>:</p>
<pre><code class="language-bash">cloudflared tunnel --url &lt;http://localhost:3000&gt;</code></pre>
<p><strong>실패 이유</strong>:</p>
<ul>
<li>도메인은 BE에서 Cloudflare에서 배포한 기본 도메인을 CDN으로 설정함</li>
<li>Cloudflare가 생성한 기본 도메인은 Cloudflare Zone 접근 불가</li>
</ul>
<hr>
<h3 id="44-self-signed-https">4.4 Self-signed HTTPS</h3>
<p><strong>시도한 방법</strong>:</p>
<pre><code class="language-bash">next dev --experimental-https</code></pre>
<p><strong>실패 이유</strong>:</p>
<ul>
<li>브라우저가 자체 서명 인증서를 신뢰하지 않음</li>
<li><code>NET::ERR_CERT_AUTHORITY_INVALID</code> 에러 발생</li>
<li>HSTS 정책으로 인해 경고 우회 불가</li>
</ul>
<hr>
<h3 id="45-ip-주소-기반-쿠키-설정">4.5 IP 주소 기반 쿠키 설정</h3>
<p><strong>시도한 방법</strong>:
백엔드를 AWS EC2 IP 주소(<code>13.124.xxx.xxx</code>)로 배포하고, BE 설정에서 쿠키 도메인을 IP로 설정</p>
<pre><code class="language-yaml"># Backend application.yml
cors:
  allowed-origins:
    - &lt;http://localhost:3000&gt;

cookie:
  domain: localhost
  secure: false
  sameSite: Lax</code></pre>
<pre><code class="language-tsx">// FE: localhost:3000
fetch(&#39;http://13.124.xxx.xxx:8080/api/members/me&#39;, {
  credentials: &#39;include&#39;
});</code></pre>
<p><strong>실패 이유</strong>:</p>
<ol>
<li><strong>Public Suffix List 규칙 위반</strong>:</li>
</ol>
<pre><code>브라우저의 Public Suffix List (PSL):
- 쿠키는 &quot;유효한 도메인&quot;에만 설정 가능
- IP 주소는 PSL에서 유효한 도메인으로 인정되지 않음
- 따라서 Set-Cookie 헤더를 브라우저가 무시

예시:
✅ Domain=.example.com → 유효한 eTLD+1
✅ Domain=.unibusk.site → 유효한 eTLD+1
❌ Domain=13.124.xxx.xxx → 유효하지 않음 (IP 주소)
❌ Domain=localhost → 유효하지 않음 (Public Suffix)</code></pre><blockquote>
<p>Public Suffix List (PSL)란?</p>
<p>Mozilla가 관리하는 &quot;쿠키 공유가 허용되는 도메인 목록&quot;입니다.
<code>.com</code>, <code>.co.kr</code> 같은 최상위 도메인과 <code>.github.io</code>, <code>.cloudfront.net</code> 같은 공용 서브도메인을 정의하여, 악의적인 사이트가 다른 사이트의 쿠키를 탈취하는 것을 방지합니다.</p>
<p>IP 주소와 <code>localhost</code>는 PSL에 포함되지 않아 쿠키 도메인으로 사용할 수 없습니다.</p>
</blockquote>
<p><strong>Cross-Origin 쿠키 차단:</strong></p>
<pre><code>요청 출처: &lt;http://localhost:3000&gt;
쿠키 도메인: 13.124.xxx.xxx

→ 브라우저는 이 두 주소를 완전히 다른 출처로 인식
→ localhost와 IP 주소는 &quot;Same-Site&quot; 관계가 아님</code></pre><hr>
<h3 id="실패-사례-요약">실패 사례 요약</h3>
<table>
<thead>
<tr>
<th>방법</th>
<th>시도 내용</th>
<th>실패 원인</th>
</tr>
</thead>
<tbody><tr>
<td>Next.js Rewrites</td>
<td>API 프록시 설정</td>
<td>OAuth 리다이렉트는 브라우저가 직접 처리</td>
</tr>
<tr>
<td>Route Handler</td>
<td>Next.js 서버에서 쿠키 전달</td>
<td>리다이렉트 시점에 Next.js 서버 경유 ❌</td>
</tr>
<tr>
<td>Cloudflare Tunnel</td>
<td>로컬을 외부 도메인으로 노출</td>
<td>Zone 접근 권한 부족</td>
</tr>
<tr>
<td>Self-signed HTTPS</td>
<td>로컬 HTTPS 환경 구성</td>
<td>브라우저가 인증서 신뢰 거부</td>
</tr>
<tr>
<td>IP 기반 쿠키</td>
<td>BE IP로 개발 서버 배포</td>
<td>Public Suffix List 규칙 위반</td>
</tr>
</tbody></table>
<hr>
<h2 id="5-왜-proxymiddleware도-동작하지-않는가">5. 왜 Proxy/Middleware도 동작하지 않는가?</h2>
<h3 id="핵심-브라우저의-localhost-저장소에-쿠키가-없음">핵심: 브라우저의 localhost 저장소에 쿠키가 없음</h3>
<p>리다이렉트 완료 후 <strong>브라우저의 localhost 저장소는 비어있으므로</strong>, 어떤 기술을 사용해도 쿠키를 찾을 수 없습니다.</p>
<h3 id="51-proxy가-쿠키를-전달하지-못하는-이유">5.1 Proxy가 쿠키를 전달하지 못하는 이유</h3>
<pre><code class="language-jsx">// FE 코드
fetch(&#39;/api/user/profile&#39;, { credentials: &#39;include&#39; });

// Proxy가 요청을 전달:
// GET &lt;https://unibusk.site/api/user/profile&gt;
// Cookie: (비어있음)

// 문제: 브라우저가 요청에 첨부할 쿠키가 없음
// → localhost 저장소가 비어있기 때문</code></pre>
<h3 id="52-middleware가-쿠키를-읽지-못하는-이유">5.2 Middleware가 쿠키를 읽지 못하는 이유</h3>
<pre><code class="language-tsx">// middleware.ts
export function middleware(request: NextRequest) {
  const token = request.cookies.get(&#39;accessToken&#39;);  // undefined

  // 브라우저가 Next.js 서버로 요청할 때:
  // GET ap/members/me
  // Cookie: (비어있음) ← unibusk.site 쿠키는 전송 안됨
}</code></pre>
<h3 id="비교표">비교표</h3>
<table>
<thead>
<tr>
<th>접근 방법</th>
<th>동작 여부</th>
<th>실패 이유</th>
</tr>
</thead>
<tbody><tr>
<td><code>document.cookie</code></td>
<td>❌</td>
<td>HttpOnly 설정으로 인해 FE에서 cookie에 접근할 수 없음</td>
</tr>
<tr>
<td><code>fetch(..., {credentials: &#39;include&#39;})</code></td>
<td>❌</td>
<td>브라우저가 요청에 첨부할 쿠키가 없음 (localhost 저장소 비어있음)</td>
</tr>
<tr>
<td>Next.js Rewrites</td>
<td>❌</td>
<td>브라우저 리다이렉트가 프록시가 아닌 브라우저가 http 통신을 함</td>
</tr>
<tr>
<td>Route Handler</td>
<td>❌</td>
<td>브라우저 리다이렉트가 프록시가 아닌 브라우저가 http 통신을 함</td>
</tr>
<tr>
<td>Middleware</td>
<td>❌</td>
<td><code>request.cookies</code>는 현재 도메인(localhost) 쿠키만 포함</td>
</tr>
</tbody></table>
<hr>
<h2 id="6-검증된-해결-방법">6. 검증된 해결 방법</h2>
<p>브라우저의 Cookie Domain Scope 정책을 만족시키기 위해서는 <strong>도메인 통일</strong> 또는 <strong>프록시를 통한 출처 위장</strong>이 필요합니다.</p>
<h3 id="방법-1-풀스택-로컬-환경">방법 1: 풀스택 로컬 환경</h3>
<p><strong>개요</strong>: FE와 BE를 모두 <code>localhost</code>에서 실행</p>
<p><strong>장점</strong>:</p>
<ul>
<li>Cross-Domain 이슈 완전 제거</li>
<li>네트워크 상태 독립적</li>
<li>별도 시스템 설정 불필요</li>
</ul>
<p><strong>단점</strong>:</p>
<ul>
<li>JDK 17+, Gradle 등 Java 개발 환경 구성 필요</li>
<li>로컬 리소스 소모</li>
</ul>
<p><strong>실행 방법</strong>:</p>
<ol>
<li>BE 저장소 클론</li>
<li><code>application-local.yml</code> 설정:</li>
</ol>
<pre><code class="language-yaml">cookie:
  domain: localhost
  secure: false
  sameSite: Lax</code></pre>
<ol>
<li>Spring Boot 애플리케이션 실행</li>
<li>FE에서 <code>http://localhost:8080</code> (BE 포트)로 OAuth 시작</li>
</ol>
<hr>
<h3 id="방법-2-dns-등록--mkcert">방법 2: DNS 등록 + mkcert</h3>
<p><strong>개요</strong>: 도메인 관리 사이트(가비아 등)에서 <code>dev.unibusk.site</code>를 <code>127.0.0.1</code>로 등록하여 로컬을 서브도메인화</p>
<p><strong>장점</strong>:</p>
<ul>
<li><strong>팀원 간 설정 공유 용이</strong>: hosts 파일 수정 없이 도메인만으로 접속 가능</li>
<li>운영 서버 데이터 사용 가능</li>
<li>Java 환경 구성 불필요</li>
<li>실제 배포 환경과 동일한 테스트</li>
</ul>
<p><strong>단점</strong>:</p>
<ul>
<li>도메인 DNS 관리 권한 필요</li>
<li>초기 인증서 발급 필요</li>
<li>BE 측 CORS/Cookie 설정 변경 협의 필요</li>
</ul>
<h3 id="설정-단계">설정 단계</h3>
<p><strong>1. DNS A 레코드 등록</strong> (도메인 관리자만 1회 설정):</p>
<pre><code>호스트: dev
타입: A
값: 127.0.0.1
TTL: 600 (10분)

→ 전 세계 어디서든 dev.unibusk.site 접속 시 로컬 서버로 연결</code></pre><p><strong>2. mkcert로 로컬 인증서 발급</strong> (각 개발자):</p>
<pre><code class="language-bash"># macOS
brew install mkcert
mkcert -install

# Windows
choco install mkcert
mkcert -install

# 인증서 생성
mkcert dev.unibusk.site localhost 127.0.0.1

# 생성된 파일을 프로젝트 .cert 폴더에 저장:
# - dev.unibusk.site+2.pem (인증서)
# - dev.unibusk.site+2-key.pem (개인키)</code></pre>
<p><strong>3. Next.js HTTPS 설정</strong>:</p>
<pre><code class="language-json">// package.json
{
  &quot;scripts&quot;: {
    &quot;dev-https&quot;: &quot;NODE_TLS_REJECT_UNAUTHORIZED=0 next dev --experimental-https --experimental-https-key ./.cert/dev.unibusk.site+2-key.pem --experimental-https-cert ./.cert/dev.unibusk.site+2.pem&quot;
  }
}</code></pre>
<p><strong>4. 환경 변수 설정</strong>:</p>
<pre><code class="language-bash"># .env.local
NEXT_PUBLIC_APP_URL=https://dev.unibusk.site:3000
NEXT_PUBLIC_API_URL=https://unibusk.site
API_URL=https://unibusk.site</code></pre>
<p><strong>5. BE 설정 변경 요청</strong>:</p>
<pre><code class="language-yaml"># Backend application.yml
cors:
  allowed-origins:
    - &lt;https://dev.unibusk.site:3000&gt;

cookie:
  domain: .unibusk.site  # 서브도메인 공유
  secure: true
  sameSite: None

redirect:
  allowed-hosts:
    - dev.unibusk.site</code></pre>
<p><strong>6. OAuth 로그인 실행</strong>:</p>
<pre><code class="language-tsx">// 로그인 버튼 클릭 시
window.location.href = &quot;&lt;https://unibusk.site/api/auths/login?state=https%3A%2F%2Fdev.unibusk.site%3A3000%2Fcallback&gt;&quot;;</code></pre>
<hr>
<h3 id="방법-3-hosts-파일--mkcert">방법 3: hosts 파일 + mkcert</h3>
<p><strong>개요</strong>: <code>/etc/hosts</code> 파일을 수정하여 <code>dev.unibusk.site</code>를 로컬로 매핑</p>
<p><strong>장점</strong>:</p>
<ul>
<li>DNS 관리 권한 불필요</li>
<li>개인 개발 환경 독립적 구성</li>
</ul>
<p><strong>단점</strong>:</p>
<ul>
<li><strong>각 팀원이 hosts 파일을 수동으로 수정</strong>해야 함</li>
<li>OS별 설정 방법이 다름</li>
<li>초기 인증서 발급 필요</li>
<li>BE 측 CORS/Cookie 설정 변경 협의 필요</li>
</ul>
<h3 id="설정-단계-1">설정 단계</h3>
<p><strong>1. Hosts 파일 수정</strong> (각 개발자):</p>
<pre><code class="language-bash"># macOS/Linux
sudo vi /etc/hosts

# Windows
notepad C:\\Windows\\System32\\drivers\\etc\\hosts

# 추가:
127.0.0.1 dev.unibusk.site</code></pre>
<p><strong>2~6</strong>: 방법 2의 2~6 단계와 동일</p>
<hr>
<h3 id="방법-4-nextjs-proxy--authorization-code-교환-코드만으로-해결-🚀">방법 4: Next.js Proxy + Authorization Code 교환 (코드만으로 해결 🚀)</h3>
<p><strong>개요</strong>: BE가 임시 코드만 전달하고, FE가 Next.js 프록시를 통해 토큰을 교환받아 쿠키 출처를 <code>localhost</code>로 위장</p>
<p><strong>핵심 아이디어</strong>:</p>
<pre><code>기존 방식 (실패):
BE → Set-Cookie + Redirect → FE (localhost)
❌ 브라우저가 도메인 불일치로 쿠키 차단</code></pre><pre><code class="language-bash">Proxy 방식 (성공):
Step 1: BE → 임시 코드를 query string으로 FE 콜백 URL로 Redirect
        Location: http://localhost:3000/oauth-callback/kakao?code=temp_abc123
        (Set-Cookie 없음)

Step 2: FE 콜백 페이지 렌더링
        → useEffect에서 searchParams.get(&#39;code&#39;)로 임시 코드 추출

Step 3: FE → Next.js 프록시로 POST 요청
        fetch(&#39;/api/auth/token/exchange&#39;, {
          method: &#39;POST&#39;,
          body: JSON.stringify({ code: &#39;temp_abc123&#39; })
        })
        → 브라우저는 &quot;localhost:3000/api/... 요청&quot;으로 인식

Step 4: Next.js Rewrites → 실제 BE로 프록시
        POST https://unibusk.site/api/auth/token/exchange
        (브라우저는 이 과정을 모름)

Step 5: BE → 임시 코드 검증 후 Set-Cookie 응답
        200 OK
        Set-Cookie: accessToken=xxx; HttpOnly; Path=/; (Domain 없음)
        Set-Cookie: refreshToken=yyy; HttpOnly; Path=/; (Domain 없음)

Step 6: Next.js → 응답을 브라우저에 그대로 전달

Step 7: 브라우저 → &quot;localhost:3000/api/... 요청의 응답&quot;으로 인식
        ✅ Domain이 없는 Set-Cookie를 현재 출처(localhost)의 쿠키로 저장</code></pre>
<p><strong>왜 프록시가 필요한가?:</strong></p>
<pre><code class="language-bash">프록시 없이 직접 호출 시 (실패):
FE: fetch(&#39;https://unibusk.site/api/auth/token/exchange&#39;)
→ Cross-Origin 요청
→ BE가 Set-Cookie 응답 (Domain 없음)
→ 브라우저: &quot;unibusk.site에서 온 응답이구나&quot;
❌ unibusk.site 저장소에 저장 (localhost에서 접근 불가)</code></pre>
<p>프록시 사용 시 (성공):</p>
<pre><code class="language-bash">FE: fetch(&#39;/api/auth/token/exchange&#39;)
→ 브라우저는 &quot;localhost:3000/api/... 요청&quot;으로 인식
→ Next.js가 내부적으로 unibusk.site로 프록시
→ BE가 Set-Cookie 응답 (Domain 없음)
→ Next.js가 응답을 그대로 전달
→ 브라우저: &quot;localhost:3000에서 온 응답이구나&quot;
✅ Domain이 없으므로 요청 출처인 localhost에 저장</code></pre>
<p><strong>장점</strong>:</p>
<ul>
<li>✅ <strong>인프라 설정 Zero</strong>: DNS, hosts, mkcert 불필요</li>
<li>✅ <strong>빠른 구현</strong>: 코드 몇 줄로 즉시 적용</li>
<li>✅ <strong>HttpOnly 쿠키 보안 유지</strong></li>
<li>✅ <strong>팀원 설정 최소화</strong>: npm install만으로 시작</li>
</ul>
<p><strong>단점</strong>:</p>
<ul>
<li>❌ <strong>BE API 변경 필수</strong>: 임시 코드 생성/토큰 교환 엔드포인트 추가</li>
<li>❌ <strong>임시 저장소 필요</strong>: Redis 등 (코드 5분 TTL)</li>
<li>❌ <strong>환경별 Cookie 설정</strong>: 로컬/운영 분기 필요</li>
</ul>
<hr>
<h3 id="⚠️-방법-4-proxy-사용-시-필수-체크리스트">⚠️ 방법 4 (Proxy) 사용 시 필수 체크리스트</h3>
<p><strong>1. Domain 속성 절대 설정 금지</strong>:</p>
<pre><code class="language-java">// ❌ 이렇게 하면 실패
Cookie cookie = new Cookie(&quot;accessToken&quot;, token);
cookie.setDomain(&quot;unibusk.site&quot;); // localhost에서 저장 안됨

// ✅ 이렇게 해야 성공
Cookie cookie = new Cookie(&quot;accessToken&quot;, token);
// setDomain() 호출하지 않음 → 브라우저가 localhost로 설정</code></pre>
<p><strong>2. 임시 코드 보안</strong>:</p>
<pre><code class="language-java">// 1. 충분한 엔트로피
String code = UUID.randomUUID().toString();

// 2. 짧은 만료 시간 (5분)
redisTemplate.opsForValue().set(key, value, 5, TimeUnit.MINUTES);

// 3. 1회용: 사용 후 즉시 삭제
redisTemplate.delete(key);</code></pre>
<p><strong>3. 환경별 Cookie 설정 분기(Host-only Cookie)</strong>:</p>
<p><strong>핵심 포인트</strong></p>
<ul>
<li><code>Domain</code>  속성을 설정하지 않으면 브라우저가 <strong>현재 요청 도메인을 자동으로 쿠키 도메인으로 설정</strong></li>
<li><code>Domain=unibusk.site</code> 를 설정하면 프록시를 사용해도 브라우저가 차단</li>
</ul>
<pre><code class="language-java">// application-local.yml
cookie:
  secure: false
  # domain 설정하지 않음

// application-prod.yml
cookie:
  secure: true
  domain: .unibusk.site</code></pre>
<hr>
<h3 id="방법-비교">방법 비교</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>방법 1: 풀스택 로컬</th>
<th>방법 2: DNS + mkcert ⭐</th>
<th>방법 3: hosts + mkcert</th>
<th>방법 4: Proxy 🚀</th>
</tr>
</thead>
<tbody><tr>
<td><strong>설정 난이도</strong></td>
<td>높음 (Java)</td>
<td>중간 (DNS)</td>
<td>중간 (OS별)</td>
<td><strong>낮음 (코드만)</strong></td>
</tr>
<tr>
<td><strong>BE API 변경</strong></td>
<td>불필요</td>
<td>불필요</td>
<td>불필요</td>
<td><strong>필요</strong></td>
</tr>
<tr>
<td><strong>팀원 설정</strong></td>
<td>BE 클론 + 실행</td>
<td>mkcert만</td>
<td>hosts + mkcert</td>
<td><strong>npm install만</strong></td>
</tr>
<tr>
<td><strong>인프라 설정</strong></td>
<td>불필요</td>
<td>DNS 레코드</td>
<td>hosts 파일</td>
<td><strong>불필요</strong></td>
</tr>
<tr>
<td><strong>데이터</strong></td>
<td>로컬 DB</td>
<td>운영 서버</td>
<td>운영 서버</td>
<td>운영 서버</td>
</tr>
<tr>
<td><strong>구현 속도</strong></td>
<td>느림</td>
<td>중간</td>
<td>중간</td>
<td><strong>빠름</strong></td>
</tr>
<tr>
<td><strong>운영 환경 일치</strong></td>
<td>낮음</td>
<td>높음</td>
<td>높음</td>
<td>중간</td>
</tr>
</tbody></table>
<hr>
<h3 id="권장-방법-선택-가이드">권장 방법 선택 가이드</h3>
<p><strong>방법 4 (Proxy) 추천 케이스</strong>:</p>
<ul>
<li>✅ <strong>빠른 프로토타입</strong> 개발이 필요할 때</li>
<li>✅ <strong>BE 팀과 협업</strong>이 원활하여 API 변경 가능</li>
<li>✅ <strong>인프라 설정 권한</strong>이 없거나 복잡도 회피</li>
<li>✅ 개발 초기 단계에서 빠른 검증</li>
</ul>
<p><strong>방법 2 (DNS + mkcert) 추천 케이스</strong>:</p>
<ul>
<li>✅ <strong>장기 프로젝트</strong>에서 안정적인 환경 구축</li>
<li>✅ <strong>운영 환경과 동일한</strong> 테스트 필요</li>
<li>✅ <strong>팀원 설정 최소화</strong> (hosts 수정 불필요)</li>
<li>✅ BE API 변경이 어려운 경우</li>
</ul>
<p><strong>방법 1 (풀스택 로컬) 추천 케이스</strong>:</p>
<ul>
<li>✅ BE/FE <strong>모두 개발</strong>하는 풀스택 개발자</li>
<li>✅ <strong>네트워크 독립적</strong> 개발 환경 필요</li>
<li>✅ 로컬 데이터로 개발 선호</li>
</ul>
<p><strong>방법 3 (hosts + mkcert) 추천 케이스</strong>:</p>
<ul>
<li>✅ <strong>DNS 관리 권한</strong>이 없는 경우</li>
<li>✅ 개인 개발 환경 독립 구성 선호</li>
</ul>
<hr>
<h3 id="⚠️-방법-4-proxy-사용-시-필수-체크리스트-1">⚠️ 방법 4 (Proxy) 사용 시 필수 체크리스트</h3>
<p><strong>1. Domain 속성 절대 설정 금지</strong>:</p>
<pre><code class="language-java">// ❌ 이렇게 하면 실패
Cookie cookie = new Cookie(&quot;accessToken&quot;, token);
cookie.setDomain(&quot;unibusk.site&quot;); // localhost에서 저장 안됨

// ✅ 이렇게 해야 성공
Cookie cookie = new Cookie(&quot;accessToken&quot;, token);
// setDomain() 호출하지 않음 → 브라우저가 localhost로 설정</code></pre>
<p><strong>2. 임시 코드 보안</strong>:</p>
<pre><code class="language-java">// 1. 충분한 엔트로피
String code = UUID.randomUUID().toString();

// 2. 짧은 만료 시간 (5분)
redisTemplate.opsForValue().set(key, value, 5, TimeUnit.MINUTES);

// 3. 1회용: 사용 후 즉시 삭제
redisTemplate.delete(key);</code></pre>
<p><strong>3. 환경별 Cookie 설정 분기(Host-only Cookie)</strong>:</p>
<p><strong>핵심 포인트</strong></p>
<ul>
<li><code>Domain</code>  속성을 설정하지 않으면 브라우저가 <strong>현재 요청 도메인을 자동으로 쿠키 도메인으로 설정</strong></li>
<li><code>Domain=unibusk.site</code> 를 설정하면 프록시를 사용해도 브라우저가 차단</li>
</ul>
<pre><code class="language-java">// application-local.yml
cookie:
  secure: false
  # domain 설정하지 않음

// application-prod.yml
cookie:
  secure: true
  domain: .unibusk.site</code></pre>
<hr>
<h2 id="7-검증-방법">7. 검증 방법</h2>
<h3 id="chrome-devtools로-쿠키-격리-확인">Chrome DevTools로 쿠키 격리 확인</h3>
<p><strong>Application → Cookies</strong>:</p>
<pre><code>&lt;https://unibusk.site&gt;
├─ accessToken: eyJhbGc...
└─ refreshToken: eyJhbGc...

&lt;http://localhost:3000&gt;
└─ (비어있음)

→ 두 저장소는 완전히 격리됨</code></pre><p><strong>Console 확인</strong>:</p>
<pre><code class="language-jsx">// localhost:3000/oauth-callback/kakao 페이지
console.log(document.domain);   // &quot;localhost&quot;
console.log(document.cookie);   // &quot;&quot;

// unibusk.site 쿠키는 DevTools에서 확인 가능하지만
// JavaScript로는 접근 불가</code></pre>
<p><strong>Network 탭 - 리다이렉트 체인</strong>:</p>
<pre><code>Request: GET &lt;https://unibusk.site/api/auths/login&gt;
Response: 302 Found
├─ Set-Cookie: accessToken=xxx; Domain=unibusk.site; Secure; HttpOnly
└─ Location: &lt;http://localhost:3000/oauth-callback/kakao&gt;

→ 다음 요청:
Request: GET &lt;http://localhost:3000/oauth-callback/kakao&gt;
├─ Cookie: (비어있음)  ← unibusk.site 쿠키 미포함
└─ (페이지 렌더링)</code></pre><hr>
<h2 id="8-기술적-배경-지식">8. 기술적 배경 지식</h2>
<h3 id="cookie-domain-scope">Cookie Domain Scope</h3>
<pre><code>쿠키는 설정된 도메인과 그 서브도메인에서만 접근 가능:

Domain=unibusk.site:
✅ unibusk.site
✅ api.unibusk.site
✅ app.unibusk.site
❌ localhost
❌ example.com

→ 브라우저 보안 정책: 악의적인 사이트의 쿠키 탈취 방지</code></pre><h3 id="etld1-effective-top-level-domain--1">eTLD+1 (Effective Top-Level Domain + 1)</h3>
<pre><code>unibusk.site        → eTLD+1: unibusk.site
dev.unibusk.site    → eTLD+1: unibusk.site (Same-Site)
localhost           → eTLD+1: localhost (Cross-Site)

→ 쿠키의 Domain=.unibusk.site 설정 시:
  dev.unibusk.site ↔ unibusk.site 간 쿠키 공유 가능</code></pre><h3 id="samesite-vs-domain">SameSite vs Domain</h3>
<p><strong>SameSite</strong>: 쿠키가 존재할 때 언제 전송할지 결정</p>
<pre><code>SameSite=Lax:
✅ unibusk.site → unibusk.site (Same-Site Navigation)
✅ dev.unibusk.site → api.unibusk.site (Same-Site)
❌ localhost → unibusk.site (Cross-Site)</code></pre><p><strong>우리 문제</strong>: Domain 자체가 달라서 쿠키가 존재하지 않음</p>
<pre><code>❌ localhost 컨텍스트에서 unibusk.site 쿠키 접근 불가
→ SameSite 검사 이전에 &quot;쿠키 없음&quot; 상태</code></pre><hr>
<h2 id="9-결론">9. 결론</h2>
<h3 id="문제의-본질">문제의 본질</h3>
<p><code>window.location.href</code> 기반 OAuth2 플로우에서:</p>
<ol>
<li><strong>리다이렉트 중</strong>: FE 코드가 실행되지 않아 응답 가로채기 불가</li>
<li><strong>쿠키 저장</strong>: 브라우저가 자동으로 처리하지만 <code>unibusk.site</code> 도메인에만 저장</li>
<li><strong>리다이렉트 완료</strong>: <code>localhost</code> 컨텍스트로 전환되어 쿠키 접근 불가</li>
<li><strong>결과</strong>: 브라우저의 <code>localhost</code> 저장소에는 쿠키가 없음</li>
</ol>
<h3 id="해결책">해결책</h3>
<p><strong>도메인 통일</strong>만이 유일한 해결 방법:</p>
<ul>
<li>풀스택 로컬 환경: 모두 <code>localhost</code></li>
<li>서브도메인 구성: 모두 <code>.unibusk.site</code></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nest 핵심 기술(1) - Controllers]]></title>
            <link>https://velog.io/@park-moen/Nest-%ED%95%B5%EC%8B%AC-%EA%B8%B0%EC%88%A01-Controllers</link>
            <guid>https://velog.io/@park-moen/Nest-%ED%95%B5%EC%8B%AC-%EA%B8%B0%EC%88%A01-Controllers</guid>
            <pubDate>Wed, 25 Sep 2024 02:01:42 GMT</pubDate>
            <description><![CDATA[<p><strong>컨트롤러(Controller)</strong>의 목적은 특정 request를 처리하고 클라이언트에 response를 반환하는 역할을 하는 핵심합니다. <strong>컨트롤러(Controller)</strong>는 클라이언트로부터 들어온 GET, POST, PUT, DELETE 등의 HTTP requset를 처리하며, 각 요청을 적절한 핸들러 메서드에 매핑하여 response를 반환합니다. 이러한 과정은 <a href="https://en.wikipedia.org/wiki/Routing">라우팅</a> 메커니즘을 통해 이루어집니다.</p>
<p><img src="https://velog.velcdn.com/images/park-moen/post/3bb4139b-01cb-42eb-b9f3-a1103b9bbb5d/image.png" alt=""></p>
<p><strong>컨트롤러(Controller)</strong>를 생성하기 위해서는 <strong>데코레이터(decorator)</strong>를 사용합니다. <strong>데코레이터(decorator)</strong>는 클래스에 필요한 메타데이터와 연결하고 Nest가 라우팅 맵을 생성할 수 있도록 합니다.</p>
<blockquote>
<p><strong>HINT</strong>
Nest에서 사용되는 <a href="https://www.typescriptlang.org/docs/handbook/decorators.html"><strong>데코레이터(decorator)</strong></a>는 <strong>메타프로그래밍을 지원하는 TypeScript 기능을 기반으로 하며, 클래스, 메서드, 프로퍼티, 매개변수 등에 **추가적인 기능</strong>을 정의할 수 있도록 돕는 기능입니다.</p>
</blockquote>
<h2 id="routing">Routing</h2>
<p><strong>→ [Controller 정의]</strong></p>
<p>컨트롤러(Controller)를 정의하기 위해서는 <code>@Controller</code> 데코레이터를 사용합니다. <code>@Controller</code>는 특정 prefix(경로 접두사)를 설정하여 해당 경로와 관련된 모든 라우터를 처리할 수 있습니다.</p>
<p>예를 들어, <em>고양이 엔티티</em>와 상호작용하는 모든 라우트를 <code>/cats</code> 경로 아래에 그룹화할 수 있습니다.</p>
<pre><code class="language-tsx">// cats.controller.ts

import { Controller, Get } from &#39;@nestjs/common&#39;;

@Controller(&#39;cats&#39;)
export class CatsController {
  @Get()
  findAll(): string {
    return &#39;This action returns all cats&#39;;
  }
}</code></pre>
<p>여기서 <code>@Get()</code> 데코레이터는 HTTP GET 요청을 처리하는 핸들러 메서드인 <code>findAll()</code> 을 정의합니다.</p>
<p><strong>→ [라우트 경로]</strong></p>
<ul>
<li>컨트롤러에 선언된 prefix(경로 접두사)와 HTTP 메서드 데코레이터(<code>@Get</code>, <code>@Post</code>, <code>@Put</code>, <code>@Delete</code>, etc…)에 지정된 경로를 결합하여 라우트가 결정됩니다.</li>
<li>예를 들어, <code>@Controller(’cats’)</code>와 <code>@Get(’bread’)</code>를 결합하면 <code>GET/cates/bread</code> 라우트가 생성됩니다.</li>
<li>prefix(경로 접두사)를 통해 사용자가 원하는 라우트 경로를 지정할 수 있습니다.</li>
</ul>
<p><strong>→ [표준 응답 처리(recommended)]</strong></p>
<ul>
<li>Nest는 기본적으로 요청 핸들러에서 반환된 데이터를 <strong>자동으로 직렬화</strong>하여 <strong>JSON format</strong>으로 응답합니다. Nest에서 자동으로 직렬화하는 반환값은 <strong>JavaScript Object 타입</strong>이나 <strong>Array 타입</strong>인 경우 뿐입니다.</li>
<li>기본 JavaScript primitive 타입(<code>string</code>, <code>number</code>, <code>boolean</code>)을 반환할 경우에는 Nest는 직렬화를 시도하지 않고 기본 값을 반환합니다.</li>
<li><strong>응답 상태 코드</strong>는 기본적으로 <strong>200</strong>이며, POST 요청에서는 <strong>201</strong>을 기본으로 사용합니다. <code>@HttpCode(...)</code> 데코레이터를 통해 상태 코드를 변경할 수 있습니다.</li>
</ul>
<p><strong>→ [라이브러리별 응답 처리]</strong></p>
<ul>
<li>Nest는 Express나 Fastify 같은 특정 라이브러리의 <a href="https://expressjs.com/en/api.html#res"><code>response object</code></a>를 직접 사용할 수 있습니다. 이를 위해 <code>@Res()</code> 데코레이터를 사요합니다.</li>
<li>라이브러리별 응답 객체를 사용하여 <strong>상태 코드 설정</strong>, <strong>헤더 추가</strong> 등의 작업을 할 수 있습니다.</li>
</ul>
<pre><code class="language-tsx">@Get()
findAll(@Res() resposne) {
    return response.status(200).send(&#39;This action returns all cats&#39;)
}</code></pre>
<ul>
<li><code>@Res()</code>데코레이터를 사용하면 <strong>표준 응답 처리 방식</strong>이 비활성됩니다. 만약 <a href="https://expressjs.com/en/api.html#res"><code>response object</code></a>를 사용하면서도 나머지 처리를 Nest에 맡기고 싶다면, <code>@Res({ passthrough: true })</code> 옵션을 사용할 수 있습니다.</li>
</ul>
<blockquote>
<p><strong>HINT
Nest CLI 명령어</strong>를 사용해서 쉽게 컨트롤러를 생성할 수 있습니다.
<code>$ nest g controller [name]</code> 명령어를 실행하기만 하면 됩니다.</p>
</blockquote>
<h2 id="request-object">Request Object</h2>
<p>Nest는 기본적으로 <strong>Express</strong>나 <strong>Fastify</strong> 등의 플랫폼에서 <a href="https://expressjs.com/en/api.html#req">requset object(요청 객체)</a>를 처리하며, <code>@Req()</code> 데코레이터를 사용해서 핸들러에서 직접 <strong>요청 객체</strong>에 접근할 수 있습니다.</p>
<pre><code class="language-tsx">// cats.controller.ts

import { Controller, Get, Req } from &#39;@nestjs/common&#39;;
import { Request } from &#39;express&#39;;

@Controller(&#39;cats&#39;)
export class CatsController {
  @Get()
  findAll(@Req() request: Request): string {
    return &#39;This action returns all cats&#39;;
  }
}</code></pre>
<blockquote>
<p><strong>HINT</strong>
위 예제처럼 <code>request: Request</code> 타입을 추가하기 위해서 express 타입을 활용할 수 있는 <code>@types/express</code> 패키지를 설치하세요.</p>
</blockquote>
<p><code>@Req()</code> 데코레이터를 사용한 <strong>요청 객체(request object)</strong>는 HTTP 요청을 나타내며 <code>requset query string</code>, <code>parameters</code>, <code>HTTP headers</code> 및 <code>body</code>에 대한 프로퍼티를 가지고 있습니다. 대부분의 경우 이런 속성을 수동으로 가져올 필요 없이 <code>@Body()</code>, <code>@Query()</code>, <code>@Param()</code> 등의 데코레이터를 통해 즉시 사용할 수 있습니다. 아래는 Nest에서 제공하는 <strong>요청 객체(Request object)</strong> 데코레이터입니다.</p>
<table>
<thead>
<tr>
<th><code>@Request(), @Req()</code></th>
<th><code>req</code></th>
</tr>
</thead>
<tbody><tr>
<td><code>@Response(), @Res()</code>*****</td>
<td><code>res</code></td>
</tr>
<tr>
<td><code>@Next()</code></td>
<td><code>next</code></td>
</tr>
<tr>
<td><code>@Session()</code></td>
<td><code>req.session</code></td>
</tr>
<tr>
<td><code>@Param(key?: string)</code></td>
<td><code>req.params</code> / <code>req.params[key]</code></td>
</tr>
<tr>
<td><code>@Body(key?: string)</code></td>
<td><code>req.body</code> / <code>req.body[key]</code></td>
</tr>
<tr>
<td><code>@Query(key?: string)</code></td>
<td><code>req.query</code> / <code>req.query[key]</code></td>
</tr>
<tr>
<td><code>@Headers(name?: string)</code></td>
<td><code>req.headers</code> / <code>req.headers[name]</code></td>
</tr>
<tr>
<td><code>@Ip()</code></td>
<td><code>req.ip</code></td>
</tr>
<tr>
<td><code>@HostParam()</code></td>
<td><code>req.hosts</code></td>
</tr>
</tbody></table>
<p><strong>응답 객체(response object)</strong>를 사용할 때는 <code>@Res()</code> <strong>또는</strong> <code>@Response</code> 데코레이터를 통해 기본 HTTP 플랫폼(Express 및 Fastify) 응답 객체를 직접 관리할 수 있습니다.  <code>@Res()</code> <strong>또는</strong> <code>@Response</code>를 사용하면 <a href="https://docs.nestjs.com/controllers#routing"><strong>Nest 라이브러리 전용 모드(Library-specific)</strong></a>로 전환하고 응답(Response)을 관리할 책임이 사용자에게 있다는 점을 유의하세요. 라이브러리 전용 모드를 사용하게 된다면  <code>response</code> 객체(예: <code>res.json(...)</code> 또는 <code>res.send(...)</code>)를 호출하여  응답을 보내야 하며, 그렇지 않으면 HTTP 서버가 중단됩니다.</p>
<h2 id="resources">Resources</h2>
<p>Nest에서는 <strong>HTTP 메서드 데코레이터(</strong><code>@Get()</code>, <code>@Post()</code>, <code>@Put()</code>, <code>@Delete()</code>, <code>@Patch()</code>, <code>@Options()</code>, <code>@Head()</code><strong>)</strong>를 통해 리소스 엔트포인트를 정의할 수 있습니다. , 또한 <code>@All</code>데코레이터는 모든 메서드를 처리하는 에든포인트를 정의할 수 있습니다.</p>
<pre><code class="language-tsx">import { Controller, Get, Post } from &#39;@nestjs/common&#39;;

@Controller(&#39;cats&#39;)
export class CatsController {
  @Get()
  findAll(): string {
    return &#39;This action returns all cats&#39;;
  }

  @Post()
  create(): string {
    return &#39;This action adds a new cat&#39;;
  }

  @Put()
  modify(): string {
    return &#39;This action modify a cat&#39;;
  }

  @Delete()
  delete(): string {
    return &#39;This action delete a cat&#39;;
  }
}</code></pre>
<h2 id="route-wildcares">Route wildcares</h2>
<p>엔드포인트에 <strong>와이드카드 경로</strong>를 사용하면 어떠한 문자 조합과도 메서드를 일치시킬 수 있습니다. </p>
<pre><code class="language-tsx">@Get(&#39;ab*cd&#39;)
findAll() {
  return &#39;This route uses a wildcard&#39;;
}</code></pre>
<p><code>ab*dc</code> 라우트 경로는 <code>abcd</code>, <code>ab_cd</code>, <code>adecd</code> 등과 일치합니다. 문자 <code>?</code>, <code>+</code>, <code>*</code>, <code>()</code>는 라우트 경로에 사용할 수 있으며 정규표현식입니다. 하이픈(<code>-</code>),과 점(<code>.</code>)은 문자 그대로 해석됩니다.</p>
<blockquote>
<p><strong>HINT</strong>
<strong>Express</strong>에서만 중간 와이드카드가 지원된다는 점을 유의하세요.</p>
</blockquote>
<h2 id="status-code">Status code</h2>
<p>응답 상태 코드는 기본적으로 <strong>201</strong>인 POST 요청을 제외하고 항상 <strong>200</strong>입니다. 핸들러 수준에서 <code>@HttpCode(...)</code>데코레이터를 추가하면 쉽게 응답 상태 코드를 변경할 수 있습니다.</p>
<pre><code class="language-tsx">@Post()
@HttpCode(204)
create() {
  return &#39;This action adds a new cat&#39;;
}</code></pre>
<blockquote>
<p><strong>HINT</strong>
<code>@nestjs/common</code> 패키지에서 <code>HttpCode</code>를 가져옵니다.</p>
</blockquote>
<p>상태 코드는 정적이지 않은 경우 <code>@Res</code> 또는 <code>@Response</code>데코레이터를 사용해서 라이브러리별로 응답 객체를 사용할 수 있으며, 오류가 발생하면 예외를 발생시킬 수 있습니다.</p>
<h2 id="headers">Headers</h2>
<p><code>@Header</code> 데코레이터 또는 <code>@Res</code>(라이브러리별 응답 객체 <code>res.header()</code>)를 사용해서 커스텀 응답 헤더를 지정할 수 있습니다.</p>
<blockquote>
<p><strong>HINT</strong>
<code>@nestjs/common</code> 패키지에서 <code>Header</code>를 가져옵니다.</p>
</blockquote>
<h2 id="redirection">Redirection</h2>
<p>응답을 특정 URL로 리다이렉션하려면 <code>@Redirect(url?: string, statusCode?: number)</code> 또는 <code>@Res() → res.redirect()</code>를 직접 호출해야 합니다. </p>
<pre><code class="language-tsx">@Get(&#39;default&#39;)
@Redirect(&#39;https://nestjs.com&#39;, 301)

@Get(&#39;use-res&#39;)
redirectToNewRoute(@Res() res: Response) {
  return res.redirect(&#39;/new-route&#39;);
}
</code></pre>
<p><code>@Redirect()</code> 데코레이터는 두개의 인수, <code>url</code>과 <code>statusCode</code>를  선택적으로 받을 수 있습니다. <code>statusCode</code>의 기본값은 생략할 수 경우 <code>302(Found)</code>입니다.</p>
<p>반환된 값은 <code>@Redirect()</code>데코레이터에 전달된 모든 인수를 재정의합니다. 예를 들어:</p>
<pre><code class="language-tsx">@Get(&#39;docs&#39;)
@Redirect(&#39;https://docs.nestjs.com&#39;, 302)
getDocs(@Query(&#39;version&#39;) version) {
  if (version &amp;&amp; version === &#39;5&#39;) {
    return { url: &#39;https://docs.nestjs.com/v5/&#39; };
  }
}</code></pre>
<h2 id="route-parameters"><strong>Route parameters</strong></h2>
<p>Nest에서 <strong>Route Parameters(라우트 매개변수)</strong>는 URL 경로에서 변수를 받아와 동적으로 처리하는 기능입니다. <code>@Param()</code> 데코레이터를 사용하여 요청 경로의 변수를 캡쳐할 수 있습니다. 예를 들어 <code>id</code>와 같은 매개변수는 URL의 일부로 전달되어 핸들러 함수에서 처리할 수 있습니다.</p>
<pre><code class="language-tsx">@Get(&#39;:id&#39;)
getUserById(@Param(&#39;id&#39;) id: string) {
  return `User ID is: ${id}`;
}</code></pre>
<p>이 코드는 <code>/cats/123</code>과 같은 요청을 처리하면, <code>id</code> 값이 함수로 전달됩니다.</p>
<p><strong>→ [라우트 매개변수의 주요 개념]</strong></p>
<ol>
<li><strong>동적 URL 처리</strong>: ****URL 경로에서 변수를 동적으로 받아서 id와 같은 사용자에 따라 동적으로 바뀔 수 있는 입력을 처리합니다.</li>
<li><strong>매개변수 처리</strong>: 하나 이상의 매개 변수를 처리할 수 있습니다.</li>
</ol>
<pre><code class="language-tsx">@Get(&#39;:userId/:postId&#39;)
getPost(@Param(&#39;userId&#39;) userId: string, @Param(&#39;postId&#39;) postId: string) {
  return `User ID: ${userId}, Post ID: ${postId}`;
}</code></pre>
<p>위 코드는 <code>GET cats/123/12</code>와 같은 URL에서 두 개의 매개변수를 동적으로 받아서 처리합니다.</p>
<p><strong>→ [라우트 매개변수와 쿼리 매개변수의 차이]</strong></p>
<ul>
<li><strong>라우트 매개변수</strong>는 URL의 경로 자체에 포함되며, 동적인 경로 데이터를 처리합니다. (<code>@Params</code> 데코레이터 사용)</li>
<li><strong>쿼리 매개변수</strong>는 URL 끝에 <code>?key=value</code> 형식으로 전달됩니다. 검색 필터와 같은 정보를 전달할 때 사용할 수 있습니다.(<code>@Query</code> 데코레이터를 사용합니다.)</li>
</ul>
<pre><code class="language-tsx">// route parameter example
// client request: GET /usrs/123

@Get(&#39;users/:id&#39;)
getUserById(@Param(&#39;id&#39;) id: string) {
  return `User ID: ${id}`;
}</code></pre>
<pre><code class="language-tsx">// query parameter example
// client requset: GET /users?page-2

@Get(&#39;users&#39;)
getUserByQuery(@Query(&#39;page&#39;) page: number) {
  return `Page: ${page}`;
}</code></pre>
<p><strong>→ [동적 라우트 매개변수 사용 시 주의 사항]</strong></p>
<p><strong>동적 라우트 경로를 정적 라우트 경로 뒤에 선언해야 하는 규칙이 있습니다.</strong>  이유는 매개변수가 있는 경로는 여러 패턴과 일치할 수 있기 때문에, 정적 경로가 먼저 선언되지 않으면 동적 경로가 먼저 처리되어 정적 경로에 해당하는 요청도 동적 경로로 전달될 수 있기 때문입니다.</p>
<pre><code class="language-tsx">@Get(&#39;profile&#39;)
getProfile() {
  return &#39;Profile Page&#39;;
}

@Get(&#39;:username&#39;)
getUserByUsername(@Param(&#39;username&#39;) username: string) {
  return `User: ${username}`;
}</code></pre>
<p>위 예시에서, <code>/profile</code> 과 <code>/john</code> 같은 경로가 있다고 가정하면, 정적 경로(<code>/profile</code>)를 먼저 선언하고 다음에 동적 경로를 선언해야 매개변수화된 경로가 정적 경로의 요청을 가로채지 않습니다.</p>
<h2 id="sub-domain-routing"><strong>Sub-Domain Routing</strong></h2>
<p>서브 도메인 라우팅은 특정 <strong>서브 도메인</strong>을 처리해야 할 경우에 사용됩니다. 예를 들어 서브 도메인을 운영하는 서비스에서 각각의 서브 도메인에 대한 <strong>다른 로직</strong>을 처리해야 할 때 유용합니다.</p>
<p>Nest에서 서브 도메인 라우팅을 사용하기 위해서는 <code>@Controller</code> 데코레이터에 <code>host</code> 옵션 추가하여 특정 HTTP host와 일치시키도록 할 수 있습니다.</p>
<pre><code class="language-tsx">@Controller({ host: &#39;admin.example.com&#39; })
export class AdminController {
  @Get()
  index(): string {
    return &#39;Admin page&#39;;
  }
}</code></pre>
<p><strong>서브 도메인 라우팅</strong>에서 동적 값을 처리하는 방법</p>
<ol>
<li><strong>host option</strong>: 경로(path)와 비슷하게 <strong>서브 도메인</strong>에서도 특정 값을 <strong>동적으로 캡쳐</strong>할 수 있습니다.</li>
<li><code>@Controller()</code>에서 <strong>host parameter token</strong>을 사용하여 서브 도메인의 동적 값을 캡쳐할 수 있습니다.</li>
<li>이렇게 캡쳐한 <strong>동적 값</strong>은 <code>@HostParam()</code> 데코레이터를 통해 메서드에 접근할 수 있습니다.</li>
</ol>
<blockquote>
<p>HINT
<strong>host parameter token</strong>은 서브 도메인에서 <strong>동적 값</strong>을 캡쳐하기 위한 방식입니다. 예를 들어, 도메인이 <code>{username}.example.com</code> 일 경우, <code>{username}</code>이 <strong>host parameter token</strong>으로 동작하여 사요자 이름을 동적으로 캡쳐할 수 있습니다.</p>
</blockquote>
<pre><code class="language-tsx">@Controller({ host: &#39;:username.example.com&#39; })
export class UserController {
  @Get()
  getUser(@HostParam(&#39;username&#39;) username: string) {
    return `Hello, ${username}`;
  }
}</code></pre>
<h2 id="request-payloads"><strong>Request payloads</strong></h2>
<p>Nest에서 <strong>Request Payload</strong>는 클라이언트가 서버로 보내는 데이터를 의미하며, 주로 <code>POST</code>, <code>PUT</code>, <code>PATCH</code> 요청에서 사용됩니다. 데이터를 처리하기 위해 <code>@Body()</code> 데코레이터를 사용해서 <strong>Request Payload</strong>를 처리할 수 있습니다. 타입스크립트로 Nest 프로젝트를 구축하는 경우. <strong>DTO(Data Transfer Object)</strong>를 사용해서 입력 데이터(request payload)의 구조를 명확하게 정의하고, <strong>데이터의 일관성</strong>을 유지하고, <strong>유효성 검사</strong>를 쉽게 적용할 수 있습니다.</p>
<pre><code class="language-tsx">export class CreateDto {
  @IsString()
  name: string;

  @IsNumber()
  age: number;
}</code></pre>
<pre><code class="language-tsx">@Post()
create(@Body() createDto: CreateDto) {
  return this.service.create(createDto);
}</code></pre>
<p><strong>→ [DTO를 클래스로 정의하는 이유]</strong></p>
<ol>
<li><strong>클래스</strong>는 <strong>런타임 메타데이터</strong>를 제공하므로, Nest는 <strong>유효성 검사</strong> 및 <strong>데이터 변환</strong>과 같은 기능을 자동으로 처리할 수 있습니다.</li>
<li><strong>인터페이스</strong>는 <strong>컴파일 시점</strong>에만 존재하며, 런타임에서는 사용할 수 없으므로, <strong>유효성 검사</strong>와 같은 동적 기능을 지원하지 못합니다.</li>
<li><strong>클래스</strong>는 <strong>생성자</strong>를 사용하여 데이터의 기본값을 설정하거나 초기화를 할 수 있어서 더 많은 유연성을 제공합니다.</li>
</ol>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li><a href="https://docs.nestjs.com/controllers">NestJS Docs - Controllers</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Algorithm] 에라토스테네스의 체 ]]></title>
            <link>https://velog.io/@park-moen/Algorithm-%EC%97%90%EB%9D%BC%ED%86%A0%EC%8A%A4%ED%85%8C%EB%84%A4%EC%8A%A4%EC%9D%98-%EC%B2%B4</link>
            <guid>https://velog.io/@park-moen/Algorithm-%EC%97%90%EB%9D%BC%ED%86%A0%EC%8A%A4%ED%85%8C%EB%84%A4%EC%8A%A4%EC%9D%98-%EC%B2%B4</guid>
            <pubDate>Thu, 08 Aug 2024 07:45:45 GMT</pubDate>
            <description><![CDATA[<h2 id="에라토스테네스의-체란">에라토스테네스의 체란?</h2>
<p>에라토스테네스의 체(Sieve of Eratosthenes)는 주어진 숫자까지의 소수를 찾기 위한 효율적인 알고리즘입니다. 이 알고리즘은 고대 그리스의 수학자 에라토스테네스(Eratosthenes)에 의해 개발되었습니다. 이제 이 알고리즘의 동작 방식과 Node.js에서 어떻게 구현할 수 있는지 설명드릴게요.</p>
<h2 id="에라토스테네스의-체-동작">에라토스테네스의 체 동작</h2>
<p><img src="https://github.com/user-attachments/assets/c11cd0d6-0a3c-4786-94aa-53ff78cc7c4b" alt="Sieve_of_Eratosthenes_animation"></p>
<table>
<thead>
<tr>
<th>단계</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>2부터 소수를 구하고자 하는 구간의 모든 수를 나열한다. 그림에서 회색 사각형으로 두른 수들이 여기에 해당합니다.</td>
</tr>
<tr>
<td>2</td>
<td>2는 소수이므로 오른쪽에 2를 씁니다. (빨간색)</td>
</tr>
<tr>
<td>3</td>
<td>자기 자신을 제외한 2의 배수를 모두 지웁니다.</td>
</tr>
<tr>
<td>4</td>
<td>남아있는 수 가운데 3은 소수이므로 오른쪽에 3을 씁니다. (초록색)</td>
</tr>
<tr>
<td>5</td>
<td>자기 자신을 제외한 3의 배수를 모두 지웁니다.</td>
</tr>
<tr>
<td>6</td>
<td>남아있는 수 가운데 5는 소수이므로 오른쪽에 5를 씁니다. (파란색)</td>
</tr>
<tr>
<td>7</td>
<td>자기 자신을 제외한 5의 배수를 모두 지웁니다.</td>
</tr>
<tr>
<td>8</td>
<td>남아있는 수 가운데 7은 소수이므로 오른쪽에 7을 씁니다. (노란색)</td>
</tr>
<tr>
<td>9</td>
<td>자기 자신을 제외한 7의 배수를 모두 지웁니다.</td>
</tr>
<tr>
<td>10</td>
<td>위의 과정을 반복하면 구하는 구간의 모든 소수가 남습니다.. (보라색)</td>
</tr>
</tbody></table>
<ul>
<li><p>이 표는 에라토스테네스의 체 알고리즘의 각 단계를 명확하게 설명합니다. 각 단계에서 소수를 찾고, 그 소수의 배수를 제거하는 과정을 반복하여 최종적으로 모든 소수를 구합니다.</p>
</li>
<li><p>그림의 경우, $$11^2 &gt; 120$$이므로 11보다 작은 수의 배수들만 지워도 충분하다. 즉, 120보다 작거나 같은 수 가운데 2, 3, 5, 7의 배수를 지우고 남는 수는 모두 소수입니다.</p>
</li>
</ul>
<h2 id="구현">구현</h2>
<pre><code class="language-js">function sieveOfEratosthenes(limit) {
  // 0과 1은 소수가 아니므로 제외
  const isPrime = new Array(limit + 1).fill(true).fill(false, 0, 2);

  // 소수 판별 알고리즘
  for (let i = 2; i &lt;= Math.sqrt(limit); i++) {
    if (isPrime[i]) {
      for (let j = i * i; j &lt;= limit; j += i) {
        isPrime[j] = false;
      }
    }
  }

  return isPrime;
}</code></pre>
<h2 id="시간-복잡도-분석">시간 복잡도 분석</h2>
<p>알고리즘의 주된 시간 소모는 내부의 중첩된 루프에서 발생합니다.</p>
<ol>
<li><strong>외부 루프</strong>: $$𝑖 = 2$$부터 $$𝑖 ≤ \sqrt{n}$$까지의 숫자를 반복합니다. 이 반복은 최대 $$\sqrt{𝑛}$$ 번 수행됩니다.</li>
<li><strong>내부 루프</strong>: 각 소수 $$𝑖$$에 대해, $$i^2$$부터 $$n$$까지의 숫자 중 $$i$$의 배수를 <code>false</code>로 설정합니다.</li>
</ol>
<p>내부 루프의 반복 횟수를 계산해보면, 각 소수 $$i$$에 대해 $$\frac{n}{i}$$번 수행됩니다. 이를 모든 소수에 대해 합산하면 다음과 같은 수식이 됩니다:</p>
<p>$$\sum_{i=2}^{\sqrt{n}} \frac{n}{i}$$</p>
<p>이 합을 대략적으로 계산하면 이 됩니다. 이는 소수의 밀도와 관련이 있습니다.</p>
<p>따라서, 전체 알고리즘의 시간 복잡도는 $$O(n \log \log n)$$이 됩니다. 이 시간 복잡도는 $$n$$이 매우 큰 경우에도 비교적 효율적입니다.</p>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://ko.wikipedia.org/wiki/%EC%97%90%EB%9D%BC%ED%86%A0%EC%8A%A4%ED%85%8C%EB%84%A4%EC%8A%A4%EC%9D%98_%EC%B2%B4">[wikipedia]에라토스테네스의 체</a></li>
<li><a href="https://velog.io/@woody_/JS-%EC%97%90%EB%9D%BC%ED%86%A0%EC%8A%A4%ED%85%8C%EB%84%A4%EC%8A%A4%EC%9D%98-%EC%B2%B4-%EC%86%8C%EC%88%98-%ED%8C%90%EB%B3%84">[JS] 에라토스테네스의 체 - 소수 판별</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[REST API]]></title>
            <link>https://velog.io/@park-moen/REST-API</link>
            <guid>https://velog.io/@park-moen/REST-API</guid>
            <pubDate>Fri, 24 Feb 2023 01:56:42 GMT</pubDate>
            <description><![CDATA[<h2 id="rest-api의-탄생한-배경">REST API의 탄생한 배경</h2>
<br />

<blockquote>
<p><code>a way provide interoperability between computer systems on the Internet.</code> (컴퓨터 시스템간의 상호 운영성을 제공하는 방법 중 하나)</p>
</blockquote>
<br />

<h2 id="web1991">WEB(1991)</h2>
<p><strong>어떻게 인터넷에서 정보를 공유할 것인가?</strong></p>
<ul>
<li>모든 정보들을 하이퍼텍스트로 연결한다.<ul>
<li>표현 형식: HTML</li>
<li>식별자: URI</li>
<li>전송 방법: HTTP</li>
</ul>
</li>
</ul>
<pre><code>HTML 형식으로 정보들을 표현하고 정보들의 식별자로 URI를 만들었고 그리고 그 정보들을 전송하는 방법으로 HTTP Protocol을 만들었다.</code></pre><br />

<h2 id="rest-탄생">REST 탄생</h2>
<p>HTTP는 정보들을 전송하는 방식으로 HTTP/1.0 version이  나오기전에 이미 많은 사용자들이 WEB 전송 방식으로 급속도로 사용하고 있었습니다. 그러는 도중에 HTTP/1.0을 만들기 위해서  Roy T. Fielding이 제작에 참여하였습니다. 이 시점에서 HTTP를 정립하고 명세의 기능을 더하고 기존의 기능을 수정해야 하는 상황이 발생했습니다. 그러면 기존에 이미 구축된 WEB과 충돌하는 문제가 발생했습니다. </p>
<p>이전에 구축한 WEB을 망가트리지 않고 HTTP를 진보시킬 수 있는지 Roy T. Fielding은 고민을 했고 그 해결책으로 HTTP Object Model을 발표하게 되었습니다. 발표한 HTTP Object Model은 4년 후에 REST 논문을 Roy T. Fielding이 발표하면서 REST가 탄생하게 되었습니다.</p>
<p>위의 글을 정리해보면 Roy T. Fielding은 이전에 구축한 WEB에 영향을 미치지 않으면서 새롭게 구축한 HTTP/1.0 Protocol을 사용하는 방법을 만들기 위해서 REST를 만들게 되었다고 정리할 수 있습니다. </p>
<br />

<h2 id="rest란">REST란?</h2>
<blockquote>
<p><code>REST: Representational State Transfer</code> </p>
<ul>
<li>Architectural Styles and the Design of Network-based Software Architectures</li>
</ul>
</blockquote>
<ul>
<li>REST API: REST 아키텍쳐 스타일을 따르는 API</li>
<li>REST: 분산 하이퍼미디어 시스템(예: 웹)을 위한 아키텍쳐 스타일이면서 동시에 아키텍쳐 스타일의 집합</li>
<li>아키텍쳐 스타일: 제약조건들의 집합</li>
</ul>
<p>REST는 제약 조건의 집합을 의미하며 모든 제약조건을 지켜야 REST라고 지칭할 수 있는 점을 주의해야합니다. 그러기 위해서는 REST에서 사용하는 제약 조건에 대해서 알아보겠습니다.</p>
<br />

<h2 id="rest-구성하는-아키텍쳐-스타일">REST 구성하는 아키텍쳐 스타일</h2>
<ol>
<li><strong>Client - Server 구조</strong> : REST 서버는 API 제공, 클라이언트는 사용자 인증이나 컨텍스트(세션, 로그인 정보 등)을 직접 관리하는 구조로 각각의 역할이 확실히 구분되기 때문에 클라이언트와 서버에서 개발해야 할 내용이 명확해지고 서로간 의존성이 줄어들게 된다.</li>
<li><strong>Stateless (무상태성)</strong> : HTTP는 Stateless Protocol 이므로, REST 역시 무상태성을 갖는다. 즉, HttpSession과 같은 컨텍스트 저장소에 상태정보를 따로 저장하고 관리하지 않고, API 서버는 들어오는 요청만을 단순 처리하면 된다. 세션과 같은 컨텍스트 정보를 신경쓸 필요가 없어 구현이 단순해진다.</li>
<li><strong>Cacheable (캐시가능)</strong> : HTTP 기존의 웹 표준을 그대로 사용하므로, 웹에서 사용하는 기존의 인프라를 그대로 활용 가능하다. HTTP 프로토콜 기반의 로드밸런서(mod_proxy)나, SSL은 물론이고 HTTP가 가진 가장 강력한 특징 중의 하나인 캐싱 기능을 적용할 수 있다. 일반적인 서비스에서 조회 기능이 주로 사용됨을 감안하면, HTTP 리소스들을 웹 캐쉬 서버 등에 캐싱하는 것은 용량이나 성능 면에서 이점이 있다. 캐싱 구현은 HTTP 프로토콜 표준에서 사용하는 Last-Modified 태그나 E-Tag를 이용하면 가능하다.</li>
<li><strong>Uniform Interface(유니폼 인터페이스)</strong> : HTTP 표준에만 따른다면, 안드로이드/IOS 플랫폼이든, 특정 언어나 기술에 종속되지 않고 모든 플랫폼에 사용이 가능하며, URI로 지정한 리소스에 대한 조작이 가능한 아키텍처 스타일을 의미한다. </li>
<li><strong>계층형 구조</strong> : API 서버는 순수 비지니스 로직을 수행하고, 그 앞단에 사용자 인증, 암호화(ssl), 로드밸런싱 등을 하는 계층을 추가하여 구조상의 유연상을 둘 수 있다. 이는 간단하게는 HA Proxy나 Apache의 Reverse Proxy를 통해, 더 나아가서는 API gateway 등을 활용하여 Micro Service Architecture로도 구현이 가능하게 한다.</li>
<li><strong>code-on-demand(optional)</strong>:</li>
<li><strong>Self-descriptiveness (자체 표현 구조)</strong> : 동사(Method) + 명사(URI) 로 이루어져있어 어떤 메서드에 무슨 행위를 하는지 알 수 있으며, 메시지 포맷 역시 JSON을 이용해서 직관적으로 이해가 가능한 구조로, REST API 메시지만 보고도 이를 쉽게 이해할 수 있다.</li>
</ol>
<p><em>대체로 REST API라고 불리는 API들은 어느정도의 REST를 잘 지키고 있습니다. 그 이유로는 우리가 정보 전송을 위해 사용하는 HTTP의 규칙만 잘 지켜도  Client - Server, Stateless, Cacheable, layered system와 같은 대부분의 REST Design Architecture을 지킬 수 있기 때문입니다. Code-on-demand 같은 경우는 optional로 serverd에서 코드를 Client 보내서 실행할 수 있어야 하며 가장 유명한 예시는 JavaScript를 server에서 Client로 보내는 방식입니다.</em></p>
<br />

<h2 id="uniform-interface의-제약-조건">Uniform Interface의 제약 조건</h2>
<p>대부분의 REST 규칙은 HTTP의 규칙을 따른다고 해도 REST의 규칙 중 하나인 Uniform Interface는 외에는 전부 지킬 수 있습니다. 그렇다면 Uniform Interface를 지킬 수 있는 방법을 알아야 완벽한 REST API를 만들 수 있습니다.</p>
<ul>
<li>identification of resources</li>
<li>manipulation of resources through representations</li>
<li><strong>self-descriptive messages</strong></li>
<li><strong>hypermedia as the engine of application state(HATEOAS)</strong></li>
</ul>
<p>Uniform Interface의 제약 조건 중에서도 identification of resources와 manipulation of resources through representations는 잘 지켜지고 있습니다.</p>
<h3 id="identification-of-resources">identification of resources</h3>
<ul>
<li>resources가 URI로 식별되어야 합니다.</li>
<li>정확하게 말하면 리소스를 식별하는 방법이 동일해야 합니다. 대부분의 API는 URI로 resources를 식별하는 경우가 많습니다. 그렇기때문에 대부분의 API는 Uniform Interface의 제약 조건 중 첫번째 제약 조건을 쉽게 만족할 수 있습니다.</li>
</ul>
<pre><code>URL로 통일해서 통신을 합니다.

http://localhost:8080/profile-photo
http://localhost:8080/location </code></pre><h3 id="manipulation-of-resources-through-representations">manipulation of resources through representations</h3>
<ul>
<li>representations 전송을 통해서 resources를 조작해야 합니다.</li>
<li>resources를 만들거나 업데이트하고나 삭제하거나 할때 HTTP message에 표현을 담아서 전송을 해서 달성할 수 있어서 대체로 만족하고 있습니다.</li>
<li>동일한 URI에서 동일한 자원의 표현을 둘 이상 지원할 수 있습니다. </li>
<li>representations의 형태는 content-type으로 결정합니다.</li>
</ul>
<h3 id="self-descriptive-messages-자체-표현-구조"><strong>self-descriptive messages (자체 표현 구조)</strong></h3>
<p><strong>자기 서술적 메시지라는 의미로 각 메시지는 자신을 어떻게 처리해야 하는지 충분한 정보를 HTTP Method, Status Code, Header 등을 활용하여 전달 해야합니다.</strong></p>
<pre><code class="language-http">GET / HTTP/1.1

이 HTTP 요청 메시지는 목적지가 존제하지 않기때문에 self-descriptive가 아닙니다.</code></pre>
<pre><code class="language-http">GET / HTTP/1.1
Host: www.example.org

목적지를 추가해서 self-descriptive 조건을 만족 시킬 수 있습니다.</code></pre>
<pre><code class="language-http">HTTP/1.1 200 OK
    [ { “op” : “remove”, “path” : “a/b/c&quot;} ] 

이 부분을 해석하기 위해서는 Client가 이 응답을 받고 해석해야 하지만 이 정보가 어떤 문법으로 작성된것인지 알 수 없기때문에 self-descriptive가 아닙니다.</code></pre>
<pre><code class="language-http">HTTP/1.1 200 OK
Content-Type: application/json
    [ { “op” : “remove”, “path” : “a/b/c&quot;} ] 

Content-Type에 어떻게 문법을 해석할것인지를 알려주면 대갈호의 의미, 중갈호의 의미, 큰 따옴표의 의미 등을 Client가 문법에 맞게 파싱이 가능해지고 문법을 해석할 수 있게 만들어 줍니다. 

여기까지 작성하여도 self-descriptive의 조건을 만족하지 않습니다. 왜냐하면 op, path 와 같은 문자열로 보내는 데이터의 정확한 의미를 알 수 없기때문입니다.</code></pre>
<pre><code class="language-http">HTTP/1.1 200 OK
Content-Type: application/json + patch+json
    [ { “op” : “remove”, “path” : “a/b/c&quot;} ] 

patch+json은 media type으로 정의되어 있는 메시지로 JSON PATCH 명세를 찾아서 메시지를 해석할 수 있습니다. 이제는 self-descriptive 조건을 만족하고 있습니다.</code></pre>
<p>self-descriptive messages는 메세지를 봤을때 메시지의 내용으로 온전히 모든 정보가 해석이 가능해야 하지만 대부분의 API가 이 부분을 상당히 만족하지 못하고 있습니다.</p>
<h3 id="hypermedia-as-the-engine-of-application-statehateoas">Hypermedia As The Engine Of Application State(HATEOAS)</h3>
<ul>
<li>애플리케이션의 상태는 Hyperlink를 이용해서 전이되어야합니다. </li>
<li>Hypermedia는 HyperText를 확장한 개념으로 hypertext가 아닌 매체를 고려해서 상위 개념으로 정의했습니다. 대부분의 API는 hypertext로 표현합니다.</li>
<li>Server에서 응답을 보낼때는 Hpyermedia를 데이터와 함께 보내서 다음 액션에 대한 선택지를 클라이언트에게 줘야합니다.</li>
<li>클라이언트는 서버의 독자적인 진화에 영향을 받아선 안되며, 서버 상의 구현에 의존하면 안됩니다.</li>
</ul>
<p><strong>결국 HATEOAS의 목적은(서버-클라이언트 간 의존성을 분리해야만 가능한) 독자적인 진화와 확장을 보조하는 것이며, hypermedia는 그 목적을 이루는 데 기여해야 합니다.</strong></p>
<pre><code class="language-http">HTTP/1.1 200 OK
Content-Type: text/html
&lt;html&gt;
&lt;head&gt;&lt;/head&gt;
&lt;body&gt;
    &lt;a href=&quot;/test&quot;&gt;test&lt;/a&gt;
&lt;/body&gt;
&lt;/html&gt;

server의 응답이 html 같은 경우는 &lt;a&gt; 태그를 통해서 hyperLink가 있고 hyperLink로 다음 상태로 전이가 가능해서 따로 설정을 하지 않아도 HATEOAS를 만족합니다.</code></pre>
<pre><code class="language-http">HTTP/1.1 200 OK
Content-Type: application/json
Link: &lt;/articles/1&gt;; rel=“previous”,
      &lt;/articles/3&gt;; rel=“next”;
{
    “Title” : “The second article”,
    “content” : “Hello! Brother&quot;
}

JSON data 또한 Link 헤더를 사용하여 hyperLikn를 통해서 리스소와 연결되어 있는 다른 리소스를 가르킬 수 있는 기능을 제공해서 HATEOAS를 만족할 수 있습니다.

위의 http를 보면 게시글에 관련된 정보로 이전 게시물의 URI는 &lt;/articles/1&gt;이고 다음 게시물의 URI는 &lt;/articles/3&gt;라고 하는 정보를 표현해 주었습니다. 또한 이 정보는 Link 헤더가 http 표준으로 이미 정의되어 있기 때문에 이 메시지를 보는 사람이 온전히 해석해서 어떻게 링크가 되어 있는지가를 이해하고 hyperLink를 찾아 다른 상태로 전이를 할 수 있어서 위의 예제는 HATEOAS의 조건을 만족합니다.</code></pre>
<h3 id="왜-uniform-interface가-필요한가">왜 Uniform Interface가 필요한가?</h3>
<ul>
<li>서버와 클라이언트가 각각 독립적인 진화를 이루기 위해서입니다.</li>
<li>*<em>서버의 기능이 변경되어도 클라이언트를 업데이트할 필요가 없어야 하기 때문입니다. *</em></li>
<li>REST를 만들게 된 계기를 생각해보면 HTTP/1.0를 만들게 되면서 이전에 구축한 WEB에 영향이 미치지 않게 하기 위해서 REST가 등장했습니다. 즉, 독립적인 진화를 통해 상호 운영성(처음 REST 개념에서 이해할 수 없는 용어)을 지키는 방법을 고안한 것이 REST입니다.</li>
</ul>
<p><strong>REST가 목적하는 것은 독립적인 진화입니다. 독립적인 진화를 하기 위해서는 Uniform Interface가 반드시 만족되어야 합니다. 그렇기 때문에 Uniform Interface를 만족하지 못하면 REST라고 부를 수 없습니다.</strong></p>
<br />

<h2 id="참고-자료">참고 자료</h2>
<p><a href="https://tansfil.tistory.com/58">쉽게 알아보는 서버 인증 1편(세션/쿠키 , JWT)</a></p>
<p><a href="https://woowacourse.github.io/tecoble/post/2021-05-22-cookie-session-jwt/">인증 방식 : Cookie &amp; Session vs JWT</a></p>
<p><a href="http://www.opennaru.com/opennaru-blog/jwt-json-web-token/">JWT ( JSON WEB TOKEN ) 이란?</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스터디] 비동기 프로그래밍 in JavaScript]]></title>
            <link>https://velog.io/@park-moen/js-Asynchronous</link>
            <guid>https://velog.io/@park-moen/js-Asynchronous</guid>
            <pubDate>Fri, 03 Feb 2023 12:25:17 GMT</pubDate>
            <description><![CDATA[<h2 id="비동기-프로그래밍">비동기 프로그래밍</h2>
<p>비동기 프로그래밍은 현재 실행중인 작업이 완료되지 않아도 다음 작업을 실행할 수 있는 방식입니다. </p>
<p>즉, 동식에 여러 작업이 진행할 수 있는 장점이 있습니다. 하지만 코드가 실제로 실행되는 순서가 항상 동일하지 않으므로, 코드의 가독성을 해치고 디버깅을 어렵게 만든다는 비판을 받아왔습니다. 이러한 문제점을 해결하기 위해 JavaScript에서는 다양한 비동기 프로그래밍 기법이 존재합니다.</p>
<h2 id="callback-함수">Callback 함수</h2>
<p>프로그래밍에서 콜백 또는 콜백 함수는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드입니다. 콜백 함수로 비동기 로직을 작성할 수 있지만, 모든 콜백 함수가 비동기 로직에 사용되는 것은 아닙니다. 배열 고차 함수 map, filter, etc… 에서 인수로 콜백 함수를 받아서 동기적으로 사용 될 수 있습니다.</p>
<pre><code class="language-jsx">// [Deep Dive] 도서에 코드를 가져왔습니다.

const POSTS_URL = &#39;https://jsonplaceholder.typicode.com/posts&#39;;

const getPosts = (url) =&gt; {
  const xhr = new XMLHttpRequest();
  xhr.open(&#39;GET&#39;, url);
  xhr.send();

  xhr.onload = () =&gt; {
    if (xhr.status === 200) {
      console.log(JSON.parse(xhr.response));
    } else {
      console.error(`${xhr.status} ${xhr.statusText}`);
    }
  };
};

const posts = getPosts(POSTS_URL);

console.log(posts) // ??</code></pre>
<p><code>console.log(posts)</code>의 결과는 <code>undefined</code> 입니다. <code>undefined</code> 값이 나온 이유는 <code>getPosts</code>의 <code>xhr</code> 내장 함수가 비동기로 동작하기 때문에 모든 동기 로직이 끝난 후에 비동기 로직이 실행되어서 실행 순서를 보장받지 못해서입니다. 이렇듯 비동기 로직을 통해 결괏값을 받아 오기 위해서는 콜백 함수 내부에서 그 처리를 진행해야 합니다.</p>
<pre><code class="language-jsx">const getPosts = (url, successCallback, failCallback) =&gt; {
  const xhr = new XMLHttpRequest();

  xhr.open(&#39;GET&#39;, url);
  xhr.send();

  xhr.onload = () =&gt; {
    if (xhr.status === 200) {
      successCallback(JSON.parse(xhr.response));
    } else {
      failCallback(xhr.status, xhr, statusText);
    }
  };
};

const posts = getPosts(
  POSTS_URL,
  result =&gt; {
    // 성공 로직
  },
  error =&gt; {
    // 실패 로직
  }
);</code></pre>
<p>이제는 비동기 로직의 통한 결괏값을 받아 오기 위해서는 콜백 함수 내부에서 모든 작업을 처리하는 방법으로 비동기 로직을 동기적으로 수행할 수 있게 되었습니다. 그런데 만약 해당 콜백 함수의 결괏값을 받아서 또 다른 네트워크 통신을 여러 번 진행해야 하거나, 여러 에러 처리를 진행해야 하는 경우는 어떻게 해야 할까요? </p>
<pre><code class="language-jsx">getPosts(POSTS_URL, result =&gt; {
  getPosts(POSTS_URL, result =&gt; {
    getPosts(POSTS_URL, result =&gt; {
      getPosts(POSTS_URL, result =&gt; {
        getPosts(POSTS_URL, result =&gt; {
          getPosts(POSTS_URL, result =&gt; {
            getPosts(POSTS_URL, result =&gt; {
              getPosts(POSTS_URL, result =&gt; {
                getPosts(POSTS_URL, result =&gt; {});
              });
            });
          });
        });
      });
    });
  });
});</code></pre>
<p>극단적인 예시이지만 데이터 로직이 조금만 복잡해져도 가독성이 떨어지면서 쉽게 접근할 수 없는 로직이 만들어집니다. 이런 상황이 <code>콜백 헬(callback hell)</code> 이라는 단어로 붙었습니다.</p>
<h2 id="promise">Promise</h2>
<p>콜백 함수의 문제점을 해결하기 위해 ES6에서 Promise 생성자 함수가 등장했습니다.  Promise 생성자 함수의 인수 <code>resolve</code> 와 <code>reject</code> 는 JavaScript에서 자체 제공하는 콜백 함수입니다.</p>
<ul>
<li>resolve(value): 성공적으로 로직이 끝난 경우 value 인자와 함께 호출됩니다.</li>
<li>reject(error): 에러가 발생 시 에러 객체를 나타내는 error 인자와 함께 호출됩니다.</li>
</ul>
<pre><code class="language-jsx">const getPostsWithPromise = (url) =&gt; {
  return new Promise((resolve, reject) =&gt; {
    const xhr = new XMLHttpRequest();

    xhr.open(&#39;GET&#39;, url);
    xhr.send();

    xhr.onload = () =&gt; {
      if (xhr.status === 200) {
        resolve(JSON.parse(xhr.response));
      } else {
        reject(xhr.status, xhr.statusText);
      }
    };
  });
};

const posts = getPostsWithPromise(POSTS_URL);
console.log(&#39;posts: &#39;, posts);</code></pre>
<p>그렇다면 Promise 생성자 함수에서 어떻게 성공과 실패를 판단하고 후속 처리를 할 수 있을까요? </p>
<p>Promise 생성자 함수 결괏값으로는 Promise 객체를 반환합니다. 반환 결과로 상태를 가지고 있으며 JavaScript에서 직접 접근할 수 없는 값으로 이루어져 있습니다.</p>
<ul>
<li>pendding: 비동기 처리가 아직 처리되지 않은 상태</li>
<li>fulfilled: 비동기 처리가 성공적으로 수행된 상태 → resolve(value) 콜백 함수를 실행</li>
<li>rejected: 비동기 처리가 실패적으로 수행된 상태 → reject(error) 콜백 함수를 실행</li>
</ul>
<aside>
🔑 settled: pendding 상태에서 fulfilled 또는 rejected 상태로 변화하는 모든 과정

</aside>

<p><code>pendding</code>, <code>fulfilled</code>, <code>rejected</code> 의 결과를 통해 Promise 함수가 판단해서 콜백 함수를 실행해줍니다. </p>
<p>또한,  Promise 생성자 함수의 인수 <code>resolve</code> 와 <code>reject</code>  콜백 함수에서 비동기 처리 상태가 변화하면 이에 따른 후속처리를 해야 합니다. Promise 생성자 함수가 반환하는 Promise 객체에는 <code>then</code>, <code>catch</code>, <code>fallnay</code> 후속 처리 메서드를 제공합니다. </p>
<h3 id="promisepropotypethen">Promise.propotype.then</h3>
<p><code>Promise.propotype.then</code> 메서드는 Promise 후처리에서 가장 기본입니다. </p>
<ul>
<li>첫 번째 콜백 함수는 Promise가 fullfilled 상태가 되면 호출됩니다. 이때 콜백 함수의 인수로 비동기 처리 결과를 받습니다.</li>
<li>두 번째 콜백 함수는 Promise가 rejected 상태가 되면 호출됩니다. 이때 콜백 함수의 인수로 에러 객체를 받습니다.</li>
</ul>
<pre><code class="language-jsx">const posts = getPostsWithPromise(POSTS_URL);

posts.then(
  result =&gt; console.log(result),
  error =&gt; console.error(error)
);</code></pre>
<p>then 메서드는 언제나 Promise 객체를 반환합니다. 만약 이전 비동기 결과를 바탕으로 새로운 비동기 결과를 반환하고 싶다면 then 메서드를 체이닝 할 수 있습니다.</p>
<pre><code class="language-jsx">const posts = getPostsWithPromise(POSTS_URL);

posts
  .then(
    result =&gt; getPostsWithPromise(result.POSTS_URL),
    error =&gt; console.error(error)
  )
  .then(
    result =&gt; getPostsWithPromise(result.POSTS_URL),
    error =&gt; console.error(error)
  )
  .then(
    result =&gt; getPostsWithPromise(result.POSTS_URL),
    error =&gt; console.error(error)
  )
  .then(
    result =&gt; console.log(result),
    error =&gt; console.error(error)
  );</code></pre>
<h3 id="promiseprototypecatch">Promise.prototype.catch</h3>
<p><code>Promise.prototype.catch</code> 메서드는 한 개의 콜백 함수를 인수로 전달 받습니다. <code>catch</code> 메서드는 <code>rejected 상태</code>인 경우에만 호출됩니다. 즉, Error 처리를 위한 후처리 메서드입니다. </p>
<pre><code class="language-jsx">const posts = getPostsWithPromise(POSTS_URL);

posts.catch(
  error =&gt; console.error(error)
);</code></pre>
<p>또한 <code>catch</code> 메서드 항상 Promise 객체를 반환받으며 <code>then(null, onErrorCallback)</code> 과 동일하게 동작합니다.</p>
<pre><code class="language-jsx">const posts = getPostsWithPromise(POSTS_URL);

posts.then(
    null,
  error =&gt; console.error(error)
);</code></pre>
<h3 id="promiseprototypefinally">Promise.prototype.finally</h3>
<ul>
<li><code>Promise.prototype.finally</code> 메서드는 한 개의 콜백 함수를 순수로 전달받습니다.</li>
<li><code>Promise.prototype.finally</code> 메서드의 콜백 함수는 성공 또는 실패와 무관하게 무조건 한 번 실행(호출)합니다.</li>
<li><code>Promise.prototype.finally</code> 메서드는 Promise 객체를 반환합니다.</li>
<li><code>Promise.prototype.finally</code> 메서드는 프로미스 상태와 상관없이 공통적으로 수행해야 하는 처리 과정이 있을 때 유용합니다.</li>
</ul>
<pre><code class="language-jsx">posts.finally(() =&gt; console.log(&#39;프로미스 공통 처리&#39;));</code></pre>
<h2 id="async-await">async-await</h2>
<p>그러나 Promise는 여전히 콜백 함수를 사용하기 때문에, 콜백 헬의 문제를 해결할 수 없습니다. ES6에 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Generator">제너레이터</a>가 도입되어 비동기를 동기처럼 구현했지만, 코드가 장황해지고 가독성이 나빠졌다. 이에 뒤따라 ES8에서 보다 간단하고 가독성 좋게 비동기 처리를 동기 처리처럼 구현하는 async-await가 도입되었습니다.</p>
<p>async/await는 Promise를 기반으로 동작하며, then/catch/finally와 같은 후속 처리 메서드 없이 마치 동기 처리처럼 사용할 수 있습니다.</p>
<pre><code class="language-jsx">const getPostWithAsync = async (url) =&gt; {
  try {
    const response = await fetch(url);
    return await response.json();
  } catch (err) {
    console.err(err);
  } finally {
    console.log(&#39;공통 처리&#39;);
  }
};

const posts = getPostWithAsync(POSTS_URL);
console.log(&#39;posts: &#39;, posts);

posts.then(console.log);</code></pre>
<p>콜백 함수나 Promise는 무조건 api를 호출한 후, 또 다른 콜백 함수를 실행하여 데이터의 처리가 가능했지만, async/await는 해당 함수 내부에서 바로 동기 처리처럼 데이터를 수정할 수 있다. 또한 try-catch 문으로 에러 처리를 진행할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DNS의 기본 개념과 동작 방식]]></title>
            <link>https://velog.io/@park-moen/devcourse-study01</link>
            <guid>https://velog.io/@park-moen/devcourse-study01</guid>
            <pubDate>Sun, 08 Jan 2023 21:22:49 GMT</pubDate>
            <description><![CDATA[<h2 id="dnsdomain-name-system-기본-개념">DNS(Domain Name System) 기본 개념</h2>
<ul>
<li>도메인 네임 시스템은 도메인(google.com)을 IP 주소(142.250.196.142)로 변환해주는 역할을 합니다. 또는 IP주소를 도메인으로 변환하는 작업도 할 수 있습니다.</li>
<li>인터넷을 사용하는 사용자는 특정 숫자로 저장된 IP 주소를 모두 외울 수 없어서, 사용자가 이해할 수 있는 문자열 형태인 도메인 네임을 사용합니다. 도메인 네임을 위치(위도, 경도)를 도로명으로 비유하는 경우와 유사합니다.</li>
<li>DNS는 도메인 네임 스페이스 구조로 계층적 데이터 베이스의 실체입니다.</li>
<li>DNS가 도메인 네임을 IP주소로 변경하기 위해서 내부적으로 DNS 서버가 동작합니다.</li>
<li>DNS 서버 요청 / 응답의 매개체로 DNS query를 사용합니다.</li>
<li>매번 도메인 주소를 IP 주소로 바꾸는 작업은 과도한 리소스 낭비일 수 있어서 DNS cache를 사용해서 리소스 낭비를 방치할 수 있습니다.</li>
</ul>
<br>

<h2 id="domain-name-space">Domain Name Space</h2>
<ul>
<li>도메인을 IP 주소로 변경하는 서버가 하나만 존재한다면 전세계적으로 무수히 많은 도메인을 동시에 처리할 수 있는 물리적인 컴퓨터가 현대 과학으로 해결할 수 있을까요?</li>
<li>위의 문제를 해결하기 위해 DNS 구조는 분산 시스템과 계층적 구조로 이루어져 있습니다.</li>
<li>분산 시스템을 통해 레이블을 dot(.)으로 구분해서 각 기관에서 관리합니다.<ul>
<li>레이블은 도메인을 나누는 최소 단위인 데이터로 문자열과 레이블 길이로 이루어져 있습니다.</li>
<li>레이블은 고유해야 합니다. 그렇지 않으면 도메인 네임을 탐색한 과정에서 충돌이 발생할 수 있습니다.</li>
</ul>
</li>
<li>분산 시스템을 통해 나누어진 레이블은 트리 자료구조로 이루어진 계층적 구조로 IP 주소를 매핑합니다.<ul>
<li>계층 구조로 이루어진 Domain Name Space는 최상위 계층으로 시작해서 하위 계층으로 순차적으로 탐색해서 IP 주소를 반환합니다.</li>
</ul>
</li>
<li>이러한 분산 시스템과 계층적 구조를 통해 효율적으로 도메인을 관리할 수 있습니다.</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/57402711/211219530-8e01e104-3902-4d36-857d-94c5b572294a.png" alt="image"></p>
<br>

<h2 id="dns-서버">DNS 서버</h2>
<h3 id="dns-서버-기본-개념">DNS 서버 기본 개념</h3>
<ul>
<li>모든 DNS 서버는 DNS recursor, Root Name Server, TLD Name Server, Authoritative Name Server(SLD Name Server)로 분류됩니다.</li>
<li>DNS cache가 존재하지 않는다면 4개의 DNS 서버를 순차적으로 작동하여 요청한 도메인 주소를 IP 주소로 변환해서 Client에 전달하는 작업을 합니다.</li>
<li>DNS 서버는 Domain Name Space의 계층적 구조에 대한 정보를 저장하고 있는 서버입니다.</li>
<li>DNS recursor는 Resolver, Recursive DNS Server, Local Server, ISP(통신사) Server라고 부르기도 합니다. 이 글에서는 DNS recursor로 통일하겠습니다.</li>
</ul>
<h3 id="root-name-server">Root Name Server</h3>
<ul>
<li>Root Name Server는 계층 구조 트리에서 최상위 경로를 담당하고 있습니다.</li>
<li>도메인 주소 맨 뒤에는 대부분 dot<code>naver.com(.)</code>이 생략되어 있고, 생략된 dot이 Root Name Server입니다.</li>
<li>사람이 이해할 수 있는 도메인 네임을 IP 주소로 변환하는 첫 번째 단계로 ICANN이 직접 관리하는 절대 존엄 서버입니다.<ul>
<li>패킷의 실질적인 크기 제한으로 인해 루트 서버 수를 13개 서버 주소로 제한하도록 결정되었습니다.</li>
<li>갯수가 13가지가 아닌 13가지 유형의 루트 이름 서버가 있으며 전 세계에 각각의 사본이 다수 있습니다.</li>
</ul>
</li>
<li>도메인 확장자(<code>.kr</code>, <code>.com</code>) 레이블을 구분해서 TLD Name Server로 안내합니다.</li>
</ul>
<h3 id="tldtop-level-domain-name-server">TLD(<strong>Top-Level Domain</strong>) Name Server</h3>
<ul>
<li>Authoritative DNS 서버의 주소를 저장하고 안내하는 역할을 합니다.</li>
<li>만약 <code>naver.com</code> 으로 DNS에 요청을 보내게 된다면 Root Name Server를 통해 <code>.com</code> 도메인 확장자를 반환 받아서 <code>.com</code> 을 관리하는 기관에 <code>naver.com</code> 도메인 주소가 존재하는지 확인하여 Authoritative DNS로 안내합니다.</li>
<li>TLD Name Server는 ICANN의 지사인 IANA(Internet Assigned Numbers Authority)가 관리하며, 두가지 서버로 구분합니다.<ul>
<li>일반 최상위 도메인: 가별로 고유하지 않은 도메인입니다. (<code>.com</code>, <code>.net</code>)</li>
<li>국가 코드 최상위 도메인: 국가 또는 주와 관련된 모든 도메인이 포함됩니다. (<code>.kr</code>, <code>.us</code>)</li>
</ul>
</li>
</ul>
<h3 id="authoritative-name-serversld-name-server">Authoritative Name Server(SLD Name Server)</h3>
<ul>
<li>일반적으로 DNS 서버에서 가장 마지막 단계로 실제로 DNS 리소스 레코드를 보유하고 담당하는 서버입니다.</li>
<li>실제 도메인과 IP 주소의 관계가 기록,저장, 변경되는 서버입니다. - <code>유료 도메인을 대여했을때 명의가 변경된다는 것을 의미하는가?</code></li>
</ul>
<h3 id="dns-recursor">DNS recursor</h3>
<ul>
<li>DNS recursor는 Client 요청을 받아 Name Server로 전달하고 반환 받은 정보(IP 주소)를 Client에게 알려주는 중계자 역할을 합니다.</li>
<li>이 과정에서 계층적 구조 트리에서 순차적으로 반환하는 결과를 재귀적으로 탐색합니다.</li>
<li>재귀적으로 탐색해서 최종 결과를 DNS cache에 저장해 동일한 도메인 주소로 요청을 보내면 재귀적으로 탐색하는 작업을 하지 않고 즉시 Client에게 IP 주소를 반환해서 불필요한 리소스가 발생하지 않게 할 수 있습니다.</li>
</ul>
<p><img src="https://www.cloudflare.com/img/learning/dns/what-is-dns/dns-record-request-sequence-1.png" alt="https://www.cloudflare.com/img/learning/dns/what-is-dns/dns-record-request-sequence-1.png"></p>
<br>

<h2 id="dns-query">DNS query</h2>
<ul>
<li>DNS 클라이언트와 DNS 서버는 DNS 쿼리를 교환하며, 요청과 응답의 사용되는 매개체입니다.</li>
<li>쿼리를 조합을 사용하면 DNS 확인을 위한 최적화된 프로세스가 되어 이동 거리를 줄일 수 있습니다.</li>
</ul>
<h3 id="recursive-query">Recursive Query</h3>
<ul>
<li>Client와 DNS recursor 통신에 사용되는 쿼리입니다.</li>
<li>실제 IP 주소를 반환합니다.</li>
<li>Recursive Query와 DNS recursor의 차이를 정확히 인지하고 있어야 합니다.<ul>
<li>Recursive Query는 요청과 응답하는 과정을 포함하는 실제 반환 결과 값입니다.</li>
<li>DNS recursor는 요청과 응답 과정을 실행하는 서버입니다.</li>
</ul>
</li>
</ul>
<h3 id="iterative-query">Iterative Query</h3>
<ul>
<li>DNS recursor과 Name Server 통신에 사용되는 쿼리입니다.</li>
<li>반복적으로 쿼리를 보내서 결과물(IP 주소)를 알아내서 DNS recursor에게 IP 주소를 보냅니다.</li>
<li>DNS recursor에 이미 IP 주소가 캐시 되어있다면 이 과정은 생략합니다.</li>
</ul>
<br>

<h2 id="dns-동작-과정">DNS 동작 과정</h2>
<ol>
<li>브라우저(Client)에 <code>naver.com</code> 입력하면 Recursive Query가 DNS Recursor로 요청을 보냅니다.</li>
<li>DNS Recursor에서 DNS cache 유무를 확인합니다.</li>
<li>만약 DNS cache가 존재하지 않는다면 DNS Recursor에서 Iterative Query가 Root Name Server로 요청을 보냅니다.</li>
<li>Root Name Server에서 DNS Recursor로 도메인 확장자 <code>.com</code> 을 반환합니다.</li>
<li>반환 받은 쿼리를 DNS Recursor는 Iterative Query 형태로 TLD Name Server로 보내서 <code>.com</code> 을 관리하는 기관에서 <code>naver.com</code> 이 존재하는지 확인 후 쿼리를 DNS Recursor로 보냅니다.</li>
<li>DNS Recursor는 Authoritative Name Server에 실제 도메인 주소와 동일한 주소가 있는지 확인하기 위해 Iterative Query를 보내서 IP 주소를 반환합니다. </li>
<li>6번 과정에서 DNS cache를 저장해서 불필요한 리소스가 발생하는 문제를 예방합니다.</li>
<li>이어서, DNS Recursor는 처음 요청한 도메인가 매핑된 IP 주소로 웹 브라우저에게 응답합니다.</li>
<li>브라우저가 IP 주소로 HTTP 요청을 보냅니다.</li>
<li>IP 주소의 서버가 브라우저에서 렌더링할 웹 페이지를 반환합니다.</li>
</ol>
<p><img src="https://i0.wp.com/hanamon.kr/wp-content/uploads/2022/04/DNS-%E1%84%83%E1%85%A9%E1%86%BC%E1%84%8C%E1%85%A1%E1%86%A8%E1%84%80%E1%85%AA%E1%84%8C%E1%85%A5%E1%86%BC.png?w=2810&ssl=1" alt="https://i0.wp.com/hanamon.kr/wp-content/uploads/2022/04/DNS-%E1%84%83%E1%85%A9%E1%86%BC%E1%84%8C%E1%85%A1%E1%86%A8%E1%84%80%E1%85%AA%E1%84%8C%E1%85%A5%E1%86%BC.png?w=2810&amp;ssl=1"></p>
<br>

<h2 id="보완해야-할-정보">보완해야 할 정보</h2>
<ul>
<li>브라우저 캐싱, OS 캐싱, ISL 캐싱에 대한 정보 추가</li>
<li>만약 브라우저 캐싱에 존재하지 않는다면 -&gt; OS 캐싱을 확인하고 OS 캐싱에 존재하지 않으면 ISL 캐싱 순서호 확인하는지에 대한 정보 찾아보기</li>
</ul>
<p><a href="https://dnssec.tistory.com/27">도메인 이름 데이터 포맷, 레이블, 그리고 FQDN</a></p>
<p><a href="https://kongit.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%8C%A8%ED%82%B7Network-Packet%EC%9D%B4%EB%9E%80-%EC%A0%95%EC%9D%98-%ED%8C%A8%ED%82%B7-%EC%86%90%EC%8B%A4">네트워크 패킷(Network Packet)이란? / 정의/ 패킷 손실</a></p>
<h2 id="참고-자료">참고 자료</h2>
<p><a href="https://hanamon.kr/dns%eb%9e%80-%eb%8f%84%eb%a9%94%ec%9d%b8-%eb%84%a4%ec%9e%84-%ec%8b%9c%ec%8a%a4%ed%85%9c-%ea%b0%9c%eb%85%90%eb%b6%80%ed%84%b0-%ec%9e%91%eb%8f%99-%eb%b0%a9%ec%8b%9d%ea%b9%8c%ec%a7%80/">DNS란? (도메인 네임 시스템 개념부터 작동 방식까지) - 하나몬</a></p>
<p><a href="https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-DNS-%EA%B0%9C%EB%85%90-%EB%8F%99%EC%9E%91-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4-%E2%98%85-%EC%95%8C%EA%B8%B0-%EC%89%BD%EA%B2%8C-%EC%A0%95%EB%A6%AC">[WEB] 🌐 DNS 개념 &amp; 동작 ★ 알기 쉽게 정리</a></p>
<p><a href="https://www.cloudflare.com/ko-kr/learning/dns/what-is-dns/">DNS란 무엇입니까? | DNS 작동 원리</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LocalStorage, SessionStorage, Cookie 차이점]]></title>
            <link>https://velog.io/@park-moen/LocalStorage-SessionStorage-Cookie-%EC%B0%A8%EC%9D%B4%EC%A0%90-1wuxhf4g</link>
            <guid>https://velog.io/@park-moen/LocalStorage-SessionStorage-Cookie-%EC%B0%A8%EC%9D%B4%EC%A0%90-1wuxhf4g</guid>
            <pubDate>Wed, 19 Oct 2022 08:23:22 GMT</pubDate>
            <description><![CDATA[<h2 id="web-storage">Web Storage</h2>
<p>HTML5에 웹 데이터를 클라이언트에서 저장할 수 있는 새로운 자료구조인 Web Storage가 추가되었습니다.</p>
<p>Web Storage의 개념은 키/값 쌍으로 데이터를 저장하며 키를 기반으로 데이터를 조회하는 패턴입니다.</p>
<p>그리고 영구 저장소, 임시 저장소를 구분해서 사용할 수 있습니다.</p>
<h2 id="localstorage-sessionstorage">LocalStorage, SessionStorage</h2>
<p>localStorage와 sessionStorage의 차이점음 영구성입니다. </p>
<p>localStorage는 브라우저 창을 닫아도 저장소에 저장된 데이터는 지워지지 않고 다시 브라우저 창을 열어도 그 데이터가 그대로 유지됩니다.</p>
<p>sessionStorage는 브라우저 창을 닫으면 저장소에 저장된 데이터는 지워지며 다시 브라우저 창을 열어도 그 전의 데이터를 유지하지 않고 사라집니다. 즉, 두 Web Storage의 차이점은 영구성이며 모두 전역 객체의 프로퍼티입니다. </p>
<p>그리고  Storage 객체를 상속받기 때문에 localStorage와 sessionStorage가 사용하는 메서드는 setItem, getItem, removeItem, clear로 모두 동일하게 동작합니다.</p>
<p>Web Storage를 사용하며 주의점으로는 setItem 메서드를 사용하여 키/값 쌍으로 데이터를 저장하면 항상 문자열로 저장되며 getItem 메서드로 호출하면 문자열이 출력됩니다. 만약 객체 타입을 저장하고 싶을 경우 바로 객체 타입을 작성하지 않고 JSON.stringify()로 객체를 감싸서 직열화를 해주며 저장한 데이터를 출력하고 싶을 경우 JSON.parse로 역직렬화를 해줘야 정확한 데이터를 출력받을 수 있습니다.</p>
<h2 id="cookie">Cookie</h2>
<p>Cookie는 Web Storage가 나오기 이전에 브라우저 저장소 역할을 해왔습니다.</p>
<p>쿠키는 만료기간이 있는 key-value storage입니다.</p>
<p>쿠키는 하나의 도메이 페이지에서 최대 20개, 4KB의 용량제한이 있습니다.</p>
<p>매 요청마다 서버로 쿠키가 같이 전송(쿠키는 처음부터 서버와 클라이언트 간의 지속적인 데이터 교환을 위해 만들어졌기 때문)됩니다.</p>
<p>서버로 전송안해도 되는 불필요한 데이터 또한 서버로 보내는 단점을 가지고 있어서 web Storage가 생기고 localStorage, sessionStorage에 저장하면 됩니다.</p>
<p>반영구적으로 데이터를 저장할 수 있으며 유효기간을 정할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[실행 컨텍스트 & 호이스팅과 스코프의 관계]]></title>
            <link>https://velog.io/@park-moen/writeid4116e23f-706c-4c7b-9761-aa98ea82752c</link>
            <guid>https://velog.io/@park-moen/writeid4116e23f-706c-4c7b-9761-aa98ea82752c</guid>
            <pubDate>Wed, 19 Oct 2022 06:56:21 GMT</pubDate>
            <description><![CDATA[<h2 id="실행-컨텍스트execution-context란">실행 컨텍스트(Execution Context)란?</h2>
<blockquote>
<p>🔑 실행 컨텍스트는 식별자(변수, 함수, 클래스 드의 이름)를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 메커니즘으로, 모든 코드는 실행 컨텍스트를 통해 실행되고 관리됩니다.</p>
</blockquote>
<p>실행 컨텍스트는 소스코드 평과와 실행을 통해 JavaScript의 로직을 실제로 구현하는 실체입니다.</p>
<h3 id="1-소스코드-평과">1. 소스코드 평과</h3>
<ul>
<li><p>전역 코드 평과: 전역 실행 컨텍스트 생성과 함께 전역 범위의 변수 선언문, 함수 선언문을 먼저 실행하여 실행 컨텍스트 환경(Global Lexical Enviroment)의 <code>Key: Value</code> 형태에서 <code>key</code>에 식별자를 저장합니다. <u>이 과정에서 JavaScript의 Hoisting이 발생하게 되는 원인입니다.</u></p>
</li>
<li><p>함수 코드 평과: 함수 호출문을 만나가 되면 호출된 함수로 실행 순서가 변경되고 함수 실행 컨텍스트를 생성과 함께 매개변수, 지역 범위의 변수 선언문, 함수 선언문을 먼저 실행하여 실행 컨텍스트 환경(Function Lexical Enviroment)의 <code>Key: Value</code> 형태에서 <code>key</code>에 식별자를 저장합니다. 또한 arguments 객체가 생성되어 지역 스코프에 등록되고 this 바인딩도 결정됩니다.</p>
</li>
</ul>
</br>

<h3 id="2-소스코드-실행">2. 소스코드 실행</h3>
<ul>
<li><p>전역 코드 실행: 소스코드 실행 단계를 런타임이라고 지칭하며 선언문을 제외한 문들을 순차적으로 실행합니다. 이 과정에서 미리 선언문을 실행한 식별자의 존재유무를 확인하여 값을 할당하소 함수 호출문을 실행합니다.</p>
</li>
<li><p>함수 코드 실행: 전역 코드의 실행처럼 런타임이 시작되어 함수 코드가 순차적으로 실행됩니다. 이 과정에서 값의 할당과 내부 함수의 호출문을 실행합니다.</p>
</li>
</ul>
</br>
</br>

<h2 id="호이스팅">호이스팅</h2>
<blockquote>
<p>🔑 호이스팅은 JavaScript에서 선언문을 해당 스코프의 선두로 옮긴 것처럼 동작하는 특성입니다.</p>
</blockquote>
<h3 id="1-var-키워드-함수-선언문의-호이스팅">1. var 키워드, 함수 선언문의 호이스팅</h3>
<p>var 키워드와 함수 선언문은 스코프의 선두로 옮긴 것처럼 동작하여 선언문 이전에 식별자를 참조하게 되어도 Reference Error가 발생하지 않습니다.</p>
<pre><code class="language-js">// var 키워드
console.log(foo); // undefined

var foo;


// 함수 선언문 
bar() // &#39;Hello World&#39;

function bar() {
  console.log(&#39;Hello World&#39;);
}</code></pre>
<ul>
<li><p>var 키워드는 호이스팅 현상이 발생하여 선언문 이전에 값을 참조하게 된다면 선언 단계와 초기화 단계가 한번에 진행되어 undefinde를 반환합니다.</p>
</li>
<li><p>함수 선언문으로 정의한 함수는 런타임 이전에 함수가 평가되어 함수 객체를 생성하고 자바스크립트 엔진이 함수 이름과 동일한 식별자를 암묵적으로 생성하여 생성된 함수 객체를 할당하는 특징으로 인해 함수 호출문을 함수 선언문보다 먼저 작성하여도 Reference Error가 아닌 함수 return 값을 반환합니다. </p>
</li>
</ul>
</br>

<h3 id="2-let-const-키워드의-호이스팅">2. let, const 키워드의 호이스팅</h3>
<p>let, const 키워드로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행됩니다. 이런 특징으로 선언문 이전에 값을 참조하게 된다면 Reference Errorr가 발생합니다. <u>그렇다고 let, const 키워드가 호이스팅이 발생하지 않은 것은 아닙니다.</u></p>
<pre><code class="language-js">// 선언과 초기화가 분리되어 진행됩니다.
console.log(foo); // ReferenceError: foo is not defined

let foo;
console.log(foo); // undefined

// 만약 호이스팅이 발생하지 않는다면 foo 값은 출력되어야 하지만 Reference Errorr가 발생합니다.
let foo = 1; // Global Scope

{
  console.log(foo); // ReferenceError: foo is not defined
  let foo = 2; // Local Scope
}
</code></pre>
<br>

<h3 id="호이스팅을-강제하는-방법">호이스팅을 강제하는 방법</h3>
</br>
</br>

<h2 id="스코프">스코프</h2>
<blockquote>
<p>🔑 스코프는 식별자가 선언된 렉시컬 환경을 기억하여 참조할 식별자를 찾아내는 규칙입니다.</p>
</blockquote>
<h3 id="1-렉시컬-환경">1. 렉시컬 환경</h3>
<p>렉시컬 환경(Lexical Environment)은 식별자와 식별자에 바인딩된 값, 그리고 상위 스코프에 대한 참조를 기록하는 자료구조이며 내부적으로 두 부분으로 구성되어 있습니다. </p>
<ul>
<li><p>Environment Record (환경 레코드): 지역 변수를 프로퍼티로 저장하고 있는 객체입니다. Environment Record는 소스코드의 타입에 따라 관리하는 내용이 다릅니다.</p>
</li>
<li><p>Outer Lexical Environment(외부 렉시컬 환경): 자신의 상위 렉시컬 환경을 참조합니다. <u>Outer Lexical Environment를 통해 스코프 체인을 구현합니다.</u></p>
</li>
</ul>
</br>

<h3 id="2-함수-레벨-스코프-블록-레벨-스코프">2. 함수 레벨 스코프, 블록 레벨 스코프</h3>
<ul>
<li>함수 레벨 스코프: var 키워드를 사용하여 변수를 선언하게 된다면 함수 단위로 스코프가 지정됩니다.</li>
</ul>
<pre><code class="language-js">var value = 1;

if (true) {
  // if문 안에서만 x의 스코프를 지정하고 싶어도 var 키워드는 함수 레벨 스코프를 지원해서 if문의 스코프를 가질 수 없습니다.
  var value = 2;
}

function functionLavelScope() {
  // 함수에서 전역 변수 value와 동일한 변수를 선언했지만 함수 레벨 스코프로 인해 지역 변수를 가질 수 있습니다.
  var value = 3;

  console.log(value); // 3;
}

console.log(value); // 2;</code></pre>
<ul>
<li>블록 레벨 스코프: let, const 키워드를 사용하여 변수를 선언하게 된다면 블록 단위로 스코프를 지정합니다.</li>
</ul>
<pre><code class="language-js">let value = 1;

if (true) {
  // if문도 블록문이므로 블록 레벨 스코프를 지원하여 if문만의 지역 스코프를 가질 수 있습니다.
  let value = 2;
}

function functionLavelScope() {
  // 함수에서 전역 변수 value와 동일한 변수를 선언했지만 함수 레벨 스코프로 인해 지역 변수를 가질 수 있습니다.
  let value = 3;

  console.log(value); // 3;
}

console.log(value); // 1;</code></pre>
<p><strong>이런 이유 외에 다양한 var 키워드의 문제가 있어서 var 키워드를 지양하고 let, const 키워드로 변수를 선언하는 습관을 가져야 합니다.</strong> </p>
</br>

<h3 id="3-정적-스코프렉시컬-스코프-vs-동적-스코프">3. 정적 스코프(렉시컬 스코프) vs 동적 스코프</h3>
<ul>
<li><p>정적 스코프(렉스컬 스코프): 함수가 정의된 위치에 따라 상위 스코프를 결정합니다.</p>
</li>
<li><p>동적 스코프: 함수가 호출된 위치에 따라 상위 스코프를 결정합니다.</p>
</li>
</ul>
<pre><code class="language-js">const x = 1;

function foo() {
  const x = 2;

  bar()
}

function bar() {
  console.log(x); // 정적스코프, 동적 스코프에 따라 값이 달라진다.
}</code></pre>
<p>만약 동적 스코프를 따르는 언어라면 x값은 2를 출력하게 됩니다. 하지만 JavaScrip는 정적 스코프(렉시컬 스코프) 방식에 따라 동작하여 전역 스코프에 정의된 bar 함수의 상위 스코프는 bar 함수가 어디서 호출되어도 동일한 상위 스코프를 유지합니다. 즉, JavaScript에서 x값은 1입니다.</p>
</br>

<h3 id="4-스코프-체인">4. 스코프 체인</h3>
<pre><code class="language-js">let x = 1;
let y = 2;

function outerFunction(z) {
  function innerFunction() {
    let x = 10;
    let w = 100;

    console.log(x, y, z, w); // 10, 2 , 30, 100
  }

  innerFunction();
}

console.log(outerFunction(30));</code></pre>
<p>Outer Lexical Environment(외부 렉시컬 환경)을 통해 상위 스코프에 접근할 수 있습니다. 스코프 체인은 단방향 링크드 리스트 자료구조를 사용하여 구현하여 역방향으로 스코프를 참조하는 문제를 방지했습니다. 또한 Outer Lexical Environment을 통해 상위 스코프를 찾아 Environment Record (환경 레코드)에 객체 형태로 저장된 식별자에 접근하는 방식이 스코프 체인입니다.</p>
<img src="https://velog.velcdn.com/images/park-moen/post/9059e246-03ed-4f71-a6e8-edff936d836a/image.png" alt="image" style="zoom:50%;" />

</br>
</br>

<h2 id="호이스팅과-스코프의-관계">호이스팅과 스코프의 관계</h2>
<p>호이스팅은 소스코드 평과 단계에서 선언문을 먼저 실행하여 실행 단계에서 식별자 유무를 판단할 수 있게 하기 위해서 생겨나 JavaScript의 특수한 현상입니다. 호이스팅을 통해 식별자를 미리 실행 컨텍스트에 알려주면서 소스코드 실행 단계(런타임)에 스코프의 범위를 확인하여 식별자 참조를 하여 결과를 반환합니다. 이렇듯 호이스팅 과정을 통해 식별자 유무를 확인하고 스코프를 통해 식별자의 범위를 지정하는 관계를 가지고 있습니다.</p>
</br>
</br>


<h2 id="참고한-자료">참고한 자료</h2>
<ul>
<li><a href="https://poiemaweb.com/js-execution-context">poiemaweb 실행 컨텍스트와 자바스크립트의 동작 원리
</a></li>
<li><a href="https://catsbi.oopy.io/fffa6930-ca30-4f7e-88b6-28011fde5867#4db6b80c-5074-40d1-8eaa-a3308870ccf7">실행 컨텍스트와 자바스크립트의 동작 원리</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[클로저(closure)]]></title>
            <link>https://velog.io/@park-moen/%ED%81%B4%EB%A1%9C%EC%A0%80closure</link>
            <guid>https://velog.io/@park-moen/%ED%81%B4%EB%A1%9C%EC%A0%80closure</guid>
            <pubDate>Fri, 30 Sep 2022 06:25:55 GMT</pubDate>
            <description><![CDATA[<p><strong>mdn에서 정의하는 클로저는 함수와 그 함수의 렉시컬 환경의 조합이라고 정의하고 또는 외부 변수를 기억하고 이 외부함수에 접근할 수 있는 함수입니다.</strong></p>
<p>여기서 렉시컬 환경와 정적 스코프(렉시컬 스코프)의 개념을 알고 있어야 클로저를 이해할 수 있습니다. </p>
<h2 id="렉시컬-스코프정적-스코프">렉시컬 스코프(정적 스코프)</h2>
<hr>
<p>프로그래밍 언어에서는 함수 정의 위치, 함수 호출 위치에 따라서 상위 스코프를 결정하는 정적 스코프, 동적 스코프로 정의합니다.</p>
<ul>
<li><strong>정적 스코프</strong>: 함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정합니다.</li>
<li><strong>동적 스코프</strong>: 함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정합니다.</li>
</ul>
<pre><code class="language-js">const exVar = 1;

function foo() {
  const exVar = 100;

  bar();
}

function bar() {
  console.log(exVar);
}

foo(); // 정적 스코프: 1, 동적 스코프: 100

// result: 1</code></pre>
<p>위 예제를 통해서 JavaScript는 정적 스코프로 프로그램이 정의 되어있으며 <strong>호출 위치는 상위스코프의 결정에 어떠한 영향도 주지 않으며 함수를 정의한 위치를 통해서만 스코를 결정합니다. 즉 한번 결정된 스코프를 동적으로 변화하지 않고 정의한 위치만을 상위 스코프로 인지합니다.</strong></p>
<h2 id="내부-슬로--environment-">내부 슬로 [[ Environment ]]</h2>
<hr>
<p>내부 슬롯 [[ Environment ]]는 자바스크립트 엔진 구현 알고리즘을 설명하기 ECMAScript 사양에서 사용하는 의사 프로퍼티입니다. 내부슬롯 [[ Environment ]]를 알기 위해서는 실행 컨텍스트의 개념인 외부 렉시컬 환경의 참조, 렉시컬 환경의 참조에 대해서 알고 있어야 합니다.</p>
<ul>
<li><p><strong>렉시컬 환경</strong>: 식별자와 식별자에 바인딩된 값, 그리고 상위 스코프의 참조값을 기록하는 자료구조입니다. 즉, <strong>렉시컬 환경은 스코프의 실체입니다.</strong> (식별자와 식별자에 바인딩된 값은 객체의 프로퍼티처럼 자료구조에 저장됩니다.)</p>
</li>
<li><p><strong>외부 렉시컬 환경의 참조</strong>: 현재 평가중인 소스코드를 포함하는 외부 소스코드의 렉시컬 환경(상위 스코프)의 참조값입니다. <strong>외부 렉시컬 환경에 대한 참조를 통해서 스코프 체인이 구현됩니다.</strong></p>
</li>
</ul>
<p>함수가 평가되어 함수 객체가 되는 시점에 내부슬롯[[ Environment ]]도 함수를 정의한 상위 스코프를 기억합니다. 함수가 평가되어 함수 객체가 만들어지는 시점은 함수를 정의한 상위 스코프가 평가 또는 실행되는 시점입니다. 편의상 함수 호이스팅이 발생하지 않는 함수 표현식을 통해서 설명하겠습니다. </p>
<pre><code class="language-js">var x = 1;

const foo = function () {
    console.log(x); // 1
};

foo() // 실행 컨텍스트가 생성되는 시점은 함수가 호출되는 시점가 동일입니다.</code></pre>
<p>간단한 예제를 통해서 외부 렉시컬 환경에 대한 참조(상위 스코프)와 내부 슬롯[[Environment]]의 관계에 대해서 설명하겠습니다.</p>
<ol>
<li>foo함수는 전역 객체가 실행되는 시점에 함수를 평가하여 함수 객체가 만들어집니다. </li>
<li>함수 객체가 만들어지면서 내부 슬롯[[Environment]]도 함께 만들어집니다. <ul>
<li><strong>내부 슬롯 [[Environment]]에는 함수가 평가되는 상위 스코프의 참조값이 저장됩니다.</strong></li>
</ul>
</li>
<li>foo함수가 호출되면 foo함수의 실행 컨텍스트가 생성되고 렉시컬 환경이 생성됩니다.<ul>
<li>렉시컬 환경이 생성되면서 식별자와 식별자에 바인딩된 값을 저장합니다. </li>
</ul>
</li>
<li>외부 렉시컬 환경에 대한 참조의 참조값은 내부슬롯[[Environment]]에 저장된 상위 스코프의 참조값이 외부 렉시컬 환경에 대한 참조에 저장됩니다.</li>
<li>foo함수가 호출되고 console.log(x)함수가 실행 됩니다.(console은 전역 객체의 프로퍼티로 실행 컨텍스를 만들지만 생략하겠습니다.)</li>
<li>Console.log의 인수 x는 외부 렉시컬 환경에 대한 참조로 만들어진 스코프 체인을 통해서 상위 스코프에서 식별자를 검색하고 먄약 상위 스코프에 식별자가 없다면 그 위의 상위 스코프에서 식별자를 검색하여 결과를 반환합니다.(전체 스코프에서 식별자가 전재하지 않으면 undefined를 반환합니다.) <ul>
<li><strong>console 스코프 =&gt; foo 함수 스코프 =&gt; 전역 스코프</strong>로 단반향 스코프 체인이 형성됩니다.</li>
</ul>
</li>
</ol>
<p><strong>함수가 호출되어 실행 컨텍스트를 생성하면 외부 렉시컬 환경에 대한 참조는 내부슬롯[[Environment]]에 저장된 상위 스코프를 통해서 상위 스코프를 결정하게 됩니다.</strong></p>
<p>다시 mdn에서 정의한 클로저를 보면 함수와 그 함수의 렉시컬 환경의 조합입니다. mdn의 정의에서는 자바스크립트의 모든 함수는 내부슬롯 [[Environment]]가 존재하기 때문에 모든 함수는 클로저입니다. </p>
<h2 id="브라우저-최적화">브라우저 최적화</h2>
<hr>
<p>위의 설명처럼 자바스크립트의 모든 함수는 클로저입니다. 하지만 사용하지 않는 식별자를 메모리에 저장하고 있다면 최적화 부분에서 매우 좋지 않는 포퍼먼스를 보여줍니다. 이런 부분을 해결하기 위해서는 모든 브라우저 자체에서 최적화를 진행합니다.</p>
<h3 id="브라우저-성능-관련-고려-사항">브라우저 성능 관련 고려 사항</h3>
<ul>
<li><strong>상위 스코프를의 식별자를 식별하지 않는 경우에는 브라우저가 최적화를 통해서 상위 스코프의 식별자를 메모리에서 제거합니다.</strong></li>
</ul>
<pre><code class="language-js">const x = 1;

function outer() {
    const y = 2;

    function inner() {
        const z = 3;    
        console.log(z); // 외부 스코프를 참조하지 않고 내부 함수에서만 변수를 식별합니다. 
    }
    return inner;
}

const result = outer();

result();</code></pre>
<ul>
<li><strong>상위 스코프의 식별자를 식별하지만 상위 스코프보다 생명 주기가 짧을 경우 브라우저는 내부 함수를 클로저라고 생각하지 않는다.</strong></li>
</ul>
<pre><code class="language-js">const x = 1;

function outer() {
    const y = 2;

    function inner() {
        const z = 3;    
        console.log(x, y, z); // 외부 식별자를 참조하지만 외부 함수보다 내부 함수의 생명주기가 짧습니다. 
    }
    inner();
}

outer();</code></pre>
<ul>
<li><p><strong>상위 스코프의 식별자를 식별하면서 외부 함수보다 생명 주기가 긴 내부 함수는 브라우저에서 클로저라고 생각합니다.</strong></p>
<p>(만약 상위 스코프에 여러개의 식별자가 있지만 내부 함수에서 하나의 식별자만을 참조한다면 내부 함수에서 식별하는 식별자를 제외하고 다른 식별자는 브라우저 최저고하를 통해서 메모리에 저장하지 않습니다.)</p>
</li>
</ul>
<pre><code class="language-js">const x = 1;

function outer() {
    const y = 2;

    function inner() {
        const z = 3;    
        console.log(x, y, z); // 외부 식별자를 참조하며 내부 함수가 외부 함수보다 생명주기가 더 깁니다. 
    }
    return inner;
}

const result = outer();

result();</code></pre>
<ul>
<li><p><strong>클로저는 상위 스코프의 식별자를 참조하면서 외부 함수보다 내부 함수의 생명 주기가 더 오래 유지되는 경우를 클로저라고 지칭합니다.</strong> </p>
</li>
<li><p><strong>클로저를 통해서 은닉화 한 변수를 자유 변수라고 지칭합니다.</strong></p>
</li>
</ul>
<h2 id="캡슐화">캡슐화</h2>
<hr>
<h3 id="클로저의-활용">클로저의 활용</h3>
<p>자바스크립트는 객체 지향 언어이지만 다른 객체 지향 언어와 다르게 public, private, protected를 지원하지 않고 public만을 지원한다. 그말은 자바스크립트의 모든 객체는 어디서나 접근하여 객체의 프로퍼티에 접근할 수 있다. </p>
<p><strong>pubilc만을 지원하는 자바스크립트는 어디서나 객체의 프로퍼티에 접근할 수 있어서 동적으로 프로퍼티를 변경할 수 있다</strong></p>
<ul>
<li>객체에 접근하여 동적으로 프로퍼티 변경을 할 수 있고 어디서나 접근이 가능해서 보완성 및 신뢰성이 떨어진다.</li>
<li>문제점을 해결하기 위해 캡슐화를 통한 객체의 특정 프로퍼티나 메서드를 감출 목적으로 정보 은닉을 할 수 있다.</li>
</ul>
<p>정보 은닉을 위해서는 클로저를 사용할 수 있습니다.</p>
<pre><code class="language-js">function Company(name, personnel) {
  this.name = name;
  let _personnel = personnel;

    this.getPersonnel = function () {
        return _personnel
    };

  this.getInfo = function () {
    return `회사명 : ${this.name}, 인원 수 : ${_personnel} 입니다.`;
  };
}

const company = new Company(&#39;코카콜라&#39;, 300);

console.log(company.name); // 코카콜라
console.log(company._personnel); // undefined
console.log(company.getPersonnel()); // 300
console.log(company.getInfo()); // 회사명 : 코카콜라, 인원 수 : 300 입니다.</code></pre>
<p>생성자 함수도 함수 평가 후 함수 객체를 반환하기 때문에 자신의 프로퍼티를 가질 수 있습니다. 이러한 성질을 이용하여 private 변수를 만들 수 있으며 인스턴스에서는 _personnel 변수에 접근 할 수 없으며 getPersonnel 인스턴스 메서드를 통해서만 접근 할 수 있는 private 변수입니다. 그렇지만 메서드들은 인스턴스의 메서드이기 때문에 new 연산자와 함께 인스턴스를 생성하는 경우에는 중복된 메서드를 가지고 있는 문제점이 있습니다.</p>
<pre><code class="language-js">function Company(name, personnel) {
  this.name = name;
  let _personnel = personnel;
}

Company.prototype.getPersonnel = function () {
  return _personnel;
};

Company.prototype.getInfo = function () {
  return `회사명 : ${this.name}, 인원 수 : ${_personnel} 입니다.`;
  // _personnel을 참조할 수 없습니다.
};

const company = new Company(&#39;코카콜라&#39;, 300);

console.log(company.getPersonnel()); // ReferenceError: _personnel is not defined 
console.log(company.getInfo()); // ReferenceError: _personnel is not defined</code></pre>
<p>위의 예시에서 Company.prototype가 정의되는 스코프인 전역 스코프가 상위 스코프로 내부 슬롯[[ Environment ]]에는 전역 스코프가 저장 되기 때문에 _personnel를 참조 할 수 없어서 ReferenceError가 발생합니다.</p>
<pre><code class="language-js">const Company = (function Company() {
  let _personnel = 0;

  function Company(name, personnel) {
    this.name = name;
    _personnel = personnel;
  }

  Company.prototype.getPersonnel = function () {
    return _personnel;
  };

  Company.prototype.getInfo = function () {
    return `회사명 : ${this.name}, 인원 수 : ${_personnel} 입니다.`;
  };

  return Company;
})();

const company1 = new Company(&#39;코카콜라&#39;, 300);

console.log(company1); // Company { name: &#39;코카콜라&#39; }
console.log(company1.getInfo()); // 회사명 : 코카콜라, 인원 수 : 300 입니다.

const company2 = new Company(&#39;팹시&#39;, 250);

console.log(company2); // Company { name: &#39;팹시&#39; }
console.log(company2.getInfo()); // 회사명 : 팹시, 인원 수 : 250 입니다.

console.log(company1.getInfo()); // 회사명 : 코카콜라, 인원 수 : 250 입니다.    </code></pre>
<p>즉시 실행 함수를 통해서 변수 _personnel를 private 변수로 만들고 Company.prototype의 메서드로 만들어서 동일한 메서드는 중복 생성 되지 않게 만들었습니다. 하지만 예시의 마지막 줄에서 company1.getInfo( )의 반환값이 재대로 적용되지 않은 모습입니다.</p>
<p><strong>Company.prototype.getInfo 메서드는 단 한번만 실행되는 클로저이기 때문에 발생하는 현상입니다.</strong> 이 처럼 자바스크립트는 캡슐화를 통한 정보 은닉이 완전하게 지원하지 않는 상태입니다. </p>
<h2 id="참고-문서">참고 문서</h2>
<hr>
<p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures">클로저 - MDN</a></p>
<p><a href="https://poiemaweb.com/js-closure">클로저 - poiemaweb</a></p>
<p><a href="https://ko.javascript.info/closure">변수의 유효범위와 클로저 - 모던 JavaScript 튜토리얼</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[브라우저 렌더링 과정, Reflow, Repaint]]></title>
            <link>https://velog.io/@park-moen/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95-Reflow-Repaint-s895tt2x</link>
            <guid>https://velog.io/@park-moen/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95-Reflow-Repaint-s895tt2x</guid>
            <pubDate>Fri, 30 Sep 2022 06:23:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>2022년 05월 01일 ~ 2022년 11월 20일까지 진행한 스터디를 바탕으로 로컬에 작성한 글을 Velog에 옮긴 내용입니다.</p>
</blockquote>
<h2 id="브라우저-렌더링">브라우저 렌더링</h2>
<hr>
<h3 id="요청과-응답">요청과 응답</h3>
<ul>
<li>브라우저 주소창에 Protocol(scheme), Domain(Host)를 통해서 서버로 요청을 보낼 수 있습니다. <ul>
<li>https: //naver.com이라고 작성하면 암묵적으로 https: //naver.com/index.html으로 서버에 전송합니다.</li>
</ul>
</li>
<li>자신이 원하는 브라우저에 접근하기 위해서는 리소스(HTML, CSS, JavaScript, Font, Image 등)을 서버로 요청하여 응답을 받습니다.</li>
<li>응답 받은 리소스는 문자열로 전송 받습니다. <ul>
<li>정확히는 지정된 바이트(2진수)로 전송 받아서 HTML의 meta tag를 파싱하면서 문자열로 변환합니다. </li>
</ul>
</li>
<li>전달받은 문자열은 브라우저가 빠르게 해석할 수 있는 객체로 변환됩니다. ([정확한 동작은 DOM Tree 생성 과정에서 설명 예정](#DOM Tree 생성 과정))</li>
<li>위의 설명들은 정적으로 서버와 통신을 하는 내용으로 Ajax, REST API를 통해서 JavaScript로 동적으로 통신하여 원하는 데이터를 JSON 형식으로 받을 수 있습니다.</li>
</ul>
<p>![image-20210318164554752](/Users/parkmoen/Library/Application Support/typora-user-images/image-20210318164554752.png)</p>
<h3 id="dom-tree-생성-과정">DOM Tree 생성 과정</h3>
<ol>
<li>HTML 파일을 서버로부터 응답 받습니다. (지정된 바이트로 전송받습니다.)</li>
<li>바이트 형태로 전송 받은 HTML 파일은 랜더린 엔진을 통해 meta 태그의 charset 어트리뷰에 지정된 인코딩 방식(UTF-8 등)으로 문자열로 변환됩니다.</li>
<li>문자열로 변환된 코드는 문법적으로 가장 작은 단위인 토큰으로 분해합니다.</li>
<li>분해한 토큰은 객체의 형태인 노드로 변환됩니다. 변환된 노드들은 DOM을 구성하는 기본 단위가 됩니다.</li>
<li>HTML 요소들은 중첩관계를 유지하고 있으며(부모와 자식의 관계), 중첩관계를 노드로 표현한 자료구조가 DOM Tree입니다. </li>
</ol>
<p><strong>DOM Tree는 HTML 파일을 랜더링 엔진을 통해 해석한 객체들의 집합입니다.</strong></p>
<p><strong>렌더링 엔진: 서버로부터 요청받은 파일을 시각적으로 보여지는 픽셀단위로 해석하여 사용자에게 보여주는 엔진입니다.</strong></p>
<p><img src="https://user-images.githubusercontent.com/57402711/111768553-6b128500-88eb-11eb-8c6c-656790620d2b.png" alt="image"></p>
<h3 id="cssom-tree-생성-과정">CSSOM Tree 생성 과정</h3>
<ul>
<li>랜더링 엔진을 통해서 순서대로 위에서 밑으로 문자열을 해석하여 노드들을 생성합니다.<ul>
<li>바이트 → 문자 → 토큰 → 노드 →  CSSOM 순서로 생성</li>
</ul>
</li>
<li>생성한 노드들의 집합인 CSSOM Tree를 생성합니다.</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/57402711/111771519-2983d900-88ef-11eb-923f-ac7271bc8fd1.png" alt="image"></p>
<p><strong>렌더링 엔진을 통해 HTML을 파싱하는 도중에 link 태그를 만나면 HTML 파싱을 중단하고 CSS 파일을 서버로부터 요청하고 응답 받은 CSS 파일을 렌더링 엔진이 파싱합니다. CSS 파일의 해석이 완료되어 CSSOM 생성이 완료되면 다시 HTML 파일을 해석하여 DOM을 생성합니다.</strong></p>
<p>즉, <em>렌더링 엔진은 동기적으로 파일을 해석하기때문에 blocking현상이 발생합니다.</em></p>
<p><img src="https://user-images.githubusercontent.com/57402711/111777488-b41c0680-88f6-11eb-823f-a41c8f3e6dc2.png" alt="image"></p>
<h3 id="render-tree-생성-및-layout-계산">Render Tree 생성 및 Layout 계산</h3>
<ul>
<li>DOM, CSSOM이 모두 만들어지면 화면에 렌더링 되는 부분으로만 이루어진 Render Tree가 만들어집니다.</li>
<li>화면에 보여지는 부분만을 모와서 자료구조로 만들어서 meta, script 등의 html 태그는 포함되지 않습니다.<ul>
<li>display: none 속성은 화면의 어떠한 공간도 차지하지 않기때문에 Render Tree에 포함되지 않습니다.</li>
<li>visibility: invisible 속성은 화면에 보이지는 않지만 공간을 차지하기때문에 Render Tree에 포함됩니다.</li>
</ul>
</li>
<li>완성된 Render Tree를 기반으로 HTML 요소의 레이아웃을 계산하여 화면을 렌더링하는 페이팅(Painting)처리를 진행합니다.</li>
</ul>
<p>![image-20210319202301477](/Users/parkmoen/Library/Application Support/typora-user-images/image-20210319202301477.png)</p>
<p>지금까지 살펴본 브라우저의 렌더링 과정은 반복해서 실행될 수 있다. 예를 들어, 다음과 같은 경우 반복해서 레이아웃 계산과 페인팅이 재차 실행된다.</p>
<ul>
<li>자바스크립트에 의한 노드 추가 또는 삭제</li>
<li>브라우저 창의 리사이징에 의한 뷰포트(viewport) 크기 변경</li>
<li>HTML 요소의 레이아웃(위치, 크기)에 변경을 발생시키는 width/height, margin, padding, border, display, position, top/right/bottom/left 등의 스타일 변경</li>
</ul>
<h2 id="reflow-repaint">Reflow, Repaint</h2>
<hr>
<h3 id="reflow">Reflow</h3>
<p>Render Tree로 구성된 노드들을 HTML 요소의 위치 및 크기 등의 Layout을 계산합니다. DOM API로 위치, 크기를 수정하면 영향을 받은 DOM, CSSOM Tree는 다시 파싱을 통해 Render Tree를 만들어서 Layout 계산을 하는 행위입니다. </p>
<p><em>즉, Reflow는 Layout을 다시 계산하는 행동입니다.</em></p>
<p><strong>reflow 영향을 주는 속성</strong></p>
<table>
<thead>
<tr>
<th align="center">width</th>
<th align="center">height</th>
<th align="center">padding</th>
<th align="center">margin</th>
<th align="center">border</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>top</strong></td>
<td align="center"><strong>bottom</strong></td>
<td align="center"><strong>left</strong></td>
<td align="center"><strong>right</strong></td>
<td align="center"><strong>position</strong></td>
</tr>
<tr>
<td align="center"><strong>font-size</strong></td>
<td align="center"><strong>line-height</strong></td>
<td align="center"><strong>font-family</strong></td>
<td align="center"><strong>overflow</strong></td>
<td align="center"><strong>text-align</strong></td>
</tr>
<tr>
<td align="center"><strong>display</strong></td>
<td align="center"><strong>float</strong></td>
<td align="center"><strong>font-weight</strong></td>
<td align="center"><strong>vertical-align</strong></td>
<td align="center"><strong>white-space</strong></td>
</tr>
</tbody></table>
<h3 id="repaint">Repaint</h3>
<ul>
<li>RePaint는 Reflow가 발생하여 다시 Render Tree를 생성해서 Layout 작업 후 픽셀 단위로 화면에 랜더링하는 작업입니다. </li>
<li>위의 설명만 보면 Reflow가 발생해야지만 Repaint가 발생하는 것처럼 보이지만 Repaint만을 발생시키는 CSS 속성도 존재하기때문에 *<em>Reflow가 발생하지 않아도 Repint가 발생할 수 있습니다. *</em></li>
</ul>
<p><strong>Repaint에 영향을 주는 속성</strong></p>
<table>
<thead>
<tr>
<th align="center">background</th>
<th align="center">color</th>
<th align="center">visibility</th>
<th align="center">text-decoration</th>
<th align="center">line-style</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>background-image</strong></td>
<td align="center"><strong>background-position</strong></td>
<td align="center"><strong>background-repeat</strong></td>
<td align="center"><strong>background-size</strong></td>
<td align="center"><strong>border-radius</strong></td>
</tr>
<tr>
<td align="center"><strong>box-shadow</strong></td>
<td align="center"><strong>border-style</strong></td>
<td align="center"><strong>outline</strong></td>
<td align="center"><strong>outline-color</strong></td>
<td align="center"><strong>outline-style</strong></td>
</tr>
</tbody></table>
<img src="https://user-images.githubusercontent.com/57402711/111770105-5800b480-88ed-11eb-9743-6c704740e174.png" alt="image" style="zoom:60%;" />



<h2 id="브라우저-최적화">브라우저 최적화</h2>
<hr>
<p>위의 설명처럼 Reflow가 발생하지 않아도 Repint가 발생할 수 있습니다. 그 말은 Reflow가 발생하면 무조건적으로 Repaint가 발생한다는 의미입니다. 즉, 브라우저 최적화를 가장 간단하게 하는 방법은 Reflow가 최소한으로 발생하게 하는 방식을 채택할 수 있습니다.</p>
<h3 id="reflow-생성되는-과정"><em>Reflow 생성되는 과정</em></h3>
<ul>
<li>페이지 초기 랜더링 시.(최초 Layout 과정) </li>
<li>윈도우 리사이징 시</li>
<li>HTML 요소 크기 변경 시</li>
<li>HTML 요소 위치 변경 시</li>
<li>텍스트 변경(폰트) 시</li>
<li>원본 이미지 크기 변경 시</li>
<li>DOM API를 통해 노드 추가 및 삭제 시</li>
</ul>
<p><em><a href="#Reflow">reflowr가 발생하는 속성 보기</a></em></p>
<h3 id="reflow-최적화하기">Reflow 최적화하기</h3>
<p><strong>1. DOM API를 통해 Style. 변경 시 하나의 동작으로. 묶어서. 실행하기</strong></p>
<ul>
<li>Style  여러번 호출해서 변경하기</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/57402711/111857153-f420cf00-8972-11eb-92f3-3adbeef3b137.png" alt="image" style="zoom:50%;" />          <img src="https://user-images.githubusercontent.com/57402711/111855567-e8300f80-8968-11eb-8ccd-75861e50252d.png" alt="image" style="zoom:45%;" /></p>
<ul>
<li>CSSStyleDeclaration.cssText 메서드를 사용하여 여러가지 Style 한번에 묶어서 변경하기</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/57402711/111857229-6f828080-8973-11eb-8d3c-aa1b797cf977.png" alt="image" style="zoom:47%;" />         <img src="https://user-images.githubusercontent.com/57402711/111855761-bf5c4a00-8969-11eb-8050-c64ab5c42178.png" alt="image" style="zoom:50%;" /></p>
<ul>
<li>Element.classList를 사용하여 지정한 class에 Style 적용하여 스타일 변경</li>
</ul>
<p><img src="/Users/parkmoen/Library/Application Support/typora-user-images/image-20210320120035633.png" alt="image-20210320120035633" style="zoom:50%;" />         <img src="https://user-images.githubusercontent.com/57402711/111856014-39d99980-896b-11eb-9f19-a12364d63ef3.png" alt="image" style="zoom:55%;" /></p>
<p><em>간단한 작업에서도 0.10ms 정도의 차이가 나며, 프로젝트를 진행시 스타일 작업에 적절히 적용해서 Reflow를 줄이는 방식을 채택하면 좋습니다.</em></p>
<p><strong>2. animation을 사용하는 노드는 position: fixed, absolute를 사용하기</strong></p>
<img src="/Users/parkmoen/Library/Application Support/typora-user-images/image-20210320132019604.png" alt="image-20210320132019604" style="zoom:50%; " />

<ul>
<li>Javascript + CSS 조합으로 animation 적용 시 많은 프레임 계산으로 인한 브라우저 성능에 악영향을 줍니다.</li>
<li>position: fixed, absolute 속성을 사용하여 전체 노드에서 animation 적용을 원하는 부분만을 따로 분리 시킬 수 있습니다.<ul>
<li>전체 노드에서 animation 적용을 원하는 노드를 분리시키면 전체 노드는 Reflow가 발생하지 않고 animation을 적용한 노드에서만 Repaint를 진행합니다.</li>
<li>position: fixed, absolute + transform: translate3D(x, y, z)를 사용하면 더욱 효과적인 Performance를 기대할 수 있습니다.</li>
</ul>
</li>
</ul>
<p><strong>3. CSS 선택자 선택</strong></p>
<ul>
<li>CSS 선택자를 통해 Style을 적용할때는 하위 선택자부터 선택해야 합니다.</li>
<li>만약 상위 선택자를 필요로 하면 최소한의 선택자를 선택해야 합니다.</li>
<li>CSS 규칙은 오른쪽에서 왼쪽으로 이동합니다. 이 과정에서는 더 이상 일치하는 규칙이 없거나 잘못된 규칙이 나올 때 까지 계속됩니다. 그러므로 불필요한 선택자를 사용하는 것은 성능을 저하시킬 수 있습니다.</li>
</ul>
<img src="/Users/parkmoen/Library/Application Support/typora-user-images/image-20210320135120294.png" alt="image-20210320135120294" style="zoom:60%;" />



<p><strong>4. DOM API를 최소화 하여 Reflow 줄이기, 인라인 Style 방식 최소화 하기</strong></p>
<ul>
<li>위 부분은 타협점을 정하여 자신이 원하는 방식을 사용하면 됩니다.</li>
</ul>
<h2 id="참고-자료">참고 자료</h2>
<hr>
<p><a href="https://developers.google.com/web/fundamentals/performance/critical-rendering-path/constructing-the-object-model?hl=ko">Google 개발자 문서</a></p>
<p><a href="https://poiemaweb.com/js-dom">poiemaweb</a></p>
<p><a href="https://webclub.tistory.com/346">Reflow or Repaint(or ReDraw)과정 설명 및 최적화 방법</a></p>
<p><a href="https://wit.nts-corp.com/2017/06/05/4571">CSS 애니메이션 성능 개선 방법</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[검색엔진 최적화(SEO)]]></title>
            <link>https://velog.io/@park-moen/%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84-%EC%B5%9C%EC%A0%81%ED%99%94SEO-4xn4i9qx</link>
            <guid>https://velog.io/@park-moen/%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84-%EC%B5%9C%EC%A0%81%ED%99%94SEO-4xn4i9qx</guid>
            <pubDate>Fri, 30 Sep 2022 06:21:13 GMT</pubDate>
            <description><![CDATA[<h2 id="검색-엔진-최적화seo에-필요한-용어-정리">검색 엔진 최적화(SEO)에 필요한 용어 정리</h2>
<p><code>색인(indexing)</code> : <strong>검색을 빠르게 하기 위해서 항목별로 데이터를 저장하는 장소</strong> 를 지칭하며 색인 과정이 존재하지 않게 되면 필요한 데이터를 찾기 위해 검색 엔진이 모든 데이터를 찾아야하는 문제가 발생합니다. </p>
<p>예를 들어 도서관에서 소설, 수필 등으로 나누는 과정을 통해서 원하는 서적을 찾는 방식처럼 <strong>특정 항목별로 데이털르 저장하는 과정을 색인(indexing)</strong>이라고 합니다.</p>
<p><code>크롤링</code> : 웹 페이지를 가져와서 데이터를 추출하는 행위로 구글에서는 링크를 따라가거나, 사이드맵을 읽거나, 다른 여러 방법으로 URL을 찾아냅니다. 즉, 필요한 정보를 찾는 작업으로 인덱싱하기 위해 사전에 필요한 작업입니다. </p>
<p>예: Google은 신규 또는 업데이트된 웹을 크롤링하여 페이지를 찾은 다음 필요에 따라 색인(indexing)을 생성합니다.</p>
<p><code>크롤러</code> : 웹 페이지를 크롤링한 다음 색인을 생성하는 자동 소프트웨어로 구글에서 사용하는  Gogglebot이 대표적인 크롤러입니다.</p>
<p><code>랭킹(순위 지정)</code> : 사용자가 검색어를 입력하면 여러 요소를 바탕으로 색인에서 관련성 있는 답변을 찾기 위해 품질 높은 콘텐츠를 결정하고 최상의 사용자 환경과 적절한 답변을 제공할 수 있는 여러 요소를 고려해서 순위를 결정합니다. </p>
<p><strong>게재 및 순위 개선하는 방법</strong></p>
<ul>
<li>모바일 친화적인 페이지를 만들어 빠르게 로드할 수 있는 환경 만들기</li>
<li>사용자가 윈하는 품질 높은 콘텐츠를 포함하고 최신 상태로 유지</li>
<li>사용하는 브라우저 환경에 알맞은 가이드 라인 지키기</li>
</ul>
<h2 id="검색-엔진-최적화seo란">검색 엔진 최적화(SEO)란?</h2>
<p>검색 엔진 최적화(search engine optimization)은 웹 페이지 검색엔진이 자료를 수집하고 순위를 매기는 방식에 맞게 웹 페이즈를 구성해서 검색 결과의 상위에 나올 수 있도록 하는 작업입니다.</p>
<p>2000년대 초반의 SEO의 시책에서는 HTML 태깅 개선을 통한 랭크 상승 방식을 지향했지만 최근의 SEO는 검색자의 니즈에 맞추는 것을 중시하고 있습니다. 이런 현상은 다른 웹 검색엔진들 보다 압도적인 시장 점유율을 차지하는 구글의 영향을 통해 검색자의 의도를 이해하고 적절한 콘텐츠를 제작하고, 페이지가 검색 결과 페이지에 잘 노출 되도록 태그와 링크 주소와 같은 방식을 사용하여 개선을 통해 자연 유입 트래픽을 늘리는 방향으로 변화하고 있습니다. </p>
<p>즉, 검색 결과 상위에 페이즈를 노출하기 위해서는 다양한 기법(HTML 태깅 등)을 사용하더라도 콘텐츠 질이 좋지 않다면 사용자의 선택을 받을 수 없으며 콘텐츠의 품질이 높더라도 사용자가 찾지 못하면 사용자 유입이 증가하지 않는 문제가 발생한다. 이런 문제를 해결하기 위해서는 적절한 기법과 품질 높은 콘텐츠의 궁합이 매우 중요해진 시기입니다.</p>
<h2 id="검색엔진-최적화를-위한-제안-사항">검색엔진 최적화를 위한 제안 사항</h2>
<h3 id="robotstxt을-사용하여-크롤링-안되는-페이지-알리기">robots.txt을 사용하여 크롤링 안되는 페이지 알리기</h3>
<p><code>robots.txt파일</code>은 검색엔진이 사이트의 일부 URL에 엑세스하여 크롤링할 수 있는지 알려주는 역할입니다. robots.txt 파일이 없으면 검색엔진이 웹 페이지에서 찾을 수 있는 모든 route를 크롤링하며, 불필요한(민감한 정보) route 접근을 막을때 유용합니다.</p>
<pre><code># googlebot management
User-agent: Googlebot
Disallow: /book
Allow: /catagory/</code></pre><p>robots.txt 파일을 위와 같이 설정하면, 규칙에 따라 googlebot은 /book 페이지를 크롤링하지 않으며, /catagory/로 지각하는 모든 페이즈를 크롤링합니다.  <a href="https://developers.google.com/search/reference/robots_txt?hl=ko">robots.txt 사용법을 참고하세요.</a></p>
<h3 id="사이트맵sitemapxml을-사용하여-내-콘텐츠를-찾을-수-있게-돕기">사이트맵(sitemap.xml)을 사용하여 내 콘텐츠를 찾을 수 있게 돕기</h3>
<p><code>sitemap.xml</code>은 사이트에 있는 페이지, 동영상 및 기타 파일에 관련된 정보를 제공하는 파일입니다. 사이트에서 중요하다고 생각하는 페이지와 파일을 sitemap.xml 파일에 알리면 중요한 관련 정보를 제공하며 누락될 수 있는 페이지를 사전에 방지할 수 있습니다. </p>
<p>사이트맵은 웹사이트 검색엔진최적화 점수를 높이는데 직접적인 영향을 주지 않지만 검색엔진이 크롤리 과정에서 누락될 수 있는 웹페이지에 대한 정보를 제공하여 검색엔진최적화에 긍정적인 영향을 줍니다.</p>
<p><strong>사이트맵 만드는 과정</strong></p>
<ol>
<li>사이트맵 제작 웹사이트, 크롤링 프로그램, Yoast SEO 플러그인을 사용하여 sitemap.xml 파일을 생성합니다.</li>
<li>사이트맵을 구글 서치콘솔 또는 네이버 서치 어드바이저에 제출합니다.</li>
</ol>
<h3 id="고유하고-정확한-페이지-제목을-위해-title-태그-작성하기">고유하고 정확한 페이지 제목을 위해 title 태그 작성하기</h3>
<p><code>&lt;title&gt;</code>태그는 사용자와 검색 엔진에게 페이지의 주제를 알려주는 중요한 역할을 합니다. <code>&lt;title&gt;</code>태그는 HTML 문서의 <code>&lt;head&gt;</code> 태그 안에 위치해야 하며 페이지별로 고유한 제목을 가지고 있어야 합니다.</p>
<p><code>&lt;title&gt;</code> 태그 작성시 유의 사항</p>
<ul>
<li>페이지 내용과 관련 없는 제목을 사용하지 않고 정확한 설명을 작성해야 합니다. 예: 제목없음, 새 페이지1 과 같이 불분명한 내용을 사용하면 안됩니다.</li>
<li>사이트의 모든 페이지 또는 여러 페이지에 단일 제목을 사용하지 않으며, 각 페이지마다 고유한 제목을 작성해야 합니다.</li>
<li>사용자에게 도움이 되지 않는 매우 긴 제목을 사용하지 않고 간단하지만 설명이 담긴 제목을 사용해야 합니다.</li>
</ul>
<pre><code class="language-html">{/* koreanFood.html */}

&lt;html&gt;
  &lt;head&gt;
      &lt;title&gt;2021년 한식 랭킹&lt;/title&gt; // 페이지별 고유한 제목을 지정합니다.
    &lt;meta name=&quot;description&quot; content=&quot;올해 가장 맛있는 한식을 뽑는 자리입니다.&quot;&gt;
  &lt;/head&gt;
&lt;/html&gt;
&lt;body&gt;
  ...
&lt;/body&gt;</code></pre>
<pre><code class="language-html">{/* japaneseFood.html */}

&lt;html&gt;
  &lt;head&gt;
      &lt;title&gt;2021년 일식 랭킹&lt;/title&gt; // 페이지별 고유한 제목을 지정합니다.
    &lt;meta name=&quot;description&quot; content=&quot;올해 가장 맛있는 일식을 뽑는 자리입니다.&quot;&gt;
  &lt;/head&gt;
&lt;/html&gt;
&lt;body&gt;
  ...
&lt;/body&gt;</code></pre>
<h3 id="description-메타-태그-사용하기">description 메타 태그 사용하기</h3>
<p><code>description</code> 메타 태그는 <code>title</code> 태그의 제목에 대한 정보를 간략하게 설명할 수 있으며 <code>&lt;head&gt;</code> 요소에 작성합니다.</p>
<pre><code class="language-html">{/* index.html */}

&lt;html&gt;
  &lt;head&gt;
      &lt;title&gt;2021년 양식 랭킹&lt;/title&gt; 
    &lt;meta name=&quot;description&quot; content=&quot;올해 가장 맛있는 일식을 뽑는 자리입니다. 필요한 재료에 대한 정보를 유익하게 알려주는 레시피를 알아가세요!&quot;&gt;
  &lt;/head&gt;
&lt;/html&gt;
&lt;body&gt;
  ...
&lt;/body&gt;</code></pre>
<p><code>description</code> 메타 태그는 페이지의 스니펫으로 사용할 수 있으며 Google에서 페이지에 표시되는 텍스트 중에 사용자의 검색어와 잘 어울리는 텍스트가 있는 경우 이를 선택할 수도 있습니다. </p>
<p>적절한 description 작성하는 방법</p>
<ul>
<li>페이지 콘텐츠를 정확하게 요약해야합니다.</li>
<li>각 페이지마다 고유한 설명을 사용해야 합니다. (모든 페이지가 동일한 설명을 사용하지 않아야 합니다.)</li>
<li>설명을 키워드로만 채우는 경우 및 <code>description</code> 메타 태그에 문서 전체 내용을 복사하여 붙여넣지 말아야 합니다.</li>
</ul>
<h3 id="제목-태그h1-h2-를-사용하여-중요한-텍스트-강조하기">제목 태그(h1, h2 ...)를 사용하여 중요한 텍스트 강조하기</h3>
<p>의미 있는 제목을 사용하여 중요한 주제를 표시하고 콘텐츠의 계층 구조를 만들어 사용자가 쉽게 문서를 탐색할 수 있도록 해야합니다. </p>
<ul>
<li>페이지에서 꼭 필요한 부분만 제목을 사용해야합니다. (제목을 너무 남용하면 주제의 파악이 힘들어집니다.)</li>
<li>제목을 너무 길게 작성하지 말고 스타일을 위해서 제목 구조를 나타내면 안됩니다.</li>
</ul>
<pre><code class="language-html">{/* index.html */}

&lt;html&gt;
  &lt;head&gt;
      &lt;title&gt;2021년 양식 랭킹&lt;/title&gt; 
    &lt;meta name=&quot;description&quot; content=&quot;올해 가장 맛있는 일식을 뽑는 자리입니다. 필요한 재료에 대한 정보를 유익하게 알려주는 레시피를 알아가세요!&quot;&gt;
  &lt;/head&gt;
&lt;/html&gt;
&lt;body&gt;
  &lt;h1&gt;양식 레시피&lt;/h1&gt;
  ...

  &lt;h2&gt;토마토 파스타 레시피&lt;/h2&gt;
  ...
  ...
  &lt;h2&gt;알리오 올리오파스타 레시피&lt;/h2&gt;
&lt;/body&gt;</code></pre>
<blockquote>
<p>위에서 설명한 &#39;고유하고 정확한 페이지 제목을 위해 title 태그 작성하기&#39;, &#39;description 메타 태그 사용하기&#39;, &#39;제목 태그(h1, h2 ...)를 사용하여 중요한 텍스트 강조하기&#39; 와 같은 SEO를 통틀어서 시멘틱 요소(Semantic Elements)를 적절하게 사용하는 방식을 세분한 내용입니다.</p>
<p>시멘틱 요소를 적절하게 사용하기 위해서는 위의 3가지 방법 외에도 의미를 가지지 않는 <div>, <span>으로만 HTML을 구성하지 않고 의미 있는 태그를 적절하게 사용하는 방식을 고수해야 합니다.</p>
</blockquote>
<pre><code class="language-html">{/* 시멘틱 요소를 적절하게 사용하지 않은 예시 */}

&lt;div&gt;
  &lt;div&gt;
    &lt;div&gt;포스팅 제목&lt;/div&gt;
    &lt;div&gt;포스팅 관련 내용&lt;/div&gt;
  &lt;/div&gt;
  &lt;div&gt;First Group&lt;/div&gt;
  &lt;span&gt;First Group에 관련된 내용이 들어갑니다.&lt;/span&gt;
&lt;/div&gt;</code></pre>
<pre><code class="language-html">{/* 시멘틱 요소를 적절하게 사용한 예시 */}

&lt;body&gt;
  &lt;header&gt;
    &lt;h1&gt;포스팅 제목&lt;/h1&gt;
    &lt;p&gt;포스팅 관련 내용&lt;/p&gt;
  &lt;/header&gt;
  &lt;main&gt;
      &lt;h2&gt;First Group&lt;/h2&gt;
      &lt;p&gt;First Group에 관련된 내용이 들어갑니다.&lt;/p&gt;
  &lt;main&gt;
&lt;/body&gt;</code></pre>
<h3 id="구조화된-데이터-마크업-추가하기">구조화된 데이터 마크업 추가하기</h3>
<p>구조화된 데이터란 사이트 페이지에 추가할 수 있는 코드로, 검색엔진에 콘텐츠를 설명해주기 때문에 검색엔진이 페이지에 어떤 내용이 있는지 더 잘 이해할 수 있습니다.</p>
<p>구조화된 데이터 마크업을 사용하면 결과를 표시하는 것 외에도, 관련성 있는 검색 결과를 추가 설명하는 방법을 제공합니다. 예를 들어 식당을 검색하게 되면 가격, 평점, 카테고리 정보 등이 노출된 부분을 볼 수 있는 페이즈를 볼 수 있습니다. </p>
<img src="https://user-images.githubusercontent.com/57402711/126251489-8b354512-ebc2-47f2-bdd5-240a8e8963e7.png" alt="image" style="zoom:40%;" />

<p><strong>구조회된 데이터를 작성하는 방법</strong></p>
<p>구조회된 데이터 작업을 위해서는 schema.org에서 제공하는 타입(type)과 속성(property)값을 이용하여 제작할 수 있습니다. 구조화된 데이터 작업 시 Microdata와 RDFa, JSON-LD의 세 가지 언어 형식을 지원합니다</p>
<pre><code>schema.org는 2011년 6월 2일 google, bing, yahoo 등 당시 검색 엔진 시장에서 점유율이 높은 운영자들이 모여 웹 페이지에서 구조화된 데이터 마크업을 웨한 공통 스키마 작성을 위한 schema.org를 발표했습니다.</code></pre><ul>
<li>JSON_LD(권장): 페이지 헤드 또는 분문의 <code>&lt;script&gt;</code> 태그 내에 삽입되는 자바스크립트 표기입니다. </li>
</ul>
<pre><code class="language-html">// JSON-LD 형식
&lt;script type=&quot;application/ld+json&quot;&gt;
{
 &quot;@context&quot;: &quot;http://schema.org&quot;,
 &quot;@type&quot;: &quot;Person&quot;,
 &quot;name&quot;: &quot;My Site Name&quot;,
 &quot;url&quot;: &quot;http://www.mysite.com&quot;,
 &quot;sameAs&quot;: [
   &quot;https://www.facebook.com/myfacebook&quot;,
   &quot;http://blog.naver.com/myblog&quot;,
   &quot;http://storefarm.naver.com/mystore&quot;
 ]
}
&lt;/script&gt;</code></pre>
<ul>
<li>마이크로데이터: HTML 콘텐츠 내에 구조화된 데이터를 사용되는 개방형 커뮤니티 HTML 사양입니다. RDFa와 같이 HTML 태그 속성을 사용해 구조화된 데이터를 표시하려는 속성의 이름을 지정합니다. 대개 페이지 본문에 사용되지만 헤드에 사용될 수 있습니다.</li>
</ul>
<pre><code class="language-html">&lt;span itemscope=&quot;&quot; itemtype=&quot;http://schema.org/Organization&quot;&gt;
 &lt;link itemprop=&quot;url&quot; href=&quot;http://www.mysite.com&quot;&gt;
 &lt;a itemprop=&quot;sameAs&quot; href=&quot;https://www.facebook.com/myfacebook&quot;&gt;&lt;/a&gt;
 &lt;a itemprop=&quot;sameAs&quot; href=&quot;http://blog.naver.com/myblog&quot;&gt;&lt;/a&gt;
 &lt;a itemprop=&quot;sameAs&quot; href=&quot;http://storefarm.naver.com/mystore&quot;&gt;&lt;/a&gt;
&lt;/span&gt;</code></pre>
<ul>
<li>RDFa: 사용자에게 표시되며 검색엔진에 제시하려는 콘텐츠에 해당하는 HTML 태그 속성을 도입하여 연결된 데이터를 지원하는 HTML 확장입니다.</li>
</ul>
<pre><code class="language-html">&lt;p vocab=&quot;http://schema.org/&quot;&gt;
   My name is Manu Sporny and you can give me a ring via 1-800-555-0199.
&lt;/p&gt;</code></pre>
<h3 id="단순한-url은-콘텐츠-정보를-전달하기">단순한 URL은 콘텐츠 정보를 전달하기</h3>
<p>웹사이트 문서와 관련된 설명을 제공하는 카테고리 및 파일 이름을 만들면 사이트를 더 잘 구성하는 데 도움이 될 뿐만 아니라 콘텐츠에 관심을 두고 있는 사용자가 좀 더 쉽게 사용할 수 있으며 이들에게 더욱 친숙한 URL을 만들 수 있습니다. 인식할 수 있는 단어가 거의 없는 긴 암호문과도 같은 URL에 겁을 먹는 사용자도 있을 수 있습니다.</p>
<ul>
<li><p><strong>적절한 URL을 사용하기</strong></p>
<pre><code>// 사용자에게 혼란을 줄 수 있는 URL
https://github.com/park-moen/folder1/22447478/x2/14032015.html

// URL을 보고 바로 문맥을 이해할 수 있는 URL
https://github.com/park-moen/VanillaJS_fn/issues/new</code></pre></li>
</ul>
<ul>
<li><strong>canonical URL을 사용하여 대표 URL 지정하기</strong></li>
</ul>
<blockquote>
<ul>
<li><a href="http://github.com/park-moen/aaa">http://github.com/park-moen/aaa</a></li>
<li><a href="https://github.com/park-moen/aaa">https://github.com/park-moen/aaa</a></li>
<li><a href="https://github.com/park-moen/b.html">https://github.com/park-moen/b.html</a></li>
<li><a href="https://github.com/park-moen/b/word">https://github.com/park-moen/b/word</a></li>
</ul>
</blockquote>
<p>같은 페이지가 위처럼 여러 가지 URL을 가질 때, 검색엔진은 그중 하나를 표준으로 지정한다. 그리고 나머지는 표준 URL의 복사본으로 간주합니다. 하지만 내가 생각하는 표준 URL이 지정될지 안될지는 알 수 없기 때문에 canonical URL을 사용하여 표준 URL을 직접 지정해주는 것이 바람직합니다.</p>
<p>canonical URL을 지정하기 위해서는 HTML Header 영역에 canonical URL을 넣으면 표준 URL을 지정할 수 있습니다.</p>
<pre><code class="language-html">&lt;link rel=&quot;canonical&quot; href=&quot;https://example.com/&quot;/&gt;</code></pre>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://support.google.com/webmasters/answer/7451184?hl=ko">구글 SEO 초보자 가이드</a></li>
<li><a href="https://blueshw.github.io/2020/06/14/seo/">검색엔진 최적화를 위한 9가지 방법</a></li>
<li><a href="https://www.ascentkorea.com/what-is-seo/">검색엔진최적화는 무엇인가?</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[인증 방식: Cookie & Session vs JWT]]></title>
            <link>https://velog.io/@park-moen/%EC%9D%B8%EC%A6%9D-%EB%B0%A9%EC%8B%9D-Cookie-vs-Cookie-Session-vs-JWT</link>
            <guid>https://velog.io/@park-moen/%EC%9D%B8%EC%A6%9D-%EB%B0%A9%EC%8B%9D-Cookie-vs-Cookie-Session-vs-JWT</guid>
            <pubDate>Fri, 30 Sep 2022 06:19:22 GMT</pubDate>
            <description><![CDATA[<pre><code>인증(Authentication) vs 인가/권한부여(Authorization)

인증과 인가/권한부여 인증 방식에 대해서 알기 위해서 필요한 사전 지식으로 두 단어의 정확한 의미를 알고 서로의 의미를 혼돈해서는 안됩니다.

인증과 인가를 출입증에 비교해서 설명하는 예를 들어보겠습니다.

인증(Authentication)이란, 방문자가 회사 건물에 출입하려고 할때 확인 받는 과정입니다. 이 과정을 통해서 자신의 신상을 확인할 수 있으며 출입 여부를 확인 받을 수 있습니다.

인가/권한부여(Authorization)이란, 방문자가 회사 건물에 방문했을 때, 허가된 공간에만 접근 가능하며, 개인마다 회사 건물에서 출입할 수 있는 공간의 차이가 존재할 수 있습니다.(vip, 일반 사원, 외부 방문자)</code></pre><br />

<h2 id="http-요청과-인증의-필요성">HTTP 요청과 인증의 필요성</h2>
<p>HTTP 통신은 현재 WEB 통신 수단으로 가장 많이 사용되는 방식입니다. 그런 HTTP는 <strong>비연결성</strong> 및 <strong>무상태성</strong>이라는 특징을 가지고 있습니다. 이런 특징으로 인해서 HTTP 통신은 응답 후 연결을 끊고 과거에 요청한 정보를 기억하지 않습니다. HTTP의 특징으로 인해서 앱을 이용하는 사용자가 여러명이 있을 경우 A 사용자와 B 사용자의 정보 및 콘텐츠가 달라야 하지만 HTTP는 자신의 특징으로 인해서 사용자를 구분할 수 없는 현상이 발생하였습니다. 이러한 문제로 인증을 사용하게 되었습니다.  </p>
<p><em>즉, HTTP는 비연결성 및 무상태의 특징을 가지고 있어서 요청을 보낸 직후 연결을 끊고 정보를 저장하지 않아서 인증이라는 절차를 통해서 확인할 수 있는 수단이 필요합니다.</em></p>
<p><img src="https://user-images.githubusercontent.com/57402711/127996947-f4b3392b-22b4-41c0-bd68-175490885d21.png" alt="image"></p>
<pre><code>▪︎ 비연결성(Connectionless): 클라이언트와 서버가 한 번 연결을 맺을 후, 클라이언트 요청에 대해 서버가 응답을 마치면 맺었던 열결을 끊어버리는 성질을 말합니다.

▪︎ 무상태(Stateless): 이전 요청과 무관한 각각의 요청을 독립적으로 취급하며 상태를 서버에 저장하지 않는 성질입니다.</code></pre><br />

<h2 id="cookie를-이용한-인증">Cookie를 이용한 인증</h2>
<p>HTTP 쿠키(Cookie)는 서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각으로, 서버를 통해 웹 브라우저에 설치되는 작은 기록 정보입니다. Cookie를 이용한 인증 방식은 가장 쉽게 구현을 할 수 있지만 모든 정보가 public해서 쉽게 정보가 유출 될 수 있는 점을 유의해야 합니다. </p>
<h3 id="인증-방법">인증 방법</h3>
<ol>
<li>서버는 클라이언트의 로그인 요청에 대한 응답을 작성할 떄, 클라이언트 측에 저장하고 싶은 정보를 응답 해더의 `Set-Cookie에 담습니다. (쿠키는 Key-Value 형식의 문자열입니다.)</li>
<li>클라이언트에서 인증이 필요한 요청을 보낼 때는 정보를 담은 쿠키를 요청 헤더의 &#39;Cookie&#39;에 담아 보냅니다. (재응답에는 따로 Cookie를 담지 않아도 자동으로 요청시에 Cookie를 담아서 보내는 특징이 있습니다.)</li>
<li>서버는 클라이언트에서 받은 응답에 포함된 쿠키정보를 바탕으로 요청한 클라이언트를 식별할 수 있습니다.</li>
</ol>
<h3 id="장점">장점</h3>
<ul>
<li>인증 테스트를 할 경우 및 간단한 앱을 만들때 빠르게 설계할 수 있습니다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>보안에 매우 취약해서 해킹을 당하는 일이 빈번하게 생깁니다.</li>
<li>쿠키에는 용량 제한이 있어 많은 정보를 담을 수 없습니다.(용량을 커질수록 네트워크에 부하 심해져 성능에 문제가 발생할 수 있습니다.)</li>
</ul>
<br />

<h2 id="session--cookie를-이용한-인증">Session / Cookie를 이용한 인증</h2>
<p>Cookie를 이용한 인증 방법은 계정 정보가 public해서 보안에 매우 취약합니다. 보안이 좋지 않다면 인증을 한다고 해도 인증 절차를 신뢰할 수 없습니다. 그래서 보안된 방법으로 Session / Cookie를 이용한 인증 방법이 등장했습니다.</p>
<p><img src="https://user-images.githubusercontent.com/57402711/128005155-6e85cb3a-f582-4bf5-aa30-c3b1b320a34f.png" alt="image"></p>
<h3 id="인증-절차">인증 절차</h3>
<ol>
<li>클라이언트에서 로그인을 시도합니다.</li>
<li>서버에서는 응답 정보를 읽어 사용자를 확인 후, 사용자의 고유한 ID값을 부여하여 세션저장소에 저장한 후, 이와 연결되는 세션ID를 발행합니다.</li>
<li>사용자는 서버에서 해당 세션ID를 받아 쿠키에 저장을 한 후, 인증이 필요한 요청마다 쿠키를 헤더에 담아 보냅니다.</li>
<li>서버에서는 쿠키를 받아 세션 저장소에서 대조를 한 후 대응되는 정보를 가져옵니다.</li>
<li>인증이 완료되고 서버는 사용자에 맞는 데이터를 보내줍니다.</li>
</ol>
<p>Session / Cookie 인증 방식은 기본적으로 세션 저장소가 필요합니다. 세션 저장소는 로그인 정보를 통해서 사용자의 정보를 저장하고 열쇠가 되는 세션ID값을 만들어 줍니다. 그리고 HTTP 헤더에 세션ID를 담아 사용자에게 보냅니다. 그러면 클라이언트에서는 쿠키로 보관하고 인증을 하는 경우에 요청에 쿠키를 담아 보냅니다. 웹 서버에서는 세션 저장소에서 쿠키(세션ID)를 받고 저장되어 있는 정보와 매칭시켜 인증을 완료합니다.</p>
<h3 id="장점-1">장점</h3>
<ul>
<li>세션/쿠키 방식은 기본적으로 쿠키를 매개로 인증을 거칩니다. 여기서 쿠키는 세션 저장소에 담긴 유저 정보를 얻기 위한 열쇠라고 보시면 됩니다. 따라서 쿠키가 담긴 HTTP 요청이 도중에 노출되더라도 쿠키 자체(세션 ID)는 유의미한 값을 갖고있지 않습니다(중요 정보는 서버 세션에) 이는 위의 계정정보를 담아 인증을 거치는 것보단 안전해 보입니다. </li>
<li>각 사용자마다 고유한 세션 ID가 발급되기 때문에, 요청이 들어올 때마다 회원정보를 확인할 필요가 없습니다.</li>
</ul>
<h3 id="단점-1">단점</h3>
<ul>
<li>쿠키를 탈취당하더라도 안전할 수 있지만. 만일 A 사용자의 HTTP 요청을 B 사용자(해커)가 가로챘다면 그 안에 들어있는 쿠키도 충분히 훔칠 수 있습니다. 그리고 B 사용자는 그 훔친 쿠키를 이용해 HTTP 요청을 보내면 서버의 세션저장소에서는 A 사용자로 오인해 정보를 잘못 뿌려주게 되는 현상이 발생합니다.(세션 하이재킹 공격이라고 합니다) </li>
<li>세션 저장소를 서버에 구축해야하는 노력이 필요하며 추가적인 저장공간이 필요하여 서버에 부하가 걸릴 수 있습니다.</li>
</ul>
<p><strong>쿠키를 탈취당하는 것을 방지하기 위해서는 HTTPS를 사용하여 요청을 탈취해도 해석할 수 없게 만드는 방법이 있고, 세션의 유효시간을 넣거나 탈취당하면 세션을 파기시키는 방법이 있습니다.</strong></p>
<br />

<h2 id="jwt를-이용한-인증">JWT를 이용한 인증</h2>
<p>JWT(JSON Web Token)이란 인증에 필요한 정보들을 암호화를 통해서 토큰을 관리하는 것을 의미합니다. JWT 기반 인증은 Session / Cookie 인증 방식과 같이 앱 인증에서 가장 보편적으로 사용되는 방식입니다. 또한 Session / Cookie 인증 방식과 유사하게 사용되는 Access Token(JWT Token)을 HTTP 헤더에 담아서 서버로 보내는 방식입니다.</p>
<h3 id="jwt-구성-요소">JWT 구성 요소</h3>
<p><img src="https://user-images.githubusercontent.com/57402711/128010325-7da352a2-ed7d-474c-9f40-cb0ca18e20b5.png" alt="image"></p>
<ul>
<li>Header: 토큰의 타입과 해시 암호화 알고리즘으로 구성되어 있습니다. </li>
<li>Payload: 토큰에 담을 클레임(claim)정보를 포함하고 있습니다. Payload에 담는 저보의 한 조각을 클레임이라고 지칭하며, key / value의 한 쌍으로 이뤄져있습니다.</li>
<li>Verify Signature: Base64 방식으로 인코딩한  Header,payload 그리고 SECRET KEY를 더한 후 서명됩니다.</li>
</ul>
<pre><code>Header, Payload는 인코딩될 뿐(16진수로 변경), 따로 암호화되지 않습니다. 따라서 JWT 토큰에서 Header, Payload는 누구나 디코딩하여 확인할 수 있습니다. 여기서 누구나 디코딩할 수 있다는 말은 Payload에는 유저의 중요한 정보(비밀번호)가 들어가면 쉽게 노출될 수 있다는 말이 됩니다. 하지만 Verify Signature는 SECRET KEY를 알지 못하면 복호화할 수 없습니다. </code></pre><p><code>인코딩(Encoding)</code>: 컴퓨터에서 인코딩은 동영상이나 문자 인코딩 뿐 아니라 사람이 인지할 수 있는 형태의 데이터를 약속된 규칙에 의해 컴퓨터가 사용하는 0과 1로 변환하는 과정을 통틀어 말합니다. (코드화 = 암호화)</p>
<p><code>디코딩(Decoding)</code>: 디코딩은 인코딩의 반대로서 사람이 이해 할 수 있도록 바꿔주는 것을 의미합니다. 즉, 바이트 형식을 문자열로 변환한다는 의미입니다.</p>
<pre><code>A 사용자가 토큰을 조작하여 B 사용자의 데이터를 훔쳐보고 싶다고 가정하겠습니다. 그래서 payload에 있던 A의 ID를 B의 ID로 바꿔서 다시 인코딩한 후 토큰을 서버로 보냈습니다. 그러면 서버는 처음에 암호화된 Verify Signature를 검사하게 됩니다. 여기서 Payload는 B사용자의 정보가 들어가 있으나 Verify Signature는 A의 Payload를 기반으로 암호화되었기 때문에 유효하지 않는 토큰으로 간주하게 됩니다. 여기서 A사용자는 SECRET KEY를 알지 못하는 이상 토큰을 조작할 수 없다는 걸 확인할 수 있습니다.</code></pre><h3 id="인증-절차-1">인증 절차</h3>
<p><img src="https://user-images.githubusercontent.com/57402711/128011468-a5e929a4-8d20-452d-ab39-b163f11fd2aa.png" alt="image"></p>
<ol>
<li>사용자가 id와 password를 입력하여 로그인을 시도합니다.</li>
<li>서버는 요청을 확인하고 secret key를 통해 Access token을 발급합니다.(JWT의 유효 기간을 설정합니다.) </li>
<li>JWT 토큰을 클라이언트에 전달 합니다.</li>
<li>클라이언트에서 API 을 요청할때 클라이언트가 Authorization header에 Access token을 담아서 보냅니다.</li>
<li>서버는 JWT Signature를 체크하고 Payload로부터 사용자 정보를 확인해 데이터를 반환합니다.</li>
<li>검증이 완료된다면, Payload를 디코딩하여 사용자의 ID에 맞는 데이터를 가져옵니다. </li>
</ol>
<h3 id="장점-2">장점</h3>
<ul>
<li>세션 저장소를 서버에 구축할 필요가 없어서 서버를 구축할 노력이 필요하지 않아서 간편합니다.</li>
<li>세션 저장소를 서버에 구축하지 않아도 되어서 정보를 담는 메모리를 차지 하지 않아 Statelless 서버를 구축할 수 있습니다.(서버 확장 및 유지보수에 유리합니다 ./ 인증 정보에 대한 별도의 저장 공간이 필요하지 않습니다.)</li>
<li>외부 인증 시스템 Token을 이용한 접근이 가능합니다. OAuth가 가장 대표적인 Token을 이용한 이증 방식으로 대부분의 소셜 로그인이 토큰을 기반으로 외부 인증 시스템을 구축하고 인증을 합니다.</li>
<li>모바일 어플리케이션 환경에서도 잘 동작합니다.</li>
</ul>
<h3 id="단점-2">단점</h3>
<ul>
<li>Session / Cookie과 다르게 JWT의 Token의 길이가 길어, 인증 요청이 많아질수록 네트워크에 부하가 심해집니다.</li>
<li>한 번 발급된 Token은 유효기간이 만료될 때까지 계속 사용이 가능합니다.</li>
<li>Token을 탈취당하면 대처하기 어렵습니다.</li>
<li>Payload 자체는 암호화되지 않아서 사용자의 중요 정보를 담을 수 없습니다.</li>
</ul>
<pre><code>이미 발급된 JWT에 대해서는 돌이킬 수 없습니다. 세션/쿠키의 경우 만일 쿠키가 악의적으로 이용된다면, 해당하는 세션을 지워버리면 됩니다. 하지만 JWT는 한 번 발급되면 유효기간이 완료될 때 까지는 계속 사용이 가능합니다. 따라서 악의적인 사용자는 유효기간이 지나기 전까지 신나게 정보들을 털어갈 수 있습니다. 

해결책: 기존의 Access Token의 유효기간을 짧게 하고 Refresh Token이라는 새로운 토큰을 발급합니다. 그렇게 되면 Access Token을 탈취당해도 상대적으로 피해를 줄일 수 있습니다. 이는 다음 포스팅에 나올 Oauth2에 더 자세히 다루도록 하겠습니다.</code></pre><h2 id="jwt-보안-문제-대체-방안">JWT 보안 문제 대체 방안</h2>
<p>JWT의 단점을 보안하기 위해서 많은 방법이 하나의 방법만을 사용하기 보다 여러가지 대체 방안을 활용하면 보안 안전에 크게 도움이 됩니다. </p>
<h3 id="짧은-만료-기한-설정">짧은 만료 기한 설정</h3>
<p>토큰의 만료 시간을 짧게 설정하는 방법을 고려할 수 있습니다. 토큰이 탈취되더라도 빠르게 만료되기 때문에 피해를 최소화할 수 있습니다. 하지만 탈취된 동안은 대처할 수 없는 단점을 완벽하게 해결할 수는 없으며 자주 토큰을 발급받아야 하는 단점도 존재합니다.</p>
<h3 id="refresh-token">Refresh Token</h3>
<p>Access Token(JWT)를 통한 인증 방식의 문제는 만일 제 3자에게 탈취당할 경우 보안에 취약하다는 점입니다. 위에서 제시한 만료 기한을 짧게 설정하는 방식이 존재하지만 여러번 로그인을 통한 인증받아야 하는 단점이 있었습니다. Refresh Token을 이용하면 만료 기한을 짧게 설정하고 로그인을 여러번 하는 시도하는 문제를 해결할 수 있습니다.</p>
<p>Refresh Token은 Access Token과 똑같은 형태의 JWT입니다. 처음에 로그인을 완료했을 때 Access Token과 동시에 발급되는 Refresh Token은 긴 유효기간을 가지면서, Access Token이 만료됐을 때 새로 발급해주는 열쇠가 됩니다.</p>
<pre><code>사용 예를 간단히 들어보겠습니다. Refresh Token의 유효기간은 2주, Access Token의 유효기간은 1시간이라 하겠습니다. 사용자는 API 요청을 신나게 하다가 1시간이 지나게 되면, 가지고 있는 Access Token은 만료됩니다. 그러면 Refresh Token의 유효기간 전까지는 Access Token을 새롭게 발급받을 수 있습니다. </code></pre><p><img src="https://user-images.githubusercontent.com/57402711/128017043-8f95c910-3632-4948-87be-89dc53972450.png" alt="image"></p>
<p><strong>인증 절차</strong></p>
<ol>
<li>사용자가 ID , PW를 통해 로그인합니다.</li>
<li>서버에서는 회원 DB에서 값을 비교합니다(보통 PW는 일반적으로 암호화해서 들어갑니다)</li>
<li>로그인이 완료되면 Access Token, Refresh Token을 발급합니다.</li>
<li>Access Token, Refresh Token을 클라이언트에 전달 합니다.</li>
<li>사용자는 Refresh Token은 안전한 저장소에 저장 후, Access Token을 헤더에 실어 요청을 보냅니다.</li>
<li>Access Token을 검증합니다.</li>
<li>검증이 완료되면 이에 맞는 데이터를 클라이언트에 보냅니다.</li>
<li>시간이 지나 Access Token이 만료됩니다.(여기서 만료라는 개념은 그냥 유효기간을 지났다는 의미입니다.)</li>
<li>사용자는 이전과 동일하게 Access Token을 헤더에 실어 요청을 보냅니다.</li>
<li>서버는 Access Token이 만료됨을 확입합니다.</li>
<li>확인하고 권한없음을 신호로 클라인언트에 응답합니다.</li>
<li>사용자는 Refresh Token과 Access Token을 함께 서버로 보냅니다.</li>
<li>서버는 받은 Access Token이 조작되지 않았는지 확인한후, Token이 동일하고 유효기간도 지나지 않았다면 새로운 Access Token을 발급해줍니다.</li>
<li>서버는 새로운 Access Token을 헤더에 실어 다시 API 요청을 진행합니다. </li>
</ol>
<p><strong>장점</strong></p>
<p>기존에서 사용하던 JWT(Access Token만을 사용한 인증)보다 더욱 안전한 인증 절차입니다.</p>
<p><strong>단점</strong></p>
<ul>
<li>구현이 복잡하며, 검증 절차가 로직이 길어집니다. (frontEnd와 Server 둘다)</li>
<li>Access Token이 만료될 때마다 새롭게 발급하는 과정에서 생기는 HTTP 요청 횟수가 많습니다. 이는 서버의 자원 낭비로 귀결됩니다.    </li>
</ul>
<br />

<h2 id="참고-자료">참고 자료</h2>
<p><a href="https://sabarada.tistory.com/9">[REST API]REST를 사용할 때 주의해야할 점</a></p>
<p><a href="http://haah.kr/2017/05/24/rest-the-dissertation-summary/">REST - 논문(요약) 훑어보기</a></p>
<p><a href="https://blog.npcode.com/2017/04/03/rest%ec%9d%98-representation%ec%9d%b4%eb%9e%80-%eb%ac%b4%ec%97%87%ec%9d%b8%ea%b0%80/">REST의 representation이란 무엇인가</a></p>
<p><a href="https://www.youtube.com/watch?v=RP_f5dMoHFc">그런 rest api로 괜찮은가</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[EMO Project 보완 후 회고(1)]]></title>
            <link>https://velog.io/@park-moen/EMO-Project-%EB%B3%B4%EC%99%84-%ED%9B%84-%ED%9A%8C%EA%B3%A01-9o4i2dxu</link>
            <guid>https://velog.io/@park-moen/EMO-Project-%EB%B3%B4%EC%99%84-%ED%9B%84-%ED%9A%8C%EA%B3%A01-9o4i2dxu</guid>
            <pubDate>Fri, 06 May 2022 04:33:28 GMT</pubDate>
            <description><![CDATA[<h2 id="🚀-다시-시작하게-된-계기">🚀 다시 시작하게 된 계기</h2>
<p>처음 블로그 포스팅을 통해서 직접 참여하여 프로젝트를 진행한 경험이 부족한 것을 알게 되었고 부족한 부분을 해결하기 위해서 새로운 토이 프로젝트를 진행할 예정이었으나, 이전에 진행했던 프로젝트들이 항상 아쉬운 마음으로 마무리했다는 생각이 들면서 다시 한번 github repo를 둘러보았습니다. </p>
<p>그중에 처음 진행한 EMO 프로젝트를 찾게 되었고 부족한 부분이 많은 소스 코드라는 것을 알게 되었고, 새로운 프로젝트를 하는 것도 좋은 방안이지만 부족한 부분을 찾아보고 수정을 통해서 성장을 할 수 있겠다는 생각에 유지 보수를 진행할 결정을 하게 되었습니다.</p>
<p>특히 매력적인 부분은 밑에서 리스트로 정리할 예정이지만 간단하게 언급하면 MPA로 만들어진 소스 코드를 SPA로 만들면 서버와의 네트워크 통신 자원도 줄어들고 EndPoint 또한 명확하게 정해지면서 전역 스코프에 식별자를 생성하지 않을 수 있다는 점이 EMO Project를 선택한 가장 큰 이유입니다.</p>
<br>

<h2 id="🫂-팀-프로젝트-정리">🫂 팀 프로젝트 정리</h2>
<p><a href="https://github.com/EeeeeMO/EMO/wiki/Wiki-proposal">기획 자료</a></p>
<p>부트캠프를 통해서 4명의 팀이 협력을 통해 일주일 동안 기획 + 제작을 진행한 프로젝트입니다.</p>
<p>기획 회의를 통해 아이디어를 공유하고 의견을 모은 결과 많은 사람이 음식에 대한 관심도가 높다는 것을 파악하고 그중에서 1인 가구의 수가 증가하는 점에 대해 중점을 맞추어 다양한 종류와 요리 방법을 쉽게 알려주는 어플을 만들어 보자는 취지에서 EMO(이 재료로 모해 먹지?)가 만들어지게 되었습니다.</p>
<h3 id="📖-기술-스텍">📖 기술 스텍</h3>
<img src="https://user-images.githubusercontent.com/57402711/164351783-07f976d1-b085-47f9-8794-3eb61e9c9355.jpg">


<h3 id="📖-협업을-통해-배운-점">📖 협업을 통해 배운 점</h3>
<p>팀원들과 회의를 통해서 기간 내에 마무리 할 수 있는 기능을 추가하기로 결정하고, 기능별로 분배를 통해서 자신이 맞은 업무를 진행하는 것을 목표로 프로젝트를 진행하였습니다. </p>
<p>프로젝트 기간이 짧고 대부분 팀원이 처음 협업을 해보는 상황이라서 우왕좌왕하는 모습도 보였습니다. 그래도 기간 안에 기획한 기능들을 구현하기 위해서 여러 밤을 함께 보내면서 부족한 부분이 많이 보였지만, 원하는 주요 기능들을 모두 완성할 수 있었습니다. </p>
<p>프로젝트를 진행하면서 성장할 수 있었던 계기는 기획 단계와 팀원들이 작성한 코드에 대한 의견을 주고받는 일이었습니다.</p>
<p>기획을 통해서 배운 점은 기획을 준비하면서 서로의 의견을 통합하고 자신의 주장을 펼칠 수 있었고, 다른 사람의 의견을 듣고 서로 다른 점을 마쳐 가면서 넓은 시각과 세세한 부분까지 신경 쓸 수 있다는 장점을 느꼈습니다.</p>
<p>팀원들이 작성한 코드에 대한 의견을 주고 받으면서 자신만이 사용하는 코드 작성 방식을 사용하지 않고 통합된 컨벤션을 사용하여 컨벤션에 위배되는 코드를 쉽게 찾을 수 있었습니다. </p>
<br>

<h2 id="📝-문제점-및-보완-리스트">📝 문제점 및 보완 리스트</h2>
<h3 id="📖-문제점">📖 문제점</h3>
<ol>
<li>MPA 사용으로 인한 코드의 흐름을 읽기 어렵고 어플리케이션이 무거워지는 문제 발생</li>
<li>MPA로 제작하여 EndPoint가 따로 존재하지 않고 무분별한 .html, .js 확장자가 존재해서 가독성이 매우 떨어짐</li>
<li>모듈을 사용하지 않아서 js file이 분리되어 있어도 전역 스코프는 공통으로 사용하여 암묵적 전역 현상이 발생할 수 있다.</li>
<li>식별자 네이밍이 명확하지 않아서 </li>
<li>중복된 코드로 레거시 코드가 발생</li>
<li>잘못된 비동기 로직을 사용</li>
<li>Type을 정적으로 컴파일하지 않아서 프로퍼티 접근 시 null, undefind를 대처하지 못하는 경우가 많이 발생하는 코드가 존재한다.</li>
</ol>
<h3 id="📖-보완-리스트">📖 보완 리스트</h3>
<ul>
<li>MPA로 만들어져 있는 코드를 SPA와 Ajax 네트워크 통신으로 어플리케이션을 가볍게 만들기 (1번 문제)</li>
<li>Webpack을 도입해서 모듈 스코프를 사용하고 EndPoint 설정 후 Babel을 사용해서 JS 크로스브라우징 문제 해결하기 (2번, 3번 문제)</li>
<li>깔끔한 코드 작성<ul>
<li>명확하게 의미로 작성하지 않은 식별자 수정 및 무분별한 변수 사용 억제, 상수 식별자 대문자 네이밍 규칙을 통해서 가독성 높이기 (4번 문제) </li>
<li>중복된 코드로 레거시 코드가 발생한 부분을 함수 단위로 묶어 명확한 작업 만들기(너무 과도한 작업을 하는 함수는 작은 단위로 분리하기) (5번 문제)</li>
<li>aync, await 문법으로 모든 비동기 로직을 통일하여 비동기로 자동하는 코드를 을 동기로 작동하는 코드처럼 만들기 (6번 문제)</li>
</ul>
</li>
<li>TypeScript을 도입하여 정적으로 컴파일하여 타입 문제 해결 및 런타임에 발생하던 에러를 컴파일 단계에서 에러를 캐치하여 효율성 높이기 (7번 문제)</li>
</ul>
<p><a href="https://github.com/park-moen/EMO-v2/issues?q=is%3Aissue+is%3Aclosed">문제점 보완에 관련된 Schedule Task 한번에 보기</a></p>
<br>

<h2 id="🤔-난관에-부딪히고-깨달은-점">🤔 난관에 부딪히고, 깨달은 점</h2>
<h3 id="📖-코드-분석">📖 코드 분석</h3>
<p>EMO 프로젝트를 진행할 시기에는 4명의 팀원이 분업을 통해서 기능을 구현했습니다. 그리고 시간도 많이 흘러서 코드가 어떻게 동작하는지 알 수 없는 때도 있고, 왜 이렇게 작성하였는지에 대한 의문도 생겼습니다. 그래서 모든 코드를 분석하면서 코드가 어떻게 만들어졌는지 알아가는 시간이 가졌습니다. </p>
<p>코드 분석을 하면서 분명 시간이 어느 정도 걸렸지만, 다른 사람 및 과거의 나 자신이 코드를 작성한 의도를 생각해보았고, 코드에 흐름을 알아가면서 프로젝트를 어떤 방향으로 개선 및 코드 컨벤션을 통일할 방법도 구축하였습니다. 이를 통해 문제점 개선이 필요한 코드가 어디에 있는지 알게 되었고, 더 효율적으로 문제점을 개선할 수 있었습니다.</p>
<h3 id="📖-image-upload">📖 Image upload</h3>
<image width="40%" src="https://user-images.githubusercontent.com/57402711/164360009-4606cdbd-6661-4f30-a30a-9ebff2eae0d0.jpg" />

<p>위의 사진은 레시피 목록을 보여주는 Page로 작업을 진행할 당시에 레시피 마다 image가 다르지만 모든 이미지를 하나로 통일하여 작업하였습니다. 그 이유로는 Webpack을 사용하는 경우 JS 확장자에서 image 확장자(jpg, png, etc….)를 사용하기 위해서는 import로 사용할 file을 가져와야 합니다. 하지만 저는 Json-server에서 URL 경로만을 알려주는 data를 AJAX 통신을 통해서 받아 왔습니다. 그 결과 JS file에서 명시적으로 image를 지정하지 않아서 경로를 확인할 수 없는 문제가 발생했습니다.</p>
<p><img src="https://user-images.githubusercontent.com/57402711/164361276-a3e1cca3-e3c0-442d-b28f-edf1f97e755d.png" alt="image"></p>
<p>많은 방법을 찾아 보았지만, 해결책이 잘 떠오르지 않고 차선책 한 가지만 가지고 있었습니다. 그래서 어쩔 수 없이 차선책으로 선택한 방법으로는 Entry point file(index.ts)에서 데이터로 사용하는 모든 image를 import 후에 image를 참조만 하는 함수를 만들고 바로 실행하는 방법을 선택했습니다.</p>
<p>물론 차선책이지만 webpack에서 mode를 production으로 설정하면 Tree Shaking을 자동으로 실행되어 사용하지 않는 import 로직을 제거하기 때문에 차선책으로 선택했습니다. (대신 Tree Shaking을 사용하고 싶으면 ESM을 사용해야 합니다) 또한 EMO-V3를 시작하게 된다면 개선점을 찾아볼 예정입니다.</p>
<p><img src="https://user-images.githubusercontent.com/57402711/164362367-cbe266fd-74cb-49f5-bcb8-546827c67e3b.png" alt="image"> </p>
<blockquote>
<p>imageAllUplad 함수를 실행하면 데이터에 있는 모든 image를 참조하게 만들었습니다.</p>
</blockquote>
<h3 id="📖-javascript-▶︎▶︎-typescript">📖 JavaScript ▶︎▶︎ TypeScript</h3>
<p>EMO Project를 시작하면서 TypeScript를 Project에 적용해서 부족한 TypeScript의 실력도 같이 올리고 싶다는 욕심이 생겼습니다. 그래서 Project를 directory에 옮기자마자 <code>npm i -D typescript</code>로 TypeScript를 Project에 적용했습니다.</p>
<p>난관에 봉착한 부분으로는 무시무시하다는 Setting 부분이었습니다. 특히 JavaScript에서 TypeScript로 마이그레이션을 하는 상황이라서 js Extention으로 작성한 logic 들이 많은 Error를 던졌습니다. 저는 마이그레이션을 하면서 tsconfig.json 설정에 allowJs 속성을 지정하지 않는 선택을 했습니다. 그래서 처음 Js로 만든 Project는 완전히 망가져 버렸습니다. 시간이 더 많이 걸리지만, 이런 작업 선택을 하게 된 이유로는 SPA로 Project를 수정하면서 순서를 지키고 싶다는 생각이 크게 들었습니다. 예시로 들면 일단 login page =&gt; ingredient page =&gt; cuisine page로 Project의 흐름을 잡고 싶다고 생각하면 일단 login page에 대한 js extention을 ts extention으로 바꾸면서 type에 대해 정의를 하고 Route를 설정하고 그다음 순서로 가는 페이지의 작업을 반복하는 방식을 통해서 획일적인 작업 방식을 통해 작업을 통일하고 싶다는 생각을 가지게 되었고 그 결과 모든 작업을 위의 방식으로 작업을 마무리할 수 있었습니다.</p>
<p>협업을 하지 않고 혼자서 모든 작업을 수행해야 했던 저는, 획일적으로 모든 작업 방식을 통일하지 않았다면 뒤죽박죽이 된 작업 방식으로 가독성 저하 및 통일되지 않은 코드 스타일로 더욱 많은 시간을 소비해야 할 수 있었다고 생각합니다.</p>
<br>

<h2 id="🔑-결과물">🔑 결과물</h2>
<p><a href="https://github.com/park-moen/EMO-v2">github 주소</a>
<em>작동이 궁금하면 github repo로 가서 git clone을 통해서 실행 할 수 있습니다.</em></p>
<p><image width="40%" alt="part-1" src="https://user-images.githubusercontent.com/57402711/164357324-807e2046-fd3b-4bd5-93d6-ab02eb96c0fd.gif"/><image width="40%" alt="part-2" src="https://user-images.githubusercontent.com/57402711/164357463-ea4d398a-6eb4-4c36-b4c6-f99017297905.gif"/></p>
<br>

<h2 id="🖌-작업하며-배우고-느낀-것">🖌 작업하며 배우고 느낀 것</h2>
<h3 id="📖-라우터를-통해-spa-구현">📖 라우터를 통해 SPA 구현</h3>
<image src="https://user-images.githubusercontent.com/57402711/165251579-b93ab53d-1a75-4b9a-86dd-d5f969ffa3cf.jpg"/>

<p>History 기본으로 Router API를 구현했습니다.</p>
<p>Router가 동작하는 원리를 설명하면 router 변수에 URL의 address를 문자열로 미리 지정하여 화면이 변경될 때마다 history.pushState function을 호출하여 URL의 address를 변경하고 변경 후에 root dom에 접근하여 innerHTML을 사용하여 이전 화면을 바뀐 화면으로 새롭게 교체하는 방식을 사용했습니다.</p>
<p>그렇다면 innerHTML function을 호출할 때 마다 리렌더링이 발생하는 문제가 있는데 왜 굳이 사용했는가 하는 의문이 들 수 있습니다. 저도 처음에 createElement와 DocumentFragment를 사용하여 최소한의 리렌더를 발생하면 되지 않을까 하는 생각을 했지만, 코드를 분석하고 보니 화면을 바꿀 때는 고정적으로 변하지 않는 화면을 제외하고는 모든 부분이 바뀌어야 한다는 것을 알게 되었고, createElement와 DocumentFragment를 사용하면 가독성이 떨어지고 막노동 같은 코드가 많이 생길 수 있다는 생각을 가지게 되었고 innerHTML의 문제점을 정확히 인지하고 사용하면 유용하게 사용할 수 있겠다는 생각에 innerHTML을 사용하여 DOM 업데이트를 진행했습니다.</p>
<p><em>위의 image의 코드에서는 Router를 사용하기 위해 중요한 두 가지 function으로 구축되어 있습니다.</em></p>
<p><img src="https://user-images.githubusercontent.com/57402711/165255119-da091773-d180-43bc-9cf8-f96c619ba007.png" alt="image"></p>
<p>첫 function은 initialRoutes를()의 동작은 처음 화면을 렌더링하기 위해서 만든 function으로 entry point(index.ts)에서 initialRoutes를 호출하면 Login page를 보여주는 기능입니다.</p>
<p>두 번째 function은 Router를 구축하는 가장 중요한 기능으로 pushRouter()입니다. pushRouter()는 첫 화면을 제외하고 모든 router가 이동하기 위해 거쳐야 하는 과정으로 <code>pushRouter(pathName: string, qureyId?: string, backPageType?:</code>cuisine<code>|</code>recommend<code>)</code>의 매개변수를 넣어서 호출하면 새로운 화면을 보여주는 역할입니다.</p>
<p><img src="https://user-images.githubusercontent.com/57402711/165256704-99c70d89-8422-4033-a6dc-0d0a84af5021.png" alt="image"></p>
<p>위의 예시 Image는 ingredient page의 일부 logic으로 다음으로 이동할 URL의 address pathName을 DOM에서 추출하여 <code>pushRouter(url address pathName)</code> 호출로 동작을 구현할 수 있습니다.</p>
<p>Router 작업을 통해 어떻게 SPA가 동작하는지 알게 되었습니다. 처음에는 History API만을 사용하여 모든 Router 작업을 수행하려고 생각했지만, 검색을 통해 Location API 같은 쉽게 URL에 접근할 수 있는 여러 가지 Web API에 대해서도 알게 되었습니다.</p>
<h3 id="📖-drydont-repeat-yourself-원칙-적용">📖 DRY(Don&#39;t Repeat Yourself) 원칙 적용</h3>
<p>처음 코드를 분석하면서 중복되는 코드가 많다는 생각을 가지게 되었습니다. 그래서 <code>EMO/src/utils</code> dir 경로에 중복되는 utils function들은 재사용할 수 있게 만들었습니다.</p>
<p><img src="https://user-images.githubusercontent.com/57402711/165258563-1428bdb4-d2b2-4580-91c8-c30063d6bc37.png" alt="image"></p>
<p>대표적인 중복 function으로 찜하기를 눌렀을 경우 전체 화면의 배경 화면의 opacity를 투명하게 만들고 modal을 2초 정도 띄워주는 UI를 만들었습니다. 하지만 찜하기 기능은 cuisine page, recipe page, recommed page에서 모두 사용했고 중복된 Logic을 찾아서 <code>timeModel()</code>으로 공통 Logic을 옮기고 과정을 거쳐서 어디서나 재사용할 수 있게 만들었습니다.</p>
<img width="40%" src="https://user-images.githubusercontent.com/57402711/165262300-a365f73d-a1c5-408b-a346-6e4bd5614aa2.gif">

<p><code>timeModel()의 UI 모습</code></p>
<p>DRY 원칙을 적용하여 획일적인 코드 스타일을 가지게 되었고, Refactoring을 하면서 아무 생각 없이 Logic을 작성하면 이렇게 중복되는 코드가 많을 수 있다는 경험을 하게 되었습니다. 중복 Logic을 제거 후 재사용할 수 있는 function을 만드는 경험을 통해 다음 Project에서는 기능 구현만을 생각하기보다 재사용할 수 있으며, 가독성 있는 Logic을 목표로 세우고 Project를 진행하겠습니다.</p>
<h3 id="📖-상수에-대한-명시적인-코드-작성">📖 상수에 대한 명시적인 코드 작성</h3>
<p>EMO Project 보완을 진행할 때는 혼자서 모든 Logic에 대해서 작업을 진행하여 획일적인 코드 스타일을 쉽게 가질 수 있었습니다. 물론 위에서 설명한 DRY 원칙도 많은 도움이 되었습니다. 그러나 모든 Project에 대해서 혼자서 코드 작성을 할 수 있는 게 아니며, 언젠가는 협업을 진행해야 한다는 생각을 가지게 되었습니다. 그래서 혼자서 지킬 수 있는 협업 규칙을 정하게 되었고 명시적인 코드를 상수로 작성하는 규칙을 EMO Project에 적용하게 되었습니다.</p>
<p><img src="https://user-images.githubusercontent.com/57402711/165264938-89e87ef6-9378-4661-9f28-7020151bd49c.png" alt="image"></p>
<p><img src="https://user-images.githubusercontent.com/57402711/165265139-ba6b265f-c074-400c-b182-fcd6dcd53cdf.png" alt="image"></p>
<p>Server 통신을 위해 사용되는 URL은 많은 Logic에서 중복되어 사용되는 점을 생각하여 따로 file을 분리하여 중복 Logic을 제거하고 수정하는 경우에는 하나의 Logic만을 수정하면 의존성을 통해 사용된 부분도 쉽게 수정할 수 있었고(DRY 원칙 적용) JavaScript의 문법 const 변수 선언으로 상수로 URL 변수를 지정하여 상수로 만들었습니다. 상수로 변수를 선언할 때 가장 중요하게 생각했던 점으로는 변수 네이밍으로 의미를 정확하게 알려 주어 다른 사람이 Logic의 흐름을 읽지 않고 변수 네이밍만으로 의미를 알 수 있게 만들었습니다.</p>
<p><img src="https://user-images.githubusercontent.com/57402711/165265765-38c50518-10cc-4457-a4c4-4e9a1a1821ba.png" alt="image"></p>
<p><img src="https://user-images.githubusercontent.com/57402711/165265804-41359917-a77e-442c-9e4c-38629c10fd8c.png" alt="image"></p>
<p>위의 Logic은 Cuisine Page로 Number Type을 사용해야 하는 경우가 있었습니다. 처음에는 <code>addToCartElement.style.opacity = &quot;1&quot;;</code> 같이 Number를 바로 삽입하는 방식을 사용했지만, Number의 의미가 정확하지 않다고 생각하여 Object의 Property로 지정하였습니다. 그러나 문제가 되는 부분으로는 객체 리터럴의 Property는 불변이 아니면서 Private 지정도 안 된다는 문제가 있어 어디서나 쉽게 접근하여 변경할 수 있었습니다. 해결 방안을 고민하였고 TypeScript의 readonly 문법을 통해 읽기 전용으로 변경할 수 있어서 상수의 의미를 가진 Property로 만들 수 있었습니다.</p>
<br>

<h2 id="🛠-보완해야-할-부분">🛠 보완해야 할 부분</h2>
<h3 id="📖-test-logic-작성하기">📖 Test Logic 작성하기</h3>
<p>2022.04.26일 기준으로 아직 저는 정확히 Test의 중요성을 완벽하게 인지하지 못하고 있습니다. 이론적으로 Unit Test, E2E Test 등의 여러 가지 Test 이론에 대해서 읽어보았고 필요성을 알게 되었고 실무에서 직접 사용을 통해 Test의 중요성을 이론적인 부분뿐만 아리나 실무적인 부분까지 느끼고 싶다는 생각에 보완할 부분에 추가하기로 선택하게 되었습니다.</p>
<p>특히 EMO Project에서 보완해야 할 첫 번째 순서로 Test를 선택할 생각입니다. 그 이유로는 EMO Project 보완 후 회고(2)를 시작하게 되면 새롭게 Refactoring 해야 하는 부분의 Logic을 수정하기 전에 Test를 미리 작성해 두면 더욱 쉽게 Error를 잡고, 어디에서 문제가 발생하는지를 쉽게 파악할 수 있으며, Test Logic을 작성하지 않은 경우보다 Test Logic을 작성할 때 시간적인 이점도 많이 챙길 수 있다고 생각해서 첫 번째로 보완할 점으로 선택하게 되었습니다.</p>
<p>물론 Test를 진행하게 되면 초기 투자 시간이 많이 들어간다고 생각하지만, 이 경험 또한 저한테 많은 도움이 될 것으로 예상하고 TypeScript에 대해서 이론만 풍부하게 알고 있던 저에게 EMO Project에 직접 적용하면서 실무적으로도 많은 도움을 받은 좋은 경험이 있으므로 꼭 처음 보완할 부분으로 선택하고 싶습니다.</p>
<h3 id="📖-json-server-▶︎▶︎-실제-server로-변경하기">📖 Json-Server ▶︎▶︎ 실제 Server로 변경하기</h3>
<p>Json-Sever를 처음 팀원들과 Project에 도입한 이유로는 쉬운 HTTP API 사용 방법과 간단하게 DB를 구축할 수 있는 이점이 있어서였습니다. (물론 그 당시에는 BackEnd를 구축할 방법도 모르게 했습니다….) 하지만 쉽게 사용할 수 있는 이점에도 불구하고 다시 Project를 진행하면서 제한된 사용 범위로 Ajax 통신에서 비용을 많이 차지하는 방법을 차선책으로 선택하는 경우가 많았고 보안 측면인 문제도 큰 문제로 떠올랐습니다.</p>
<image src="https://user-images.githubusercontent.com/57402711/165238747-b756e147-d71d-4363-8222-d57ad0639372.png" />

<p>위의 코드 예제를 보면 Login 관련 Logic으로 Id, Password를 확인할 때마다 Server 통신을 해야 하는 문제도 발생하고, Password처럼 보안을 철저하게 신경 써야 하는 부분에는 bcrypt library 등의 hash 알고리즘으로 암호화를 하는 방식도 사용할 수 없는 문제가 발생했습니다.</p>
<p>이러한 여러 문제를 해결하기 위해 Node.js(Express) + Mysql(sequelize)를 사용해서 Server를 구축할 예정으로 EMO Project 보완 후 회고(2)를 통해서 수정할 예정입니다. </p>
<h3 id="📖-webpack-production-환경-만들기">📖 Webpack production 환경 만들기</h3>
<p>처음에는 위의 두 가지(Server, Test 구축) 경우만 보완하면 더 좋은 Web Application으로 만들 수 있다고 생각했습니다. 그러는 도중 Server를 구축하면 직접 Server를 배포하지 않으면 포트폴리오에서 실제 Service를 보여 줄 수 없다는 생각이 떠올리게 되었습니다.</p>
<p>Server 배포를 생각하다 문득 현재 Webpack의 Mode 설정이 development로 설정했다는 것을 알게 되었고, 그 결과 Webpack의 Mode가 Production일 경우에 Webpack에서 자동으로 성능 향상(최적화)을 도와주는 기능들의 도움을 받지 못하고 있다는 생각을 가지게 되었습니다.</p>
<p>간단하게 Production mode를 사용할 경우의 이점을 설명하면 Tree Shaking(ESM을 사용할 경우만 해당)을 자동으로 지원하며, Optimization을 모드에 따라 조건을 정하여 production mode일때 최적화를 진행할 수 있습니다. 그 외에 Plugin을 통해 production에서 불필요한 console.log() 로직을 complie 단계에서 모두 지우는 방법도 지원합니다.</p>
<p>위의 설명처럼 많은 혜택을 EMO Project에서는 누리지 못하고 있다는 생각을 가지게 되었고, 직접 Server와 DB를 구축하게 되면 server 배포뿐만 아니라 Front 또한 Webpack의 설정을 production에 맞춰서 Front 배포를 통해 실제 사이트를 포트폴리오로 사용할 생각입니다.</p>
<blockquote>
<p>수정 예정 일자는 6월로 생각하고 예상하고 있습니다.</p>
</blockquote>
<br>

<h2 id="💻-후기">💻 후기</h2>
<p>EMO-v2 Project를 2주 동안 작업을 하면서 많은 부분을 배우게 되었습니다. 특히 Vanilla JS로 모든 작업을 진행하여 JS Library, Framework에서 내부적으로 작동되는 부분에 대해서 깊이 있게 알게 되었습니다. 물론 아쉬운 점도 많이 있었습니다. 특히 가장 아쉽게 생각했던 부분으로는 Vanilla JS로 모든 작업을 진행하여서 Library와 Framework을 사용할 때보다 더 많은 자유도로 작업을 진행할 수 있어서 구조에 관한 생각 및 상태 관리에 대해서 생각할 수 있었는데 그 부분을 신경 쓰지 못했던 게 가장 아쉽게 생각이 들었습니다. 그래서 다음 Project에서는 MVC 패턴을 사용하여 상태 관리 및 객체 지향 프로그래밍을 중점을 두고 작업을 진행하고 싶다는 생각을 가지게 되었습니다.</p>
<p>다음 Project 또한 Vanilla JS로 작업을 진행할 예정이며 React 또는 Vue와 같이 JS Library를 사용하지 않는 의문이 들 수 있어서 간단하게 설명하면 Vanilla JS를 사용하는 것보다 JS Library를 사용하면 쉽게 자유도 또한 많이 줄어들며 다른 사람이 만든 UI Library 등을 사용하여 쉽게 UI를 만들 수 있어서 매우 편해서 빠르게 작업을 진행할 수 있으며 어느 정도의 퀄리티 보장받을 수 있다는 장점이 있어서 수익을 창출하고 빠르게 기능을 구현하는 곳에서 사용하는 게 적합하다는 생각을 가지게 되었습니다.</p>
<p>하지만 많은 내부 동작을 Library가 마법을 부리는 것처럼 보이는 부분에 대해서 library 사용자들은 알기가 쉽지 않습니다. 저는 Library가 마법을 부리는 부분에 대해서 많은 궁금증을 가지게 되었고 Library의 내부 코드에 대해서 찾아보니 모든 부분이 Vanilla JS로 만들어졌다는 것을 알게 되었습니다. 그래서 Vanilla JS의 중요성을 더 크게 느끼게 되었고 EMO-v2 Project를 Vanilla JS로 진행하였습니다.</p>
<p>EMO-v2 Project를 끝내고 나서는 Webpack의 동작 원리, React에서 SPA 동작 원리, TypeScript의 사용 방법 및 컴파일 방법, Reflow, Repaint 고려한 Logic 작성, 획일적인 코드 스타일 적용 방법과 같은 많은 부분에 대해서 알게 되었고 이러한 경험에서 배운 것에만 만족하지 않고 더 많은 부분에 호기심을 가지게 되었고 내가 아직 많이 부족하다는 것을 알게 되었고 여기서 멈춰서 뒤처지지 말고 계속해서 앞으로 나아가야 한다는 것을 깨닫는 계기가 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 개발환경 설정 (webpack)]]></title>
            <link>https://velog.io/@park-moen/Webpack-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@park-moen/Webpack-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 27 Feb 2022 07:30:33 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Webpack을 시작으로 babel, typescript, eslint, prettier, nvm 등의 개발 환경을 순서대로 설정할 계획입니다.</p>
</blockquote>
<h2 id="📌-webpack-개념-정리">📌 Webpack 개념 정리</h2>
<p><strong>Webpack5 를 기준으로 모든 기능을 소개하고 있습니다.</strong></p>
<p>현재 자바스크립트는 광범위하게 사용되고 있는 반면에 파일 단위로 관리할 수 있는 모듈을 지원하지 않았습니다. (ES6 이후에 ESM을 지원을 하지만 현재 지원하지 않는 브라우저도 아직 존재한다.)  그렇다고 여러 개의 파일을 브라우저에 로딩하는 것은 그만큼 네트워크에 무리를 주는 행동이였습니다. 또한 자바스크립트는 파일별로 분리하여 식별자 이름을 작성해도 파일(module) 단위로 스코프를 가지고 있지 않고 공통의 스코프를 가지고 있어서 식별자 이름 충돌이 발생하는 문제가 존재했습니다.(여러가지 문제를 발생시키는 전역 스코프에 식별자가 등록되는 문제가 발생할 수 있습니다.)</p>
<p>이러한 문제를 해결하기 위해서 즉시실행함수(IIFE)를 사용해서 모듈 기능을 대처하려는 노력을 하였지만 하나의 파일에서 모든 작업을 해야하 다른 문제가 존재했습니다. 이러한 문제를 해결하기 위해서 모듈 번들러 관리 라이브러리가 등장하게 되었고 현재 가장 많이 사용하고 있는 모듈 번들러로는 Webpack입니다.</p>
<img src="https://user-images.githubusercontent.com/57402711/124529586-e02bab00-de45-11eb-85a3-2c365bcb45ed.png" alt="image" style="zoom:30%;" />

<p>Wepbkac 등장 전에 여러가지 모듈 번들러가 존재했습니다. 현재 트랜드로는 Webpack을 사용하여 많은 프로젝트가 진행되고 있으며, ESM(ES2015)가 새롭게 등장했지만 모든 브라우저에서 지원하지 않으며, Webpack configuration에서만 지원하는 다양한 option들을 사용하여 프로젝트를 진행할 수 있습니다.</p>
<h2 id="🚪-entry">🚪 Entry</h2>
<blockquote>
<p>📚 Entry Point 용어 정의</p>
<p>Entry 속성 객체의 property key를 지칭합니다. <code>ex) entry: {keyNmae: &#39;filePath/index.js&#39;}</code> 여기서 Entry Point는 keyName입니다. 축약표현으로 작성하게 될 경우 main이 Entry Point의 기본값입니다. <code>ex) entry: &#39;filePath/index.js&#39;</code></p>
</blockquote>
<p><em>Entry 속성</em>은 webpack이 내부의 디펜던시 그래프를 생성하기 위해 사용해야 하는 모듈이며, webpack은 Entry Point가 의존하는 다른 모듈과 라이브러리를 찾습니다. </p>
<p>Entry 속성은 설정 방법에 따라서 단일 엔트리 구문과 다중 엔트리 구문을 설정할 수 있습니다. SPA(single page application)에서는 대부분 단일 엔트리 구문으로 Entry 속성을 설정하여 사용할 수 있습니다.</p>
<blockquote>
<p>🔑 Tip</p>
<p>디펜던시 그래프는 webpack 공식문서에서 사용하는 개념으로 의존하고 있는 모든 모듈에 대해 재귀적으로 빌드한 다음 설정한 엔트리 모듈로 번들합니다.(여러가지 파일을 재귀적으로 찾아서 엔트리 파일로 통일한다고 생각하면 이해하기 쉽습니다.)</p>
</blockquote>
<h3 id="📖-single-entry-syntax">📖 Single Entry Syntax</h3>
<pre><code class="language-js">// webpack.config.js

// 축약된 single entry syntax 표현
module.exports = {
  entry:&#39;./src/index.js&#39;, 

  // 축약된 표현을 사용하면 chunk name과 asset file name이
  // 기본값 main으로 자동 생성됩니다.
}</code></pre>
<pre><code class="language-js">// webpack.config.js

// 기본 single entry syntax 표현
module.exports = {
  entry: {
    app: &#39;./src/app.js&#39;,
  }

  // chunk name과 asset file name을 변경하고 싶은 경우 축약된
  // 표현이 아닌 기본 문법을 사용해서 변경할 수 있습니다.
}</code></pre>
<p>프로젝트를 단위가 작은 경우 단일 엔트리 구문로 Webpack을 설정할 수 있습니다. 하지만 단일 엔트리 구문을 사용하면 설정을 확장할 수 있는 유연성이 떨어질 수 있습니다. </p>
<h3 id="📖-multi-page-application">📖 Multi-Page Application</h3>
<pre><code class="language-js">// webpack.config.js

module.exports = {
    entry: {
        app1: &#39;./src/app1.js&#39;,
        app2: &#39;./src/app2.js&#39;,
    }
      // entry.app1, entry.app2가 Entry Point로 
      // asset file(컴파일 후 출력된 file)에서 2개의 file이 생성됩니다.
}</code></pre>
<img width="196" alt="image" src="https://user-images.githubusercontent.com/57402711/157438752-e55d0ab5-e1e8-4866-aa34-3a64aeecd701.png">

<p>다중 Entry Point를 설정하면 chunk name(Entry point name을 사용)으로 개별 디펜던시 그래프를 생성하여 asset을 사용할 수 있습니다.</p>
<pre><code class="language-js">// webpack.config.js

module.exports = {
    entry: [&#39;./src/app1.js&#39;, &#39;./src/app2.js&#39;],
      output: {
        filename: &#39;bundle.js&#39;,
      },
}</code></pre>
<p>위 예제는 Multi-Page Application처럼 동작한다고 예상할 수 있겠지만 <code>다중-메인 엔트리</code> 방식으로 app1.js, app2.js을 컴파일 해서 하나의 bundle로 만드는 방식입니다. 이 방식은 여러 의존성 파일을 한 번에 주입하고 해당 의존성을 하나의 chunk에 그래프를 표시하려는 경우에 유용합니다. (하나의 파일에 여러가지 module 의존성을 주입하기에 유용하다는 의미입니다.) </p>
<h3 id="📖-each-entry-point-description">📖 Each Entry Point Description</h3>
<p>Entry Point에 Description 객체 option을 통해서 여러가지 기능을 구현할 수 있습니다.</p>
<ul>
<li>dependOn: 현재 Entry Point를 의존하는 Entry Point. dependOn에 지정한 Entry Point가 실행된 후에 실행됩니다.</li>
<li>import: 현재 Entry Point가 참조하고 로드하는 모듈(file path)</li>
</ul>
<pre><code class="language-js">// webpack.config.js

module.exports = {
  entry: {
    app1: &#39;./src/js/app1.js&#39;,
    app2: {
      dependOn: &#39;app1&#39;,
      // app1.js의 entry point인 app1이 먼저 로드하고 
      // app2.js의 entry point app2를 로드하겠다고 명시적으로 알려주는 방법
      import: &#39;./src/js/app2.js&#39;,
      // app2 Entry Point에 명시적으로 로드할 모듈(file path)을 지정
    },
  },
  output: {
    filename: &#39;[name].js&#39;,
    // entry 속성에서 2개의 디펜던시 그래프를 로드하므로, 
    // output 속성에도 따로 이름을 지어줘야합니다.
    // 여러개의 output이 필요한 경우 동적으로 asset file을 가져오기 위해서는 
    // webpack에 미리 지정된 Placeholders를 사용할 수 있습니다. ex) [name], [hash]
    clean: true,
  },
};</code></pre>
<img width="783" alt="image" src="https://user-images.githubusercontent.com/57402711/157594255-f0ecf326-a85d-47e8-a330-86325dac8f99.png">

<p>위의 사진을 보면 app2가 의존(depondOn 속성)하는 app1이 컴파일되고 난 후에 app2에서 설정한 import 속성에 맞게 file path를 정확히 찾아서 컴파일 됩니다.</p>
<ul>
<li>filename: Entry Point에서 output asset(컴파일 결과 file)를 명시적으로 지정할 수 있습니다.<pre><code class="language-js">// webpack.config.js
</code></pre>
</li>
</ul>
<p>module.exports = {
  entry: {
    app1: &#39;./src/js/app1.js&#39;,
    app2: {
      dependOn: &#39;app1&#39;,
      import: &#39;./src/js/app2.js&#39;,
      filename: &#39;utile.js&#39;, 
      // output 속성에서 filename을 지정하지 않고 entry 속성에서
      // 바로 작성할 수 있습니다.
    },
  },
  output: {
    filename: &#39;[name].js&#39;,
    // 만약 위의 app2처럼 명시적으로 entry.app2.filename을
    // 작성한 경우 Placeholderss는 무시됩니다.
    clean: true,
  },
};</p>
<pre><code>
&lt;img width=&quot;196&quot; alt=&quot;image&quot; src=&quot;https://user-images.githubusercontent.com/57402711/157597911-ea395584-dcff-4065-89ae-35162dc4688e.png&quot;&gt;

- runtime: 런타임 청크의 이름입니다. 설정되면 runtime에 지정한 이름의 런타임 청크가 생성되고 그렇지 않을 경우에는 기존의 Entry Point 이름을 사용합니다. 
&lt;img width=&quot;541&quot; alt=&quot;image&quot; src=&quot;https://user-images.githubusercontent.com/57402711/157599740-02cda728-96f3-4944-b38a-6e39e35b44e1.png&quot;&gt;

&gt; 위의 예시 사진에서 보면 Error가 발생하는데 `They will use the runtime(s) from referenced entrypoints instead.
Remove the &#39;runtime&#39; option from the entrypoint.`라는 문구가 보이며 현재는 사용하지 않는 option처럼 보입니다. 만약 제가 잘못 사용했다면 댓글로 알려주세요. 

```js
// webpack.config.js

// runtime 속성은 기존의 Entry Point를 가리키지 않아야 합니다.

module.exports = {
  entry: {
    app1: &#39;./src/js/app1.js&#39;,
    app2: {
      runtime: &#39;app1&#39;, // &lt;- 기존의 entry point 사용 
      dependOn: &#39;app1&#39;,
      import: &#39;./src/js/app2.js&#39;,
      filename: &#39;utile.js&#39;,
    },
  },
}</code></pre><ul>
<li><p>publicPath: host된 브라우저를 참조할 때 Entry의 출력 파일(asset)에 대한 공용 URL 주소를 지정합니다. publicPath을 잘못 지정하면 외부 리소스를 가져올 때 404 Error가 발생할 수 있습니다. output.publicPath에 대해서 공부하면 더욱 명확하게 사용법을 확인할 수 있습니다.</p>
</li>
<li><p>library:  현재 엔트리에서 라이브러리를 번들링하려면 라이브러리 option을 지정합니다. (사용 방법 미숙지이며 라이브러리 프로젝트 제작에서 webpack을 사용하게 되는 경우 사용하는 option입니다.)</p>
</li>
</ul>
<h2 id="📇-output">📇 Output</h2>
<p>output 속성은 생성된 번들을 내보내는 위치와 파일 이름을 지정하는 방법을 webpack에게 알려주는 역할입니다. 가장 보편적으로 사용하는 폴더 이름을 <code>./dist</code>입니다.</p>
<pre><code class="language-js">// webpack.config.js

const path = require(&#39;path&#39;) // node.js에서 지원하는 path 모듈을 사용할 수 있습니다.

module.exports = {
  output: {
    path: path.resolve(__dirname, &#39;dist&#39;),
    filename: &#39;bundle.js&#39;
  }
}</code></pre>
<p>화면을 보여줄 html 파일에 번들한 결과인 bundle.js를 script tag에 삽입합니다.</p>
<pre><code class="language-html">&lt;!-- index.html --&gt;

&lt;body&gt;
    &lt;script src=&quot;./dist/bundle.js&quot;&gt;&lt;/script&gt;            
&lt;/body&gt;</code></pre>
<p>webpack은 커맨드 라인을 통해 빌드가 가능하며 ``webpack.config.js<code>파일을 만들면 간단한게</code>webpack` 을 커맨들 라인에 작성하면 됩니다.</p>
<pre><code class="language-js">// webpack.config.js 파일을 작성하지 않은 경우 
$ webpack ./src/index.js ./dist/bundle.js

// webpack.config.js 파일을 작성한 경우
$ webpack </code></pre>
<h2 id="🎞-loader-and-asset-modules">🎞 Loader and Asset Modules</h2>
<p>webpack은 모든 파일은 모듈로 관리합니다.(js, json 파일뿐만 아니라 이미지, 폰트, 스타일시드도 모듈로 관리하는게 목적) 그러나 webpack은 js 파일만 이해합니다. 로더를 사용하면 webpack에서 다른 유형의 파일을 사용할 수 있습니다. </p>
<p>로더는 <code>test</code> 와 <code>use</code> 키로 구성된 객체로 설정합니다.</p>
<ul>
<li>변환이 필요한 파일(들)을 식별하는 <code>test</code> 속성</li>
<li>변환을 수행하는데 사용하는 로더를 가리키는 <code>use</code> 속성</li>
</ul>
<p><strong>css-loader, style-loader</strong></p>
<p>자주 사용하는 로더인 css-loader 와 style-loader로 예시를 들게습니다.</p>
<pre><code>$ npm i --save-dev css-loader style-loader</code></pre><pre><code class="language-js">module: {
  rules: [
    {
      test: /\.css$/,  // 정규 표현식을 사용하여 확장자 css가 보이는 모든 파일을 로더한다.
      use: [&#39;style-loader&#39; ,&#39;css-loader&#39;],
    },
  ],
},</code></pre>
<ul>
<li><code>css-loader</code> : css 파일을 자바스크립트로 변환하는 로더</li>
<li><code>style-loader</code> : css-loader를 통해 자바스크립트로 변환된 스타일시트를 동적으로 돔에 추가하는 로더 </li>
</ul>
<p>위 설정에서는 <code>test</code> 와 <code>use</code> 라는 두 가지 필수 속성을 가진 하나의 모듈을 위해 <code>rules</code> 속성을 정의했습니다. webpack의 컴파일러가 이해하는 방식을 예로 들어 보겠습니다. </p>
<blockquote>
<p>&quot;webpack 컴파일러야 <code>require()</code> / <code>import</code> 문 내에서 &#39;.css&#39; 파일로 확인되는 경로를 발견하면 번들러에 추가하기 전에 <code>style-loader</code>, <code>css-loader</code> 를 사용하여 변환해 주렴&quot;</p>
</blockquote>
<p><strong>handlebars-loader</strong></p>
<p>js 파일, css 파일처럼 html 파일 또한 js 코드에 따라서 랜더링 시키고 싶은 경우가 있습니다. 이런 경우 템플릿 엔진인 handlebars를 사용할 수 있습니다. (다른 템플릿 엔진을 사용해도 괜찮습니다.)</p>
<pre><code>$ npm i --save-dev handlebars-loader handlebars</code></pre><p>handlebars를 install하지 않고 loader만 install 하는 경우 경고가 발생하기 때문에 handlebars도 npm에서 같이 다운 받아야 합니다.</p>
<pre><code class="language-js">module: {
  rules: [
    {
      test: /\.hbs$/,  // 확장자 이름은 hbs를 사용하면 파일을 만들때 간편합니다.(취향 존중)
      use: [&#39;handlebars-loader&#39;],
    },
  ],
},</code></pre>
<pre><code class="language-html">&lt;!-- template.hbs --&gt;

&lt;main&gt;
    &lt;ul&gt;
    &lt;li&gt;사과&lt;/li&gt;
    &lt;li&gt;바나나&lt;/li&gt;
    &lt;li&gt;딸기&lt;/li&gt;
  &lt;/ul&gt;
&lt;/main&gt;</code></pre>
<pre><code class="language-js">// index.js

const tempHtml = require(&#39;./pages/template.hbs&#39;); 

console.log(tempHtml()); // 모든 htmp tag를 문자열로 파싱하는 특징이 있습니다.

</code></pre>
<h2 id="📚-플러그인">📚 플러그인</h2>
<p>로더는 파일 단위로 특정 유형의 모듈을 변환하는 데 사용되자만, 플러그인은 번들된 결과를 처리합니다. 번들된 결과물을 최적화, 환경 변수 주입 등과 같은 광범위한 작업을 수행합니다.</p>
<p>플러그인을 사용하려면 <code>require ()</code>를 통해 플러그인을 요청하고 <code>plugins</code> 배열에 추가해야 합니다. 대부분의 플러그인은 옵션을 통해 사용자가 지정할 수 있습니다. 다른 목적으로 플러그인을 여러 번 사용하도록 설정할 수 있으므로 <code>new</code> 연산자로 호출하여 플러그인의 인스턴스를 만들어야 합니다.</p>
<p><strong>HtmlWebpackPlugin</strong></p>
<p>HtmlWebpackPlugin은 html 파일을 후처리하는데 사용합니다. HtmlWebpackPlugin 플러그인을 사용하여 빌드하면 html 파일로 아웃풋에 생성됩니다. </p>
<p><strong>CleanWebpackPlugin</strong></p>
<p>CleanWebpackPlugin은 빌드 이전에 결과물을 제거하는 플러그인입니다. 과거 파일이 남아 있을 수 있는 문제를 해결합니다.</p>
<p><strong>MiniCssExtractPlugin</strong></p>
<p>스타일시트가 점점 많아지면 하나의 자바스크립트 결과물로 만드는 것이 부담일 수 있습니다. 번들 결과에서 스트일시트 코드만 뽑아서 별도의 CSS 파일로 만들어 역할에 따라 파일을 분리하는 것이 좋습니다..</p>
<pre><code>$ npm install -D html-webpack-plugin clean-webpack-plugin</code></pre><pre><code class="language-js">const HtmlWebpackPlugin = require(&#39;html-webpack-plugin&#39;);
const { CleanWebpackPlugin } = require(&#39;clean-webpack-plugin&#39;);
const MiniCssExtractPlugin = require(&#39;mini-css-extract-plugin&#39;);


module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      filename: &#39;index.html&#39;, // output file name
      template: &#39;index.html&#39;, // template file name
      collapseWhitespace: true, // 빈칸 제거
        removeComments: true, // 주석 제거
    }),
    new MiniCssExtractPlugin({ filename: &#39;[name].css&#39; }),
    new CleanWebpackPlugin({
      cleanAfterEveryBuildPatterns: [&#39;dist&#39;],
    }),
  ],</code></pre>
<h2 id="🧲-mode">🧲 Mode</h2>
<p><code>mode</code> 파라미터를 <code>development</code>, <code>production</code> 또는 <code>none</code>으로 설정하면 webpack에 내장된 환경별 최적화를 활성화 할 수 있습니다. 기본값은 <code>production</code> 입니다. 환경 변수를 사용하여 production 모드와 development 모드를 변경하는 방식을 사용 할 수 있습니다.</p>
<pre><code class="language-js">module.exports = {
  mode: &#39;production&#39;, 
};</code></pre>
<h2 id="참고-자료">참고 자료</h2>
<p><a href="https://webpack.kr/concepts/">Webpack 공식 문서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEB Component]]></title>
            <link>https://velog.io/@park-moen/WEB-Component</link>
            <guid>https://velog.io/@park-moen/WEB-Component</guid>
            <pubDate>Fri, 25 Feb 2022 05:59:14 GMT</pubDate>
            <description><![CDATA[<h2 id="🤔-who-web-components">🤔 WHO Web Components?</h2>
<hr>
<p>WEB Component는 기능별로 재사용 가능한 코드 블록을 캡슐화하여 커스텀 엘리먼트를 생성하고 웹, 앱에서 활용할 수 있도록 해주는 다양한 기술들의 모음이라고 MDN에서 정의하고 있습니다. MDN 정의를 읽으면서 React에서 사용하는 Component 개념이 떠올랐다. 매우 비슷한 개념이지만 Web Component와 React Component는 부분 집합 관계로 이루어져 있습니다. WEB Component 개념에 React Component 개념이 포함되어 있습니다.</p>
<img src="https://user-images.githubusercontent.com/57402711/113571559-928a8100-9651-11eb-8a61-912b9b7bbe5b.png" alt="image" style="zoom:67%;" />

<p>​                                                                                               <a href="https://ko.wikipedia.org/wiki/%EC%97%AC%EC%A7%91%ED%95%A9">[wikipedia 부분 집합]</a></p>
<blockquote>
<p>컴포넌트는 독립적인 소프트웨어 모듈로써, 소프트웨어 시스템에서 독립적인 업무 또는 독립적인 기능을 수행하는 <code>모듈</code>로서 이후 시스템을 유지보수 하는 데 있어 교체 가능한 부품입니다.</p>
</blockquote>
<p>W3C에서 제한적인 HTML Element의 한계를 개선하기 위해서 Custom Element를 만드는 기술인 웹 컴포넌트 표준 및 명세를 만들었습니다. 웹 컴포넌트의 개념이 MDN에 비해서 조금 모호한 느낌을 받아서 간단한 예시를 보여 드리겠습니다.</p>
<pre><code class="language-html">&lt;div&gt;{현재 시간}&lt;/div&gt; // HTML 엘리먼트에서는 현재 시간을 의미하는 엘리먼트가 존재하지 않습니다. </code></pre>
<pre><code class="language-html">&lt;current-time&gt;{현재 시간}&lt;current-time&gt; // 개발자가 직접 커스텀 엘리먼트를 만들어서 확실한 의미를 부여할 수 있습니다.</code></pre>
<h3 id="📖-html의-한계">📖 HTML의 한계</h3>
<ul>
<li>HTML 요소는 W3C에서 표준 및 명세가 있고 HTML 요소마다 의미가 있지만 브라우저와 운영체제에 따라 다른 동작으로 작동할 수 있습니다. 이 부분은 개발의 생산성 및 유지 보수 측면에 있어서 비효율적입니다.</li>
<li>HTML5가 등장하면서 많은 요소가 보충되었지만, 이외에도 여러 기능을 지원하는 요소가 필요합니다.</li>
</ul>
<p><em>이러한 한계로 인해 Web Component라는 개념이 탄생하게 되었습니다.</em></p>
<h3 id="📖-web-component가-이슈화되고-있는-이유">📖 Web Component가 이슈화되고 있는 이유?</h3>
<p>사실 웹 컴포넌트는 이전부터 존재했던 개념이었습니다. 2012년에 <a href="https://d2.naver.com/helloworld/188655">웹 컴포넌트 - NAVER D2</a>에 대해서 상세한 설명을 하였습니다. 그렇다면 많은 프레임워크 컴포넌트가 존재하는데 Web Component가 이슈화되었을까요?</p>
<ul>
<li>작은 서비스를 개발하는 경우 프레임워크 사용 자체가 오버 스펙이 될 수 있습니다.</li>
<li>프레임워크 및 라이브러리 별로 문법 및 개념을 학습해야 하는 러닝 커브가 존재합니다.</li>
<li>서로 다른 프레임워크를 통합해야 하는 경우 하나의 프레임워크를 선정해서 새로 개발을 진행해야 합니다. 즉, 상호응용성(Interoperability)이 발생합니다.</li>
<li>구글에서 웹 컴포넌트 스펙에 따라 구현한 ‘Google - Polymer’ 큰 주목을 받으면서 개발자들 사이에서 유행이 발생하게 되었습니다.</li>
</ul>
<p><strong>‘Google I/O 2016’에서 ‘#UseThePlatform’ 키워드로 웹 표준에 대한 중요성을 각인시켜주었습니다.</strong></p>
<blockquote>
<p>프레임워크들은 다양한 문제를 해결할 강력한 도구이지만 무거운 소스코드는 앱을 무겁게 만들고 리소스를 사용자에게 전가 시키며 프레임워크 종속적인 코드를 생산합니다.</p>
<p>그러한 문제들을 프레임워크 대신 브라우저 기능을 사용하여 해결하면 프레임워크를 가볍게 만들어도 되고 더 적은 자바스크립트 코드를 사용하게 되어 표준 코드로 만든 성능 좋은 Awesome APP! 이 된다는 결론입니다.</p>
</blockquote>
<p><em>구글의 #UseThePlatform 키워드는 프레임워크로 무거운 앱을 만들지 말고 만들려는 기능을 표준대로 코딩하자는 의미를 내포하고 있는 거 같습니다. 이러한 상황을 보면서 구글의 영향력이 개발자들한테 매우 큰 바람을 일으키는 거 같다는 느낌도 받게 되었습니다!!</em></p>
<p><br><br></p>
<h2 id="📐-web-component-표준">📐 Web Component 표준</h2>
<hr>
<h3 id="📖-custom-elements">📖 Custom Elements</h3>
<ul>
<li><p>기존의 HTML 요소 외에 사용자 인터페이스에서 원하는 대로 사용할 수 있는 사용자 정의 요소 및 해당 동작을 정의할 수 있는 JavaScript API 세트입니다.</p>
</li>
<li><p>HTML5 엘리먼트가 지원하지 않는 사용자 Custom Elements를 만들 수 있습니다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;body&gt;
    &lt;div class=&quot;layout-container&quot;&gt;
      &lt;!-- html 문법에서는 의미를 가지지 않는 div태그로써 layout에 대한 정보를 명확하게 알 수 없습니다. --&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;body&gt;
    &lt;layout-container&gt;
      &lt;!-- HTML5에 속한 Elemen에서는 div로 의미 없는 Element를 만들었지만 --&gt;
      &lt;!-- Custom Elements를 사용하면 의미가 정확한 Element를 만들 수 있습니다. --&gt;
    &lt;/layout-container&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
</li>
<li><p>HTML 엘리먼트와 JavaScript Class를 한 몸으로 만들어서 Custom Elements의 Life Cycle을 조작할 수 있습니다. </p>
<pre><code class="language-js">class LayoutContainer extends HTMLElement {
  constructor() {
    // 클래스 초기화. 속성이나 하위 노드는 접근할 수는 없습니다.
    super();
  }

  static get observedAttributes() {
    // 모니터링 할 속성 이름
    return [&quot;layout-container&quot;];
  }

  connectedCallback() {
    // DOM에 추가되었다. 렌더링 등의 처리.
  }

  disconnectedCallback() {
    // DOM에서 제거되었다. 엘리먼트를 정리하.
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    // 속성이 추가/제거/변경되었다.
    // class에서의 this는 Custom Elements의 인스턴스를 가르키고 있습니다.
    this[attrName] = newVal;
  }
}

// &lt;layout-container&gt; 태그가 LayoutContainer 클래스를 사용하도록 연결하는 역할입니다..
customElements.define(&quot;layout-container&quot;, LayoutContainer);
</code></pre>
</li>
<li><p>HTML5에 속한 엘리먼트를 상속받아서 Custom Elements를 만들 수 있습니다.</p>
<pre><code class="language-js">class LayoutContainer extends HTMLDivElement {
    ...
}
customElements.define(&#39;layout-container&#39;, LayoutContainer, {extends: &#39;div&#39;});
</code></pre>
</li>
<li><p>Custom Elements의 Tag 이름을 작성할 때는 예약어 및 업데이트 예정인 요소와 이름 충돌을 막기 위해서 케밥 표기법(-)을 사용해야 합니다.</p>
</li>
</ul>
<h3 id="📖-shadow-dom">📖 Shadow DOM</h3>
<ul>
<li>HTML과 CSS의 스코프를 독립으로 다룰 수 있게 지원하는 개념 및 기능으로 private 하게 유지할 수 있어, 도큐먼트의 다른 부분과의 충돌에 대한 걱정 없이 스크립트와 스타일을 작성할 수 있습니다.</li>
<li>서비스를 만들게 되면 HTML, CSS, JS 모두 하나의 글로벌 스코프라는 진입점이 존재합니다. 즉, 모든 부분을 open 하는 Public 한 점이 FE 개발에 있어서 단점이 될 수 있습니다. (document.querySelector()로 모든 DOM tree에 접근할 수 있습니다.)</li>
<li>Shadow DOM이 등장하기 전에는 <code>&lt;iframe&gt;</code>을 통해서 스코프를 독립적으로 관리할 수 있었지만, 최선이 아니었습니다.</li>
<li>의미 없는 http 요청이 발생할 수 있습니다.</li>
<li>별도의 페이지를 만들기 때문에 performance 관점에서 매우 비효율적입니다.</li>
<li>iframe와 서비스의 도메인 주소가 같지 않으면 접근이 불가능합니다.</li>
<li>이러한 단점을 해결하기 위해서 Shad</li>
</ul>
<pre><code class="language-js">const $publicContainer = document.createElement(&#39;div&#39;);

document.body.appendChild($publicContainer).innerHTML =
  &#39;&lt;style&gt;div { background-color: #82b74b; }&lt;/style&gt;&lt;div&gt;Public Layout&lt;/div&gt;&#39;;

const $privateContainer = document.createElement(&#39;div&#39;);

document.body
  .appendChild($privateContainer)
  .attachShadow({ mode: &#39;open&#39; }).innerHTML =
  &#39;&lt;style&gt;div { background-color: #ccc; }&lt;/style&gt;&lt;div&gt;Private Layout&lt;/div&gt;&#39;;
</code></pre>
<img src="https://user-images.githubusercontent.com/57402711/113661277-c3fe5d80-96e0-11eb-8584-a343a8af433e.png" alt="image" style="zoom:50%;" />

<p><strong><code>attachShadow({ mode: &#39;open&#39; })</code>를 사용하여 글로벌 스코프에서 간단하게 분리할 수 있습니다.</strong> 다른 방식으로는 슬롯 조합을 이용하는 방식도 존재합니다. 슬롯 조합은 <code>&lt;slot name=&quot;blabla&quot;&gt;</code> 을 사용하 <code>&lt;Element slot=&#39;blabla&#39;&gt;</code>  서로를 연결하는 조합입니다.</p>
<blockquote>
<ul>
<li><strong>쉐도우 돔</strong>: 아래의 코드에서 h1, p등 <strong>쉐도우 루트</strong>에 붙어있는 DOM</li>
<li><strong>쉐도우 루트</strong>: <code>#shadow-root</code></li>
<li><strong>쉐도우 호스트</strong>: <strong>쉐도우 루트</strong>의 부모. 아래의 코드에서 <code>div#slot-test</code></li>
<li><strong>라이트 돔</strong>: 도큐먼트의 <strong>쉐도우 호스트</strong>에 붙어있는 노드들. 아래 코드에서 <code>span</code></li>
</ul>
</blockquote>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;title&gt;Document&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;test-slot&quot;&gt;
      &lt;!-- Light DOM --&gt;
      &lt;span slot=&quot;title&quot;&gt;Hello&lt;/span&gt;
      &lt;span slot=&quot;desc&quot;&gt;world&lt;/span&gt;
    &lt;/div&gt;

    &lt;script&gt;
      // Shadow DOM
      document
        .querySelector(&#39;#test-slot&#39;)
        .attachShadow({ mode: &#39;open&#39; }).innerHTML = `
          &lt;h1&gt;
            &lt;slot name=&quot;title&quot;&gt;&lt;/slot&gt;
          &lt;/h1&gt;
          &lt;p&gt;
            &lt;slot name=&quot;desc&quot;&gt;&lt;/slot&gt;
          &lt;/p&gt;
                `;
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<img width="546" alt="image" src="https://user-images.githubusercontent.com/57402711/155661867-87cb4fa9-46a6-4cf3-bdf8-d1c3d8c5952c.png">



<h3 id="📖-html-템플릿">📖 HTML 템플릿</h3>
<p><code>&lt;template&gt;</code>과 <code>&lt;slot&gt;</code> 엘리먼트는 렌더링 된 페이지에 나타나지 않는 마크업 템플릿을 작성할 수 있게 해줍니다. 그 후, 커스텀 엘리먼트의 구조를 기반으로 여러 번 재사용할 수 있습니다.</p>
<ul>
<li>Template Element는 페이지를 불러온 순간 즉시 렌더링하지 않지만, JS를 사용해 인스턴스를 생성할 수 있는 HTML 코드를 담는 방법을 제공합니다. (content 프로퍼티에서 참조합니다.)</li>
<li>Template Element가 등장하면서 자바스크립트 코드의 양을 줄일 수 있게 되었으며 조건에 따라서 DOM의 변경도 가능해졌습니다. (로그인/ 회원가입 페이지 변경 등)</li>
<li>Template Element는 fragment를 활용해서 Virtual DOM을 만들어 content 프로퍼티로 접근하여 사용할 수 있습니다.</li>
<li>저의 추측이지만 Template Element의 content 프로퍼티는 DocumentFragment DOM Tree 자료구조를 참조하고 있습니다. 즉, 가상 돔 자체로 여러 개의 DOM 자식들을 변경하게 되면 content 프로퍼티가 원하는 방향으로 동작하지 않는 현상이 발생하게 됩니다. <strong>해결 방안으로 importNode(cloneNode, deep), cloneNode(deep) 메서드 사용을 통해서 DOM node를 복사하여 사용해야 합니다.</strong></li>
</ul>
<pre><code class="language-html">&lt;section class=&quot;main-container&quot;&gt;
    &lt;!-- Template Element Area --&gt;
&lt;/section&gt;

&lt;template id=&quot;list-container&quot;&gt;
    &lt;ul class=&quot;items&quot;&gt;
    &lt;li class=item&gt;
        &lt;!-- value --&gt; 
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/template&gt;

&lt;script&gt;
  const $mainContainer = document.querySelector(&#39;.main-container&#39;);
    const $template = document.getElementById(&#39;list-container&#39;);
  const $clone = $template.importNode($template.content, true);
     const $li = $clone.querySelector(&#39;.item&#39;);

  $li.textContent = &#39;Hello Template Element&#39;;

  $mainContainer.appendChild($clone);
&lt;/script&gt;
</code></pre>
<p><strong>Template Element와 Custom Elements를 컴포넌트로 구성하려면 HTML, JS 두 개의 파일이 필요하다는 단점으로 인해 현재 Template Element의 Web Component에서의 영향력이 떨어지고 있는 상태입니다.</strong></p>
<p><br><br></p>
<h2 id="🔑-글을-마치며">🔑 글을 마치며...</h2>
<hr>
<p>글을 마치기 전에 작성하지 않은 부분에 대해서 말씀드리겠습니다. W3C 표준 명세에서는 Web Component 스펙으로 3가지가 아닌 4가지가 공표하고 있습니다. HTML Import라는 하나의 표준이 더 존재하지만 제가 포스터에서 작성하지 않은 이유가 있습니다.</p>
<ol>
<li>Firefox에서 현재 HTML Import를 지원하지 않고 있으며 다른 브라우저들도 구현에 나서지 않고 있기 때문입니다.</li>
<li>위에서 말했던 Template Element의 단점인 HTML, JS 파일을 각각 필요한 단점을 해결하기 위해서 HTML Import가 탄생했지만 이미 ES Module 표준이 존재하고 있으며, 많은 브라우저에서 지원 및 개발 중인 단계입니다.</li>
<li>제 생각이지만 HTML Import 사용을 위한 <code>&lt;link&gt;</code> 가 CSS 적용을 위한 <code>&lt;link&gt;</code> 유사하며, 차라리 EM Module을 사용하는 방식이 깔끔하다고 생각합니다.</li>
<li>HTTP/2를 사용하면 여러 개 파일을 빠르게 받을 수 있다고 해도, 여전히 번들링한 단일 파일을 받는 것이 빠릅니다.</li>
</ol>
<p>이러한 단점으로 인해서 HTML Import는 MDN에서도 보이지 않고 있으며 다른 대책으로 Web Component를 만드는 현상이 발생하고 있습니다. (lit-html을 사용하는 경우가 많습니다).</p>
<p>현재 Web Component는 여러 충돌이 있었지만 빠르게 발전하고 있으며 Google에서는 polymer라는 라이브러리도 발표하며 많은 사람에게 영향을 주고 있습니다. 10년 후에는 React, Vue, Angular 등의 프레임워크(라이브러리)를 사용하지 않으며, 순수 JS로 코딩을 하는 날이 오지 않을까 하는 제 생각이 반영되어 블로그를 작성하게 되었습니다. (미리미리 준비하는 자세!!)</p>
<p>또한 현재 대부분의 개발은 React를 사용하고 있으며 React의 Virtual DOM 개념에 대해서 모호하게 알고 있다고 생각하고 있었습니다. 이번 Web Component 블로그를 작성하면서 React Component 및 Virtual DOM이 어떻게 만들어지고 있는지에 대해서 생각을 정리할 수 있는 시간이었습니다. </p>
<h2 id="참고-자료">참고 자료</h2>
<hr>
<p><a href="https://ui.toast.com/weekly-pick/ko_20170428">Web Components: Keep calm and UseThePlatform</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/Web_Components">Web Component in MDN</a></p>
<p><a href="https://haeminmoon.github.io/2020/02/08/web-component/">Web Component</a></p>
<p><a href="https://frontsom.tistory.com/5">웹 컴포넌트 (Web Component)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[블로그 개설 목적(첫 포스팅)]]></title>
            <link>https://velog.io/@park-moen/%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B0%9C%EC%84%A4-%EB%AA%A9%EC%A0%81%EC%B2%AB-%ED%8F%AC%EC%8A%A4%ED%8C%85</link>
            <guid>https://velog.io/@park-moen/%EB%B8%94%EB%A1%9C%EA%B7%B8-%EA%B0%9C%EC%84%A4-%EB%AA%A9%EC%A0%81%EC%B2%AB-%ED%8F%AC%EC%8A%A4%ED%8C%85</guid>
            <pubDate>Tue, 22 Feb 2022 13:36:08 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-개설-목적">📌 개설 목적</h2>
<p>전부터 FE 개발자를 준비하면서 공개된 포스팅으로는 따로 기록하지 않고 개인적 소장으로 기록을 가지고 있었다. 매 순간 공개 포스팅을 작성하겠다는 의지는 있었지만, 막상 하려고 하니 조금 부끄러운 마음도 들면서도 귀찮음 또한 공개 포스팅을 개설하는 것을 미루게 된 이유이다. 하지만 이제는 미루면 안 되겠다는 생각이 크게 들면서 Vlog를 통해서 블로그(공개 포스팅)를 개설하게 되었다.</p>
<p>일단 FE 개발자를 준비하는 기간은 현 시간을 기준으로 1년 정도를 공부한 것 같다. 준비 기간에 비해서 프로젝트 경험을 많이 접해보지 못한 것 같은 생각이 들었다. 그래서 새로운 개인 프로젝트를 여러 개 진행할 예정이었으나 이전에 만들었던 프로젝트들이 미흡하다는 생각이 들면서 지금까지 진행한 프로젝트를 보완 및 회고(후기)를 통해서 포트폴리오 및 이력서를 작성하기를 가장 큰 목표로 설정하였다.</p>
<hr>
<h2 id="🖌-목표-설정">🖌 목표 설정</h2>
<p><em>현시점 최우선 순위로 생각하는 목표로써, 지금 기록한 목표를 기준으로 공부 방향을 잡으면 정체된 현 실력이 향상될 가능성이 크다. (나 자신에게 하고 싶은 말...)</em></p>
<ul>
<li>moen-mart Project 완성 및 회고록 작성(New)</li>
<li>EMO Project 보완 및 회고록 작성(Legacy)</li>
<li>K-bnb Project 보완 및 회고록 작성(Legacy)</li>
<li>기존에 작성한 개인 기록 보완 후 vlog로 이동</li>
<li>새롭게 공부한 자료는 vlog로 작성</li>
<li>algorithm 및 자료구조 문제 풀이 작성</li>
<li>블로그 제작 후 기존 데이터를 새로 만들 블로그로 이동</li>
</ul>
<hr>
<h2 id="🔑-vlog-vs-notion">🔑 Vlog VS Notion</h2>
<p>처음에는 블로그를 개설할 때 고민했던 플랫폼으로 여러 가지 사이트가 떠올랐다. 그중에서 Notion, Vlog가 가장 우선순위로 생각하게 되었다. 그 이유로는 편의성, markdown 문법을 지원을 중점으로 두고 선택하게 되었다. Notion은 개인 포트폴리오 및 이력서에 중점을 두고 있지만 비공개적인 성향이 강한 느낌을 받았다. Vlog는 markdown 문법으로 글을 작성하면서 즉시 포스팅되는 글을 미리보기로 볼 수 있는 매력이 있으며, 검색엔진최적화를 간편하게 설정할 수 있는 장점을 가지고 있었다. 또한 검색엔진최적화로 공개적인 성향이 있었다.</p>
<p>처음에는 포트폴리오, 이력서에 집중하고 싶은 생각에 Notion을 선택하려고 했지만, 내가 작성한 글을 여러 사람이 보면서 소통하고 부족한 부분에 대해서 피드백을 받고 싶다는 욕심이 생기면서 Vlog로 블로그를 개설하게 되었다. 또한 개발 서적을 읽고 후기도 작성하면서 머리로만 이해했던 부분을 글로 풀어내면서 한 번 더 정리하고 싶다는 생각이 들었다. (가끔은 감명 깊게 읽은 비개발 서적도 작성 예정...) 그 결과 최종 선택은 Vlog가 되었다.</p>
<p>다음 단계로는 Gatsby를 공부하고 직접 블로그를 만들고 싶다. 굳이 시간과 노력을 넣어서 직접 블로그를 만들어야 하는 의문이 들 수 있다. 하지만 나는 2개월 정도 번아웃이 오고 쉬면서 잊고 있었던 사실을 깨닫게 되었다. <strong>발전하려고 하지 않고 편안한 보금자리에만 있으면 절대 발전할 수 없다. 그렇기 때문에 계속해서 변화에 관심을 기울이고 새로운 도전을 해야 한다</strong>는 알고 있었지만 무뎌져서 잊고 있던 사실을 정확히 알게 되었다. 나는 책을 읽으면서 르네 데카르트(물론 가장 존경하는 철학자는 아니지만...)의 명언을 좋아하게 되었다. <code>나는 생각한다, 고로 나는 존재한다</code>라는 문장을 개발자의 입장으로 표현하면 <code>나는 변화를 인지한다, 고로 나는 개발자다</code>라고 표현할 수 있는 거 같다. (내 슬로건으로 삼아야겠다!!!) 그렇기 때문에 Vlog가 편리한 사용성을 가지고 있지만 안도해서 발전하지 못하는 개발자가 되지 않겠다는 생각으로 새로운 목표를 미리 정해두었다.</p>
<hr>
<h2 id="🌈-첫-포시팅-후기">🌈 첫 포시팅 후기</h2>
<p>현재 포스팅을 작성하면서 내 상황이 어떤지 정확히 파악할 수 있게 되어서 유용한 시간을 가지게 되었다. 나는 이론을 중요시하면서 실제 프로젝트(실전)에 중요도를 잠시 잊고 있었다. 내가 이해하기 어려운(실전 기술의 부족으로 인한) 이론에 너무 많은 관심을 가지게 되면서 번아웃이 왔던 거 같다. 그러면서 FE 개발에 대한 흥미가 점차 떨어지게 되고 2개월 정도 공부를 소홀히 하게 되었다. 물론 2개월을 쉬면서도 마음 한편으로는 다시 개발 공부를 하고 싶다는 작은 불씨가 있었지만 외면하고 있었던거 같다. 지금 블로그를 작성하면서 새롭게 깨닮은 사실인거 같다. </p>
<p>그리고 새로운 사실을 알게 되었는데 글을 작성하는데 생각만큼 쉽지 않은 거 같다. 어떤 어휘가 좋은지도 생각해야 하고 사용하고 싶은 어휘도 생각이 잘 나지 않는 거 같아서 글 쓰는데 막히는 느낌이 많이 들었다. 개발 공부도 중요하지만, 책을 읽으면서 어휘력도 늘려야 하는 거 같다. 🥲😅</p>
<p>소소한 잡담은 이제 그만하고 이제 목표가 명확하게 설정되었으니 다시 처음으로 개발을 시작했던 마음가짐을 가지고 부족한 부분을 보완하자. 물로 목표 설정은 점진적으로 추가할 예정이다. 미루지 말자!!!!</p>
]]></description>
        </item>
    </channel>
</rss>