<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>96_inggu.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 22 Apr 2025 05:59:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>96_inggu.log</title>
            <url>https://velog.velcdn.com/images/96_inggu/profile/e9b4833b-73c3-45fb-ab15-2dd9b7b7b1a2/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 96_inggu.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/96_inggu" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[카페24 가상호스팅에서 Node.js 개발 환경 구축하기 -2]]></title>
            <link>https://velog.io/@96_inggu/%EC%B9%B4%ED%8E%9824-%EA%B0%80%EC%83%81%ED%98%B8%EC%8A%A4%ED%8C%85%EC%97%90%EC%84%9C-Node.js-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-2</link>
            <guid>https://velog.io/@96_inggu/%EC%B9%B4%ED%8E%9824-%EA%B0%80%EC%83%81%ED%98%B8%EC%8A%A4%ED%8C%85%EC%97%90%EC%84%9C-Node.js-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-2</guid>
            <pubDate>Tue, 22 Apr 2025 05:59:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>안녕하세요! 오늘은 이전 포스팅에 이어서 사용하게될 bun과 pm2 , nginx와 ssl 인증서 설치까지 포스팅해보려고합니다</p>
</blockquote>
<p>🌟 이 포스팅에서 다룰 내용
이번 포스팅에서는 실제 프로덕션 환경에서 필요한 모든 설정을 다룹니다:
Bun으로 더 빠른 JavaScript 실행환경 구축하기
PM2로 서버 무중단 운영하기
Nginx 웹서버 설정과 SSL 인증서 적용하기</p>
<h3 id="1사용된-플러그인">1.사용된 플러그인</h3>
<h4 id="1-bun">1. Bun</h4>
<p>JavaScript/TypeScript 런타임
Node.js보다 빠른 실행 속도
내장 패키지 매니저 제공</p>
<h4 id="2-pm2">2. PM2</h4>
<p>Node.js 애플리케이션을 위한 프로세스 매니저
무중단 서비스 운영 가능
로그 관리 및 모니터링 기능 제공</p>
<h4 id="3-nginx">3. Nginx</h4>
<p>고성능 웹 서버
리버스 프록시 기능
로드 밸런싱 지원</p>
<h4 id="4-ssl-인증서-설정">4. SSL 인증서 설정</h4>
<p>Let&#39;s Encrypt와 Certbot
무료 SSL 인증서 제공
자동 갱신 가능
보안 연결 지원</p>
<h3 id="2-플러그인-설치">2. 플러그인 설치</h3>
<h4 id="1-bun-설치-및-설정">1. Bun 설치 및 설정</h4>
<pre><code class="language-typescript"># Bun 설치
curl -fsSL https://bun.sh/install | bash

# 환경변수 설정
export BUN_INSTALL=&quot;$HOME/.bun&quot;
export PATH=$BUN_INSTALL/bin:$PATH

# 설치 확인
bun --version</code></pre>
<h4 id="2-pm2를-통한-프로세스-관리">2. PM2를 통한 프로세스 관리</h4>
<pre><code class="language-typescript"># PM2 전역 설치
npm install -g pm2

# React 프로젝트 빌드 후 PM2로 서비스 시작
pm2 serve dist 3001 --name &quot;cesium-front&quot; --spa

# 주요 PM2 명령어
pm2 list                    # 프로세스 목록 확인
pm2 stop cesium-front       # 프로세스 중지
pm2 restart cesium-front    # 프로세스 재시작
pm2 delete cesium-front     # 프로세스 삭제
pm2 logs cesium-front       # 로그 확인
pm2 startup                 # 서버 재부팅시 자동 시작 설정
pm2 save                    # 현재 실행 중인 프로세스 목록 저장</code></pre>
<h4 id="3-nginx-설치-및-설정">3. Nginx 설치 및 설정</h4>
<pre><code class="language-typescript"># Nginx 설치
sudo apt update
sudo apt install -y nginx

# Nginx 상태 확인
sudo systemctl status nginx

# 기본 설정 파일 백업
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup

# 사이트 설정
sudo nano /etc/nginx/sites-available/your-domain.conf

# 설정 예시
server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &#39;upgrade&#39;;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

# 설정 활성화
sudo ln -s /etc/nginx/sites-available/your-domain.conf /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default

# 설정 테스트 및 재시작
sudo nginx -t
sudo systemctl restart nginx</code></pre>
<h4 id="4-ssl-인증서-설정-1">4. SSL 인증서 설정</h4>
<pre><code class="language-typescript"># Certbot 설치
sudo apt install -y certbot python3-certbot-nginx

# SSL 인증서 발급
sudo certbot --nginx -d your-domain.com

# 자동 갱신 테스트
sudo certbot renew --dry-run

# Nginx SSL 설정 예시
server {
    listen 443 ssl;
    server_name your-domain.com;

    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

    # SSL 설정
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    location / {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &#39;upgrade&#39;;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}</code></pre>
<p>지금까지 카페24 가상호스팅 서버에 개발 환경을 구축하는 방법을 기록과 함께 포스팅해봤습니다!</p>
<p>긴 글 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[카페24 가상호스팅에서 Node.js 개발 환경 구축하기 -1]]></title>
            <link>https://velog.io/@96_inggu/%EC%B9%B4%ED%8E%9824-%EA%B0%80%EC%83%81%ED%98%B8%EC%8A%A4%ED%8C%85%EC%97%90%EC%84%9C-Node.js-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@96_inggu/%EC%B9%B4%ED%8E%9824-%EA%B0%80%EC%83%81%ED%98%B8%EC%8A%A4%ED%8C%85%EC%97%90%EC%84%9C-Node.js-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Tue, 22 Apr 2025 05:38:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>안녕하세요! 오늘은 회사에서 세팅하게된 카페24 가상호스팅 서버에 Node.js 개발 환경을 구축하는 방법을 기록과 함께 정리하려고합니다 !</p>
</blockquote>
<h3 id="1사용된-플러그인">1.사용된 플러그인</h3>
<h4 id="1-git">1. Git</h4>
<ul>
<li>버전 관리 시스템</li>
<li>코드 변경사항 추적 및 협업 가능</li>
<li>GitHub/GitLab 등의 원격 저장소 활용 가능</li>
</ul>
<ol start="2">
<li>Zsh &amp; Oh My Zsh<h4 id="zsh-bash보다-강력한-기능을-제공하는-셸">Zsh: Bash보다 강력한 기능을 제공하는 셸</h4>
</li>
</ol>
<ul>
<li>Oh My Zsh: Zsh 설정을 관리하는 프레임워크</li>
<li>주요 장점:<ul>
<li>강력한 자동 완성</li>
<li>다양한 플러그인과 테마</li>
<li>Git 통합 기능</li>
<li>명령어 히스토리 검색 개선<h4 id="3-nvm-node-version-manager">3. NVM (Node Version Manager)</h4>
</li>
</ul>
</li>
<li>Node.js 버전 관리 도구</li>
<li>프로젝트별 다른 Node.js 버전 사용 가능</li>
<li>버전 업그레이드/다운그레이드 용이<h4 id="4-nodejs">4. Node.js</h4>
</li>
<li>JavaScript 런타임 환경</li>
<li>서버 사이드 JavaScript 실행</li>
<li>npm(Node Package Manager) 포함</li>
</ul>
<h3 id="2-플러그인-설치">2. 플러그인 설치</h3>
<h4 id="1-git-설치-및-설정">1. Git 설치 및 설정</h4>
<pre><code class="language-typescript"># Git 설치 (curl이 의존성 패키지로 자동 설치됨)
sudo apt install -y git

# Git 기본 설정
git config --global user.name &quot;Your Name&quot;
git config --global user.email &quot;your.email@example.com&quot;</code></pre>
<h4 id="2-zsh--oh-my-zsh-설치">2. Zsh &amp; Oh My Zsh 설치</h4>
<pre><code class="language-typescript"># Zsh 설치
sudo apt install -y zsh

# Oh My Zsh 설치
sh -c &quot;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&quot;

# Zsh를 기본 셸로 설정
chsh -s $(which zsh)</code></pre>
<h3 id="3-nodejs-환경-구축">3. Node.js 환경 구축</h3>
<h4 id="1-nvm-설치">1. NVM 설치</h4>
<pre><code class="language-typescript"># NVM 설치
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

# Zsh 설정 파일에 NVM 환경변수 추가
echo &#39;export NVM_DIR=&quot;$HOME/.nvm&quot;&#39; &gt;&gt; ~/.zshrc
echo &#39;[ -s &quot;$NVM_DIR/nvm.sh&quot; ] &amp;&amp; \. &quot;$NVM_DIR/nvm.sh&quot;&#39; &gt;&gt; ~/.zshrc
echo &#39;[ -s &quot;$NVM_DIR/bash_completion&quot; ] &amp;&amp; \. &quot;$NVM_DIR/bash_completion&quot;&#39; &gt;&gt; ~/.zshrc

# 설정 적용
source ~/.zshrc</code></pre>
<h4 id="2nodejs-lts-설치">2.Node.js LTS 설치</h4>
<pre><code class="language-typescript"># Node.js LTS 버전 설치
nvm install --lts

# 설치 확인
node --version
npm --version</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 UI 디자인 AI 툴 비교: v0.dev, Readdy, Lovable]]></title>
            <link>https://velog.io/@96_inggu/%EC%9B%B9-UI-%EB%94%94%EC%9E%90%EC%9D%B8-AI-%ED%88%B4-%EB%B9%84%EA%B5%90-v0.dev-Readdy-Lovable</link>
            <guid>https://velog.io/@96_inggu/%EC%9B%B9-UI-%EB%94%94%EC%9E%90%EC%9D%B8-AI-%ED%88%B4-%EB%B9%84%EA%B5%90-v0.dev-Readdy-Lovable</guid>
            <pubDate>Fri, 28 Mar 2025 05:06:54 GMT</pubDate>
            <description><![CDATA[<p>웹 UI 디자인 작업에 혁신을 가져올 AI 도구들이 계속해서 등장하고 있습니다. 
실제로 제가 작업하면서 사용하는 ai가 여러개있는데 그중 UI/UX 작업때 사용하는 ai는 v0.dev와 딥시크를 종종사용하곤 했는데요 이번에 이렇게 ai를 비교하는 타 포스팅을보고 저도 확실히 정리해서 실 사용시 어떤 ai가 좋은지 파악하는겸해서 블로그 포스팅을 하려고합니다
이번 포스팅에서는 현재 주목받고 있는 세 가지 AI 웹 디자인 도구 - v0.dev, Readdy, Lovable을 비교 분석해보겠습니다.</p>
<p><a href="https://mag.surfit.io/webflow-vs-lovable-build-war/">https://mag.surfit.io/webflow-vs-lovable-build-war/</a>
해당 포스팅에서 영감을 받아서 제작했습니다</p>
<p>하나의 동일한 프롬프트를 입력했을때 각 3가지툴이 구현한 자료로 비교를 진행했습니다.</p>
<blockquote>
<h3 id="생성소요시간">생성소요시간</h3>
</blockquote>
<h3 id="v0dev-2분">v0.dev (2분)</h3>
<p>  <img src="https://velog.velcdn.com/images/96_inggu/post/89837bf9-5011-42bc-b4cc-fb75790e529c/image.png" alt=""></p>
<h3 id="readdy-생성-소요시간-4분">Readdy 생성 소요시간 (4분)</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/5a7645b7-a84e-45ef-a25d-a824d71a26e4/image.png" alt=""></p>
<h3 id="lovable-생성소요시간-5분-에러-1회-발생">Lovable 생성소요시간 (5분 에러 1회 발생)</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/9cf0435e-c790-422c-b212-82ccb8dea2b8/image.png" alt=""></p>
<p>가장 빠르고 안정적인건 v0.dev였지만 readdy는 2개를 4분만에 생성한점을 비교했을때
전반적으로 3가지의 툴다 안정적인 모습을 보여줬습니다</p>
<blockquote>
<h3 id="기능-구현-퀄리티">기능 구현 퀄리티</h3>
</blockquote>
<h3 id="v0dev">v0.dev</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/5be46b8d-9361-4a7b-8d25-b116cd7c117d/image.png" alt=""></p>
<p>여러 인터렉션이 존재하지만
사용되는건 상단에 탭 1개 정렬과 버튼 , 
필터기능은 작동 x <strong>구현 성공율 약 12% 1/8 작동</strong></p>
<h3 id="readdy">Readdy</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/f01ef82d-a9ec-47fc-8bfc-fa0f58d2494a/image.png" alt=""></p>
<p>상품카드에 위치한 위시리스트 1개, 카테고리 차트 작동 
그외에 탭이나,정렬기능 작동 x <strong>구현율 20% -&gt; 2/5 작동</strong></p>
<h3 id="lovable">Lovable</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/d04b4da9-7854-4234-9145-f6535533ed2d/image.png" alt="">
탭기능 작동 , 카테고리 분류 작동, 찜하기 작동 
클릭시 리렌더링 발생 *<em>구현율 약 80% -&gt; 5/6 작동 *</em></p>
<blockquote>
<h3 id="이미지-생성여부">이미지 생성여부</h3>
</blockquote>
<h3 id="v0dev-1">v0.dev</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/84679a2b-4c01-460d-b3e7-e30470c5b2d3/image.png" alt=""></p>
<p>이번 구현에는 보이지않지만 요청시 unsplash api 사용가능</p>
<h3 id="readdy-1">Readdy</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/20c6f56e-f257-4795-9a7e-877f29ffcf28/image.png" alt=""></p>
<p>Readdy의 경우 4분 사이에 AI가 이미지도 직접 제작하는 걸 볼수있습니다
링크를 보니 임시 호스팅중이고 다운로드가 가능합니다
조금 섬뜩한 사진도 있긴하지만 퀄리티면에선 상당히 높다고 볼수있습니다</p>
<h3 id="lovable-1">Lovable</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/6a6c5c00-c9a9-4c47-bc0a-364cca8890d6/image.png" alt="">
lovable의 경우 따로 요청없이도 unsplash api를 가져와서 이미지를 채워준 모습을 볼 수있습니다</p>
<blockquote>
<h3 id="코드-반환여부">코드 반환여부</h3>
</blockquote>
<h3 id="v0dev-2">v0.dev</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/6915cf3f-64e0-4066-9708-a1bd363f6924/image.png" alt=""></p>
<p>v0.dev경우도 여러번 입력시 또는 프롬프트를 조금 더 정교하게 요청시 컴포넌트를 분리해주는 
모습을 보긴했지만 동일한 단일프롬프트 에서는 컴포넌트화가 아닌 하나의 
한묶음의 리액트 코드 jsx 반환만 반환되었네요
<strong>리액트 코드 jsx 반환 컴포넌트화 x 웹 빌더 및 호스팅가능웹 빌더 및 호스팅가능</strong></p>
<h3 id="readdy-2">Readdy</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/35373e9c-49bc-42a3-8c0e-67d5051e9320/image.png" alt=""></p>
<p>Readdy의 경우도 v0.dev와 크게 차이는 없습니다</p>
<h3 id="lovable-2">Lovable</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/028250cc-86dd-481e-9125-848c6621ebfc/image.png" alt=""></p>
<p>lovable의 경우 명확한 컴포넌트화, 리액트 코드 jsx 반환 , 자체웹 빌더 및 호스팅가능 , 추후 깃허브 레포지토리 연결 및 슈퍼베이스 DB연동가능등의 타 사이트에 비해 안내도 명확하게 되어있고 UI구현 뿐만아니라 웹빌더로써의 성능도 아주 높게 구현해주는걸 볼 수 있습니다</p>
<h3 id="결론">결론</h3>
<p>세 가지 AI 디자인 도구는 각각 다른 강점을 가지고 있습니다:
v0.dev: 개발자 친화적이며 다양한 UI 컴포넌트 생성과 빠른 구현의 강점
Readdy: 사진 ai 커스텀제작 등 전반적으로 안정적임
Lovable: 높은 기능 구현률과 세련된 UI디자인 그리고 빌더로써의 높은 가능성도 보여줌</p>
<p><strong>lovable이 첫시도에선 에러가 한번 있긴했지만 그후엔 에러가 발생하지않아 전반적으로 가장 높은 사용성을 보여준것같습니다</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[대용량 파일 깃허브에 업로드하기 (feat. Git LFS)]]></title>
            <link>https://velog.io/@96_inggu/%EB%8C%80%EC%9A%A9%EB%9F%89-%ED%8C%8C%EC%9D%BC-%EA%B9%83%ED%97%88%EB%B8%8C%EC%97%90-%EC%97%85%EB%A1%9C%EB%93%9C%ED%95%98%EA%B8%B0-feat.-Git-LFS</link>
            <guid>https://velog.io/@96_inggu/%EB%8C%80%EC%9A%A9%EB%9F%89-%ED%8C%8C%EC%9D%BC-%EA%B9%83%ED%97%88%EB%B8%8C%EC%97%90-%EC%97%85%EB%A1%9C%EB%93%9C%ED%95%98%EA%B8%B0-feat.-Git-LFS</guid>
            <pubDate>Wed, 05 Mar 2025 06:10:49 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요! 오늘은 제가 개발하면서 겪은 대용량 파일 관리에 대한 이슈를 공유해보려고 합니다.</p>
<h3 id="🤔-문제-상황">🤔 문제 상황</h3>
<p>프로젝트를 진행하면서 3D 모델링 파일(.splat)을 깃허브에 올려야 하는 상황이 생겼는데요.
파일 크기가 200MB라서 깃허브에 바로 올리기가 힘들었습니다.</p>
<p>깃허브 업로드 용량 제한사항
무료 플랜: 저장소당 총 용량 제한 100GB
단일 파일 크기 제한: 100MB
Git LFS 사용시 최대 2GB</p>
<p>먼저 Git LFS를 사용했습니다</p>
<h4 id="💡-git-lfslarge-file-storage란">💡 Git LFS(Large File Storage)란?</h4>
<p>Git LFS는 대용량 파일을 효율적으로 관리할 수 있게 해주는 Git 확장 기능입니다.
대용량 파일을 별도의 서버에 저장하고, Git 저장소에는 파일에 대한 참조만 저장합니다.</p>
<p>😅 시도했던 방법들</p>
<ol>
<li>Git LFS 시도</li>
</ol>
<pre><code># Git LFS 설치 및 설정
git lfs install
git lfs track &quot;*.splat&quot;
git add .gitattributes
git commit -m &quot;Git LFS 설정 추가&quot;

# 파일 추가 시도
git add public/data/*.splat
git commit -m &quot;대용량 파일 추가&quot;
git push</code></pre><p>결과: 실패 😭
기존에 사용중이던 레포지토리라 이전에 lfs를 사용하려다 실패한 이력이 있어서 
더 문제를 해결하는데 어려움이 있네요</p>
<pre><code>remote: error: File public/data/model.splat is 202.05 MB; this exceeds GitHub&#39;s file size limit of 100.00 MB</code></pre><h3 id="💡-해결방법-aws-s3-활용">💡 해결방법: AWS S3 활용</h3>
<p>결국 AWS S3를 사용하기로 했습니다.</p>
<p>aws s3 계정생성 및 버킷 생성
버킷을 로컬과 배포서버에서 불러오기위해선
아래와같이 버킷 CORS 설정과 정책을 설정해줘야됩니다.</p>
<ol>
<li><p>S3 버킷 CORS설정</p>
<pre><code>[
 {
     &quot;AllowedHeaders&quot;: [&quot;*&quot;],
     &quot;AllowedMethods&quot;: [&quot;GET&quot;],
     &quot;AllowedOrigins&quot;: [
         &quot;http://localhost:3001&quot;,
         &quot;https://your-domain.vercel.app&quot;
     ],
     &quot;ExposeHeaders&quot;: [],
     &quot;MaxAgeSeconds&quot;: 3000
 }
]</code></pre></li>
<li><p>S3 버킷 정책설정</p>
<pre><code>{
 &quot;Version&quot;: &quot;2012-10-17&quot;,
 &quot;Statement&quot;: [
     {
         &quot;Sid&quot;: &quot;AllowSpecificOrigins&quot;,
         &quot;Effect&quot;: &quot;Allow&quot;,
         &quot;Principal&quot;: &quot;*&quot;,
         &quot;Action&quot;: &quot;s3:GetObject&quot;,
         &quot;Resource&quot;: &quot;arn:aws:s3:::your-bucket-name/*&quot;,
         &quot;Condition&quot;: {
             &quot;StringLike&quot;: {
                 &quot;aws:Referer&quot;: [
                     &quot;http://localhost:3001/*&quot;,
                     &quot;https://your-domain.vercel.app/*&quot;
                 ]
             }
         }
     }
 ]
}</code></pre><p>이와같이 설정하니 정상적으로 버킷 스토리지에 있는 파일을 불러올 수 있었습니다</p>
</li>
</ol>
<h3 id="🤔-추가로-발견된-문제">🤔 추가로 발견된 문제</h3>
<h4 id="기존-로컬로-파일렌더링시-속도841ms-492mb-기준">기존 로컬로 파일렌더링시 속도(841ms 49.2MB 기준)</h4>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/9cfb3fbc-ba37-4878-9dd2-35202609dfd0/image.png" alt=""></p>
<h4 id="aws-s3로-파일렌더링시-속도평균-15초-492mb-기준">AWS S3로 파일렌더링시 속도(평균 15초 49.2MB 기준)</h4>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/0af7cf3f-e1a8-43a3-b08a-a5352757a078/image.png" alt=""></p>
<p> 성능 비교
로컬 vs S3 로딩 속도 비교
로컬 환경: 50MB 파일 렌더링 시간 0.8초
S3 환경: 50MB 파일 다운로드 + 렌더링 시간 평균 15초</p>
<p>이같이 s3로 사용하자니 파일 다운로드시간이 대폭 상승해 사용하기엔 어려움이 발생했습니다.
200mb인 파일도 있기때문에 2분이 넘을것으로 예상해 결국 aws s3도 포기하고 다시한번
git LFS에 도전한결과</p>
<p>각종 캐시문제나 이전 커밋들이 방해가 된다고 판단해 새로운 레포지토리를 생성해 
레포지토리 세팅을 마치고 파일을 전부 이전 레포지토리에서 다운받은 후 진행했습니다</p>
<pre><code>Git LFS 설치
git lfs install

추적할 파일 확장자 설정
git lfs track &quot;*.splat&quot;

.gitattributes 파일 커밋
git add .gitattributes
git commit -m &quot;Configure Git LFS&quot;

파일 추가 및 커밋
git add your-large-file.splat
git commit -m &quot;Add large file&quot;
git push origin main</code></pre><p>레포지토리를 새로 생성하니 정상적으로 깃허브에 올라간걸 확인할 수 있었습니다 
<img src="https://velog.velcdn.com/images/96_inggu/post/4c63dd3d-d39e-4013-9ce6-e0f6b54c6520/image.png" alt=""></p>
<h3 id="💡주의사항">💡주의사항</h3>
<p>이번 Git LFS를 사용하면서 느낀건 초기 설정 순서가 중요합니다
Git LFS 설치 후 .gitattributes 설정을 먼저 해야 합니다
대용량 파일을 추가하기 전에 LFS 트래킹이 제대로 설정되어 있는지 꼭 확인하세요</p>
<h3 id="🤔-결론">🤔 결론</h3>
<p>Git LFS를 사용하면 2GB까지의 파일을 깃허브에 안전하게 업로드할 수 있습니다
초기 설정만 제대로 하면 대용량 파일 관리가 매우 편리합니다
무료 계정의 경우 저장소 용량과 대역폭 제한이 있으므로 주의가 필요합니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js에서 검색엔진 최적화(SEO) 구현하기]]></title>
            <link>https://velog.io/@96_inggu/Next.js%EC%97%90%EC%84%9C-%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84-%EC%B5%9C%EC%A0%81%ED%99%94SEO-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@96_inggu/Next.js%EC%97%90%EC%84%9C-%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84-%EC%B5%9C%EC%A0%81%ED%99%94SEO-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 01 Dec 2024 23:37:12 GMT</pubDate>
            <description><![CDATA[<h3 id="nextjs에서-검색엔진-최적화seo-구현하기">Next.js에서 검색엔진 최적화(SEO) 구현하기</h3>
<blockquote>
<h3 id="목차">목차</h3>
</blockquote>
<ul>
<li>동적 sitemap 생성</li>
<li>메타데이터 최적화</li>
<li>구조화된 데이터 적용</li>
<li>google search 콘솔 설정</li>
</ul>
<h3 id="1-개요">1. 개요</h3>
<p>Next.js 프로젝트에서 검색엔진 최적화를 구현하여 구글 검색엔진 노출을 향상시키는 방법을 기록합니다</p>
<h3 id="2-디렉토리-구조">2. 디렉토리 구조</h3>
<pre><code>src/
├── app/
│   ├── [locale]/
│   │   └── (view)/
│   │       └── product/
│   │           └── [id]/
│   │               ├── page.tsx       # 상품 상세 페이지
│   │               └── metadata.ts    # 동적 메타데이터
│   ├── sitemap.ts                     # 동적 사이트맵 생성
│   └── layout.tsx                     # 기본 메타데이터
├── types/
│   └── product.ts                     # 타입 정의
└── public/
    └── robots.txt </code></pre><h3 id="3-패키지-설치-및-설정">3. 패키지 설치 및 설정</h3>
<p>next.js 13버전 이상부터는 기존의 next-sitemap 등의 추가 라이브러리 설치 없이 내장되어있습니다.</p>
<h3 id="4-구현-내용">4. 구현 내용</h3>
<p>동적 사이트맵 생성</p>
<pre><code class="language-typescript">//app/sitemap.ts

import { MetadataRoute } from &#39;next&#39;;
import { IMainProduct, PaginatedProductResponse } from &#39;@/types/product&#39;;

export default async function sitemap(): Promise&lt;MetadataRoute.Sitemap&gt; {
  const productsResponse = await fetch(
    &#39;https://{도메인명}/api&#39;,
    { next: { revalidate: 3600 } }
  ).then(res =&gt; res.json()) as PaginatedProductResponse;

  const baseUrl = &#39;도메인명&#39;;

  const staticPages = [&#39;&#39;, &#39;/ja&#39;, &#39;/ko&#39;, &#39;/products&#39;].map((route) =&gt; ({
    url: `${baseUrl}${route}`,
    lastModified: new Date(),
    changeFrequency: &#39;daily&#39; as const,
    priority: 1,
  }));

  const productPages = productsResponse.products.map((product: IMainProduct) =&gt; ({
    url: `${baseUrl}/ja/product/${product.id}`,
    lastModified: new Date(product.createdAt),
    changeFrequency: &#39;daily&#39; as const,
    priority: 0.8,
  }));

  return [...staticPages, ...productPages];
}</code></pre>
<pre><code>///root layout.tsx
export const metadata: Metadata = {
  title: {
    template: &#39;%s | MyBrand&#39;,
    default: &#39;MyBrand - Fashion E-commerce&#39;,
  },
  description: &#39;Discover trending fashion items from your favorite celebrities. Shop official merchandise and celebrity-worn items.&#39;,
  keywords: [
    &#39;Fashion Store&#39;,
    &#39;Celebrity Fashion&#39;,
    &#39;Korean Fashion&#39;,
    &#39;Brand Clothes&#39;,
    &#39;Fashion E-commerce&#39;,
    &#39;Style Guide&#39;,
    &#39;Trending Fashion&#39;,
    &#39;Celebrity Style&#39;,
    &#39;Fashion Trends&#39;,
    &#39;Online Shopping&#39;,
    &#39;Designer Clothes&#39;,
    &#39;Fashion Brand&#39;,
    &#39;Style Tips&#39;,
    &#39;Fashion Collection&#39;,
    &#39;Seasonal Fashion&#39;,
  ],
  openGraph: {
    title: &#39;MyBrand - Fashion E-commerce&#39;,
    description: &#39;Discover trending fashion items from your favorite celebrities.&#39;,
    url: &#39;https://example.com&#39;,
    siteName: &#39;MyBrand&#39;,
    locale: &#39;en_US&#39;,
    type: &#39;website&#39;,
    images: [
      {
        url: &#39;https://example.com/images/logo.png&#39;,
        width: 1200,
        height: 630,
        alt: &#39;MyBrand - Fashion E-commerce&#39;,
      },
    ],
  },
  twitter: {
    card: &#39;summary_large_image&#39;,
    title: &#39;MyBrand - Fashion E-commerce&#39;,
    description: &#39;Discover trending fashion items from your favorite celebrities.&#39;,
    images: [&#39;https://example.com/images/logo.png&#39;],
  },
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      &#39;max-video-preview&#39;: -1,
      &#39;max-image-preview&#39;: &#39;large&#39;,
      &#39;max-snippet&#39;: -1,
    },
  },
  verification: {
    google: &#39;google-site-verification-code&#39;,
  },
};</code></pre><pre><code>//robot.txt
User-agent: *
Allow: /
Allow: /ja/
Allow: /ko/
Allow: /ja/product/
Allow: /ko/product/

Disallow: /api/
Disallow: /admin/

Sitemap: https://test.com/sitemap.xml

Crawl-delay: 1

기존 div태그 최상위에 Schema.org 마크업 입력

&lt;&gt;
    &lt;main&gt;
        &lt;script
          type=&quot;application/ld+json&quot;
          dangerouslySetInnerHTML={{
            __html: JSON.stringify({
              &#39;@context&#39;: &#39;https://schema.org&#39;,
              &#39;@type&#39;: &#39;Organization&#39;,
              name: &#39;MyBrand&#39;,
              alternateName: &#39;MyBrandShop&#39;,
              url: &#39;https://example.com&#39;,
              logo: &#39;https://example.com/images/logo.png&#39;,
              sameAs: [
                &#39;https://instagram.com/mybrand&#39;,
                &#39;https://twitter.com/mybrand&#39;,
              ],
            }),
          }}
        /&gt;
&lt;/main&gt;
&lt;/&gt;</code></pre><h3 id="5-사용-방법">5. 사용 방법</h3>
<ul>
<li><p>Google Search Console 설정</p>
</li>
<li><p>사이트 소유권 확인</p>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/1d32e055-4ba5-410c-aaca-572e8c508d9d/image.png" alt=""></p>
</li>
<li><p>sitemap.xml 제출</p>
</li>
<li><p>URL 검사 및 색인 생성 요청</p>
</li>
<li><p>메타데이터 확인</p>
</li>
</ul>
<h3 id="6-검색결과">6. 검색결과</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/8f05c0c0-6e75-40ca-83fd-2a34993c44eb/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/369ed4bd-62e2-4f9b-94d5-c829446253ed/image.png" alt=""></p>
<p>이러한 구현으로 최종적으로 구글검색엔진에서의 상품 노출이 및 링크 복사시 메타데이터가 노출되며, 사용자가 더 쉽게 상품을 찾을 수 있게 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nginx에서 Let's Encrypt SSL 인증서 자동 갱신 설정하기]]></title>
            <link>https://velog.io/@96_inggu/Nginx%EC%97%90%EC%84%9C-Lets-Encrypt-SSL-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EC%9E%90%EB%8F%99-%EA%B0%B1%EC%8B%A0-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@96_inggu/Nginx%EC%97%90%EC%84%9C-Lets-Encrypt-SSL-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EC%9E%90%EB%8F%99-%EA%B0%B1%EC%8B%A0-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 04 Nov 2024 01:05:32 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="1-문제-상황">1. 문제 상황</h3>
<p>Let&#39;s Encrypt SSL 인증서는 90일마다 만료되며, 수동으로 갱신하는 것은 번거롭습니다. 특히 Nginx를 사용하는 경우 포트 80 충돌로 인한 갱신 실패가 자주 발생할 수 있습니다.</p>
</blockquote>
<h3 id="2-해결-방법">2. 해결 방법</h3>
<p>2.1. Certbot Nginx 플러그인 설치</p>
<pre><code>sudo apt-get install python3-certbot-nginx</code></pre><p>2.2. 인증서 갱신 설정 수정</p>
<pre><code>sudo nano /etc/letsencrypt/renewal/your-domain.com.conf</code></pre><pre><code>//설정파일 내용
version = 1.21.0
archive_dir = /etc/letsencrypt/archive/your-domain.com
cert = /etc/letsencrypt/live/your-domain.com/cert.pem
privkey = /etc/letsencrypt/live/your-domain.com/privkey.pem
chain = /etc/letsencrypt/live/your-domain.com/chain.pem
fullchain = /etc/letsencrypt/live/your-domain.com/fullchain.pem

[renewalparams]
authenticator = nginx
installer = nginx
account = your-account-hash
server = https://acme-v02.api.letsencrypt.org/directory
</code></pre><p>2.3. 자동 갱신 설정</p>
<pre><code>sudo crontab -e</code></pre><p>다음 내용 추가:</p>
<pre><code>0 0,12 * * * /usr/bin/certbot renew --quiet --nginx</code></pre><p>2.4. 설정 테스트</p>
<pre><code># 갱신 테스트 (실제 갱신하지 않음)
sudo certbot renew --nginx --dry-run</code></pre><h3 id="3-설정-확인">3. 설정 확인</h3>
<p>3.1. 인증서 만료일 확인</p>
<pre><code>sudo tail -f /var/log/letsencrypt/letsencrypt.log</code></pre><p>3.2. 자동 갱신 로그 확인</p>
<pre><code>sudo tail -f /var/log/letsencrypt/letsencrypt.log</code></pre><p>3.3. Cron 작업 확인</p>
<pre><code>sudo crontab -l</code></pre><h3 id="4-주요-특징">4. 주요 특징</h3>
<ul>
<li>Nginx 플러그인을 사용하여 자동으로 웹서버 관리</li>
<li>매일 0시와 12시에 갱신 시도</li>
<li>만료 30일 전부터 자동 갱신 시도</li>
<li>로그를 통한 갱신 상태 모니터링 가능</li>
</ul>
<h3 id="5-문제-해결">5. 문제 해결</h3>
<p>만약 갱신이 실패하는 경우:</p>
<pre><code># 수동 갱신 시도
sudo certbot renew --force-renewal --nginx

# 로그 확인
sudo tail -f /var/log/letsencrypt/letsencrypt.log</code></pre><h3 id="6-주의사항">6. 주의사항</h3>
<p>방화벽 설정에서 포트 80과 443이 열려있어야 함
Nginx 설정 파일에 SSL 관련 설정이 올바르게 되어있어야 함
도메인의 DNS 설정이 올바르게 되어있어야 함
이제 SSL 인증서가 자동으로 갱신되며, 만료 걱정 없이 안전한 웹 서비스를 운영할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js에서 Sharp를 활용한 이미지 최적화 구현]]></title>
            <link>https://velog.io/@96_inggu/Next.js%EC%97%90%EC%84%9C-Sharp%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@96_inggu/Next.js%EC%97%90%EC%84%9C-Sharp%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Thu, 31 Oct 2024 02:27:40 GMT</pubDate>
            <description><![CDATA[<h3 id="nextjs-프로젝트에서-sharp를-사용해-이미지-최적화를-구현하여-성능을-향상시키는-방법을-기록합니다">Next.js 프로젝트에서 Sharp를 사용해 이미지 최적화를 구현하여 성능을 향상시키는 방법을 기록합니다</h3>
<blockquote>
<h3 id="목차">목차</h3>
</blockquote>
<ul>
<li>개요</li>
<li>디렉토리 구조</li>
<li>설치 및 설정</li>
<li>구현 내용</li>
<li>사용 방법</li>
<li>성능 개선 효과</li>
</ul>
<h3 id="1-개요">1. 개요</h3>
<p>이미지 최적화는 웹 성능에 큰 영향을 미치는 요소입니다. Sharp 라이브러리를 활용하여 다음과 같은 최적화를 구현했습니다:</p>
<ol>
<li>WebP 포맷 변환</li>
<li>이미지 크기 최적화</li>
<li>품질 조정</li>
<li>캐싱 전략</li>
<li>프로그레시브 로딩</li>
</ol>
<h3 id="2-디렉토리-구조">2. 디렉토리 구조</h3>
<pre><code class="language-typescript">src/
├── app/
│   └── api/
│       └── image/
│           └── route.ts        # 이미지 최적화 API 라우트
├── lib/
│   └── image/
│       ├── optimizer.ts        # 이미지 최적화 유틸리티
│       └── constants.ts        # 이미지 관련 상수
└── components/
    └── (view)/
        └── MainCard/
            └── index.tsx       # 이미지를 사용하는 컴포넌트</code></pre>
<h3 id="3-패키지-설치-및-설정">3. 패키지 설치 및 설정</h3>
<pre><code class="language-typescript">npm install sharp</code></pre>
<pre><code class="language-typescript">//next.config.js 

/** @type {import(&#39;next&#39;).NextConfig} */
const nextConfig = {
  images: {
      formats: [&#39;image/webp&#39;, &#39;image/avif&#39;], // AVIF 포맷도 지원
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    minimumCacheTTL: 60,
    dangerouslyAllowSVG: true,
    contentSecurityPolicy:
        &quot;default-src &#39;self&#39;; script-src &#39;none&#39;; sandbox;&quot;,
    domains: [
      &#39;product-images.kr.object.ncloudstorage.com&#39;,
      &#39;image.msscdn.net&#39;
    ],
    formats: [&#39;image/webp&#39;],
    minimumCacheTTL: 60,
  },
  experimental: {
    serverActions: true,
  },
}

module.exports = nextConfig</code></pre>
<h3 id="4-구현-내용">4. 구현 내용</h3>
<p>이미지 최적화 API 라우트</p>
<pre><code class="language-typescript">import { NextRequest, NextResponse } from &#39;next/server&#39;;
import sharp from &#39;sharp&#39;;

export const runtime = &#39;nodejs&#39;;

export async function GET(request: NextRequest) {
  try {
    const searchParams = request.nextUrl.searchParams;
    const imageUrl = searchParams.get(&#39;url&#39;);
    const width = parseInt(searchParams.get(&#39;width&#39;) || &#39;800&#39;);
    const quality = parseInt(searchParams.get(&#39;quality&#39;) || &#39;80&#39;);

    if (!imageUrl) {
      return new NextResponse(&#39;Image URL is required&#39;, { status: 400 });
    }

    const response = await fetch(imageUrl);
    const buffer = await response.arrayBuffer();

    const optimizedImage = await sharp(Buffer.from(buffer))
      .resize(width, null, {
        withoutEnlargement: true,
        fit: &#39;inside&#39;,
      })
      .webp({ quality })
      .toBuffer();

    return new NextResponse(optimizedImage, {
      headers: {
        &#39;Content-Type&#39;: &#39;image/webp&#39;,
        &#39;Cache-Control&#39;: &#39;public, max-age=31536000, immutable&#39;,
      },
    });
  } catch (error) {
    console.error(&#39;Image optimization error:&#39;, error);
    return new NextResponse(&#39;Image optimization failed&#39;, { status: 500 });
  }
}</code></pre>
<h3 id="이미지-최적화-유틸리티">이미지 최적화 유틸리티</h3>
<pre><code class="language-typescript">//constants.ts

export const IMAGE_QUALITY = 80;
export const DEFAULT_WIDTH = 800;
export const THUMBNAIL_WIDTH = 172;</code></pre>
<pre><code class="language-typescript">//optimizer.ts

import { IMAGE_QUALITY, DEFAULT_WIDTH } from &#39;./constants&#39;;

export const getOptimizedImageUrl = (
  originalUrl: string,
  width = DEFAULT_WIDTH,
  quality = IMAGE_QUALITY
) =&gt; {
  if (!originalUrl) return &#39;&#39;;

  if (originalUrl.startsWith(&#39;https://&#39;)) {
    return originalUrl;
  }

  const baseUrl = process.env.NEXT_PUBLIC_APP_URL || &#39;&#39;;
  const encodedUrl = encodeURIComponent(originalUrl);
  return `${baseUrl}/api/image?url=${encodedUrl}&amp;width=${width}&amp;quality=${quality}`;
};</code></pre>
<h3 id="5-컴포넌트에서-사용">5. 컴포넌트에서 사용</h3>
<pre><code class="language-typescript">import { getOptimizedImageUrl } from &#39;@/lib/image/optimizer&#39;;
import { THUMBNAIL_WIDTH, IMAGE_QUALITY } from &#39;@/lib/image/constants&#39;;

const MainCard: React.FC&lt;MainCardProps&gt; = ({ mainImg, ...props }) =&gt; {
  const optimizedImageUrl = getOptimizedImageUrl(
    mainImg,
    THUMBNAIL_WIDTH,
    IMAGE_QUALITY
  );

  return (
    &lt;Image
      src={optimizedImageUrl}
      alt={props.title}
      width={THUMBNAIL_WIDTH}
      height={180}
      quality={IMAGE_QUALITY}
      placeholder=&quot;blur&quot;
      blurDataURL={`data:image/svg+xml;base64,${toBase64(shimmer(172, 180))}`}
    /&gt;
  );
};</code></pre>
<h3 id="6-사용-방법">6. 사용 방법</h3>
<p>이미지 URL을 최적화된 URL로 변환:</p>
<pre><code class="language-typescript">const optimizedUrl = getOptimizedImageUrl(originalUrl, width, quality);</code></pre>
<h2 id="nextjs-image-컴포넌트에서-사용">Next.js Image 컴포넌트에서 사용:</h2>
<pre><code class="language-typescript">&lt;Image
  src={optimizedUrl}
  alt=&quot;이미지 설명&quot;
  width={width}
  height={height}
  quality={IMAGE_QUALITY}
  placeholder=&quot;blur&quot;
  blurDataURL={blurDataUrl}
/&gt;</code></pre>
<h3 id="개선-전">개선 전</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/14418de6-2f32-455e-9b3d-7da25bb98418/image.png" alt=""></p>
<h3 id="개선-후">개선 후</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/e8c73483-51b3-441c-a5c9-a36384d1c434/image.png" alt=""></p>
<h3 id="6-성능-개선-효과">6. 성능 개선 효과</h3>
<ul>
<li>이미지 크기 감소: 70~80% 용량 감소</li>
<li>로딩 속도 향상: WebP 포맷 사용으로 빠른 로딩</li>
<li>프로그레시브 로딩: 사용자 경험 향상</li>
<li>캐싱: 반복 요청 시 빠른 응답</li>
<li>서버 부하 감소: 최적화된 이미지 캐싱</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[네이버 클라우드와 Let's Encrypt를 이용한 HTTPS 구현]]></title>
            <link>https://velog.io/@96_inggu/%EB%84%A4%EC%9D%B4%EB%B2%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C%EC%99%80-Lets-Encrypt%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-HTTPS-%EA%B5%AC%ED%98%84-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@96_inggu/%EB%84%A4%EC%9D%B4%EB%B2%84-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C%EC%99%80-Lets-Encrypt%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-HTTPS-%EA%B5%AC%ED%98%84-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Sun, 04 Aug 2024 23:44:56 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요! 오늘은 네이버 클라우드 환경에서 Let&#39;s Encrypt를 사용하여 SSL 인증서를 발급받고 HTTPS를 구현하는 방법에 대해 공유하려고 합니다. 이 과정에서 nginx, certbot, 프록시 서버 설정 등 다양한 기술을 사용했는데, 이를 통해 얻은 경험과 해결한 문제들을 여러분과 나누고자 합니다.</p>
<blockquote>
<h3 id="http-vs-https-왜-https를-사용해야-하는가">HTTP vs HTTPS: 왜 HTTPS를 사용해야 하는가?</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/5c37bc7b-1bd2-4f6e-8100-c00a0cb8d2f1/image.png" alt=""></p>
<h4 id="http와-https의-차이">HTTP와 HTTPS의 차이:</h4>
<ul>
<li>HTTP: 평문 통신으로 데이터 노출 위험</li>
<li>HTTPS: SSL/TLS를 통한 암호화로 보안 강화</li>
</ul>
<h4 id="https-사용의-필요성">HTTPS 사용의 필요성:</h4>
<ul>
<li>데이터 보안: 중요 정보 보호</li>
<li>신뢰성 향상: 사용자에게 안전한 환경 제공</li>
<li>SEO 개선: 검색 엔진 최적화에 유리</li>
<li>브라우저 호환성: 최신 웹 기술 사용 가능</li>
</ul>
<h4 id="프론트엔드가-https인-경우-백엔드-서버도-https여야-안전한-통신이-가능합니다">프론트엔드가 HTTPS인 경우, 백엔드 서버도 HTTPS여야 안전한 통신이 가능합니다.</h4>
<p>현재 크롬에서는 &quot;onlyhttps&quot;는 기본적으로 활성화되어 있기때문에
버셀로 구축한 프론트는 https가 자동으로 배포가 되어있어서 서버가 https가 아닌경우에는
통신이 이루어지지않습니다.
이를 무시하고 백엔드가 HTTP를 사용하면 &quot;Mixed Content&quot; 상황이 발생하며, 다음과 같은 심각한 보안 이슈가 생길 수 있습니다:</p>
<ul>
<li><h4 id="중간자-공격-man-in-the-middle-attack-취약성">중간자 공격 (Man-in-the-Middle Attack) 취약성:</h4>
</li>
</ul>
<p>HTTP 통신은 암호화되지 않아 데이터가 평문으로 전송됩니다.
공격자가 네트워크 트래픽을 가로채 민감한 정보를 훔칠 수 있습니다.</p>
<ul>
<li><h4 id="데이터-무결성-손상">데이터 무결성 손상:</h4>
</li>
</ul>
<p>HTTP 통신은 변조 감지 메커니즘이 없어, 전송 중 데이터 변경을 확인하기 어렵습니다.
공격자가 악의적인 코드나 잘못된 정보를 주입할 수 있습니다.</p>
<ul>
<li><h4 id="세션-하이재킹">세션 하이재킹:</h4>
</li>
</ul>
<p>암호화되지 않은 세션 쿠키나 토큰이 노출될 수 있습니다.
공격자가 이를 이용해 사용자의 세션을 탈취하고 권한을 얻을 수 있습니다.</p>
<p>이러한 이유로, 프론트엔드와 백엔드 모두 HTTPS를 사용하는 것이 매우 중요합니다. 전체 통신 과정을 암호화함으로써 데이터의 기밀성, 무결성, 그리고 사용자의 프라이버시를 보호할 수 있습니다.</p>
<h3 id="네이버-클라우드-및-dns-세팅">네이버 클라우드 및 DNS 세팅</h3>
<p>저는 네이버 클라우드를 사용해서 서버를 배포했고 각 dns와 acg, 네임서버에 대한 입력값을 세팅해줘야됩니다</p>
<h4 id="네이버-클라우드-dnsglobal-dns-설정">네이버 클라우드 DNS(Global DNS) 설정</h4>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/ef96eb9a-9f05-4064-9fe9-b4f6d8571575/image.png" alt=""></p>
<ul>
<li>Host: @</li>
<li>Type: A 레코드</li>
<li>레코드값: [서버의 공인 IP 주소]
빈값과 www 둘다 입력해줘야됩니다</li>
</ul>
<h4 id="네이버-클라우드-acg-access-control-group-인바운드-규칙-설정">네이버 클라우드 ACG (Access Control Group) 인바운드 규칙 설정</h4>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/c559f34c-8655-4a89-af5a-8b97ffc05884/image.png" alt=""></p>
<ul>
<li>TCP 443 포트 (HTTPS)</li>
<li>TCP 80 포트 (HTTP)</li>
<li>TCP 3001 포트 (애플리케이션 서버)</li>
<li>TCP 22 포트 (SSH)</li>
</ul>
<p>사용하게될 포트 전부 acg에 입력해주면됩니다!</p>
<p>모든 포트에 대해 접근 소스가 0.0.0.0/0으로 설정되어 있어, 모든 IP에서의 접근을 허용하고 있습니다.</p>
<h4 id="구매한-dns-네임서버-설정">구매한 DNS 네임서버 설정<img src="https://velog.velcdn.com/images/96_inggu/post/017426c5-976d-4d71-b3b7-3da5ac9ec4f5/image.png" alt=""></h4>
<p>저같은 경우는 godaddy에 도메인을 구입했습니다
도메인 구매 사이트에서 다음 네임서버를 설정해야 합니다</p>
<h4 id="로그인-후---dns---네임서버">로그인 후 -&gt; dns -&gt; 네임서버</h4>
<ul>
<li>ns1-1.ns-ncloud.com</li>
<li>ns1-2.ns-ncloud.com</li>
</ul>
<blockquote>
<p>이같이 네이버 클라우드와 DNS, 네임서버에 대한 세팅 끝났습니다.
이제 ssh서버에 진입해서 nginx와 certbot, 프록시를 설정해주면됩니다</p>
</blockquote>
<h3 id="nginx">nginx</h3>
<p>nginx란 고성능 웹 서버이자 리버스 프록시 서버 세팅이 가능한 모듈입니다
certbot을 연결해 Let&#39;s Encrypt SSL/TLS 인증서를 발급할 수 있습니다.</p>
<p>3.1 nginx 설치 및 설정</p>
<pre><code class="language-javascript">sudo apt update
sudo apt install nginx

//nginx 설정진입 명령어
sudo nano /etc///nginx/sites-available/default

server {
    listen 80;
    server_name your_domain.com www.your_domain.com;
    # 추가 설정...
}

//https는 443 포트이며 포트:3000을 프록시 설정하면
//도메인 입력시 도메인:3000으로 이동
server {
    listen 443 ssl;
    server_name your_domain.com;

    ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;

    location / {
        proxy_pass http://localhost:3000; # Node.js 서버 포트
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &#39;upgrade&#39;;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

</code></pre>
<h3 id="주요-이슈">주요 이슈</h3>
<h4 id="certbot-실행-시-80포트-바인딩-문제">certbot 실행 시 80포트 바인딩 문제</h4>
<p>원인: nginx가 80포트 사용 중
해결: sudo systemctl stop nginx 후 certbot 실행</p>
<h4 id="ginx와-nodejs-서버-연동-문제">ginx와 Node.js 서버 연동 문제</h4>
<p>원인: 프록시 설정 오류
해결: nginx 설정 파일에서 proxy_pass 지시어 확인 및 수정</p>
<h4 id="클라우드-acg-설정-누락">클라우드 ACG 설정 누락</h4>
<p>문제: 외부에서 서버 접근 불가
해결: ACG에서 필요한 포트 개방 확인</p>
<p>결론
네이버 클라우드 환경에서 Let&#39;s Encrypt를 이용한 HTTPS 구현은 여러 단계와 주의사항이 필요합니다. nginx, certbot, 프록시 설정, ACG 관리 등 다양한 요소를 고려해야 하지만, 이를 통해 안전하고 신뢰할 수 있는 웹 서비스를 제공할 수 있습니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[mysql] MySQL 메서드로 데이터 패칭하기]]></title>
            <link>https://velog.io/@96_inggu/mysql-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@96_inggu/mysql-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 22 Apr 2024 05:12:02 GMT</pubDate>
            <description><![CDATA[<p>MySQL은 관계형 데이터베이스 관리 시스템(RDBMS)으로 다양한 메서드를 사용하여 데이터를 조작하고 분석할 수 있습니다. 데이터를 패칭하는 기본적인 메서드들을 예시를 함께 까먹지 않도록 남겨보려고 합니다.</p>
<p>목업 데이터 생성</p>
<pre><code class="language-sql">CREATE TABLE employees (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100),
    age INT,
    department VARCHAR(100),
    salary DECIMAL(10, 2),
    joined_date TIMESTAMP
);</code></pre>
<p>이제 테이블에 몇 명의 직원을 추가하겠습니다.</p>
<pre><code class="language-sql">INSERT INTO employees (name, age, department, salary, joined_date)
VALUES 
(&#39;John Doe&#39;, 30, &#39;Finance&#39;, 50000.00, NOW()),
(&#39;Jane Smith&#39;, 25, &#39;IT&#39;, 48000.00, &#39;2021-06-17&#39;),
(&#39;Emma Jones&#39;, 45, &#39;IT&#39;, 55000.00, &#39;2019-05-21&#39;),
(&#39;Michael Brown&#39;, 31, &#39;Marketing&#39;, 45000.00, &#39;2020-02-15&#39;);

SELECT 문
SELECT * FROM employees;
데이터 조회는 가장 기본적인 작업 중 하나입니다.
</code></pre>
<p>조건을 사용한 SELECT 문
특정 조건을 만족하는 행만 선택합니다.</p>
<pre><code class="language-javascript">SELECT * FROM employees WHERE department = &#39;IT&#39;;</code></pre>
<p>데이터의 업데이트 (UPDATE)
직원의 정보를 업데이트하는 방법입니다.</p>
<pre><code class="language-javascript">UPDATE employees SET salary = 51000 WHERE name = &#39;Jane Smith&#39;;</code></pre>
<p>데이터의 삭제 (DELETE)
데이터를 삭제하는 방법입니다.</p>
<pre><code class="language-javascript">DELETE FROM employees WHERE name = &#39;Michael Brown&#39;;
JOIN 사용
두 테이블을 연결하여 데이터를 조회합니다. 다음은 employees 테이블과 가상의 department 테이블을 JOIN하는 예입니다.</code></pre>
<p>집계 함수 (AVG, COUNT, MAX 등)
직원들의 평균 연봉을 계산합니다.</p>
<pre><code class="language-javascript">SELECT employees.name, department.dept_name
FROM employees
JOIN department ON employees.department = department.id;</code></pre>
<p>날짜 함수 사용
특정 월에 입사한 직원 수를 찾습니다.</p>
<pre><code class="language-javascript">SELECT AVG(salary) AS average_salary FROM employees;</code></pre>
<pre><code class="language-javascript">SELECT COUNT(*) FROM employees
WHERE MONTH(joined_date) = &#39;06&#39;;</code></pre>
<p>이러한 기본적인 SQL 명령들을 사용하여 MySQL 데이터베이스에서 다양한 작업을 수행할 수 있습니다. 각 명령의 결과와 상호 작용을 이해하면, 실제 데이터베이스 환경에서 효율적으로 데이터를 관리하고 문제를 해결할 수 있습니다.</p>
<p>JOIN 연산 예시
employees 테이블</p>
<table>
    <thead>
        <tr>
            <th>id</th>
            <th>name</th>
            <th>department</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1</td>
            <td>John Doe</td>
            <td>1</td>
        </tr>
        <tr>
            <td>2</td>
            <td>Jane Smith</td>
            <td>2</td>
        </tr>
        <tr>
            <td>3</td>
            <td>Emma Jones</td>
            <td>2</td>
        </tr>
        <tr>
            <td>4</td>
            <td>Michael Brown</td>
            <td>3</td>
        </tr>
    </tbody>
</table>

<p>department 테이블</p>
<table>
    <thead>
        <tr>
            <th>id</th>
            <th>dept_name</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1</td>
            <td>Finance</td>
        </tr>
        <tr>
            <td>2</td>
            <td>IT</td>
        </tr>
        <tr>
            <td>3</td>
            <td>Marketing</td>
        </tr>
    </tbody>
</table>


<pre><code>JOIN 연산 결과
SELECT employees.name, department.dept_name
FROM employees
JOIN department ON employees.department = department.id;</code></pre><ol start="3">
<li>최종 테이블 (JOIN 결과)</li>
</ol>
<p>이 쿼리는 employees 테이블의 department 필드와 department 테이블의 id 필드를 연결합니다. 이로 인해 각 직원의 이름과 그들이 속한 부서의 이름이 함께 나타납니다.</p>
<table>
    <thead>
        <tr>
            <th>name</th>
            <th>dept_name</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>John Doe</td>
            <td>Finance</td>
        </tr>
        <tr>
            <td>Jane Smith</td>
            <td>IT</td>
        </tr>
        <tr>
            <td>Emma Jones</td>
            <td>IT</td>
        </tr>
        <tr>
            <td>Michael Brown</td>
            <td>Marketing</td>
        </tr>
    </tbody>
</table>

]]></description>
        </item>
        <item>
            <title><![CDATA[Nest.js + prisma 환경구성]]></title>
            <link>https://velog.io/@96_inggu/%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@96_inggu/%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 07 Apr 2024 05:54:41 GMT</pubDate>
            <description><![CDATA[<img src="https://www.tomray.dev/static/images/nestjs-prisma/nestjs-prisma.png"/>
안녕하세요 오늘은 Nest.js + prisma를 사용하면서 알게된 내용을 공유하려고 합니다.
최근 맡게된 프로젝트에서 데이터 베이스를 다루게됐는데 백엔드 찍먹도 해볼겸 Nest.js를 구성했습니다. 우연히 글을 읽는 다른분들에게도 도움이 됐으면 좋겠습니다

<p>먼저 번들링 툴은 vite웹팩을 사용해서 구성했고 DB는 mysql을 사용했습니다</p>
<p>프론트엔드 기준에서 보자면 백엔드도 결국 db랑의 연동과 서버를 구성할뿐 코드를 빌드하고 배포하는 점에선 나름 구조는 동일합니다</p>
<p>프론트엔드 ➡ React // 백엔드 ➡ Nest.js / Java Spring
웹팩 ➡ Vite // 프론트 백 동일하게 사용
DB 연동 ➡ mysql
ORM ➡ Prisma (SQL Simplification)
-&gt; 구조에서 이같은 차이점이 있으며, 사용하게될 스택입니다.</p>
<h3 id="데이터베이스란">데이터베이스란?</h3>
<p>데이터베이스는 구조화된 데이터의 집합을 효율적으로 저장, 관리, 검색할 수 있는 시스템입니다. 다양한 유형의 데이터베이스가 있으며, 각각의 용도와 환경에 맞게 설계되었습니다. 데이터베이스는 일반적으로 데이터베이스 관리 시스템(DBMS)을 통해 관리됩니다.
<strong>-&gt; 온라인 쇼핑몰의 고객 정보 관리</strong></p>
<pre><code>{
  &quot;_id&quot;: &quot;507f191e810c19729de860ea&quot;,
  &quot;name&quot;: &quot;홍길동&quot;,
  &quot;email&quot;: &quot;hong@example.com&quot;,
  &quot;addresses&quot;: [
    {&quot;type&quot;: &quot;home&quot;, &quot;address&quot;: &quot;서울시 강남구&quot;},
    {&quot;type&quot;: &quot;work&quot;, &quot;address&quot;: &quot;서울시 서초구&quot;}
  ]
}</code></pre><h3 id="ormobject-relational-mapping이란">ORM(Object-Relational Mapping)이란?</h3>
<p><strong>ORM은 객체 관계 매핑을 의미합니다.</strong> 이는 객체와 관계형 데이터베이스의 테이블을 연결할수 있으며, ORM을 사용하면 데이터베이스의 데이터를 객체로 변환하여 개발자는 SQL 쿼리를 직접 작성하는 대신, 객체의 메소드와 속성을 사용하여 데이터를 관리할 수 있게 됩니다.</p>
<p>장점: </p>
<ul>
<li>데이터베이스와 객체 지향 프로그래밍 간의 격차를 해소합니다.</li>
<li>개발자가 SQL 쿼리보다 친숙한 객체 지향 코드를 사용할 수 있어 생산성이 향상됩니다.</li>
<li>데이터베이스 구조 변경 시 애플리케이션 코드의 수정을 최소화할 수 있습니다.</li>
</ul>
<h3 id="프리즈마prisma란">프리즈마(Prisma)란?</h3>
<p><strong>프리즈마는 최신 ORM 중 하나로</strong>, 타입스크립트 및 노드.js 환경에 최적화되어 있습니다. 안전한 타입 안전성과 함께, 데이터베이스 스키마를 기반으로 CRUD(Create, Read, Update, Delete) 작업을 위한 클라이언트 API를 자동으로 생성해줍니다. 프리즈마는 데이터 모델을 정의하는 스키마 파일을 통해 작동하며, 이를 통해 데이터베이스 마이그레이션과 실시간 데이터베이스 관리를 용이하게 합니다.</p>
<p>장점:</p>
<ul>
<li>강력한 타입 안전성을 제공하여 런타임 에러를 최소화합니다.</li>
<li>데이터베이스 스키마와 동기화되는 자동 생성 API를 통해 개발 시간을 단축시킵니다.</li>
<li>다양한 데이터베이스에 대한 지원과 함께, 마이그레이션 관리 기능을 내장하고 있습니다.</li>
</ul>
<h3 id="sql과의-차이점">SQL과의 차이점</h3>
<p>전통적인 SQL 쿼리 방식과 ORM, 특히 프리즈마의 가장 큰 차이점은 개발자가 데이터를 조작하는 방식에 있습니다.</p>
<p>SQL을 사용한 데이터 조회:</p>
<pre><code class="language-typescript">SELECT name, email FROM users WHERE id = 1;
  // SQL 사용한 데이터 조회
</code></pre>
<pre><code class="language-typescript">const user = await prisma.user.findUnique({
  where: { id: 1 },
  select: { name: true, email: true },
  // 프리즈마를 사용한 데이터 조회
});</code></pre>
<p>SQL은 데이터베이스와 직접적으로 소통하기 위한 선언적 언어입니다. 반면, ORM과 프리즈마는 개발자가 객체 지향 프로그래밍 패러다임 내에서 데이터를 조작할 수 있도록 해주며, 이 과정에서 복잡한 SQL 쿼리 작성을 추상화합니다.</p>
<p>소개는 여기까지하고 실제 개발환경을 구성해보겠습니다</p>
<p>개발을 시작하기 전, Node.js가 설치되어 있어야 합니다. 
Node.js는 JavaScript를 서버 사이드에서 실행할 수 있게 해주는 런타임 환경입니다. 설치 여부와 버전은 터미널에서 node -v 명령어로 확인해보시고 설치되어 있지않다면 설치하시면됩니다</p>
<h2 id="개발-환경-구성">개발 환경 구성</h2>
<h3 id="nestjs-설치">Nest.js 설치</h3>
<p>Nest.js는 TypeScript 기반의 백엔드 프레임워크로, 효율적인 개발과 유지보수를 가능하게 합니다. 설치는 다음의 명령어로 간단히 할 수 있습니다.</p>
<h3 id="새-프로젝트-생성">새 프로젝트 생성</h3>
<pre><code class="language-typescript">npm i -g @nestjs/cli
//Nest.js CLI를 사용하여 새 프로젝트를 생성합니다. 다음 명령어를 실행하면 프로젝트 폴더가 생성됩니다.
</code></pre>
<h3 id="prisma-추가">Prisma 추가</h3>
<pre><code class="language-typescript">nest new movie-selctor
//project-name 부분에는 원하는 프로젝트 이름을 입력합니다.</code></pre>
<p>Prisma는 타입 안전성과 개발 편의성을 제공하는 ORM입니다. Prisma를 프로젝트에 추가하기 위해 다음 명령어를 실행합니다.</p>
<pre><code class="language-typescript">cd movie-selctor
npm install prisma --save-dev
npx prisma init
//이 명령어들은 Prisma를 설치하고, 프로젝트에 필요한 기본 설정 파일을 생성합니다.</code></pre>
<h3 id="데이터베이스-연결">데이터베이스 연결</h3>
<p>prisma/.env 파일에서 데이터베이스 연결 설정을 합니다. 예를 들어, MySQL을 사용하는 경우, 다음과 같이 설정할 수 있습니다.</p>
<pre><code class="language-typescript">DATABASE_URL=&quot;mysql://username:password@localhost:3306/mydb&quot;
여기서 username, password, mydb 부분을 실제 데이터베이스 정보로 변경합니다.</code></pre>
<h3 id="스키마-정의-및-마이그레이션">스키마 정의 및 마이그레이션</h3>
<p>prisma/schema.prisma 파일에서 데이터 모델을 정의합니다. 예를 들어, 책 정보를 관리하는 Book 모델을 다음과 같이 생성할 수 있습니다.</p>
<pre><code class="language-typescript">model Book {
  id    Int     @id @default(autoincrement())
  title String
  author String
  year  Int
}
//모델 정의 후, 데이터베이스에 반영하기 위해 마이그레이션을 실행합니다.</code></pre>
<pre><code class="language-typescript">npx prisma migrate dev --name init
이 명령어는 정의된 스키마를 바탕으로 데이터베이스 테이블을 생성합니다.</code></pre>
<h3 id="book-모듈-컨트롤러-서비스-생성">Book 모듈, 컨트롤러, 서비스 생성</h3>
<p>Nest.js는 모듈, 컨트롤러, 서비스의 구조로 애플리케이션을 구성합니다. 먼저, Book에 대한 이 세 가지 요소를 생성해보겠습니다.</p>
<pre><code class="language-typescript">nest g module book
nest g controller book
nest g service book
//위 명령어는 각각 Book 모듈, 컨트롤러, 서비스를 생성합니다. 이 구조를 통해 코드를 체계적으로 관리할 수 있습니다.</code></pre>
<h3 id="prisma-서비스-통합">Prisma 서비스 통합</h3>
<p>Book 모델을 다루기 위해, Prisma 클라이언트를 서비스에 통합합니다. 먼저, Prisma 서비스 클래스를 생성합니다.</p>
<pre><code class="language-typescript">// prisma.service.ts

import { Injectable } from &#39;@nestjs/common&#39;;
import { PrismaClient } from &#39;@prisma/client&#39;;

@Injectable()
export class PrismaService extends PrismaClient {
  constructor() {
    super();
  }
}
//</code></pre>
<p>이제 생성된 PrismaService를 BookService에 주입합니다.</p>
<pre><code class="language-typescript">// book.service.ts
import { Injectable } from &#39;@nestjs/common&#39;;
import { PrismaService } from &#39;./prisma.service&#39;;

@Injectable()
export class BookService {
  constructor(private prisma: PrismaService) {}

  // 책 정보 조회
  async findAll() {
    return this.prisma.book.findMany();
  }

  // 책 정보 추가
  async create(data: { title: string; author: string; year: number }) {
    return this.prisma.book.create({ data });
  }
}</code></pre>
<h3 id="컨트롤러-구현">컨트롤러 구현</h3>
<p>서비스에서 정의한 기능을 외부에 노출하기 위해 컨트롤러를 구현합니다.</p>
<pre><code class="language-typescript">// book.controller.ts
import { Controller, Get, Post, Body } from &#39;@nestjs/common&#39;;
import { BookService } from &#39;./book.service&#39;;

@Controller(&#39;books&#39;)
export class BookController {
  constructor(private readonly bookService: BookService) {}

  @Get()
  findAll() {
    return this.bookService.findAll();
  }

  @Post()
  create(@Body() bookData: { title: string; author: string; year: number }) {
    return this.bookService.create(bookData);
  }
}
//이 컨트롤러는 /books 엔드포인트에서 GET 요청을 통해 모든 책 정보를 조회하고, POST 요청을 통해 새로운 책 정보를 추가하는 기능을 제공합니다.</code></pre>
<blockquote>
<p>Nest.js와 Prisma를 활용하여 Book 모델 기반의 RESTful API 엔드포인트를 성공적으로 만들었습니다. 엔드포인트 /boos으로 진입가능하며 포스트맨 또는 브라우저에서 get을 통해 불러올수있고 Post를 사용하면 값을 추가할수있습니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[MUI 커스텀 - 버튼 오버라이드]]></title>
            <link>https://velog.io/@96_inggu/MUI-%EC%BB%A4%EC%8A%A4%ED%85%80-%EB%B2%84%ED%8A%BC-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@96_inggu/MUI-%EC%BB%A4%EC%8A%A4%ED%85%80-%EB%B2%84%ED%8A%BC-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Tue, 02 Jan 2024 05:39:30 GMT</pubDate>
            <description><![CDATA[<p>온리유 프로젝트에서는 몇가지의 폼작성과 제출, 그리고 작성완료전까지 버튼에 disabled를 적용하는 등 갖은 버튼을 사용하기때문에
MUI버튼을 직접 오버라이딩 작업하게되었는데요 이중에 잊으면 안되는 내용을 기록해보려고합니다.</p>
<blockquote>
<p>왜 우리팀은 제공해주는 버튼을 사용하지않고 오버라이딩을 하게되었나?
먼저 MUI에서 제공해주는 버튼과 커스터마이징에 관한 공식문서 링크입니다
<a href="https://mui.com/material-ui/customization/how-to-customize/">https://mui.com/material-ui/customization/how-to-customize/</a></p>
</blockquote>
<p>먼저 각 버튼마다 오버라이딩 작업을 하게된 계기는 sx로 내부에 직접 스타일링을 해줘야되는 MUI 커스터마이징 방식때문입니다
팀원들과의 협의하에 styled-components방식으로 스타일링을 입히고있는데 그렇게되면 내부 코드에는
Javascript와 jsx그리고 내부에 입력하는 sx스타일링까지 총 3가지가 코드에 담기게되는데 처음 팀원이 작성한 코드를 보고
코드를 이해하는데만 스크롤을 위아래로 몇번을 다녀간 기억이있네요.</p>
<pre><code class="language-typescript">예시)
&lt;React.Fragment&gt;
  &lt;GlobalStyles styles={{ h1: { color: &#39;grey&#39; } }} /&gt;
  &lt;h1&gt;Grey h1 element&lt;/h1&gt;
&lt;/React.Fragment&gt;

&lt;Slider
  defaultValue={30}
  sx={{
    width: 300,
    color: &#39;success.main&#39;,
    &#39;&amp; .MuiSlider-thumb&#39;: {
      borderRadius: &#39;1px&#39;,
    },
  }}
/&gt;</code></pre>
<p><strong>- 가독성이 떨어진다</strong>
하나의 파일에 로직따로 jsx따로 그리고 커스터마이징 작업이된 컴포넌트 따로 작업이 되어있다보니 가독성이 떨어지는 문제가있었고
프로젝트에서 사용하게될 버튼에 기능이 많은것이 아니기에 매번 이렇게 일일히 적용해줄필요가 없다고 느꼈습니다.</p>
<p><strong>- 공통으로 사용하기 힘들다</strong>
매 버튼마다 커스터마이징이 된 컴포넌트를 가져오게되면 요구사항이 변경되었을때 일일히 바꿔줘야되는 번거러움이 있다</p>
<p>그리고 타이트한 출시일정덕에 팀원이 임의로 제작한 버튼까지 이렇게 규칙없는 버튼이 3가지 유형이나 탄생하는 어려움이 있었습니다</p>
<p>이같은 불필요한 업무를 막고자 오버라이딩의 필요성을 느끼게되었고 파편화되어있는 버튼들의 공통적인 역할과 패턴을 파악해 최상위 버튼에 적용해주었습니다</p>
<p>-요구사항
<img src="https://velog.velcdn.com/images/96_inggu/post/6e2b5ea1-a8eb-40ed-9390-26938f01758e/image.gif" width="30%" align="left"/>
<img src="https://velog.velcdn.com/images/96_inggu/post/7313dae6-d8bf-4208-8b31-c96b96dbd358/image.png" width="30%"/></p>
<p>MUI에서 제공하는 버튼API는 크게 color,disabled,size,variant 등이 있습니다
가장 먼저 정해야될 부부은 색상과 사용여부, 크기 , 형태입니다</p>
<p>네이밍의 경우 MUI에서 제공하는 네이밍을 그대로 사용하는것이 갖은 에러를 발생시키지않고 사용하기 편하다 느꼈습니다
<img src="https://velog.velcdn.com/images/96_inggu/post/cd680eb9-6f86-48f3-92c0-6ba0e67f4473/image.png" width="30%" align="left"/>
<strong>&lt;색상 및 형태&gt;</strong></p>
<ul>
<li><p>primary-contained: 가장 많이 사용하게될 프로젝트이 메인색상과 색상이 채워진 버튼입니다.</p>
</li>
<li><p>secondary-contained: 두번째로 사용하게될 색상이며 역시나 색상이 채워진 버튼입니다.</p>
</li>
<li><p>primary-outlined: 메인색상을 사용했으며 border가 적용된 버튼입니다.</p>
</li>
<li><p>cotained-disabled : 버튼의 사용이 막힌 disabled의 경우이며 이경우는 MUI에서 프롭값으로 제공해주기때문에 variant와 color를 적용하지않고 최상위 버튼에 disabled인 경우에 값을 입력하면 커스텀이 가능합니다</p>
</li>
</ul>
<pre><code class="language-typescript">적용 디렉터리 구조
assets/base/components/button
-contained.ts // contained 내용입력
-index.ts // 버튼을 묶어서 출력
-outlined.ts // outlined 내용입력
-root.ts // 최상위 root버튼 내용입력

//index.ts
  styleOverrides: {
  root: { ...root },
  containedSizeMedium: { ...contained.default },
  containedSizeLarge: { ...contained.large },
  containedPrimary: { ...contained.primary },
  containedSecondary: { ...contained.secondary },
  outlinedSizeMedium: { ...outlined.default },
  outlinedSizeLarge: { ...outlined.large },
  outlinedPrimary: { ...outlined.primary },
  outlinedSecondary: { ...outlined.secondary },
  }</code></pre>
<p>위와같이 각형태의 버튼을 파일화해 내용을 입력해주었고 공통으로 import해 사용했습니다</p>
<p>그리고 색상의 경우 기존에는 버튼을 가져와 Root파일을 따로 제작해 각 버튼마다 스타일링 작업을 해주었는데
<img src="https://velog.velcdn.com/images/96_inggu/post/059ac920-69ed-4f46-b1bd-ec21295e7814/image.png" width="30%"/></p>
<pre><code>&lt;Button color=&quot;primary&quot; variant=&quot;contained&quot;&gt;
  버튼입니다
&lt;/Button&gt;
&lt;Button color=&quot;secondary&quot; variant=&quot;contained&quot;&gt;
 세컨버튼입니다
&lt;/Button&gt;</code></pre><p>MUI의 스타일링은 color=&quot;primary&quot;를 지정했을시 그값을 버튼에 클래스로 씌우는 형태인걸 확인할수있습니다
그렇기에 사용하고자 하는 형태의 파일에 직업 클래스명과 함께 값을 설정해주면 color를 사용해 공통을 사용할수있는 버튼제작이 가능합니다</p>
<pre><code>/contained.ts
  primary: {
    &quot;&amp;.MuiButton-root&quot;: {
      backgroundColor: primary,
      &quot;&amp;:hover&quot;: {
        backgroundColor: primary_lighten3,
      },
      &quot;&amp;:focus&quot;: {
        backgroundColor: primary_lighten3,
      },
    },
  },</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[MUI 커스텀 - 색상 팔레트 만들기]]></title>
            <link>https://velog.io/@96_inggu/MUI-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%83%89%EC%83%81-%ED%8C%94%EB%A0%88%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@96_inggu/MUI-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%83%89%EC%83%81-%ED%8C%94%EB%A0%88%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 28 Dec 2023 14:13:20 GMT</pubDate>
            <description><![CDATA[<p>이번 프로젝트를 사용하며 MUI로 화면구성을 진행했는데 생각보다 커스텀이 쉽지않아서
그중 팔레트를 커스텀하는 과정에서 애먹던 부분들을 기록하려고합니다</p>
<blockquote>
<p><a href="https://mui.com/material-ui/customization/palette/#custom-colors">https://mui.com/material-ui/customization/palette/#custom-colors</a>
MUI에서 제공하는 공식 팔레트 링크입니다</p>
</blockquote>
<pre><code class="language-typescript">// theme.ts
import { createTheme } from &quot;@mui/material/styles&quot;;

const theme = createTheme({
  palette: {
    primary: {
      main: &quot;#ff7700&quot;,
    },
    light:{
      main:&quot;f1f3f6&quot;,
    }
    dark:{
    main:&quot;000000&quot;,
  }
  },
});</code></pre>
<p>기본 팔레트에선 primary,light,dark,secondary 등의 기본 색상을 제공해주는데
이같은 값을 수정하려면 먼저 createTheme를 사용해 먼저 테마를 생성하고</p>
<pre><code class="language-typescript">// index.tsx
import { theme } from &quot;@/styles/theme&quot;;
......
root.render(
    &lt;ThemeProvider theme={theme}&gt;
          &lt;App /&gt;
    &lt;/ThemeProvider&gt;
);</code></pre>
<p>이렇게 최상위 루트에 지정해주게 되면 적용이 됩니다.</p>
<p>피그마 공식문서에서 사용하는 색상들을 보면 primary 객체를 생성해 primary 내부에 main,light등을 담아서 사용하게됩니다 primary등의 값을 직접 수정하면 바로 적용이 가능합니다</p>
<pre><code class="language-typescript">const primary = {
  main: &#39;#1976d2&#39;,
  light: &#39;#42a5f5&#39;,
  dark: &#39;#1565c0&#39;,
  contrastText: &#39;#fff&#39;,
};
// 실제 사용시 입력해야되는 값
 &lt;Typography color=`${color.primary.light}` &gt;</code></pre>
<p>단순하게 메인컬러인 primary와 서브컬러인 secondary등 색상을 많이 사용하지 않는다면 상관없지만
지금 프로젝트에선 disabled등 상태에따라 색상이 다르기때문에 따로 지정을 해줘야 나중에 두번일하는 경우가 없을것 같아서 추가적인 작업이 필요했습니다.</p>
<pre><code class="language-typescript">// theme.ts
const theme = createTheme({
  palette: {
 white:{
    main:&quot;#ffffff&quot;
  },
  black:{
    main:&quot;#000000&quot;
  },
  primary: {
    main: &quot;#Ff7700&quot;,
  },
  primary_darken: {
    main: &quot;#F16416&quot;,
  },
  primary_lighten1: {
    main: &quot;#FFB06C&quot;,
  },
  primary_lighten2: {
    main: &quot;#FFD9B7&quot;,
  },
  primary_lighten3: {
    main: &quot;#FFF0E4&quot;,
  },

  gray1: {
    main: &quot;#3B3C3F&quot;,
  },
  gray2: {
    main: &quot;#5C5F63&quot;,
  },
  gray3: {
    main: &quot;#999DA3&quot;,
  },
  gray4: {
    main: &quot;#D3D6DB&quot;,
  },
  gray5: {
    main: &quot;#F1F3F6&quot;,
  },
});
</code></pre>
<p>저희 팀 피그마에서 사용되는 색상은 총 10가지입니다</p>
<p>기존에는 공식문서에따라 사용하는 모든값마다 객체에 담아 main에 컬러를 담아주었지만
이렇게 사용하게되면 실제사용시에 코드가 길어지는 부분이있고 primary의 경우 .main이나 객체값을 들어가지 않더라도 색상이 바로 적용되는 반면 darken이나 gray4 경우 mui의 기본값이 아니라서 그런지 .main을 붙이지않고서는 적용이 안되는 이슈를 생겼습니다</p>
<pre><code class="language-typescript">
// 사용하고 싶은값
 &lt;Typography color=light &gt;</code></pre>
<p>그래서 전부.main을 빼서 작성을하니
<img src="https://velog.velcdn.com/images/96_inggu/post/7f79ea3f-7769-4ee1-9a90-180ca6c5e50c/image.png" alt="">
위와같이 에러가 발생했습니다
primary의 경우 값이 세팅값에 main이 꼭 필요하기 때문에 생긴 에러였습니다
그래서 primary외 다른값은 main을 빼고 사용하니 실제사용시에 .main을 붙일 필요도 없이 간결하게 작성이 되었습니다</p>
<p>최종사용코드입니다</p>
<pre><code class="language-typescript">interface ColorType {
  main: string | any;
}

interface ColorsTypes {
  white?: string | any;
  black?: string | any;
  primary?: ColorType;
  primary_darken?: string | any;
  primary_lighten1?: string | any;
  primary_lighten2?: string | any;
  primary_lighten3?: string | any;
  gray1?: string | any;
  gray2?: string | any;
  gray3?: string | any;
  gray4?: string | any;
  gray5?: string | any;
}

const colors: ColorsTypes = {
  // white: {
  //   main: &quot;#ffffff&quot;,
  // },
  white: &quot;#ffffff&quot;,
  black: &quot;#000000&quot;,
  primary: {
    main: &quot;#Ff7700&quot;,
  },
  primary_darken: &quot;#F16416&quot;,
  primary_lighten1: &quot;#FFB06C&quot;,
  primary_lighten2: &quot;#FFD9B7&quot;,
  primary_lighten3: &quot;#FFF0E4&quot;,
  gray1: &quot;#3B3C3F&quot;,
  gray2: &quot;#5C5F63&quot;,
  gray3: &quot;#999DA3&quot;,
  gray4: &quot;#D3D6DB&quot;,
  gray5: &quot;#F1F3F6&quot;,
  // disabled: {
  //   main: &quot;#9f99ac&quot;,
  //   focus: &quot;#d3d6db&quot;,
  // },
};

export default colors;

&lt;Typography color=&quot;primary_lighten1&quot; variant=&quot;subtitle2&quot;&gt;
</code></pre>
<p>다른값이 또 추가되더라도 mui에서 기본으로 제공하는 값이 아닌경우에는 .main을 붙이지않아도 에러가 발생하지 않는것을 확인했습니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[next/ts] react-dropzone으로 이미지 업로드 및 미리보기 구현하기]]></title>
            <link>https://velog.io/@96_inggu/react-dropzone%EC%9C%BC%EB%A1%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%97%85%EB%A1%9C%EB%93%9C</link>
            <guid>https://velog.io/@96_inggu/react-dropzone%EC%9C%BC%EB%A1%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%97%85%EB%A1%9C%EB%93%9C</guid>
            <pubDate>Wed, 15 Nov 2023 11:59:22 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.velcdn.com/images/96_inggu/post/ab686522-30c2-411c-ab38-907679d9cf5b/image.gif"></a></p>
<p>오늘은 진행중이던 프로젝트에서 공연 썸네일등록 위해 사용한 react-dropzone 라이브러리 내역을 기록하려고합니다. </p>
<h3 id="react-dropzone">react-dropzone</h3>
<p>react-dropzone은 React에서 파일 드래그 앤 드롭 기능을 쉽게 구현할 수 있게 해주는 라이브러리입니다. 사용자가 파일을 드래그나 직접 업로드하며 사용이 가능하며 제가 제공받은 api는 multipart/form-data이기때문에 기본 json이 아닌 form-data로 한번 변환후에 링크만 출력해서 사용할 예정입니다</p>
<p>.</p>
<pre><code class="language-typescript">import { useDropzone } from &quot;react-dropzone&quot;;

const {
  getRootProps,
  getInputProps,
} = useDropzone({
  accept: {
    &quot;image/*&quot;: [&quot;.jpeg&quot;, &quot;.jpg&quot;, &quot;.png&quot;],
  },
});</code></pre>
<h3 id="기본-설정">기본 설정</h3>
<p><strong>먼저, react-dropzone을 사용하기 위해 필요한 기본 설정입니다</strong></p>
<ul>
<li>useDropzone 훅을 사용하여 드롭존을 설정합니다.</li>
<li>accept 속성을 통해 업로드 가능한 파일 형식을 지정할 수 있습니다. JPEG, JPG, PNG 형식을 지정해주면 해당 확장자의 이미지 파일만을 허용합니다.</li>
</ul>
<h3 id="썸네일-이미지-업로드-구현">썸네일 이미지 업로드 구현</h3>
<p>다음으로 썸네일 이미지를 업로드하는 부분을 구현해보겠습니다.</p>
<pre><code class="language-typescript">const onDropThumbnail = useCallback((acceptedFiles: File[]) =&gt; {
  const file = acceptedFiles[0];
  //file 첫번째 파일을 저장합니다
  const fileURL = URL.createObjectURL(file);
  //createObjectURL는 임시로 URL을 저장할수 있는 메서드입니다
  setThumbnailPreview({ url: fileURL, name: file.name, size: file.size });
}, []);</code></pre>
<p><strong>onDropThumbnail 함수는 사용자가 드롭존에 이미지를 놓았을 때 호출됩니다.</strong>
선택된 첫 번째 파일을 thumbnailPreview 상태에 저장한 후 파일의 URL, 이름, 크기를 관리할 수 있습니다.
URL.createObjectURL 함수를 사용하여 파일의 미리보기 URL을 생성합니다.</p>
<h3 id="다중-이미지-업로드-구현">다중 이미지 업로드 구현</h3>
<pre><code class="language-typescript">const onDropImages = useCallback((acceptedFiles: File[]) =&gt; {
  const mappedFiles = acceptedFiles.map((file) =&gt;
    Object.assign(file, {
      preview: URL.createObjectURL(file),
    })
  );
  setImagesPreview([...imagesPreview, ...mappedFiles]);
}, [imagesPreview]);
</code></pre>
<p><strong>onDropImages 함수는 사용자가 여러 이미지 파일을 map메서드와 Object.assign메서드로 이미지 배열 구현이 가능합니다</strong>
onDrop은 acceptedFiles를 인자로 받을 수 있는데 map을 사용하여 여러 이미지를 받고 
URL.createObjectURL와 Object.assign로 객체를 복사해서 구현했습니다 </p>
<h3 id="이미지-삭제">이미지 삭제</h3>
<pre><code>const removeFile = (fileToRemove: FileWithPreview | Preview) =&gt; {
  if (&quot;preview&quot; in fileToRemove) {
    setImagesPreview((prevFiles) =&gt;
      prevFiles.filter((file) =&gt; file.preview !== fileToRemove.preview)
    );
    URL.revokeObjectURL(fileToRemove.preview);
  }
};</code></pre><p><strong>removeFile 함수는 특정 파일을 삭제할 때 사용됩니다.</strong>
파일의 미리보기 URL을 사용하여 해당 파일을 imagesPreview 상태에서 제거 후 
URL.revokeObjectURL 함수를 사용하여 더 이상 필요하지 않은 URL을 해제합니다</p>
<h3 id="이미지-업로드">이미지 업로드</h3>
<pre><code>  const uploadFiles = async (e: any) =&gt; {
    e.stopPropagation();
    try {
      if (!thumbnail || images.length === 0) {
        alert(&quot;썸네일과 이미지 파일을 모두 추가해주세요.&quot;);
        return;
      }
      setUploading(true);
      const response = await postEventImageApi(atk, thumbnail, images);
      setThumbNailUrl(response.data.thumbNailImageUrl);
      setImageUrls(response.data.imageUrls);
      setUploading(false);
    } catch (error) {
      console.error(&quot;이미지 업로드 중 에러 발생:&quot;, error);
    }
  };

&lt;Button type=&quot;button&quot; onClick={uploadFiles}&gt;
  이미지 업로드
&lt;/Button&gt;</code></pre><p><strong>제공받은 api에 formdata를 보내 링크를 생성받으면 setThumbNailUrl과 setImageUrls에 상태를 보관해서 사용이 가능합니다</strong></p>
<p>formdata api를 처음사용해봐서 끝나고나니 별로 어렵다고 느껴지진않았는데 이래서 삽질도 경험이 필요한가봅니다.. 여기까집니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[next/ts] react-hook-form 상태관리]]></title>
            <link>https://velog.io/@96_inggu/react-hook-form-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@96_inggu/react-hook-form-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Tue, 14 Nov 2023 17:25:31 GMT</pubDate>
            <description><![CDATA[<p>오늘은 진행중이던 프로젝트에서 공연 이벤트생성을 위해 사용한 react-hook-form 라이브러리 내역을 기록하려고합니다. </p>
<p>폼 특성상 수많은 값을 관리하거나 보관해야되는데 이를 전부 상태로 관리하게되면 잦은 리렌더링이 예상되기때문에 이같은점을 보완하기위해 react-hook-form을 많이 사용합니다</p>
<h3 id="react-hook-form">react-hook-form</h3>
<p>해당 라이브러리에서는 몇가지 훅을 제공해주는데 대표적으로 useform , register, handleSubmit, watch 등이 있습니다.</p>
<pre><code class="language-typescript">import { useForm } from &quot;react-hook-form&quot;;

interface FormData {
  startEvent: string;
  endEvent: string;
  name: string; ... 등

const { register, handleSubmit, watch } = useform&lt;FormData&gt;();</code></pre>
<p>먼저 useform 선언해 제공해주는 훅들을 불러올 수 있습니다.</p>
<pre><code class="language-typescript"> const isAdult = **watch**(&quot;isAdult&quot;)

&lt;form onSubmit={handleSubmit(onSubmit)}&gt;
 &lt;input
     {...register(&quot;isAdult&quot;)}
    type=&quot;radio&quot;
    value=&quot;true&quot;
    id=&quot;adult&quot;
 /&gt;


 &lt;div&gt;{isAdult}&lt;/div&gt;
 &lt;button type=&quot;submit&quot;&gt;제출&lt;/button&gt;
 &lt;/form&gt;</code></pre>
<h3 id="register">register</h3>
<p><strong>register</strong>를 사용하면 이름을 지정해 따로 useState나 상태관리 라이브러리 없이 폼에 입력된 값을 관리할 수 있게되는데 이값을 <strong>watch</strong>를 사용하면 해당값에 어떤값이 입력되는지 확인이 가능하며 <strong>handleSubmit</strong>를 사용해 제출이 가능합니다</p>
<p>또한, register를 사용하면 유효성검사기능도 가볍게 구현할수 있게되는데</p>
<pre><code class="language-typescript">    {...register(&quot;place&quot;, {
        required: &quot;주소는 필수 입력입니다.&quot;,
        minLength: {
        value: 2,
         message: &quot;2자리 이상 입력해주세요.&quot;,
    },
})}  </code></pre>
<h3 id="required">required</h3>
<p>이같이 required는 true,false로 필수값으로 지정도 가능하지만 저렇게 문자열을 입력하게되면 기본값 true 적용됨가 동시에 하단에 입력해달라는 안내문구가 나오게됩니다
minLength와 value로 최소 글자수 지정이 가능하며 지정된 값보다 글자 수가 적게되면 message를 통해 추가적으로 입력해달라는 문구도 지정이 가능합니다</p>
<p>앞서 기본적인 기능에 대해서 적어봤고 실제로 사용하면서 애좀 먹었던 기능들을 기록겸 정리해보겠습니다</p>
<h3 id="공연-시작일과-종료일-제출">공연 시작일과 종료일 제출</h3>
<p>공연 시작일과 종료일을 제출해야되는데 DatePicker와 같이사용하기위해 제공해주는 Controller 훅을 사용했습니다.
<strong>Controller</strong>는 onChange, onBlur와 같은 이벤트 핸들링을 쉽게 할 수 있도록 해줍니다.</p>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/c342be0d-28d0-415f-8292-b35dc306cb4d/image.gif" alt=""></p>
<pre><code class="language-typescript">    &lt;Controller
      control={control}
      name=&quot;endEvent&quot;
      render={({ field }) =&gt; (
        &lt;DatePicker
          {...field}
          showTimeSelect
          dateFormat=&quot;yy년 MM월 dd일 aa h시 mm분&quot;
          selected={field.value ? dayjs(field.value).toDate() : null}
          onChange={(date) =&gt; field.onChange(dayjs(date).toDate())}
          minDate={minDate ? new Date(minDate) : undefined}
          maxDate={maxDate ? new Date(maxDate) : undefined}
        /&gt;
      )}
    /&gt;
name : register와 같이 사용되는 이름
dateFormat: 출력되는 날짜의 포맷을 설정
selected , onChange : 선택되는 날짜의 값을 받아 onChange로 상태값저장
minDate , maxDate  : 최소일짜와 최대날짜 지정</code></pre>
<h3 id="배우명-입력을-위한-배열제출">배우명 입력을 위한 배열제출</h3>
<p><img src="https://velog.velcdn.com/images/96_inggu/post/81551a24-b9b7-4b48-bcf2-c1ba587a0c47/image.gif" alt=""></p>
<p>배우는 여러명이기에 배열로 제출을 해야되는데 
저는 사용자가 편하게 사용할 수 있게 배우명을 입력하고 엔터를 사용하여 많은 값을 입력할수 있도록 구현했습니다
이를 구현하기위해서 register아닌 배열을 관리하는 훅 <strong>useFieldArray</strong>을 사용했습니다</p>
<pre><code>  const {
    fields: castingsFields,
    append: appendCasting,
    remove: removeCasting,
  } = useFieldArray({
    control,
    name: &quot;castings&quot;,
  });</code></pre><p>fields를 사용해 배열을 관리가능하며, append와 remove를 사용해서 배열 끝에 값을 추가및 제거가 가능합니다</p>
<pre><code>  const addCasting = (castingName: string): void =&gt; {
    if (castingName &amp;&amp; castingName.length &gt;= 2) {
      appendCasting({ name: castingName });
    }
  };
  const handleKeyDown = (
    event: KeyboardEvent&lt;HTMLInputElement&gt;,
    action: (name: string) =&gt; void
  ) =&gt; {
    if (event.key === &quot;Enter&quot;) {
      event.preventDefault();
      const name = event.currentTarget.value.trim();
      if (name) {
        action(name);
        event.currentTarget.value = &quot;&quot;;
      }
    }
  };</code></pre><p>appendCasting을 addCasting에 호출하여 값을 추가하도록 구현했습니다.&quot;
handleKeyDown을 작성해 enter시에 새로고침을 막고 값이 입력될수 있게끔 작성했습니다</p>
<pre><code>  &lt;div&gt;
      &lt;label htmlFor={id}&gt;
        {label}
      &lt;/label&gt;
      &lt;input
        id={id}
        name={id}
        placeholder={placeholder}
        onKeyDown={onKeyDown}
      /&gt;
      &lt;div className=&quot;flex flex-row&quot;&gt;
        {fields.map((field, index) =&gt; (
          &lt;div key={field.id} className=&quot;flex 
          items-center space-x-2 mt-2&quot;&gt;
            &lt;div&gt;
              {field.name}
              &lt;button type=&quot;button&quot;
               onClick={() =&gt; remove(index)} &gt;
                &lt;IoCloseSharp /&gt;
              &lt;/button&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
</code></pre><p>생각보다 제공해주는 hook도 많고 상황에 따라 여러 훅을 사용하게 해서 애를 먹었던 react-hook-form 그래도 작성하다보니 상태값으로 하나하나 관리하는거보단 관리하기엔 훨씬 용이하다는 점을 느끼며 포스팅 마무리 해보겠습니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 13 사용해보기]]></title>
            <link>https://velog.io/@96_inggu/Next.js-13-%EC%82%AC%EC%9A%A9%EA%B8%B0</link>
            <guid>https://velog.io/@96_inggu/Next.js-13-%EC%82%AC%EC%9A%A9%EA%B8%B0</guid>
            <pubDate>Tue, 08 Aug 2023 14:32:06 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요 오늘은 Next.js 13사용기를 정리해보려고합니다 
새롭게 시작한 프로젝트에서 Next.js를 사용하게되었는데 다른 프론트한분이 경력자분이셔서 
초보 티안나게끔 부랴부랴(?)정리해서 사용해보려고합니다</p>
<p>(출처 : <a href="https://www.youtube.com/watch?v=PCkiz2GUFg8">https://www.youtube.com/watch?v=PCkiz2GUFg8</a>) 코딩애플님의 유튜브 강의를 듣고 적용할 수 있는 부분을 정리해서 저도 틈틈히 볼겸 정리해볼게요</p>
<h2 id="nextjs란">Next.js란</h2>
<p>React는 라이브러리이고, Next.js는 리액트의 프레임워크라고 합니다
기존 React는 CSR(Client-Side Rendering)을 사용하기때문에 SEO검색에 취약하고 프로젝트 파일이 큰 경우에는 초기 렌더링 느린경우가 있는데 이같은 점을 보완하기위해 Next.js를 사용해야된다고합니다. Next.js는 SSR(Server-Side Rendering)을 사용하며 첫 렌더링시 SSR을 사용해 정적인 페이지를 빠르게 렌더링 해주기때문에 사용자 접근성이 좀 더 좋다는 이점이 있다고합니다.</p>
<h2 id="nextjs-설치방법">Next.js 설치방법</h2>
<pre><code>npx create-next-app@latest --experimental-app 입력 후 
(v13.5.2 버전 기준)프로젝트명 입력 -&gt; 타입스크립트, ESLint, Tailwind, src/디렉토리, App router 사용여부를 사용여부에 따라 입력을 하면 프로젝트가 성공적으로 설치됩니다 </code></pre><h2 id="패키지-매니저-설치">패키지 매니저 설치</h2>
<p>프로젝트를 정상적으로 생성하셨다면 해당 프로젝트로 이동하여 패키지를 설치합니다</p>
<pre><code>npm : npm install
npm run dev</code></pre><p>npm install을 입력후 npm run dev하면 정상적으로 실행이 됩니다.</p>
<h2 id="nextjs-디렉토리-구조">Next.js 디렉토리 구조</h2>
<p>Next는 React와 전반적으로 디렉토리 구조가 다른것을 볼수가 있습니다
먼저 리액는 src폴더 내부에 컴포넌트들을 제작하지만 Next는 app디렉토리를 사용하네요
또 기본적으로 4가지의 파일이 설치가 됩니다.</p>
<pre><code>globals.css - 전역 css
layout.js
page.js
page.module.css - page 내부 css</code></pre><img src ="https://velog.velcdn.com/images/96_inggu/post/5eb34c84-bd91-4a8b-93cf-d1d622fa5d7d/image.png" width="30%"/>

<h3 id="react의-mainapp과-달리-layoutpage">React의 main,app과 달리 layout,page</h3>
<p>가장 다른점은 main과 app이 아닌 layout과 page가 설치되는점 입니다.
기존 리액트에서는 layout 컴포넌트를 따로 생성해 outlet등을 사용하여 전체 레이아웃을 설정해줬다면 next는 이같은 레이아웃구조가 미리 설치가 되어있다고하네요.
layout내부에 children을 사용해 page를 렌더링한다고하니 공통적으로 사용하는 navbar나 sidebar같은 컴포넌트는 layout내부에 호출하면 전체 layout에 적용이 된다고합니다 </p>
<h2 id="nextjs-라우팅">Next.js 라우팅</h2>
<img src ="https://velog.velcdn.com/images/96_inggu/post/a6981831-5927-4776-a9e5-0a43d4878904/image.png" width="30%"/>


<p>React와 또 다른점은 react-router-dom을 사용해 라우팅을 할필요가 없다는 점입니다.
Next는 기본적으로 라우팅처리를 해주기때문에 app디렉토리 내부에 폴더를 생성하여 해당 컴포넌트명을 입력해주면 sidebar 생성 -&gt; 폴더명인 /sidebar:폴더명을 입력해보면 라우팅처리 없이도 url로 이동이 되는걸 볼수있습니다 이같은 점을 봤을때 Next는 자동으로 라우팅처리를 해주는것을 알 수 있습니다.</p>
<h3 id="자동으로-이미지를-최적화-해주는-image-컴포넌트">자동으로 이미지를 최적화 해주는 Image 컴포넌트</h3>
<img src ="https://velog.velcdn.com/images/96_inggu/post/d1eab47c-3bf7-4e4a-9fc8-9f34a7c7b436/image.png" width="30%"/>

<p>기존의 React는 img태그를 사용해 src경로를 설정해줘서 사용했지만 이미지최적화를 이용하기 위해선 추가적인 방법을 적용해야 했지만 Next는 Image컴포넌트를 제공해 import 후 사용하면 자동으로 최적화가 적용이됩니다
*단 외부경로에서 이미지파일을 사용할 경우에는 width와 height를 사용해 크기를 미리 선언해줘야 됩니다</p>
<h3 id="client-컴포넌트와-server-컴포넌트">Client 컴포넌트와 Server 컴포넌트</h3>
<p>Next는 Client 컴포넌트와 Server 컴포넌트 2가지의 컴포넌트로 구분이됩니다
server 컴포넌트와 달리 Client 컴포넌트는 최상단에 &#39;use client&#39;를 선언해 client 컴포넌트임을 명시해줘야됩니다</p>
<p><strong>2가지 컴포넌트의 가장 큰 차이점은</strong></p>
<ul>
<li>client 컴포넌트 : 자바스크립트 기능 사용가능 ( ex : onClick, useState, useEffect 등의 이벤트 )</li>
<li>server 컴포넌트 : 자바스크립트 기능 사용불가능 입니다</li>
</ul>
<p>client컴포넌트에서만 자바스크립트 기능이 사용이 가능하기 때문에 상대적으로 사용 빈도수가 높지만
server컴포넌트의 장점은 자바스크립트 기능이 없기에 로딩속도가 현저히 빠르다는 장점이있습니다</p>
<img src ="https://velog.velcdn.com/images/96_inggu/post/48c7c26c-c425-4e20-9d51-376550bb95ae/image.png" width="30%"/>


<blockquote>
<p>두달간 프로젝트에서 next를 사용하기 위해서 꼭 필요한 내용들을 짧게 정리해봤습니다 주간적으로 제가 기존에 몰랐던 내용만 기제했기때문에 내용이 많이 짧은 느낌이 있지만 추가적인 내용은 공부하면서 더 포스팅해보려고합니다 읽어주셔서 감사합니다 !</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[vite + typescript + tailwind]vite로 리액트 초기 개발환경 세팅]]></title>
            <link>https://velog.io/@96_inggu/aa-6or34asb</link>
            <guid>https://velog.io/@96_inggu/aa-6or34asb</guid>
            <pubDate>Sun, 06 Aug 2023 14:44:48 GMT</pubDate>
            <description><![CDATA[<img src="https://ko.vitejs.dev/logo-with-shadow.png" width="50%"/>

<p>타입스크립트를 배우면서 사용하게된 vite의 초기 개발환경 세팅하기를 포스팅하려고합니다 !
Vite에 대한 정보가 많이 없어서 초기세팅에서 삽질을 꽤나 했는데요.
저는 vite + typescript + tailwind를 사용할 예정인데
저랑 개발환경이 같으신분들에게 도움이 되고자 작성하고 마무리까지 포스팅해보려합니다</p>
<ol>
<li>프로젝트명을 입력 </li>
<li>리액트(리액트 사용자) 선택</li>
<li>TypeScript + SWC</li>
</ol>
<ul>
<li><strong>SWC는</strong> 바벨의 느린 컴파일속도때문에 이를 보완한 <strong>Rust언어기반의 컴파일링 도구</strong>입니다 </li>
</ul>
<p>SWC를 실제로 설치해보니 기존에 CRA에서 사용되던 바벨없이도 컴파일링이 정상적으로 작동되는걸 확인할 수 있습니다.</p>
<p>타입스크립트를 배우면서 사용하게된 vite의 초기 개발환경 세팅하기를 포스팅하려고합니다 !
Vite에 대한 정보가 많이 없어서 초기세팅에서 삽질을 꽤나 했는데요.
저는 vite + typescript + tailwind를 사용할 예정인데
저랑 개발환경이 같으신분들에게 도움이 되고자 작성하고 마무리까지 포스팅해보려합니다</p>
<p>먼저 vite 프로젝트를 생성해줍니다</p>
<h2 id="vite-프로젝트-생성">vite 프로젝트 생성</h2>
<pre><code>npm : npm create vite@latest
yarn : yarn create vite</code></pre><p>터미널에 사용하는 패키지매니저에 내용을 입력하면 
다음과 같은 화면이나오는데
<img src="https://velog.velcdn.com/images/96_inggu/post/2b97ce5d-06e8-4227-b4ed-e6972eb46ac6/image.png" alt=""></p>
<p>SWC를 실제로 설치해보니 기존에 CRA에서 사용되던 바벨없이도 컴파일링이 정상적으로 작동되는걸 확인할 수 있습니다.</p>
<h2 id="패키지-매니저-설치">패키지 매니저 설치</h2>
<p>프로젝트를 정상적으로 생성하셨다면 해당 프로젝트로 이동하여 패키지를 설치합니다</p>
<pre><code>npm : npm install
yarn : yarn install</code></pre><p>전 yarn install 이전에 직접 루트 디렉토리에 yarn.lock를 생성하지않으니 에러가 나오더군요 그래서 생성 후 yarn install을 하니 정상적으로 설치가됩니다.</p>
<h2 id="eslint-설치-및-설정">eslint 설치 및 설정</h2>
<blockquote>
<p>yarn add -D eslint - 개발의존성을 위해 -D를 붙여줍니다</p>
</blockquote>
<p>설치후 yarn eslint —init를 입력하면 다음과 같은 화면이 나옵니다.
<img src="https://velog.velcdn.com/images/96_inggu/post/bc51a7ae-b851-44eb-b238-9cab93aa39a2/image.png" alt=""></p>
<p>본인에게 해당되는 옵션을 선택하면 .eslintrc.json이 생성되니 기존의 .eslintrc.cjs 파일은 제거해주셔도 됩니다.</p>
<p>그리고 eslint를 typescript에 적용하기 위해 추가적인 플러그인 설치를 해줍니다</p>
<blockquote>
<p>yarn add -D eslint-plugin-import @typescript-eslint/parser eslint-import-resolver-typescript</p>
</blockquote>
<h2 id="prettier-설치">prettier 설치</h2>
<blockquote>
<p>yarn add -D prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-react-hooks</p>
</blockquote>
<p>프리티어를 설치 후 .prettierrc 파일 생성을 생성해서 코드 컨벤션을 정해줍니다</p>
<pre><code>{
  &quot;printWidth&quot;: 100, // 최대 100중
  &quot;semi&quot;: true, // 세미콜론 허용
  &quot;singleQuote&quot;: false, // 큰따옴표 사용
  &quot;tabWidth&quot;: 2, //탭의 크기능 2등
  &quot;trailingComma&quot;: &quot;es5&quot;,
  &quot;arrowParens&quot;: &quot;always&quot;
} </code></pre><p>그리고 package.json에 해당 코드를 추가하면</p>
<pre><code>&quot;format&quot;: &quot;prettier --write --cache .”</code></pre><p>성공적으로 pritter 적용이 됐을겁니다
작은 따옴표를 &#39;&#39;입력하고 저장하면 &quot;&quot;로 변경이 될건데 이러면 적용이 완료된겁니다.</p>
<p>vite와 eslint, pritter의 설치를 완료했을 시 저한테는 몇가지 작은 에러가 발생해서 해당 내용도 공유하려고합니다 !</p>
<pre><code>tsconfig.tsx

    /* Bundler mode */
    &quot;moduleResolution&quot;: &quot;bundler&quot;,
    &quot;allowImportingTsExtensions&quot;: true,
    &quot;resolveJsonModule&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;,</code></pre><p>해당옵션중 moduleResolution 옵션이 초기값으로 bundler로 지정되어있는데
<img src="https://velog.velcdn.com/images/96_inggu/post/1d99632e-f11e-44bd-89bc-f2c5310d85ca/image.png" alt="">
이같은 오류가 발생해서 초기값인 bundler를 node로 변경해주면 에러가 해결됩니다 tsconfig.node.json에도 동일한 에러가 발생하니 똑같이 node로 변경해주면 됩니다</p>
<h2 id="tailwind-적용하기">tailWind 적용하기</h2>
<p>tailWind는 공식문서에 따라설치하면 되는데 저한테는 몇가지 에러가 발생해서 제가 적용된 순서를 기제해보려고해요 ! 
<img src="https://images.velog.io/images/jwhan/post/d48bb9cd-04e4-4185-86ba-73cfbd99a247/tailwindcss.png"/></p>
<p>vite 설치 이후에도 Tailwind 적용까지 꽤나 시간이 들었는데요
여러 블로그를 봐도 CRA로 설명하는 등에 다른 개발환경 덕에 바로바로 적용이 안됐기때문에 도움이 됐으면 좋겠네요</p>
<h2 id="tailwind-설치">tailWind 설치</h2>
<p>먼저 tailwindcss를 설치해줍니다</p>
<blockquote>
<p>yarn add tailwindcss@latest postcss@latest autoprefixer@latest</p>
</blockquote>
<p>설치시에 tailwindcss이외에도 postcss, autoprefixer까지 총 2개의 플러그인을 같이 설치해줘야됩니다</p>
<ul>
<li><strong>postcss</strong>: tailwind는 내부에 입력한 클래스를 스타일로 변환해주는 도구입니다.</li>
<li><strong>autoprefixer</strong> : autoprefixer는 크로스 브라우징 도구이며 -mz-, -webkit- 등의 접두사를 붙이지않아도 자동으로 붙여주는 플러그인입니다</li>
</ul>
<p>Tailwind CSS v2 부터는 IE를 지원하지 않기 때문에 PostCSS의 autoprefixer 등을 함께 사용해야된다고 합니다</p>
<h2 id="tailwindcss-초기화">TailwindCss 초기화</h2>
<p>설치 후 해당 내용을 입력해 TailwindCss를 초기화 해줍니다.</p>
<blockquote>
<p>npx tailwind init -p</p>
</blockquote>
<p>해당 내용을 입력하면 tailwind.config.js 파일이 생성되는데</p>
<pre><code>export default {
//index.html을 포함한 src경로 내부에 자바스크립트, 
타입스크립트, jsx를 리턴하는 파일들을 모두 작성 해줍니다.

  content: [&quot;./index.html&quot;, &quot;./src/**/*.{js,ts,jsx,tsx}&quot;],
  theme: {
    extend: {
      colors: {
        primary: {
          DEFAULT: &quot;#2bca43&quot;,
        },
      },
    },
  },
  plugins: [],
};
</code></pre><p>해당 내용을 입력해줍니다</p>
<h2 id="postcssconfigjs-생성">postcss.config.js 생성</h2>
<p>루트디렉토리에 postcss.config.js를 생성해 내용을 입력해줍니다.</p>
<pre><code>// 루트경로의 postcss.config.js 등 postcss의 설정파일입니다
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}</code></pre><p>postcss같은 경우도 경로 설정을 해줘야되기때문에 해당 파일을 생성해야된다고 합니다</p>
<h2 id="전역-스타일-설정">전역 스타일 설정</h2>
<p>저는 src내부에 그냥 index.css를 생성해줬습니다
공식문서에서 기제해달라고 내용은 아래와 같습니다</p>
<pre><code>src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;</code></pre><p>그리고 아래 내용을 입력해주면 정상적으로 적용이 된다고하는데</p>
<pre><code>npx tailwindcss -i ./src/index.css(파일명) -o ./dist/output.css --watch</code></pre><p>적용후에 index.css에서 빨간줄 에러가 발생한다면 vscode의 경우 아래와 같은 확장프로그램을 설치해주면 됩니다
<img src="https://velog.velcdn.com/images/96_inggu/post/c1f1521e-ea6f-4d14-83fe-cbcaf1dc2b85/image.png" alt=""></p>
<p>저는 postCss Language Support를 설치해준후 빨간줄에러는 제거가 됐습니다 
그러면 정상적으로 tailwind가 적용이 되야되는데 vite를 사용하신다면 vite.config.ts를 열어서 아래와 같은 내용을 추가해줘야됩니다</p>
<pre><code>vite.config.ts
import { defineConfig } from &quot;vite&quot;;
import react from &quot;@vitejs/plugin-react-swc&quot;;
import tsconfigPaths from &quot;vite-tsconfig-paths&quot;;
import tailwindcss from &quot;tailwindcss&quot;;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), tsconfigPaths(), tailwindcss()],
});
// 플러그인에 tailwindcss()을 내용을 추가해줍니다</code></pre><p>*저도 엄청 헤매다 스택오버플로우의 도움을 받고 해결이 되었네요</p>
<p>여기까지 vite + typescript + tailwind를 적용하는 방법을 포스팅했습니다 vite 관련 블로그가 많이 없어서 찾는데 어려움이 있지만 같은 개발환경을 구성하는데 도움이 됐으면합니다 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ㅁㅁㅁ]]></title>
            <link>https://velog.io/@96_inggu/%E3%85%81%E3%85%81%E3%85%81-xoltncn2</link>
            <guid>https://velog.io/@96_inggu/%E3%85%81%E3%85%81%E3%85%81-xoltncn2</guid>
            <pubDate>Sun, 30 Jul 2023 13:32:29 GMT</pubDate>
            <description><![CDATA[<img src="https://ko.vitejs.dev/logo-with-shadow.png" width="50%"/>

<ol>
<li>프로젝트명을 입력 </li>
<li>리액트(리액트 사용자) TypeScript + SWC</li>
</ol>
<ul>
<li><strong>SWC는</strong> 바벨의 느린 컴파일속도때문에 이를 보완한 <strong>Rust언어기반의 컴파일링 도구</strong>입니다 </li>
</ul>
<p>SWC를 실제로 설치해보니 기존에 CRA에서 사용되던 바벨없이도 컴파일링이 정상적으로 작동되는걸 확인할 수 있습니다.</p>
<p>타입스크립트를 배우면서 사용하게된 vite의 초기 개발환경 세팅하기를 포스팅하려고합니다 !
Vite에 대한 정보가 많이 없어서 초기세팅에서 삽질을 꽤나 했는데요.
저는 vite + typescript + tailwind를 사용할 예정인데
저랑 개발환경이 같으신분들에게 도움이 되고자 작성하고 마무리까지 포스팅해보려합니다</p>
<p>먼저 vite 프로젝트를 생성해줍니다</p>
<h2 id="vite-프로젝트-생성">vite 프로젝트 생성</h2>
<pre><code>npm : npm create vite@latest
yarn : yarn create vite</code></pre><p>터미널에 사용하는 패키지매니저에 내용을 입력하면 
다음과 같은 화면이나오는데
<img src="https://velog.velcdn.com/images/96_inggu/post/2b97ce5d-06e8-4227-b4ed-e6972eb46ac6/image.png" alt=""></p>
<p>SWC를 실제로 설치해보니 기존에 CRA에서 사용되던 바벨없이도 컴파일링이 정상적으로 작동되는걸 확인할 수 있습니다.</p>
<h2 id="패키지-매니저-설치">패키지 매니저 설치</h2>
<p>프로젝트를 정상적으로 생성하셨다면 해당 프로젝트로 이동하여 패키지를 설치합니다</p>
<pre><code>npm : npm install
yarn : yarn install</code></pre><p>전 yarn install 이전에 직접 루트 디렉토리에 yarn.lock를 생성하지않으니 에러가 나오더군요 그래서 생성 후 yarn install을 하니 정상적으로 설치가됩니다.</p>
<h2 id="eslint-설치-및-설정">eslint 설치 및 설정</h2>
<blockquote>
<p>yarn add -D eslint - 개발의존성을 위해 -D를 붙여줍니다</p>
</blockquote>
<p>설치후 yarn eslint —init를 입력하면 다음과 같은 화면이 나옵니다.
<img src="https://velog.velcdn.com/images/96_inggu/post/bc51a7ae-b851-44eb-b238-9cab93aa39a2/image.png" alt=""></p>
<p>본인에게 해당되는 옵션을 선택하면 .eslintrc.json이 생성되니 기존의 .eslintrc.cjs 파일은 제거해주셔도 됩니다.</p>
<p>그리고 eslint를 typescript에 적용하기 위해 추가적인 플러그인 설치를 해줍니다</p>
<blockquote>
<p>yarn add -D eslint-plugin-import @typescript-eslint/parser eslint-import-resolver-typescript</p>
</blockquote>
<h2 id="prettier-설치">prettier 설치</h2>
<blockquote>
<p>yarn add -D prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-react-hooks</p>
</blockquote>
<p>프리티어를 설치 후 .prettierrc 파일 생성을 생성해서 코드 컨벤션을 정해줍니다</p>
<pre><code>{
  &quot;printWidth&quot;: 100, // 최대 100중
  &quot;semi&quot;: true, // 세미콜론 허용
  &quot;singleQuote&quot;: false, // 큰따옴표 사용
  &quot;tabWidth&quot;: 2, //탭의 크기능 2등
  &quot;trailingComma&quot;: &quot;es5&quot;,
  &quot;arrowParens&quot;: &quot;always&quot;
} </code></pre><p>그리고 package.json에 해당 코드를 추가하면</p>
<pre><code>&quot;format&quot;: &quot;prettier --write --cache .”</code></pre><p>성공적으로 pritter 적용이 됐을겁니다
작은 따옴표를 &#39;&#39;입력하고 저장하면 &quot;&quot;로 변경이 될건데 이러면 적용이 완료된겁니다.</p>
<p>vite와 eslint, pritter의 설치를 완료했을 시 저한테는 몇가지 작은 에러가 발생해서 해당 내용도 공유하려고합니다 !</p>
<pre><code>tsconfig.tsx

    /* Bundler mode */
    &quot;moduleResolution&quot;: &quot;bundler&quot;,
    &quot;allowImportingTsExtensions&quot;: true,
    &quot;resolveJsonModule&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;,</code></pre><p>해당옵션중 moduleResolution 옵션이 초기값으로 bundler로 지정되어있는데
<img src="https://velog.velcdn.com/images/96_inggu/post/1d99632e-f11e-44bd-89bc-f2c5310d85ca/image.png" alt="">
이같은 오류가 발생해서 초기값인 bundler를 node로 변경해주면 에러가 해결됩니다 tsconfig.node.json에도 동일한 에러가 발생하니 똑같이 node로 변경해주면 됩니다</p>
<h2 id="tailwind-적용하기">tailWind 적용하기</h2>
<p>tailWind는 공식문서에 따라설치하면 되는데 저한테는 몇가지 에러가 발생해서 제가 적용된 순서를 기제해보려고해요 ! 
<img src="https://images.velog.io/images/jwhan/post/d48bb9cd-04e4-4185-86ba-73cfbd99a247/tailwindcss.png"/></p>
<p>vite 설치 이후에도 Tailwind 적용까지 꽤나 시간이 들었는데요
여러 블로그를 봐도 CRA로 설명하는 등에 다른 개발환경 덕에 바로바로 적용이 안됐기때문에 도움이 됐으면 좋겠네요</p>
<h2 id="tailwind-설치">tailWind 설치</h2>
<p>먼저 tailwindcss를 설치해줍니다</p>
<blockquote>
<p>yarn add tailwindcss@latest postcss@latest autoprefixer@latest</p>
</blockquote>
<p>설치시에 tailwindcss이외에도 postcss, autoprefixer까지 총 2개의 플러그인을 같이 설치해줘야됩니다</p>
<ul>
<li><strong>postcss</strong>: tailwind는 내부에 입력한 클래스를 스타일로 변환해주는 도구입니다.</li>
<li><strong>autoprefixer</strong> : autoprefixer는 크로스 브라우징 도구이며 -mz-, -webkit- 등의 접두사를 붙이지않아도 자동으로 붙여주는 플러그인입니다</li>
</ul>
<p>Tailwind CSS v2 부터는 IE를 지원하지 않기 때문에 PostCSS의 autoprefixer 등을 함께 사용해야된다고 합니다</p>
<h2 id="tailwindcss-초기화">TailwindCss 초기화</h2>
<p>설치 후 해당 내용을 입력해 TailwindCss를 초기화 해줍니다.</p>
<blockquote>
<p>npx tailwind init -p</p>
</blockquote>
<p>해당 내용을 입력하면 tailwind.config.js 파일이 생성되는데</p>
<pre><code>export default {
//index.html을 포함한 src경로 내부에 자바스크립트, 
타입스크립트, jsx를 리턴하는 파일들을 모두 작성 해줍니다.

  content: [&quot;./index.html&quot;, &quot;./src/**/*.{js,ts,jsx,tsx}&quot;],
  theme: {
    extend: {
      colors: {
        primary: {
          DEFAULT: &quot;#2bca43&quot;,
        },
      },
    },
  },
  plugins: [],
};
</code></pre><p>해당 내용을 입력해줍니다</p>
<h2 id="postcssconfigjs-생성">postcss.config.js 생성</h2>
<p>루트디렉토리에 postcss.config.js를 생성해 내용을 입력해줍니다.</p>
<pre><code>// 루트경로의 postcss.config.js 등 postcss의 설정파일입니다
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}</code></pre><p>postcss같은 경우도 경로 설정을 해줘야되기때문에 해당 파일을 생성해야된다고 합니다</p>
<h2 id="전역-스타일-설정">전역 스타일 설정</h2>
<p>저는 src내부에 그냥 index.css를 생성해줬습니다
공식문서에서 기제해달라고 내용은 아래와 같습니다</p>
<pre><code>src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;</code></pre><p>그리고 아래 내용을 입력해주면 정상적으로 적용이 된다고하는데</p>
<pre><code>npx tailwindcss -i ./src/index.css(파일명) -o ./dist/output.css --watch</code></pre><p>적용후에 index.css에서 빨간줄 에러가 발생한다면 vscode의 경우 아래와 같은 확장프로그램을 설치해주면 됩니다
<img src="https://velog.velcdn.com/images/96_inggu/post/c1f1521e-ea6f-4d14-83fe-cbcaf1dc2b85/image.png" alt=""></p>
<p>저는 postCss Language Support를 설치해준후 빨간줄에러는 제거가 됐습니다 
그러면 정상적으로 tailwind가 적용이 되야되는데 vite를 사용하신다면 vite.config.ts를 열어서 아래와 같은 내용을 추가해줘야됩니다</p>
<pre><code>vite.config.ts
import { defineConfig } from &quot;vite&quot;;
import react from &quot;@vitejs/plugin-react-swc&quot;;
import tsconfigPaths from &quot;vite-tsconfig-paths&quot;;
import tailwindcss from &quot;tailwindcss&quot;;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), tsconfigPaths(), tailwindcss()],
});
// 플러그인에 tailwindcss()을 내용을 추가해줍니다</code></pre><p>*저도 엄청 헤매다 스택오버플로우의 도움을 받고 해결이 되었네요</p>
<p>여기까지 vite + typescript + tailwind를 적용하는 방법을 포스팅했습니다 vite 관련 블로그가 많이 없어서 찾는데 어려움이 있지만 같은 개발환경을 구성하는데 도움이 됐으면합니다 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ cs스터디 7/27 ] 리액트와 리덕스]]></title>
            <link>https://velog.io/@96_inggu/cs%EC%8A%A4%ED%84%B0%EB%94%94-27</link>
            <guid>https://velog.io/@96_inggu/cs%EC%8A%A4%ED%84%B0%EB%94%94-27</guid>
            <pubDate>Wed, 26 Jul 2023 14:21:44 GMT</pubDate>
            <description><![CDATA[<h2 id="리액트란">리액트란?</h2>
<img src="https://velog.velcdn.com/images%2Fjini_eun%2Fpost%2F107f5cfb-e97c-4c4c-b997-06098062e5b3%2Fimage.png"/>

<blockquote>
<p>React는 facebook에서 제공해주는 프론트엔드 라이브러리로 
자바스크립트 라이브러리의 하나로서 사용자 인터페이스를 만들기 위해 사용된다.
현재 웹/앱의 View개발에 가장 인기있는 라이브러리라고 볼 수 있습니다.</p>
</blockquote>
<h3 id="리액트의-장점">리액트의 장점</h3>
<p><strong>리액트 사용하는 이유는 이러한 장점때문이다</strong></p>
<ul>
<li>컴포넌트 기반의 화면구성, 유지보수가 용이 (필요한 부분의 component만 렌더링 된다. 최적화된 렌더링이 가능)</li>
<li>virtual DOM을 활용하여 빠른 렌더링이 가능</li>
<li>생태계가 넓고, 다양한 라이브러리 사용 가능</li>
<li>리액트 네이티브를 활용하여 앱 개발 가능</li>
</ul>
<ol>
<li>컴포넌트 기반의 화면구성</li>
</ol>
<p>리액트는 화면의 한 부분을 <strong>컴포넌트</strong> 라는 단위로 나눌 수 있으며 독립적으로 관리할 수 있다.
대규모 웹 애플리케이션에서 컴포넌트는 역할과 기능에 따라 따로 관리하기 용이하며, 반복되는 부분을 대체할 수 있게 
해주어서 코드 재사용성을 높여준다.또 컴포넌트 기반의 화면을 구성한다면 블록 쌓기처럼 컴포넌트를 쌓아서 빠르고 효율적으로 화면을 구성할 수 있다.</p>
<ol start="2">
<li>Virtual DOM으로 인한 충분히 빠른 속도</li>
</ol>
<p><strong>먼저 DOM(Document Object Model)이란 XML이나 HTML 문서에 접근하기 위한 일종의 인터페이스입니다</strong> 
<strong>Virtual DOM은</strong> UI의 이상적인 또는 가상적인 표현을 메모리에 저장하고 ReactDOM과 같은 라이브러리에 의해 
“실제” DOM과 동기화하는 프로그래밍 개념입니다. 이 과정을 <strong>재조정</strong>이라고 합니다.
이 접근방식이 React의 선언적 API를 가능하게 합니다. React에게 원하는 UI의 상태를 알려주면 DOM이 그 상태와 일치하도록 합니다. 이러한 방식은 앱 구축에 사용해야 하는 어트리뷰트 조작, 이벤트 처리, 수동 DOM 업데이트를 추상화합니다.
DOM은 문서의 구조화된 표현(structured representation)을 제공하며 프로그래밍 언어가 DOM 구조에 접근할 수 있는 방법을 제공하여 그들이 문서 구조, 스타일, 내용 등을 변경할 수 있게 돕는다.</p>
<p>HTTP response &gt; DOM tree &gt; CSSOM tree &gt; render tree &gt; painting</p>
<p>DOM은 새로운 요청이 있으면 리렌더링을 진행하게 되는데 매번 새롭게 구성하기 때문에 DOM의 속도는 느리지 않다. 하지만 , 양이 엄청 많으면 분명 퍼포먼스가 떨어지게되고 이 때, 리액트의 Virtual DOM이 더 효과적이다  </p>
<p>리액트는 Virtual DOM을 사용하며, 추상화한 자바스크립트 객체를 구성하여 사용합니다. 이는 실제 DOM의 가벼운 사본과 비슷합니다. 리액트에서 1) 데이터가 변하여 웹 브라우저에 리렌더링 할시엔 2) 이전 내용과 현재 내용을 비교하여 3) 바뀐 부분만 실제 DOM에 적용한다</p>
<p>Virtual DOM을 사용한다고 해서 무조건 빠른 것은 아니며 , 리액트 사용의 핵심은  ‘지속적으로 데이터가 변화하는 대규모 애플리케이션 구축하기’입니다</p>
<ol start="3">
<li>SPA(싱글 페이지 애플리케이션)</li>
</ol>
<ul>
<li><p>장점 : 서버의 자원을 아낄 수 있다. 더 좋은 사용자 경험을 누릴 수 있다.</p>
</li>
<li><p>단점 : 사용자와 인터렉션이 많은 경우에는 서버의 자원이 많이 사용되고 불필요한 트레픽이 낭비 될 수 있다.</p>
</li>
</ul>
<h3 id="프레임워크와-라이브러리">프레임워크와 라이브러리</h3>
<p><strong>프레임워크는</strong> 어플리케이션 개발시 코드의 품질, 필수적인 코드, 같은 기능들을 구성이 되어있는 뼈대(구조)를 제공하도록 만들어진걸 프레임워크라고합니다.
개발 구조나 설계 시 제공되는 인터페이스의 집합입니다. 완성된 어플리케이션은 아니지만 어느정도 프로그래머가 개발자가 제공된 환경을 사용해 프레임워크의 구조에서 어플리케이션을 개발이 가능해야된다</p>
<p>프레임워크는 Application 개발시 코드의 품질, 필수적인 코드, 알고리즘, 암호화, 데이터베이스 연동 같은 기능들을 어느정도 구성이 되어있는 뼈대(구조)를 제공하도록 만들어진걸 프레임워크라고합니다.</p>
<p><strong>라이브러리는</strong> 특정 기능에 대한 API(도구 / 함수)를 모은 집합이며, 필요한 기능을 호출해서 사용하는 방식이다</p>
<p>특정기능을 사용하기 위한도구</p>
<h3 id="프레임워크와-라이브러리의-차이는">프레임워크와 라이브러리의 차이는?</h3>
<p><strong>어플리케이션의 flow를 누가 제어하는냐가 프레임워크인지 라이브러리인지 구분할 수 있었습니다</strong></p>
<p>프레임워크는 단지 미리 만들어 둔 반제품이나, 확장해서 사용할 수 있도록 준비된 추상 라이브러리의 집합이 아니다.</p>
<p>라이브러리를 사용하는 애플리케이션 코드는 애플리케이션 흐름을 직접 제어한다.
단지 동작하는 중에 필요한 기능이 있을 때 능동적으로 라이브러리를 사용할 뿐이다.</p>
<p>반면에 프레임워크는 거꾸로 애플리케이션 코드가 프레임워크에 의해 사용된다.
프레임워크에는 분명한 제어의 역전 개념이 적용되어 있어야 합니다.</p>
<p>애플리케이션 코드는 프레임워크가 짜놓은 틀에서 수동적으로 동작해야 합니다.</p>
<p><strong>리덕스란</strong></p>
<p>리덕스는 현재 가장 많이 사용되는 상태관리 라이브러리 중 하나입니다
스토어라는 변수를 이용하여 전역 상태관리가 가능하며,전역으로 상태를 관리하기 때문에 props &lt;-&gt; state를 통해 부모 컴포넌트에서 자식 컴포넌트로, 자식의 자식 컴포넌트로 내려주지 않아도 사용할 수 있습니다.</p>
<h3 id="리덕스의-기본-원칙은">리덕스의 기본 원칙은</h3>
<ol>
<li>응용 프로그램의 전역상태는 단일 저장소 내의 트리에 저장됩니다</li>
<li>상태(state)는 읽기 전용입니다.</li>
<li>순수 함수에 의해서 변경되어야 합니다.</li>
</ol>
<p>리액트를 사용하지 않아도 코드 최적화를 열심히 하면 DOM 작업이 느려지는 문제를 개선할 수 있고, 또 작업이 매우 간단할 때는 오히려 리액트를 사용하지 않는 편이 더 나은 성능을 보이기도 합니다.</p>
<p>리액트와 Virtual DOM이 <strong>언제나 제공할 수 있는 것은 바로 업데이트 처리 간결성</strong>입니다. UI를 업데이트하는 과정에서 생기는 복잡함을 모두 해소하고, 더욱 쉽게 업데이트에 접근할 수 있습니다.</p>
<h3 id="리액트는-함수-컴포넌트">리액트는 함수 컴포넌트</h3>
<p>기존에는 함수형 컴포넌트 라는 단어를 사용했습니다. 하지만 이러한 네이밍이 <strong>*함수형 프로그래밍</strong>과 비슷했고, 함수형 프로그래밍은 순수 함수를 지향하는데
반면, 리액트에서는 훅(hook)을 사용함으로써 발생하는 여러 사이드 이펙트 이슈때문에 함수형 프로그래밍이라고 볼 수 없습니다. 이같은 혼동을 방지하기위해
<em>2018년 이후 변경 *</em>함수 컴포넌트**로 불리게 되었습니다
*사이트 이펙트 예제 작성</p>
<h3 id="react에서-함수-컴포넌트와-클래스-컴포넌트의-차이">React에서 함수 컴포넌트와 클래스 컴포넌트의 차이</h3>
<p><strong>클래스형 컴포넌트</strong></p>
<ul>
<li>객체지향 프로그래밍의 구조를 띄고 있으며, state를 초기화하기 위해서는 constructor → 생성자 함수를 필요로 합니다</li>
<li>state 기능 및 라이프 사이클 기능을 사용할 수 있으며 임의 메서드를 정의할 수 있다 * 중요!</li>
<li>생성자 함수를 통해 state를 초기화해야 하기 때문에 함수 컴포넌트에 비해서 코드가 길어지고, 사이즈가 커질 수 있습니다.</li>
<li>render 함수가 꼭 있어야 하고, 그 안에서 보여 주어야 할 JSX를 반환해야 한다.</li>
</ul>
<p><strong>함수 컴포넌트</strong>  ( 함수형 컴포넌트 x )</p>
<ul>
<li>Hooks 를 사용하여 생성자 함수를 통해 state를 초기화하지 않더라도 사용이 가능하다 (<code>useState()</code> 등)</li>
<li>선언하기가 좀 더 편하고 메모리 자원을 덜 사용한다는 장점이 있다</li>
<li>제공되는 hook 함수뿐만 아니라 커스텀 훅을 생성하여 동작시킬 수 있다</li>
<li>프로젝트를 완성하여 빌드한 후 배포할 때도 함수 컴포넌트를 사용하는 것이 결과물의 파일 크기가 더 작습니다</li>
<li><em>함수 컴포넌트*</em>는 state와 라이프사이클 API의 사용이 불가능하다는 점인데, 이를 해결 하기 위해 v16.8 업데이트 이후에 적용된 Hooks를 통해 해결되었습니다.</li>
</ul>
<h3 id="props와-state의-차이">props와 state의 차이</h3>
<ul>
<li><p>props : <strong>컴포넌트 속성을 설정할 때 사용하는 요소</strong>이다. props값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정할 수 있습니다.</p>
</li>
<li><p>state : <strong>컴포넌트 내부에서 바뀔수 있는 값을 의미한다</strong>. props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이며, 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있습니다. props를 바꾸려면 부모 컴포넌트에서 바꾸어 주어야 합니다.</p>
</li>
</ul>
<h3 id="props가-컴포넌트간에-전달받는-것이라고-했는데-자식에서-부모로도-전달할-수-있는가">props가 컴포넌트간에 전달받는 것이라고 했는데 자식에서 부모로도 전달할 수 있는가</h3>
<ol>
<li><p>부모 컴포넌트에서 설정할 수 있으며, 부모에서 자식으로만 데이터를 줄 수 있다.
(자식이 props를 통해 부모에게 데이터를 줄 수 없다는 뜻이다.)</p>
</li>
<li><p>자식에서 부모로 데이터를 전송하는 방법 = *함수를 이용한다.</p>
</li>
</ol>
<ul>
<li>자식은 props를 사용해서 부모에게 데이터를 건네줄 수 없다.
따라서 부모가 함수를 넣어 props로 자식에게 넘겨주면, 자식이 데이터를 파라미터로 넣어 호출하는 방식으로 동작한다. 즉, 부모가 props로 함수를 넣어주면 자식이 그 함수를 이용해 값을 건네주는 방식이다.</li>
</ul>
<h3 id="flux란">FLUX란</h3>
<p>Flux 란 애플리케이션의 데이터 흐름을 관리하는 패턴을 말한다. 중요한 것은 데이터의 흐름이 단방향으로 흐른다는 것이다.</p>
<p><code>리덕스는 페이스북에서 리액트가 함께 소개한 FLUX 아키텍쳐로 구현한 라이브러리입니다.</code></p>
<p>Flux를 사용하는 이유</p>
<ul>
<li>예측가능성을 높여준다.</li>
<li>데이터의 일관성을 유지하기 쉽게 만들어준다.</li>
<li>버그를 발견하기 쉽게 해준다.</li>
<li>테스트를 쉽게 해준다.</li>
</ul>
<p>사용자의 행위 <code>액션</code>은 <code>디스패쳐</code>에 의해 <code>스토어</code>로 업데이트되고 변경된 <code>스토어</code>에 대한 <code>뷰</code>를 리렌더링합니다.</p>
<p><code>뷰</code>에서는 <code>스토어</code>에 직접 접근하지 않으며, <code>디스패처</code>로 다시 액션을 보내고 <code>스토어</code>를 업데이트한 뒤, 다시 <code>뷰</code>를 리렌더링하는 단방향적 구조를 가지기에 어떤 액션이 디스패처에 의해 어떤 변화가 생기는지 명확히 파악이 가능하다
`</p>
<blockquote>
<p>7월 27일 강남역 윙스터디에서 2시간가량 진행됐으며 , 내용 암기 후 질의응답 후 마무리했습니다 !</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ cs스터디 7/24 ] 타입스크립트와 웹팩]]></title>
            <link>https://velog.io/@96_inggu/cs%EC%8A%A4%ED%84%B0%EB%94%94-724%EC%9B%94-%EB%82%B4%EC%9A%A9%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@96_inggu/cs%EC%8A%A4%ED%84%B0%EB%94%94-724%EC%9B%94-%EB%82%B4%EC%9A%A9%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 24 Jul 2023 08:52:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>7월 24일(월) cs스터디에서 진행한 내용을 기록해보려고합니다 !
테스트,  웹팩 , 타입스크립트 등 리액트관련 전반적인 지식을 공부했습니다
정리하면서 기존에 암기했던 내용보다 조금 더 축약해서 정리해보려고 노력중입니다 !</p>
</blockquote>
<h3 id="테스트란">테스트란?</h3>
<p>어플리케이션이 요구사항에 맞게 동작하는지를 검증하는 행위입니다</p>
<h3 id="테스트의-예는-어떤-것들이-있나요"><code>테스트의 예는 어떤 것들이 있나요?</code></h3>
<ul>
<li>DB에 데이터를 입력하는 API를 개발
(ex : 개발 &gt; API 호출 &gt; DB값 검증 )</li>
<li>디자인 시안에 맞게 HTML/CSS를 작성 후 브라우저 렌더링된 결과를 확인</li>
<li>새로운 기능 추가를 위해 리팩토링 후 확인작업</li>
<li>버그가 발생해 기존함수 수정 후 실행결과 확인</li>
<li>개발이 마무리 테스트된 어플리케이션을 실제 환경에 배포</li>
</ul>
<h3 id="테스트의-중요성은"><code>테스트의 중요성은?</code></h3>
<p>개발자는 작성한 프로그램의 퀄리티에 대한 책임이 있습니다.
자동화된 테스트를 작성해두지 않으면, 어플리케이션이 복잡해질 수록 테스트 비용이 증가합니다.
개발 기간이나 인력 등이 한정되어 있기 때문에, 테스트를 소홀히 하게 되는 경우가 있는데 이럴 경우 QA 와의 커뮤니케이션 비용이 늘어나, 업무의 효율이 떨어질 수 있습니다.</p>
<h3 id="테스트의-종류"><code>테스트의 종류</code></h3>
<p><strong>단위(Unit) 테스트</strong></p>
<ul>
<li>모듈(함수/클래스) 단위의 테스트</li>
<li>작성 비용이 적게 들고 실행 속도가 빠름</li>
<li>실패했을 때 문제가 생긴 부분을 비교적 정확하게 파악할 수 있음</li>
<li>상호테스트가 안되기때문에 통합확인이 힘듬</li>
</ul>
<p><strong>통합(Integration) 테스트</strong></p>
<ul>
<li>주로 단위 테스트보다 큰 범위의 테스트를 의미</li>
<li>개별 모듈(함수/클래스)들이 연결되어 제대로 상호작용하는지를 테스트</li>
<li>단위 테스트에 비해 실패 시 문제가 생긴 부분을 정확히 파악하기가 어려움</li>
</ul>
<p><strong>E2E(End to End) 테스트</strong></p>
<ul>
<li>실제 사용자가 사용하는 것과 같은 조건에서 전체 시스템을 테스트</li>
<li>단위/통합 테스트에 비해 작성이 어렵고 실행 속도가 비교적 느림</li>
<li>API 서버, DB 등의 외부 서비스들을 모두 사용하여 통합된 시스템을 테스트</li>
</ul>
<h3 id="business-logic">business logic</h3>
<p>먼저, 로직이란 어떤 프로그램을 만들 때의 논리적인 흐름을 이야기합니다
그중에서도 비즈니스 로직(도메인 로직/ 애플리케이션 로직)이란 <strong>프로그램의 핵심 로직을</strong> 뜻합니다.
<strong>어떻게 데이터가 생성되고 저장되고 수정되는지를 정의한 것이 비즈니스 로직이라고 할 수 있습니다.</strong></p>
<p>*<em>회원가입으로 예를 들어본다면 *</em>
클라이언트는 회원가입 폼에 양식을 입력하고 회원가입 버튼을 누르면 회원가입이 진행됩니다
이같은 로직중 중복찾기기능 중 비즈니스 로직이란?</p>
<blockquote>
<ol>
<li>회원이 작성한 아이디 값을 저장</li>
<li>회원정보가 있는 데이터베이스 연결</li>
<li>데이터베이스에 회원이 작성한 아이디 값이 있는지 중복검사</li>
<li>회원의 아이디가 이미 있는지 없는지 여부를 데이터화 하여 저장</li>
<li>데이터베이스 연결 끊기</li>
<li>View 영역에게 가공된 데이터 전달</li>
</ol>
</blockquote>
<p>해당 논리적인 흐름이 중복찾기 기능중에 비즈니스 로직이됩니다 !</p>
<h3 id="snippet"><code>Snippet</code></h3>
<p><strong>스니펫은 작은 조각을 뜻한다.</strong> 지정한 접두어를 입력하면 템플릿이 입력되는 식이다.
요즘 활용되는 왠만한 코드편집기, IDE 등은 code snippet 단위로 ’템플릿’을 만들어 써먹을 수 있게 되어있다.</p>
<p>VS Code 의 확장 프로그램 활용 - <strong>Reactjs code snippets / Tabnine AI</strong></p>
<h3 id="웹팩의-구성요소"><code>웹팩의 구성요소</code></h3>
<p><strong>웹팩이란 최신 프런트엔드 프레임워크에서 가장 많이 사용되는 모듈 번들러입니다.</strong>
모듈 번들러란 웹 애플리케이션을 구성하는 자원(HTML, CSS, Javscript, Images 등)을 모두 각각의 모듈로 보고 
이를 조합해서 병합된 하나의 결과물을 만드는 도구를 의미합니다.</p>
<h3 id="웹팩-사용-시에-이점"><code>웹팩 사용 시에 이점</code></h3>
<ol>
<li>파일 단위의 자바스크립트 모듈 관리의 필요성이 강조되어 개발됨</li>
<li>웹 개발 작업 자동화 도구</li>
<li>웹 애플리케이션의 빠른 로딩 속도와 높은 성능</li>
</ol>
<p><strong>모듈이란 프로그래밍 관점에서 특정 기능을 갖는 작은 코드 단위를 의미합니다.</strong> 
자바스크립트로 치면 아래와 같은 코드가 모듈입니다.</p>
<pre><code class="language-javascript">math.js
const sum = (a, b) =&gt; { return a + b;}
export { sum };</code></pre>
<p>이 math.js 파일은 아래의 기능을 갖고 있는 모듈입니다.</p>
<ul>
<li>두 숫자의 합을 구하는 sum() 함수</li>
</ul>
<p>이처럼 하나의 의미있는 파일로 관리하면 모듈이 됩니다.
웹팩에서 지칭하는 모듈이라는 개념은 위와 같이 자바스크립트 모듈에만 국한되지 않고 웹 애플리케이션을 구성하는 모든 자원을 의미합니다.
웹 애플리케이션을 제작하려면 HTML, CSS, Javascript, Images, Font 등 많은 파일들이 필요하죠. 이같은 파일들이 모두 모듈입니다.</p>
<h3 id="모듈-번들링이란"><code>모듈 번들링이란?</code></h3>
<img src="https://velog.velcdn.com/images/96_inggu/post/8a438f29-8024-4d83-a949-e3ec6bd809ca/image.png"  width="30%">


<p>아래 그림과 같이 웹 애플리케이션을 구성하는 몇십, 몇백개의 자원들을 하나의 파일로 병합 및 압축 해주는 동작을 모듈 번들링이라고 합니다.
파일들의 연관된 관계를 파악하여 파일들을 하나의 파일로 압축시켜주는 과정을 번들링 과정이라 합니다.</p>
<h3 id="바벨이란"><code>바벨이란?</code></h3>
<p>바벨은 자바스크립트에서 지원하는 최신 문법 (ES6, ES7, ES8, ES9, …) 들을 최대한 많은 브라우저 환경에서 호환이 가능하도록 변환해주는(트랜스파일링해주는) 언어입니다.</p>
<h3 id="바벨-변환"><code>바벨 변환</code></h3>
<p>바벨을 사용하면 거대한 변화가 생기기 시작한 기점인 ES6 (ECMAScript 2015) 이후의 문법을 브라우저에서 범용적으로 사용되는 문법 단계로 변환해줄 수 있습니다.</p>
<pre><code>// Babel Input: ES2015 arrow function[1, 2, 3].map((n) =&gt; n + 1);
// Babel Output: ES5 equivalent[1, 2, 3].map(function (n) {  return n + 1;});</code></pre><p><strong>트랜스파일은 한언어로 작성된 소스코드를 비슷한 수준의 추상화를 가진 다른 언어로 변환하는 것을 말합니다</strong>
예를들어, es6 -&gt; es5를 변환하는 경우와 c++-&gt;c로 변환하는 경우를 예로 들수있습니다 !</p>
<h3 id="웹팩의-주요-속성-4가지"><code>웹팩의 주요 속성 4가지</code></h3>
<ol>
<li>entry</li>
<li>output</li>
<li>loader</li>
<li>plugin</li>
</ol>
<h3 id="entry"><code>entry</code></h3>
<p>entry 속성은 웹팩에서 웹 자원을 변환하기 위해 필요한 <strong>최초 진입점이자</strong> 자바스크립트 파일 경로입니다.</p>
<p><strong>빌드를 할 대상 파일의 위치</strong>라고 볼 수 있습니다</p>
<pre><code class="language-javascript">// webpack.config.js
module.exports = {  entry: &quot;./src/index.js&quot;,};</code></pre>
<p><strong>entry</strong> 속성에 지정된 파일에는 웹 애플리케이션의 전반적인 구조와 내용이 담겨져 있어야 합니다.<br>웹팩이 해당 파일을 가지고 웹 애플리케이션에서 사용되는 모듈들의 연관 관계를 이해하고 분석하기 때문에 
애플리케이션을 동작시킬 수 있는 내용들이 담겨져 있어야 합니다.</p>
<pre><code class="language-javascript">// webpack.config.js
    import LoginView from &quot;./LoginView.js&quot;;
    import HomeView from &quot;./HomeView.js&quot;;
    import PostView from &quot;./PostView.js&quot;;
    function initApp() {  LoginView.init();  HomeView.init();  PostView.init();}
    initApp();</code></pre>
<p> 싱글 페이지 애플리케이션으로 작성된 index.js를 예로 들어보겠습니다. 3개의 컴포넌트를 index.js에 불러와서 실행을 하고있는 구조입니다.</p>
<p>사용자의 로그인 화면, 로그인 후 진입하는 메인 화면, 그리고 게시글을 작성하는 화면 등 웹 서비스에 필요한 화면들이 모두 index.js 파일에서 불려져 사용되고 있기 때문에 웹팩을 실행하면 해당 파일들의 내용까지 해석하여 파일을 빌드해줄 것입니다.</p>
<h3 id="output"><code>output</code></h3>
<p><strong>output</strong> 속성은 웹팩을 돌리고 난 <strong>결과물의 파일 경로</strong>를 의미합니다.</p>
<pre><code class="language-javascript">// webpack.config.js
module.exports = {  output: {    filename: &quot;bundle.js&quot;,  },};</code></pre>
<p>앞에서 배운 entry 속성과는 다르게 객체 형태로 옵션들을 추가해야 합니다.</p>
<p>최소한 filename은 지정해줘야 하며 일반적으로 아래와 같이 path 속성을 함께 정의합니다.</p>
<pre><code class="language-javascript">// webpack.config.js
var path = require(&quot;path&quot;);
module.exports = {  output: {    filename: &quot;bundle.js&quot;,    
path: path.resolve(__dirname, &quot;./dist&quot;),  },};</code></pre>
<ul>
<li>filename 속성은 웹팩으로 빌드(번들링)한 파일의 이름을 의미합니다.</li>
<li>path 속성은 해당 파일의 경로를 의미합니다.</li>
<li>path 속성에서 사용된 path.resolve() 코드는 인자로 넘어온 경로들을 조합하여 유효한 파일 경로를 만들어주는 Node.js API입니다.</li>
</ul>
<p><strong>따라서 dist 라는 폴더 안에 있는 bundle.js라는 파일 이름으로 엔트리에 들어온 파일을 빌드(번들링)하여 결과물로 가져올 것입니다.</strong></p>
<h3 id="loader"><code>loader</code></h3>
<p><strong>로더는</strong> 웹팩이 웹 애플리케이션을 해석할 때 자바스크립트 파일이 아닌 <strong>웹 자원(HTML, CSS, Images, 폰트 등)들을 빌드 시에, 
자바스크립트의 output(산출물) 파일에 포함될 수 있도록 도와주는 속성입니다.</strong></p>
<pre><code class="language-javascript">// webpack.config.js
module.exports = {  module: {    rules: [],  },};</code></pre>
<p>엔트리나 아웃풋 속성과는 다르게 module라는 이름을 사용합니다.</p>
<h3 id="plugin"><code>plugin</code></h3>
<p>우리가 설정한 output인 bundle.js에 <strong>css 파일을 같이 번들링하는 것이 아닌 별도의 css 파일로 만들어주기는 것이 플러그인의 기능입니다</strong> 
해당 적용이 완료된 후에 빌드를 하면 다음과 같이 번들링 시에 css 파일이 별도로 분리된 것을 볼 수 있습니다
플러그인(plugin)은 웹팩의 기본적인 동작에 추가적인 기능을 제공하는 속성입니다.
로더랑 비교하면 로더는 파일을 해석하고 변환하는 과정에 관여하는 반면, 플러그인은 해당 결과물의 형태를 바꾸는 역할을 한다고 보면 됩니다.
플러그인은 아래와 같이 선언합니다.</p>
<pre><code class="language-javascript">// webpack.config.js
module.exports = {  plugins: [],};</code></pre>
<p>플러그인의 배열에는 생성자 함수로 생성한 객체 인스턴스만 추가될 수 있습니다.</p>
<pre><code class="language-javascript">// webpack.config.js
var webpack = require(&quot;webpack&quot;);
var HtmlWebpackPlugin = require(&quot;html-webpack-plugin&quot;);module.exports = { 
  plugins: [new HtmlWebpackPlugin(), new webpack.ProgressPlugin()],};</code></pre>
<h2 id="타입과-인터페이스">타입과 인터페이스</h2>
<h3 id="타입스크립트를-왜-쓰나요">타입스크립트를 왜 쓰나요?</h3>
<p>타입을 먼저 지정하고 선언함으로써, 프로그래밍 단계, HTTP 통신을 통한 데이터를 주고받는 과정에서 생기는 데이터를 
안전하게 주고 받을 수 있고 타입에 관련된 프로토타입 메서드를 손쉽게 찾아 쓸 수 있다는 가장 큰 장점입니다 또, <strong>정적 타이핑</strong>을 지원하며 
자바스크립트의 경우 <strong>동적 타이핑</strong>이기에 <strong>자바스크립트 엔진에 의해 자동으로 해석</strong>되기 작성자의 의도에 따라 데이터의 
타입이 불분명해질 수 있는데 이같은 점을 방지할 수 있는것이 타입스크립트의 장점입니다</p>
<p><strong>타입이 없을 때</strong></p>
<img src="https://velog.velcdn.com/images/96_inggu/post/b1e4500d-20b4-4822-85ab-21280ec4a3b3/image.png"  width="50%">

<p><strong>타입이 있을 때</strong></p>
<img src="https://velog.velcdn.com/images/96_inggu/post/4e2f384d-5fb3-4721-8be1-8b11f2762bf4/image.png"  width="50%">  


<h3 id="타입과-인터페이스의-차이">타입과 인터페이스의 차이</h3>
<p>타입과 인터페이스는 모두 객체의 타입의 이름을 지정하는 방법들 중 하나이다</p>
<pre><code class="language-typescript">// interfaceinterface 
PeopleInterface {  name: string;  age: number;}
const me1: PeopleInterface = {  name: &quot;yc&quot;,  age: 34,};</code></pre>
<p>하지만 일정 부분에 있어서 타입과 인터페이스는 차이점이 존재한다</p>
<ol>
<li>확장하는 방법</li>
</ol>
<p>인터페이스는 <code>extends</code> 키워드를 사용하여 확장이 가능하고, 타입은 <code>&amp;</code> 연산자를 통해 확장이 가능하다</p>
<pre><code class="language-typescript">// interfaceinterface People
Interface {  name: string;  age: number;}
interface StudentInterface extends PeopleInterface {  school: string;}</code></pre>
<ol start="2">
<li>선언적 확장</li>
</ol>
<p>인터페이스는 동일한 이름을 통해 재정의함으로써 선언적 확장이 가능하지만, 타입은 불가능하다</p>
<pre><code class="language-typescript">// interfaceinterface
Window {  title: string;}interface Window {  ts: TypeScriptAPI;}
// 같은 interface 명으로 Window를 다시 만든다면, 자동으로 확장이 된다.</code></pre>
<p>여러 타입 혹은 인터페이스를 병합할 때 (<code>&amp;</code> or <code>extends</code>), 인터페이스는 속성간의 충돌을 해결하기 위해 단순한 객체 타입을 만든다.</p>
<p>인터페이스는 오로지 객체의 타입을 만들기 위한 것이기 때문이다.</p>
<p>타입은 재귀적으로 순회하면서 속성을 병합하는데, 이 경우에 일부 <code>never</code>가 나오면서 제대로 머지되지 않을 수 있다. 인터페이스와는 다르게 타입에는 원시 타입이 올 수도 있으므로, 충돌이 나서 병합이 안되는 경우가 발생한다.</p>
<p>객체에만 쓰는 용도라면 interface를 사용하는 것이 좋을 것이다.</p>
<ol>
<li>변환시 제거</li>
</ol>
<p>타입과 인터페이스의 또 다른 차이점은 생성된 자바스크립트 코드에서 인터페이스가 제거되는 반면 타입은 제거되지 않는다는 것이다. 타입스크립트를 빌드할 경우, 해당 코드를 자바스크립트로 번들링하게 되는데, 이때 인터페이스의 경우 자바스크립트로 넘어가며 제거되지만, 타입의 경우 그대로 남아 있는다.</p>
<p><strong>즉, TypeScript 코드에서 인터페이스를 사용하는 경우 브라우저나 서버에서 실제로 실행되는 JavaScript 코드에는 인터페이스가 존재하지 않습니다.</strong></p>
<p>인터페이스와 타입은 객체의 모양을 정의하는 데 모두 사용되지만,</p>
<ul>
<li>인터페이스는 더 유연하고, 확장 과정에서 확장되거나 제거될 수 있는 반면</li>
<li>타입은 덜 유연하고, 확장될 수 없으며 생성된 자바스크립트 코드에 남아 있습니다</li>
</ul>
<p>인터페이스는 자신의 이름을 중복 선언하여 <strong>extends</strong> 키워드를 사용하지 않더라도 확장이 가능하지만,
타입의 경우 중복 선언을 통해 확장할 수 없다. (&amp; 연산자를 사용하여 확장해야 한다)</p>
<h3 id="제네릭이란">제네릭이란?</h3>
<p><strong>제네릭은 클래스, 함수, 인터페이스 등을 다양한 타입으로 재사용가능하게 해주는 문법</strong>입니다</p>
<pre><code class="language-typescript">function getSize(arr: number[] | string[] | boolean[]): number {  return arr.length;}
const arr = [1, 2, 3];getSize(arr); 
// 3const arr2 = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;];getSize(arr2); // 3const arr3 = [true, false, true];getSize(arr3); // 3</code></pre>
<p>위와 같이 <strong>한가지 함수에서 다양한 타입의 데이터를 받아줘야한다고</strong> 가정할 때 올바른 타입을 지정하기 위해서는 타입을 확장시켜줘야 합니다.</p>
<p>이런 상황에서 제네릭을 사용하여 함수의 매개변수에 대한 타입 <code>&lt;T&gt;</code>를 명시해줌으로써 다양한 타입에 대응하는 함수를 만들어줄 수 있습니다.</p>
<pre><code class="language-typescript">function getSize&lt;T&gt;(arr: T[]): number {  return arr.length;}
const arr = [1, 2, 3];getSize&lt;number&gt;(arr); 
// 3const arr2 = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;];getSize&lt;string&gt;(arr2); // 3</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ cs스터디 7/21 ] http와 쿠키, 세션]]></title>
            <link>https://velog.io/@96_inggu/cs%EC%8A%A4%ED%84%B0%EB%94%94-721</link>
            <guid>https://velog.io/@96_inggu/cs%EC%8A%A4%ED%84%B0%EB%94%94-721</guid>
            <pubDate>Fri, 21 Jul 2023 12:35:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>7월 21일(금) cs스터디에서 진행한 내용을 기록해보려고합니다 !
HTTP와 쿠키,세션,CORS 등 cs의 전반적인 지식을 공부했으며
면접에서 활용하기위해 문답자료로 정리했습니다 
분명히 읽을땐 크게 어려움없이 술술 읽다가 면접연습을하려니 입이 막히네요
확실히 면접을 떠나서라도 공부한것을 말로 옮기거나 글로 적는것은 더 연습이 필요할듯 합니다 !!</p>
</blockquote>
<blockquote>
<h3 id="http란"><code>HTTP란?</code></h3>
<p><strong>HTTP는  데이터를 주고받기 위해 정의한 통신 프로토콜입니다</strong></p>
</blockquote>
<p>웹을 기준으로 브라우저와 서버 간에 데이터를 주고받기 위항 방식으로 HTTP 프로토콜을 사용하고 있습니다.</p>
<p>*HTTP : HyperText Transfer Protocol<img src="https://velog.velcdn.com/images/96_inggu/post/eec71df0-1f4b-457d-9194-719d44f35067/image.png" alt=""></p>
<blockquote>
<h3 id="http-프로토콜의-가장-큰-특징은"><code>HTTP 프로토콜의 가장 큰 특징은?</code></h3>
<p>**HTTP 프로토콜은 상태가 없는 (stateless) 프로토콜</p>
</blockquote>
<p>무상태성이란 데이터를 주고 받기 위한 각각의 데이터 요청이 서로 독립적으로 관리가 가능함을 뜻합니다 이전또한 이전에 보냈던 데이터 요청과 다음에 보낼 데이터 요청이 서로 관련이 없습니다 **
이러한 특징 덕분에 서버는 세션과 같은 별도의 추가 정보를 관리하지 않아도 되고, 다수의 요청 처리 및 서버의 부하를 줄일 수 있는 성능 상의 이점이 생깁니다.</p>
<p>HTTP 프로토콜은 일반적으로 TCP/IP 통신 위에서 동작하며 기본 포트는 80번입니다</p>
<blockquote>
<h3 id="url이란"><code>URL이란?</code></h3>
<p><strong>*URL은 서버에 자원(resource)를 요청하기 위해 입력하는 영문 주소입니다.
*URL: Uniform Resource Locators</strong></p>
</blockquote>
<p>URL은 숫자로 되어있는 IP주소보다는 훨씬 기억하기 쉽다는 장점이 있습니다.</p>
<p>브라우저에서는 이렇게 url로 되어있는 HTTP 요청을 DNS를 통해 host에 해당하는 실제 IP 주소로 변환하여 서버에 요청(Request)을 보냅니다</p>
<p>*DMS : Domain Name System</p>
<blockquote>
<h3 id="http11-과-http20의-차이는"><code>HTTP/1.1 과 HTTP/2.0의 차이는?</code></h3>
<p><strong>HTTP/1.1은 기본적으로 커넥션 당 하나의 요청과 응답만 처리가능
HTTP/2는 커넥션당 여러 개의 요청과 응답, 다중 요청/응답 처리가능</strong></p>
</blockquote>
<p>HTTP/1.1는 여러 개의 요청을 한 번에 전송할 수 없고 응답 또한 마찬가지다.</p>
<p>따라서 HTML 문서 내에 포함된 여러 개의 리소스 요청 및 시맨틱 태그 즉 CSS 파일을 로드하는 link 태그, 이미지 파일을 로드하는 img 태그, 자바스크립트를 로드하는 script 태그 등에 의한 리소스 요청이 개별적으로 전송되고 응답 또한 개별적으로 전송된다.</p>
<p>이처럼 HTTP/1.1은 리소스의 동시 전송이 불가능한 구조이므로 요청할 리소스의 개수에 비례하여 응답 시간도 증가하는 단점이 있다.</p>
<p>이같은 점을 보완하기 위해 사용하는 HTTP/2는 여러 리소스의 동시 전송이 가능하므로 HTTP/1.1에 비해 페이지 로드 속도가 약 50% 정도 빠르다고 알려져 있다.</p>
<blockquote>
<h3 id="https는-http랑-뭐가-다른가요"><code>HTTPS는 HTTP랑 뭐가 다른가요?</code></h3>
<p><strong>HTTPS는 HTTP에 데이터 암호화가 추가된 프로토콜이다.</strong></p>
</blockquote>
<p>HTTPS는 HTTP(80번)와 다르게 443번 포트를 사용하며, 네트워크 상에서 중간에 제3자가 정보를 볼 수 없도록 <strong>공개키 암호화를 지원하고 있다.</strong></p>
<p>HTTPS를 사용할 경우 내가 브라우저를 통해 입력하는 정보를 (ex: form data) 다른 누군가가 훔쳐보지 못하게 만드는 기능입니다.</p>
<p>HTTP 형식으로 입력한 정보를 보낼 경우, 입력한 형태 그대로 보내지게 된다</p>
<pre><code>id: my_naver_id
pw: my_naver_pw</code></pre><p>따라서 누군가가 악의적으로 이 요청을 캐치해서 내 정보를 들여다 본다면, 그대로 아이디와 비밀번호가 노출될 수 있다.</p>
<p>하지만 HTTPS를 사용할 경우, 이 통신 요청(request)을 보낼 때 응답(response)을 해주는 서버만 알아볼 수 있도록 정보를 암호화해서 보내게 된다
<img src="https://velog.velcdn.com/images/96_inggu/post/fceb4cd5-1c10-45c1-9ff1-d061befd6a33/image.png" alt=""></p>
<pre><code>실제로 이렇다는 것은 아니고 이해를 돕기위해 작성하였습니다 😁

id: ^!@$!A_!@E!@#(_a*&amp;@)
pw: !@#%_+#$%_!#@!$</code></pre><p>또한 HTTPS는 기관으로부터 검증된 사이트만 주소에 HTTPS 사용이 허가되기 때문에, 내가 접속한 사이트가 상대적으로 안전한 주소를 가졌음을 증명하는 것이기도 하다</p>
<blockquote>
<h3 id="대칭키-암호화와-배대칭키-암호화란">대칭키 암호화와 배대칭키 암호화란?</h3>
<p><strong>HTTPS는 공개키/개인키 암호화 방식을 이용해 데이터를 암호화하고 있다. 공개키와 개인키는 서로를 위한 1쌍의 키이다.</strong></p>
</blockquote>
<ul>
<li>공개키: 모두에게 공개된, 또는 공개가 가능한 키</li>
<li>개인키: 나만 가지고 알고 있어야 하는 키</li>
</ul>
<p>공개키와 개인키로 암호화하면 다음과 같은 효과를 얻을 수 있다.</p>
<ul>
<li>공개키 암호화: 공개키로 암호화를 하면 개인키로만 복호화할 수 있다. -&gt; 개인키는 나만 가지고 있으므로, 나만 볼 수 있다.</li>
<li>개인키 암호화: 개인키로 암호화하면 공개키로만 복호화할 수 있다. -&gt; 공개키는 모두에게 공개되어 있으므로, 내가 인증한 정보임을 알려 신뢰성을 보장할 수 있다.</li>
</ul>
<p>HTTPS를 사용하기 위해서는 인증된 기관 CA(Certificate Authority)에 공개키를 전송하여 인증서를 발급받아야 한다.</p>
<h3 id="쿠키-세션이란"><code>쿠키, 세션이란?</code></h3>
<p>HTTP는 항상 연결되어있는 것이 아닌 필요할 때마다 요청을 보내고 응답을 받는 <strong>비연결성</strong>이라는 특징을 가지고 있다.</p>
<p>이는 클라이언트가 응답을 받으면 서버는 접속을 끊는다는 것인데, 연결이 끝나면 상태 정보가 유지되지 않는 특성이 있다.</p>
<p>이렇게 유지되지 않는 로그인 정보를 유지하기 위한 방법이 <strong>쿠키와 세션</strong>이다.</p>
<blockquote>
<h3 id="쿠키란"><code>쿠키란?</code></h3>
<p>HTTP의 일종으로 사용자가 어떠한 웹 사이트를 방문할 경우, 그 사이트가 사용하고 있는 서버에서 사용자의 컴퓨터에 저장하는 작은 기록 정보 파일이다.</p>
</blockquote>
<img src="https://velog.velcdn.com/images/96_inggu/post/cd284a7a-c538-4069-9542-452f2800036f/image.png"  width="30%">



<h3 id="쿠키의-특징"><code>쿠키의 특징</code></h3>
<ul>
<li>HTTP에서 클라이언트의 상태 정보를 클라이언트의 PC에 저장하였다가 필요시 정보를 참조하거나 재사용할 수 있다.</li>
<li>이름, 값, 만료일(저장 기간 설정), 경로 정보로 구성되어 있다.</li>
<li>클라이언트에 총 300개의 쿠키를 저장할 수 있다.</li>
<li>하나의 도메인 당 20개의 쿠키를 가질 수 있다</li>
<li>하나의 쿠키는 4KB(=4096byte)까지 저장 가능하다.</li>
</ul>
<h3 id="쿠키의-동작-순서"><code>쿠키의 동작 순서</code></h3>
<ol>
<li>클라이언트가 페이지를 요청한다 (사용자가 웹사이트 접근) 웹 서버는 쿠키를 생성한다</li>
<li>생성한 쿠키에 정보를 담아 HTTP 화면을 돌려줄 때, 같이 클라이언트에게 돌려준다</li>
<li>넘겨 받은 쿠키는 클라이언트가 가지고 있다가(로컬 PC에 저장) 다시 서버에 요청할 때 요청과 함께 쿠키를 전송한다</li>
<li>동일 사이트 재방문시 클라이언트의 PC에 해당 쿠키가 있는 경우, 요청 페이지와 함께 쿠키를 전송한다</li>
</ol>
<h3 id="사용-예시"><code>사용 예시</code></h3>
<ol>
<li>방문했던 사이트에 다시 방문 하였을 때 아이디와 비밀번호 자동 입력</li>
<li>팝업창을 통해 “오늘 이 창을 다시 보지 않기” 체크</li>
</ol>
<h3 id="쿠키의-약점"><code>쿠키의 약점</code></h3>
<ol>
<li>쿠키의 특징으로는 클라이언트(브라우저)단에 저장된다는 것이다</li>
<li>즉 보안에 약함</li>
<li>쿠키를 훔쳐서 계정 접근 권한 등을 탈취하여 유저의 정보를 악용할 수 있다</li>
</ol>
<pre><code>document.cookie 를 통해 쿠키 스토리지에 저장된 사용자 권한이 있는 쿠키에 접근</code></pre><blockquote>
<h3 id="세션이란"><code>세션이란?</code></h3>
<p><strong>HTTP 세션이란 클라이이언트가 웹서버에 연결된 순간부터 웹 브라우저를 닫을때까지 보관한다</strong></p>
</blockquote>
<p>하지만 보통 세션이라고 말할 때에는 <strong>서버에 세션에 대한 정보(세션 상태, 클라이언트 상태, 세션 데이터 등)를 저장해 놓고 세션 쿠키( 고유한 세션 ID 값 )를 클라이언트에게 주어 서버가 클라이언트를 식별할 수 있도록 하는 방식자체를 의미하는 경우</strong> 가 많다.
<img src="https://velog.velcdn.com/images/96_inggu/post/5242d454-fb2f-4876-826f-5e0151f64318/image.png"  width="30%" align="left" >
<br/>
<br/>
<br/>
<br/></p>
<h3 id="세션의-특징"><code>세션의 특징</code></h3>
<ul>
<li>따로 용량의 제한이 없다 (서버의 능력에 따라 다를 수 있다)</li>
<li>서버에 세션 객체를 생성하며 각 클라이언트 마다 고유한 세션 ID 값을 부여한다</li>
<li>쿠키를 사용하여 세션 ID 값을 클라이언트에 보낸다</li>
<li>웹 브라우저가 종료되면 세션 쿠키는 삭제된다</li>
</ul>
<h3 id="세션의-동작-방식"><code>세션의 동작 방식</code></h3>
<ol>
<li>클라이언트 페이지가 요청한다</li>
<li>서버가 클라이언트마다 개별의 세션 ID를 부여한다</li>
<li>클라이언트는 요청할 때마다 세션 ID를 서버에 전달한다</li>
<li>서버는 받은 세션 ID로 클라이언트 정보를 가져와 활용한다</li>
</ol>
<h3 id="쿠키와-세션의-차이점은"><code>쿠키와 세션의 차이점은?</code></h3>
<ol>
<li><p>저장 위치</p>
<p> 쿠키: 클라이언트에 파일로 저장되어 있다</p>
<p> 세션: 서버에 저장되어 있다</p>
</li>
<li><p>보안</p>
<p> 쿠키: 클라이언트의 브라우저 로컬에 저장되기 때문에 변질되거나 HTTP request 요청 시에 이를 갈취당할 수 있어서 보안에 취약하다</p>
<p> 세션: 쿠키를 이용해서 세션id만 저장하고 그것으로 구분해서 서버에서 처리하기 때문에 비교적으로 안전하다</p>
</li>
<li><p>라이프 사이클</p>
<p> 쿠키: 만료시간은 있지만 파일로 저장되기 때문에 브라우저를 종료해도 계속해서 정보가 남아있다. 만료기간에 따라 상대적으로 넉넉하게 쿠키를 삭제할 때까지 유지된다</p>
<p> 세션: 만료기간을 정할 수는 있지만 브라우저가 종료되면 그에 상관없이 <strong>삭제된다</strong></p>
</li>
<li><p>속도</p>
<p> 쿠키: 쿠키에 정보가 있기 때문에 서버에 요청시 속도가 빠르다</p>
<p> 세션: 정보가 서버에 있기 때문에 처리가 요구되어 비교적으로 느리다</p>
</li>
</ol>
<blockquote>
<h3 id="cors란"><code>CORS란?</code></h3>
<p><strong>CORS는 Cross Origin Resource Sharing의 약자로, 교차 출처 공유라는 의미입니다.</strong></p>
</blockquote>
<p>Origin은 ① scheme, ② host, ③ port 로 이루어진 도메인을 의미합니다. (IE의 경우 port를 비교하지 않음)</p>
<pre><code>https://www.naver.com/

① scheme : https
② host: www.naver.com
③ port: null (공개되지 않음)</code></pre><p>현재 <code>① 자신이 속한 출처(Origin)</code>를 기준으로 <code>② 다른 출처(Origin)</code>에 API를 요청하게 되면 브라우저에서 이 요청으로 넘어오는 경과가 안전한지 판단하게 되는데,</p>
<p>응답을 보내는 출처가 <code>① 자신이 속한 출처</code>가 아닌, <code>② 다른 출처</code>여도 서로 예상되는 출처라면 요청에 대해 허용해주는 응답 헤더를 보내, 브라우저가 응답 결과를 보여줍니다.</p>
<p>이를 CORS(Cross Origin Resource Sharing)이라 합니다.</p>
<h3 id="왜-브라우저가-cors-요청을-처리하나요">왜 브라우저가 CORS 요청을 처리하나요?</h3>
<p>모든 서버들이 다 CORS를 인지하지는 않기 때문이다.</p>
<p>결과적으로 브라우저는 거부했다고 하더라도, 서버는 처리해버리는 결과가 생길 수 있기 때문에</p>
<p>서버가 안전하게 요청을 주고받을 수 있도록 브라우저에서 해당 요청(CORS)을 처리한다</p>
<h3 id="실제-요청에서는-어떻게-처리하나요">실제 요청에서는 어떻게 처리하나요?</h3>
<p>CORS는 다른 Origin에 대한 요청을 허용하는 정책입니다.</p>
<p>같은 Origin에서 http 통신을 하는 경우 알아서 cookie가 request header에 들어가지만, 교차 출처로 요청하는 상황에서는 그렇지 않습니다.</p>
<p>Origin이 다른 http 통신에서는 request header에 쿠키가 자동으로 들어가지 않기 때문에 서버에게 또는 클라이언트에게 내가 어떤 요청을 보내는 지 알려줄 필요가 있습니다.</p>
<pre><code>프론트 &gt; WithCredentials: true

서버 &gt; Access-Control-Allow-Credentials: true</code></pre><h3 id="cors-에러-발생-시-해결방법은"><code>CORS 에러 발생 시 해결방법은?</code></h3>
<ol>
<li>서버 개발자와 빠르게 소통한다</li>
</ol>
<p>만약 프론트에서 CORS관련 설정이 다 끝난 이후에 HTTP 요청을 보냈을 때 CORS 오류가 뜰 경우 해당 오류를 캡쳐해서 같이 확인해보는 방법</p>
<p>먼저 프론트에서 응답 헤더에 제대로 된 정보를 넣어는 지 확신을 가지는 것이 중요하다 (credentials 관련 설정을 했는지?)</p>
<ol>
<li>개발 환경에 프록시 설정을 해둔다</li>
</ol>
<p>만약 개발 환경에 있어서 세팅을 잘 해놓은 상태이고 서버의 세팅이 완벽함에도 문제가 생긴다면, 개발 환경에서의 프록시 설정도 대안이 될 수 있습니다.</p>
<p>해당 프록시 설정은 환경에 따라 (CRA면 CRA) 방법이 다르므로 확인해보고 넣으시면 됩니다!</p>
<blockquote>
<h3 id="saas란"><code>SaaS란?</code></h3>
<p>서비스로서의 소프트웨어(Software-as-a-Service, SaaS)는 클라우드 애플리케이션과 기본 IT 인프라 및 플랫폼을 사용자에게 제공하는 클라우드 컴퓨팅 형태입니다.</p>
</blockquote>
<p><code>SaaS</code>는 클라우드 서비스 제공업체<code>(ex: AWS, Azure, IBM Cloud, ...)</code>에 의해 클라우드 환경이 관리됩니다.</p>
<p>SW, 데이터를 PC에 별도 설치없이 웹에서 직접 사용할 수 있다는 장점이 있습니다.</p>
<p>SaaS의 예로는 <code>Google Docs, Microsoft Office 365</code>와 같은 소비자 대상 서비스와 <code>인사 관리 소프트웨어, 콘텐츠 관리 시스템, 고객 관계 관리 툴, IDE(통합 개발 환경)를 제공하는 엔터프라이즈 서비스</code>가 있습니다.</p>
<h3 id="saas를-이용한-예시"><code>SaaS를 이용한 예시)</code></h3>
<h3 id="1-paasplatform-as-a-service">1. PaaS(Platform as a Service)</h3>
<ul>
<li>앱 개발 지원 도구 제공합니다. 사용자가 실제 애플리케이션및 데이터를 처리하므로 개발자와 프로그래머에게 이상적인 솔루션일 수 있습니다</li>
<li>플랫폼 기반(Cloud 업체가 제공하는 S/W 사용)으로 앱을 개발하기 때문에 특정 플랫폼에 종속 가능성이 있음 → 타업체의 PaaS에서 호환 어려움</li>
</ul>
<h3 id="2-iaasinfrastructure-as-a-service">2. IaaS(Infrastructure as a Service)</h3>
<ul>
<li>서비스 제공업체가 고객을 대신해 클라우드를 통해 인프라(실제 서버, 네트워크, 가상화, 스토리지)를 관리합니다</li>
<li>컴퓨팅 인프라(운영체제, 서버, 스토리지 등) 제공(Amazon의 AWS 등)</li>
<li>물리적으로 만들어지지 않은 가상 환경이므로 시스템 해체 용이</li>
</ul>
<blockquote>
<h2 id="개발-방법론">개발 방법론</h2>
<p>폭포수 방법론과 애자일 방법론이란?</p>
</blockquote>
<blockquote>
<h3 id="폭포수-방법론"><code>폭포수 방법론</code></h3>
<p>폭포수 모델(waterfall model)은 순차적인 소프트웨어 개발 프로세스로, 개발 흐름이 마치 폭포수처럼 지속적으로 아래로 향하는 것처럼 보이는 데서 이름이 붙여졌습니다. </p>
</blockquote>
<p>이 폭포수 모델의 흐름은 소프트웨어 시험 → 요구사항 분석 → 설계 → 구현 → 통합 → 유지보수단계에 이릅니다.</p>
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Waterfall_model.svg/525px-Waterfall_model.svg.png"  width="30%">

<p>폭포수 모델을 따르기 위해서는, 완전히 순차적으로 한 단계, 한 단계를 진행해 나가야 한다. 예를 들어, 가장 먼저 요구사항 기술을 진행하여 이를 확정하여야 하며, 그런 이후에 설계를 진행할 수 있다.</p>
<p>소프트웨어가 설계된 후, 그 설계도가 구현자(개발자)에게 따라서 구현해야할 계획으로 전달된다. 따라서 설계가 완전히 완료된 후에 설계에 대한 구현이 코더에 의해 진행될 수 있는 것이다. 이 구현의 마지막 단계에 이르면, 가각의 생성된 컴포넌트를 결합하여, 새로운 기능을 실현시키고 그때까지 발생한 버그를 해결하게 된다.</p>
<p>폭포수 모델은 전 단계가 수행되어 완료되기 전에는 다음 단계를 진행할 수 없도록 제한하는 것이 특징이다.</p>
<blockquote>
<h3 id="애자일-방법론이란"><code>애자일 방법론이란?</code></h3>
<p><code>**애자일</code>은 신속한 반복 작업을 통해 실제 작동 가능한 소프트웨어를 개발하여 지속적으로 제공하기 위한 소프트웨어 개발 방식입니다.**</p>
</blockquote>
<p>작동하는 소프트웨어의 작은 구성 요소를 신속하게 제공하여 고객의 만족도를 개선하는 것이 애자일 방법론의 핵심입니다.</p>
<p>다음과 같은 부분에 가치를 둡니다.</p>
<ul>
<li><strong>개인과 개인 간의 상호작용</strong>이 프로세스 및 툴보다 우선</li>
<li><strong>작동하는 소프트웨어</strong>가 포괄적인 문서보다 우선</li>
<li><strong>고객과의 협업</strong>이 계약 협상보다 우선</li>
<li><strong>변화에 대응</strong>하는 것이 계획을 따르는 것보다 우선</li>
</ul>
<blockquote>
<h2 id="캐시란"><code>캐시란?</code></h2>
<p><strong>Cache</strong>는 프랑스어로 <code>숨기다</code> 라는 뜻을 가지는 단어인 cacher 에서 파생된 단어로, 물건을 일시적으로 저장, 보관하기 위해 사용하는 곳</p>
</blockquote>
<pre><code>Caching == Cache + ing</code></pre><h3 id="기술적-cache">기술적 Cache</h3>
<p>자주 필요한 데이터나 값의 복사본을 일시적으로 저장, 보관하기 위해 사용하는 곳</p>
<pre><code>### Flow

- CPU는 데이터 처리를 위해 메모리와 끊임없이 데이터를 주고받는 구조
- 메모리가 CPU의 데이터 처리 속도를 쫓아가지 못함
- CPU가 메모리를 기다려야 하는 `병목현상` 발생</code></pre><p>여기서 발생하는 딜레이를 완화하기 위해 CPU와 메인 메모리 사이에 크기는 작지만 속도가 빠른 <strong>캐시 메모리</strong>를 두고,</p>
<p>캐시 메모리에 향후 재사용할 가능성이 클 것으로 예상되는 데이터의 복사본을 저장해 둔 후 바로바로 전달할 수 있도록 합니다</p>
<p>캐시 메모리의 용량을 크게 쓰거나, 아예 메인 메모리로 사용하면 되지 않냐고 물을 수 있는데, 캐시 메모리는 가격이 비싸다</p>
<p>메인 메모리는 DRAM, 캐시 메모리는 SRAM의 구조를 가지는데, SRAM이 셀당 트랜지스터의 개수도 많고 물리적으로 차지하는 면적 또한 훨씬 크다</p>
<pre><code>메모리의 계층 구조

- 상층 구조로 갈수록 빠르고, 비싸고, 작은 용량을 가집니다
- 또한 CPU와 가까이 위치하고 있습니다
- 캐시 메모리 들이 메인 메모리(RAM)보다 빠르지만, 비싸며, 면적 차지 또한 큰 것을 알 수 있습니다</code></pre><p>이러한 메모리 계층 구조의 목적은 캐싱을 이용하여 빠르고 작은 메모리와 크고 느린 메모리의 조합을 이용하여 크고 빠른 메모리처럼 행동하도록 만들기 위함입니다.</p>
<p>재사용할 가능성이 클 것으로 예상되는 데이터의 복사본을 저장함으로써 캐싱을 할 수 있다고 했는데, 재사용할 가능성이 클 지는 어떻게 알 수 있을까? - 데이터 지역성의 원리를 이용합니다</p>
<pre><code>### 데이터 지역성의 원리

- 데이터 접근이 시간적 혹은 공간적으로 가깝게 일어나는 것을 의미함
- 한 번 참조된 변수는 잠시 후에 또 참조될 가능성이 높다
- 어떤 데이터에 접근할 때, 그 데이터 근처에 있는 다른 데이터도 참조될 가능성이 높다</code></pre><p>캐싱: 캐시에 데이터나 계산된 결과 값의 복사본을 저장해 둠으로써 전체적인 처리 속도를 향상시킨다.</p>
<ul>
<li>데이터에 직접적으로 접근하는 데 걸리는 시간이 오래 걸릴 때</li>
<li>필요한 값을 얻기 위해 계산하는 과정을 생략하고 싶을 때</li>
<li>반복적으로 동일한 결과를 돌려주는 경우(이미지나 썸네일 등)</li>
</ul>
<p>캐싱은 복사본을 이용하는 것이다.</p>
<p>복사본과 원본이 달라지는 경우가 생길 수 있으니 일관성 유지에 유의하자</p>
<blockquote>
<h3 id="ci-cd란"><code>CI CD란?</code></h3>
<p><strong>CI/CD는 애플리케이션 개발 단계를 자동화하여 애플리케이션을 보다 짧은 주기로 고객에게 제공하는 방법입니다.
CI/CD의 기본 개념은 지속적인 통합, 지속적인 서비스 제공, 지속적인 배포입니다.</strong></p>
</blockquote>
<p>CI/CD는 새로운 코드 통합으로 인해 개발 및 운영팀에 발생하는 문제(일명 “인테그레이션 헬(integration hell)”)을 해결하기 위한 솔루션입니다.</p>
<p><code>CI</code>는 개발자를 위한 자동화 프로레스인 지속적인 통합 <code>Continuous Integration</code>을 의미합니다.</p>
<p>CI를 성공적으로 구현할 경우 애플리케이션에 대한 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트되어 공유 리포지토리에 통합되므로 여러 명의 개발자가 동시에 애플리케이션 개발과 관련된 코드 작업을 할 경우 서로 충돌할 수 있는 문제를 해결할 수 있습니다.</p>
<p><code>CD</code>는 지속적인 서비스 제공 <code>Continuous Delivery</code> 및 지속적인 배포 <code>Continuous Deployment</code>를 의미합니다.</p>
<p><code>지속적인 제공</code>이란 개발자들이 애플리케이션에 적용한 변경 사항이 버그 테스트를 거쳐 리포지토리(예: GitHub 또는 컨테이너 레지스트리)에 자동으로 업로드되는 것을 뜻합니다.</p>
<p><code>지속적인 배포</code>란 개발자의 변경 사항을 리포지토리에서 고객이 사용 가능한 프로덕션 환경까지 자동으로 릴리스하는 것을 의미합니다</p>
<blockquote>
<h3 id="cdn이란-뭔가요"><code>CDN이란 뭔가요?</code></h3>
<p>*<em>CDN(콘텐츠 전송 네트워크)은 지리적으로 분산된 여러 개의 서버입니다 *</em></p>
</blockquote>
<p><strong>웹 콘텐츠를 사용자와 가까운 곳에서 전송함으로써 전송 속도를 높입니다</strong>. 전 세계 데이터센터는 파일 복사본을 임시로 저장하는 프로세스인 캐싱을 사용합니다. 따라서 <strong>사용자는 가까운 서버를 통해 웹 활성화 디바이스 또는 브라우저에서 인터넷 콘텐츠에 빠르게 접속할 수 있습니다</strong>. CDN은 웹 페이지, 이미지, 비디오 등의 콘텐츠를 사용자의 물리적 위치와 가까운 프록시 서버에 캐싱합니다. 이렇게 하면 콘텐츠가 로딩될 때까지 기다릴 필요 없이 영화 감상, 소프트웨어 다운로드, 은행 잔고 확인, 소셜 미디어 포스팅, 구매 등의 작업을 할 수 있습니다.</p>
<p><strong>인터넷으로 그래픽과 비디오 등 용량이 큰 웹 콘텐츠를 전송하면 트래픽이 폭주할 때처럼 네트워크 혼잡 문제가 발생할 수 있습니다</strong>. <strong>CDN 서비스는 이러한 문제를 해결하기 위해 개발되었습니다</strong>. 중앙 서버에서 개별 사용자에게 콘텐츠를 전송하려면 시간이 너무 오래 걸렸습니다. 이제 CDN은 텍스트, 그래픽, 스크립트, 미디어 파일부터 소프트웨어 다운로드, 문서, 포털, 이커머스, 라이브 스트리밍 미디어, 온디맨드 비디오 스트리밍 미디어, 소셜미디어 사이트에 이르기까지 모든 것을 처리합니다.</p>
<p><strong>CDN의 미션은 지연 시간을 줄이는 것입니다</strong>. <strong>지연 시간은 웹 페이지 또는 비디오 스트리밍 콘텐츠가 디바이스에 완전히 로딩되기 전에 발생하는 불편한 지연을 의미합니다.</strong> 지연 시간은 밀리초 단위이지만 사용자가 체감하는 시간은 매우 길며, 시간 초과 또는 로딩 오류가 발생할 수 있습니다. 콘텐츠가 사용자에게 도달하기 위해 이동해야 하는 물리적 거리를 줄여 지연 시간을 줄이는 콘텐츠 전송 네트워크도 있습니다. 따라서 CDN이 보다 광범위하고 넓게 분산되어 있으면 사용자와 최대한 가까운 곳에 콘텐츠를 배치함으로써 웹 콘텐츠를 보다 빠르고 안정적으로 전송할 수 있습니다.</p>
<p>어떤 사람들은 CDN을 ‘엣지’라고 부릅니다. 엣지는 물리적 환경과 디지털 세계가 네트워크 경계에서 만나 상호작용하는 곳입니다. 전 세계에 광범위하게 분산된 수천 개의 PoP(<strong>Points of Presence, 전 세계 여러 곳</strong>)</p>
<p>CDN 을 사용하는 대표적인 기업 - 넷플릭스 , 슬랙 </p>
<p>CDN 서비스를 제공하는 대표적인 기업 - Amazon CloudFront</p>
<p><strong>자료출처 :</strong>
<a href="https://joshua1988.github.io/web-development/http-part1/">https://joshua1988.github.io/web-development/http-part1/</a>
<a href="https://github.com/junh0328/TIL/tree/master/Chore#%EC%BF%A0%ED%82%A4%EC%99%80-%EC%84%B8%EC%85%98">https://github.com/junh0328/TIL/tree/master/Chore#%EC%BF%A0%ED%82%A4%EC%99%80-%EC%84%B8%EC%85%98</a>
<a href="https://velog.io/@young_pallete/CORS">https://velog.io/@young_pallete/CORS</a>
<a href="https://www.redhat.com/ko/topics/cloud-computing/what-is-saas">https://www.redhat.com/ko/topics/cloud-computing/what-is-saas</a>
<a href="https://www.redhat.com/ko/devops/what-is-agile-methodology">https://www.redhat.com/ko/devops/what-is-agile-methodology</a>
<a href="https://www.redhat.com/ko/topics/devops/what-is-ci-cd">https://www.redhat.com/ko/topics/devops/what-is-ci-cd</a></p>
<blockquote>
<p>7월 21일(금) cs스터디 정리자료 이상입니다 !</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>