<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>p-samename</title>
        <link>https://velog.io/</link>
        <description>@p_samename</description>
        <lastBuildDate>Fri, 26 Sep 2025 09:31:46 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. p-samename. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/p-samename" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[marketing solution] 네이버 애널리틱스]]></title>
            <link>https://velog.io/@p-samename/marketing-solution-%EB%84%A4%EC%9D%B4%EB%B2%84-%EC%95%A0%EB%84%90%EB%A6%AC%ED%8B%B1%EC%8A%A4</link>
            <guid>https://velog.io/@p-samename/marketing-solution-%EB%84%A4%EC%9D%B4%EB%B2%84-%EC%95%A0%EB%84%90%EB%A6%AC%ED%8B%B1%EC%8A%A4</guid>
            <pubDate>Fri, 26 Sep 2025 09:31:46 GMT</pubDate>
            <description><![CDATA[<h2 id="marketing-solution">marketing solution</h2>
<p>현재 운영중인 서비스에 IFDO, GoogleTagManager, Criteo 3가지의 마케팅 솔루션을 사용하고 있다.
최근 naverAnalytics에 대한 추가 요청을 받게 되었다.</p>
<p>react 및 next 에 대한 마케팅 솔루션 관련 스크립트에 대한 문서를 흔하게 찾을 수 없고, 한번 정리해두면 좋을 것 같아 해당 글을 작성하게 되었다. </p>
<p>이 글에서는 네이버 마케팅 솔루션에 관한 내용만 다룰 예정이다.</p>
</br>

<h3 id="공통-스크립트">공통 스크립트</h3>
<p>네이버 애널리틱스 문서에 따르면</p>
<pre><code class="language-jsx">&lt;script type=&quot;text/javascript&quot; src=&quot;//wcs.naver.net/wcslog.js&quot;/&gt;
위 스크립트를 최상단에 import 후,

&lt;script type=&quot;text/javascript&quot;&gt; 
    if (!wcs_add) var wcs_add={}; 
    wcs_add[&quot;wa&quot;] = &quot;accountId&quot;; // 사이트 식별자 (=네이버공통키, na_account_id)
    if(window.wcs){
      wcs.inflow(`${siteDomain}`); // 광고 전환추적을 위한 cookie domain설정
      wcs_do(); // PageView 이벤트 전송
    }    
&lt;/script&gt;
해당 스크립트를 실행해야 한다.</code></pre>
<p>NaverTracker 이란 컴포넌트를 생성 후, 공통적으로 페이지가 전환될때마다, PageView 전환 이벤트를 전송해야하기에
useEffect 의 dependency에 router 변화를 감지할때마다 전환이벤트를 호출하도록 해주었다. 
여기서 중요한 것은 위의 네이버 애널리틱스 문서에는 var 를 사용하고있는데, useEffect안에서의 var 사용을 하게되면 
스코프가 함수내에서만 적용되기에 naverAnalytics의 스크립트에서 window.wcs_add 에 접근하면 undefined 를 반환하게 된다. </p>
<p>때문에 <span style='font-weight:bold'>꼭 !!</span> </p>
<p>❌ if (!wcs_add) var wcs_add={}; 가 아니라 !! , </p>
<p>⭕ if (!window.wcs_add) window.wcs_add = {}; 로 window객체에 할당해주어야 정상적으로 작동한다.</p>
<pre><code class="language-jsx">import { useEffect } from &#39;react&#39;;
import { useRouter } from &#39;next/router&#39;;

export const NaverTracker = () =&gt; {
  const router = useRouter();
  // router 변경 감지시 pageview 이벤트 전송
  useEffect(() =&gt; {
    if (!window.wcs_add) window.wcs_add = {};
    //  각 사이트별 식별자 설정
    window.wcs_add.wa = &quot;accountId&quot;; // 사이트 식별자 (=네이버공통키, na_account_id)
    if (window.wcs) {
      // 광고 전환추적을 위한 cookie domain설정
      wcs.inflow(`${siteDomain}`);
      wcs_do(); // PageView 이벤트 전송
    }
  }, [router.asPath]);
};
</code></pre>
<p>또한 필자는 개발환경에서는 마케팅 정보가 수집되지 않도록,
해당 스크립트 파일을 조건을 걸어 삽입하였다.</p>
<pre><code class="language-jsx">if(process.env.NEXT_PUBLIC_MODE !== &#39;production&#39;){
  &lt;script type=&quot;text/javascript&quot; src=&quot;//wcs.naver.net/wcslog.js&quot; async /&gt;
}</code></pre>
<p>해당 공통 스크립트가 진입 및 페이지 전환시 전송되는 것을 확인할 수 있다.
페이로드의 wcs 객체 wa값에 accountId 가 들어가있다면 제대로 작동하는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/p-samename/post/0a1addc4-819a-4f7e-8e56-57d5cc26815b/image.png" alt=""></p>
<h3 id="전환-스크립트">전환 스크립트</h3>
<p>전환 이벤트는 현재 검수 요청하기 위해 찜 리스트 및 검색 시에만 하도록하였다.
네이버 애널리틱스를 예로 들자면,</p>
<pre><code class="language-jsx">운영 중인 서비스에서는 여러가지 마케팅 솔루션을 사용하기에 hooks 안에서 switch 문을 통해 각각의 타입을 지정하였다.

  export const useMarketingSolution = (marketingInitializeType) =&gt; {
    const insertMarketingSolutionTags = (solutionType, analysisData, analysisType, extraUserEmail) =&gt; {
      if (process.env.NEXT_PUBLIC_MODE === &#39;production&#39;) {
        switch (solutionType) {
          case MARKETING_SOLUTION_TYPE.CRITEO:
            criteoTag(userEmail, analysisData, analysisType);
            return;
          case MARKETING_SOLUTION_TYPE.IFDO:
            ifDoTag(userEmail, analysisData, analysisType);
            return;
          case MARKETING_SOLUTION_TYPE.HURDLERS:
            hurdlersTag(analysisData, analysisType);
            return;
          case MARKETING_SOLUTION_TYPE.NAVER:
            naverTag(analysisData, analysisType);
            return;

          default:
            console.error(&#39;정의되지 않은 solutionType&#39;);
        }
      }
    };
    return { insertMarketingSolutionTags };
  };


// 크리테오 
  const criteoTag = (...) =&gt; {...} 
// 이프두 
  const ifDoTag = (...) =&gt; {...} 
// 허들러스 
  const hurdlersTag = (...) =&gt; {...} 
// 네이버
  const naverTag = (naverData, analysisType) =&gt; {
    try {
      switch (analysisType) {
        case &#39;wish&#39;: {
          // 구매 완료
          const _conv = {}; // 전환이벤트 정보 담을 object 생성

          _conv.type = &#39;add_to_wishlist    &#39;; // 찜하기

          _conv.items = naverData;

          wcs.trans(_conv); // 전환 이벤트 정보를 담은 object를 서버에 전송
          break;
        }
        case &#39;search&#39;: {
          // 구매 완료
          const _conv = {}; // 전환이벤트 정보 담을 object 생성

          _conv.type = &#39;search&#39;; // 찜하기

          _conv.items = naverData;

          wcs.trans(_conv); // 전환 이벤트 정보를 담은 object를 서버에 전송
          break;
        }
        default:
          console.error(`정의되지 않은 analysisType : ${analysisType}`);
      }
    } catch (error) {
      console.error(error);
    }
  };




 - pages\mypage\wishes.jsx

const naverData = [];

if (wishResponse.wishList.length &gt; 0) {
        wishResponse.wishList.forEach((item) =&gt; {
          naverData.push({ id: productIdx, name: productName });
        });
}

insertMarketingSolutionTags(MARKETING_SOLUTION_TYPE.NAVER, naverData, &#39;wish&#39;);

찜한 상품이 한개 이상이라면 상품의 idx와 상품의 name을 push후, 스크립트를 전송.
</code></pre>
<p>전환이벤트는 네이버 애널리틱스에서 제공하는 문서를 보면 다양한 전환스크립트가 있으니, 해당 문서를 참고하여 _convs.items 의 배열에 담아 스크립트를 전송하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/p-samename/post/26ed335b-ff96-4024-8e96-7ca49b977667/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[PM2] 서버 메모리 사용량 최적화]]></title>
            <link>https://velog.io/@p-samename/PM2-%EC%84%9C%EB%B2%84-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%82%AC%EC%9A%A9%EB%9F%89-%EA%B0%9C%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@p-samename/PM2-%EC%84%9C%EB%B2%84-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%82%AC%EC%9A%A9%EB%9F%89-%EA%B0%9C%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Thu, 25 Sep 2025 14:10:55 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/p-samename/post/c60eee04-96ac-4edf-8056-2e6716fd9337/image.png" alt=""></p>
<p>프론트 서버의 메모리 사용량이 많이 올라가는 것을 확인하였다.</p>
<p>첫째로, 프론트 프로젝트에서 메모리 누수가 발생하는것은 아닌지 확인하였으나, 메모리 누수는 아니었고 예상하기로는 당사의 서비스가 다량의 이미지를 사용하기에 이미지의 캐싱과 관련되어있는것은 아닐까 라는 생각이 들었다.</p>
<p>nextjs에서 Image 태그를 사용할 때, 모든 사이즈에 해당하는 최적화된 이미지를 사용하기에 캐싱되는 수가 그만큼 많아지기에 불필요한 많은 캐싱을 줄이기 위해 next.config.js 파일의 deviceSizes를 지정해 주기로 하였다.</p>
<pre><code class="language-js">images: {
    deviceSizes: [375, 800],
}</code></pre>
<p>설정 후, 프론트에서 2가지의 사이즈만으로 최적화 하는것을 확인하였다.
<img src="https://velog.velcdn.com/images/p-samename/post/649ceff4-c460-48b1-95b1-5d5c0f36b8a4/image.png" alt=""></p>
<p>또한 프로세스의 메모리가 과부하가 걸리는 것을 방지하기 위해 ecosystem.config.js의 pm2 설정을 추가 해주었다.</p>
<p>max_memory_restart: &#39;500M&#39;, // 클러스터의 메모리 사용량이 일정 수준을 초과하면 재시작 (30초 간격으로 체크됨)</p>
<p>또한 서버에서 16GB 의 메모리를 사용하며 16개의 cluster를 사용하고있다.
각 cluster 마다 1GB 정도의 메모리를 사용하게 되는데, 500MB가 초과 될 경우 프로세스를 재시작하도록 설정해주었다. </p>
<pre><code class="language-js">module.exports = {
  apps: [
    {
      name: &#39;front&#39;,
      cwd: &#39;./&#39;,
      script: &#39;node_modules/next/dist/bin/next&#39;,
      args: &#39;start&#39;,
      exec_mode: &#39;cluster&#39;, // 실행모드 cluster
      instances: &#39;max&#39;, // CPU 코어 수 만큼 프로세스를 생성
      max_memory_restart: &#39;500M&#39;, // 클러스터의 메모리 사용량이 일정 수준을 초과하면 재시작 (30초 간격으로 체크됨)
      watch: false, // 프로젝트가 리스타트되거나 파일이 체인지 될경우를 와칭시켜줌
      source_map_support: true,
      time: true,
      env_production: {
        NODE_ENV: &#39;production&#39;, // 배포환경시 적용될 설정 지정
      },
      env_development: {
        NODE_ENV: &#39;development&#39;, // 개발환경 적용
      },
    },
  ],
};</code></pre>
<p> 그후 MobaXterm에서 
 pm2 monit 명령어를 통해 해당 서버를 확인하고 각 클러스터당 메모리 사용량을 확인했을때, 500MB 이하를 유지하는 것을 확인할 수 있으며 평균 메모리 사용량에 과부하가 걸리지 않는 것을 확인하였다.</p>
<p><img src="https://velog.velcdn.com/images/p-samename/post/42d5098f-9ee1-4290-b2ed-dd1d4a97d271/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[국제화, 다국어 서비스 도입 (internationalization, I18N)
]]></title>
            <link>https://velog.io/@p-samename/%EA%B5%AD%EC%A0%9C%ED%99%94-internationalization-I18N</link>
            <guid>https://velog.io/@p-samename/%EA%B5%AD%EC%A0%9C%ED%99%94-internationalization-I18N</guid>
            <pubDate>Wed, 17 Sep 2025 05:41:32 GMT</pubDate>
            <description><![CDATA[<h1 id="글로벌-서비스를-위한-i18n-도입">글로벌 서비스를 위한 i18n 도입</h1>
<p>운영 중인 닷컴 서비스에 **i18n(국제화)**을 도입을 하게 되었다. 단순히 번역 파일을 작성하는 것이 아닌, <strong>i18next-scanner</strong>로 번역 키를 자동 추출하고, <strong>구글 스프레드시트</strong>와 연동하여 각 팀이 협업할 수 있는 워크플로우를 구축한 과정을 정리 해보려고한다.</p>
<hr>
<h2 id="1-왜-i18n을-도입하게-되었는가">1. 왜 i18n을 도입하게 되었는가?</h2>
<p>서비스가 글로벌 유저를 타깃으로 확장되면서 걱정되었던 문제는 <strong>언어 지원</strong>이었다. 영어와 일본어,중국어등 최소5개 국어에 대한 언어를 도입하겠다는 계획에 번역 관리의 필요성에 대한 고민이 커졌다.</p>
<p>체감한 문제들:</p>
<ul>
<li>페이지 곳곳에 흩어져 있는 하드코딩 텍스트</li>
<li>프로젝트가 커질수록 누적되는 불필요한 번역 키</li>
<li>디자이너와 마케터와의 원활한 협업 불가</li>
</ul>
<p>이에 <code>i18next</code> 도입을 결정했다.</p>
<hr>
<h2 id="2-i18next-scanner로-번역-키-자동화">2. i18next-scanner로 번역 키 자동화</h2>
<p>수동으로 번역 키를 추출하고 JSON에 정리하는 방법은 곧 한계에 다다랐다. 그래서 <strong>i18next-scanner</strong>를 도입했다.</p>
<pre><code class="language-js">var fs = require(&#39;fs&#39;);
var chalk = require(&#39;chalk&#39;);
const path = require(&#39;path&#39;);

// JavaScript와 TypeScript 파일 확장자
const COMMON_EXTENSIONS = &#39;/**/*.{js,jsx,ts,tsx}&#39;;

module.exports = {
  // 스캔 대상 입력 파일/디렉토리
  input: [`pages${COMMON_EXTENSIONS}`, `components${COMMON_EXTENSIONS}`],
  options: {
    defaultLng: &#39;ko&#39;, // 기본 언어 설정: &#39;ko-KR&#39;
    createNamespace: true,
    lngs: [&#39;ko&#39;, &#39;en&#39;, &#39;ja&#39;, &#39;zh&#39;], // 지원 언어 목록
    trans: {
      component: &#39;Trans&#39;,
      i18nKey: &#39;i18nKey&#39;,
      defaultsKey: &#39;defaults&#39;,
      extensions: [&#39;.js&#39;, &#39;.jsx&#39;],
      fallbackKey: function (ns, value) {
        return value;
      },
    },
    resource: {
      // 번역 파일 경로. {{lng}}, {{ns}}는 언어 및 네임스페이스로 대체
      loadPath: &#39;public/locales/{{lng}}/{{ns}}.json&#39;,
      savePath: &#39;public/locales/{{lng}}/{{ns}}.json&#39;,
      jsonIndent: 2,
      lineEnding: &#39;\n&#39;,
    },
    ns: [&#39;common&#39;, &#39;footer&#39;, &#39;event&#39;],
    defaultNs: &#39;common&#39;,
    defaultValue(lng, ns, key) {
      return &#39;&#39;;
    },
    nsSeparator: &#39;:&#39;,
    keySeparator: false,
    removeUnusedKeys: true, // 사용하지 않는 키 자동 제거
  },
  transform: function customTransform(file, enc, done) {
    &#39;use strict&#39;;
    const parser = this.parser;
    const content = fs.readFileSync(file.path, enc);
    let ns;
    const match = content.match(/useTranslation\(.+\)/);
    if (match) ns = match[0].split(/(\&#39;|\&quot;)/)[2];
    let count = 0;
    parser.parseFuncFromString(content, { list: [&#39;t&#39;] }, function (key, options) {
      parser.set(
        key,
        Object.assign({}, options, {
          ns: ns ? ns : &#39;common&#39;,
          nsSeparator: &#39;:&#39;,
          keySeparator: false,
          removeUnusedKeys: true, // 사용하지 않는 키 자동 제거
        }),
      );
      ++count;
    });
    if (count &gt; 0) {
      console.log(`i18next-scanner: count=${chalk.cyan(count)}, file=${chalk.yellow(JSON.stringify(file.relative))}`);
    }

    done();
  },
};
</code></pre>
<h3 id="상세-설명">상세 설명</h3>
<ul>
<li><code>input</code>: <code>pages</code>와 <code>components</code> 폴더 내의 모든 <code>.js, .jsx, .ts, .tsx</code> 파일을 스캔.</li>
<li><code>options</code>: 기본 언어와 지원 언어, 네임스페이스, 사용하지 않는 키 자동 제거 설정 등..</li>
<li><code>transform</code>: 파일을 읽고 <code>t()</code> 함수 호출을 파싱하여 namespace에 맞게 JSON에 반영.</li>
</ul>
<p>이로써 코드 내 모든 번역 키를 자동으로 추출하고, namespace별로 정리할 수 있어 유지보수가 용이할 수 있도록 하였다.</p>
<hr>
<h2 id="3-구글-스프레드시트와-연동">3. 구글 스프레드시트와 연동</h2>
<p>개발자가 아닌 팀원들도 번역을 편하게 수정할 수 있어야 했다. json파일에 쉽게 접근할 수 없기에 구글 스프레드시트 사용을 선택하였다. </p>
<h3 id="3-1-번역-업로드-uploader">3-1. 번역 업로드 (Uploader)</h3>
<p>프로젝트 내 json형식으로 되어있는 번역 키 파일을 파싱하여 스프레드시트로 업로드해 팀원들이 수정할 수 있도록 동기화한다.</p>
<pre><code class="language-js">class I18nUploader {
  constructor(spreadsheetId, credentials) {
    this.spreadsheetId = spreadsheetId;
    this.credentials = credentials;
  }


  async upload(namespace, data) {
    const doc = new GoogleSpreadsheet(this.spreadsheetId);
    await doc.useServiceAccountAuth(this.credentials);
    await doc.loadInfo();


    const sheet = doc.sheetsByTitle[namespace];
    if (!sheet) return;
    // 데이터의 키와 값을 시트에 업데이트
    // 존재하지 않으면 새 행 추가

    ... 생략
  }
}</code></pre>
<h3 id="3-2-번역-다운로드-downloader">3-2. 번역 다운로드 (Downloader)</h3>
<p>스프레드시트에서 최신 번역 데이터를 json 형식으로 파싱하여 프로젝트로 가져올 수 있도록 동기화한다.</p>
<pre><code class="language-js">const { GoogleSpreadsheet } = require(&#39;google-spreadsheet&#39;);
const creds = require(&#39;./credentials.json&#39;);
const SPREADSHEET_ID = &#39;스프레드시트-ID&#39;;


class I18nDownloader {
  constructor(spreadsheetId, credentials) {
    this.spreadsheetId = spreadsheetId;
    this.credentials = credentials;
  }


  async download() {
    const doc = new GoogleSpreadsheet(this.spreadsheetId);
    await doc.useServiceAccountAuth(this.credentials);
    await doc.loadInfo();


    for (const sheet of doc.sheetsByIndex) {
      const rows = await sheet.getRows();
      // 각 시트의 데이터를 언어별 JSON로 변환 및 저장

      ... 생략
    }
  }
}</code></pre>
<p>프로젝트에서 새로 추가된 번역 키를 스프레드시트에 반영.</p>
<p>스프레드시트 API를 통해 행 데이터를 가져오고, 언어별 JSON 파일을 생성.</p>
<p>비개발자인 팀원들이 스프레드시트에서 번역을 바로 수정할 수 있도록 함.</p>
<p>팀원들은 스프레드시트에서 바로 텍스트를 수정하고, 개발자는 스크립트 실행만으로 최신 번역을 반영할 수 있도록 하였다.</p>
<hr>
<h2 id="4-번역-프로세스">4. 번역 프로세스</h2>
<h3 id="번역-키-추출-scan">번역 키 추출 (scan)</h3>
<p>명령어:</p>
<pre><code>&quot;i18n:scan&quot;: &quot;i18next-scanner --config i18next-scanner.config.js&quot;</code></pre><p>역할:
<code>pages/**/*.js, ts, jsx, tsx</code> 파일에서 <code>t(&quot;...&quot;)</code> 같은 번역 키를 자동으로 추출</p>
<p>결과:
<code>i18n/locales/{{lng}}/{{ns}}.json</code> 파일 업데이트 (키 추가)</p>
<h3 id="구글-시트-업로드-export">구글 시트 업로드 (export)</h3>
<p>명령어:</p>
<pre><code>i18n:export: &quot;node i18n/uploadSheets.js&quot;</code></pre><p>역할:
로컬 <code>locales/ko/translation.json</code>, <code>locales/en/translation.json</code> → Google Spreadsheet에 업로드</p>
<p>목적:
협업자가 구글 시트에서 번역을 쉽게 관리 가능</p>
<p>업로드 프로세스 (JSON → 구글 시트)</p>
<pre><code>&quot;i18n:export&quot;: &quot;node i18n/uploadSheets.js&quot;</code></pre><p>flow:</p>
<pre><code>translation.json (ko/en)
        ↓
  Node.js 스크립트
        ↓
  GoogleSpreadsheet 객체 생성
        ↓
  useServiceAccountAuth(creds)
        ↓
  loadInfo() → 시트 정보 로드
        ↓
  시트 clear &amp; setHeaderRow([&#39;Key&#39;,&#39;Korean&#39;,&#39;English&#39;])
        ↓
  addRows(rows) → 구글 시트에 업로드</code></pre><ul>
<li>JSON에서 Key / Korean / English 매핑</li>
<li>빈 값 처리 가능 (|| &#39;&#39;)</li>
<li>CommonJS (require) 권장</li>
<li>creds 파일 로컬에서 읽어서 안전하게 사용</li>
<li>google sheet api v3 &lt; 최신 버전 v4 이나 현재 프로젝트 commonJS 사용으로 v3 설치</li>
</ul>
<h3 id="다운로드-프로세스-구글-시트-→-json">다운로드 프로세스 (구글 시트 → JSON)</h3>
<pre><code>&quot;i18n:import&quot;: &quot;node i18n/downloadSheets.js&quot;</code></pre><p>flow:</p>
<pre><code>Node.js 스크립트
        ↓
  GoogleSpreadsheet 객체 생성
        ↓
  useServiceAccountAuth(creds)
        ↓
  loadInfo() → 시트 정보 로드
        ↓
  sheet.getRows() → 모든 행 가져오기
        ↓
  각 행을 Ko / En 객체에 매핑
        ↓
  locales/ko/translation.json, locales/en/translation.json 저장</code></pre><p>디렉토리 없으면 생성</p>
<p>JSON.stringify(..., null, 2)로 가독성 확보 (들여쓰기)</p>
<p>로컬 개발 환경에서 바로 실행 가능</p>
<hr>
<h3 id="최종-프로세스">최종 프로세스</h3>
<pre><code>  A --&gt; [Source Code pages/**] --&gt;|i18n:scan| --&gt; [Locales JSON]
  B --&gt; |i18n:import| google sheet --&gt; 프로젝트 JSON 파싱
  C --&gt; |i18n:export| 프로젝트 JSON --&gt; google sheet
  D --&gt; [Next.js Dev / Build]
  E --&gt; [Production 서비스]</code></pre><ol>
<li>google sheet에 있는 언어팩 파일을 json형식으로 파싱하여 프론트로 import</li>
<li>프론트 전체 설정 경로의 i18n key들을 추출하여 locales json 파일로 파싱</li>
<li>json 으로 파싱된 프론트의 key들을 google sheet 로 excel 형식으로 파싱하여 export</li>
<li>프론트 서버 빌드</li>
<li>production 실행</li>
</ol>
<hr>
<h2 id="5-실제-json-예시">5. 실제 JSON 예시</h2>
<p>예시 <code>common</code> 네임스페이스:</p>
<pre><code class="language-json">// public\locales\ko\common.json

{
  &quot;welcomeMessage&quot;: &quot;환영합니다!&quot;,
  &quot;logoutButton&quot;: &quot;로그아웃&quot;,
  &quot;profileTitle&quot;: &quot;프로필&quot;
}


// public\locales\en\common.json

{
  &quot;welcomeMessage&quot;: &quot;welcome!&quot;,
  &quot;logoutButton&quot;: &quot;logout&quot;,
  &quot;profileTitle&quot;: &quot;profile&quot;
}


-------------------


// public\locales\ko\footer.json
{
  &quot;terms&quot;: &quot;이용약관&quot;,
  &quot;policy&quot;: &quot;개인정보취급방침&quot;,
  &quot;businessInfo&quot;: &quot;사업자정보&quot;
}


// public\locales\en\footer.json
{
  &quot;terms&quot;: &quot;Terms&quot;,
  &quot;policy&quot;: &quot;Policy&quot;,
  &quot;businessInfo&quot;: &quot;BusinessInfo&quot;
}

...</code></pre>
<p>스프레드시트에서 키와 번역 값을 관리하면 각 네임스페이스별(&#39;common.json&#39;, &#39;footer.json&#39; ...등) 언어별(<code>ko.json</code>, <code>en.json</code>, ...등 ) JSON 파일이 자동 생성된다.</p>
<hr>
<p>이번 i18n 도입은 팀 전체가 효율적으로 번역을 관리할 수 있는 시스템을 고민할 수 있는 경험이 되었다. 글로벌 서비스 준비에 있어 자동화와 협업 중심 접근은 필수적임을 확인했다. 또한 구글시트를 json 파싱할때에 생기는 실수 혹은 오류들에 대한 검증 프로세스도 준비해야 할 것 같다. 각 행에 대한 빈값이나, 허용되지 않는 문자 등.. 정책을 정하고 해당 정책에 대한 검증 로직을 추가 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[infra] 로그 관리 최적화 및 서버 HDD 사용량 개선]]></title>
            <link>https://velog.io/@p-samename/infra-%EC%84%9C%EB%B2%84-hdd-%EC%82%AC%EC%9A%A9%EB%9F%89-%EA%B0%9C%EC%84%A0</link>
            <guid>https://velog.io/@p-samename/infra-%EC%84%9C%EB%B2%84-hdd-%EC%82%AC%EC%9A%A9%EB%9F%89-%EA%B0%9C%EC%84%A0</guid>
            <pubDate>Thu, 12 Dec 2024 05:18:41 GMT</pubDate>
            <description><![CDATA[<h2 id="pm2-logrotate로-로그-관리-최적화하기">PM2 Logrotate로 로그 관리 최적화하기</h2>
<p>필자의 라이브 프론트 서버에서 애플리케이션의 로그가 현재까지 계속 쌓여 하드디스크(HDD)의 용량이 출시 초기부터 현재까지 조금씩 증가하는 현상을 인지하게 되었다. 필자는 최근 PM2를 이용하여 서버의 프로세스를 관리하는데, <strong>1년 넘게 로그가 누적되어 HDD 용량이 지속적으로 증가</strong>하는 것이었다. 이 문제를 해결하기 위해 PM2의 <strong>Logrotate 모듈</strong>을 도입하여, 로그를 효율적으로 관리하고 용량 증가 문제를 개선하기로 했다.</p>
<hr>
<h3 id="문제-상황">문제 상황</h3>
<ol>
<li><p>로그의 누적</p>
<ul>
<li>PM2는 기본적으로 애플리케이션 실행 로그를 파일에 기록한다.</li>
<li>별도의 설정이 없을 경우, 로그는 파일 크기 제한 없이 지속적으로 기록되어 파일 크기가 증가한다.</li>
</ul>
</li>
<li><p>HDD 용량 부족</p>
<ul>
<li>1년 넘게 로그가 쌓이면서 디스크 공간이 점차 줄어들었다.</li>
</ul>
</li>
<li><p>로그 관리 부재</p>
<ul>
<li>기존 로그는 특정 날짜나 파일 크기에 따라 분리되지 않았기 때문에 기간별로 로그를 관리하기가 힘들었고, 과거 로그를 삭제하는 자동화된 프로세스가 없었다.</li>
</ul>
</li>
</ol>
<hr>
<h3 id="pm2-logrotate-도입">PM2 Logrotate 도입</h3>
<p>PM2는 기본적으로 로그 관리 기능을 제공하지 않지만, <a href="https://www.npmjs.com/package/pm2-logrotate">PM2 Logrotate</a> 모듈을 사용하면 로그를 효율적으로 관리할 수 있다. 이를 통해 로그를 일정 기준에 따라 <strong>분리, 압축, 삭제</strong>할 수 있다.</p>
<h4 id="설치-및-설정">설치 및 설정</h4>
<ol>
<li>PM2 Logrotate 설치
PM2 Logrotate는 PM2의 공식 모듈로 쉽게 설치할 수 있다.<pre><code class="language-shell">pm2 install pm2-logrotate # npm install 이 아닌 pm2 의 모듈이므로 pm2 install 로 설치를 해주어야 한다.</code></pre>
<img src="https://velog.velcdn.com/images/p-samename/post/74e8f8c2-6149-4cf0-86a1-39b2cbe30bad/image.png" alt=""></li>
</ol>
<p> pm2의 module list 에 pm2-logrotate가 설치된 것을 확인 할 수 있다.
<br/></p>
<ol start="2">
<li><p>Logrotate 설정 확인 및 수정
설치 후, 기본 설정을 확인하고 필요에 따라 수정한다.</p>
<pre><code class="language-shell">pm2 show pm2-logrotate</code></pre>
<br/>
</li>
<li><p>주요 설정 변경
설정을 변경하려면 다음 명령어를 사용한다:</p>
<pre><code class="language-shell">pm2 set pm2-logrotate:rotateInterval &quot;0 0 * * *&quot;   # 로그를 하루 단위로 분리.
pm2 set pm2-logrotate:retain 7             # 파일이 7개 이상 넘어가면 오래된 파일을 지우고 최신 파일을 쌓음.
pm2 set pm2-logrotate:max_size 10M         # 로그 파일 크기가 10MB 이상이면 분리.</code></pre>
<br/>
</li>
<li><p>Logrotate 적용 확인</p>
</li>
</ol>
<pre><code class="language-shell">pm2 conf</code></pre>
<p><img src="https://velog.velcdn.com/images/p-samename/post/4b904b5f-fdca-4115-b887-dcad7eb146d7/image.png" alt="">
pm2-logrotate 설정을 확인 할 수 있다.</p>
<hr>
<h3 id="개선-결과">개선 결과</h3>
<ol>
<li><p><strong>HDD 용량 안정화</strong>:</p>
<ul>
<li>Logrotate를 통해 과거 로그가 자동으로 삭제되면서 디스크 용량 증가 문제를 개선 할 수 있었다.</li>
</ul>
</li>
<li><p><strong>로그 관리 자동화</strong>:</p>
<ul>
<li>로그가 일자별로 분리하여 로그관리의 효율을 개선하였고, 오래된 로그는 자동으로 삭제되어 더 이상 수동 관리가 필요 없었다.</li>
</ul>
</li>
<li><p><strong>서버 안정성 확보</strong>:</p>
<ul>
<li>로그 관리로 인해 디스크 공간의 확보로 인하여, 서버의 안정성이 향상될 수 있도록 하였다.</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CI/CD] CI/CD 프로세스 개선]]></title>
            <link>https://velog.io/@p-samename/CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EA%B0%9C%EC%84%A0</link>
            <guid>https://velog.io/@p-samename/CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EA%B0%9C%EC%84%A0</guid>
            <pubDate>Tue, 10 Dec 2024 03:21:07 GMT</pubDate>
            <description><![CDATA[<h1 id="jenkins-기반-cicd-프로세스-개선-배포-시간-단축과-리소스-최적화">Jenkins 기반 CI/CD 프로세스 개선: 배포 시간 단축과 리소스 최적화</h1>
<p>필자의 서비스는 Jenkins를 이용하여 CI/CD를 하고 있다.
프로젝트를 각 deploy 서버에서 배포할 때, 각 서버의 CPU와 MEMORY 사용량이 튀는(갑자기 증가하는) 현상을 발견하였다. 또한 빌드 시간이 오래 걸리는 문제도 있어 이를 해결하고자 했다.</p>
<p>Jenkins의 build 서버에서 GitLab의 repository를 Jenkins 워크스페이스로 가져와 shell script를 실행할 때, 기존의 실행 코드는 아래와 같은 순서로 이루어지고 있었다.</p>
<hr>
<h2 id="기존의-shell-script">기존의 shell script</h2>
<h3 id="build-서버">build 서버</h3>
<pre><code class="language-shell"># 1. 기존의 압축파일(front.tar) 과 node_modules, package-lock.json 등등의 재설치 및 빌드파일을 제거.
rm -f front.tar
rm -rf node_modules
rm package-lock.json
rm -rf .git
rm -rf .next

# 2. npm install 실행. - 패키지 설치
npm install

# 3. npm build (.next) - 프로젝트 빌드.
npm run build

# 4. 로컬 디렉토리를 front.tar 파일로 압축.
tar -cvf front.tar ./

# 5. 로컬의 front.tar 압축파일을 첫 번째 deploy 서버와 두 번째 deploy 서버에 복사. 
cp -f /var/lib/jenkins/workspace/Front-Build/front.tar /var/lib/jenkins/workspace/Front1-Deploy/front.tar
cp -f /var/lib/jenkins/workspace/Front-Build/front.tar /var/lib/jenkins/workspace/Front2-Deploy/front.tar</code></pre>
<h3 id="deploy-서버">deploy 서버</h3>
<pre><code class="language-shell"># 1. /opt/front.tar 기존 파일을 front.tar.back으로 이름을 변경하여 백업.
mv -f /opt/front.tar /opt/front.tar.back

# 2. /home/service-name/jenkins-target/front.tar 파일을 /opt/front.tar로 이동. 새로운 빌드파일을 /opt 디렉토리에 배치.
mv -f /home/service-name/jenkins-target/front.tar /opt/front.tar 

# 3. /opt/front 디렉토리를 초기화하기 위해 삭제 후, 새로운 /front 디렉토리 생성.
cd /opt
rm -rf front
mkdir front

# 4. /opt/front.tar 파일을 /opt/front/front.tar로 복사.
cp -f /opt/front.tar /opt/front/front.tar

# 5. front.tar 파일에 권한(읽기, 쓰기, 실행)을 부여.
chmod 777 /opt/front/front.tar

# 6. front.tar 파일을 현재 디렉토리(/opt/front)에 압축 해제.
cd /opt/front
tar -xvf front.tar

# 7. /opt/front 디렉토리와 모든 하위 파일의 소유권을 root 사용자 및 그룹으로 변경.
sudo chown -R root:root /opt/front

# 8. 새로 빌드된 어플리케이션을 실행하기 위해 기존 프로세스를 정리 - PM2 프로세스 종료.
pm2 kill

# 9. package.json에 정의된 모든 의존성 패키지를 설치.
npm install

# 10. 프로젝트 빌드 - 배포 파일 생성.
npm run build

# 11. 프로젝트 시작.
npm run start</code></pre>
<h3 id="문제점">문제점</h3>
<p>위의 기존 파이프라인에서 발견된 문제점은 다음과 같다.</p>
<ol>
<li>중복된 작업</li>
</ol>
<ul>
<li>빌드 서버에서 이미 npm install과 npm run build를 실행하여 빌드 파일을 생성했음에도, 배포 서버에서 다시 npm install과 npm run build를 실행하고 있었다.
이는 불필요한 리소스 낭비를 초래하고 CPU와 메모리 사용량을 급증시켰다.</li>
</ul>
<ol start="2">
<li>비효율적인 배포 시간</li>
</ol>
<ul>
<li>중복된 작업으로 인해 배포 시간이 불필요하게 길어졌다.</li>
</ul>
<hr>
<h2 id="개선된-shell-script">개선된 Shell Script</h2>
<h3 id="개선-방향">개선 방향</h3>
<ul>
<li>빌드 서버에서 이미 npm install 및 npm run build를 완료했기 때문에, 배포 서버에서는 이 과정을 다시 실행하지 않는다.</li>
<li>배포 서버는 빌드 서버에서 전달된 front.tar 파일을 압축 해제하고, 프로젝트를 실행하는 역할만 수행하도록 변경하였다.</li>
</ul>
<h3 id="개선된-build-서버-script">개선된 build 서버 script</h3>
<p>기존 build서버의 script는 build서버에서 npm install 및 npm run build 후 해당 디렉토리의 파일을 front.tar로 압축시켜 각 deploy 서버의 디렉토리로 이동시키는 역할을 하고있기에, 그대로 수행시키도록 한다. </p>
<p>따라서, 기존의 deploy 서버의 실행 script의 개선을 진행하였다.</p>
<p>프로세스는 앞서 말한 것처럼 중복된 npm install 및 npm run build 실행문을 제거하고 build서버에서 build후 압축된 front.tar 압축파일을 압축 해제한 후, 해당 프로젝트를 npm run start로 실행만 할 수 있도록 하여 기존의 불필요한 중복 실행 script를 개선하였다.</p>
<h3 id="개선된-deploy-서버-shell-script">개선된 deploy 서버 shell script</h3>
<pre><code class="language-shell"># 1. /opt/front.tar 기존 파일을 front.tar.back으로 이름을 변경하여 백업.
mv -f /opt/front.tar /opt/front.tar.back

# 2. /home/service-name/jenkins-target/front.tar 파일을 opt/front.tar로 이동. 새로운 빌드파일을 /opt 디렉토리에 배치.
mv -f /home/service-name/jenkins-target/front.tar /opt/front.tar

# 3. /opt/front 디렉토리를 초기화 하기 위해 삭제 후, 새로운 /front 디렉토리 생성.
cd /opt
rm -rf front
mkdir front

# 4. /opt/front.tar 파일을 /opt/front/front.tar로 복사.
cp -f /opt/front.tar /opt/front/front.tar

# 5. front.tar 파일에 권한(읽기, 쓰기, 실행)을 부여.
# 파일 권한 문제로 스크립트가 중단되는 상황을 피하기 위해 권한을 부여한다. 
chmod 777 /opt/front/front.tar

#6. front.tar 파일을 현재 디렉토리(/opt/front)에 압축 해제.
cd /opt/front
tar -xvf front.tar

# 7. /opt/front 디렉토리와 모든 하위 파일의 소유권을 root 사용자 및 그룹으로 변경.
sudo chown -R root:root /opt/front

# 8. 새로 빌드된 어플리케이션을 실행하기 위해 기존 프로세스를 정리 - PM2 프로세스 종료.
pm2 kill

# 9. 프로젝트 시작.
npm run start</code></pre>
<hr>
<h1 id="결과">결과</h1>
<ul>
<li>리소스 사용량 개선</li>
<li>배포시간 단축.</li>
</ul>
<p>배포 시 CPU와 MEMORY 사용량이 안정적으로 유지되는 것을 확인할 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/p-samename/post/83634bb9-d869-4d5c-9439-73a98a1c695a/image.png" alt=""></p>
<p>배포 시간 단축
개선 전에는 약 1분 40초가 소요되던 배포가, 개선 후 약 25초로 단축되었다.</p>
<p><img src="https://velog.velcdn.com/images/p-samename/post/4297ca30-c2c3-4efd-a51f-9248fce7ed5e/image.png" alt=""></p>
<p>최근 script 를 개선하기 전과 후의 소요시간 그래프이다.
개선전 1분40초 가량 소요되던 deploy소요 시간이 25초로 감소하여 개선된 것을 확인 할 수 있다.</p>
<p>이렇게 기존의 중복된 작업을 제거하고, 빌드 서버와 배포 서버의 역할을 명확히 분리함으로써 리소스 낭비를 줄이고 효율적인 배포 프로세스를 구성할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[crawling] 구글봇, 크롤링 봇방지]]></title>
            <link>https://velog.io/@p-samename/crawling-%EA%B5%AC%EA%B8%80%EB%B4%87-%ED%81%AC%EB%A1%A4%EB%A7%81-%EB%B4%87%EB%B0%A9%EC%A7%80</link>
            <guid>https://velog.io/@p-samename/crawling-%EA%B5%AC%EA%B8%80%EB%B4%87-%ED%81%AC%EB%A1%A4%EB%A7%81-%EB%B4%87%EB%B0%A9%EC%A7%80</guid>
            <pubDate>Wed, 13 Nov 2024 01:51:07 GMT</pubDate>
            <description><![CDATA[<p>리뷰 서비스 개발 중, 리뷰에 관련한 써드파티 API를 사용하는데 있어서
문서의 내용 중에 구글 봇이 해당 컨텐츠를 크롤링 할 수 없도록 처리해야한다고 명시되어있었다.</p>
<p>크롤링을 처리하는 방법에는 여러가지 방법이 있지만 동적으로 데이터를 로드하거나 User-Agent를 판별하여 호출하는 방법 둘 중 하나를 선택하기로 하였다.</p>
<ol>
<li><p><code>클라이언트</code> 측에서 동적으로 데이터를 로드하도록 설정하여 접근을 방지, 혹은 클라이언트 측에서 이벤트가 발생 했을 때 데이터를 호출하여 크롤링을 방지하는 방법이있다. 하지만 최근에는 JavaScript를 해석하는 크롤러도 많아지고 있어 해당 방법은 사용하지 않았다.</p>
</li>
<li><p><code>User-Agent</code> 를 판별하여 해당 User-Agent가 bot 혹은 crawl 관련이라면 api 호출을 막는 방법이 있는데, 구글 봇은 크롤링할때 User-Agent를 Googlebot으로 요청한다고 명시되어 있기에, 필자는 해당 방법을 선택하도록 하였다.</p>
</li>
</ol>
<p>❗주의해야 할것은 어디까지나 User-Agent는 클라이언트가 서버에 요청할때, 요청헤더를 변경하여 요청할 수 있으므로 User-Agent만을 판별하여 접근을 제어하는 것은 한계가 있다는 것은 꼭! 알아두어야 한다.</p>
<p>필자는  정규식을 사용하여 크롤링을 방지할 요청 헤더의 User-Agent를 명시해 놓고, 해당 정규식에 포함이 된다면 API호출을 하지 않도록 구성하였다.</p>
<pre><code class="language-jsx">
const AgentUtils = {
  // 웹에서 크롤링이 되면 안되는 영역이 있을 경우 user agent 를 판별하여 봇인지를 판별하는 함수.
  detectRobot(userAgent) {
    /**
     * Checks if a given userAgent string belongs to a bot or web crawler.
     * @param {string} userAgent // string type
     * @returns {Number} // return 값은 boolean
     */

    const robots = new RegExp(
      [
        /bot/,
        /crawl/, 
        /APIs-Google/,
        /AdsBot/,
        /Googlebot/, 
      ]
        .map((r) =&gt; r.source)
        .join(&#39;|&#39;),
      &#39;i&#39;,
    );
    return robots.test(userAgent);
  },
};


const getReviewList = async (productIdx) =&gt; {
    const isCrawlingBot = AgentUtils.detectRobot(navigator.userAgent); // User-Agent를 기반으로 봇 탐지
    if (isCrawlingBot) return; // 봇인 경우 API 호출을 중지
    try {
        // 봇이 아닌 경우에만 리뷰 데이터 가져오기
        const { data: reviewResponse } = await axios.get(`/api/reviews?productIdx=${productIdx}`);
        return reviewResponse;
    } catch (error) {
        console.error(&quot;Failed to fetch reviews&quot;, error);
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UX] 점진적 향상법(progressive enhancement)과 우아한 성능저하법(graceful degradation)]]></title>
            <link>https://velog.io/@p-samename/UX-%EC%A0%90%EC%A7%84%EC%A0%81-%ED%96%A5%EC%83%81%EB%B2%95progressive-enhancement%EA%B3%BC-%EC%9A%B0%EC%95%84%ED%95%9C-%EC%84%B1%EB%8A%A5%EC%A0%80%ED%95%98%EB%B2%95graceful-degradation</link>
            <guid>https://velog.io/@p-samename/UX-%EC%A0%90%EC%A7%84%EC%A0%81-%ED%96%A5%EC%83%81%EB%B2%95progressive-enhancement%EA%B3%BC-%EC%9A%B0%EC%95%84%ED%95%9C-%EC%84%B1%EB%8A%A5%EC%A0%80%ED%95%98%EB%B2%95graceful-degradation</guid>
            <pubDate>Thu, 22 Aug 2024 05:07:21 GMT</pubDate>
            <description><![CDATA[<h2 id="점진적-향상법progressive-enhancement과-우아한-성능저하법graceful-degradation">점진적 향상법(progressive enhancement)과 우아한 성능저하법(graceful degradation)</h2>
<p>점진적 향상법(Progressive Enhancement)과 우아한 성능저하법(Graceful Degradation)은 웹 개발에서 사용자 경험을 향상시키거나 다양한 환경에 적응하는 방법론이다. 이 두 개념은 특히 웹 접근성과 다양한 장치, 브라우저, 네트워크 조건에 대한 대응 전략에서 중요하게 다뤄진다.</p>
<h3 id="1-점진적-향상법progressive-enhancement">1. 점진적 향상법(progressive enhancement)</h3>
<p>#기본 기능 우선 #하향식 개발</p>
</div>

<p>점진적 향상은 가능한 많은 사용자에게 <strong>필수 콘텐츠와 기능을 제공</strong>하기 위한 설계 방법이다. 그리고 필요한 모든 코드를 실행할 수 있는 최신 브라우저 사용자에게 최상의 경험을 제공한다.</p>
<p>구현 방식:
기본 레이어: HTML과 같은 기본적인 콘텐츠와 구조를 먼저 구축한다. 이 단계에서는 모든 사용자가 콘텐츠에 접근할 수 있도록 단순하고 기능적인 웹 페이지를 만들어야한다.
스타일 추가: 기본적인 HTML 구조 위에 CSS를 추가하여 디자인과 레이아웃을 향상시킨다.
기능 추가: 마지막으로, JavaScript와 같은 스크립트를 통해 상호작용성과 고급 기능을 추가한다. 이 단계에서 JavaScript가 없는 환경에서도 웹 페이지가 기본적으로 동작할 수 있게 Polyfill을 적절히 사용하여 설계할 수 있도록 해야한다.</p>
<p>핵심 개념:
점진적 향상법은 웹 페이지나 애플리케이션을 기본적인 기능부터 시작해 단계적으로 향상시키는 방법이다. 모든 사용자가 기본적인 콘텐츠와 기능에 접근할 수 있도록 하며, 고급 기능은 더 나은 환경(예: 최신 브라우저, 빠른 네트워크)에서 추가적으로 제공한다.</p>
<p>기본 기능 우선: 모든 사용자가 접근할 수 있는 기본적인 콘텐츠와 기능을 먼저 제공한 후, 더 나은 환경에서 고급 기능을 추가한다.
하향식 개발: 기본 구조에서 시작해 점차 기능을 추가하는 방식이다.</p>
<p>장점:</p>
<ul>
<li>모든 사용자에게 기본적인 접근성을 보장한다.</li>
<li>다양한 환경과 기기에서 일관된 사용자 경험을 제공한다.</li>
<li>향상된 기능이 불필요하거나 사용 불가능한 환경에서는 기본적인 기능만 제공되므로 성능이 최적화된다.</li>
</ul>
<p>ex) HTML에서 기본적인 네비게이션 링크를 제공한 후, JavaScript를 사용하여 드롭다운 메뉴를 추가하는 방식.</p>
<h3 id="2-우아한-성능저하법graceful-degradation">2. 우아한 성능저하법(graceful degradation)</h3>
<p>#고급 기능 우선 #상향식 개발</p>
<p>우아한 성능 저하는 <strong>최신 브라우저에서 동작하는 웹 사이트/응용 프로그램 구축</strong>에 주력하는 설계 방법이다. 그러나 오래된 브라우저에서는 비록 좋지 않은 경험이라 할 지라도 필수 콘텐츠와 기능을 여전히 제공한다.</p>
<p>구현 방식:
고급 기능 중심 개발: 최신 브라우저와 최신 기술을 사용하여 고급 기능을 먼저 개발한다.
기능 저하 처리: 기능이 지원되지 않는 구형 브라우저나 환경에서는 최소한의 기능과 콘텐츠를 제공하도록 하여, 완전한 비정상 상태로 가지 않도록 설계한다.</p>
<p>핵심 개념:
우아한 성능저하법은 고급 기능을 가진 웹 사이트나 애플리케이션을 개발한 다음, 그 기능이 지원되지 않는 환경에서도 중요한 콘텐츠와 기본 기능을 제공할 수 있도록 하는 방법이다. 최신 브라우저나 빠른 네트워크를 위한 고급 기능이 중심이 되고, 이를 지원하지 않는 환경에서는 성능이 저하되더라도 사이트가 동작하도록 한다.</p>
<p>고급 기능 우선: 최신 브라우저와 고급 기능을 먼저 개발한 후, 기능이 지원되지 않는 환경에서도 최소한의 기능을 유지하도록 한다.
상향식 개발: 고급 기능을 구현한 후, 이를 지원하지 않는 환경에서 기능이 잘 작동하지 않더라도 문제를 최소화하는 방식이다.</p>
<p>장점:</p>
<ul>
<li>최신 기술과 기능을 활용한 고급 사용자 경험을 제공할 수 있다.</li>
<li>기능이 제한된 환경에서도 중요한 콘텐츠 접근성을 유지한다.</li>
</ul>
<p>ex) 최신 브라우저에서는 애니메이션과 고급 인터랙션을 제공하지만, 오래된 브라우저에서는 이러한 기능을 비활성화하고 기본적인 텍스트 콘텐츠만 제공하는 방식.</p>
<h3 id="요약">요약</h3>
<p><strong>점진적 향상법</strong>은 모든 사용자가 접근할 수 있는 기본 기능을 먼저 제공하고, 환경에 따라 고급 기능을 추가하는 방식이다.
<strong>우아한 성능저하법</strong>은 최신 기술을 중심으로 고급 기능을 개발하고, 기능이 지원되지 않는 환경에서 핵심 기능만 동작하도록 만드는 방법이다.</p>
<p>공통점: 두 방법론 모두 다양한 환경에서 핵심기능에 대한 사용자 경험과 접근성을 제공하려는 목적을 가지고 있다.
차이점: 점진적 향상은 기본 기능에서 출발해 추가 기능을 더하는 방식이고, 우아한 성능 저하는 고급 기능을 먼저 개발하고 이를 지원하지 않는 환경에서도 최소한의 기능을 제공하려는 방식이다.</p>
<hr>
<h3 id="참고">참고</h3>
<p> MDN Web Docs
<a href="https://developer.mozilla.org/ko/docs/Glossary/Graceful_degradation">https://developer.mozilla.org/ko/docs/Glossary/Graceful_degradation</a>
<a href="https://developer.mozilla.org/ko/docs/Glossary/Progressive_Enhancement">https://developer.mozilla.org/ko/docs/Glossary/Progressive_Enhancement</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[hoisting, scope] 호이스팅과 스코프]]></title>
            <link>https://velog.io/@p-samename/hoisting-scope-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85%EA%B3%BC-%EC%8A%A4%EC%BD%94%ED%94%84</link>
            <guid>https://velog.io/@p-samename/hoisting-scope-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85%EA%B3%BC-%EC%8A%A4%EC%BD%94%ED%94%84</guid>
            <pubDate>Mon, 19 Aug 2024 08:51:20 GMT</pubDate>
            <description><![CDATA[<h2 id="호이스팅과-스코프">호이스팅과 스코프</h2>
<h3 id="호이스팅">호이스팅</h3>
<p>호이스팅은 JavaScript의 실행 컨텍스트에서 변수와 함수 선언이 코드 실행 전에 해당 스코프의 최상단으로 끌어올려지는 것처럼 보이는 현상이다. 중요한 점은 선언만 끌어올려지고, 초기화는 끌어올려지지 않는다는 것이다.</p>
<p>JavaScript에서 변수를 선언할 때 사용하는 주요 키워드에는 var, let, const가 있다.</p>
<h3 id="스코프scope">스코프(Scope)</h3>
<p>스코프란 변수가 유효한 범위를 정의한다. JavaScript에는 두 가지 주요 스코프가 있다.</p>
<ul>
<li>전역 스코프(Global Scope): 스크립트 전체에서 접근할 수 있는 변수.</li>
<li>지역 스코프(Local Scope): 특정 함수나 블록 내에서만 유효한 변수.</li>
</ul>
<p>var는 함수 스코프를 따르고, let과 const는 블록 스코프를 따른다.</p>
<h3 id="var">var</h3>
<pre><code class="language-js">console.log(x); // undefined
var x = 5;
console.log(x); // 5</code></pre>
<p>첫째줄의 변수 x 는 undefined가 반환된다. </p>
<p>var로 선언된 변수는 호이스팅 시 선언과 함께 undefined로 초기화된다.
그래서 변수 선언 전에 변수 x 를 출력하면 에러 없이 undefined가 출력된다. 나중에 x에 값이 할당되면 그 이후의 호출에서는 할당된 값이 반환됩니다.</p>
<h3 id="let--const">let &amp; const</h3>
<pre><code class="language-js">console.log(x); // ReferenceError: Cannot access &#39;x&#39; before initialization
let x = 5;
console.log(x); // 5</code></pre>
<p>let과 const로 선언된 변수도 호이스팅은 되지만, var와 다르게 초기화되지 않는다.
선언은 스코프의 최상단으로 끌어올려지지만, 초기화는 실제 선언문에 도달했을 때 이루어진다.
그 사이에 있는 영역은 <strong>TDZ(Temporal Dead Zone, 일시적 사각지대)</strong>에 속하는데, 이 구간에서는 변수를 참조할 수 없기 때문에 ReferenceError가 발생한다.</p>
<br>

<p>호이스팅을 시각적인 관점에서 간단하게 정리하자면,</p>
<ul>
<li>var: 선언과 함께 초기화(undefined) → console.log 호출 시 undefined가 반환된다.</li>
<li>let과 const: 선언만 호이스팅, 초기화는 선언 시점에서 → console.log 호출 시 ReferenceError가 발생한다.</li>
</ul>
<p>이후 포스팅에서는 호이스팅, 스코프와 밀접한 관련이있는 실행 컨텍스트에 대해 포스팅하도록 하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[js] 함수의 종류(선언문,표현식,화살표,즉시실행 등...)]]></title>
            <link>https://velog.io/@p-samename/js-%ED%95%A8%EC%88%98%EC%9D%98-%EC%A2%85%EB%A5%98%EC%84%A0%EC%96%B8%EC%9D%B5%EB%AA%85%EB%8C%80%EC%9E%85%ED%98%95%EC%A6%89%EC%8B%9C%EC%8B%A4%ED%96%89-%EB%93%B1</link>
            <guid>https://velog.io/@p-samename/js-%ED%95%A8%EC%88%98%EC%9D%98-%EC%A2%85%EB%A5%98%EC%84%A0%EC%96%B8%EC%9D%B5%EB%AA%85%EB%8C%80%EC%9E%85%ED%98%95%EC%A6%89%EC%8B%9C%EC%8B%A4%ED%96%89-%EB%93%B1</guid>
            <pubDate>Wed, 14 Aug 2024 07:38:01 GMT</pubDate>
            <description><![CDATA[<p>서비스 운영 및 협업을 하면서, 사람마다 함수를 사용하는 스타일이 달라 선언형 함수, 표현형 함수, 화살표 함수 등이 의미없이 섞여있어 문득 각각의 차이점을 정확히 이해하고 사용하고 있을까 라는 의문이 들었다. </p>
<p>이참에 나도 놓치고 있는부분이 있지는 않을까 짚고 넘어가기 위해, 또한 팀원들과의 공유를 위해 해당 게시글을 작성하게 되었다.</p>
<h2 id="함수의-종류">함수의 종류</h2>
<p>javascript에서의 함수의 종류에 대해 정리하려고 한다.</p>
<h3 id="함수-선언문-function-declaration">함수 선언문 (Function Declaration)</h3>
<p>function 함수이름( ) { code... }</p>
<p>함수 선언문은 *호이스팅되므로, 선언 위치에 상관없이 코드에서 사용할 수 있다.
즉, 선언 보다 호출을 먼저 하여도 정상적으로 잘 작동한다.
함수 선언문은 함수 자체가 *호이스팅되어 초기화되므로, 선언 이전에 호출할 수 있다.</p>
<p>*호이스팅에 관한 내용은 js의 실행 컨텍스트와 함께 추후 포스팅하도록 할 예정이다.</p>
<pre><code class="language-js">function func() {
  console.log(&#39;Hello!&#39;);
}

---------------------------------------

// 호이스팅 예제
func() // Hello!

function func() {
  console.log(&#39;Hello!&#39;);
}</code></pre>
<br>

<h3 id="함수-표현식-function-expression">함수 표현식 (Function Expression)</h3>
<p>const 함수이름 = function() { code... }</p>
<p>함수 표현식은 변수나 상수에 함수가 할당되는 시점에 정의된다. func는 호이스팅되지만, 초기화 이전에는 사용할 수 없다. 아래의 상황에서는 func의 초기화는 선언시점에 되기때문에 선언 전에는 해당 변수를 참조할 수 없다. 때문에 참조에러가 발생한다.
위에서 말했지만 이후 포스팅에서 *호이스팅에 관해 다룰 예정이기에 간단하게 넘어가도록 하겠다.</p>
<pre><code class="language-js">const func = function() {
  console.log(&#39;Hello!&#39;);
}

---------------------------------------
// 호이스팅 예제
func() // ReferenceError: func is not defined

const func = function() {
  console.log(&#39;Hello!&#39;);
}</code></pre>
<br>

<h3 id="화살표-함수-arrow-function">화살표 함수 (Arrow Function)</h3>
<p>const 함수이름 = () =&gt; { code... }</p>
<p>간결한 구문을 제공하며, this를 바인딩하지 않는다. 주로 짧은 콜백 함수나 간단한 로직을 작성할 때 유용하다. 화살표 함수는 생성자로 사용할 수 없다.</p>
<p>또한 일반함수의 표현식과 동일하게 선언이 먼저 호이스팅되고 이후에 코드실행에서 초기화되므로, 선언전에 함수가 호출 될 수 없다.</p>
<pre><code class="language-js">const func = () =&gt; {
  console.log(&#39;Hello!&#39;);
};</code></pre>
<br>

<h3 id="즉시-실행-함수-iife-immediately-invoked-function-expression">즉시 실행 함수 (IIFE, Immediately Invoked Function Expression)</h3>
<p>(function() { code... })()
정의와 동시에 실행되는 함수로, 전역 스코프를 오염시키지 않고 코드를 실행할 수 있다.</p>
<pre><code class="language-js">(function() {
  console.log(&#39;즉시 실행 함수 !!&#39;);
})(); // &#39;즉시 실행 함수 !!&#39;</code></pre>
<br>

<h3 id="생성자-함수-constructor-function">생성자 함수 (Constructor Function)</h3>
<p>function 함수이름() { code... }
new 키워드를 사용해 객체를 생성할 때 사용된다. 생성자 함수는 주로 대문자로 시작한다.</p>
<p>생성자 함수는 ES6(ECMAScript 2015)에서 클래스 문법이 생기며, 잘 사용하지 않게 되었지만, 일부 레거시 시스템과의 호환성을 유지하기 위해서 사용될 수 있기에 정리했다.</p>
<pre><code class="language-js">function User(name, age) {
  this.name = name;
  this.age = age;
}
const kitty = new User(&#39;kitty&#39;, 30);</code></pre>
<br>

<h3 id="메서드-method">메서드 (Method)</h3>
<p>객체의 속성으로 정의된 함수.
객체 내부에서 정의된 함수로, 객체의 속성으로 동작한다.</p>
<pre><code class="language-js">const obj = {
  greet: function() {
    console.log(&#39;Hello!&#39;);
  }
};
obj.greet(); // Hello!</code></pre>
<br>


<p>함수의 종류를 크게 나누어 두었지만 각각의 함수의 장단점이 있고, 특징이 있다. 상황에 따라 알맞는 형식의 함수를 사용할 수 있도록 정확히 이해하고 사용하고자 해당 내용을 정리하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ESLint] 규칙 설정]]></title>
            <link>https://velog.io/@p-samename/ESLint-%EA%B7%9C%EC%B9%99-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@p-samename/ESLint-%EA%B7%9C%EC%B9%99-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Thu, 27 Jun 2024 05:46:27 GMT</pubDate>
            <description><![CDATA[<h3 id="eslint란-">ESLint란 ?</h3>
<p>ESLint는 EcmaScript(javascript) 와 Lint를 합친 것으로 여기서 Lint는 에러가 있는 코드에 표시를 달아놓는 것을 의미한다.
즉 ESLint는 자바스크립트 문법에서 에러가 발생하면 표시해주는 도구이다.</p>
<p>ES는 EcmaScript로서, Ecma라는 기구에서 만든 Script, 즉, 표준 <strong>Javascript</strong>를 의미한다.
Lint는 에러가 있는 코드에 표시를 달아놓는 것을 의미한다.</p>
<p>이뿐만 아니라 전반적인 코딩스타일까지 지정할 수 있으므로 <strong>협업</strong>에 유용하다.</p>
<br/>


<p>기존 프론트의 소스가 많아지고 프로젝트가 점점 커지면서 코드의 통일성이 점점 없어지고 있었다.
코드 통일성을 위해 <label style='background:blue;border-radius:8px;padding:0 4px'><strong>ESLint</strong></label> 설정을 적용하기로 하였다.</p>
<p>기본적으로는** airbnb와 airbnb/hooks**를 따르되, 프로젝트와 맞게 커스텀한 설정을 추가해주기로 하였다.</p>
<h3 id="설정값">설정값</h3>
<table>
    <thead>
    <tr>
    <th>속성명</th>
    <th>속성 설명</th>
    <th>변경 사유</th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td>"extends": ["airbnb", "airbnb/hooks", "next", "prettier"]</td>
    <td>프론트에서 가장 많이 사용하는 airbnb와 airbnb/hooks, prettier 적용</td>
    <td>코드 스타일을 린트를 통해 어느정도 맞추고, ESLint와 prettier 간 충돌을 방지하기 위해 prettier 확장함.</td>
    </tr>
    <tr>
    <td>"import/no-unresolved": [2, { "ignore": ["^@"] }]</td>
    <td>import 또는 require시 해당 모듈 파일을 파일 시스템에서 못 찾을 때 에러를 띄워줌</td>
    <td>절대 경로를 추적하지 못해 절대 경로에 대해서만 비활성화 처리</td>
    </tr>
    <tr>
    <td>"radix": ["error", "as-needed"]</td>
    <td>parseInt의 두 번째 인자인 진수를 명시해 혼란을 방지하는 목적의 옵션</td>
    <td>parseInt는 첫 번째 인자에 “0x”가 prefix로 붙는지에 따라 10진수, 16진수를 판단해 처리하기 때문에 필요 없을 것 같다고 판단되어, 유효하지 않은 문법으로 사용하는 경우에만 에러를 띄울 수 있도록 해당 값 지정</td>
    </tr>
    <tr>
    <td>"no-nested-ternary": "warn”</td>
    <td>삼항 연산자 중첩 사용 방지</td>
    <td>가독성에 안 좋긴 하나 상황에 따라 한 번의 중첩 정도는 괜찮다고 판단하여, warn으로 설정</td>
    </tr>
    <tr>
    <td>"react/jsx-filename-extension": "warn”(추후 변경예정)</td>
    <td>파일 내에서 컴포넌트를 사용하는데 js 확장자를 가진 경우 에러</td>
    <td>일부 유틸 함수에서 UI 로직을 반환하고 있어 warn 으로 설정</td>
    </tr>
    <tr>
    <td>"react/jsx-boolean-value": "warn"(추후 변경예정)</td>
    <td>boolean 타입의 props 전달 시 true / false를 명시적으로 전달하지 않는 것</td>
    <td>좋다고 생각되나 당장 도입하기엔 수정해야 할 컴포넌트들이 많고, 코드 스타일을 정해야하므로 이에 대해 논의 후 활성화</td>
    </tr>
    <tr>
    <td>"jsx-a11y/no-noninteractive-element-interactions": "warn"(추후 변경예정)</td>
    <td>상호작용 용도가 아닌 요소에 이벤트를 붙인 경우</td>
    <td>예외 케이스가 있을수도 있는 부분이라 판단되어 warn으로 설정</td>
    </tr>
    <tr>
    <td>"jsx-a11y/no-static-element-interactions": "warn”(추후 변경예정)</td>
    <td>div, span과 같은 시맨틱하지 않은 요소에 이벤트를 붙이면 스크린 리더가 해당 요소의 role을 인식할 수 없어서 이를 방지하는 옵션</td>
    <td>접근성을 지키는 것이 좋긴 하나, 이에 대해 팀원 전체가 추가적인 학습 필요 및 일관성 등 여러 부분이 관련되어 추후 활성화 예정</td>
    </tr>
    <tr>
    <td>"jsx-a11y/control-has-associated-label": "warn”</td>
    <td>button과 같은 대화형 요소에 aria-label등의 속성 값 명시를 강제하는 옵션</td>
    <td>당장 모든 곳에 적용하기에는 어려움이 있어 warn으로 설정</td>
    </tr>
    <tr>
    <td>"camelcase": "warn"</td>
    <td>카멜케이스 외 이름이 작명된 경우</td>
    <td>일부 백엔드 API, queryParameter (구글 접근) 등 프론트에서 제어할 수 없는 부분도 있기 때문에 warn으로 설정</td>
    </tr>
    <tr>
    <td>"no-console": ["warn", { "allow": ["warn", "error"] }]</td>
    <td>production 환경에서 console이 포함되는 것을 방지하는 옵션</td>
    <td>별도 프론트용 로깅 시스템을 사용하고 있지 않기 때문에 실시간 로그 확인을 하고 있어 console.warn, console.error는 허용, 그 외는 warn으로 표시되도록 처리</td>
    </tr>
    <tr>
    <td>"one-var": "off"</td>
    <td>다수의 변수 선언 시 키워드를 한번 혹은 여러번으로 사용하는 것을 강제하는 옵션</td>
    <td>불필요하다고 판단</td>
    </tr>
    <tr>
    <td>"import/order": "off”(추후 변경예정)</td>
    <td>import 구문의 순서 및 구분에 관한 옵션</td>
    <td>어떻게 구분할지에 대한 논의 후 적용 필요</td>
    </tr>
    <tr>
    <td>"no-useless-constructor": "off"</td>
    <td>class 내 constructor 생략 가능시 명시에 대한 옵션</td>
    <td>현재는 명시하고 있음</td>
    </tr>
    <tr>
    <td>"max-classes-per-file": "off”</td>
    <td>파일 당 클래스를 몇 개 선언할 수 있는지에 대한 옵션</td>
    <td>현재는 굳이 이에 대해 제한을 두고 있지 않음</td>
    </tr>
    <tr>
    <td>"class-methods-use-this": "off”</td>
    <td>클래스의 메소드 내부에서 무조건 this를 명시해야 하는 옵션</td>
    <td>this를 사용하지 않는 메소드라도 경고가 나타나기 때문에 off, 객체지향적 관점에서 필요하다고 생각은 되지만, 도입에 대해서 논의 필요</td>
    </tr>
    <tr>
    <td>"operator-assignment": "off”</td>
    <td>shorthand 연산자를 사용할 수 있다면 사용을 강제하는 옵션</td>
    <td>가독성을 지키기 위해 사용이 불필요한 경우도 있을 수 있다고 판단됨</td>
    </tr>
    <tr>
    <td>"import/prefer-default-export": "off”</td>
    <td>모듈 파일에 export default가 무조건 존재해야 된다고 강제하는 옵션</td>
    <td>필요없다고 판단</td>
    </tr>
    <tr>
    <td>"no-underscore-dangle": "off"</td>
    <td>네이밍에 언더바를 사용하지 못하도록 강제하는 옵션</td>
    <td>언더바를 기피하는게 좋은 방향이라고 생각되지만 당장 적용하기엔 논의가 필요함</td>
    </tr>
    <tr>
    <td></td>
    <td></td>
    <td></td>
    </tr>
    <tr>
    <td>"prefer-template": "off”</td>
    <td>템플릿 리터럴 문법을 강제하는 옵션</td>
    <td>가독성을 지키기 위해 사용이 불필요한 경우도 있을 수 있다고 판단됨</td>
    </tr>
    <tr>
    <td>"no-alert": "off”</td>
    <td>alert, confirm, prompt 사용시 에러</td>
    <td>빌트인 기능들을 사용하고 있음</td>
    </tr>
    <tr>
    <td>"prefer-destructuring": "off”</td>
    <td>배열, 객체의 요소에 접근할 때 구조 분해 할당을 강제하는 옵션</td>
    <td>상황에 따라 다를 수 있다고 판단</td>
    </tr>
    <tr>
    <td>"no-useless-concat": "off”</td>
    <td>문자열끼리 붙일 때 연산자로 붙이지 않도록 하는 옵션</td>
    <td>가독성을 위해 필요한 경우도 있다고 판단</td>
    </tr>
    <tr>
    <td>"consistent-return": "off”</td>
    <td>함수 내 return을 필수로 명시하도록 강제하는 옵션</td>
    <td>일관성이 있어 좋을 순 있으나 이에 대해 논의 필요</td>
    </tr>
    <tr>
    <td>"func-names": "off”</td>
    <td>함수를 사용할 때 함수명을 필수로 명시하도록 하는 옵션</td>
    <td>IIFE 나 변수에 할당되는 함수의 경우 필요하지 않다고 판단</td>
    </tr>
    <tr>
    <td>"no-plusplus": "off”</td>
    <td>증감 연산자 사용을 방지하는 옵션</td>
    <td>필요하지 않다고 판단</td>
    </tr>
    <tr>
    <td>"no-shadow": "off”</td>
    <td>외부 스코프의 변수와 같은 이름의 변수를 사용하지 못하도록 하는 옵션</td>
    <td>필요하지 않다고 판단</td>
    </tr>
    <tr>
    <td>"no-restricted-globals": "off”</td>
    <td>지정된 전역 변수에 직접 접근하는 것을 방지하는 옵션</td>
    <td>필요하지 않다고 판단</td>
    </tr>
    <tr>
    <td>"react/button-has-type": "off”</td>
    <td>버튼 요소 사용 시 type을 필수로 명시하도록 하는 옵션</td>
    <td>필요하지 않다고 판단</td>
    </tr>
    <tr>
    <td>"react/no-unstable-nested-components": "off”</td>
    <td>컴포넌트 내부에 불안정한 컴포넌트가 HOC 로 들어가는 경우</td>
    <td>학습 필요 후 필요하면 활성화</td>
    </tr>
    <tr>
    <td>"react/jsx-no-bind": "off”</td>
    <td>props로 함수를 넘기는 경우 this를 붙이도록 강제하는 옵션</td>
    <td>학습 필요 후 필요하면 활성화</td>
    </tr>
    <tr>
    <td>"react/require-default-props": "off”</td>
    <td>Component.defaultProps를 강제하는 옵션</td>
    <td>필요하지 않다고 판단 (추후 TS를 도입하게 되면 더욱 필요가 없다고 판단됨)</td>
    </tr>
    <tr>
    <td>"react/function-component-definition": "off”(추후 변경예정)</td>
    <td>함수형 컴포넌트를 정의 할 때에 관한 옵션</td>
    <td>일관성을 위해 해야 할 필요는 있으나, 어떤 것을 채택할지에 대한 논의가 필요</td>
    </tr>
    <tr>
    <td>"react/jsx-no-constructed-context-values": "off”</td>
    <td>Context API에 값을 전달할 때 useMemo를 사용하지 않고 넘기는 경우 방지</td>
    <td>학습 필요</td>
    </tr>
    <tr>
    <td>"react/jsx-props-no-spreading": "off”</td>
    <td>props를 넘길 때 가독성 및 props 파악이 용이하도록 전개 구문을 사용하지 않도록 강제하는 것</td>
    <td>필요하지 않다고 생각함. (현재 PropsTypes를 통해 명시하고 있기도 하고, TS 도입하면 더욱 필요가 없음.)</td>
    </tr>
    <tr>
    <td>"react/no-array-index-key": "off”</td>
    <td>컴포넌트의 키로 인덱스를 넣는 것을 방지하는 역할</td>
    <td>이해하고 사용하고 있기에 문제가 없다고 판단</td>
    </tr>
    <tr>
    <td>"react/destructuring-assignment": "off"</td>
    <td>함수형 컴포넌트의 매개변수 위치에서 바로 구조 분해 할당을 금지하는 옵션</td>
    <td>필요하지 않다고 판단</td>
    </tr>
    <tr>
    <td>"jsx-a11y/click-events-have-key-events": "off”(추후 변경예정)</td>
    <td>비대화형 요소(div, span)에 onClick 이벤트가 있는 경우,  onKeyUp, onKeyDown, onKeyPress도 같이 할당해주는 것</td>
    <td>활성화하면 좋으나 "jsx-a11y/no-noninteractive-element-interactions”, "jsx-a11y/no-static-element-interactions” 옵션과도 상관관계가 있어 이들과 함께 같이 변경</td>
    </tr>
    </tbody>
    </table>

]]></description>
        </item>
        <item>
            <title><![CDATA[[js] 날짜 - new Date() issue]]></title>
            <link>https://velog.io/@p-samename/js-%EB%82%A0%EC%A7%9C-new-Date-issue</link>
            <guid>https://velog.io/@p-samename/js-%EB%82%A0%EC%A7%9C-new-Date-issue</guid>
            <pubDate>Thu, 02 May 2024 08:38:35 GMT</pubDate>
            <description><![CDATA[<p>얼마 전 , 해외에서 우리 서비스를 접속 하였을 때 backend 에서 내려주는 날짜 데이터와 클라이언트에게 노출되는 날짜가 다르다는 이슈가 있었다. </p>
<p>backend 에서 날짜 형식을 yyyy-MM-dd string 형식으로 내려주는데 해당 데이터를 new Date() 의 인자로 그대로 넣어주었기에 생기는 문제였다.</p>
<p>코드로 예시를 들면 다음과 같다.
ex) 시스템 설정의 표준 시간대를 GMT-1200 시간대로 변경하였을 때.</p>
<pre><code class="language-js">const date = new Date(&#39;2024-05-02&#39;); // Wed May 01 2024 12:00:00 GMT-1200 (GMT-12:00)

const date = new Date(2024, 4, 2); // Thu May 02 2024 00:00:00 GMT-1200 (GMT-12:00)</code></pre>
<p>new Date() 의 인자로 string 형태로 날짜를 넣었을 때, 24년 5월 2일 자로 넣었지만 5월 1일을 반환한다.</p>
<p>하지만 인자로 number 를 넣었을때, 5월 2일자로 잘 반환한다.
<br/></p>
<p>해당 문제를 해결하기 위해 yyyy-MM-dd 형식으로 받아온 데이터를 년 , 월 , 일로 나누어주고 number 형태로 new Date()를 생성해주기로 하였다.</p>
<pre><code class="language-js">const fullDate = &#39;2024-05-02&#39;

const [year, month, day] = fullDate.split(&#39;-&#39;).map(Number); // [&#39;2024&#39;,&#39;05&#39;,&#39;02&#39;] &gt;&gt; [2024,5,2]

// const date = new Date(fullDate) // Wed May 01 2024 12:00:00 GMT-1200 (GMT-12:00) ❌
const date = new Date(year, month - 1, day) // month는 0부터 시작하기에 1월 이라면 인자로 0을, 2월 이라면 1을 넣어주어야 하기에 -1 을 해준다. ⭕

console.log(date) // Thu May 02 2024 00:00:00 GMT+0900 (한국 표준시) </code></pre>
<p>fullDate 를 string 형태로 받아와 &#39;-&#39; 를 기준으로 split 해준 후, 구조분해 하여 첫번째 요소를 year 두번째 요소를 month 세번째 요소를 day에 할당한다. </p>
<p>혹은 </p>
<pre><code class="language-js">// const date = new Date(&#39;2024-05-02&#39;) // Wed May 01 2024 12:00:00 GMT-1200 (GMT-12:00)
const date = new Date(&#39;2024-05-02&#39;).toISOString() // 2024-05-02T00:00:00.000Z

date.split(&#39;T&#39;)[0] // 2024-05-02</code></pre>
<p>위의 방법을 이용하여도 될 것 같지만 개인적으로 전자의 방식을 사용하여 해당 이슈를 수정하였다.</p>
<p>위의 방법 외에도 다양한 방법이 있으나, 개인차에 따라, 혹은 프로젝트의 상황에 맞게끔 방식을 선택하여 사용하면 될 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 브라우저 (Web browser) - 메모리 & 디스크 캐시 (cache) ]]></title>
            <link>https://velog.io/@p-samename/%EC%9B%B9-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-Web-browser-%EC%BA%90%EC%8B%9C-cache</link>
            <guid>https://velog.io/@p-samename/%EC%9B%B9-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-Web-browser-%EC%BA%90%EC%8B%9C-cache</guid>
            <pubDate>Wed, 03 Apr 2024 02:00:37 GMT</pubDate>
            <description><![CDATA[<h2 id="캐시-cache">캐시 (cache)</h2>
<p><code>캐시</code>란 데이터에 빠르게 접근하기 위해 자주 사용되는 데이터나 값을 미리 복사해 놓는 임시 장소를 의미한다.</p>
<p>웹 브라우저에서 네트워크를 통해 서버에 데이터를 요청하게 되면, 해당 서버가 응답을 반환할 때까지 페이지가 로드되지 않는다.
페이지 로딩에 필요한 css, js, html, 이미지 등의 정적 리소스를 캐싱하여 사용하게 되면, 요청을 보내는 네트워크 요청 횟수를 줄일 수 있을 뿐만 아니라 서버 응답을 기다려야 할 필요가 없기 때문에 사용자에게 보다 빠르게 화면을 보여줄 수 있다.
이러한 개념을 <code>웹 캐싱</code>이라고 한다.
<br></p>
<h3 id="캐시의-종류">캐시의 종류</h3>
<p> - 캐시는 클라이언트에 보관할 수도 있고,  클라이언트와 서버 사이에 존재하는 프록시 서버 등의 중개 서버에서 보관할 수도 있다. </p>
<p>1) 클라이언트 측이 보관하는 것이 개인 캐시 (사설 캐시, 브라우저 캐시, private cache, browser cache) 이다. (Chrome 기준 disk cache, memory cache)
 
2) 서버에서 보관하는 것이 공용 캐시 (공유 캐시, shared cache, public cache)이다. (대표적인 예시로는 CDN)
 - HTTP 헤더를 통해 Private 캐시, Public 캐시 별 설정을주기를 의도적으로 가져갈 수 있다.
ex) 보안상 Public 캐시에 저장하지 않아야되는 케이스,  다르게 가져가는 케이스 등...
<br></p>
<p>HTTP 캐시는 2가지 종류로 나뉘어진다.</p>
<ul>
<li>메모리 캐시</li>
<li>디스크 캐시</li>
</ul>
<p><code>디스크 캐시</code>는 하드 디스크 드라이브(HDD)에 저장되는 데이터의 임시 복사본이다. 웹 브라우저는 사용자가 방문한 웹 페이지의 리소스(이미지, CSS 파일, JavaScript 파일 등)를 디스크 캐시에 저장하여 나중에 동일한 페이지에 다시 방문할 때 해당 리소스를 다시 다운로드할 필요 없이 빠르게 불러올 수 있다. 이는 페이지 로딩 시간을 줄이고 대역폭을 절약하는 데 도움이 된다. 디스크 캐시는 일반적으로 하드 디스크의 일부 공간을 할당받아 관리되며, 운영 체제 또는 브라우저 설정을 통해 크기를 조정할 수 있다.</p>
<p><code>메모리 캐시</code>는 웹 브라우저가 사용자의 시스템 RAM (Random Access Memory)에 저장하는 데이터의 임시 복사본입니다. 메모리 캐시는 더 빠른 액세스 속도를 제공하기 때문에 디스크 캐시보다 성능이 우수합니다. 웹 브라우저는 주로 사용자가 방문한 웹 페이지의 리소스를 메모리 캐시에 저장하여 더 빠르게 로드할 수 있도록 합니다. 메모리 캐시의 크기는 사용자 시스템의 사용 가능한 RAM에 따라 달라지며, 일반적으로 브라우저가 관리합니다. 브라우저가 종료되면 메모리 캐시에 저장된 데이터는 사라지므로 다시 다운로드해야 합니다.</p>
<p><img src="https://velog.velcdn.com/images/p-samename/post/2f815e8d-b9ce-468a-9a7d-225eebb4fced/image.png" alt=""></p>
<p>웹사이트에 처음 접속했을때의 네트워크 상태이다. 이후에는 css 파일들이 캐싱되는 것을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/p-samename/post/d891d572-c2ce-48c3-8538-e11f4fadf2e7/image.png" alt=""></p>
<h3 id="캐시-제어-헤더-종류">캐시 제어 헤더 종류</h3>
<p>Cache-Control 헤더
 - HTTP/1.1부터 명시적으로 캐시를 제어할 수 있는 헤더를 추가했는데 그것이 cache-control 헤더 이다.
 - 캐시의 유효 시간(생명 주기)과 관련된 내용을 명시하는 헤더
  <br></p>
<p><code>no-cache</code></p>
<ul>
<li>request 헤더 : 캐싱된 응답을 받지 않고 이 요청을 원본 서버에 그대로 전달 해 원본 서버로부터 항상 최신의 응답을 받겠다.</li>
<li>response 헤더 : 캐싱된 데이터를 항상 유효한지 확인하도록 강제하는 설정이다.
캐싱을 하긴 하지만 원본 서버에는 항상 갔다 오게된다. 캐싱을 안하는건 아니다. 실제로 아무 캐시도 저장하지 않도록 하기 위해서는 no-store로 설정해주어야 한다.</li>
</ul>
<p><code>no-store</code></p>
<ul>
<li>request, response 모두 동일하게 캐시를 사용하지 않겠다 라는 의미로 사용한다.</li>
<li>no-store는 로컬에 저장하는 것 자체를 금지한다.</li>
<li>데이터에 민감한 정보가 포함되어 있어 저장 불가 하도록 처리할때 사용한다. 
 
<code>max-age</code></li>
<li>캐시 유효 시간, 초 단위 설정</li>
<li>예를 들어 max-age=60 은 1분간 캐싱한다는 내용이다.</li>
<li>다만, max-age 60초가 지났다고 해서 해당 리소스에 대한 캐시를 완전 버리지 않는다. 
   서버에 한번 물어보고(조건부 요청), 리소스 변경이 없다면 해당 캐싱된 리소스를 그대로 사용한다.</li>
<li>흔히, 캐싱되지 않아야 하는 민감한 리소스에 대해 응답헤더에 Cache-Control:max-age=0을 사용한다.
 
<code>Age</code></li>
<li>Origin Server 의 응답이 프록시 캐시 서버에 머문 시간, 초 단위(sec).
 
<code>expire</code> </li>
<li>max-age는 유효기간을 초단위로 설정 하였지만 expire는 만료일자를 지정한다. 정확한 날짜를 지정하여야 한다.</li>
<li>Cache-Control 헤더에도 max-age로 유효 시간을 명시하는 것이 더 추천되기 때문에, 현재는 사용이 권장 되지 않고 하위 호환을 위해 사용된다.</li>
<li>만일 max-age와 동시에 사용되면 Expires는 무시된다.
 
<code>public</code></li>
<li>응답 헤더에 사용할 수 있다.  </li>
<li>모든 캐시 서버에 캐시될 수 있고 사용자 제한 없이 모든 사용자에게 응답이 전달될 수 있다.
 
<code>private</code></li>
<li>public 캐시에 저장 불가</li>
<li>최종 사용자 개인의 브라우저 환경에서만 캐싱을 하고, 외부 캐시서버(CDN 등의 범용 캐시 서버)에서는 캐싱할 수 없다. 
 ex) 로그인을 해서 내 정보가 들어있는 페이지 접근시, 내 브라우저가 아닌 만약 다른 외부서버에까지 내 정보가 캐싱되는 경우를 방지해 준다. 응답 메시지를 어디에 캐시할 것인지 지정하기 위해 사용할 뿐, 그 자체로 개인정보를 보호하는 장치가 되지는 않는다. 
 
<code>s-maxage</code></li>
<li>공유 캐시에 해당하는 캐시에만 적용되는 수명이며, 개인 캐시에서는 무시. </li>
<li>헤더의 age 필드와 비교해서 캐시의 유효성을 판단한다.
 
<code>must-revalidate, proxy-revalidate</code></li>
<li>must-revalidate : 캐시 만료후 최초 조회시 Origin Server 에 검증</li>
<li>proxy-revalidate : 위의 내용을 공유(public) 캐시에만 적용한다.
 
<code>ETag</code></li>
<li>HTTP 응답 헤더로 리소스의 특정 버전에 대한 식별자라고 하며, 원본 서버가 리소스를 식별하기 위해 부여하는 고유 번호 이다.</li>
<li>ETag는 임의의 문자들이 따옴표 안에 포함되도록 하며, 이 값은 원본서버에서 결정한다. 
 
<code>Pragma</code></li>
<li>HTTP/1.0 하위 호환을 위해 사용하는 캐시 제어 헤더 이다.  - Cache-Control과 동일한 역할을 수행하지만 권장되지 않는다.
 
<code>ETC</code> </li>
<li>크롬 브라우저를 기준으로 웹 브라우저는 크게 2가지 방법으로 캐싱을 한다. (브라우저 자체 알고리즘에 따라 다르다.)</li>
<li> 메모리캐시 : RAM에 데이터를 저장해놓는 방식 </li>
<li> 디스크 캐시 : 하드 디스크에 파일로 저장해놓고 파일을 읽어서 저장하는 방식</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] 요청(Request)과 응답(Response)
- Header & Body]]></title>
            <link>https://velog.io/@p-samename/HTTP-%EC%9A%94%EC%B2%ADRequest%EA%B3%BC-%EC%9D%91%EB%8B%B5Response-Header-Body</link>
            <guid>https://velog.io/@p-samename/HTTP-%EC%9A%94%EC%B2%ADRequest%EA%B3%BC-%EC%9D%91%EB%8B%B5Response-Header-Body</guid>
            <pubDate>Mon, 01 Apr 2024 06:20:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/p-samename/post/468480a1-5986-4e1e-b4d9-88dd9d7564e4/image.png" alt=""></p>
<h1 id="http-요청응답-헤더란">HTTP 요청/응답 헤더란?</h1>
<p>브라우저에서 보이는 많은 데이터들은 HTTP 프로토콜에 의해 서버에서 브라우저로 데이터가 넘어오게 됩니다. 이때 요청(Request)을 보내고 응답(Response)를 받는다.</p>
<p>HTTP 헤더에는 브라우저, 서버, 요청 데이터의 정보가 함께 들어있고 전달한다. 그렇다면  헤더에 어떤 키-값이 있을까 ? </p>
<p><img src="https://velog.velcdn.com/images/p-samename/post/34988071-3614-4478-a7f8-0b3b3f1437ce/image.png" alt=""></p>
<h2 id="요청request-헤더">요청(Request) 헤더</h2>
<h4 id="span-stylecolorcoralauthorityspan"><span style='color:coral'>:authority</span></h4>
<pre><code class="language-js">:authority : :authority: github.com</code></pre>
<p>: 가 앞에 붙는 이유??
HTTP/2에서는 : 로 시작하는 헤더 필드는 모두 예약된 헤더 필드로, 이들은 HTTP/2 프로토콜에서 사용하기 위해 미리 정의된 헤더 필드이다.</p>
<h4 id="span-stylecolorcoralmethodspan"><span style='color:coral'>:method</span></h4>
<p>HTTP 요청 메서드를 나타내는 헤더 필드. HTTP 요청 메서드는 클라이언트가 서버에게 요청하는 작업의 종류를 나타내며, 일반적으로 GET, POST, PUT, DELETE 등이 있다.</p>
<p>HTTP/1.1에서는 :method 대신에 실제 HTTP 요청 메서드를 사용하였지만, HTTP/2에서는 :method 헤더 필드가 추가되어 HTTP 요청 메서드를 명시한다. 이는 HTTP/2에서 다중 요청을 처리할 때 요청의 우선순위를 결정하는 데에 사용.</p>
<pre><code class="language-js">:method  :  GET | POST | PUT | DELETE</code></pre>
<p>예를 들어, 클라이언트가 :method: GET 헤더 필드를 포함하는 HTTP/2 요청을 서버에 보낸다면, 해당 요청은 GET 메서드를 사용한 요청임을 나타내며, 서버는 이를 바탕으로 요청을 처리.</p>
<h4 id="span-stylecolorcoralpathspan"><span style='color:coral'>:path</span></h4>
<p>HTTP 요청 URI를 나타내는 헤더 필드. HTTP 요청 URI는 클라이언트가 요청하는 리소스의 경로를 나타내며, 일반적으로 /path/to/resource와 같은 형식을 갖는다.</p>
<p>HTTP/1.1에서는 요청 라인에 URI가 포함되어 전송되었지만, HTTP/2에서는 요청 라인이 없기 때문에 :path 헤더 필드가 추가.
:path 헤더 필드를 사용하여 클라이언트는 요청하는 리소스의 경로를 서버에게 전달할 수 있다.</p>
<pre><code class="language-js">:path: github/index.html</code></pre>
<p>예를 들어, 클라이언트가 :path: /index.html 헤더 필드를 포함하는 HTTP/2 요청을 서버에 보낸다면, 해당 요청은 /index.html 경로에 있는 리소스를 요청하는 요청임을 나타내며, 서버는 이를 바탕으로 요청을 처리.</p>
<h4 id="span-stylecolorcoralschemespan"><span style='color:coral'>:scheme</span></h4>
<p>:scheme은 HTTP 요청이 사용하는 URI scheme을 나타내는 헤더 필드입니다. URI scheme은 클라이언트가 요청하는 리소스의 위치를 나타내는 URI의 첫 번째 부분으로, 일반적으로 http나 https와 같은 문자열을 갖는다.</p>
<pre><code class="language-js">:scheme: https</code></pre>
<h4 id="span-stylecolorcoralacceptspan"><span style='color:coral'>accept</span></h4>
<p>클라이언트가 서버로부터 받고자 하는 콘텐츠 타입을 나타내는 헤더 필드이다. 클라이언트는 accept 헤더를 사용하여 서버가 제공할 수 있는 콘텐츠 타입을 나열하고, 서버는 이 중에서 클라이언트가 받고자 하는 콘텐츠 타입을 선택하여 응답한다.</p>
<p>accept 헤더의 값은 콤마로 구분된 하나 이상의 콘텐츠 타입을 포함합니다. 각 콘텐츠 타입은 MIME 타입으로 지정되며, 일반적으로 type/subtype 형식을 갖습니다. 예를 들어, application/json은 JSON 데이터를 나타내는 * MIME 타입입니다.</p>
<ul>
<li>MIME 
미디어 타입 (Multipurpose Internet Mail Extensions 또는 MIME type로도 알려져 있음)이란 문서, 파일 또는 바이트 집합의 성격과 형식을 나타낸다.</li>
</ul>
<pre><code>accept: text/html
accept: */* =&gt; 모든 요쳥 ok</code></pre><p>accept 헤더는 HTTP 요청에서 자주 사용되며, RESTful API에서는 클라이언트가 서버로부터 받을 수 있는 콘텐츠 타입을 명시하는 데에 사용.</p>
<h4 id="span-stylecolorcoralaccept-encodingspan"><span style='color:coral'>accept-Encoding</span></h4>
<p>클라이언트가 서버로부터 받고자 하는 콘텐츠 인코딩을 나타내는 헤더 필드이다. 콘텐츠 인코딩은 서버가 콘텐츠를 압축하여 전송할 때 사용되며, 클라이언트는 accept-Encoding 헤더를 사용하여 서버가 지원하는 콘텐츠 인코딩을 나열한다.</p>
<p>accept-Encoding 헤더는 콤마로 구분된 하나 이상의 콘텐츠 인코딩을 포함합니다. 각 콘텐츠 인코딩은 압축 알고리즘으로 지정되며, 일반적으로 gzip, deflate, br 등이 사용된다.</p>
<pre><code>accept-encoding: gzip, deflate, br</code></pre><p>accept-Encoding 헤더는 HTTP 요청에서 자주 사용되며, 웹 브라우저에서는 콘텐츠를 더 빠르게 전송하기 위해 gzip 등의 콘텐츠 인코딩을 사용.</p>
<h4 id="span-stylecolorcoralaccept-languagespan"><span style='color:coral'>accept-language</span></h4>
<p>클라이언트가 서버로부터 받고자 하는 언어를 나타내는 헤더 필드이다. 클라이언트는 accept-Language 헤더를 사용하여 서버가 제공할 수 있는 언어를 나열하고, 서버는 이 중에서 클라이언트가 받고자 하는 언어를 선택하여 응답한다.</p>
<p>accept-Language 헤더는 콤마로 구분된 하나 이상의 언어 태그를 포함한다. 각 언어 태그는 ISO 639 언어 코드로 지정되며, 일반적으로 en, ko, ja 등이 사용된다. 언어 태그 뒤에는 옵션으로 해당 언어의 지역을 나타내는 ISO 3166 국가 코드를 추가할 수 있다.</p>
<p>accept-Language 헤더는 HTTP 요청에서 자주 사용되며, 웹 브라우저에서는 사용자가 설정한 언어를 나타내기 위해 사용된다. 서버는 accept-Language 헤더를 사용하여 적절한 언어로 응답을 반환하며, 다국어 웹 사이트에서는 accept-Language 헤더를 사용하여 사용자가 원하는 언어로 콘텐츠를 제공한다.</p>
<pre><code class="language-js">accept-language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6</code></pre>
<h4 id="span-stylecolorcoralauthorizationspan"><span style='color:coral'>authorization</span></h4>
<p>클라이언트가 서버에게 자신의 인증 정보를 전달하는 헤더 필드이다. 보안이 필요한 리소스에 접근할 때 사용되며, 일반적으로 사용자 이름과 암호를 전송. authorization 헤더는 HTTP 요청에서만 사용된다.</p>
<p>authorization 헤더의 값은 일반적으로 Basic Authentication, Digest Authentication, OAuth 등의 인증 체계를 사용하여 생성된 인증 토큰이다. Basic Authentication의 경우 인증 토큰은 사용자 이름과 암호를 Base64로 인코딩한 문자열이다.</p>
<pre><code class="language-js">authorization: Bearer eyJhbGciOiJIUz...</code></pre>
<p>Bearer는 Authorization 헤더에서 사용되는 인증 체계 중 하나이다. OAuth 2.0에서 정의되었으며, 토큰을 전달할 때 사용된다.</p>
<p>authorization 헤더는 HTTP 요청에서 자주 사용되며, RESTful API에서는 인증된 사용자만이 접근할 수 있는 리소스에 접근하기 위해 사용된다. 서버는 authorization 헤더를 사용하여 클라이언트의 인증 정보를 검증하고, 인증이 성공하면 요청된 리소스를 반환한다.</p>
<h4 id="span-stylecolorcoralcontent-lengthspan"><span style='color:coral'>content-length</span></h4>
<p>요청 본문의 길이</p>
<pre><code class="language-js">content-length: 10385</code></pre>
<h4 id="span-stylecolorcoralcontent-typespan"><span style='color:coral'>content-type</span></h4>
<p>요청 본문의 미디어 타입을 나타낸다. accept와 비슷한데 content-type 헤더는 현재 전송하는 데이터가 어떤 타입인지에 대한 설명을 하는 개념이고 accept 헤더는 클라이언트가 서버에게 어떤 특정한 데이터 타입을 보낼때 클라이언트가 보낸 특정 데이터 타입으로만 응답을 해야한다.
또한 content-type은 요청/응답 둘다 사용 가능하다.</p>
<pre><code class="language-js">content-type: application/json</code></pre>
<h4 id="span-stylecolorcoralcookiespan"><span style='color:coral'>cookie</span></h4>
<p>HTTP 요청에서 사용되는 헤더 필드 중 하나이다. 이 헤더는 클라이언트가 서버에 전송하는 쿠키를 나타낸다.</p>
<p>쿠키는 클라이언트 측에서 저장되는 작은 데이터 조각으로, 클라이언트가 서버에 요청을 보낼 때마다, cookie 헤더에 저장된 쿠키를 서버에 전송하여 사용자를 식별하고, 맞춤화된 경험을 제공한다. </p>
<p>cookie 헤더는 다음과 같은 형식으로 구성된다.</p>
<pre><code class="language-js">cookie: name1=value1; name2=value2; name3=value3</code></pre>
<p>여기서, name1, name2, name3은 쿠키의 이름을 나타내고, value1, value2, value3은 쿠키의 값이다. 쿠키의 이름과 값은 서버에서 지정합니다. 쿠키의 이름과 값은 반드시 URL 인코딩이 필요하다.</p>
<p>예를 들어, 사용자의 로그인 정보를 저장하는 쿠키는 다음과 같이 cookie 헤더에 포함될 수 있다.</p>
<pre><code class="language-js">cookie: username=stone; sessionid=1234567890abcdef</code></pre>
<p>이 경우, username은 쿠키의 이름이고, stone은 쿠키의 값이다. 마찬가지로, sessionid는 쿠키의 이름이고, 1234567890abcdef는 쿠키의 값이다.</p>
<h4 id="span-stylecolorcoralrefererspan"><span style='color:coral'>Referer</span></h4>
<p>HTTP 요청에서 사용되는 헤더 필드 중 하나이다. 이 헤더는 클라이언트가 현재 요청을 보내기 전에 방문했던 웹페이지의 URL을 나타낸다.</p>
<p>Referer 헤더는 다음과 같은 형식으로 구성된다.</p>
<pre><code class="language-js">Referer: https://www.example.com/page.html</code></pre>
<p>여기서, <a href="https://www.example.com/page.html%EC%9D%80">https://www.example.com/page.html은</a> 방문했던 웹페이지의 URL이다</p>
<p>일반적으로 웹사이트 분석 및 보안 목적으로 사용. 예를 들어, 웹사이트에서는 Referer 헤더를 사용하여 사용자가 해당 웹사이트에서 어떤 페이지로 이동했는지 추적할 수 있다. 또한, Referer 헤더는 웹사이트가 외부 링크를 통해 유입되는 트래픽을 추적하는 데 사용된다.</p>
<p>그러나, Referer 헤더는 사용자의 개인 정보 보호 문제가 있을 수 있으므로, 일부 브라우저에서는 Referer 헤더를 전송하지 않도록 설정할 수 있다.</p>
<h4 id="span-stylecolorcoraluser-agentspan"><span style='color:coral'>User-Agent</span></h4>
<p>HTTP 요청에서 사용되는 헤더 필드 중 하나이다. 이 헤더는 클라이언트가 사용하는 소프트웨어나 애플리케이션의 정보를 나타낸다.</p>
<p>User-Agent 헤더는 다음과 같은 형식으로 구성된다.</p>
<pre><code class="language-js">User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36</code></pre>
<p>여기서, Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36은 클라이언트가 사용하는 브라우저의 정보이다.</p>
<p>User-Agent 헤더는 웹사이트가 클라이언트의 운영 체제, 브라우저 종류, 버전 등을 파악하여, 맞춤화된 경험을 제공하는 데 사용된다. 예를 들어, 모바일 기기에서 웹사이트를 열었을 때, 웹사이트는 User-Agent 헤더를 사용하여 모바일 버전의 페이지를 제공할 수 있다.</p>
<p>그러나, User-Agent 헤더는 사용자의 개인 정보 보호 문제가 있을 수 있으므로, 일부 브라우저에서는 사용자 에이전트 정보를 조작하거나 감추는 기능을 제공한다.</p>
<h2 id="응답response-헤더">응답(Response) 헤더</h2>
<h4 id="span-stylecolorcornflowerbluecache-controlspan"><span style='color:cornflowerblue'>cache-control</span></h4>
<p>HTTP 요청 및 응답에서 사용되는 헤더 필드 중 하나이다. 이 헤더는 캐시 제어 지시자를 정의하여 캐시 동작을 제어한다.</p>
<p>cache-control 헤더는 다음과 같은 값으로 구성될 수 있다.</p>
<pre><code>** cache-control **

no-cache : 캐시된 리소스를 사용하기 전에 해당 리소스를 검증해야 함을 나타낸다.
no-store : 리소스를 캐시하지 않아야 함을 나타낸다.
max-age : 리소스가 캐시될 최대 시간(초)을 지정한다.
public : 모든 사용자가 리소스를 캐시할 수 있도록 허용한다.
private : 리소스를 캐시할 수 있는 사용자를 제한한다.
must-revalidate : 캐시된 리소스를 사용하기 전에 해당 리소스를 검증해야 함을 나타내지만, 캐시된 리소스가 만료되었을 때만 검증한다.
proxy-revalidate : 프록시 서버가 캐시된 리소스를 사용하기 전에 해당 리소스를 검증해야 함을 나타내지만, 캐시된 리소스가 만료되었을 때만 검증한다.
no-transform : 리소스가 프록시에 의해 수정되어서는 안 된다는 것을 나타낸다.</code></pre><p>cache-control 헤더는 웹 브라우저나 프록시 서버 등이 리소스를 캐시할 때 사용. 이 헤더는 캐시 동작을 제어하여 웹사이트의 성능을 향상시키는 데 도움을 준다.</p>
<p>** 브라우저 캐시에 대하여는 추후 포스팅 하려고 한다.</p>
<h4 id="span-stylecolorcornflowerbluecontent-security-policycspspan"><span style='color:cornflowerblue'>Content-Security-Policy(CSP)</span></h4>
<p>웹 사이트에서 로드되는 콘텐츠의 출처를 지정하여 XSS(Cross-Site Scripting) 등의 공격을 방지하는 데 사용되는 보안 헤더이다.</p>
<p>CSP는 다음과 같은 지시자를 사용하여 구성된다.</p>
<pre><code>default-src : 모든 리소스에 대한 기본 출처 지정자를 정의.
script-src : 자바스크립트 파일에 대한 출처 지정자를 정의.
style-src : CSS 파일에 대한 출처 지정자를 정의.
img-src : 이미지 파일에 대한 출처 지정자를 정의.
connect-src : XMLHttpRequest, WebSockets 등의 연결 요청에 대한 출처 지정자를 정의.
font-src : 웹 폰트에 대한 출처 지정자를 정의.
object-src : embed, object 등의 요소에 대한 출처 지정자를 정의.
media-src : 미디어 파일에 대한 출처 지정자를 정의.
frame-src : frame 및 iframe 요소에 대한 출처 지정자를 정의.
sandbox : 현재 문서가 실행될 때 적용할 보안 정책을 정의.</code></pre><p>CSP를 사용하면, 웹 페이지에 로드되는 리소스의 출처를 명시적으로 지정함으로써 XSS 등의 공격을 예방할 수 있다. 웹 개발자는 CSP를 사용하여 웹 사이트의 보안을 강화할 수 있다.</p>
<h4 id="span-stylecolorcornflowerblueset-cookiespan"><span style='color:cornflowerblue'>Set-Cookie</span></h4>
<p>서버에서 웹 브라우저로 쿠키를 생성하거나 수정할 때 사용되는 헤더.</p>
<p>Set-Cookie 헤더는 다음과 같은 값으로 구성된다.</p>
<pre><code>name=value : 쿠키의 이름과 값.
Expires : 쿠키의 만료 일자.
Max-Age : 쿠키의 유효 기간을 초 단위로 지정.
Domain : 쿠키가 전송될 도메인을 지정.
Path : 쿠키가 전송될 URL 경로를 지정.
Secure : HTTPS 프로토콜을 사용하여 쿠키를 전송.
HttpOnly : 자바스크립트에서 쿠키를 접근할 수 없도록 한다.</code></pre><p>Set-Cookie 헤더를 사용하면, 서버에서 웹 브라우저로 쿠키를 생성하거나 수정하여, 서버와 클라이언트 간의 상태 정보를 유지할 수 있다. 이를 통해, 로그인 정보, 사용자 환경 설정 등을 클라이언트 측에서 유지할 수 있다.</p>
<p>또한 응답 헤더에 set-cookie가 여러개가 존재할수가 있는데 이것은 쿠키를 여러개 생성하기 위한 것이다.</p>
<h4 id="span-stylecolorcornflowerblueaccess-control-allow-originspan"><span style='color:cornflowerblue'>Access-Control-Allow-Origin</span></h4>
<p>웹 브라우저에서 XMLHttpRequest나 fetch API 등을 통해 다른 도메인의 리소스를 요청할 때, 해당 리소스가 CORS(Cross-Origin Resource Sharing) 정책에 따라 접근 가능한 출처인지를 판단하기 위해 사용되는 응답 헤더.</p>
<p>이 헤더는 서버에서 클라이언트(웹 브라우저)로 보내지며, 다음과 같은 값으로 구성될 수 있다.</p>
<ul>
<li>Access-Control-Allow-Origin: * : 모든 출처에서 접근 가능하도록 허용.</li>
<li>Access-Control-Allow-Origin: <a href="http://example.com">http://example.com</a> : 특정 도메인에서만 접근 가능하도록 허용.</li>
</ul>
<p>이 헤더로 지정된 출처만이 해당 리소스에 접근할 수 있다. 이를 통해, 다른 도메인에서의 CSRF(Cross-Site Request Forgery) 공격 등을 방지할 수 있다.</p>
<p>또한 CORS 보안정책을 회피할수 있게 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[브라우저 캐시 (browser cache)]]></title>
            <link>https://velog.io/@p-samename/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%BA%90%EC%8B%9C-browser-cache</link>
            <guid>https://velog.io/@p-samename/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%BA%90%EC%8B%9C-browser-cache</guid>
            <pubDate>Mon, 01 Apr 2024 02:14:47 GMT</pubDate>
            <description><![CDATA[<h2 id="캐시cache란">캐시(cache)란?</h2>
<p>캐시(Cache)는 다운로드 받은 데이터나 값을 미리 복사해 놓는 임시 장소이다.
데이터에 접근하는 시간이 오래 걸리는 경우나 값을 다시 계산하는 시간을 절약하고 싶은 경우에 사용한다.</p>
<p>클라이언트와 서버가 요청과 응답을 주고 받을 때 브라우저 캐시를 이용하면, 리소스 낭비를 줄일 수 있다. 캐시는 클라이언트가 서버로 반복된 데이터를 요청할 경우, 캐시에 그 데이터이 저장되어 있다면 클라이언트는 서버를 통하지 않고 캐시에서 그 데이터를 가져와 사용한다.</p>
<p>클라이언트는 어떠한 기준으로 캐시에 있는 데이터를 가져올까? 
클라이언트는 캐시에 있는 데이터의 신뢰성을 확인하기 위해 <strong>HTTP 요청의 헤드</strong>를 활용한다.
<br></p>
<h3 id="캐시-사용하기">캐시 사용하기</h3>
<h4 id="유효-시간으로-데이터-검증">유효 시간으로 데이터 검증</h4>
<pre><code class="language-js&#39;">// 응답 헤더
Cache-Control:max-age=300 // max-age - 리소스가 유효하다고 판단되는 최대 시간.
Content-Length:128475
Content-Type:image/jpeg</code></pre>
<p>클라이언트의 요청으로 서버에서 클라이언트로 데이터를 보낼 떄 헤더에 Cache-Control을 추가하여 응답한다. 
&quot;max-age = 300&quot;이란 뜻은 300초간 해당 데이터가 유효하다는 의미이다. 클라이언트가 첫 응답으로 부터 60초 이내에 동일한 데이터를 요청하면 서버가 아닌 캐시에서 데이터를 가져와 사용하게 된다.</p>
<p>요청 시 만약 300초가 지났다면 서버에서 다시 데이터를 받아 오게 된다.</p>
<h3 id="데이터-최신화-여부-검증">데이터 최신화 여부 검증</h3>
<p>캐시 유효 시간은 지났지만, 서버에서 다시 받아와야하는 파일이 캐시에 저장되어 있는 파일과 완전히 동일한 경우에 검증을 통해서 캐시의 데이터를 사용할 수 있다.</p>
<pre><code class="language-js">Date:Mon, 01 Apr 2024 00:56:40 GMT 
Expires:Sat, 27 Apr 2024 10:56:50 GMT
Last-Modified:Fri, 08 Sep 2023 00:00:00 GMT
Etag:Z-2GCz4R6Sq+4IXlm6BaPmer+MUS6U831KjrkexB1Nw=
</code></pre>
<ul>
<li><p><strong>Date</strong>
HTTP 응답이 발생한 날짜와 시간. HTTP 응답을 생성하는 서버가 해당 응답을 생성할 때의 시간.</p>
</li>
<li><p><strong>Expires</strong>
리소스가 캐시되어 있을 때 캐시가 만료되는 날짜와 시간. 클라이언트는 이 날짜 이후에는 캐시된 데이터를 다시 서버로 요청하여 새로운 데이터를 받아와야 한다.</p>
</li>
<li><p><strong>Last-Modified</strong>
해당 리소스가 마지막으로 수정된 날짜와 시간. 이 값은 서버에서 리소스를 마지막으로 수정한 시간.</p>
</li>
<li><p><strong>Etag</strong>
특정 리소스의 버전을 나타내는 식별자. 클라이언트가 이전에 받은 리소스의 ETag를 서버에게 다시 제공하면, 서버는 이 ETag를 사용하여 리소스가 변경되었는지 여부를 판단.. 이를 통해 캐시된 리소스를 다시 전송할 필요가 있는지를 결정하게 된다.</p>
</li>
</ul>
<h3 id="last-modified-와-if-modified-since">Last-Modified 와 If-Modified-Since</h3>
<p>클라이언트가 서버에 데이터를 요청하면 서버에서는 응답과 함께 서버에서 데이터가 마지막으로 수정된 시간을 의미하는 헤더의 Last-Modified 값 Fri, 08 Sep 2023 00:00:00 GMT 을 캐시에 함께 저장.</p>
<p>이 후에 클라이언트가 요청을 보낼 때 서버의 응답 헤더에 있던 Last-Modified 값인 08 Sep 2023 00:00:00 GMT 을 헤더에 If-Modified-Since 값으로 작성.</p>
<pre><code class="language-js">GET /logo.jpg
If-Modified-Since : 08 Sep 2023 00:00:00 GMT</code></pre>
<p>이 후에 서버는 요청 헤더에 있는 08 Sep 2023 00:00:00 GMT 값을 이용하여 데이터가 동일한지 검증하고 데이터가 그 이후 수정되지 않았다면 304 Not Modified라는 응답 메시지를 클라이언트에 보낸다. 그리고 캐시 데이터의 유효 시간이 다시 갱신되어 클라이언트는 브라우저 캐시에서 300초간 데이터를 받아 올 수 있게 된다.</p>
<h3 id="etag-와-if-none-match">Etag 와 If-None-Match</h3>
<p>Last-Modified 와 If-Modified-Since와 마찬가지로 동작한다. 다만 서버의 응답에 헤더 포함되어 있는 ETag 값 &quot;Z-2GCz4R6Sq+4IXlm6BaPmer+MUS6U831KjrkexB1Nw=&quot; 이 캐시에 저장된다.</p>
<pre><code class="language-js">HTTP/1.1 200 OK
Content-Type : image/jpeg
Cache-Control : max-age = 300
ETag : &quot;Z-2GCz4R6Sq+4IXlm6BaPmer+MUS6U831KjrkexB1Nw=&quot;
Cache-Control:max-age=300 // max-age - 리소스가 유효하다고 판단되는 최대 시간.
Content-Length:128475</code></pre>
<p>이 후에 클라이언트가 서버에 요청을 보낼 때 헤더에 If-None-Match 값을 &quot;Z-2GCz4R6Sq+4IXlm6BaPmer+MUS6U831KjrkexB1Nw=&quot;를 포함시킬 경우 서버에서 서버와 캐시의 데이터가 동일한 데이터임이 검증한 후 데이터가 수정되지 않았음을 의미하는 304 Not Modified 라는 응답을 보내주고, 캐시 데이터의 유효 시간이 갱신되면서 해당 데이터를 재사용할 수 있게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[hydration 이란?]]></title>
            <link>https://velog.io/@p-samename/hydration-%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@p-samename/hydration-%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Tue, 05 Mar 2024 05:35:58 GMT</pubDate>
            <description><![CDATA[<h1 id="hydration">hydration</h1>
<p>Hydrate는 Server Side 에서  <span style='color:skyblue'>Pre-Redering</span> 렌더링 된 정적 페이지와 번들링된 JS파일을 클라이언트에게 보낸 뒤, 클라이언트 단에서 HTML 코드와 React인 JS코드를 서로 매칭 시키는 과정을 말한다.</p>
<ul>
<li>(JavaScript가 모든 작업을 수행하는 대신) Next.js는 각 페이지에 대한 HTML을 미리 렌더링한다.</li>
<li>생성된 각 HTML은 해당 페이지에 필요한 최소한의 JS 코드와 연결된다.</li>
<li>이후, 브라우저에서 페이지를 로드하면 해당 JS 코드가 실행되어 페이지가 완전한 인터랙티브 상태가 된다.</li>
</ul>
<br/>
<br/>

<p>next.js 에서의 렌더링과 react.js의 렌더링 비교</p>
  <img style='margin:12px auto' src="https://velog.velcdn.com/images/p-samename/post/c03c58e0-56bb-4d87-a623-dd3558c926d7/image.png" width="50%" height="50%">
  <img style='margin:0 auto' src="https://velog.velcdn.com/images/p-samename/post/a3c1bde3-9289-4a40-8a6a-2f3500be5790/image.png" width="50%" height="50%">



<p>Hydration은 그럼 Next.js에서만 발생할 수 있다고 생각하면 안된다.</p>
<p>Hydration은 Next.js만의 특별한 동작이 아니라 ReactDOM 함수이다. 때문에 오직 next.js 에서만 사용 할 수 있다고 오해하면 안된다.</p>
<h2 id="동작-과정">동작 과정</h2>
<img style='margin:0 auto' src="https://velog.velcdn.com/images/p-samename/post/3d1fb7ab-c0ac-4b67-a119-1da85ac0e5e8/image.png" width="50%" height="50%">


<p>서버에서 받아온 DOM tree와 자체적으로 렌더링한 tree를 비교한다.
두 tree 사이의 차이점을 얻은 후, 자체적으로 Client-Side-Rendering한 tree와 비교하며 어떤 DOM과 매칭되는지 이해한다.
이해한 내용에 따라 클라이언트 렌더링 동작을 진행한다</p>
<pre><code class="language-jsx">export default function Hydrate() {
  const currentTime = new Date().getTime();
  return &lt;div&gt;{currentTime}&lt;/div&gt;;
}
// Error: Text content does not match server-rendered HTML.
// See more info here: https://nextjs.org/docs/messages/react-hydration-error</code></pre>
<p>해당 코드로 page를 띄우면 </p>
<p>hydrate mismatch 에러가 발생한다. 즉, 서버에서 미리 렌더링된 React 트리와 브라우저에서의 첫 번째 렌더링의 React 트리 사이에 차이 (서버 측에서 렌더링 될 때의 시간과 클라이언트 측에서 렌더링 될 때의 시간의 오차) 가 있기 때문에 발생한다.</p>
<br/>

<p> 수정된 코드</p>
<pre><code class="language-jsx">import { useEffect, useState } from &#39;react&#39;;

export default function Hydrate() {
  const [currentTime, setCurrentTime] = useState();

  useEffect(() =&gt; {
    setCurrentTime(new Date().getTime());
  }, []);

  return (
    &lt;&gt;
      &lt;div&gt;{currentTime}&lt;/div&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>이렇게 서버 측에서는 해당 tree 를 렌더링하지 않고 클라이언트 측에서만 렌더링 될 수 있도록 수정해 주어야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체 지향 설계의 5원칙 S.O.L.I.D]]></title>
            <link>https://velog.io/@p-samename/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EC%9B%90%EC%B9%99-S.O.L.I.D</link>
            <guid>https://velog.io/@p-samename/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-5%EC%9B%90%EC%B9%99-S.O.L.I.D</guid>
            <pubDate>Fri, 29 Dec 2023 07:03:58 GMT</pubDate>
            <description><![CDATA[<h2 id="solid-원칙">S.O.L.I.D 원칙</h2>
<p>SOLID 원칙이란 객체지향 설계에서 지켜줘야 할 5개의 소프트웨어 개발 원칙( SRP, OCP, LSP, ISP, DIP )을 말한다.</p>
<ul>
<li><span style='color:yellow'>S</span>RP(Single Responsibility Principle): 단일 책임 원칙</li>
<li><span style='color:yellow'>O</span>CP(Open Closed Priciple): 개방 폐쇄 원칙</li>
<li><span style='color:yellow'>L</span>SP(Listov Substitution Priciple): 리스코프 치환 원칙</li>
<li><span style='color:yellow'>I</span>SP(Interface Segregation Principle): 인터페이스 분리 원칙</li>
<li><span style='color:yellow'>D</span>IP(Dependency Inversion Principle): 의존 역전 원칙</li>
</ul>
<p>SOLID 설계 원칙은 oop의 4가지 특징(추상화, 상속, 다형성, 캡슐화)와 더불어, 객체 지향 프로그래밍의 단골 면접 질문 중 하나이다. 또한 앞으로 배우게 될 여러 디자인 패턴(Design Pattern)들이 SOLID 설계 원칙에 입각해서 만들어진 것이기 때문에, 표준화 작업에서부터 아키텍처 설계에 이르기까지 다양하게 적용되는 이의 근간이 되는 SOLID 원칙에 대해 탄탄하게 알아볼 필요가 있다.</p>
<h3 id="--단일-책임-원칙">- 단일 책임 원칙</h3>
<p>SRP (Single Responsibility Principle)</p>
<p>단일 책임 원칙은 클래스(객체)는 단 하나의 책임만 가져야 한다는 원칙 여기서 &#39;책임&#39; 이라는 의미는 하나의 &#39;기능 담당&#39;으로 보면 된다.
즉, 하나의 클래스는 하나의 기능 담당하여 하나의 책임을 수행하는데 집중되도록 클래스를 따로따로 여러개 설계하라는 원칙이다.
만일 하나의 클래스에 기능(책임)이 여러개 있다면 기능 변경(수정) 이 일어났을때 수정해야할 코드가 많아진다.예를 들어 A를 고쳤더니 B를 수정해야하고 또 C를 수정해야하고, C를 수정했더니 다시 A로 돌아가서 수정해야 하는, 마치 책임이 순환되는 형태가 되어버린다.따라서 SRP 원칙을 따름으로써 한 책임의 변경으로부터 다른 책임의 변경으로의 연쇄작용을 극복할 수 있게 된다.
최종적으로 단일 책임 원칙의 목적은 프로그램의 유지보수 성을 높이기 위한 설계 기법이다.
이때 책임의 범위는 딱 정해져있는 것이 아니고, 어떤 프로그램을 개발하느냐에 따라 개발자마다 생각 기준이 달라질 수 있다. 따라서 단일 책임 원칙에 100% 해답은 없다.</p>
<pre><code class="language-ts">ex)

// ❌ 단일 책임 원칙을 따르지 않은 경우 

class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  saveToDatabase() {
    // 유저 정보를 데이터베이스에 저장하는 코드
    // 데이터베이스 관련 로직과 유저 정보 생성이 함께 있음
  }

  sendEmail(message) {
    // 이메일을 보내는 코드
    // 이메일 전송과 관련된 로직과 유저 정보 사용이 함께 있음
  }
}


// ⭕ 단일 책임 원칙을 따르는 경우

class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

class UserDB {
  saveToDatabase(user) {
    // 유저 정보를 데이터베이스에 저장하는 코드
  }
}

class EmailService {
  sendEmail(user, message) {
    // 이메일을 보내는 코드
  }
}</code></pre>
<pre><code>💡  클래스로 너무 많은 일을 하지 말고 딱 한 가지 책임만 수행하라는 뜻으로 보면 된다.</code></pre><br>

<h3 id="--개방-폐쇄-원칙">- 개방 폐쇄 원칙</h3>
<p>OCP (Open Closed Principle)</p>
<p>OCP 원칙은 클래스는 &#39;확장에 열려있어야 하며, 수정에는 닫혀있어야 한다&#39; 를 뜻한다.
기능 추가 요청이 오면 클래스를 확장을 통해 손쉽게 구현하면서, 확장에 따른 클래스 수정은 최소화 하도록 프로그램을 작성해야 하는 설계 기법이다.</p>
<p>[ 확장에 열려있다 ] - 새로운 변경 사항이 발생했을 때 유연하게 코드를 추가함으로써 큰 힘을 들이지 않고 애플리케이션의 기능을 확장할 수 있음
[ 변경에 닫혀있다 ] - 새로운 변경 사항이 발생했을 때 객체를 직접적으로 수정을 제한함. </p>
<p>어렵게 생각할 필요없이, OCP 원칙은 추상화 사용을 통한 관계 구축을 권장을 의미하는 것이다.
즉, 다형성과 확장을 가능케 하는 객체지향의 장점을 극대화하는 기본적인 설계 원칙
말아야 한다. (인터페이스는 한번 구성하였으면 왜만해선 변하면 안되는 정책 개념)
<br></p>
<h3 id="--리스코프-치환-원칙">- 리스코프 치환 원칙</h3>
<p>LSP (Liskov Substitution Principle)</p>
<p>LSP 원칙은 서브 타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 한다는 원칙이다.
쉽게 말하면 LSP는 다형성 원리를 이용하기 위한 원칙 개념으로 보면 된다.
간단히 말하면 리스코프 치환 원칙이란, 다형성의 특징을 이용하기 위해 상위 클래스 타입으로 객체를 선언하여 하위 클래스의 인스턴스를 받으면, 업캐스팅된 상태에서 부모의 메서드를 사용해도 동작이 의도대로 흘러가야 하는 것을 의미하는 것이다.
따라서 기본적으로 LSP 원칙은 부모 메서드의 오버라이딩을 조심스럽게 따져가며 해야한다.왜냐하면 부모 클래스와 동일한 수준의 선행 조건을 기대하고 사용하는 프로그램 코드에서 예상치 못한 문제를 일으킬 수 있기 때문이다.
<br></p>
<h3 id="--인터페이스-분리-원칙">- 인터페이스 분리 원칙</h3>
<p>ISP (Interface Segregation Principle)</p>
<p>ISP 원칙은 인터페이스를 각각 사용에 맞게 끔 잘게 분리해야한다는 설계 원칙이다.
SRP 원칙이 클래스의 단일 책임을 강조한다면, ISP는 인터페이스의 단일 책임을 강조하는 것으로 보면 된다.
즉, SRP 원칙의 목표는 클래스 분리를 통하여 이루어진다면, ISP 원칙은 인터페이스 분리를 통해 설계하는 원칙.
ISP 원칙은 인터페이스를 사용하는 클라이언트를 기준으로 분리함으로써, 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공하는 것이 목표이다.
다만 ISP 원칙의 주의해야 할점은 한번 인터페이스를 분리하여 구성해놓고 나중에 무언가 수정사항이 생겨서 또 인터페이스들을 분리하는 행위를 가하지 말아야 한다. (인터페이스는 한번 구성하였으면 왜만해선 변하면 안되는 정책 개념)</p>
<pre><code>💡 인터페이스는 제약 없이 자유롭게 다중 상속(구현)이 가능하기 때문에, 분리할 수 있으면 분리하여 각 클래스 용도에 맞게 implements 하라는 설계 원칙이라고 이해하면 된다.</code></pre><br>

<h3 id="--의존-역전-원칙">- 의존 역전 원칙</h3>
<p>DIP (Dependency Inversion Principle)</p>
<p>DIP 원칙은 어떤 Class를 참조해서 사용해야하는 상황이 생긴다면, 그 Class를 직접 참조하는 것이 아니라 그 대상의 상위 요소(추상 클래스 or 인터페이스)로 참조하라는 원칙 
쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻
의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는, 변화하기 어려운 것 거의 변화가 없는 것에 의존하라는 것</p>
<pre><code>💡 의존 역전 원칙의 지향점은 각 클래스간의 결합도(coupling)을 낮추는 것이다.</code></pre><br>]]></description>
        </item>
        <item>
            <title><![CDATA[객체지향 프로그래밍(Object-Oriented Programming) 이란?]]></title>
            <link>https://velog.io/@p-samename/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8DObject-Oriented-Programming-%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@p-samename/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8DObject-Oriented-Programming-%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Fri, 29 Dec 2023 02:06:08 GMT</pubDate>
            <description><![CDATA[<h2 id="객체-지향-프로그래밍이란">객체 지향 프로그래밍이란?</h2>
<p>객체 지향 프로그래밍 (Object-Oriented Programming, OOP)은 프로그래밍에서 필요한 데이터를 추상화 시켜 상태와 행위를 가진 객체로 만들고, 객체들간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법이다.</p>
<p><img src="https://velog.velcdn.com/images/p-samename/post/0e14f490-748a-464d-b010-a303434f3b99/image.png" alt=""></p>
<p>초기 프로그래밍은 절차적 프로그래밍이었다. 명시된 입력을 받아서 명시된 순서로 처리한 다음, 그 결과는 내는 방식이었다. 프로그램을 어떤 논리로 어떤 순서대로 써 내려가는것이 주요한 쟁점이었다. 그러나 간단한 알고리즘이면 몰라도, 조금만 복잡해지면 순서도로 나타내지는게 불가능한 스파게티코드를 양산하게 된다. 시간이 흐를수록 복잡한 프로그램이 요구되었는데, 기존 절차적 프로그래밍으로는 도저히 작성할 수가 없었던것.</p>
<p>이를 극복하기 위한 대안으로 나온것이 <span style='color:coral'><strong>객체지향 프로그래밍</strong></span>이다. 큰 문제를 쪼개는 것이 아니라, 먼저 작은 문제들을 해결할 수있는 객체들을 만든 뒤, 이 객체들을 조합해서 큰 문제를 해결하는 상향식(Bottom-up) 해결법을 도입한 것이다. 객체를 독립성과 신뢰성이 보장되게 만들어 놓으면 재사용성도 높아지므로 개발기간과 비용 또한 줄어들게 되었다.</p>
<h2 id="객체란">객체란?</h2>
<p>객체는 프로그램에서 사용되는 데이터 또는 식별자에 의해 참조되는 공간을 의미하며 값을 저장 할 변수와 작업을 수행 할 메소드를 서로 연관된 것들끼리 묶어서 만든 것을 객체라고 할 수 있다.</p>
<p>객체지향 프로그래밍을 레고에 빗대 표현 할 수 있는데, 객체가 레고의 조각이 될 것이고 레고의 조각을 조립해서 무언가를 만드는 방식이 객체지향 프로그래밍이라고 할 수 있다.</p>
<p>객체는 또한 레고 조각과도 비슷하게 여러군데에서 재사용 할 수 있는데 이는 부품화 와 재사용성 이라는 객체 지향 프로그래밍의 특징을 보여주기도 한다.</p>
<h2 id="객체-지향-프로그래밍의-특징">객체 지향 프로그래밍의 특징</h2>
<p>객체 지향 프로그래밍은 크게 추상화 , 캡슐화 , 상속 , 다형성 의 네가지 특징을 가진다.</p>
<h3 id="1-추상화">1. 추상화</h3>
<p>객체에서 공통된 속성과 행위를 추출 하는 것
공통의 속성과 행위를 찾아서 타입을 정의하는 과정
추상화는 불필요한 정보는 숨기고 중요한 정보만을 표현함으로써 프로그램을 간단하게 만드는 것</p>
<p><img src="https://velog.velcdn.com/images/p-samename/post/7e1c5660-a087-42ec-892c-b4406f203267/image.png" alt=""></p>
<pre><code class="language-js">class Animal {
    constructor(name) {
        this.name = name;
    }
}

class Dog extends Animal {
    speak() {
        return `${this.name} : 멍멍 !`;
    }
}

class Cat extends Animal {
    speak() {
        return `${this.name} : 야옹 !`;
    }
}


const dog = new Dog(&#39;강아지&#39;)
const cat = new Cat(&#39;고양이&#39;)

dog.speak() // &#39;강아지 : 멍멍 !&#39;
cat.speak() // &#39;고양이 : 야옹 !&#39;</code></pre>
<h4 id="추상화가-왜-필요할까">추상화가 왜 필요할까?</h4>
<p>&#39;현대&#39;와 같은 다른 자동차 브랜드가 추가될 수도 있다. 이때 추상화로 &#39;자동차&#39;를 구현 해놓으면 다른 곳의 코드를 수정할 필요 없이 추가로 만들 부분만 새로 생성해주면 된다.</p>
<h3 id="2-캡슐화">2. 캡슐화</h3>
<p>데이터 구조와 데이터를 다루는 방법들을 결합 시켜 묶는 것 (변수와 함수를 하나로 묶는 것을 뜻함)
낮은 결합도를 유지할 수 있도록 설계하는 것.</p>
<p><img src="https://velog.velcdn.com/images/p-samename/post/0f828fd0-a04a-4e16-9e90-bce5809b9592/image.png" alt=""></p>
<pre><code class="language-js">class Car {
    // 생성자
    constructor(make, model) {
        this.make = make;   // 제조사
        this.model = model; // 모델
        this.speed = 0;      // 초기 속도는 0
    }

    // Getter 메서드: 속도를 가져오는 메서드
    getSpeed() {
        return this.speed;
    }

    // Setter 메서드: 속도를 설정하는 메서드
    setSpeed(newSpeed) {
        if (newSpeed &gt;= 0 &amp;&amp; newSpeed &lt;= 200) {
            this.speed = newSpeed;
            console.log(`${this.make} ${this.model}의 속도가 ${this.speed}km/h로 설정되었습니다.`);
        } else {
            console.log(&quot;유효한 속도 범위는 0에서 200까지입니다.&quot;);
        }
    }

    // 가속 메서드
    accelerate() {
        this.setSpeed(this.speed + 10);
    }

    // 감속 메서드
    decelerate() {
        this.setSpeed(this.speed - 10);
    }
}

const myCar = new Car(&quot;Hyundai&quot;, &quot;Sonata&quot;);

console.log(myCar.getSpeed()); // 0
myCar.accelerate();
console.log(myCar.getSpeed()); // 10
myCar.decelerate();
console.log(myCar.getSpeed()); // 0
myCar.setSpeed(150); // Hyundai Sonata의 속도가 150km/h로 설정되었습니다.
console.log(myCar.getSpeed()); // 150
myCar.setSpeed(250); // 유효한 속도 범위는 0에서 200까지입니다.</code></pre>
<h3 id="3-상속">3. 상속</h3>
<p>클래스의 속성과 행위를 하위 클래스에 물려주거나 하위 클래스가 상위 클래스의 속성과 행위를 물려받는 것을 말한다.
새로운 클래스가 기존의 클래스의 데이터와 연산을 이용할 수 있게 하는 기능.</p>
<p>상속의 장점과 단점</p>
<table>
  <tr>
    <th>장점</th>
    <th>단점</th>
  </tr>
  <tr>
    <td>재사용으로 인하여 코드가 감소</td>
    <td>상위 클래스의 변경이 어려워짐</td>
  </tr>
  <tr>
    <td>범용적인 사용이 가능</td>
    <td>불필요한 클래스가 증가할 수 있음</td>
  </tr>
  <tr>
    <td>자료와 메서드의 자유로운 사용 및 추가가 가능</td>
    <td>상속이 잘못 사용될 수 있음</td>
  </tr>
</table>
<br>


<pre><code class="language-js">// 부모 클래스
class Parent {
    constructor(name) {
        this.name = name;
    }

    sayHello() {
        return console.log(`안녕하세요, ${this.name}입니다.`);
    }
}

// 자식 클래스
class Child extends Parent {
    constructor(name, age) {
        super(name); // 부모 클래스의 생성자 호출
        this.age = age;
    }

    sayAge() {
        return console.log(`${this.name}의 나이는 ${this.age}세입니다.`);
    }
}

// 사용 예시
const childObject = new Child(&quot;John&quot;, 10);

childObject.sayHello(); // 부모 클래스의 메서드 호출: 안녕하세요, John입니다.
childObject.sayAge();    // 자식 클래스의 메서드 호출: John의 나이는 10세입니다.</code></pre>
<h3 id="4-다형성">4. 다형성</h3>
<ul>
<li>하나의 변수명, 함수명이 상황에 따라 다른 의미로 해석 될 수 있는 것.</li>
<li>어떠한 요소에 여러 개념을 넣어 놓는 것.</li>
</ul>
<p>객체 지향 프로그래밍은 하나의 클래스 내부에 같은 이름의 행위를 여러개 정의하거나 상위 클래스의 행위를 하위 클래스에서 재정의하여 사용할 수 있기 때문에 다형성이라는 특징을 갖게 된다.</p>
<h4 id="오버라이딩">오버라이딩</h4>
<ul>
<li>상위 클래스가 가지고 있는 메소드를 하위 클래스가 재정의해서 사용하는 것</li>
</ul>
<h4 id="오버로딩">오버로딩</h4>
<ul>
<li>같은 이름의 메서드가 인자의 개수나 자료형에 따라 다른 기능을 하는 것</li>
</ul>
<h2 id="객체-지향-프로그래밍의-장단점">객체 지향 프로그래밍의 장단점</h2>
<table>
  <tr>
    <th>장점</th>
    <th>단점</th>
  </tr>
  <tr>
    <td>클래스 단위로 모듈화시켜서 개발하기 때문에 업무 분담이 편리하고 대규모 소프트웨어 개발에 적합</td>
    <td>처리속도가 상대적으로 느림</td>
  </tr>
  <tr>
    <td>클래스 단위로 수정이 가능하기 때문에 유지 보수가 편리</td>
    <td>객체의 수가 많아짐에 따라 용량이 커질 수 있음</td>
  </tr>
  <tr>
    <td>클래스를 재사용하거나 상속을 통해 확장함으로써 코드 재사용이 용이</td>
    <td>설계시 많은 시간과 노력이 필요하게 수 있음</td>
  </tr>
</table>
<br>

<p>객체 지향 프로그래밍의 반대는 절차 지향 프로그래밍이다?</p>
<p>객체 지향 프로그래밍은 절차 지향 프로그래밍과 장단점이 겹치는 부분이 있기 때문에 그렇게 생각이 들 수 있다. 하지만 절차지향은 순차적으로 실행에 초점이 되어있고 객체지향은 관계/조직에 초점을 맞추고 있다는 초점의 차이일 뿐이다.
절차적 프로그래밍이라고 해서 객체를 다루지 않는 것이 아니고, 객체지향 프로그래밍이라고해서 절차가 없는 것도 아니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자료구조] 이진트리(Binary Tree)란?
(완전이진트리)]]></title>
            <link>https://velog.io/@p-samename/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%9D%B4%EC%A7%84%ED%8A%B8%EB%A6%ACBinary-Tree%EB%9E%80%EC%99%84%EC%A0%84%EC%9D%B4%EC%A7%84%ED%8A%B8%EB%A6%AC</link>
            <guid>https://velog.io/@p-samename/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%9D%B4%EC%A7%84%ED%8A%B8%EB%A6%ACBinary-Tree%EB%9E%80%EC%99%84%EC%A0%84%EC%9D%B4%EC%A7%84%ED%8A%B8%EB%A6%AC</guid>
            <pubDate>Thu, 28 Dec 2023 02:20:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="이진트리binary-tree">이진트리(Binary Tree)</h3>
<p>이진트리(Binary Tree)는 트리 중에서도 각 노드가 최대 2개의 자식노드를 가질 때 이진트리(Binary Tree)라고 한다.
최대 2개이기 때문에 자식이 없을 수도 있고, 한개만 있을 수도 있다.
이때 자식노드는 각각 왼쪽 자식노드와 오른쪽 자식노드로 표현을 한다. <br />
그래서 같은 루트에 같은 자식노드 하나를 가지고 있어도
<span style="color:cornflowerblue">자식노드의 위치가 각각 왼쪽과 오른쪽으로 다르다면 그 두 트리는 서로 다른 트리</span>가 된다.</p>
</blockquote>
<p align="center">  
  <img src="https://velog.velcdn.com/images/p-samename/post/2ba4c4d2-c88b-4f26-9b4c-46d34a722fa0/image.png" width='500px'> 
  <figcaption style='font-size:12px' align="center">자식 노드의 위치가 다르다면 서로 다른 트리다</figcaption>
</p>

<h3 id="완전이진트리complete-binary-tree">완전이진트리(Complete binary tree)</h3>
<p>이 트리는 두가지 조건을 충족해야 한다.
첫째, 마지막 레벨(level)을 제외하고 모든 노드가 채워져있어야 한다.</p>
<p>마지막 레벨의 노드는 다 채워져 있을 수도 있고 아닐수도 있다.</p>
<p>둘째, 노드는 <span style='padding:4px;background:grey;border-radius:4px'>왼쪽에서 오른쪽 방향</span> 으로 채워져야 한다.
그래서 어느 노드에 오른쪽 자식이 존재한다면 왼쪽 자식도 가지고 있어야 완전이진트리로 볼 수 있다.
<br></p>
<p align="center">  
  <img src="https://velog.velcdn.com/images/p-samename/post/087f4960-7f22-40c4-a1d7-7736bc2ab071/image.png"  width='500px'> 
  <figcaption style='font-size:12px' align="center">완전 이진트리 성립의 예</figcaption>
</p>


<p>위 왼쪽 그림에서 이 부분은 왼쪽 자식 하나만 있어도 왼쪽에서 오른쪽으로 채워져야 한다는 완전이진트리(Complete binary tree)의 조건을 충족하기 때문에 완전이진트리(Complete binary tree)라고 할 수 있다.
<br></p>
<p align="center">  
  <img src="https://velog.velcdn.com/images/p-samename/post/0157f0ec-2577-4c54-98d5-7c8c7e030eb9/image.png"  width='500px'> 
  <figcaption style='font-size:12px' align="center">완전 이진트리가 성립되지 않는 예</figcaption>
</p>

<p>위 왼쪽 그림의 트리처럼 오른쪽 자식노드는 있지만 왼쪽 자식 노드는 없다면 완전이진트리가 성립하지 않는다. 그리고 오른쪽 그림의 트리도 왼쪽부터 채우고 있지 않기 때문에 완전 이진트리가 될 수 없다.
<br></p>
<h3 id="이진트리의-특징">이진트리의 특징</h3>
<h4 id="1-1차원-배열로-표현-할-수-있다">1) 1차원 배열로 표현 할 수 있다.</h4>
<p>그래서 이런 이진트리는 트리지만 선형자료구조인 1차원 배열로도 표현할 수 있다는 특징을 가진다.</p>
<p align="center">  
  <img src="https://velog.velcdn.com/images/p-samename/post/864a7742-6177-4ae0-8123-a03f64ec1da2/image.png" width='500px'> 
  <figcaption style='font-size:12px' align="center">이진트리는 선형자료구조인 1차원 배열로도 표현 할 수 있다</figcaption>
</p>

<p>위 그림처럼 루트에서 시작해서 왼쪽노드부터 오른쪽 노드까지 순서를 매기면</p>
<p>완전이진트리(Complete binary tree)같은 경우는 빈숫자가 없게 된다.</p>
<p>그래서 완전이진트리(Complete binary tree)는 각 번호를 인덱스로 써서 1차원 배열로 표현이 가능하다.
완전 이진트리는 1차원 배열에서 이렇게 빈틈없이 값을 채운 배열이 될 수 있고,
중간에 빈 값이 있는 이진트리는 배열로 표현하면 비어있는 공간의 인덱스에</p>
<p>null값이 들어간 1차원 배열로 표현할 수 있다.
<br></p>
<p align="center">  
  <img src="https://velog.velcdn.com/images/p-samename/post/b4efd48f-4062-487d-9aa2-435abd822343/image.png" width='300px'> 
  <figcaption style='font-size:12px' align="center">중간에 빈 값이 있는 이진트리의 배열 표현</figcaption>
</p>
1차원 배열로 이진트리를 표현할 때는 0번째 인덱스를 비워두고 첫번째 인덱스부터 루트값이 들어간다
첫번째 인덱스부터 루트값을 넣으면은 인덱스 위치로 찾을 수 있는 몇가지 연산이 있기 때문.
<br><br>



<p align="center">  
  <img src="https://velog.velcdn.com/images/p-samename/post/c68db614-720d-46a7-b4ce-6c0e5174d2e9/image.png" width='500px'> 
  <figcaption style='font-size:12px' align="center">이진트리의 연산</figcaption>
</p>

<h4 id="2-이진트리의-연산">2) 이진트리의 연산</h4>
<p>i번재 인덱스에 들어있는 노드의 부모는 i/2연산을 한 인덱스의 위치에 들어가 있게 되고,
노드 i의 왼쪽 자식은 i*2를한 인덱스에 들어있다.
노드 i의 오른쪽 자식은 i*2+1을한 위치의 인덱스에서 찾을 수 있다.
배열로 표현된 트리에서 6번째 노드의 부모는 나누기2를 한 3번째 인덱스에 위치하게 되고,
노드 2의 왼쪽 자식 노드는 2*2를한 4번째 위치에서 찾을 수 있다는 것을 알 수 있다.</p>
]]></description>
        </item>
    </channel>
</rss>