<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jamee_.log</title>
        <link>https://velog.io/</link>
        <description>안녕하세요</description>
        <lastBuildDate>Mon, 16 Mar 2026 05:51:55 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jamee_.log</title>
            <url>https://velog.velcdn.com/images/jamee_/profile/b080b65d-bb12-42d5-b727-204e8082ce3c/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jamee_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jamee_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Claude] 워크플로우 기초부터 실전까지(2)]]></title>
            <link>https://velog.io/@jamee_/Claude-%EC%9B%8C%ED%81%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EA%B8%B0%EC%B4%88%EB%B6%80%ED%84%B0-%EC%8B%A4%EC%A0%84%EA%B9%8C%EC%A7%802</link>
            <guid>https://velog.io/@jamee_/Claude-%EC%9B%8C%ED%81%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EA%B8%B0%EC%B4%88%EB%B6%80%ED%84%B0-%EC%8B%A4%EC%A0%84%EA%B9%8C%EC%A7%802</guid>
            <pubDate>Mon, 16 Mar 2026 05:51:55 GMT</pubDate>
            <description><![CDATA[<h3 id="여러가지-mcp-서버-설치하기">여러가지 MCP 서버 설치하기</h3>
<pre><code>// context7
// 최신 라이브러리 참고할 수 있도록 도움
claude mcp add --scope user --transport http context7 https://mcp.context7.com/mcp

// Sequential thinking mcp server
// 복잡한 문제해결을 단계별로 분석하고 추론할 수 있도록 도움
&quot;sequential-thinking&quot;: {
  &quot;command&quot;: &quot;npx&quot;,
  &quot;args&quot;: [
    &quot;-y&quot;,
    &quot;@modelcontextprotocol/server-sequential-thinking&quot;
  ]
}

// shadcn/ui mcp
// 문서나 예제를 빠르게 가져오는데 도움</code></pre><hr>
<h3 id="ai-작업-관리-도구">AI 작업 관리 도구</h3>
<p>랜딩페이지나 투두리스트앱은 AI 를 활용하여 간단히 만들 수 있는데, 쇼핑몰과 같은 복잡한앱은 어떻게 체계적으로 관리하고 개발을 할 수 있을까?
핵심은 <strong>복잡한 프로젝트를 작은 작업들로 나눠서 순차적으로 완료</strong></p>
<h4 id="taskmaster-ai">TASKMASTER AI</h4>
<p>사용자와 AI 가 협력해서 체계적으로 개발
분석할까요? 이 작업은 복잡하니 나눌까요? 최신 기술스택을 조사할까요? 처럼 협력해서 진행
<strong>Main Model:</strong> 메인으로 사용할 AI Model
<strong>Research Model:</strong> 최신 정보 검색이나 기술 동향 조사
<strong>Fallback Model:</strong> 메인 Model 실패 시 자동 전환되는 Model</p>
<pre><code>// 실행 순서

// docs 폴더에 작성된 PRD 파싱
// 작업이 완료되면 tasks 폴더에 task 생성됨
task-master parse-prd

// 모든 task 목록 보기
// 여기서 복잡한 task 는 서브 task 들로 나눠주기
task-master list

// 현재 각 task 들에 대한 서브 task 들을 생성
task-master expand --all

// 서브 task 까지 보기
task-master list --with-subtasks

// Status 변경하여 취소하거나 스킵할거 수정작업

// 작업 시작
task-master next
</code></pre><h4 id="shrimp-task-manager">Shrimp Task Manager</h4>
<p>AI 가 개발자가 되어 주도적으로 개발
시니어 개발자 고용한 느낌
스스로 기술 스택 조사, 프로젝트 규칙 정하고, 작업 계획 세운 후에 구현까지</p>
<pre><code>// 실행 순서

// shrimpt-task-manager 설치
git clone https://github.com/...
cd mcp-shrimp-task-manager
npm install
npm run build

// 프로젝트내에서 사용하기위한 mcp 세팅
args: 빌드로 생성된 파일을 사용하기위해 경로 뒷부분에는 /dist/index.js
DATA_DIR: 태스크 관리했던 다양한 데이터 저장

// 현재 프로젝트 구조 스캔
// 개발 표준과 규칙을 자동 설정
// 어떤 기술스택, 구조를 가져갈 지 초기화
init_project_rules

// 큰 요청을 단계별 실행 가능한 계획으로 나눠줌 
plan_task: [ROADMAP.md 에 생성된 task 제목]
@docs/ROADMAP.md

// 위 내용이 요청되면 태스크가 서브 태스크로 나눠질 수 있음

// 현재 생성된 task 들 확인
list_tasks

// 프로젝트 완성을 위해 하나씩 수행
execute_task

// 중간에 수정해야할 부분이 있다거나 추가할 부분이 있다면 
// plan task 로 생성하고 차례대로 다시 실행

// 어떠한 작업을 수행했고, 어떠한 작업이 남았는지 확인
list_tasks

// 연속 모드 사용하면 모든 작업 알아서 순차적 수행

// 모든 작업 완료한 후 클로드 init</code></pre><hr>
<h3 id="개발-오류-줄이기">개발 오류 줄이기</h3>
<p>AI 에게 개발을 진행시킨 결과물의 오류 줄이는 방법</p>
<h4 id="테스트-코드">테스트 코드</h4>
<p>비즈니스 로직이나 API 연동과 같은 코드를 구현한 다음에는
playwright mcp 를 활용해서 통합 테스트하는 프롬프트 추가</p>
<h4 id="린트-및-타입-체크">린트 및 타입 체크</h4>
<p>스스로 검사 및 빌드</p>
<pre><code>// CLAUDE.md

## 작업 관료 체크리스트
npm run check-all
npm run build</code></pre><hr>
<h3 id="claude-agent-skills">Claude Agent Skills</h3>
<p>Claude 를 위한 업무 메뉴얼
ex)) 엑셀, PPT 가이드라인 제공하여 일관된 수준으로 높은 퀄리티의 결과물을 받을 수 있도록</p>
<p>기본적으로 Claude 에는 몇 가지 스킬이 내장되어있는데,
만약 요청으로 &quot;PPT 제안서를 만들어줘&quot; 라고 한다면, 자동으로 PPTX 스킬문서를 확인하여 전문가수준으로 만들어줌</p>
<p>스킬 폴더는 아래와 같이 구성되어 있음
지침서: 메뉴얼
참고자료: 추가 정보
스크립트: 실제 실행되는 코드 파일</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/45504835-9332-4627-b90e-b437b506c717/image.png" alt=""></p>
<h4 id="이게-mcp-서브-에이전트와-무슨-차이가-있을까">이게 MCP, 서브 에이전트와 무슨 차이가 있을까?</h4>
<p>★ MCP 와 서브 에이전트는 사용을 하든 안하든 Context 에 자리를 차지하고 있는 상태인데, 스킬은 차지하고 있는게 아닌 점진적으로 로드된다. 즉, 컨텍스트 공간을 효율적으로 사용</p>
<p>이게 가능한 이유는</p>
<ol>
<li>클로드가 처음 시작할 때 모든 skill.md 파일들을 훑어보면서 프론트매터 정보인 name, description 만 먼저 읽음
이렇다보니 적은 토큰으로도 많은 스킬 정보들을 파악할 수 있음</li>
<li>실제로 해당 스킬을 사용해야할 때 로드됨 ( 이때 컨텍스트 메모리에 할당 )</li>
<li>리소스 로드. skill.md 파일을 읽다가 참조 링크를 발견하면 필요한 순간에만 열어서 확인. 스크립트 코드의 경우 컨텍스트에 로드하지 않고 직접 실행 후 결과만 컨텍스트에 할당</li>
</ol>
<p>★ 여러 스킬을 조합하여 함께 사용 가능
예를들어 워드문서에 데이터 차트를 넣어달라 요청하면
문서 스킬과 차트 생성 스킬을 조합하여 복잡한 작업을 완성</p>
<blockquote>
<p>추가 2.1.0 : 
<strong>context:</strong> 프론트매터에 context 를 fork 로 설정하면 서브 에이전트처럼 컨텍스트를 별도로 지님
<strong>agent:</strong> context 가 fork 로 설정되어있을 때 사용할 에이전트 유형. Explore, Plan, general-purpose ...
별도의 컨텍스트를 지닌 상태에서 설정하는거보면 서브 에이전트 느낌?
<strong>hooks:</strong> 해당 스킬 사용 트리거에 따라 특정 이벤트 실행 가능</p>
</blockquote>
<p>대체해야할 작업
커스텀 커맨드 =&gt; skills
자주 사용하는 서브 에이전트 =&gt; skills
일회성 서브 에이전트 =&gt; sub agent
mcp =&gt; mcp tool search</p>
<hr>
<h3 id="플러그인">플러그인</h3>
<p>매번 프로젝트 생성할때마다 이전 프로젝트의 서브에이전트, mcp, 훅, 커스텀커맨드, 스킬... 등을 복붙으로 가져오면 번거로움
플러그인으로 패키징하여 불러올 수 있음
또한, 전세계 사람들이 이미 구현해둔 패키징들을 마켓플레이스에서 가져올 수 있음</p>
<hr>
<h3 id="mcp-tool-search">MCP Tool Search</h3>
<p>MCP 를 필요할때만 불러와 컨텍스트에 할당하여 사용해 효율 측면에서 좋음
MCP 가 컨텍스트 윈도우의 10%를 넘으면 자동으로 활성화</p>
<pre><code>// settings.local.json
&quot;env&quot;: {
    // 10% 가 아닌 5% 로 설정
    // true 로 설정 시 항상 활성화
    &quot;ENABLE_TOOL_SEARCH&quot;: &quot;auto:5&quot;
}</code></pre><hr>
<h3 id="agent-teams">Agent Teams</h3>
<p>기존 서브 에이전트는 독립된 공간에서 독자적으로 실행된 결과를 메인에 넘기는거였다면
Agent Teams 는 하나의 팀을 이루면서 서로 소통하면서 협업하는 방식
각 팀원들은 메인 세션처럼 CLAUDE.md, mcp, skills 이런거는 동일하게 로드됨
공식문서는 팀원 3~5명 권장</p>
<pre><code>Agent Teams 를 사용해서 직장인 점심 도시락 배달 서비스 런칭 기획안을 만들어줘.

팀원 4명을 생성해줘.
- 팀원 1: 시장 조사 담당 (시장규모, 타겟 고객, 성장 추이)
- 팀원 2: 경쟁사 분석 담당 (직접/간접 경쟁사, 차별화 전략)
- 팀원 3: 예산안 담당 (초기 투자, 운영비, 손익분기점)
- 팀원 4: 일정표 담당 (5단계 로드맵, 마일스톤)

각 팀원이 작업을 마치면 결과를 하나의 기획안으로 종합해줘.</code></pre><p><img src="https://velog.velcdn.com/images/jamee_/post/ec14c1b1-e601-4f4f-bea4-2aed928e2c61/image.png" alt=""></p>
<p>Agent Teams 활성화</p>
<pre><code>// settings.local.json
&quot;env&quot;: {
    &quot;CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS&quot;: &quot;1&quot;
}</code></pre><h4 id="효율적으로-agent-teams-사용하기">효율적으로 Agent Teams 사용하기</h4>
<p>비용은 절반이며, 품질은 2배</p>
<ol>
<li><p><strong>계획 승인 모드</strong>
일반적으로 팀을 생성하면 바로 코딩을 시작하는데, 프롬프트에 다음 내용 추가 </p>
<p> 각 팀원이 변경하기 전에 plan approval 을 받도록 해줘 
위 프롬프트는 팀원이 읽기 전용 모드(plan mode)가 됨</p>
<ul>
<li><ol>
<li>팀 생성</li>
</ol>
<ul>
<li><ol start="2">
<li>계획 분석 (plan mode)</li>
</ol>
</li>
<li><ol start="3">
<li>계획 제출 (리드에게 보고)</li>
</ol>
</li>
<li><ol start="4">
<li>승인/거절 (리드가 판단)</li>
</ol>
</li>
<li><ol start="5">
<li>구현 시작 (승인 후 실행)</li>
</ol>
</li>
</ul>
<p>TeamCreate 로 에이전트 팀 생성해줘 // 서브 에이전트로 진행하는거 방지
에이전트 팀으로 프로젝트 코드를 병렬 리뷰하고 문제를 수정해줘
각 팀원이 변경하기 전에 plan approval 을 받도록 해줘</p>
<p>팀원 1 (보안): API 키 노출, XSS 취약점, 환경변수 안전성
팀원 2 (성능): &#39;use cache&#39; 패턴, 리렌더링, 이미지 최적화
팀원 3 (아키텍처): 의존성 방향 (app-&gt;features-&gt;shared-&gt;ui), cn() import 경로</p>
</li>
</ul>
</li>
<li><p><strong>모델믹싱</strong>
Agent Teams 의 토큰 사용량은 활성 팀의 수에 비례해서 증가
즉, 적절한 모델을 각 팀원에 맞게 분배하는게 중요</p>
</li>
</ol>
<p><strong>리드:</strong> 조율, 추론, 계획 검토, 작업 분해, 품질 판단 / Opus 추천
<strong>팀원:</strong> 코드 생성, 파일 편집, 구현 작업 / Sonnet 추천
    현재 세션 모델이 opus 라면 리드는 자동으로 Opus</p>
<pre><code>팀원은 모두 Sonnet 사용해</code></pre><ol start="3">
<li><strong>여러 측면에서 생각</strong><ul>
<li>팀원들끼리 소통이 필요한가? 서브 에이전트로도 충분하지 않은가</li>
<li>작업이 끝난 팀원은 즉시 종료. 팀원 세션이 살아있으면 계속 토큰 소모</li>
<li>CLAUDE.md 에 완료 후 즉시 종료, 반복 3회 제한 등 규칙 생성</li>
</ul>
</li>
</ol>
<h4 id="hooks-이벤트-자동화">Hooks 이벤트 자동화</h4>
<p>팀원 에이전트는 맡은 작업을 다 끝내거나 지시를 기다릴때 idle 상태가 됨
Claude 의 Hooks 로 이런 상태변환을 감지할 수 있음</p>
<ol>
<li><p><strong>TeammateIdle</strong>
팀원 단위로 발동되며, 팀원이 모든 일을 마치고 idle 상태에 들어가려할 때 발동되는 훅</p>
<p> 작업이 끝나면 요약 보고서를 작성해라</p>
<p>조금전에 처리한 업무와 관련된 일을 시킬 수 있음</p>
</li>
<li><p><strong>TaskCompleted</strong>
태스크 단위로 발동되며, 팀원이 해당 작업을 다 완료한 상태에 발동되는 훅</p>
</li>
</ol>
<p>하나의 팀원이 3개의 Task 를 진행한다고 가정했을때 TaskCompleted 훅은 3번, TeammateIdle 훅은 1번 발동
그래서 TaskCompleted 훅은 해당 Task 에 대한 검증을 수행하는데 사용되고,
TeammateIdle 훅은 팀원 전체 작업을 검증하는데 사용</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Claude] 워크플로우 기초부터 실전까지(1)]]></title>
            <link>https://velog.io/@jamee_/Claude-%EC%9B%8C%ED%81%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EA%B8%B0%EC%B4%88%EB%B6%80%ED%84%B0-%EC%8B%A4%EC%A0%84%EA%B9%8C%EC%A7%801</link>
            <guid>https://velog.io/@jamee_/Claude-%EC%9B%8C%ED%81%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EA%B8%B0%EC%B4%88%EB%B6%80%ED%84%B0-%EC%8B%A4%EC%A0%84%EA%B9%8C%EC%A7%801</guid>
            <pubDate>Fri, 13 Mar 2026 06:38:14 GMT</pubDate>
            <description><![CDATA[<h3 id="model-alias">Model Alias</h3>
<p>클로드 코드에서 모델은 아래와 같이 구성되어있다.
Opus: 상급
Sonnet: 중급
Haiku: 초급
<strong>만약 기획을 Opus 로 짜고, 코드는 Sonnet 으로 작성하고 싶다면
매번 모델을 변경하면서 번거롭게 해야할까?</strong></p>
<pre><code>/model opusplan</code></pre><p>/model 에서는 앞선 3개의 종류만 명시되어있는데,
공식문서를 읽다보니 발견된 opusplan!
설계나 기획은 opus 를 사용하고, 실제 코드 작성할때는 내부적으로 Sonnet 으로 전환됨
유용하게 사용할 수 있을 것 같다</p>
<hr>
<h3 id="context-window">Context window</h3>
<p>현재까지의 대화를 담은 통
보통 200K tokens (책 한권 분량)
차면 찰수록 AI 가 바보가 될 확률이 올라감
input(입력) 과 output(응답) 모두 컨텍스트에 담김
<strong>서브 에이전트, MCP 등 확장 기능도 Context window 차지</strong></p>
<ol>
<li>새로운 기능 구현할 때 clear 명령</li>
<li>구현하면서 /context 로 현재 컨텍스트 사용량 체크</li>
<li>컨텍스트 사용량이 70%를 넘어가면 clear, 계속 대화를 이어가고 싶다면 compact<pre><code>/model sonnet[1m]</code></pre>위와 같이 입력하면 100만 토큰(Beta) 지정</li>
</ol>
<hr>
<h3 id="최대-사용량-비교">최대 사용량 비교</h3>
<p>플랜별로 어떤 모델을 사용하면 좋을지 정리
<img src="https://velog.velcdn.com/images/jamee_/post/115508bb-3dee-4d9d-8750-2634adf7ee5b/image.png" alt=""></p>
<hr>
<h3 id="사용량-확인">사용량 확인</h3>
<pre><code>/usage</code></pre><p>Current session: 5시간 단위로 리셋
Current week(all models): 일주일 단위로 리셋 opus, sonnet, haiku
Current week(sonnet only): 일주일 단위로 리셋 sonnet</p>
<pre><code>/statusline</code></pre><hr>
<h3 id="토큰-효율적으로-사용-방법">토큰 효율적으로 사용 방법</h3>
<p>명확하게 범위를 제한해서 요청하기
ex)) 간단한, 단일 파일로, ~줄 이하, 배포/테스트/빌드 제외</p>
<hr>
<h3 id="응답-출력-스타일-변경">응답 출력 스타일 변경</h3>
<pre><code>(구)
/output-style
(현)
/config 안에 들어있음</code></pre><ol>
<li>Default: 기본값</li>
<li>Explanatory: 코드 작성하면서 중간중간 교육적 인사이트 제공. 왜 이렇게 구현했으며 패턴은 무엇인지</li>
<li>Learning: 통찰력 공유 및 TODO 마커를 남겨서 사용자가 직접 구현하도록 유도</li>
</ol>
<p>settings.json 파일내에 outputStyle 지정 시 매번 해당 스타일로 응답</p>
<h4 id="나만의-커스텀-출력-만들기">나만의 커스텀 출력 만들기</h4>
<p>아래와 같이 형식이 나뉘어져 있음</p>
<ol>
<li>출력 스타일의 메타정보를 갖고 있는 프론트 메터 부분
name, description 외에 keep-coding-instructions: true 가 선언되어있어야 코드 작성됨
<img src="https://velog.velcdn.com/images/jamee_/post/d25f5f56-9c6d-4eff-b422-109300add153/image.png" alt=""></li>
<li>실제 어떻게 응답하라는 지침
<img src="https://velog.velcdn.com/images/jamee_/post/4e6a2405-f0e3-42db-a1e7-8c194a00bea0/image.png" alt=""></li>
</ol>
<p>이렇게 설정하면 /output-style 에서 해당 커스텀 스타일을 선택할 수 있음</p>
<hr>
<h3 id="메모리-파일">메모리 파일</h3>
<p>세션을 종료(/exit) 하면 백지 상태가 됨
이 프로젝트에서 매번 지켜야할 규칙 정리 (CLAUDE.md)
매번 세션 시작할때 Claude 가 자동으로 읽음</p>
<pre><code>이미 프로젝트가 진행되어있는 경우
아래 명령어를 통해 초기 CLAUDE.md 파일 생성
현재 프로젝트 구조 분석해서 규칙 정리
/init

현재 작성된 사용자, 프로젝트, 로컬 등 메모리 편집하고 싶다면
/memory</code></pre><p>CLAUDE.md 파일은 500줄 이하로 유지 권장
그러므로 파일 참조 import 구문을 적극 활용하여 작성하기</p>
<h4 id="모듈형-규칙">모듈형 규칙</h4>
<p>CLAUDE.md 파일에 import 구문으로 작성하면 정리가 안되고 방대해지는 문제가 있음
모듈형 규칙을 사용하면 디렉토리 구조가 더 깔끔해지고 별도의 import 도 필요없이 매번 세션 시작할때마다 rules 도 함께 읽음</p>
<p>추가로 특정 디렉토리나 파일에만 적용시키고 싶은 경우
path frontmatter 사용
ex)) API 작성 규칙</p>
<p>.claude/rules/ 디렉토리에 md 파일 배치
<img src="https://velog.velcdn.com/images/jamee_/post/4ef87441-85e5-48e9-b2a1-084a18e2758e/image.png" alt=""></p>
<p>각 파일은 하나의 주제를 다루어야함
파일명만 봐도 내용을 알 수 있어야함</p>
<hr>
<h3 id="mcp-활용">MCP 활용</h3>
<p><strong>Context7:</strong> AI 어시스턴트들이 항상 최신 라이브러리 정보를 알 수 있게 도와주는 서비스</p>
<hr>
<h3 id="클로드코드에서-권장하는-개발-워크플로우">클로드코드에서 권장하는 개발 워크플로우</h3>
<p><strong>Explore(탐색)</strong>: 도메인 지식, 관련 파일, 이미지 등 정보 수집
<strong>plan(계획)</strong>: 특정 문제에 접근하는 방법에 대한 계획
<strong>code(구현)</strong>: &quot;~기능 만들어줘&quot; 진행 단계마다 멈춰서 &quot;이 부분은 잘 동작할까?&quot;, &quot;빠뜨린 건 없는가?&quot; 검증
<strong>commit(커밋)</strong></p>
<hr>
<h3 id="프롬프트-엔지니어링-기법">프롬프트 엔지니어링 기법</h3>
<ol>
<li><p>CoT (생각의 사슬)
Chain of Thought.
단계별로 문제를 해결하면 일반적으로 복잡한 작업에서 오류가 줄어듬</p>
</li>
<li><p>페르소나
특정 도메인 지식을 필요로 할 때 역할을 부여하면 성능을 크게 향상시킬 수 있음</p>
</li>
</ol>
<hr>
<h3 id="커스텀-커맨드-만들기">커스텀 커맨드 만들기</h3>
<p>/.claude/commands 경로에 md 파일로 생성한 파일은
커맨드로 사용할 수 있음
그룹화도 가능</p>
<pre><code>/.claude/commands/git/commit.md 작성된 md 파일을 명령어로 사용

/git:commit</code></pre><h4 id="동적-파라미터-커맨드">동적 파라미터 커맨드</h4>
<p>$ARGUMENTS 또는 $숫자 를 이용해서 커맨드 명령어 뒤에 입력한 내용이 동적으로 들어감</p>
<pre><code>// git/commit.md
---
description: git 커밋 생성
---
커밋 메시지: $ARGUMENTS
/git:commit 로그인 기능 완료

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

커밋메시지: $1, $2
/git:commit 로그인기능 장바구니기능</code></pre><blockquote>
<h3 id="추가-317"><strong>추가 (3/17):</strong></h3>
<p>skills 로 통합되어 슬래쉬 명령어 할 필요 없이 요청에 맞는 커맨드를 자체적으로 찾아 실행
커스텀 커맨드(레거시) =&gt; skills</p>
</blockquote>
<hr>
<h3 id="서브-에이전트">서브 에이전트</h3>
<p>프로젝트를 진행하면 새로운 기능, 코드 리뷰, 보안취약점, 테스트 등과 같이 여러 고려할 사항이 있는데
이걸 하나의 에이전트에서 하면 <strong>전문성도 떨어지고, 메인 컨텍스트의 맥락도 어지럽혀짐</strong>
그래서 서브 에이전트로 각각 처리하는 방식이 베스트</p>
<pre><code>/agents</code></pre><blockquote>
<h3 id="추가-317-1"><strong>추가 (3/17)</strong>:</h3>
<p>Claude Code 에 Skills 라는게 업데이트되면서 서브 에이전트는 Skills 로도 생성이 가능하며
컨텍스트 효율 측면에서도 우위
일회성이 아닌 이상 skills 로 구현하기</p>
</blockquote>
<h4 id="호출방법">호출방법</h4>
<ol>
<li>Claude 가 우리 요청을 분석해서 적절한 서브 에이전트 자동 호출</li>
<li>프롬프트 요청 시 해당 서브 에이전트 언급해서 요청</li>
</ol>
<h4 id="주요-이점">주요 이점</h4>
<p><strong>유연한 권한</strong>: 코드리뷰의 경우 READ 권한만 부여
<strong>재사용성</strong>: 한번 생성하면 다양한 프로젝트에서 사용하며 일관된 워크플로우 제공
<strong>전문 지식</strong>: 서브 에이전트만의 페르소나를 입력
<strong>컨텍스트 보존</strong>: 각 서브 에이전트는 자체 컨텍스트에서 작동하여 결과만 요약하여 메인 컨텍스트에 전달</p>
<hr>
<h3 id="훅">훅</h3>
<p>Claude Code 생명주기 다양한 지점에서 실행되는 명령어</p>
<ol>
<li>Bash 와 같은 명령어가 실행된 이후 작업될 내용</li>
<li>코드 수정 후 포맷팅 작업을 한다던가</li>
<li>Claude 에게 특정 작업을 요청 후 슬랙에서 완료했다는 알람을 받는다던가</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - 확장 가능한 앱 아키텍처]]></title>
            <link>https://velog.io/@jamee_/Flutter-%ED%99%95%EC%9E%A5-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%95%B1-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98</link>
            <guid>https://velog.io/@jamee_/Flutter-%ED%99%95%EC%9E%A5-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%95%B1-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98</guid>
            <pubDate>Wed, 14 Jan 2026 05:48:48 GMT</pubDate>
            <description><![CDATA[<h2 id="의존성-주입">의존성 주입</h2>
<h3 id="기본-패턴">기본 패턴</h3>
<p>의존성이 없는 객체는 생성 시 스스로 정해짐
무기에 대한 선택 불가</p>
<pre><code>class Adventurer {
  final Weapon = Gun();

  const Adventurer();

  void attack() {
    // 무기로 때립니다
  }
}</code></pre><p><strong>생성자 주입</strong></p>
<p><strong>가장 일반적으로 사용되며 불변성 강조에 사용</strong>
객체를 생성할 때 필요한 의존성을 명시적으로 전달하여
무기에 대한 선택이 가능 ( 단, 무기는 객체 생성 시 필수로 전달 )
코드의 가독성과 유지보수성을 높임</p>
<pre><code>final Weapon weapon = Gun();

final Adventurer adventurer = Adventurer(weapon);

adventurer.attack();</code></pre><p><strong>속성 주입</strong></p>
<p>무기가 없이도 어드벤처는 존재 가능하고 이후에 장착
<strong>일부 의존성이 필수가 아닌 선택적으로 주입되어야 할 때 유용</strong></p>
<pre><code>final Adventurer adventurer = Adventurer();

final Weapon weapon = Axe();

adventurer.equip(weapon);</code></pre><p><strong>메서드 주입</strong></p>
<p>행동을 할 때 무기를 주입
<strong>특정 메서드에서만 의존성이 필요한 경우에 사용</strong></p>
<pre><code>final Adventurer adventurer = Adventurer();

final Weapon weapon = Axe();

final Weapon weapon2 = Gun();

adventurer.attack(weapon2);</code></pre><p>정리해보자면,
생성자 주입: 기본적인 필수 의존성
속성 주입: 선택적인 의존성 ( 주입된 객체 필드에 저장 )
메서드 주입: 선택적이면서 특정 메서드 ( 매개변수에서 주입된 객체 1회성 사용 )
<em>※ 메서드 주입 과정에서, 주입된 객체를 필드에 저장하는 순간
그건 더 이상 메서드 주입이 아닌 속성 주입</em></p>
<blockquote>
<p><strong>의존성 주입 왜 필요해?</strong>
빼빼로 클래스에 대해 내부에서 초코 코팅을 선언하면, 빼빼로 클래스는 초코만 생성 가능. 즉, 딸기, 아몬드와 같은 맛을 동시에 생성할 수 없음</p>
</blockquote>
<h3 id="get_it-이론">get_it 이론</h3>
<p>서비스 로케이터 기반 간단하고 가벼운 의존성 주입 라이브러리</p>
<p><strong>서비스 로케이터</strong>
객체의 인스턴스를 관리하고 검색하는 디자인 패턴
의존성 주입이 적용되지 않은 상황에 객체간의 의존성 해결에 사용
<strong>장점</strong>
싱글톤 패턴이며, 모든 의존성이 중앙 집중식 관리.
<strong>단점</strong>
객체가 자신에게 필요한 의존성을 클래스 내부에서 직접 조회하는 방식이기 때문에,
외부에서 객체를 생성하거나 사용하는 입장에서는 해당 객체가 어떤 의존성에 의존하고 있는지 명확히 드러나지 않는다.</p>
<h3 id="get_it-실습">get_it 실습</h3>
<p><strong>enum</strong>
컴파일 타임에 고정된(const) 값들의 집합이며,
내부에는 가변 상태를 가질 수 없다.</p>
<pre><code>// 아래 코드에 int counter 넣는건 부적절 (가변이어서)
enum CounterMode {
  plus,
  minus;

  CounterMode next() {
    switch (this) {
      case CounterMode.plus:
        return CounterMode.minus;
      case CounterMode.minus:
        return CounterMode.plus;
    }
  }
}</code></pre><p>상태에 대한 이해를 돕기 위한 추가 코드</p>
<pre><code>enum TrafficLight { 
    red,
    yellow,
    green;

    TrafficLight next() {
      switch (this) {
          ...
      }
    }
  }
}

TrafficLight light = TrafficLight.red;

TrafficLight 는 총 3개의 상태를 가지고 있으며 현재 red 로 초기화
next() 안에 switch 의 this 는 red</code></pre><p>주로 enum 으로 상태를 선언하고 해당 enum 을 컨트롤하는 class 를 따로 구현해 비즈니스 로직 구현</p>
<h4 id="get_it-실습코드">get_it 실습코드</h4>
<pre><code>// main.dart
final GetIt locator = GetIt.instance;

void main() {
  locator.registerSingleton&lt;CounterModel&gt;(CounterModel());
  locator.registerSingleton&lt;CounterModeModel&gt;(CounterModeModel());
  runApp(const CounterApp());
}

// screen.dart
locator.get&lt;CounterModel&gt;().counter.toString();</code></pre><p>위 코드에서 알 수 있듯이 Dart 에서는 Type 값으로 해당하는 인스턴스를 불러올 수 있다
<strong>Dart 는 런타임 후에도 타입 정보가 유지되기 때문</strong>
( TS 에서는 컴파일 후 삭제되어서 Type 값으로 특정 값 리턴 불가 )</p>
<h3 id="injectable-이론">injectable 이론</h3>
<p>@Annotation 을 이용한 의존성 주입. 주로 get_it 과 함께 사용
<strong>injectable:</strong> 클래스나 함수에 어노테이션 추가하여 의존성을 자동으로 등록
<strong>get_it:</strong> 불러오기만 담당</p>
<pre><code>// dependency.dart

import &#39;package:get_it/get_it.dart&#39;;
import &#39;package:injectable/injectable.dart&#39;;
import &#39;dependency.config.dart&#39;;

final GetIt locator = GetIt.instance;

@injectableInit
void configureDependencies() =&gt; locator.init();
</code></pre><p>위와 같이 파일 생성 후 아래 명령어 실행
build_runner 는 모든 작성된 코드를 훑어보면서 @Annotation 이 발견되면 그에 맞는 .g.dart 혹은 .config.dart 파일을 자동으로 생성
injectable 라이브러리와 build_runner 사이에는 어떤 파일에 @injectableInit 이라고 써놓여져있으면 build_runner 실행 시 .config.dart 를 붙인 설정 파일을 만들어줌</p>
<pre><code>flutter pub run build_runner build --delete-conflicting-outputs</code></pre><p>아래 코드는 build_runner 실행으로 인해 생성된 코드</p>
<pre><code>// dependency.config.dart

import &#39;package:get_it/get_it.dart&#39; as _i1;
import &#39;package:injectable/injectable.dart&#39; as _i2;

extension GetItInjectableX on _i1.GetIt {
// initializes the registration of main-scope dependencies inside of GetIt
  _i1.GetIt init({
    String? environment,
    _i2.EnvironmentFilter? environmentFilter,
  }) {
    _i2.GetItHelper(
      this,
      environment,
      environmentFilter,
    );
    return this;
  }
}
</code></pre><p>dependency.dart 파일에 @singleton, @injectable 등으로 마킹해둔 클래스들이 dependency.config.dart 에 정의됨
이후 locator.init() 을 호출하면, dependency.config.dart 안의 로직이 실행되면서 Get_It 에 모든 객체가 담김</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/c00b1a0c-c674-4853-9015-3d0bb36e7a13/image.png" alt=""></p>
<p>순서 흐름</p>
<ol>
<li>dependency.dart 파일 생성</li>
<li>의존성 부분들에 @Annotation 선언</li>
<li>build_runner 실행</li>
<li>자동으로 @Annotation 선언된 부분들에 대한 코드 생성</li>
<li>init() 함수 호출하여 get_it 에 자동 주입<pre><code>ㅡㅡㅡㅡㅡㅡㅡㅡㅡ 변경 전 ㅡㅡㅡㅡㅡㅡㅡㅡㅡ
void main() {
locator.registerSingleton&lt;CounterModel&gt;(CounterModel());
locator.registerSingleton&lt;CounterModeModel&gt;(CounterModeModel());
runApp(const CounterApp());
}
</code></pre></li>
</ol>
<p>// model.dart
class CounterModel {
  ...
}
class CounterModeModel {
  ...
}</p>
<p>ㅡㅡㅡㅡㅡㅡㅡㅡㅡ 변경 후 ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
void main() {
  configureDependencies();
  runApp(const CounterApp());
}</p>
<p>// model.dart
@singleton
class CounterModel {
  ...
}
@singleton
class CounterModeModel {
  ...
}</p>
<pre><code>
### provider 이론
InheritedWidget 을 더 쉽게 사용할 수 있게 해주는 wrapper
위젯트리 전반에 걸쳐 데이터와 객체를 전달하고 관리해주는 상태 관리 라이브러리
</code></pre><p>// listen: 등록된 객체의 값이 변경될때마다 해당 위젯 재빌드 여부
Provider.of<Axe>(context, listen: true);</p>
<p>// Provider.of 에서 listen: false 와 동일
context.read<Axe>()</p>
<p>// Provider.of 에서 listen: true 와 동일
context.watch<Axe>()</p>
<pre><code>아래와 같은 방식으로 상위 위젯에 등록 후 하위 위젯들이 사용</code></pre><p>void main() {
  runApp(
    MultiProvider(
      providers: [
        Provider(create: (context) =&gt; CounterModel()),
        Provider(create: (context) =&gt; CounterModeModel()),
      ],
      child: const CounterApp(),
    ),
  );
}</p>
<pre><code>&gt;**의존성 주입 라이브러리가 필요한 이유**
화면을 만들때마다</code></pre><p>ViewModel(FetchUseCase(Repository(DataSource(LocalDB())))</p>
<pre><code>이런식으로 코드를 가져오는게 아닌</code></pre><p>context.read<ViewModel>()</p>
<pre><code>한줄이면 끝


&lt;hr/&gt;

## 아키텍처 학습
여러 기능과 상태를 어떻게 구성하고 관리할지
### MVC 이론
**Model**
데이터와 비즈니스 로직을 관리하는 부분</code></pre><p>class Model {
    int _count = 0;
    int get count =&gt; _count;</p>
<pre><code>void increment() {
      _count++;
}</code></pre><p>}</p>
<pre><code>**view**
사용자 인터페이스 부분
</code></pre><p>class _ViewState extends State<View>{
    ...</p>
<pre><code>@override
Widget build(BuildContext context) {
    return Scaffold(
        ...
    )
}</code></pre><p>}</p>
<pre><code>**controller**
Model 과 View 사이의 상호작용 관리</code></pre><p>class Controller {
    final Model _model;</p>
<pre><code>Controller(this._model);

void increment(){
    _model.increment();
}</code></pre><p>}</p>
<pre><code>위 코드를 통해 알게된 점은 class 에 변수 선언된 경우 get 으로 가져오는게 한 세트이며, 변수 선언할 때 어떤 값이 들어가야 할지 정의된 경우 enum 사용
controller 는 model 의 데이터와 로직을 호출하여 동작하므로, 두 계층 간의 결합이 발생한다

### MVVM 이론
주로 UI 가 많은 애플리케이션 개발에 적용되는 패턴
MV 까지는 MVC 와 동일

**viewModel**
view 와 model 의 중재자 역할
MVC 에서 model 은 변수, 비즈니스 로직을 담당
viewModel 은 변수, 비즈니스 로직 + 상태( 화면에 반영 ) 담당

#### MVC 와 MVVM 의 가장 큰 차이

**MVC 패턴:** View 가 중심이 되어 Controller 의 메서드를 호출하고, 그 결과를 setState() 를 통해 직접 UI에 반영. 즉, View 가 상태 변화와 화면 갱신을 직접 트리거해야 하므로 주로 StatefulWidget 구조를 가짐

**MVVM 패턴:** ValueNotifier 와 ValueListenableBuilder 가 데이터 바인딩 역할을 수행. ViewModel 이 변수, 상태, 비즈니스 로직을 전담하며, View 는 이를 관찰할 뿐 직접적인 화면 반영 로직(setState)을 갖지 않음. 결과적으로 View 는 로직으로부터 자유로운 StatelessWidget 중심의 선언적 구조

**ValueNotifier:** 데이터를 보관하고 변하는지 감시
**ValueListenableBuilder:** 해당하는 ValueNotifier 값이 변경되면 해당 부분만 다시 그림</code></pre><p>ValueListenableBuilder<int>(
   valueListenable: counterViewModel.counter,</p>
<p>   builder: (context, counter, child) =&gt; Text(
      counter.toString(),
      style: Theme.of(context).textTheme.headlineMedium,
   ),
),</p>
<pre><code>
### 클린 아키텍처

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - 로또앱 & 날씨앱]]></title>
            <link>https://velog.io/@jamee_/Flutter-%EB%A1%9C%EB%98%90%EC%95%B1-%EB%82%A0%EC%94%A8%EC%95%B1</link>
            <guid>https://velog.io/@jamee_/Flutter-%EB%A1%9C%EB%98%90%EC%95%B1-%EB%82%A0%EC%94%A8%EC%95%B1</guid>
            <pubDate>Mon, 12 Jan 2026 08:16:42 GMT</pubDate>
            <description><![CDATA[<h3 id="로또앱">로또앱</h3>
<p>svg 이용할때는 flutter_svg 라이브러리 사용</p>
<h4 id="listtile">ListTile</h4>
<p>리스트 한줄을 위해 미리 만들어진 위젯
leading, title, subtitle, trailing 이용하여 UI 구성
<img src="https://velog.velcdn.com/images/jamee_/post/b18c71d1-e0a2-4eb3-981c-cb8ed97d0ac1/image.png" alt=""></p>
<h4 id="ui-배치">UI 배치</h4>
<p>css에서 absolute, fixed 사용하듯이 flutter 에서는 Stack 과 Positioned 위젯 사용하여 배치 ( e.g. FAB )</p>
<p>FAB 에는 heroTag 라는게 존재하는데, 한 위젯내에 여러 FAB 가 선언되는 경우 동일한 default tag 값이 설정됨
페이지 이동 시 동일한 tag 값은 애니메이션 과정에서 오류 발생.
애니메이션 과정은 다음과 같음
A 라는 FAB 의 heroTag 가 b1 이라면, 이동할 페이지의 FAB 역시 heroTag 가 b1 일때, 자연스러운 FAB 레이아웃 이동 애니메이션이 동작</p>
<h4 id="외부-api-연동하기">외부 API 연동하기</h4>
<p>http 라이브러리 이용하여 통신
안드로이드에서 인터넷 접근 권한 허용을 위해 아래 파일 수정 </p>
<pre><code>android &gt; app &gt; src &gt; main &gt; AndroidManifest.xml</code></pre><p>IOS 에서는 기본으로 인터넷 접근 권한이 포함되어 있음
<strong>FutureBuilder</strong>
Future 상태를 감시하다가 결과에 맞는 UI 그려주는 위젯
<strong>실행 흐름</strong>
api 호출 &gt; 생성자에 결과 담기 &gt; initState 에서 해당 fetch 함수 결과를 변수에 초기화 &gt; FutureBuilder 에서 동적 UI 페인팅</p>
<p><strong>factory</strong>
객체를 만들기 전에 가공하거나 조건을 걸고 싶을 때 사용하는 특별한 생성자
<strong>late</strong>
변수 선언 먼저 하고 초기화는 나중에</p>
<hr/>

<h3 id="날씨앱">날씨앱</h3>
<h4 id="플랫폼별-퍼미션-설정">플랫폼별 퍼미션 설정</h4>
<p>모든 앱은 OS 안에서 격리된 공간인 샌드박스 안에서만 실행.
앱은 기본적으로 자신의 폴더 외에는 접근이 안되는데, 선언된 퍼미션을 통해 샌드박스를 뚫고 외부 데이터에 접근 가능</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/392dcf4c-515f-40d1-8f40-4fc4f559ec88/image.png" alt=""></p>
<p>Google Play나 Apple App Store는 앱이 선언한 권한과 실제 기능이 일치하는지 심사하고, 권한 선언 없이 민감한 데이터에 접근하려고 시도하는 앱은 아예 스토어 등록 자체가 거부됨</p>
<p><strong>android</strong></p>
<pre><code>// AndroidManifest.xml
// 위치정보

&lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot; /&gt;</code></pre><p><strong>ios</strong></p>
<pre><code>// Info.plist
// 위치정보

&lt;key&gt;NSLocationWhenInUseUsageDescription&lt;/key&gt;
&lt;string&gt;This app needs access to location when open.&lt;/string&gt;
&lt;key&gt;NSLocationAlwaysUsageDescription&lt;/key&gt;
&lt;string&gt;This app needs access to location when in the background.&lt;/string&gt;</code></pre><h4 id="위치-및-시간-변경">위치 및 시간 변경</h4>
<p><img src="https://velog.velcdn.com/images/jamee_/post/ec2147b1-0f43-40ac-9ff2-21e43545adf1/image.png" alt=""></p>
<h4 id="async-함수-내에서-context-사용법">async 함수 내에서 context 사용법?</h4>
<p>예를 들어 로그인 버튼 클릭하고 서버로부터 응답 받기 전에 사용자가 뒤로가기를 눌러 현재 위젯이 사라진 경우 해당 로그인 함수 안에 context 는 이미 dispose 된 context 를 사용하려하여 에러 발생
이 경우 State 클래스에 정의된 멤버 변수인 mounted 로 해결</p>
<pre><code>Future&lt;void&gt; login(BuildContext context) async {
    await Future.delayed(const Duration(seconds: 2)); // 서버 요청 가정

    // 해결 코드
     if (!mounted) return; 

    // ⚠️ 파란 밑줄
    Navigator.of(context).push(
      MaterialPageRoute(builder: (_) =&gt; const HomePage()),
    );
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () =&gt; login(context),
      child: const Text(&#39;로그인&#39;),
    );
  }</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - Stateless 위젯 vs Stateful 위젯]]></title>
            <link>https://velog.io/@jamee_/Flutter-Stateless-%EC%9C%84%EC%A0%AF-vs-Stateful-%EC%9C%84%EC%A0%AF</link>
            <guid>https://velog.io/@jamee_/Flutter-Stateless-%EC%9C%84%EC%A0%AF-vs-Stateful-%EC%9C%84%EC%A0%AF</guid>
            <pubDate>Tue, 06 Jan 2026 09:17:16 GMT</pubDate>
            <description><![CDATA[<h3 id="객체지향-언어">객체지향 언어</h3>
<p>super(): 부모 클래스의 생성자 호출</p>
<pre><code>class DialPhone {
  int? year;

  DialPhone() {
    print(&quot;다이얼을 돌려서 전화를 겁니다&quot;);
  }

  void whenCame() {
    print(&quot;188년에 발명되었습니다 $year&quot;);
  }
}

class ButtonPhone extends DialPhone {
  int? to;

  ButtonPhone() {
    print(&quot;이 전화기는 버튼을 눌러서 전화를 겁니다&quot;);
  }

  @override
  void whenCame() {
    print(&quot;1963년 발명 $year $to&quot;);
  }
}

class SmartPhone extends ButtonPhone {
  String maker;
  String model;

  SmartPhone({required this.maker, required this.model, int? year, int? to}) : super() {
    super.year = year;
    super.to = to;
  }
}

void main() {
  SmartPhone sp = new SmartPhone(maker:&quot;애플&quot;, model: &quot;아이폰20&quot;, year:30,to:50);
  sp.whenCame();
}

// print 결과 ▼
// 다이얼을 돌려서 전화를 겁니다
// 이 전화기는 버튼을 눌러서 전화를 겁니다
// 1963년 발명 30 50</code></pre><h3 id="stateless-위젯의-특징">Stateless 위젯의 특징</h3>
<p>일회성 UI 요소로 사용되며 상태가 바뀌어야할 때 기존 위젯을 제거하고 새롭게 위젯을 생성</p>
<p>내부적으로 abstract 형식으로 Stateless 가 정의되어있음
그래서 build 메소드의 경우 자식 클래스에서 재정의 해주어야함</p>
<p><strong>추상클래스 abstract</strong>
body{} 가 없는 메소드의 경우 반드시 자식 요소에서 재정의 해주어야함</p>
<pre><code>abstract class Animal{
  void makeSound();
}

class Dog extends Animal{

  @override
  void makeSound(){
    print(&quot;왈왈&quot;);
  }
}
void main(){
  Dog dog = Dog();
  dog.makeSound();
}</code></pre><h3 id="element-트리란-무엇일까요">Element 트리란 무엇일까요</h3>
<p><strong>Widget tree</strong>
이렇게 그려줘라고 지시하는 설계도 역할</p>
<p><strong>Element tree</strong>
Widget tree와 렌더 트리 중간에서 서로를 연결시키면서 앱의 효율적인 UI 업데이트를 관리
Widget tree가 생성될때마다 이에 근거해서 flutter 프레임워크가 즉시 함께 생성하고 컨트롤해줌 따라서 Widget tree의 모든 위젯은 1대1 맞대응 형식으로 Element tree에 링크되어 있음
연결된 Widget 의 모든 정보를 가지고 있음 ( 위치, 타입, 크기, 배경색 등..)
그래서 어떤 Widget 의 상태가 변경되어서 빌드 메소드가 호출되고 Widget tree 가 리빌드 된다면 이와 연결되어 있는 Element tree 도 함께 리빌드 되는것이 아닌 새롭게 리빌드된 Widget 의 위치나 타입 속성등이 이전 Widget과 일치하면 이전 Widget과의 연결을 끊고 새롭게 생성된 Widget 인스턴스로 링크만 업데이트해줌 렌더 트리가 이 부분을 다시 앱 화면에 그려주게 됨 그 외 상태 변화가 없는 Widget들은 그대로 유지 </p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/7c2130a4-601c-4579-8478-a68e74ef4eec/image.png" alt=""></p>
<p><strong>Render tree</strong>
UI 를 실제로 화면에 표시하기 위해서 각 위젯의 위치와 크기를 계산하고 그리는 역할</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/cad9d5ec-c5e0-49b0-af62-d9a7fc301c2b/image.png" alt=""></p>
<p>예를 들어 앱의 특정 텍스트 위젯에 전달되는 문자열이 setState 를 통해 바뀌었다면 기존 텍스트 위젯을 그대로 수정하는것이 아니라 빌드 메소드를 다시 호출하여 완전히 새로운 텍스트 위젯 인스턴스를 생성</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/a6e398a8-395f-4a04-896a-04fdddde96a8/image.png" alt=""></p>
<h4 id="간단-요약">간단 요약</h4>
<p>Widget이 바뀌면 무조건 새로운 Widget 만들어지고,
Element는 조건이 맞으면 재사용되고, (매핑된 새 Widget과 기존 Widget의 runtimeType과 key가 동일할 때, 즉 직접적으로 key 를 선언하여 명시하거나, Text 위젯에서 Icon 위젯처럼 변경하지 않는 이상 리빌드 되지 않음 )
RenderObject는 실제 렌더링에 영향이 있을 때만 layout/paint 된다.
Widget tree 내에서 변경된 부분은 새롭게 Widget이 만들어지고 Element tree 는 기존 Widget 은 버리고 새롭게 만들어진 Widget 과 링크해주고, 그외 변경되지 않은 Widget들은 재사용</p>
<h3 id="stateful-위젯의-구성과-특징">Stateful 위젯의 구성과 특징</h3>
<p>Stateless 위젯과 거의 동일하게 동작하는데, 가장 큰 차이는 Stateful 위젯 생성 시 State Object 도 함께 생성됨
Stateful 위젯 생성하는데 비용이 많이 드는 이유가 State Object 때문
이러한 State Object 를 버리고 새로 만들기보단 그대로 유지한 채 최대한 재사용하며 상태의 변화에 따라 새롭게 생성된 Stateful 위젯으로 링크만 업데이트되는 전략
<img src="https://velog.velcdn.com/images/jamee_/post/f291f168-ef7d-4716-9a65-e2788f906fb7/image.png" alt=""></p>
<p>State Object 는 위젯이 Element Tree에 처음 연결될 때 한 번 생성되고,
Element가 제거(dispose)될 때 함께 메모리에서 해제된다.
쉽게 설명하자면 해당 위젯이 화면에서 완전히 제거될 때 메모리에서 정리되어 함께 사라진다.</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/08664649-364e-4f5c-9f82-ad3e0fb90c6e/image.png" alt=""></p>
<h4 id="왜-모든-widget-들stateful-widget-포함이-불변성을-가져야-할까요">왜 모든 Widget 들(Stateful Widget 포함)이 불변성을 가져야 할까요?</h4>
<p>위젯은 변하면 무조건 재생성함. 이 자체가 불변성
Stateful 위젯 역시 불변성으로 인해 재생성 해야하니 비용이 많이드는 State Object 은 Stateful 위젯에 포함하지 않고 Element tree 에 State Object 가 존재하는것</p>
<ol>
<li><p>예측 가능한 UI 구조가 필요.
Widget 이 절대 변하지 않으니 데이터 전달로 인해서 발생하는 상태의 변화는 오직 Build 메소드를 통해서만 이루어짐</p>
</li>
<li><p>Element 트리의 최적화
기존 Element 를 유지한 채 링크를 업데이트하는 방식으로 Widget 만 새로 연결함으로 그 성능을 효율적으로 유지</p>
</li>
<li><p>위젯 비교 및 정보 확인의 편리성
한번 생성된 Widget 은 변하지 않으므로, 기존 Widget 과 새 Widget 을 비교해서 바뀐 부분만 다시 그릴 수 있음</p>
</li>
</ol>
<h3 id="final-과-const-이해하기">final 과 const 이해하기</h3>
<p><strong>const</strong>
컴파일 타임때 변수의 값을 결정하며, 이후 해당 값은 절대 변하지 않음
동일한 const 생성자는 컴파일 타임에 단 하나의 인스턴스만 생성되어 같은 객체 참조를 재사용</p>
<pre><code>const Text(&#39;Hello&#39;) === const Text(&#39;Hello&#39;) // true</code></pre><p>immutable 한 변수나 위젯에 사용하여 재사용 가능
<strong>현재는 flutter 에서 immutable 한 요소는 자동으로 컴파일 시점에 const 객체처럼 취급</strong></p>
<p><strong>final</strong>
런타임때 변수의 값을 결정하며, 이후 해당 값은 절대 변하지 않음
Widget 은 런타임에 사용되는 불변 객체로, 생성자는 필드를 final 로 초기화하는것이 적절
Widget 앞에 const 를 붙이면 런타임마다 새로 만들지 않고, 동일 인스턴스를 재사용하라는 의미</p>
<h3 id="setstate-메소드와-제네릭">setState 메소드와 제네릭</h3>
<p>setState 로 인해 값이 변경되면, 해당 값을 사용하는 Widget 에 Dirty mark 를 해주어서 해당 부분만 리빌드 시 다시 생성</p>
<p><strong>colorScheme</strong>
앱 전반에서 사용할 수 있도록 정리해둔 색상 팔레트
정밀한 색상 통제가 필요할 때 사용</p>
<pre><code>// 아래 코드는 colorSchemeSeed 와 비슷하지만,
// 전체적인 조화에 맞춰 생성된 컬러에 대해
// 수정하거나 정교하게 통제 가능
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple) </code></pre><p><strong>colorSchemeSeed</strong>
하나의 seed 색상을 중심으로 자동으로 flutter 가 전체적인 조화에 맞춘 컬러를 계산해줌
디자인 지식이 부족해도 손쉽게 색상 테마를 구성할 수 있도록 해줌</p>
<h3 id="stateful-위젯-인스턴스의-리빌드">Stateful 위젯 인스턴스의 리빌드</h3>
<p>.of(context) 는 현재 BuildContext 에서 가장 가까운 일치하는 위젯을 반환</p>
<p><strong>State Object 개념 확실히 이해하기</strong></p>
<pre><code>// Widget
class MyWidget extends StatefulWidget{
    ...
}

// State Object (Widget 아님)
class _MyWidgetState extends State&lt;MyWidget&gt;{
    ...
}</code></pre><h3 id="widget-속성-이해하기">Widget 속성 이해하기</h3>
<p><strong>initState</strong>
State Object 가 처음 생성될 때 딱 한번 실행되는 상태 초기화 시켜주는 메소드</p>
<pre><code>@override
void initState() {
    color = widget.passedColor;
    super.initState();
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[프론트엔드 엔지니어 관점의 인프라스트럭쳐]]]></title>
            <link>https://velog.io/@jamee_/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%EA%B4%80%EC%A0%90%EC%9D%98-%EC%9D%B8%ED%94%84%EB%9D%BC%EC%8A%A4%ED%8A%B8%EB%9F%AD%EC%B3%90</link>
            <guid>https://velog.io/@jamee_/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%EA%B4%80%EC%A0%90%EC%9D%98-%EC%9D%B8%ED%94%84%EB%9D%BC%EC%8A%A4%ED%8A%B8%EB%9F%AD%EC%B3%90</guid>
            <pubDate>Fri, 26 Dec 2025 08:58:01 GMT</pubDate>
            <description><![CDATA[<h3 id="개발-외적인-부분에서-발생하는-환경-구성-방법에-대한-안내_이해">개발 외적인 부분에서 발생하는 환경 구성 방법에 대한 안내_이해</h3>
<p>웹서버: 정적인 리소스를 제공하는 서버. 페이지(CSR, SSG), 이미지...
웹어플리케이션서버: 동적인 리소스를 생성해 제공하는 서버. 페이지(SSR, ISR), DB, API...</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/91a87df7-e579-4986-a5c7-2c29e7502b99/image.png" alt=""></p>
<h2 id="aws-서비스-안내">AWS 서비스 안내</h2>
<h3 id="amplify">Amplify</h3>
<p>기본 제공 CI/CD 워크플로를 통해 웹 애플리케이션의 글로벌 배포와 호스팅을 지원하는 완전관리형 서비스
Elastic Beanstalk처럼 개발과 배포를 쉽게 만들어준다</p>
<ol>
<li><code>npm i -g @aws-amplify/cli</code>
Amplify 를 사용하기위해 글로벌 설치</li>
<li><code>amplify confiure</code>
aws console 에 로그인 후 region 선택</li>
<li>IAM 유저 생성
aws 상호작용 하기위한 유저나 어플리케이션에대한 권한 부여.
생성된 액세스 키 및 시크릿 액세스 키를 입력하면 세팅 작업 끝</li>
<li>간단한 Next 애플리케이션 배포하기위한 <code>npm init next-app</code></li>
<li>git repository 생성</li>
<li>Amplify hosting 에 추가하기위한 init
생성한 Next 애플리케이션을 <code>amplify init</code></li>
<li>add 커맨드를 이용하여 Amplify hosting 추가
<code>amplify add hosting</code>
이 과정에서 CD(자동배포) 선택 가능</li>
<li>aws 페이지에서 Amplify Hosting 시작하기 페이지가 나옴
<img src="https://velog.velcdn.com/images/jamee_/post/49baaa15-2ae3-4c3d-9559-3382d20f4a64/image.png" alt=""></li>
<li>github 체크한 경우 repository 및 브런치 선택
<img src="https://velog.velcdn.com/images/jamee_/post/539f8e97-b36a-4438-be2c-e82cf1de10aa/image.png" alt=""></li>
<li>빌드 설정
<img src="https://velog.velcdn.com/images/jamee_/post/792123e3-750c-4d7a-8a8e-99146790b7cf/image.png" alt=""></li>
<li>검토
<img src="https://velog.velcdn.com/images/jamee_/post/3e18a7ef-075c-440c-a290-e5e4553279dc/image.png" alt=""></li>
<li>배포 완료
<img src="https://velog.velcdn.com/images/jamee_/post/c2e096b4-6649-4243-83ca-f8c1a29e8b5f/image.png" alt=""></li>
<li>자동 배포
<img src="https://velog.velcdn.com/images/jamee_/post/1d00a0ba-7c51-437e-acef-6da7f836a6ca/image.png" alt=""></li>
</ol>
<hr>
<h3 id="ec2">EC2</h3>
<p>Elastic Compute Cloud(EC2)
비용/성능/용량 측면에서 유연한 클라우드 컴퓨팅 서비스
실제 서버를 구축하는 형식이 아닌 AMI 기반으로 형성된 가상 서버를 임대해서 사용
사용한만큼 지불</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/26ff0314-9bce-4d50-87a1-282a28fa27ec/image.png" alt=""></p>
<p>EC2 인스턴스(가상 서버)는 운영체제와 기본 소프트웨어가 포함된 AMI(Amazon Machine Image)를 기반으로 생성된다.
vCPU/vRAM/Storage/Network 등 다양한 스펙의 인스턴스 타입이 존재</p>
<p><em><strong>머신 이미지:</strong> 사전 구성 템플릿</em></p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/9a031a9e-0098-4b37-8764-e3e8c85cb2ec/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/e38075c7-4332-4d38-a3d7-d3d809d5f203/image.png" alt=""></p>
<p><strong>Auto scaling</strong>
인스턴스 그룹화하여 자동으로 확장/축소 가능</p>
<p><strong>ssh 프로토콜</strong>
다른 컴퓨터에 안전하게 원격 접속하기 위한 범용 프로토콜</p>
<pre><code>ssh -i &quot;nextajs_ec2_keypair.pem&quot; ec2-user@ec2-13....ap-northeast-2.compute.amazonaws.com</code></pre><p>위와 같은 방법으로 가상 서버 접속 후 git repository 연결 및 프로젝트 실행</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/7821f174-c18c-4e08-9b90-76240142db76/image.png" alt=""></p>
<p>브라우저는 포트 번호를 명시하지 않으면 기본적으로 80번 포트를 사용
따라서 자동 할당된 공인 IP 주소를 브라우저에 입력하면 13.125.26.212:80 으로 요청이 전송된다.
이때 가상 서버에서 Nginx가 80번 포트를 리스닝하도록 설정되어 있다면, 운영체제는 해당 요청을 80번 포트에 바인딩된 Nginx 프로세스로 전달한다</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/5abbc27e-c751-405a-b8d0-901a6686724c/image.png" alt=""></p>
<p>위 정리된 이미지의 실행 순서를 정리하자면</p>
<ol>
<li>13.125.26.212 접근</li>
<li>80번 포트(Nginx)에 접근</li>
<li>Nginx 에서 3000번 포트로 전달</li>
<li>가상 서버에 실행된 Next 서버에 접근</li>
</ol>
<p>위 방식은 현재 포어그라운드로 터미널 종료되거나 ssh 끊기면 서버가 종료
<strong>그래서 pm2 라는 프로세스 매니저를 사용하여 SSH 연결과 무관하게 Next 서버를 독립적으로 실행하고, 문제 발생 시 자동 재시작되도록 구현</strong></p>
<hr>
<h3 id="s3">S3</h3>
<p>Simple Storage Service(S3)
유연하고 확장성 있는 클라우드 스토리지 서비스 중 하나
객체 저장 뿐만 아니라 웹 어플리케이션 호스팅 가능</p>
<p>cli 를 통해서 S3에 배포 시도 시, AWS 의 권한 필요하여 IAM 추가</p>
<p><em><strong>IAM:</strong> 위에서도 설명했지만, AWS와 상호작용하기 위한 profile, 허용된 권한을 확인한 후 해당 profile에 맞는 Access Key와 Secret Key를 설정</em> 
<img src="https://velog.velcdn.com/images/jamee_/post/9450d779-7902-418b-baa5-ea6474b2ed2b/image.png" alt=""></p>
<p>버킷 정책을 통해 특정 접근에 대해서만 권한(리소스 접근)을 허락하게 설정 가능</p>
<hr>
<h3 id="cloudfront">CloudFront</h3>
<p>CDN 서비스
서버와 사용자 사이의 물리적 거리를 줄여 로딩 속도 향상
가장 가까운 캐시 서버가 컨텐츠 전송
<img src="https://velog.velcdn.com/images/jamee_/post/14043b62-0620-4ad0-8efe-642bc1a39777/image.png" alt=""></p>
<p>위 이미지를 기준으로 설명하면, </p>
<ol>
<li>사용자가 데이터를 요청</li>
<li>CloudFront 에서 사용자가 요청한 데이터가 있는지 확인</li>
<li>없으면 CloudFront 는 EC2 에서 데이터를 요청하고, 받은 값을 캐시</li>
<li>다른 사용자가 동일한 데이터를 요청 시 캐시된 값을 반환</li>
</ol>
<p>정적/동적 컨텐츠 분산 제공 (트래픽이 몰리지 않도록)
지리적 제한 설정 (특정 국가에서 보지 못하도록)
다른 서비스와 연계 (Lambda@Edge 를 이용해 요청을 가로채서 캐싱, 헤더 변경 가능)</p>
<p>S3 버킷을 CloudFront 로 처리하고 배포하기</p>
<ol>
<li>CloudFront 배포 생성</li>
<li>기존 생성된 S3 버킷을 원본 도메인으로 설정</li>
<li>S3 버킷 정책 업데이트</li>
<li>배포된 CloudFront 도메인 접근 시 index.html 접근</li>
<li>오류 페이지 설정에서 사용자 정의 오류 응답 생성하여 404 페이지 대응
<img src="https://velog.velcdn.com/images/jamee_/post/d018e447-ea4e-4e7d-bcc5-ad4c2d2da425/image.png" alt=""></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[최적화와 배포]]]></title>
            <link>https://velog.io/@jamee_/%EC%B5%9C%EC%A0%81%ED%99%94%EC%99%80-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@jamee_/%EC%B5%9C%EC%A0%81%ED%99%94%EC%99%80-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Fri, 28 Nov 2025 03:48:54 GMT</pubDate>
            <description><![CDATA[<h4 id="아래는-이미지-최적화와-관련된-내용들">아래는 이미지 최적화와 관련된 내용들</h4>
<p>webp, AVIF 등의 차세대 형식으로 변환
디바이스 사이즈에 맞는 이미지 불러오기
레이지 로딩 적용
블러 이미지 활용
기타등등..</p>
<p>이러한 작업을 Nextjs 에서는 Image 컴포넌트로 쉽게 구현 가능</p>
<p>외부 서버에 보관된 이미지 사용하는 방식이라면 보안때문에 차단된 상태</p>
<pre><code>// next.config.js
const nextConfig = {
    ...
    images: {
        domains: [&quot;~.net&quot;]
    }
}</code></pre><h3 id="동적으로-메타데이터-설정하기">동적으로 메타데이터 설정하기</h3>
<pre><code>// 정적
export const metadata : Metadata = {
    ...
}

or

// 동적
// Page 에서 props 로 전달받은 값을 그대로 사용 가능
export async function generateMetadata({searchParams}): Promise&lt;Metadata&gt; {
    const { q } = await searchParams;

    // API 호출도 가능
    // const response = await fetch(...)

    return {
        title: `${q} : 검색`,
        ...
    }
}</code></pre><p>위 방식대로 작성하면 해당 페이지에 설정한 메타데이터가 index.html 에 설정됨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[고급 라우팅 패턴]]]></title>
            <link>https://velog.io/@jamee_/%EA%B3%A0%EA%B8%89-%EB%9D%BC%EC%9A%B0%ED%8C%85-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@jamee_/%EA%B3%A0%EA%B8%89-%EB%9D%BC%EC%9A%B0%ED%8C%85-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Wed, 26 Nov 2025 05:38:36 GMT</pubDate>
            <description><![CDATA[<h3 id="parallel-route">Parallel Route</h3>
<h4 id="병렬-라우트">병렬 라우트</h4>
<p>하나의 화면안에 여러개의 페이지를 병렬로 함께 렌더링</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/a2080932-36d3-4ee1-bc90-132da59f7e05/image.png" alt=""></p>
<pre><code>폴더구조
parallel
    - @feed
        - setting
            page.tsx
        - page.tsx
    - @sidebar
        - page.tsx
    - layout.tsx
    - page.tsx</code></pre><p>@sidebar 와 같이 @ 붙은 형식을 Slot 이라 부름
병렬로 렌더링 될 페이지 컴포넌트를 보관하는 폴더
여기서 선언된 page.tsx 는 부모 layout.tsx 의 props 로 전달됨</p>
<pre><code>export default function Layout({
    children,
      sidebar
}:{
    children: ReactNode;
    sidebar: ReactNode;
}) {
    return (
        &lt;div&gt;
            {children}
              {sidebar}
        &lt;/div&gt;
    )
}</code></pre><pre><code>// /parallel 경로 접근 시
{@feed 의 page.tsx}
{@sidebar 의 page.tsx}
{parallel 의 page.tsx}

// /parallel/setting 경로 접근 시
{@feed/setting 의 page.tsx}
{@sidebar/setting 의 page.tsx} // 없으면 @sidebar 에서 이전에 렌더링한 page.tsx
{parallel/setting 의 page.tsx} // 없으면 parallel 에서 이전에 렌더링한 page.tsx

그래서 &lt;Link/&gt; 태그가 아닌 직접 접속 시 이전에 렌더링한 page 가 없으므로 404 PageNotFound
이 경우 404 대신 각 Slot 폴더에 default.tsx 파일 생성해주면 이게 보여짐
</code></pre><h3 id="intercepting-route">Intercepting Route</h3>
<h4 id="인터셉팅-라우트">인터셉팅 라우트</h4>
<p>특정 경로 접속 시 원래 렌더링되어야 할 페이지가 아닌 우리가 원하는 페이지를 대신 렌더링</p>
<p><strong>Link, Router.push 와 같은 메소드로 페이지 이동 시에만 적용됨</strong></p>
<pre><code>app
    - (.)book/[id]
    - book/[id]</code></pre><p>(경로) 을 붙임으로써 가로채라
여기서 (.)book/[id] 는 동일한 경로에 있음을 의미</p>
<p>만약 Modal 형식을 위해 인터셉팅 라우팅을 적용한다면,
해당 (.)book/[id] 의 page.tsx 가 백그라운드 배경으로 띄어짐
원래 탐색하고 있던 페이지가 병렬로 함께 백그라운드 배경으로 설정되어야함
(병렬 + 인터셉팅)</p>
<pre><code>app
    - @modal
        - book/[id]
            - page.tsx
        - default.tsx
    - book/[id]
        - page.tsx
    - layout.tsx

// layout.tsx
export default function Layout({children, modal}){
    return (
        &lt;&gt;
            // book/1 경로 접근 시
            {children} // book/1 은 intercepting route
                       // children 도 결국엔 병렬라우팅처럼 동작하므로 이전 페이지인 page.tsx 나타남
            {modal}    // @modal/book/1
        &lt;/&gt;
    )
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[서버 액션]]]></title>
            <link>https://velog.io/@jamee_/%EC%84%9C%EB%B2%84-%EC%95%A1%EC%85%98</link>
            <guid>https://velog.io/@jamee_/%EC%84%9C%EB%B2%84-%EC%95%A1%EC%85%98</guid>
            <pubDate>Mon, 24 Nov 2025 03:01:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="서버-액션이란">서버 액션이란?</h3>
<p>서버에서만 실행되는 비동기 함수를 브라우저가 직접 호출
조금 더 간결하고 편리하게 서버측 동작을 정의하는데 적합</p>
</blockquote>
<pre><code>async function createReviewAction(){
    &quot;use Server&quot;
    ...
}

return (
    &lt;form action={createReviewAction}&gt;
        ...
    &lt;/form&gt;
)</code></pre><p>브라우저에서 폼을 제출하면 서버 액션을 요청(요청 주소: 현재 브라우저 주소)하는 request 가 서버로 날라감
<strong>서버 액션은 컴파일 결과 특정한 해시값을 갖는 API 로써 생성</strong>
request header 에 Next-Action 에 현재 요청하고 싶은 서버 액션 해시값을 함께 요청
<img src="https://velog.velcdn.com/images/jamee_/post/98ea53fd-47a8-46ea-a000-a3cd6116b3e5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/50179591-6b80-457c-976d-62724007db1a/image.png" alt=""></p>
<blockquote>
<h4 id="그럼-서버-액션의-응답은-어떻게-받아야하나요">그럼 서버 액션의 응답은 어떻게 받아야하나요?</h4>
<p><strong>useActionState</strong> 는 form 태그에 대한 핸들링 지원</p>
</blockquote>
<pre><code>const [state, formAction, isPending] = useActionState(핸들링하려는 액션함수, 폼 상태 초기값);

return(
    &lt;form action={formAction}&gt;
        ...
    &lt;/form&gt;
)</code></pre><h3 id="다양한-재검증-방식">다양한 재검증 방식</h3>
<p>next-cache 에서 제공하는 <strong>revalidatePath</strong> 를 이용하면 해당 경로를 재검증하게됨
<strong>서버측에서만 호출 가능하여 서버 페이지나 서버 액션에서만 호출 가능</strong>
<strong>풀 라우트 캐시와 데이터 캐시가 PURGE(삭제)</strong> 되고, 새로고침 후 접근하면 Dynamic 하게 그려짐</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/f7bd1c5c-5c7e-4690-985d-29f537817dcf/image.png" alt=""></p>
<h4 id="revalidatepath-에서-제공하는-여러-기법">revalidatePath 에서 제공하는 여러 기법</h4>
<ol>
<li>특정 경로의 모든 동적 페이지를 재검증<pre><code>revalidatePath(&quot;/book/[id]&quot;,&quot;page&quot;);</code></pre></li>
<li>특정 레이아웃을 갖는 모든 페이지 재검증<pre><code>revalidatePath(&quot;/(with-searchbar)&quot;,&quot;layout&quot;);</code></pre></li>
<li>모든 데이터 재검증<pre><code>revalidatePath(&quot;/&quot;,&quot;layout&quot;);</code></pre></li>
<li>태그 값으로 데이터 재검증<pre><code>await fetch(
...,
{next: {tags: [`review-{bookId}`]}}
)
</code></pre></li>
</ol>
<p>revalidatePath(<code>review-{bookId}</code>);
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[스트리밍과 에러핸들링]]]></title>
            <link>https://velog.io/@jamee_/%EC%8A%A4%ED%8A%B8%EB%A6%AC%EB%B0%8D%EA%B3%BC-%EC%97%90%EB%9F%AC%ED%95%B8%EB%93%A4%EB%A7%81</link>
            <guid>https://velog.io/@jamee_/%EC%8A%A4%ED%8A%B8%EB%A6%AC%EB%B0%8D%EA%B3%BC-%EC%97%90%EB%9F%AC%ED%95%B8%EB%93%A4%EB%A7%81</guid>
            <pubDate>Thu, 20 Nov 2025 05:07:28 GMT</pubDate>
            <description><![CDATA[<h3 id="스트리밍이란">스트리밍이란</h3>
<p>Streaming 이란 강물, 하천을 의미하는 단어로 큰 데이터를 잘게 쪼개어 흐르듯이 보내주는걸 뜻함
영상뿐만 아닌 웹 서비스에서도 사용할 수 있게 Next.js 에서 스트리밍 기능을 제공</p>
<p>이전에는 페이지를 불러오는 과정에서 데이터 통신과 같은 시간이 다소 걸리는 작업이 있는경우
전체적으로 화면을 전달받는데 시간이 걸림</p>
<p>비동기 작업이 없는 즉시 화면에 렌더링 가능한 컴포넌트만 브라우저에게 보내준 다음에
비교적 늦게 작업이 완료되는 컴포넌트는 작업이 완료된 후에 보내줌</p>
<p><strong>Dynamic 페이지에 주로 사용
async Page 에서만 지원
서로 다른 경로간의 탐색 시 최초 로드에 최적화된 방식</strong></p>
<pre><code>아래 형식처럼 같은 위치에 loading.tsx 를 만들어주면 끝
동일한 페이지내에서 이동(쿼리 스트링) 시 동작x
    - 이유는 클라이언트 라우트 캐시에 이미 RSC Payload 가 최초 받아올 때 저장되어 서버로 부터 받아오는게 아니므로

search
    - loading.tsx
    - page.tsx</code></pre><p><strong>※주의:</strong> 여기서 loading.tsx 는 마치 레이아웃처럼 해당 경로 아래 있는 모든 비동기 페이지를 스트리밍 되도록 설정</p>
<pre><code>search
    - /setting
        - page.tsx // /setting 경로에 접근 시 페이지내에 비동기 동작이 완료될 때 까지 상위 loading.tsx 보여짐
    - loading.tsx
    - page.tsx</code></pre><blockquote>
<h4 id="좀-더-세밀한-스트리밍을-위한-suspense">좀 더 세밀한 스트리밍을 위한 Suspense</h4>
<p>loading.tsx 이외에 컴포넌트 단위로 비동기 작업 하고있는 컴포넌트에 대해 스트리밍을 적용시키고 싶다면 Suspense 사용</p>
</blockquote>
<p>💡 Suspense 는 로드될때까지 로딩을 보여주는 컴포넌트로만 알고 있었는데, 스트리밍이라는 최적화 기법도 적용되는거였구나</p>
<h3 id="에러-핸들링">에러 핸들링</h3>
<p>try catch 문으로 모든 예외처리하기 번거로우니 error.tsx 사용
error.tsx 선언된 위치의 layout.tsx 까지만 그림</p>
<pre><code>search
    - /setting
        - page.tsx // /setting 경로에 접근하여 에러 발생 시 상위 error.tsx 반환
    - error.tsx
    - page.tsx</code></pre><p><strong>error.tsx 에 &quot;use client&quot; 사용하는 이유</strong>
사전렌더링 시 서버에서 그려질때 적용되는거 이외에 하이드레이션 과정에서도 에러 발생 시 error.tsx 파일이 필요하므로</p>
<pre><code>export default function Error({error, reset}:{error: Error, reset: ()=&gt;void}){

// reset 은 서버측에서 실행한 서버컴포넌트는 다시 실행하지 않음
// 클라이언트에서 서버로부터 이미 받았었던 정보로 다시 렌더링함

// 그래서 router.refresh 로 백그라운드에서 서버컴포넌트 다시 받아와 적용하고
// reset 으로 에러바운더리 상태 초기화

    onClick={()=&gt;{
        startTransition(()=&gt;{
            router.refresh();
            reset();
        })
    }

}</code></pre><blockquote>
<h4 id="soft-navigation-이란">Soft Navigation 이란?</h4>
<p>router.refresh 는 소프트 네비게이션이다
브라우저는 현재 페이지를 유지한 채로, 변경이 필요한 부분만 동적으로 업데이트
(스크롤 위치, 입력 폼 내용 유지 가능)</p>
</blockquote>
<blockquote>
<h4 id="starttransition-이란">startTransition 이란?</h4>
<p>콜백함수 안에 정의된 UI 변경 작업을 일괄 적용시켜줌
즉, router.refresh 와 reset 이 한몸처럼 적용되도록</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데이터 페칭]]]></title>
            <link>https://velog.io/@jamee_/%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8E%98%EC%B9%AD</link>
            <guid>https://velog.io/@jamee_/%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%8E%98%EC%B9%AD</guid>
            <pubDate>Tue, 18 Nov 2025 04:05:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="app-router-에서의-데이터-페칭">App Router 에서의 데이터 페칭</h3>
<p><strong>필요한 컴포넌트에서 필요한 데이터를 불러오자</strong>
중복 요청을 보내도 <strong>Request Memoization</strong> 으로 인해 한 번의 요청만 간다</p>
</blockquote>
<p>fetch 를 통한 API 요청 시 아래 이미지와 같이 동작한다
<img src="https://velog.velcdn.com/images/jamee_/post/2978b574-a33d-40bb-82db-66d056ebde2e/image.png" alt=""></p>
<h3 id="api-콘솔-찍는-방법">API 콘솔 찍는 방법</h3>
<pre><code>// config.js

const nextConfig = {
    logging:{
        fetches:{
            fullUrl:true,
        }
    }
}</code></pre><p><img src="https://velog.velcdn.com/images/jamee_/post/79c56d56-4cf9-4bfb-abd9-34a5f96573d7/image.png" alt=""></p>
<h3 id="다양한-fetch-캐시-옵션들-데이터-캐시">다양한 fetch 캐시 옵션들 (데이터 캐시)</h3>
<p><strong>no-store:</strong> 캐시 저장x
<strong>force-cache:</strong> 캐시 저장
<strong>next:{revalidate:3}:</strong> 3초간 캐시 저장, 3초 이후 요청 들어오면 stale 상태로 변경되고 우선 이 stale 값으로 페이지 그리고나서 다음 요청 부터 최신 데이터로 그려짐
<img src="https://velog.velcdn.com/images/jamee_/post/a1ad62bb-b8de-4530-b4a0-3c660b421ca7/image.png" alt=""></p>
<p>3초 경과 =&gt; 요청 =&gt; stale 값 =&gt; 요청 =&gt; 최신 데이터
즉, 2번째 요청부터 최신 데이터로 갱신되어 그려짐</p>
<p><strong>next:{tags:[&quot;a&quot;]}:</strong> 요청이 들어왔을 때 데이터를 최신화</p>
<p><strong><em>※ 데이터 캐시</em></strong></p>
<ul>
<li>백엔드서버로부터 불러온 데이터를 거의 영구적으로 보관하기 위해 사용</li>
<li>서버 가동중에는 영구적으로 보관된다</li>
</ul>
<h3 id="request-memoization">Request Memoization</h3>
<p>하나의 페이지를 이루고 있는 여러개의 컴포넌트에서 중복적으로 발생하는 요청들을 캐싱해서 한 번만 요청하도록
하나의 페이지를 렌더링하는 동안 중복된 API 요청을 캐싱하기 위해 존재
렌더링이 종료되면 모든 캐시가 소멸된다
아래 이미지는 no-store 기준
<img src="https://velog.velcdn.com/images/jamee_/post/cee8b6e8-66fe-409d-b12c-be01dd177803/image.png" alt=""></p>
<h3 id="full-route-cache">Full Route Cache</h3>
<p>Next 서버 측에서 빌드 타임에 특정 페이지의 렌더링 결과를 캐싱하는 기능(SSG)
<strong>Static Page</strong> 에 대해서만 Full Route Cache 적용
<strong>대부분의 페이지를 Full Route Cache 를 지원하는 Static Page 로 만들것을 권장</strong></p>
<p><strong>Dynamaic Page</strong></p>
<ul>
<li>캐시되지 않는 Data Fetching 사용하는 경우</li>
<li>동적 함수( 쿠키, 헤더, 쿼리스트링 )를 사용하는 경우</li>
</ul>
<p>fetch 에 revalidate 가 선언된 경우 데이터 캐시 및 풀 라우트 캐시도 업데이트됨(ISR)
이것도 동일하게 지난 후 요청 시 stale 값을 보여주고, 이후 요청 시 갱신된 데이터 및 페이지 반환</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/72a1ed39-13a7-4aed-8f03-2da428983b56/image.png" alt=""></p>
<h3 id="빌드-에러-발생">빌드 에러 발생</h3>
<pre><code>// SearchBar.tsx

&quot;use client&quot;
...
const searchParams = useSearchParams();</code></pre><p>빌드 과정에서 서버에서 사전 렌더링 시 서버 컴포넌트와 클라이언트 컴포넌트를 실행하여 페이지를 그리는데
쿼리스트링의 값은 알 수 없으므로 에러가 발생
그래서 아래와 같이 수정해주어야함
사전 렌더링에서 배제되고, 오직 클라이언트 측에서만 렌더링되도록</p>
<pre><code>&quot;use client&quot;
...
&lt;Suspense fallback={&lt;div&gt;Loading...&lt;/div&gt;}&gt;
    &lt;SearchBar/&gt;
&lt;/Suspense&gt;</code></pre><p>useSearchParams() 는 비동기 함수
즉, 클라이언트에서 쿼리스트링을 불러오는 때인 브라우저에 마운트 되는 시점 전까지 Suspense</p>
<h3 id="generatestaticparams">generateStaticParams</h3>
<p>Page Router 에서 SSG 형식을 위해 getStaticPaths 설정해준 것처럼
App Router 에서도 Static Page 를 위해 generateStaticParams 설정하면
빌드과정에서 설정한 Params 로 Static Page 생성</p>
<p><strong>※주의:</strong> 만약 fetch 에 cache 설정이 되어있지 않더라도 이 페이지는 Static Page 로 생성됨</p>
<pre><code>export function generateStaticParams(){
    return [{id:&quot;1&quot;},{id:&quot;2&quot;},{id:&quot;3&quot;}]
}

export default async function Page({params}){
    const {id} = params; // 1,2,3
    ...
}</code></pre><p>위 코드는 1, 2, 3 이외에 Param 에 접근하면 Dynamic Page 로 생성되고,
next 캐시에 등록되어 다시 접근 시 Static Page 로 반환</p>
<p>반대로 1, 2, 3 이외에 접근 시 404 페이지로 리다이렉트 시켜주고싶다면</p>
<pre><code>export const dynamicParams = false;</code></pre><p>🤔 <strong>궁금한점..</strong>
슬러거의 경우 Dynamic Page 로 생성?
generateStaticParams 설정 안해주면 Dynamic Page 인가?
그럼 next 서버에도 캐시된 페이지가 저장이 안되나?</p>
<h3 id="라우트-세그먼트">라우트 세그먼트</h3>
<p>풀 라우트 캐시를 설정하기 위해 데이터 캐싱 및 동적인 함수 사용 유무를 컴포넌트마다 확인해야하는 번거로움있음
그래서 나온 라우트 세그먼트. 페이지의 동작을 강제로 설정
<strong>하지만 권장되는 방식은 아님. 이미 빌드타임때 세세하게 확인해서 Dynamic, Static 으로 변환해주고 있기 때문</strong></p>
<pre><code>export const dynamic = &quot;&quot;

1. auto: 기본값, 아무것도 설정 안함
2. force-dynamic: 페이지를 강제로 Dynamic 설정
3. force-static: 페이지를 강제로 Static 설정
        - 쿼리 스트링과 같은 동적 함수가 선언된 경우 쿼리값은 항상 빈값으로 설정됨
        - 즉, 항상 같은 빈 결과 페이지가 반환됨
4. error: 페이지를 강제로 Static 설정
        - 데이터 캐싱x 및 동적함수 사용된 경우 빌드 에러 발생</code></pre><h3 id="클라이언트-라우터-캐시">클라이언트 라우터 캐시</h3>
<p>브라우저에 저장되는 캐시
페이지 이동을 효율적으로 진행하기 위해 페이지의 일부 데이터 보관</p>
<p>공통된 레이아웃을 서버로부터 중첩으로 불러오지 않고, 클라이언트 라우터 캐시에 저장해두었다가 불러오는 형식
예를들어 공통 레이아웃에 new Date 를 선언한 경우 페이지 이동 시 시간초가 변동되지 않는다 <strong>(새로고침 시 초기화)</strong></p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/9913471a-c4dc-4916-a409-cf9e8744c46c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[App Router 시작하기]]]></title>
            <link>https://velog.io/@jamee_/App-Router-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jamee_/App-Router-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 17 Nov 2025 01:52:09 GMT</pubDate>
            <description><![CDATA[<h3 id="캐치-올-세그먼트">캐치 올 세그먼트</h3>
<p>books/1/1 와 같은 중첩된 파라미터의 경우 [...id] 형식으로 폴더 생성
<strong>주의:</strong> books 경로와 같이 파라미터가 존재하지 않는 경우는 404 페이지 반환
<strong>해결법:</strong> 옵셔널 캐치 올 세그먼트로 [[...id]] 형식으로 폴더 생성</p>
<h3 id="layout">Layout</h3>
<p>Layout 의 경우 아래 이미지와 같이 하위 경로에 대해서는 중첩 적용 된다</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/6ae7abb5-4157-4b07-89ad-c6149c5df769/image.png" alt=""></p>
<h3 id="route-group">Route Group</h3>
<p>(with-searchbar) 와 같은 형식으로 폴더 생성
경로상에는 아무런 영향 끼치 않으며 Layout 에 대해 동일한 적용이 가능</p>
<h3 id="react-server-component">React Server Component</h3>
<p>Page Router 에서 JS Bundle 에 상호작용이 필요없는 JS 파일들도 같이 포함되어
브라우저에서 한번 더 실행 될 필요 없다고 느낌
그래서 상호작용이 필요한 컴포넌트만 JS Bundle 에 포함시키자
그러면 Bundle 용량도 줄고 TTI 시간도 단축되지 않을까?
대부분의 페이지를 서버 컴포넌트로 생성하자</p>
<ul>
<li><strong>서버 컴포넌트:</strong> 서버 측 사전렌더링 시에만 딱 1번 실행</li>
<li><strong>클라이언트 컴포넌트:</strong> 서버 측 사전렌더링 및 하이드레이션 때 실행되어 총 2번</li>
</ul>
<h4 id="클라이언트-컴포넌트에서-서버-컴포넌트는-import-할-수-없다">클라이언트 컴포넌트에서 서버 컴포넌트는 import 할 수 없다</h4>
<p>이유는 위에 설명했듯이 JS Bundle 에는 상호작용에 필요한 파일만 있고,
Bundle 파일로 하이드레이션 과정을 거치고 한번 더 클라이언트 컴포넌트가 호출될텐데
여기서 import 된 서버 컴포넌트는 Bundle 에 포함되어 있지 않으므로</p>
<p>이런 문제가 빈번히 발생하다보니 Next 에서는 import 된 서버 컴포넌트의 경우 자동으로 클라이언트 컴포넌트로 변환함</p>
<p><strong>편법:</strong> 클라이언트 컴포넌트에서 children 형식으로 서버 컴포넌트 받으면 문제 없음</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/8c692bdf-b48b-42cf-87ef-85370d7bbd49/image.png" alt=""></p>
<h4 id="서버-컴포넌트에서-클라이언트-컴포넌트로-직렬화-되지-않은-props-는-전달-불가능">서버 컴포넌트에서 클라이언트 컴포넌트로 직렬화 되지 않은 props 는 전달 불가능</h4>
<p>함수와 같은 직렬화 불가능한 값은 props 로 전달이 안된다
서버에서 사전렌더링 되는 방식을 좀 더 살펴보자면
서버 컴포넌트만 우선 RSC Payload 형식으로 변환됨
RSC Payload 는 직렬화된 데이터로 되어있음
이 과정에서 함수도 직렬화되어있을텐데 함수는 직렬화로 표현 안되므로 에러</p>
<p><strong>※ RSC Payload</strong></p>
<ul>
<li>React Server Component 의 순수한 데이터 (직렬화한 결과물)<ul>
<li>서버 컴포넌트 렌더링 결과(마크업과 유사한)만 포함되어 있어 결과물 안에 서버 코드는 노출 x</li>
<li>연결된 클라이언트 컴포넌트들의 위치 포함</li>
<li>클라이언트 컴포넌트에게 전달하는 props 값 포함</li>
</ul>
</li>
</ul>
<h4 id="페이지-이동의-경우-network탭">페이지 이동의 경우 Network탭</h4>
<p>최초 페이지 접근하여 사전 렌더링시 서버 컴포넌트 먼저 실행되어 RSC Payload 값으로 변환되고,
클라이언트 컴포넌트 실행된 결과값과 합쳐져 html 페이지 생성되고 클라이언트로 전달
이후 하이드레이션을 위해 JS Bundle 추가로 클라이언트로 전달</p>
<p>다른 경로로 이동 시 index.html 파일을 또 생성하여 주는게 아닌
이동하는 페이지의 RSC Payload 와 JS Bundle 을 서버로부터 받아 CSR 처럼 렌더링해줌</p>
<p><em>테스트는 빌드 파일로 해야 확인 가능</em></p>
<pre><code>&lt;Link/&gt;, router.~</code></pre><p>(프리페칭이 적용 안되어있다면)
링크 눌러서 해당 페이지로 이동할 때 RSC Payload, JS Bundle 받아옴</p>
<p>(프리페칭 적용 되어있다면)
링크 누르기 전에 이미 링크에 해당하는 페이지들이 Static 인 경우 RSC Payload, JS Bundle 받아옴
Dynamic 인 경우 RSC Payload 만 받고 페이지 이동 하고 나서 JS Bundle 받음</p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/980d3336-ef84-44be-afce-0ab2b9674cf5/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[바이브코딩 프로젝트 챌린지]]></title>
            <link>https://velog.io/@jamee_/%EB%B0%94%EC%9D%B4%EB%B8%8C%EC%BD%94%EB%94%A9-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B1%8C%EB%A6%B0%EC%A7%80</link>
            <guid>https://velog.io/@jamee_/%EB%B0%94%EC%9D%B4%EB%B8%8C%EC%BD%94%EB%94%A9-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B1%8C%EB%A6%B0%EC%A7%80</guid>
            <pubDate>Tue, 16 Sep 2025 02:40:02 GMT</pubDate>
            <description><![CDATA[<h3 id="lovable-와-구글-스프레드-시트로-랜딩사이트-만들기">Lovable 와 구글 스프레드 시트로 랜딩사이트 만들기</h3>
<p><img src="https://velog.velcdn.com/images/jamee_/post/41da54e3-1a82-4c75-8413-78221953e49d/image.png" alt=""></p>
<p>프롬프트창 +버튼 &gt; Knowledge
가이드 라인으로 앞으로 모든 요청에 대해 아래 사항을 준수해달라고 설정
<img src="https://velog.velcdn.com/images/jamee_/post/e013b052-757e-4605-8eaa-5206cb755985/image.png" alt=""></p>
<p>Edit 버튼 클릭 시 DOM 요소를 선택할 수 있게되고, 해당 DOM 요소에 대해 프롬프트 입력하여 수정 가능</p>
<p>Google AI Studio 의 System Instructions 에 구글 스프레드 시트을 위한 초기 가이드라인 설정</p>
<p>Google SpreadSheet 새 문서 만들고 상단에 설정에서 <strong>Apps Script</strong> 설정
<em><strong>Apps Script:</strong> docs, spread, drive 등 여러 서비스들을 코드로 자동화 작성할 수 있게하는 공간</em></p>
<p>AI Studio 에서 나온 GAS 코드를 붙여넣고 저장 및 우측 상단 새 배포 클릭
배포된 링크를 AI Studio 에 결과 코드중 GOOGLE_URL 코드영역에 링크 붙여넣기
해당 코드를 Lovable 프롬프트에 복붙 후 Edit 버튼 클릭하여 폼 영역 클릭 후 프롬프트 전송
이제 해당 폼에서 전송 시 구글 스프레드 시트에 내용 추가됨</p>
<h3 id="boltnew-로-크리에이터-후원-갤러리-만들기">Bolt.new 로 크리에이터 후원 갤러리 만들기</h3>
<p>github 에 올린 이미지를 cnd 형식으로 빠르게 불러오기위해 <a href="https://www.jsdelivr.com/github">jsdelivr</a> 사용
후원은 buymeacoffee 사용
웹사이트 생성은 bolt.new 사용</p>
<h3 id="v0-와-notion-api를-사용하여-애드핏-블로그-만들기">v0 와 Notion API를 사용하여 애드핏 블로그 만들기</h3>
<p>v0 에서 프롬프트 입력하여 사이트 생성
code 모드 클릭 시 직접 파일에 들어가 수정 가능
<img src="https://velog.velcdn.com/images/jamee_/post/e8c96ed9-2047-4539-b014-c8dbf8b0858a/image.png" alt=""></p>
<p>Notion 에서 API Key 생성 후 env.local NOTION_API_KEY 부분에 붙여넣기
내 DB템플릿 에 들어가서 설정 &gt; 연결 &gt; 생성한 API Key 연결
그리고 DB 템플릿 URL 의 중간 부분에 키값을 NOTION_DATABASE_ID 부분에 붙여넣기
배포 진행 시 vercel 에서 진행되고, vercel 에서 환경변수 세팅해야 정상 동작
카카오 에드핏에 설정 후 심사</p>
<h3 id="orchids-와-latpeed로-나만의-지식-상품-만들고-수익화하기">Orchids 와 latpeed로 나만의 지식 상품 만들고 수익화하기</h3>
<p>Gemini 를 사용해 주제에 대한 프롬프트 정보를 얻은 후 해당 정보 딥 리서치 활성화 후 검색
<img src="https://velog.velcdn.com/images/jamee_/post/df7e0978-6c33-48b6-a926-b30dc8532e5a/image.png" alt=""></p>
<p><strong>Orchids:</strong> 디자인에 조금 더 신경 쓴 웹사이트 생성</p>
<ul>
<li>Lovable 과 동일하게 visual edit mode 클릭 시 DOM 요소에 대해 프롬프트 입력하여 수정 가능
추가로 이미지 변경이나 텍스트를 직접 GUI 로 제공하여 편리</li>
</ul>
<p><strong>latpeed:</strong> pdf, 전자책과 같은 매체를 상품으로 사고 팔 수 있는 사이트</p>
<h3 id="mocha-로-유행하는-테스트-서비스-만들고-쿠팡-파트너스로-수익화하기">Mocha 로 유행하는 테스트 서비스 만들고 쿠팡 파트너스로 수익화하기</h3>
<p><strong>Mocha:</strong> Lovable, v0, Orchids 와 같은 웹사이트 생성툴이며, 이것 역시 동일한 기능들 제공</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[간단 프롬프트 엔지니어링]]></title>
            <link>https://velog.io/@jamee_/%EA%B0%84%EB%8B%A8-%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81</link>
            <guid>https://velog.io/@jamee_/%EA%B0%84%EB%8B%A8-%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81</guid>
            <pubDate>Mon, 15 Sep 2025 06:02:27 GMT</pubDate>
            <description><![CDATA[<h2 id="예시를-보여주며-요청하기">예시를 보여주며 요청하기</h2>
<p><strong>Zero-shot</strong></p>
<ul>
<li>예시없이 요청하는 경우</li>
</ul>
<p><strong>One-shot</strong></p>
<ul>
<li>예시를 하나 제공하는 경우</li>
</ul>
<p><strong>Few-shot</strong></p>
<ul>
<li>여러 예시를 제공하는 경우</li>
</ul>
<h2 id="생각의-사슬">생각의 사슬</h2>
<p>AI 가 자신의 사고 과정을 단계별로 설명하도록 요청하는 기법
툴에서 생각중.. 이런 경우 추론 모델</p>
<h2 id="잘게-나누어-질문하기">잘게 나누어 질문하기</h2>
<p>AI 가 해결해야 할 여러 개의 작은 하위 문제로 나누어 단계별로 요청하는 기법</p>
<h2 id="컨텍스트-제공하기">컨텍스트 제공하기</h2>
<p>작업과 관련된 최대한 많은 정보 제공
<img src="https://velog.velcdn.com/images/jamee_/post/413df068-e2a2-4826-b516-8ef9b8ae2800/image.png" alt=""></p>
<blockquote>
<h3 id="tip">Tip)</h3>
<p><strong>&quot;이 작업을 완벽하게 수행하기위해 추가로 필요한 정보가 있다면, 나에게 질문해줘&quot;</strong>
AI 가 스스로 부족한 정보를 파악하고 역으로 질문하게 만드는 사용 방법</p>
</blockquote>
<h2 id="ai-에게-질문을-만들게-하기">AI 에게 질문을 만들게 하기</h2>
<p>AI 에게 직접적으로 답을 요구하는 대신 <strong>&quot;이 문제를 해결하기 위해 내가 너에게 어떤 질문을 해야 할까?&quot;</strong> 또는
<strong>&quot;내 질문을 어떻게 더 효과적으로 만들 수 있을까?&quot;</strong> 라고 물어보기</p>
<h2 id="구글의-5단계-프롬프트-프레임워크-tcrei">구글의 5단계 프롬프트 프레임워크 (TCREI)</h2>
<p>어떤 생성형 AI 도구에서든 통용되는 가장 강력하고 기본적인 공식
<strong>TCREI:</strong> 신중하게 정말 훌륭한 입력을 만든다
<img src="https://velog.velcdn.com/images/jamee_/post/beafb6ad-c46b-46f7-9c71-0ceae5855e7e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/3b2a9c25-dc0c-4576-8c80-169f9c52a1c2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/aa90e96b-af89-4cf5-9cb4-016761c24b67/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/f69194ac-f926-494c-b465-34caf3214b23/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jamee_/post/86bad6bf-1581-4423-a843-d0e990b50d91/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTML 의미론]]></title>
            <link>https://velog.io/@jamee_/HTML-%EC%9D%98%EB%AF%B8%EB%A1%A0</link>
            <guid>https://velog.io/@jamee_/HTML-%EC%9D%98%EB%AF%B8%EB%A1%A0</guid>
            <pubDate>Sun, 03 Nov 2024 07:10:25 GMT</pubDate>
            <description><![CDATA[<h3 id="div-와-span">div 와 span</h3>
<p><code>&lt;div&gt;</code> 와 <code>&lt;span&gt;</code> 는 <strong>요소에 아무런 의미가 없음</strong>
사이트에 <code>&lt;div&gt;</code> 와 <code>&lt;span&gt;</code> 이 많다는 것은 적절하게 작성하고 있지 않다는 것
다른 적절한 HTML 요소를 사용할 수 없을 때 마지막으로 선택하는 태그임</p>
<h3 id="header-와-footer">header 와 footer</h3>
<p><code>&lt;header&gt;</code>
도입부, 헤딩, 헤딩 그룹, 목차, 검색, 로고, 글로벌 네비게이션 바
<code>&lt;footer&gt;</code>
저자, 저작권, 연락처, 관련 문서
<code>&lt;header&gt;</code> 와 <code>&lt;footer&gt;</code> 는 <strong>Sectioning Root(body) 안에</strong> 또는 <strong>Sectioning 콘텐츠(article, aside, nav, section) 감쌀 때</strong> 사용</p>
<h3 id="sectioning-콘텐츠">Sectioning 콘텐츠</h3>
<p><code>&lt;section&gt;</code>
제목이 있는 주제별 콘텐츠 그룹
한 페이지에 카드 형식의 콘텐츠들이 나뉘어져 있을 때
각각 section 으로 선언
<code>&lt;article&gt;</code>
섹션 요소 중 독립적으로 배포 가능한 완결형 콘텐츠
뉴스 기사, 블로그 본문, 사용자 댓글 등
<code>&lt;header&gt;</code>, <code>&lt;footer&gt;</code> 요소를 내부에 사용하는 것은 선택사항</p>
<p><code>&lt;nav&gt;</code>
현재 사이트 또는 일부를 링크하고 있는 주요 탐색 세션</p>
<p><code>&lt;aside&gt;</code>
페이지의 주된 내용과 관련이 약해 구분할 필요가 있는 섹션
광고 배너 등</p>
<p><strong>※ 모든 Sectioning 콘텐츠는 Heading ( h1 ~ h6 ) 콘텐츠를 포함하는 것을 강력하게 권장</strong></p>
<h3 id="main">main</h3>
<p><code>&lt;main&gt;</code>
문제의 핵심 주제 또는 애플리케이션의 핵심 기능과 직접 관련 있는 콘텐츠 영역을 의미
페이지마다 반복되지 않는 내용을 포함해야한다
하나의 페이지 안에서 하나의 <code>&lt;main&gt;</code> 요소만 표시할 수 있고,
<code>&lt;header&gt;</code> , <code>&lt;footer&gt;</code> , Sectioning 콘텐츠의 자식이 될 수 없다
<code>&lt;body&gt;</code> , <code>&lt;div&gt;</code> 요소를 제외한 다른 요소의 자손이 될 수 없다
<img src="https://velog.velcdn.com/images/jamee_/post/b96f43af-8b1e-49f7-ab8b-266a4e1494b9/image.png" width="80%"/></p>
<h3 id="dialog">dialog</h3>
<p><code>&lt;dialog&gt;</code>
사용자와 상호작용하는 응용프로그램( 대화상자 )를 의미
팝업 등
<code>&lt;dialog&gt;</code> 가 열릴 때 키보드 초점이 <code>&lt;dialog&gt;</code> 안 쪽에 항상 고정
또한, 키보드 사용자가 Tab 키만을 시용해서 <code>&lt;dialog&gt;</code> 안 쪽에 있는 이동 가능한 컨트롤들을 탐색할 수 있어야 함</p>
<h3 id="figure">figure</h3>
<p><code>&lt;figure&gt;</code>
문서의 주된 흐름을 위해 참조하는 독립적인 완결형 요소
이미지, 도표, 코드 등의 내용물과 설명을 포함</p>
<pre><code>&lt;figure&gt;
  &lt;img/&gt;
  &lt;figcaption&gt;설명&lt;/figcaption&gt;
&lt;/figure&gt;</code></pre><img src="https://velog.velcdn.com/images/jamee_/post/53ff1b1a-38d7-4cbb-8271-f13731cf0edc/image.png" width="80%"/>

<h3 id="mark">mark</h3>
<p><code>&lt;mark&gt;</code>
독자의 주의를 끌기 위한 하이라이트
노란색 형관펜을 칠한 것 처럼 강조
<img src="https://velog.velcdn.com/images/jamee_/post/a9f340a7-883c-4aee-afe6-343723b89801/image.png" width="80%"/></p>
<h3 id="address">address</h3>
<p><code>&lt;address&gt;</code>
보통 footer 의 안 쪽 저작권, 저자의 정보, 연락처 등을 나타내는 곳에 사용
접근성과 SEO 측면에서 좋음</p>
<h3 id="ins-와-del">ins 와 del</h3>
<p><code>&lt;ins&gt;</code>
추가한 내용
<code>&lt;del&gt;</code>
삭제한 내용
주로 쇼핑몰에서 기존 가격에 <code>&lt;del&gt;</code>로 줄 긋고 할인된 최종 가격에 <code>&lt;ins&gt;</code></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTML 개요 알고리즘]]></title>
            <link>https://velog.io/@jamee_/HTML-%EA%B0%9C%EC%9A%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@jamee_/HTML-%EA%B0%9C%EC%9A%94-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Sun, 03 Nov 2024 06:10:37 GMT</pubDate>
            <description><![CDATA[<p>우리가 서점에 가서 책의 목차를 보고 관심있는 주제가 있으면 구매하듯이
검색 엔진도 마찬가지로 사이트의 heading 이 얼마나 잘 정리되어 있는지 판단</p>
<p>화면 낭독기 사용자도 웹 문서에서 heading 만 따로 추려서 음성으로 받은 다음에 관심있는 영역으로 이동해서 해당 영역을 음성으로 읽음</p>
<h3 id="outline">Outline</h3>
<p>간결하게 추려낸 주요 내용. 윤곽
heading 과 section 으로 구성</p>
<p>chrome 의 HeadingMap extension 으로 heading 이 얼마나 잘 구성되어 있는지 한 눈에 확인 가능
<img src="https://velog.velcdn.com/images/jamee_/post/406cded4-21b5-469e-87e7-a2dd2bdeb62e/image.png" width="50%"/>올바른 작성법은 1부터 순차적으로 작성해야함 ex)) h1 없이 h2 사용 x</p>
<h4 id="heading">Heading</h4>
<p>문서의 개요를 형성하는 필수 아이템
사이트와 화면 낭독기에 개요를 적극적으로 드러내는 방법</p>
<h4 id="title-vs-heading">Title vs Heading</h4>
<p><code>&lt;title&gt;</code> 요소는 문서의 제목이며 한번만 사용 가능
<code>&lt;h*&gt;</code> 요소는 섹션의 제목이며 여러 번 사용 가능</p>
<h4 id="sectioning">Sectioning</h4>
<p>개요의 범위를 명시적으로 지정
<code>&lt;article&gt;</code> 기사 본문, 맥락 독립적으로 배포 가능
<code>&lt;aside&gt;</code> 배너와 같은 페이지의 주요 내용이 아닌 부수적 내용
<code>&lt;nav&gt;</code> 사이트의 주된 탐색 메뉴
<code>&lt;section&gt;</code> 주제별로 나누거나 묶음</p>
<p><strong>※ 여기서 주의점</strong>
HTML5 의 Outline 알고리즘은 웹 개발자가 heading 의 수준을 부적절하게 마크업을 했더라도 그것을 보정해주는 기능이 들어가 있음 ( 하지만 의존하면 안됨. 명세와 다르게 동작하기때문 )</p>
<pre><code>&lt;h1&gt;A
&lt;article&gt;
  &lt;h1&gt;B
  &lt;section&gt;
    &lt;h1&gt;C</code></pre><p>위 코드는 HTML5 명세의 의도대로라면
h1 A, h2 B, h3 C 로 계층이 나타내어져야 하지만,
브라우저/화면 낭독기에는 모두 같은 depth, 즉, h1 A, h1 B, h1 C 로 나타남</p>
<h3 id="결론">결론</h3>
<p>heading 을 반드시 사용하고, heading 과 section 을 1:1 로 매핑하기
HTML5 개요 알고리즘에 의존하지 않기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[검색 엔진 낱낱이]]></title>
            <link>https://velog.io/@jamee_/%EA%B2%80%EC%83%89-%EC%97%94%EC%A7%84-%EB%82%B1%EB%82%B1%EC%9D%B4</link>
            <guid>https://velog.io/@jamee_/%EA%B2%80%EC%83%89-%EC%97%94%EC%A7%84-%EB%82%B1%EB%82%B1%EC%9D%B4</guid>
            <pubDate>Sun, 03 Nov 2024 06:08:21 GMT</pubDate>
            <description><![CDATA[<h3 id="페이지-타이틀">페이지 타이틀</h3>
<p><strong>분류</strong>
Metadata 콘텐츠</p>
<p><strong>작성법</strong>
본문을 가장 잘 설명하는 키워드 중심으로 작성하며
페이지마다 구체적이고 고유한 키워드 사용.
반복하는 키워드는 최소화하며 구체적인 키워드를 앞으로 배치</p>
<p><strong>구분자</strong>
구분자는 대시(-), 파이프(|), 콜론(:) 을 추천
언더바(_)는 인접 키워드를 하나로 연결하기 때문에 추천하지 않음</p>
<p><strong>크롤링</strong>
Javascript 에 의해 동적으로 생성된 페이지 타이틀도 크롤링 한다</p>
<p><strong>접근성</strong>
화면 낭독기 사용자는 웹 페이지 접속 시
가장 먼저, 페이지 타이틀을 음성으로 전달받음
<em><strong>Info.</strong></em> <em>화면 낭독기 사용자는 음성 낭독기 속도를 2배속 이상으로 들음
불필요한 정보들을 건너뛰기 위해서</em>
따라서 페이지 타이틀은 간결하게 집어넣기</p>
<h3 id="메타데이터">메타데이터</h3>
<p>문서에 대한 정보를 제공</p>
<p><code>&lt;html lang=&quot;ko&quot;&gt;</code>
본문에서 사용중인 언어
검색 엔진이 특정 언어의 웹 페이지를 검색할 때 도움을 주며
화면 낭독기 사용자들이 웹 페이지를 읽을 때 어떤 음성 엔진을 사용해야하는지 힌트를 주기도 함</p>
<p><code>&lt;meta charset=&quot;utf-8&quot;&gt;</code>
한글뿐만 아니라 전세계 모든 국가의 언어를 웹 페이지에 문제없이 표시 할 수 있음</p>
<p><code>&lt;meta name=&quot;description&quot; content=&quot;A description of the page&quot;&gt;</code>
검색 엔진의 검색 결과 화면에 노출되는 텍스트
검색 결과 화면에 사이트에 대한 설명을 원하는대로 표시하고 싶을 때</p>
<p><code>&lt;meta name=&quot;keywords&quot; content=&quot;free, anynone&quot;&gt;</code>
많은 마케터들이 키워드에 사이트와 상관없는 정보를 집어넣음
그래서 구글 검색 엔진은 키워드를 신뢰하지 않음</p>
<p><code>&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;</code>
사이트가 모바일에서도 볼 수 있도록 최적화 되어있는지 제공
데스크탑에서는 브라우저 내에 보이는 사이트 영역만큼을 viewport
모바일에서는 문서의 전체 크기가 viewport
모바일에서도 화면에 보이는 영역만큼이 viewport 가 되기 위해 즉, 데스크탑과 일치시키기 위해서 width=device-width 선언.
initial-scale=1 은 줌 비율이 1을 의미</p>
<p><strong>실제 적용 사례</strong>
<img src="https://velog.velcdn.com/images/jamee_/post/c35adbd2-3eb5-43ca-98b3-7c0e8d76acb8/image.png"/>구글의 자체적인 알고리즘으로 사람들이 많이 클릭하는 페이지를 추려서 소개</p>
<img src="https://velog.velcdn.com/images/jamee_/post/113831fd-6583-4388-8a68-8ed66638d9e7/image.png"/>
구조화된 데이터를 제공해서 연관 채널 등록 가능

<img src="https://velog.velcdn.com/images/jamee_/post/ddf628ab-9401-424a-b7a5-5262978768e6/image.png" width="50%"/>
위와 같이 선언하면 네이버에서 크롤링해감

<h3 id="최종-메타데이터-요약">최종 메타데이터 요약</h3>
<img src="https://velog.velcdn.com/images/jamee_/post/3281578c-1dc9-4f77-a769-ce4ae2eee035/image.png" width="80%"/>












]]></description>
        </item>
        <item>
            <title><![CDATA[HTML 마크업 기초 다지기]]></title>
            <link>https://velog.io/@jamee_/HTML-%EB%A7%88%ED%81%AC%EC%97%85-%EA%B8%B0%EC%B4%88-%EB%8B%A4%EC%A7%80%EA%B8%B0</link>
            <guid>https://velog.io/@jamee_/HTML-%EB%A7%88%ED%81%AC%EC%97%85-%EA%B8%B0%EC%B4%88-%EB%8B%A4%EC%A7%80%EA%B8%B0</guid>
            <pubDate>Sun, 03 Nov 2024 03:47:05 GMT</pubDate>
            <description><![CDATA[<p>기존에 <a href="https://www.w3.org/" target="_blank" style="cursor:pointer">w3c</a> 에서 만들어지고 제공되었던 HTML5 명세는 폐기되었고,
<a href="https://html.spec.whatwg.org/" target="_blank" style="cursor:pointer"><span>What working group (브라우저 제조사 연합)</span></a> 에서 HTML 표준을 만들고 유지보수 하고있음</p>
<p><em>What working group 에서 만들어지는 표준을 w3c 에 제안을하고, 검토하고 수락하는 과정을 거치는데 이 과정이 불필요하다 느껴져서 wwg 가 직접 표준을 만들고 유지보수하기를 원했음. 그러다가 2019년부터 wwg 에서 만든 표준이 공식적으로 채택되어 w3c 에서 유지보수하던 HTML5 표준은 폐기된 표준이 되었음</em></p>
<p><a href="https://caniuse.com/" target="_blank" style="cursor:pointer">caniuse</a> 는 웹 기술( HTML,css,js )들의 표준 명세를 브라우저가 실제로 어느정도로 구현했는지 호환성 정보를 확인할 수 있음</p>
<h3 id="주요-html-콘텐츠-분류">주요 HTML 콘텐츠 분류</h3>
<p>HTML5 이전에 block 컨테이너라 부르던 류들은 주로 Flow 컨텐츠라 부름
inline 컨텐츠라 부르던 요소들은 Phrasing 컨텐츠로 대부분 들어가게됨</p>
<img src="https://velog.velcdn.com/images/jamee_/post/c806390c-db01-4316-96b0-5baeefcc710a/image.png" width="50%"/>
위 이미지의 용어들을 아는 것이 명세를 읽는데 중요함

<h4 id="flow-콘텐츠">Flow 콘텐츠</h4>
<p>body 에 포함할 수 있는 모든 요소 ( block, inline... )</p>
<h4 id="metadata-콘텐츠">Metadata 콘텐츠</h4>
<p>주로 문서의 head 안에 들어가며 콘텐츠와 문서를 구조화 하는 요소를 의미
base, link, meta, noscript, script, style, template, title
일부 요소는 경우에 따라 Flow 콘텐츠 ( body 안에서 사용 가능한 script, template ..)</p>
<h4 id="heading-콘텐츠">Heading 콘텐츠</h4>
<p>h1, h2, h3, h4, h5, h6, hgroup</p>
<h4 id="sectioning-콘텐츠">Sectioning 콘텐츠</h4>
<p>이 콘텐츠는 암시적인 개요를 형성
article, aside, nav, section</p>
<h4 id="phrasing-콘텐츠">Phrasing 콘텐츠</h4>
<p>단락을 형성하는 구문 콘텐츠
<img src="https://velog.velcdn.com/images/jamee_/post/9da5e00f-3618-4771-b811-a0a807e63050/image.png"/></p>
<h4 id="embedded-콘텐츠">Embedded 콘텐츠</h4>
<p>외부 자원을 참조하는 콘텐츠
audio, canvas, embed, iframe, img, math, object, picture, svg, video</p>
<h4 id="interactive-콘텐츠">Interactive 콘텐츠</h4>
<p>사용자와 상호 작용할 수 있는 콘텐츠
a, audio, button, details, embed, iframe, img, input, label, select, textarea, video</p>
<h4 id="그-외-기타-콘텐츠">그 외 기타 콘텐츠</h4>
<p><strong>Palpable content</strong></p>
<ul>
<li>비어있지 않은, hidden 속성이 없는. 볼 수 있는 콘텐츠</li>
</ul>
<p><strong>Script-supporting element</strong></p>
<ul>
<li>렌더링하지 않지만 사용자에게 기능을 제공 ( script, template )</li>
</ul>
<p><strong>Transparent content models</strong></p>
<ul>
<li>투명 콘텐츠 모델
a, ins, del, object, video, audio, map, noscript, canvas
문서 노드에서 제거해도 문서가 유효하게 유지
ex)) a 태그안에 있는 요소들이 a 태그가 사라져도 구조가 유효함</li>
<li><em>자기 바로 부모의 콘텐츠 모델을 따라야함*</em></li>
</ul>
<h3 id="위-내용들을-함축한-퀴즈">위 내용들을 함축한 퀴즈</h3>
<img src="https://velog.velcdn.com/images/jamee_/post/264eacce-7a11-4b5c-9035-4a7986e535bb/image.png"/>














]]></description>
        </item>
        <item>
            <title><![CDATA[[React-Native 기초편(1)]]]></title>
            <link>https://velog.io/@jamee_/React-Native-%EA%B8%B0%EC%B4%88%ED%8E%B81</link>
            <guid>https://velog.io/@jamee_/React-Native-%EA%B8%B0%EC%B4%88%ED%8E%B81</guid>
            <pubDate>Mon, 21 Oct 2024 10:06:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="네이티브-앱이란">네이티브 앱이란?</h3>
<p>특정 플랫폼 ( IOS, Android ) 을 위해 최적화 되어 개발된 애플리케이션</p>
</blockquote>
<h4 id="장점">장점</h4>
<ul>
<li>최적화된 코드로 빠른 성능을 제공</li>
<li>카메라, GPS 등 기기의 하드웨어에 접근 가능</li>
</ul>
<h4 id="단점">단점</h4>
<ul>
<li>각 플랫폼마다 별도로 개발해야함</li>
<li>스토어에서 앱 심사 과정을 거쳐야함</li>
</ul>
<blockquote>
<h3 id="웹앱이란">웹앱이란?</h3>
<p>웹 기술을 사용하여 개발하며, 별도의 다운로드 없이 모바일 브라우저를 통해 접근</p>
</blockquote>
<h4 id="장점-1">장점</h4>
<ul>
<li>어떤 운영 체제에서도 실행 가능</li>
<li>하나의 코드베이스로 운영 가능</li>
</ul>
<h4 id="단점-1">단점</h4>
<ul>
<li>네이티브 앱보다 느리며, 기기의 모든 기능을 사용할 수 없음</li>
</ul>
<blockquote>
<h3 id="하이브리드-앱이란">하이브리드 앱이란?</h3>
<p><strong>네이티브 앱 + 웹앱</strong>
웹 기술로 개발하여 네이티브 앱의 웹뷰를 통해 실행
<span style="color:#808080">※ 웹뷰: 네이티브 애플리케이션에서 웹 페이지를 표시하기 위해 사용되는 컴포넌트</span></p>
</blockquote>
<h4 id="장점-2">장점</h4>
<ul>
<li>하나의 코드베이스로 여러 플랫폼에서 운영 가능</li>
<li>카메라, GPS 등 네이티브 앱의 기능적 이점을 모두 활용 가능</li>
<li>앱 스토어를 통하지 않아도 웹 코드를 수정함으로써 변경 가능</li>
</ul>
<h4 id="단점-2">단점</h4>
<ul>
<li>순수 네이티브 앱에 비해 렌더링 성능이 떨어짐</li>
</ul>
<hr/>

<h3 id="네이티브-앱-개발">네이티브 앱 개발</h3>
<p><code>React-Native</code> 는 각 플랫폼의 네이티브 코드와 직접적인 상호작용 가능
안드로이드나 IOS 각각의 네이티브 UI 컴포넌트를 사용해서 구축
<strong>즉, 같은 코드를 사용해도 결과가 플랫폼에따라 다르게 나옴</strong>
일관성있는 UX 설계에 도움</p>
<img src="https://velog.velcdn.com/images/jamee_/post/9bbd9dd7-edad-4252-a260-c12a1ad83137/image.png" width="50%"/>

<h3 id="왜-순수-네이티브-앱에-비해-느릴까">왜 순수 네이티브 앱에 비해 느릴까?</h3>
<p><code>React-Native</code> 의 동작 매커니즘을 살펴보자:</p>
<ol>
<li>Javascript 로 작성된 코드는 하나의 큰 번들 파일로 번들링된다.</li>
<li>이 번들 파일은 애플리케이션이 실행될 때 Javascript Thread 에서 처리됨.</li>
<li>Bridge 가 Javascript 와 네이티브 코드간의 상호작용을 중재하며, Javascript 에서 발생한 명령을 네이티브 스레드로 전달하여 실제 네이티브 코드의 실행을 가능하게 함. 또한, 네이티브 코드가 실행한 결과를 Javascript 코드로 다시 전달한다.</li>
</ol>
<img src="https://velog.velcdn.com/images/jamee_/post/578e5942-a248-4c9d-8b2f-65998c412acb/image.png"/>


<p>위 과정에서 진행되는 통신 지연은 순수 네이티브 앱 보다 성능 저하가 발생</p>
<hr/>

<h3 id="react-native-cli">React Native CLI</h3>
<p><code>React Native</code> 프로젝트를 생성, 관리, 빌드하고 디버깅하는데 사용되는 표준 도구</p>
<ul>
<li>자유도가 높아 React Native 를 100% 사용 가능</li>
<li>직접 네이티브 설정에 접근하고 수정할 수 있어서 복잡한 네이티브 기능이나 타사 네이티브 모듈을 쉽게 통합 가능</li>
<li>다양한 라이브러리 사용 가능</li>
<li>앱의 빌드 설정 및 종속성 등을 자세히 조정 가능</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next-auth]]]></title>
            <link>https://velog.io/@jamee_/Next-auth-y86n0bg0</link>
            <guid>https://velog.io/@jamee_/Next-auth-y86n0bg0</guid>
            <pubDate>Wed, 18 Sep 2024 04:31:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="🔒-next-auth">🔒 Next Auth</h3>
</blockquote>
<p>Next.js 애플리케이션에서 간단하고 확장 가능한 사용자 인증을 구현하기 위한 라이브러리
여러 인증 공급자와 세션 기반 및 JWT 기반의 인증을 지원</p>
<h3 id="jwt-와-session-방식의-차이">JWT 와 Session 방식의 차이</h3>
<p><strong>JWT</strong>: 클라이언트측에서 사용자 인증에 필요한 정보를 암호화 한 웹 토큰 저장</p>
<p><strong>Session</strong>: 서버측에 사용자의 상태 정보를 저장하고 관리. 클라이언트는 세션 ID 만 가지고 있음 </p>
<p>Prisma 를 사용하고 있다면 Prisma adapter 사용하여 손쉽게 연결 가능
Next-auth 회원가입 시 유저정보를 prisma 로 supabase에 저장
<a href="https://authjs.dev/getting-started/adapters/prisma">https://authjs.dev/getting-started/adapters/prisma</a></p>
<hr/>

<p><strong>공용 인증 시작 전</strong>
중복된 계정으로 로그인하는경우 예외처리해주어야함
(e.g.) Gmail: <a href="mailto:test@gmail.com">test@gmail.com</a> / Kakao: <a href="mailto:test@gmail.com">test@gmail.com</a>
allowDangerousEmailAccountLinking: true 추가해주기</p>
<p>32byte 의 랜덤문자열 생성하고 NEXTAUTH_SECRET 환경변수에 저장
[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes([guid]::NewGuid().ToString() + [guid]::NewGuid().ToString())).Substring(0, 32)</p>
<h3 id="구글-인증-시작하기">구글 인증 시작하기</h3>
<p><a href="https://console.cloud.google.com/apis/credentials">https://console.cloud.google.com/apis/credentials</a></p>
<ol>
<li>OAuth 동의 화면탭 설정</li>
<li>사용자 인증 정보탭에서 OAuth 클라이언트 ID 설정</li>
<li>설정된 GOOGLE_CLIENT_ID 와 GOOGLE_CLIENT_SECRET 환경변수에 저장</li>
</ol>
<h3 id="네이버-인증-시작하기">네이버 인증 시작하기</h3>
<p><a href="https://developers.naver.com/main/">https://developers.naver.com/main/</a></p>
<ol>
<li>네이버 로그인 배너 클릭 &gt; 오픈 API 이용 신청 &gt; 서비스 URL 에 호스트 추가</li>
<li>Callback URL 에 ~/api/auth/callback/naver 추가</li>
<li>클라이언트 아이디 및 시크릿 키 NAVER_CLIENT_ID, NAVER_CLIENT_SECRET 환경변수에 저장</li>
</ol>
<h3 id="카카오-인증-시작하기">카카오 인증 시작하기</h3>
<p><a href="https://developers.kakao.com/">https://developers.kakao.com/</a>
※ Prisma Schema 의 Account 모델에 refresh_token_expires_in 필드를 옵셔널하게 추가해 주어야함</p>
<ol>
<li>내 애플리케이션 &gt; 앱 설정 &gt; 앱 키 REST API 키를 KAKAO_CLIENT_ID 환경 변수에 저장</li>
<li>내 애플리케이션 &gt; 제품 설정 &gt; 카카오 로그인 &gt; 보안 발급받고 코드를 KAKAO_CLIENT_SECRET 환경 변수에 저장하고 카카오 로그인탭 들어가서 활성화 및 redirect url ~/api/auth/callback/kakao 설정</li>
<li>동의항목 탭에서 개인정보 수집할 정보들 설정 (email 은 받지 못해서 prisma schema email 부분 optional 로 줌)</li>
</ol>
<p><code>api/auth/[...nextauth]/route.ts 전체 코드</code></p>
<pre><code class="language-javascript">export const authOption: NextAuthOptions = {
  session: {
    strategy: &quot;jwt&quot; as const,
    maxAge: 60 * 60 * 24,
    updateAge: 60 * 60 * 2,
  },
  adapter: PrismaAdapter(prisma) as any,
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID || &quot;&quot;,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET || &quot;&quot;,
    }),
    NaverProvider({
      clientId: process.env.NAVER_CLIENT_ID || &quot;&quot;,
      clientSecret: process.env.NAVER_CLIENT_SECRET || &quot;&quot;,
    }),
    KakaoProvider({
      clientId: process.env.KAKAO_CLIENT_ID || &quot;&quot;,
      clientSecret: process.env.KAKAO_CLIENT_SECRET || &quot;&quot;,
    }),
  ],
  ],
  pages: {
    signIn: &quot;/users/signin&quot;,
  },
  callbacks: {
    // useSession 의 data 에서 id 도 불러와지도록 설정
    session: ({ session, token }) =&gt; ({
      ...session,
      user: {
        ...session.user,
        id: token.sub,
      },
    }),
    jwt: async ({ user, token }) =&gt; {
      if (user) {
        token.sub = user.id;
      }
      return token;
    },
  },
};
const handler = NextAuth(authOption);

export { handler as GET, handler as POST };

// signInPage.tsx
signIn(&quot;google 혹은 naver 혹은 kakao&quot;, { callbackUrl: &quot;/&quot; });</code></pre>
<hr/>

<h3 id="로그인-유무-체크하여-url-접속-허용">로그인 유무 체크하여 url 접속 허용</h3>
<pre><code class="language-javascript">//middleware.ts

export { default } from &quot;next-auth/middleware&quot;;

// 로그인 정보 없이 /mypage 로 들어온 경우 로그인 페이지로 리다이렉트
export const config = { matcher: [&quot;/mypage&quot;] };</code></pre>
]]></description>
        </item>
    </channel>
</rss>