<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Mishka's 블로그</title>
    <link>https://mishka.tistory.com/</link>
    <description>Mishka 의 개발 끄적끄적</description>
    <language>ko</language>
    <pubDate>Sun, 28 Jun 2026 04:44:19 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Mishka</managingEditor>
    <image>
      <title>Mishka's 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/4716582/attach/9db1f643c2664b659672b6dc724f4ece</url>
      <link>https://mishka.tistory.com</link>
    </image>
    <item>
      <title>AI 코딩 에이전트 비용 관리: Copilot&amp;middot;Claude Code&amp;middot;Codex를 팀에서 쓸 때 정해야 할 기준</title>
      <link>https://mishka.tistory.com/58</link>
      <description>&lt;p&gt;AI 코딩 도구는 이제 자동완성 수준을 넘어섰습니다. Copilot Agent Mode, Claude Code, Codex 같은 도구는 저장소를 읽고, 변경 계획을 세우고, 여러 파일을 수정하고, 테스트를 실행하고, 때로는 pull request까지 만드는 방식으로 발전하고 있습니다. 개인이 쓰면 “생산성이 좋아졌다”로 끝날 수 있지만, 팀에서 쓰기 시작하면 이야기가 달라집니다.&lt;/p&gt;
&lt;p&gt;팀 도입의 핵심 질문은 단순히 “어떤 도구가 코드를 더 잘 짜나”가 아닙니다. 비용은 어떻게 통제할지, 어떤 저장소에 접근하게 할지, 생성된 코드를 누가 검토할지, 보안상 허용되는 작업 범위는 어디까지인지 정해야 합니다. 특히 일부 도구는 고급 모델이나 에이전트 기능을 사용할 때 premium request, usage quota, seat license 같은 비용 구조가 붙기 때문에 관리 기준이 필요합니다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 AI 코딩 에이전트를 팀에서 쓸 때 비용과 보안을 함께 관리하는 방법을 정리해보겠습니다. 특정 도구 하나를 추천하기보다, Copilot·Claude Code·Codex 계열 도구를 비교할 때 공통으로 봐야 할 실무 기준에 초점을 맞춥니다.&lt;/p&gt;
&lt;h2&gt;AI 코딩 에이전트의 비용은 어디서 발생하나&lt;/h2&gt;
&lt;p&gt;AI 코딩 도구 비용은 예전처럼 “사용자당 월 구독료”만 보면 부족합니다. 최근 도구들은 기본 채팅, 자동완성, 에이전트 작업, 고급 모델 호출, 코드 리뷰, CLI 사용량을 서로 다른 단위로 계산하는 경우가 많습니다.&lt;/p&gt;
&lt;p&gt;팀에서 확인해야 할 비용 항목은 크게 네 가지입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;비용 항목&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;관리 포인트&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Seat 비용&lt;/td&gt;
&lt;td&gt;사용자 단위 구독료&lt;/td&gt;
&lt;td&gt;실제로 자주 쓰는 개발자부터 배정합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Premium request&lt;/td&gt;
&lt;td&gt;고급 모델·에이전트 기능 사용량&lt;/td&gt;
&lt;td&gt;팀별 사용량과 남은 quota를 추적합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/자동화 사용량&lt;/td&gt;
&lt;td&gt;PR 리뷰, 테스트 생성, 에이전트 실행&lt;/td&gt;
&lt;td&gt;자동 실행 범위를 제한합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;간접 비용&lt;/td&gt;
&lt;td&gt;잘못된 코드 수정, 리뷰 시간, 보안 검토&lt;/td&gt;
&lt;td&gt;품질 기준과 리뷰 절차를 정합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;특히 에이전트 모드는 한 번의 질문으로 여러 번 모델을 호출할 수 있습니다. 단순한 코드 설명 요청보다 저장소 탐색, 수정, 테스트, 재시도 과정에서 사용량이 더 커질 수 있습니다. 그래서 “개발자가 많이 쓰면 좋다”는 방향만으로는 비용을 예측하기 어렵습니다.&lt;/p&gt;
&lt;p&gt;초기에는 모든 개발자에게 동일하게 열어주기보다, 파일럿 팀을 정해 실제 사용 패턴을 보는 것이 좋습니다. 예를 들어 프론트엔드 버그 수정, 테스트 보강, 문서 업데이트, 마이그레이션 작업처럼 반복성이 높은 작업부터 측정하면 비용 대비 효과를 더 현실적으로 볼 수 있습니다.&lt;/p&gt;
&lt;h2&gt;도입 전에 정해야 할 권한 범위&lt;/h2&gt;
&lt;p&gt;AI 코딩 에이전트는 저장소를 읽고 파일을 수정할 수 있습니다. 일부 환경에서는 터미널 명령을 실행하거나, 이슈와 PR을 읽고, 브랜치를 만들고, 외부 도구와 연결될 수도 있습니다. 편리하지만 권한을 넓게 열면 위험도 함께 커집니다.&lt;/p&gt;
&lt;p&gt;팀에서 최소한 다음 기준은 정해야 합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;어떤 저장소에서 사용할 수 있는가&lt;/li&gt;
&lt;li&gt;production secret이나 고객 데이터가 포함된 저장소는 제외할 것인가&lt;/li&gt;
&lt;li&gt;에이전트가 실행할 수 있는 명령어 범위는 어디까지인가&lt;/li&gt;
&lt;li&gt;package install, migration, deployment 명령은 허용할 것인가&lt;/li&gt;
&lt;li&gt;외부 MCP 서버나 플러그인을 연결할 때 승인 절차가 있는가&lt;/li&gt;
&lt;li&gt;생성된 PR은 반드시 사람이 리뷰하는가&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;좋은 기본값은 “읽기와 수정은 허용하되, 배포와 민감한 외부 시스템 접근은 막는 것”입니다. 예를 들어 테스트 실행, lint 실행, 문서 수정은 허용할 수 있지만, production DB migration이나 cloud credential이 필요한 명령은 에이전트가 직접 실행하지 못하게 해야 합니다.&lt;/p&gt;
&lt;p&gt;권한은 도구 설정만으로 끝나지 않습니다. repository policy, branch protection, required review, secret scanning, CI 권한과 함께 봐야 합니다. AI 에이전트가 코드를 만들더라도 main branch에 바로 들어가지 못하게 하는 기본적인 Git 운영 규칙은 여전히 중요합니다.&lt;/p&gt;
&lt;h2&gt;비용 대비 효과가 큰 작업부터 맡기기&lt;/h2&gt;
&lt;p&gt;AI 코딩 에이전트를 도입하면 처음에는 무엇이든 맡겨보고 싶어집니다. 하지만 팀 비용을 관리하려면 작업 유형별로 효과를 나눠보는 편이 좋습니다.&lt;/p&gt;
&lt;p&gt;비용 대비 효과가 좋은 작업은 대체로 다음 특징을 가집니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;변경 범위가 작고 명확합니다.&lt;/li&gt;
&lt;li&gt;테스트나 lint로 결과를 검증할 수 있습니다.&lt;/li&gt;
&lt;li&gt;기존 패턴을 따라 반복 구현하면 됩니다.&lt;/li&gt;
&lt;li&gt;실패해도 되돌리기 쉽습니다.&lt;/li&gt;
&lt;li&gt;리뷰어가 빠르게 판단할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예를 들어 다음 작업은 파일럿에 적합합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;작업 유형&lt;/th&gt;
&lt;th&gt;적합한 이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;단위 테스트 추가&lt;/td&gt;
&lt;td&gt;기대 동작이 명확하고 검증이 쉽습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deprecated API 교체&lt;/td&gt;
&lt;td&gt;반복 패턴이 많아 자동화 효과가 큽니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문서와 예제 코드 업데이트&lt;/td&gt;
&lt;td&gt;위험도가 낮고 리뷰가 쉽습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;작은 버그 수정&lt;/td&gt;
&lt;td&gt;재현 테스트와 함께 맡기면 품질 확인이 가능합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;타입 오류 수정&lt;/td&gt;
&lt;td&gt;컴파일러가 결과를 검증해줍니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;반대로 다음 작업은 초기에 조심하는 것이 좋습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;결제, 인증, 권한처럼 보안 영향이 큰 변경&lt;/li&gt;
&lt;li&gt;데이터베이스 스키마와 migration이 얽힌 변경&lt;/li&gt;
&lt;li&gt;장애 대응 중 production 환경을 직접 건드리는 작업&lt;/li&gt;
&lt;li&gt;요구사항이 모호한 대규모 리팩터링&lt;/li&gt;
&lt;li&gt;성능 개선처럼 측정 기준이 없는 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 에이전트는 빠르게 코드를 만들 수 있지만, 비즈니스 맥락을 완전히 이해하는 것은 아닙니다. 따라서 “작고 검증 가능한 작업”에서 시작해 성공 패턴을 쌓는 것이 비용과 품질 모두에 유리합니다.&lt;/p&gt;
&lt;h2&gt;팀 단위 사용량을 관리하는 방법&lt;/h2&gt;
&lt;p&gt;AI 코딩 도구의 사용량은 개인별로 흩어지기 쉽습니다. 어떤 개발자는 자동완성 위주로 쓰고, 어떤 개발자는 에이전트 모드로 큰 작업을 맡깁니다. 팀 비용을 관리하려면 사용량을 개인 통제가 아니라 작업 흐름 관리로 봐야 합니다.&lt;/p&gt;
&lt;p&gt;실무적으로는 다음 지표를 보는 것이 좋습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;월별 seat 활성 사용자 수&lt;/li&gt;
&lt;li&gt;premium request 사용량과 초과 가능성&lt;/li&gt;
&lt;li&gt;에이전트가 만든 PR 수&lt;/li&gt;
&lt;li&gt;에이전트 PR의 평균 리뷰 시간&lt;/li&gt;
&lt;li&gt;에이전트 PR의 재작업률&lt;/li&gt;
&lt;li&gt;테스트 통과율과 revert 비율&lt;/li&gt;
&lt;li&gt;문서·테스트·버그 수정 등 작업 유형별 사용 비중&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;단순히 “사용량이 많다”는 이유만으로 나쁘다고 볼 수는 없습니다. 사용량이 많아도 리뷰 시간이 줄고, 테스트 커버리지가 올라가고, 반복 작업이 줄었다면 좋은 투자일 수 있습니다. 반대로 사용량은 많은데 PR 품질이 낮고 리뷰어 시간이 늘어난다면 비용을 다시 봐야 합니다.&lt;/p&gt;
&lt;p&gt;초기 파일럿에서는 월말에 다음 질문을 해보면 좋습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;어떤 작업에서 가장 만족도가 높았는가?&lt;/li&gt;
&lt;li&gt;어떤 작업에서 리뷰 시간이 오히려 늘었는가?&lt;/li&gt;
&lt;li&gt;사람이 직접 했을 때보다 명확히 빨라진 작업은 무엇인가?&lt;/li&gt;
&lt;li&gt;premium request를 많이 쓰는 패턴은 무엇인가?&lt;/li&gt;
&lt;li&gt;사용을 제한해야 할 저장소나 명령어가 발견됐는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 질문에 답할 수 있어야 도구 확장 여부를 판단할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;코드 리뷰 기준을 바꾸지 말아야 한다&lt;/h2&gt;
&lt;p&gt;AI가 만든 코드라고 해서 리뷰 기준이 낮아져서는 안 됩니다. 오히려 리뷰어는 “그럴듯하지만 틀린 코드”를 더 주의해서 봐야 합니다. AI 에이전트는 기존 코드 스타일을 잘 따라 하지만, 요구사항을 잘못 해석하거나 edge case를 놓칠 수 있습니다.&lt;/p&gt;
&lt;p&gt;팀에서 정할 만한 리뷰 기준은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AI가 만든 PR도 일반 PR과 동일한 required review를 거칩니다.&lt;/li&gt;
&lt;li&gt;테스트 추가 없이 동작 변경만 있는 PR은 더 신중히 봅니다.&lt;/li&gt;
&lt;li&gt;인증, 권한, 결제, 개인정보 관련 변경은 별도 보안 리뷰를 요구합니다.&lt;/li&gt;
&lt;li&gt;dependency 추가는 라이선스와 보안 취약점 검사를 통과해야 합니다.&lt;/li&gt;
&lt;li&gt;에이전트가 실행한 명령과 테스트 결과를 PR 설명에 남깁니다.&lt;/li&gt;
&lt;li&gt;큰 변경은 여러 개의 작은 PR로 나눕니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;특히 PR 설명이 중요합니다. AI 에이전트가 어떤 파일을 왜 바꿨는지, 어떤 테스트를 실행했는지, 실행하지 못한 검증은 무엇인지 남겨야 리뷰어가 판단할 수 있습니다. “AI가 해줬다”가 아니라 “검증 가능한 변경 기록”이 있어야 합니다.&lt;/p&gt;
&lt;h2&gt;보안과 컴플라이언스 체크리스트&lt;/h2&gt;
&lt;p&gt;기업 환경에서는 AI 코딩 도구가 소스코드와 내부 문서를 다룬다는 점을 잊으면 안 됩니다. 도입 전에 보안팀과 함께 다음 항목을 확인하는 것이 좋습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;영역&lt;/th&gt;
&lt;th&gt;체크리스트&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;데이터 처리&lt;/td&gt;
&lt;td&gt;코드, prompt, 응답이 학습에 사용되는지 확인합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;접근 제어&lt;/td&gt;
&lt;td&gt;조직 계정, SSO, SCIM, seat 회수 정책을 확인합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;저장소 범위&lt;/td&gt;
&lt;td&gt;민감 저장소 제외 또는 별도 승인 절차를 둡니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret 보호&lt;/td&gt;
&lt;td&gt;secret scanning과 prompt 내 secret 노출 방지 정책을 둡니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;감사 로그&lt;/td&gt;
&lt;td&gt;누가 어떤 도구를 사용했는지 추적할 수 있어야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;외부 도구 연동&lt;/td&gt;
&lt;td&gt;MCP 서버, 플러그인, CLI 권한을 검토합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이 체크리스트는 도구를 막기 위한 것이 아니라 안전하게 쓰기 위한 기준입니다. AI 코딩 에이전트는 제대로 쓰면 개발자의 반복 작업을 크게 줄여줍니다. 하지만 조직의 코드와 시스템에 접근하는 도구인 만큼, SaaS 도입 심사와 비슷한 수준의 검토가 필요합니다.&lt;/p&gt;
&lt;h2&gt;결론: AI 코딩 에이전트는 생산성 도구이자 운영 대상이다&lt;/h2&gt;
&lt;p&gt;AI 코딩 에이전트를 팀에 도입할 때는 “개발자가 편해진다”는 장점만 보면 부족합니다. seat 비용, premium request, 저장소 접근 권한, PR 품질, 보안 정책을 함께 관리해야 합니다.&lt;/p&gt;
&lt;p&gt;가장 현실적인 시작점은 작은 파일럿입니다. 반복적이고 검증 가능한 작업을 정하고, 사용량과 PR 품질을 함께 측정합니다. 비용이 늘더라도 리뷰 시간이 줄고, 테스트가 늘고, 반복 작업이 줄어든다면 충분히 의미 있는 투자입니다. 반대로 큰 작업을 무작정 맡겨 리뷰 부담만 늘어난다면 도입 방식을 바꿔야 합니다.&lt;/p&gt;
&lt;p&gt;AI 코딩 에이전트는 개발자를 대체하는 도구라기보다, 팀의 개발 흐름에 들어오는 새로운 자동화 계층입니다. 그래서 코드 리뷰, 권한 관리, 비용 관리 기준을 먼저 세운 팀이 더 안전하게 효과를 얻을 수 있습니다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/features/copilot/plans&quot;&gt;GitHub Copilot plans and pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://code.visualstudio.com/docs/copilot/overview&quot;&gt;GitHub Copilot in VS Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/copilot/managing-copilot/managing-copilot-as-an-individual-subscriber/monitoring-usage-and-entitlements/about-premium-requests&quot;&gt;About premium requests in GitHub Copilot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/copilot/concepts/coding-agent/coding-agent&quot;&gt;About Copilot coding agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;관련 글: &lt;a href=&quot;https://mishka.kr/47&quot;&gt;AI 코딩 에이전트와 테스트 주도 개발: 테스트부터 맡기는 실무 흐름&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AICodingAgent</category>
      <category>claudecode</category>
      <category>codex</category>
      <category>githubcopilot</category>
      <category>productivity</category>
      <category>security</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/58</guid>
      <comments>https://mishka.tistory.com/58#entry58comment</comments>
      <pubDate>Wed, 10 Jun 2026 09:55:51 +0900</pubDate>
    </item>
    <item>
      <title>클라우드 AI 코딩 에이전트 비교: Codex와 Copilot 도입 전 실무 체크리스트</title>
      <link>https://mishka.tistory.com/57</link>
      <description>&lt;p&gt;AI 코딩 도구를 처음 쓸 때는 에디터 안 자동완성이 가장 먼저 눈에 들어옵니다. 한 줄을 완성해주고, 함수 초안을 만들고, 테스트 코드를 제안해주는 방식입니다. 그런데 최근의 흐름은 조금 더 멀리 가고 있습니다. 이제 AI 코딩 에이전트는 로컬 에디터 안에서만 움직이지 않습니다. 별도 클라우드 환경에서 저장소를 읽고, 이슈를 분석하고, 브랜치를 만들고, pull request까지 제안하는 방향으로 발전하고 있습니다.&lt;/p&gt;
&lt;p&gt;OpenAI Codex cloud나 GitHub Copilot cloud agent 같은 도구가 대표적입니다. 개발자는 작은 작업을 맡기고 다른 일을 할 수 있습니다. 잘 맞는 작업에서는 생산성이 확실히 올라갑니다. 하지만 바로 팀의 주요 저장소에 붙이기에는 생각할 것이 많습니다. 권한, 테스트, 비용, 리뷰 기준, 작업 쪼개기 방식이 정리되어 있지 않으면 “편한 자동화”가 아니라 “리뷰하기 어려운 대형 변경”이 될 수 있습니다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 클라우드 AI 코딩 에이전트를 실무에 도입할 때 어떤 기준으로 비교하고, 어떤 작업부터 맡기면 좋은지 정리해보겠습니다.&lt;/p&gt;
&lt;h2&gt;클라우드 AI 코딩 에이전트란 무엇인가&lt;/h2&gt;
&lt;p&gt;클라우드 AI 코딩 에이전트는 개발자의 로컬 컴퓨터가 아니라 서비스가 제공하는 격리된 실행 환경에서 코드 작업을 수행하는 AI 도구입니다. 단순 채팅이나 자동완성과 달리 저장소 컨텍스트를 읽고, 명령을 실행하고, 변경 사항을 브랜치나 PR 형태로 제안할 수 있습니다.&lt;/p&gt;
&lt;p&gt;간단히 구분하면 다음과 같습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;주된 사용 방식&lt;/th&gt;
&lt;th&gt;적합한 작업&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;에디터 자동완성&lt;/td&gt;
&lt;td&gt;개발자가 작성 중인 코드 보조&lt;/td&gt;
&lt;td&gt;함수 구현, 타입 보완, 작은 리팩터링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;로컬 CLI/IDE 에이전트&lt;/td&gt;
&lt;td&gt;현재 작업 트리에서 명령 실행&lt;/td&gt;
&lt;td&gt;테스트 수정, 파일 탐색, 빠른 반복 작업&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;클라우드 코딩 에이전트&lt;/td&gt;
&lt;td&gt;원격 환경에서 비동기 작업 수행&lt;/td&gt;
&lt;td&gt;이슈 기반 수정, PR 초안, 병렬 조사, 반복 버그 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;핵심 차이는 비동기성입니다. 로컬 에이전트는 보통 개발자가 옆에서 지켜보며 지시를 이어갑니다. 클라우드 에이전트는 작업을 맡긴 뒤 결과를 PR이나 브랜치로 받는 흐름에 가깝습니다. 그래서 도구 성능만 볼 것이 아니라 “어떤 작업을 맡길 때 리뷰 비용이 줄어드는가”를 함께 봐야 합니다.&lt;/p&gt;
&lt;h2&gt;Codex와 Copilot cloud agent를 비교할 때 볼 기준&lt;/h2&gt;
&lt;p&gt;도구 이름만 놓고 어느 쪽이 더 좋다고 판단하기는 어렵습니다. 팀의 GitHub 사용 방식, 권한 정책, 코드베이스 규모, 테스트 자동화 수준에 따라 체감이 달라집니다. 실무 비교에서는 다음 기준이 더 중요합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;비교 기준&lt;/th&gt;
&lt;th&gt;확인할 질문&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;저장소 연결 방식&lt;/td&gt;
&lt;td&gt;어떤 저장소에 접근할 수 있고, 환경 설정은 어디서 관리하는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;작업 시작 지점&lt;/td&gt;
&lt;td&gt;이슈, PR, 채팅, IDE 중 어디에서 작업을 맡길 수 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실행 환경&lt;/td&gt;
&lt;td&gt;의존성 설치, 테스트 실행, 시크릿 접근 제한을 어떻게 다루는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;변경 제출 방식&lt;/td&gt;
&lt;td&gt;브랜치, 커밋, PR, 리뷰 요청 흐름이 팀 규칙과 맞는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;권한 제어&lt;/td&gt;
&lt;td&gt;쓰기 권한, 외부 네트워크, 비밀값 접근을 제한할 수 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;리뷰 지원&lt;/td&gt;
&lt;td&gt;변경 요약, 테스트 결과, 실패 원인을 리뷰어가 확인하기 쉬운가?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Codex cloud는 클라우드 환경에서 작업을 병렬로 맡기는 흐름이 강합니다. GitHub 통합을 통해 PR 리뷰나 수정 제안에도 연결할 수 있습니다. GitHub Copilot cloud agent는 GitHub 이슈와 PR 흐름에 자연스럽게 붙는 장점이 있습니다. GitHub 안에서 이슈를 할당하고, 에이전트가 작업한 뒤 PR을 만드는 흐름이 팀 프로세스와 잘 맞을 수 있습니다.&lt;/p&gt;
&lt;p&gt;다만 여기서 중요한 것은 기능 목록이 아닙니다. 팀이 이미 어떤 방식으로 일을 쪼개고 리뷰하는지입니다. 이슈가 작고, 테스트가 명확하고, PR 리뷰 기준이 잘 정리된 팀일수록 클라우드 에이전트의 효과가 큽니다. 반대로 요구사항이 구두로만 오가고 테스트가 부족한 저장소에서는 에이전트가 만든 변경을 검증하는 비용이 커집니다.&lt;/p&gt;
&lt;h2&gt;어떤 작업을 먼저 맡기면 좋은가&lt;/h2&gt;
&lt;p&gt;클라우드 AI 코딩 에이전트를 처음 도입한다면 핵심 결제 로직이나 대규모 아키텍처 변경부터 맡기기보다, 경계가 분명한 작업부터 시작하는 편이 좋습니다.&lt;/p&gt;
&lt;p&gt;추천하는 첫 작업은 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;오래된 테스트 실패 수정&lt;/li&gt;
&lt;li&gt;문서와 코드 예제 동기화&lt;/li&gt;
&lt;li&gt;타입 오류나 린트 오류 정리&lt;/li&gt;
&lt;li&gt;작은 UI 버그 수정&lt;/li&gt;
&lt;li&gt;반복적인 마이그레이션 초안 작성&lt;/li&gt;
&lt;li&gt;에러 메시지 개선이나 검증 로직 보강&lt;/li&gt;
&lt;li&gt;기존 패턴을 따르는 CRUD 화면 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이런 작업은 성공 기준이 비교적 명확합니다. 테스트가 통과하는지, 린트가 통과하는지, 변경 파일 범위가 예상과 맞는지 확인할 수 있습니다. 에이전트가 잘못된 방향으로 가더라도 리뷰어가 빠르게 되돌릴 수 있습니다.&lt;/p&gt;
&lt;p&gt;반대로 처음부터 피하는 것이 좋은 작업도 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;인증, 결제, 권한 변경처럼 위험도가 높은 기능&lt;/li&gt;
&lt;li&gt;여러 서비스와 데이터베이스 스키마가 동시에 바뀌는 작업&lt;/li&gt;
&lt;li&gt;제품 요구사항이 아직 정리되지 않은 신규 기능&lt;/li&gt;
&lt;li&gt;성능 병목 원인이 불분명한 대규모 리팩터링&lt;/li&gt;
&lt;li&gt;운영 장애 대응처럼 즉시성과 책임 소재가 중요한 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 에이전트는 명확한 문제를 빠르게 처리할 때 강합니다. 불명확한 제품 판단, 보안 책임, 운영 의사결정까지 한 번에 맡기면 결과를 검증하기 어려워집니다.&lt;/p&gt;
&lt;h2&gt;권한과 보안은 도입 전에 정해야 한다&lt;/h2&gt;
&lt;p&gt;클라우드 에이전트는 저장소 밖의 환경에서 코드를 다룹니다. 그래서 보안 기준을 나중에 붙이면 늦습니다. 특히 private repository, 사내 패키지, 배포 스크립트, 테스트용 시크릿이 얽혀 있다면 먼저 권한 경계를 정해야 합니다.&lt;/p&gt;
&lt;p&gt;실무 체크리스트는 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;에이전트가 접근 가능한 저장소를 최소한으로 제한했는가?&lt;/li&gt;
&lt;li&gt;기본 브랜치에 직접 push하지 못하게 했는가?&lt;/li&gt;
&lt;li&gt;모든 변경은 PR을 거치도록 했는가?&lt;/li&gt;
&lt;li&gt;운영 시크릿, 배포 토큰, 고객 데이터에 접근하지 못하게 했는가?&lt;/li&gt;
&lt;li&gt;외부 네트워크 접근이 필요한 경우 목적과 범위를 정했는가?&lt;/li&gt;
&lt;li&gt;에이전트가 생성한 커밋과 사람의 커밋을 구분할 수 있는가?&lt;/li&gt;
&lt;li&gt;누가 어떤 작업을 맡겼고 어떤 결과가 나왔는지 감사 가능하게 남는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;특히 시크릿 접근은 보수적으로 봐야 합니다. 테스트를 통과시키기 위해 운영 키를 노출하는 식의 환경은 피해야 합니다. 가능하면 에이전트 전용 테스트 계정, 제한된 토큰, mock 서비스, 별도 sandbox 데이터를 준비하는 편이 안전합니다.&lt;/p&gt;
&lt;p&gt;또 하나 중요한 것은 branch protection입니다. 클라우드 에이전트가 PR을 만들 수 있더라도, 사람 리뷰와 필수 테스트를 통과하지 않으면 merge되지 않도록 해야 합니다. 에이전트가 빠르게 코드를 만들수록 merge 기준은 더 명확해야 합니다.&lt;/p&gt;
&lt;h2&gt;좋은 작업 지시문은 작은 PR을 만든다&lt;/h2&gt;
&lt;p&gt;클라우드 에이전트의 결과물 품질은 모델 성능뿐 아니라 작업 지시문에 크게 좌우됩니다. “관리자 페이지 개선해줘”처럼 넓은 요청은 결과가 커지고 리뷰가 어려워집니다. 반대로 성공 기준과 변경 범위를 좁히면 PR이 작아집니다.&lt;/p&gt;
&lt;p&gt;좋은 지시문에는 보통 다음 요소가 들어갑니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;요소&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;문제&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/settings/billing&lt;/code&gt; 페이지에서 플랜명이 비어 보이는 버그 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;변경 범위&lt;/td&gt;
&lt;td&gt;프론트엔드 표시 로직과 관련 테스트만 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;제외 범위&lt;/td&gt;
&lt;td&gt;API 응답 스키마와 결제 로직은 변경하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검증 방법&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pnpm test billing&lt;/code&gt;과 &lt;code&gt;pnpm lint&lt;/code&gt; 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;결과 형식&lt;/td&gt;
&lt;td&gt;변경 요약과 테스트 결과를 PR 설명에 작성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;이 정도만 있어도 에이전트가 임의로 설계를 확장할 가능성이 줄어듭니다. 특히 제외 범위를 쓰는 것이 중요합니다. AI 에이전트는 문제를 해결하려고 하다가 주변 코드를 함께 바꾸는 경우가 있습니다. 변경하지 말아야 할 파일, 건드리면 안 되는 API, 유지해야 할 호환성을 명확히 적어야 합니다.&lt;/p&gt;
&lt;p&gt;팀 단위로는 저장소에 공통 작업 규칙을 문서화하는 것도 좋습니다. 테스트 명령, 코드 스타일, PR 설명 형식, 금지된 작업, 민감 파일 목록을 정리해두면 매번 같은 설명을 반복하지 않아도 됩니다.&lt;/p&gt;
&lt;h2&gt;비용과 생산성은 리뷰 시간까지 포함해서 봐야 한다&lt;/h2&gt;
&lt;p&gt;AI 코딩 에이전트의 비용을 볼 때 구독료나 사용량만 보면 부족합니다. 실제 비용은 리뷰 시간까지 포함해야 합니다. 에이전트가 30분 만에 PR을 만들었지만 리뷰어가 2시간 동안 의도를 파악해야 한다면 생산성이 오른 것인지 애매합니다.&lt;/p&gt;
&lt;p&gt;실무에서는 다음 지표를 같이 보면 좋습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;에이전트에게 맡긴 작업 수&lt;/li&gt;
&lt;li&gt;PR당 변경 파일 수와 라인 수&lt;/li&gt;
&lt;li&gt;첫 리뷰까지 걸린 시간&lt;/li&gt;
&lt;li&gt;테스트 통과율&lt;/li&gt;
&lt;li&gt;사람 리뷰 후 수정 라운드 수&lt;/li&gt;
&lt;li&gt;merge된 PR 비율&lt;/li&gt;
&lt;li&gt;revert 또는 후속 버그 발생 비율&lt;/li&gt;
&lt;li&gt;사람이 직접 했을 때와 비교한 전체 리드타임&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;초기에는 “얼마나 많은 코드를 만들었나”보다 “얼마나 리뷰 가능한 작은 변경을 만들었나”가 중요합니다. 코드 생성량이 많아도 PR이 커지고 실패율이 높으면 팀 속도는 오히려 떨어질 수 있습니다.&lt;/p&gt;
&lt;p&gt;도입 첫 달에는 에이전트 사용 범위를 일부 저장소와 일부 작업 유형으로 제한하고, 위 지표를 간단히 기록해보는 편이 좋습니다. 그 다음 잘 맞는 작업을 늘리고, 실패가 많은 작업 유형은 지시문이나 테스트 환경을 보완합니다.&lt;/p&gt;
&lt;h2&gt;도입 전 체크리스트&lt;/h2&gt;
&lt;p&gt;클라우드 AI 코딩 에이전트를 팀에 붙이기 전에 다음 질문에 답할 수 있어야 합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;어떤 저장소에서 먼저 실험할 것인가?&lt;/li&gt;
&lt;li&gt;에이전트가 맡을 수 있는 작업과 맡기지 않을 작업이 구분되어 있는가?&lt;/li&gt;
&lt;li&gt;PR 없이는 기본 브랜치에 반영되지 않는가?&lt;/li&gt;
&lt;li&gt;테스트, 린트, 타입 체크가 CI에서 강제되는가?&lt;/li&gt;
&lt;li&gt;시크릿과 운영 데이터 접근이 제한되어 있는가?&lt;/li&gt;
&lt;li&gt;작업 지시문 템플릿이 있는가?&lt;/li&gt;
&lt;li&gt;에이전트가 만든 PR을 누가 리뷰할지 정해져 있는가?&lt;/li&gt;
&lt;li&gt;실패한 작업을 되돌리는 기준이 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 체크리스트가 비어 있다면 도구를 바꾸는 것보다 프로세스를 먼저 정리하는 편이 효과적입니다. AI 에이전트는 기존 개발 프로세스를 대체한다기보다, 잘게 나뉜 작업과 자동화된 검증 위에서 더 잘 작동합니다.&lt;/p&gt;
&lt;h2&gt;결론: 도구 비교보다 작업 경계가 먼저다&lt;/h2&gt;
&lt;p&gt;Codex나 GitHub Copilot cloud agent 같은 클라우드 AI 코딩 에이전트는 개발 워크플로를 바꿀 수 있는 도구입니다. 특히 작은 버그 수정, 테스트 보강, 문서 정리, 반복 마이그레이션처럼 범위가 명확한 작업에서는 팀의 대기 시간을 줄이는 데 도움이 됩니다.&lt;/p&gt;
&lt;p&gt;하지만 도입 기준은 “어느 모델이 더 똑똑한가”에서 끝나면 안 됩니다. 저장소 권한, 실행 환경, 테스트 자동화, PR 리뷰 방식, 비용 측정이 함께 준비되어야 합니다. AI 에이전트에게 일을 맡긴다는 것은 코드를 대신 쓰게 하는 것뿐 아니라, 팀의 작업 단위를 더 명확하게 만드는 일이기도 합니다.&lt;/p&gt;
&lt;p&gt;처음에는 작은 저장소, 작은 이슈, 작은 PR부터 시작해보는 것이 좋습니다. 그리고 에이전트가 만든 결과를 사람이 리뷰하기 쉬운 구조로 유지해야 합니다. 클라우드 AI 코딩 에이전트의 실무 가치는 자동으로 많은 코드를 만드는 데 있지 않습니다. 사람이 검증 가능한 변경을 빠르게 제안하게 만드는 데 있습니다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.openai.com/codex/cloud&quot;&gt;Codex web - OpenAI Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.openai.com/codex/integrations/github&quot;&gt;Codex code review in GitHub - OpenAI Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/copilot/concepts/agents/coding-agent/about-coding-agent&quot;&gt;About GitHub Copilot cloud agent - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-on-github&quot;&gt;Using Copilot cloud agent on GitHub - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>aiagent</category>
      <category>codex</category>
      <category>DeveloperTools</category>
      <category>GihubCopilot</category>
      <category>security</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/57</guid>
      <comments>https://mishka.tistory.com/57#entry57comment</comments>
      <pubDate>Wed, 10 Jun 2026 09:48:23 +0900</pubDate>
    </item>
    <item>
      <title>React Compiler 도입 전 체크리스트: useMemo와 useCallback을 지우기 전에 볼 것들</title>
      <link>https://mishka.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React 프로젝트에서 성능 최적화를 하다 보면 useMemo, useCallback, React.memo가 자연스럽게 늘어납니다. 처음에는 느린 계산을 줄이기 위한 선택이었는데, 어느 순간부터는 &amp;ldquo;혹시 모르니 감싸두자&amp;rdquo;에 가까운 코드가 됩니다. 리뷰에서도 실제 병목보다 dependency array가 맞는지, 콜백 참조가 바뀌는지 같은 이야기로 시간이 많이 쓰입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Compiler는 이 흐름을 바꿀 수 있는 도구입니다. React가 컴포넌트와 훅의 코드를 분석해 안전하다고 판단되는 부분을 자동으로 메모이제이션해 주기 때문입니다. 공식 문서에서도 React Compiler는 수동 useMemo, useCallback, React.memo 사용을 줄이는 방향의 자동 최적화 도구로 설명됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 바로 &amp;ldquo;이제 useMemo는 전부 지워도 된다&amp;rdquo;로 가면 위험합니다. Compiler는 마법처럼 모든 성능 문제를 해결하는 기능이 아니라, React 규칙을 잘 지키는 코드에서 더 좋은 기본값을 만들어 주는 도구에 가깝습니다. 이번 글에서는 React Compiler를 도입하기 전에 어떤 기준으로 코드를 살펴보고, 기존 메모이제이션을 어떤 순서로 정리하면 좋은지 정리해보겠습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;React Compiler가 해결하려는 문제&quot; data-ke-size=&quot;size26&quot;&gt;React Compiler가 해결하려는 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 렌더링 성능 문제는 보통 세 가지 형태로 나타납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 부모 컴포넌트가 렌더링될 때 자식 컴포넌트도 불필요하게 다시 렌더링됩니다. 둘째, 렌더링 중 비싼 계산이 반복됩니다. 셋째, 객체나 함수 참조가 매번 바뀌어서 memo로 감싼 컴포넌트도 계속 다시 렌더링됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 기존에는 다음과 같은 방식으로 대응했습니다.&lt;/p&gt;
&lt;pre class=&quot;moonscript&quot;&gt;&lt;code&gt;const filteredItems = useMemo(() =&amp;gt; {
  return items.filter((item) =&amp;gt; item.enabled);
}, [items]);

const handleSelect = useCallback((id: string) =&amp;gt; {
  setSelectedId(id);
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 명시적입니다. 개발자가 어떤 값을 기억할지 직접 정합니다. 문제는 코드가 커질수록 &amp;ldquo;정말 필요한 메모이제이션&amp;rdquo;과 &amp;ldquo;습관적으로 붙인 메모이제이션&amp;rdquo;이 섞인다는 점입니다. dependency array를 잘못 쓰면 오래된 값을 보거나, 반대로 필요 없는 의존성을 넣어 최적화 효과가 사라질 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Compiler는 이 부담을 줄이기 위해 만들어졌습니다. 컴파일 단계에서 컴포넌트와 훅의 순수성을 분석하고, 안전한 경우 React가 자동으로 재계산과 재렌더링을 줄입니다. 즉 핵심은 단순히 훅 하나를 대체하는 것이 아니라, React 코드가 예측 가능하게 작성되어 있을 때 최적화를 자동화하는 것입니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;도입 전에 먼저 확인할 코드 조건&quot; data-ke-size=&quot;size26&quot;&gt;도입 전에 먼저 확인할 코드 조건&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Compiler를 적용하기 전에 가장 먼저 볼 것은 번들 설정이 아니라 코드의 형태입니다. Compiler는 React의 규칙을 전제로 동작합니다. 컴포넌트와 훅이 순수하게 작성되어 있고, 렌더링 중 외부 상태를 임의로 바꾸지 않으며, 훅 호출 순서가 안정적이어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 패턴이 많다면 먼저 정리하는 편이 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;렌더링 중 전역 변수나 외부 객체를 수정한다.&lt;/li&gt;
&lt;li&gt;조건문 안에서 훅을 호출한다.&lt;/li&gt;
&lt;li&gt;컴포넌트 렌더링 중 네트워크 요청이나 저장소 쓰기 같은 부수 효과를 실행한다.&lt;/li&gt;
&lt;li&gt;props로 받은 객체를 직접 변경한다.&lt;/li&gt;
&lt;li&gt;dependency array 경고를 무시하기 위해 ESLint 규칙을 끈다.&lt;/li&gt;
&lt;li&gt;커스텀 훅 안에서 값의 소유권이 불분명하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 아래 코드는 렌더링 중 외부 배열을 수정합니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;const logs: string[] = [];

function UserCard({ user }: { user: User }) {
  logs.push(user.id);

  return &amp;lt;div&amp;gt;{user.name}&amp;lt;/div&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 코드는 Compiler 이전에도 좋지 않습니다. React 컴포넌트는 같은 입력에 대해 같은 UI를 반환해야 이해하기 쉽습니다. 렌더링 과정에서 외부 상태를 바꾸면 Strict Mode, 동시성 렌더링, 캐시, 자동 최적화와 모두 충돌할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도입 전 체크리스트를 짧게 만들면 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;점검 항목 확인할 질문&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;순수성&lt;/td&gt;
&lt;td&gt;렌더링 중 외부 상태를 변경하지 않는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;훅 규칙&lt;/td&gt;
&lt;td&gt;훅 호출 순서가 항상 같은가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;부수 효과&lt;/td&gt;
&lt;td&gt;네트워크 요청, 구독, DOM 조작이 useEffect 등으로 분리되어 있는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;불변성&lt;/td&gt;
&lt;td&gt;props와 state를 직접 수정하지 않는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;린트&lt;/td&gt;
&lt;td&gt;React Hooks 관련 경고를 무시하지 않는가?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compiler를 성능 도구로만 보면 이 단계가 귀찮아 보입니다. 하지만 실제로는 이 정리만 해도 코드 품질이 좋아집니다. 자동 최적화는 그 다음에 얻는 보너스에 가깝습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;useMemo와 useCallback을 바로 삭제하지 않는다&quot; data-ke-size=&quot;size26&quot;&gt;useMemo와 useCallback을 바로 삭제하지 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Compiler를 켰다고 해서 기존 useMemo와 useCallback을 한 번에 제거할 필요는 없습니다. 특히 실무 프로젝트에서는 성능 문제가 발생한 이유를 기록 없이 지워버리면 나중에 같은 문제를 다시 추적해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 메모이제이션을 세 가지로 나누어 보는 것이 좋습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;실제로 비싼 계산을 줄이기 위한 메모이제이션&lt;/li&gt;
&lt;li&gt;자식 컴포넌트의 불필요한 렌더링을 줄이기 위한 참조 안정화&lt;/li&gt;
&lt;li&gt;습관적으로 붙였지만 효과가 불분명한 메모이제이션&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째는 신중하게 다뤄야 합니다. 예를 들어 수천 개 항목을 정렬하거나, 큰 데이터를 그룹핑하는 계산은 여전히 명시적인 구조가 도움이 될 수 있습니다. Compiler가 최적화를 해주더라도, 해당 계산이 정말 병목인지 측정하고 남길지 결정하는 편이 안전합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 Compiler 도입 후 줄어들 가능성이 큽니다. 단지 React.memo 자식에게 넘기기 위해 모든 콜백을 useCallback으로 감싸는 패턴은 점차 줄일 수 있습니다. 다만 삭제 전후로 React DevTools Profiler나 실제 사용자 지표를 확인하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째는 정리 후보입니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const title = useMemo(() =&amp;gt; post.title, [post.title]);

const onClick = useCallback(() =&amp;gt; {
  console.log('clicked');
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 코드는 최적화보다 코드 소음에 가깝습니다. Compiler가 없더라도 제거해도 되는 경우가 많습니다. 하지만 한꺼번에 대량 삭제하기보다는 화면 단위, 기능 단위로 나누어 변경하는 편이 리뷰와 롤백이 쉽습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;마이그레이션 순서: 설정보다 측정이 먼저다&quot; data-ke-size=&quot;size26&quot;&gt;마이그레이션 순서: 설정보다 측정이 먼저다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Compiler 도입을 프로젝트 일정에 넣는다면 다음 순서가 현실적입니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;1. 성능 기준을 먼저 정한다&quot; data-ke-size=&quot;size23&quot;&gt;1. 성능 기준을 먼저 정한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compiler를 켜기 전 현재 상태를 기록합니다. 모든 페이지를 완벽하게 측정할 필요는 없지만, 핵심 화면 몇 개는 기준을 잡아두는 것이 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목록 페이지에서 필터 변경 시 렌더링 횟수&lt;/li&gt;
&lt;li&gt;입력 폼에서 타이핑 지연 여부&lt;/li&gt;
&lt;li&gt;대시보드 첫 렌더링 시간&lt;/li&gt;
&lt;li&gt;큰 테이블 정렬이나 검색 시 반응 속도&lt;/li&gt;
&lt;li&gt;React Profiler에서 자주 렌더링되는 컴포넌트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기준이 없으면 Compiler 적용 후 좋아졌는지, 나빠졌는지, 그대로인지 말하기 어렵습니다. &amp;ldquo;빌드는 성공했다&amp;rdquo;와 &amp;ldquo;실제 성능이 안정적이다&amp;rdquo;는 다른 이야기입니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;2. 린트와 React 규칙 위반을 먼저 줄인다&quot; data-ke-size=&quot;size23&quot;&gt;2. 린트와 React 규칙 위반을 먼저 줄인다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compiler가 잘 동작하려면 코드가 React의 기대에 맞아야 합니다. 특히 Hooks 규칙, exhaustive deps, 불변성 관련 경고를 무시하고 있다면 먼저 정리합니다. 이 과정에서 오래된 커스텀 훅의 책임도 함께 드러납니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;3. 작은 범위에서 Compiler를 켠다&quot; data-ke-size=&quot;size23&quot;&gt;3. 작은 범위에서 Compiler를 켠다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능하다면 전체 앱이 아니라 일부 패키지나 화면부터 적용합니다. 모노레포라면 UI 패키지 하나, 서비스라면 트래픽이 낮은 관리 화면부터 시작할 수 있습니다. 실패했을 때 영향 범위가 작아야 원인을 찾기 쉽습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;4. 수동 메모이제이션은 변경 단위별로 정리한다&quot; data-ke-size=&quot;size23&quot;&gt;4. 수동 메모이제이션은 변경 단위별로 정리한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compiler를 켠 PR과 useMemo 대량 삭제 PR을 분리하는 것이 좋습니다. 하나의 PR에서 설정 변경, 린트 수정, 메모이제이션 삭제, 리팩토링이 동시에 일어나면 문제가 생겼을 때 원인을 찾기 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추천 흐름은 이렇습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Compiler 적용을 위한 설정과 린트 정리&lt;/li&gt;
&lt;li&gt;테스트와 주요 화면 동작 확인&lt;/li&gt;
&lt;li&gt;불필요한 useMemo, useCallback을 화면 단위로 제거&lt;/li&gt;
&lt;li&gt;성능 지표와 렌더링 횟수 비교&lt;/li&gt;
&lt;li&gt;문제가 있으면 해당 변경만 되돌림&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 Compiler 도입이 &amp;ldquo;큰 기술 부채 정리 이벤트&amp;rdquo;가 아니라 반복 가능한 마이그레이션 작업이 됩니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;지워도 되는 메모이제이션과 남겨둘 메모이제이션&quot; data-ke-size=&quot;size26&quot;&gt;지워도 되는 메모이제이션과 남겨둘 메모이제이션&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 다음 기준으로 판단하면 편합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지워도 되는 경우는 보통 이렇습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계산 비용이 거의 없는 단순 값 조합&lt;/li&gt;
&lt;li&gt;props로 내려가지 않는 내부 콜백&lt;/li&gt;
&lt;li&gt;dependency array를 맞추기 위해 코드가 더 복잡해진 경우&lt;/li&gt;
&lt;li&gt;Profiler에서 효과가 확인되지 않은 방어적 최적화&lt;/li&gt;
&lt;li&gt;React.memo와 세트로 붙어 있지만 실제 렌더링 감소가 없는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 남겨둘 수 있는 경우도 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;큰 배열 정렬, 필터링, 그룹핑처럼 계산 비용이 분명한 경우&lt;/li&gt;
&lt;li&gt;외부 라이브러리에 안정적인 참조를 넘겨야 하는 경우&lt;/li&gt;
&lt;li&gt;dependency 변경이 의미 있는 캐시 경계인 경우&lt;/li&gt;
&lt;li&gt;성능 이슈를 해결한 이력이 있고 측정 근거가 남아 있는 경우&lt;/li&gt;
&lt;li&gt;Compiler 적용 범위 밖의 코드와 맞물려 있는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 것은 &amp;ldquo;Compiler가 있으니 전부 삭제&amp;rdquo;도 아니고, &amp;ldquo;혹시 모르니 전부 유지&amp;rdquo;도 아닙니다. 메모이제이션이 코드의 의미를 더 명확하게 만드는지, 아니면 불안해서 붙인 장식인지 구분해야 합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;흔한 실수&quot; data-ke-size=&quot;size26&quot;&gt;흔한 실수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Compiler를 도입할 때 자주 나오는 실수는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Compiler 적용과 대규모 리팩토링을 같은 PR에 넣는다.&lt;/li&gt;
&lt;li&gt;useMemo, useCallback 삭제만 목표로 삼는다.&lt;/li&gt;
&lt;li&gt;성능 측정 없이 &amp;ldquo;최신 기능을 썼으니 빨라졌을 것&amp;rdquo;이라고 판단한다.&lt;/li&gt;
&lt;li&gt;React 규칙 위반 경고를 그대로 둔 채 Compiler만 켠다.&lt;/li&gt;
&lt;li&gt;서버 상태, 네트워크 지연, 번들 크기 문제까지 Compiler가 해결한다고 기대한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compiler는 렌더링 최적화 도구입니다. API 응답이 느리거나, 이미지가 크거나, 서버 컴포넌트 경계가 잘못되어 있거나, 상태 관리 구조가 복잡한 문제까지 대신 해결해 주지는 않습니다. 성능 문제를 만났을 때는 여전히 병목을 나누어 봐야 합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;결론: 자동 최적화보다 중요한 것은 예측 가능한 코드다&quot; data-ke-size=&quot;size26&quot;&gt;결론: 자동 최적화보다 중요한 것은 예측 가능한 코드다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Compiler의 가장 큰 장점은 개발자가 성능을 매번 수동으로 챙겨야 하는 부담을 줄여준다는 점입니다. 특히 참조 안정화를 위해 반복적으로 붙이던 useCallback, 단순 계산을 감싸던 useMemo, 방어적인 React.memo 사용은 줄어들 가능성이 큽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 도입의 핵심은 훅을 몇 개 지우느냐가 아닙니다. 컴포넌트가 순수하고, 상태의 소유권이 분명하고, 부수 효과가 올바른 위치에 있고, 성능을 측정할 수 있는 상태인지가 더 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 실무 기준은 이렇습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Compiler를 켜기 전에 React 규칙 위반부터 줄인다.&lt;/li&gt;
&lt;li&gt;현재 성능 기준을 기록한다.&lt;/li&gt;
&lt;li&gt;작은 범위에서 적용하고 관찰한다.&lt;/li&gt;
&lt;li&gt;수동 메모이제이션은 효과가 불분명한 것부터 제거한다.&lt;/li&gt;
&lt;li&gt;비싼 계산이나 외부 라이브러리 경계는 측정 후 결정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Compiler는 좋은 React 코드를 더 편하게 유지하게 해주는 도구입니다. 그래서 도입 전 가장 먼저 할 일은 새로운 설정을 찾는 것이 아니라, 우리 코드가 자동 최적화를 받아들일 만큼 예측 가능한지 확인하는 것입니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;Reference&quot; data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://react.dev/learn/react-compiler&quot; data-tooltip-position=&quot;top&quot;&gt;React Compiler &amp;ndash; React&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.dev/reference/react/useMemo&quot; data-tooltip-position=&quot;top&quot;&gt;useMemo &amp;ndash; React&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.dev/reference/react/useCallback&quot; data-tooltip-position=&quot;top&quot;&gt;useCallback &amp;ndash; React&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>frontend</category>
      <category>migration</category>
      <category>Performance</category>
      <category>React</category>
      <category>ReactCompiller</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/56</guid>
      <comments>https://mishka.tistory.com/56#entry56comment</comments>
      <pubDate>Tue, 2 Jun 2026 14:44:28 +0900</pubDate>
    </item>
    <item>
      <title>원격 MCP 서버 보안 체크리스트: OAuth 2.1 권한 설계를 실무 기준으로 보기</title>
      <link>https://mishka.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;MCP(Model Context Protocol)를 처음 접할 때는 보통 로컬 개발 환경에서 시작합니다. AI 에이전트가 파일을 읽고, GitHub 이슈를 보고, 사내 문서를 검색하고, 테스트를 실행하는 식입니다. 이 단계에서는 &amp;ldquo;어떤 도구를 제공할 것인가&amp;rdquo;가 가장 먼저 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 MCP 서버가 로컬을 넘어 원격 서비스가 되는 순간 질문이 바뀝니다. 이제 중요한 것은 도구 목록만이 아닙니다. 누가 이 서버에 접근할 수 있는지, 어떤 사용자의 권한으로 실행되는지, 토큰이 어디까지 허용되는지, AI 에이전트가 실수했을 때 어디서 멈출 수 있는지가 더 중요해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 HTTP 기반 원격 MCP 서버를 운영한다면 인증과 권한 부여를 단순 API Key 수준으로 생각하기 어렵습니다. MCP 공식 Authorization 문서는 OAuth 2.1 기반 흐름과 보안 요구사항을 다룹니다. 이번 글에서는 원격 MCP 서버를 만들거나 도입할 때 OAuth 2.1 권한 설계를 어떤 실무 기준으로 봐야 하는지 정리해보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로컬 MCP와 원격 MCP는 위험 모델이 다르다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 MCP 서버는 보통 개발자의 컴퓨터 안에서 실행됩니다. 위험이 없다는 뜻은 아니지만, 접근 경로와 사용자가 비교적 제한적입니다. 개발자가 명령을 실행하고, 로컬 파일이나 특정 계정의 도구를 연결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 원격 MCP 서버는 네트워크를 통해 여러 클라이언트가 접근할 수 있습니다. 회사 내부 도구, SaaS 계정, 클라우드 리소스, 고객 데이터와 연결될 가능성도 커집니다. 이때 AI 에이전트는 단순한 채팅 상대가 아니라 사용자를 대신해 도구를 호출하는 실행 주체가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 원격 MCP의 보안 질문은 다음처럼 바뀝니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 요청은 어떤 사용자를 대신하는가?&lt;/li&gt;
&lt;li&gt;클라이언트는 신뢰할 수 있는가?&lt;/li&gt;
&lt;li&gt;토큰은 이 MCP 서버에만 쓰이도록 제한되어 있는가?&lt;/li&gt;
&lt;li&gt;도구별로 필요한 최소 권한만 허용되는가?&lt;/li&gt;
&lt;li&gt;위험한 작업은 추가 확인이나 별도 정책을 거치는가?&lt;/li&gt;
&lt;li&gt;누가 언제 어떤 도구를 호출했는지 추적할 수 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문에 답하지 못한 채 MCP 서버를 열면, 편리한 자동화 도구가 곧바로 권한 확장 지점이 됩니다. AI가 잘못 판단한 문제와 공격자가 악용한 문제를 구분하기도 어려워집니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OAuth 2.1은 로그인 기능이 아니라 권한 흐름이다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth를 &amp;ldquo;소셜 로그인에 쓰는 것&amp;rdquo; 정도로 생각하면 MCP 권한 설계가 흐려집니다. 원격 MCP에서 OAuth 2.1의 핵심은 사용자를 인증하는 것뿐 아니라, 클라이언트가 제한된 권한으로 리소스에 접근하도록 만드는 흐름입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP Authorization 문서에서는 HTTP 기반 전송에서 제한된 MCP 서버에 접근하기 위한 권한 흐름을 정의합니다. 여기서 MCP 서버는 보호된 리소스 역할을 하고, 클라이언트는 사용자의 승인을 받아 액세스 토큰을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무적으로는 세 역할을 분리해서 봐야 합니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;질문&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;사용자&lt;/td&gt;
&lt;td&gt;이 작업을 허용할 실제 주체는 누구인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;클라이언트&lt;/td&gt;
&lt;td&gt;토큰을 받아 MCP 서버를 호출하는 애플리케이션은 무엇인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP 서버&lt;/td&gt;
&lt;td&gt;어떤 도구와 데이터를 보호하는 리소스 서버인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorization Server&lt;/td&gt;
&lt;td&gt;사용자 인증과 토큰 발급을 담당하는 곳은 어디인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구분이 없으면 API Key 하나를 여러 자동화 도구에 공유하는 식으로 흘러가기 쉽습니다. 그 순간 토큰이 유출되었을 때 회수 범위도 넓어지고, 특정 사용자의 행위인지 시스템 공용 계정의 행위인지도 흐려집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth 2.1을 쓰는 이유는 복잡해 보이는 표준을 위한 것이 아닙니다. 사용자 동의, 클라이언트 식별, 토큰 만료, 권한 범위, 리다이렉트 검증, PKCE 같은 보안 장치를 한 흐름 안에서 다루기 위해서입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;토큰은 &amp;ldquo;무엇을 할 수 있는가&amp;rdquo;보다 &amp;ldquo;어디에 쓸 수 있는가&amp;rdquo;가 중요하다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 MCP 서버에서 자주 놓치는 부분은 토큰의 대상입니다. 토큰이 단순히 &amp;ldquo;읽기 권한&amp;rdquo;, &amp;ldquo;쓰기 권한&amp;rdquo;만 가지고 있으면 부족할 수 있습니다. 그 토큰이 어떤 리소스 서버를 위한 것인지도 명확해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 어떤 AI 클라이언트가 사내 업무 도구용 토큰을 받았다고 가정해보겠습니다. 이 토큰이 여러 API에서 모두 받아들여진다면, 한 MCP 서버에서 얻은 권한이 다른 서비스 호출로 이어질 수 있습니다. 공격자 입장에서는 토큰을 재사용할 수 있는 표면이 넓어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 줄이기 위해 OAuth에서는 Resource Indicators 같은 개념이 중요해집니다. 토큰이 특정 리소스 서버를 대상으로 발급되도록 하여, 다른 곳에서 재사용되지 않게 만드는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 서버를 설계할 때는 다음을 확인해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;액세스 토큰의 audience 또는 resource가 MCP 서버에 맞게 제한되어 있는가?&lt;/li&gt;
&lt;li&gt;토큰이 다른 내부 API에서 그대로 받아들여지지 않는가?&lt;/li&gt;
&lt;li&gt;클라이언트별로 허용된 redirect URI가 엄격하게 검증되는가?&lt;/li&gt;
&lt;li&gt;public client라면 PKCE를 사용하고 있는가?&lt;/li&gt;
&lt;li&gt;refresh token을 발급한다면 저장 위치와 회수 정책이 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰은 짧고 편리한 문자열처럼 보이지만 실제로는 권한의 압축본입니다. 그래서 &amp;ldquo;이 토큰으로 무엇을 할 수 있나&amp;rdquo;와 함께 &amp;ldquo;이 토큰은 어디에서만 쓸 수 있나&amp;rdquo;를 반드시 봐야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도구 권한은 scope만으로 끝나지 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth scope는 권한을 표현하는 중요한 수단입니다. 예를 들어 &lt;code&gt;issues:read&lt;/code&gt;, &lt;code&gt;issues:write&lt;/code&gt;, &lt;code&gt;deploy:read&lt;/code&gt;, &lt;code&gt;deploy:execute&lt;/code&gt; 같은 식으로 도구별 권한을 나눌 수 있습니다. 하지만 MCP 서버에서는 scope만으로 충분하지 않은 경우가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트가 호출하는 도구는 사람이 직접 누르는 버튼보다 더 빠르고 반복적입니다. 한 번 잘못된 판단이 여러 도구 호출로 이어질 수 있습니다. 따라서 권한은 최소한 세 층으로 나누어 보는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, 토큰 레벨 권한입니다. 사용자가 승인한 범위와 클라이언트가 가진 scope입니다. 둘째, 서버 정책입니다. MCP 서버가 특정 도구 호출을 허용할지 판단하는 내부 규칙입니다. 셋째, 실행 시점의 컨텍스트입니다. 요청 대상, 환경, 변경 범위, 위험도를 보고 추가로 막거나 확인하는 단계입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 배포 관련 도구를 MCP로 노출한다고 해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;readDeploymentStatus
listRecentDeployments
createPreviewDeployment
promoteToProduction
rollbackProduction&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 도구들을 모두 &lt;code&gt;deploy:write&lt;/code&gt; 하나로 묶으면 위험합니다. &lt;code&gt;createPreviewDeployment&lt;/code&gt;와 &lt;code&gt;promoteToProduction&lt;/code&gt;은 영향도가 다릅니다. 실무에서는 다음처럼 나누는 편이 안전합니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;도구&lt;/th&gt;
&lt;th&gt;기본 허용 기준&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;배포 상태 조회&lt;/td&gt;
&lt;td&gt;읽기 scope로 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최근 배포 목록&lt;/td&gt;
&lt;td&gt;읽기 scope로 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프리뷰 배포 생성&lt;/td&gt;
&lt;td&gt;쓰기 scope와 프로젝트 권한 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;운영 배포 승격&lt;/td&gt;
&lt;td&gt;별도 고위험 권한 또는 승인 단계 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;운영 롤백&lt;/td&gt;
&lt;td&gt;감사 로그와 추가 확인 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 서버는 &amp;ldquo;AI가 요청했으니 실행&amp;rdquo;하는 얇은 프록시가 되면 안 됩니다. 도구마다 위험도를 나누고, 위험한 도구는 더 좁은 권한과 더 강한 검증을 요구해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무 체크리스트: 원격 MCP 서버를 열기 전에 볼 것들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 MCP 서버를 만들 때는 다음 체크리스트를 기준으로 점검할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 클라이언트 등록과 리다이렉트 검증&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;허용된 클라이언트를 식별할 수 있는가?&lt;/li&gt;
&lt;li&gt;redirect URI가 정확히 등록된 값과 일치하는가?&lt;/li&gt;
&lt;li&gt;개발용 redirect URI가 운영에 남아 있지 않은가?&lt;/li&gt;
&lt;li&gt;public client와 confidential client를 구분하고 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth 흐름에서 redirect URI 검증은 기본처럼 보이지만, 실제 사고에서는 기본 설정이 느슨해서 문제가 생기는 경우가 많습니다. 특히 AI 도구가 여러 환경에서 실행된다면 개발, 스테이징, 운영 클라이언트를 분리하는 것이 좋습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. PKCE와 짧은 수명의 토큰&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Authorization Code 흐름에서 PKCE를 사용하는가?&lt;/li&gt;
&lt;li&gt;액세스 토큰 수명이 과하게 길지 않은가?&lt;/li&gt;
&lt;li&gt;refresh token 회수 정책이 있는가?&lt;/li&gt;
&lt;li&gt;토큰 저장 위치가 클라이언트 특성에 맞는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 클라이언트가 데스크톱 앱, 웹 앱, CLI, 서버형 도구 등 다양해질수록 토큰 저장 방식도 달라집니다. 모든 클라이언트에 같은 기준을 적용하기보다 public client는 더 보수적으로 보는 편이 안전합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. scope와 도구 매핑&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MCP tool마다 필요한 scope가 문서화되어 있는가?&lt;/li&gt;
&lt;li&gt;읽기 도구와 쓰기 도구가 분리되어 있는가?&lt;/li&gt;
&lt;li&gt;삭제, 배포, 결제, 권한 변경 같은 고위험 도구가 별도 권한으로 나뉘어 있는가?&lt;/li&gt;
&lt;li&gt;scope가 너무 넓어서 사실상 관리자 권한처럼 쓰이지 않는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scope 이름은 개발자에게도 운영자에게도 이해 가능해야 합니다. &lt;code&gt;admin&lt;/code&gt; 하나로 모든 것을 해결하면 편하지만, 나중에 감사와 회수가 어려워집니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 사용자 컨텍스트와 대리 실행&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도구 호출이 어떤 사용자의 권한으로 실행되는지 남는가?&lt;/li&gt;
&lt;li&gt;서비스 계정으로 실행한다면 실제 요청 사용자를 함께 기록하는가?&lt;/li&gt;
&lt;li&gt;사용자가 접근할 수 없는 프로젝트나 리소스를 AI가 우회해서 접근하지 못하는가?&lt;/li&gt;
&lt;li&gt;조직, 팀, 프로젝트 단위 권한이 도구 실행 전에 다시 검증되는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 서버는 기존 백엔드 권한 모델을 우회하면 안 됩니다. AI가 호출하더라도 최종적으로는 동일한 권한 검사를 통과해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 감사 로그와 재현 가능성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 클라이언트가 어떤 사용자를 대신해 어떤 tool을 호출했는가?&lt;/li&gt;
&lt;li&gt;입력 파라미터와 결과 상태가 기록되는가?&lt;/li&gt;
&lt;li&gt;실패한 권한 요청도 남는가?&lt;/li&gt;
&lt;li&gt;민감한 값은 마스킹되는가?&lt;/li&gt;
&lt;li&gt;문제가 생겼을 때 특정 토큰이나 클라이언트를 빠르게 차단할 수 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트 기반 자동화에서는 &amp;ldquo;왜 이 작업이 실행되었는가&amp;rdquo;를 나중에 추적하는 일이 중요합니다. 모든 프롬프트를 그대로 저장할 필요는 없지만, 도구 호출 단위의 감사 로그는 반드시 필요합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;흔한 실수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 MCP 서버에서 자주 나오는 실수는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부망에서만 쓴다는 이유로 인증을 약하게 둔다.&lt;/li&gt;
&lt;li&gt;모든 도구를 하나의 API Key 또는 관리자 토큰으로 실행한다.&lt;/li&gt;
&lt;li&gt;읽기 도구와 쓰기 도구의 scope를 나누지 않는다.&lt;/li&gt;
&lt;li&gt;AI 클라이언트를 일반 웹 클라이언트와 같은 위험 모델로 본다.&lt;/li&gt;
&lt;li&gt;토큰의 audience/resource를 제한하지 않는다.&lt;/li&gt;
&lt;li&gt;감사 로그 없이 &amp;ldquo;에이전트가 실행했다&amp;rdquo; 정도만 남긴다.&lt;/li&gt;
&lt;li&gt;운영 배포, 삭제, 결제 같은 고위험 작업에 추가 확인 단계가 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 공용 서비스 계정 하나로 모든 MCP 호출을 처리하는 방식은 처음에는 편하지만 오래 유지하기 어렵습니다. 권한 회수도 어렵고, 누가 실제로 요청했는지 추적하기도 어렵습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도입 판단 기준: 언제 OAuth 기반 원격 MCP가 필요한가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 MCP 서버가 처음부터 복잡한 OAuth 구성을 가져야 하는 것은 아닙니다. 로컬 개발 전용 도구나 개인 자동화라면 단순한 구성이 더 적절할 수 있습니다. 하지만 다음 조건 중 하나라도 해당한다면 원격 MCP 권한 설계를 진지하게 봐야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 사용자가 같은 MCP 서버를 사용한다.&lt;/li&gt;
&lt;li&gt;사내 문서, 고객 정보, 결제, 배포, 인프라 리소스에 접근한다.&lt;/li&gt;
&lt;li&gt;외부 SaaS 계정과 연결된다.&lt;/li&gt;
&lt;li&gt;브라우저나 데스크톱 앱처럼 다양한 클라이언트가 접근한다.&lt;/li&gt;
&lt;li&gt;조직 단위 권한, 프로젝트 권한, 역할 기반 접근 제어가 필요하다.&lt;/li&gt;
&lt;li&gt;호출 이력을 감사해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황에서는 단순히 &amp;ldquo;토큰을 헤더에 넣는다&amp;rdquo;로 끝내기 어렵습니다. 사용자, 클라이언트, 리소스 서버, 권한 범위, 감사 로그를 모두 설계해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론: MCP 서버는 도구 목록보다 권한 경계가 먼저다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP의 매력은 AI 에이전트가 실제 개발 환경과 업무 도구를 사용할 수 있게 해준다는 점입니다. 하지만 원격 MCP 서버에서는 그 장점이 곧 위험이 될 수 있습니다. AI가 더 많은 일을 할수록, 권한 경계도 더 명확해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무 기준으로 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬 MCP와 원격 MCP의 위험 모델을 분리한다.&lt;/li&gt;
&lt;li&gt;OAuth 2.1을 단순 로그인 기능이 아니라 권한 흐름으로 본다.&lt;/li&gt;
&lt;li&gt;토큰의 scope뿐 아니라 audience/resource를 제한한다.&lt;/li&gt;
&lt;li&gt;MCP tool별로 읽기, 쓰기, 고위험 작업을 나눈다.&lt;/li&gt;
&lt;li&gt;기존 백엔드 권한 검사를 MCP 서버에서도 다시 적용한다.&lt;/li&gt;
&lt;li&gt;도구 호출 단위의 감사 로그를 남긴다.&lt;/li&gt;
&lt;li&gt;운영 배포, 삭제, 결제 같은 작업은 추가 확인 단계를 둔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 MCP 서버를 안전하게 운영하려면 &amp;ldquo;AI가 무엇을 할 수 있는가&amp;rdquo;보다 &amp;ldquo;AI가 어디까지 할 수 있고, 어디서 멈춰야 하는가&amp;rdquo;를 먼저 정해야 합니다. 좋은 MCP 서버는 도구를 많이 제공하는 서버가 아니라, 필요한 도구를 명확한 권한 경계 안에서 제공하는 서버입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization&quot;&gt;Authorization - Model Context Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8707&quot;&gt;OAuth 2.0 Resource Indicators - RFC 8707&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://oauth.net/2.1/&quot;&gt;OAuth 2.1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>aiagent</category>
      <category>backend</category>
      <category>MCP</category>
      <category>OAuth</category>
      <category>security</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/55</guid>
      <comments>https://mishka.tistory.com/55#entry55comment</comments>
      <pubDate>Tue, 2 Jun 2026 14:39:37 +0900</pubDate>
    </item>
    <item>
      <title>AI가 만든 프론트엔드 코드를 리뷰할 때 보는 기준</title>
      <link>https://mishka.tistory.com/54</link>
      <description>&lt;p&gt;AI 코딩 도구는 프론트엔드 개발에서 꽤 강력합니다. React 컴포넌트를 만들고, TypeScript 타입을 붙이고, CSS를 작성하고, 테스트 초안까지 만들어 줍니다. 특히 반복적인 UI 작업이나 기존 패턴을 따라가는 작업에서는 생산성이 확실히 좋아집니다.&lt;/p&gt;
&lt;p&gt;하지만 AI가 만든 코드를 그대로 머지해도 되는지는 다른 문제입니다. 겉으로는 동작하는 것처럼 보여도 요구사항을 일부 놓치거나, 상태 관리를 복잡하게 만들거나, 접근성과 테스트가 빠져 있는 경우가 많습니다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 AI가 만든 프론트엔드 코드를 리뷰할 때 확인하면 좋은 기준을 정리해보겠습니다. React와 TypeScript 프로젝트를 기준으로 설명하지만, Vue나 다른 프레임워크에서도 대부분 비슷하게 적용할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;1. 요구사항을 제대로 만족했는가&lt;/h2&gt;
&lt;p&gt;가장 먼저 볼 것은 코드 스타일이 아니라 요구사항입니다. AI는 사용자가 말한 내용을 빠르게 구현하지만, 문장 사이에 숨어 있는 조건이나 예외 상황을 잘 놓칩니다.&lt;/p&gt;
&lt;p&gt;예를 들어 “상품 목록에서 품절 상품은 비활성화해줘”라는 요구사항이 있었다고 해보겠습니다. 리뷰할 때는 단순히 버튼이 disabled 되었는지만 보면 부족합니다.&lt;/p&gt;
&lt;p&gt;확인해야 할 항목은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;품절 상품이 클릭되지 않는가?&lt;/li&gt;
&lt;li&gt;키보드 접근에서도 비활성 상태가 유지되는가?&lt;/li&gt;
&lt;li&gt;품절 사유나 상태가 사용자에게 표시되는가?&lt;/li&gt;
&lt;li&gt;API 데이터가 늦게 도착해도 상태가 꼬이지 않는가?&lt;/li&gt;
&lt;li&gt;필터, 검색, 페이지네이션과 함께 동작하는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 코드는 “대표 케이스”에는 강하지만 “경계 조건”에는 약한 편입니다. 그래서 리뷰에서는 정상 케이스보다 예외 케이스를 먼저 보는 것이 좋습니다.&lt;/p&gt;
&lt;h2&gt;2. 변경 범위가 필요 이상으로 넓지 않은가&lt;/h2&gt;
&lt;p&gt;AI에게 작업을 맡기면 가끔 요청하지 않은 리팩터링까지 함께 들어갑니다. 변수명 몇 개를 고치거나 컴포넌트를 하나 추가하면 될 일을, 폴더 구조를 바꾸고 공통 유틸을 새로 만들고 스타일 방식까지 바꿔버리는 식입니다.&lt;/p&gt;
&lt;p&gt;리뷰에서는 다음을 확인합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;요구사항과 직접 관련 없는 파일이 수정되었는가?&lt;/li&gt;
&lt;li&gt;기존 패턴을 무시하고 새로운 구조를 만들었는가?&lt;/li&gt;
&lt;li&gt;한 PR 안에 기능 추가와 대규모 리팩터링이 섞였는가?&lt;/li&gt;
&lt;li&gt;삭제된 코드가 실제로 사용되지 않는 것이 맞는가?&lt;/li&gt;
&lt;li&gt;패키지 추가가 꼭 필요한가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI가 제안한 개선이 나쁘다는 뜻은 아닙니다. 다만 변경 범위가 넓어질수록 리뷰 비용과 회귀 위험이 커집니다. 기능 구현과 구조 개선은 가능하면 분리하는 것이 좋습니다.&lt;/p&gt;
&lt;h2&gt;3. TypeScript 타입이 안전한가&lt;/h2&gt;
&lt;p&gt;AI가 만든 TypeScript 코드는 그럴듯해 보이지만, 자세히 보면 &lt;code&gt;any&lt;/code&gt;, 과도한 optional 처리, 실제 API와 맞지 않는 타입이 섞여 있는 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;특히 아래 패턴은 주의해서 봅니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const data: any = await response.json();&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;user?.profile?.name || &amp;#39;&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type Status = string;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이런 코드는 당장은 에러를 피하게 해주지만, 장기적으로는 타입 시스템의 장점을 약하게 만듭니다. 리뷰에서는 다음 질문을 던져볼 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API 응답 타입이 실제 계약과 맞는가?&lt;/li&gt;
&lt;li&gt;상태 값은 string보다 union type으로 제한할 수 없는가?&lt;/li&gt;
&lt;li&gt;optional chaining으로 에러를 숨기고 있지는 않은가?&lt;/li&gt;
&lt;li&gt;null, undefined, empty array의 의미가 구분되어 있는가?&lt;/li&gt;
&lt;li&gt;컴포넌트 props가 너무 많은 책임을 갖고 있지는 않은가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI에게 “타입 에러 안 나게 해줘”라고만 하면 안전한 타입보다 느슨한 타입을 선택할 가능성이 있습니다. “도메인 상태를 union type으로 표현해줘”, “any 없이 작성해줘”처럼 기준을 명확히 주는 것이 좋습니다.&lt;/p&gt;
&lt;h2&gt;4. 상태 관리가 단순한가&lt;/h2&gt;
&lt;p&gt;프론트엔드 코드에서 복잡도는 대부분 상태에서 나옵니다. AI는 빠르게 동작하는 코드를 만들기 위해 &lt;code&gt;useState&lt;/code&gt;를 여러 개 추가하거나, 서버 상태와 UI 상태를 한 컴포넌트 안에 섞어두는 경우가 많습니다.&lt;/p&gt;
&lt;p&gt;리뷰할 때는 다음을 확인합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;같은 의미의 상태가 중복으로 존재하지 않는가?&lt;/li&gt;
&lt;li&gt;서버에서 계산할 수 있는 값을 클라이언트 상태로 따로 들고 있지 않은가?&lt;/li&gt;
&lt;li&gt;loading, error, empty, success 상태가 명확히 구분되는가?&lt;/li&gt;
&lt;li&gt;derived state를 불필요하게 &lt;code&gt;useEffect&lt;/code&gt;로 동기화하고 있지 않은가?&lt;/li&gt;
&lt;li&gt;컴포넌트가 너무 많은 데이터 흐름을 알고 있지는 않은가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예를 들어 필터링된 목록은 별도 state로 저장하기보다 원본 데이터와 필터 값에서 계산할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const filteredItems = useMemo(() =&amp;gt; {
  return items.filter((item) =&amp;gt; item.name.includes(keyword));
}, [items, keyword]);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;반대로 사용자의 입력 중간값, 모달 열림 여부, 선택된 탭처럼 UI 상호작용에 필요한 값은 명시적인 state로 두는 것이 자연스럽습니다.&lt;/p&gt;
&lt;h2&gt;5. 접근성과 사용성을 놓치지 않았는가&lt;/h2&gt;
&lt;p&gt;AI가 만든 UI 코드는 시각적으로는 괜찮아 보이지만 접근성 속성이 빠져 있는 경우가 많습니다. 특히 버튼처럼 보이지만 &lt;code&gt;div&lt;/code&gt;로 만든 요소, label이 없는 input, 키보드 이동이 안 되는 모달은 자주 나오는 문제입니다.&lt;/p&gt;
&lt;p&gt;체크리스트는 간단합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;클릭 가능한 요소는 실제 button 또는 anchor를 사용하는가?&lt;/li&gt;
&lt;li&gt;input에는 label 또는 aria-label이 있는가?&lt;/li&gt;
&lt;li&gt;모달, 드롭다운, 탭이 키보드로 조작되는가?&lt;/li&gt;
&lt;li&gt;disabled 상태가 시각적으로만 표현되지 않는가?&lt;/li&gt;
&lt;li&gt;에러 메시지가 사용자에게 명확한가?&lt;/li&gt;
&lt;li&gt;색상만으로 상태를 구분하지 않는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;모던 CSS나 React Server Components처럼 기술적인 주제도 중요하지만, 실제 사용자에게 닿는 마지막 품질은 이런 기본기에서 갈립니다.&lt;/p&gt;
&lt;h2&gt;6. 테스트가 의미 있는가&lt;/h2&gt;
&lt;p&gt;AI는 테스트 파일도 잘 만들어 줍니다. 하지만 “렌더링된다” 정도의 테스트만 잔뜩 만드는 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;좋은 테스트는 구현 세부사항보다 사용자 행동과 결과를 확인합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;it(&amp;#39;품절 상품은 장바구니에 담을 수 없다&amp;#39;, async () =&amp;gt; {
  render(&amp;lt;ProductCard product={soldOutProduct} /&amp;gt;);

  const button = screen.getByRole(&amp;#39;button&amp;#39;, { name: /장바구니/i });
  expect(button).toBeDisabled();
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;리뷰할 때는 다음을 봅니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;핵심 사용자 시나리오가 테스트에 포함되어 있는가?&lt;/li&gt;
&lt;li&gt;실패/빈 상태/로딩 상태 테스트가 있는가?&lt;/li&gt;
&lt;li&gt;mock 데이터가 실제 도메인과 비슷한가?&lt;/li&gt;
&lt;li&gt;내부 구현보다 화면에 보이는 결과를 검증하는가?&lt;/li&gt;
&lt;li&gt;테스트 이름만 그럴듯하고 실제 검증은 약하지 않은가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;테스트는 AI 코드의 안전장치입니다. 특히 AI에게 리팩터링을 맡길수록 테스트의 중요도는 더 올라갑니다.&lt;/p&gt;
&lt;h2&gt;7. 리뷰 코멘트를 다시 AI에게 잘 전달하기&lt;/h2&gt;
&lt;p&gt;AI 코드 리뷰의 장점은 피드백을 다시 AI에게 전달해 개선시킬 수 있다는 점입니다. 다만 “고쳐줘”라고만 하면 또 다른 문제가 생길 수 있습니다.&lt;/p&gt;
&lt;p&gt;리뷰 코멘트는 구체적으로 작성하는 것이 좋습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“상태가 복잡해요” → “&lt;code&gt;selectedItem&lt;/code&gt;과 &lt;code&gt;selectedItemId&lt;/code&gt;가 중복 상태이므로 &lt;code&gt;selectedItemId&lt;/code&gt;만 남기고 선택된 객체는 계산하도록 바꿔주세요.”&lt;/li&gt;
&lt;li&gt;“타입이 별로예요” → “&lt;code&gt;Status&lt;/code&gt;를 string이 아니라 &lt;code&gt;&amp;#39;idle&amp;#39; | &amp;#39;loading&amp;#39; | &amp;#39;success&amp;#39; | &amp;#39;error&amp;#39;&lt;/code&gt; union type으로 제한해주세요.”&lt;/li&gt;
&lt;li&gt;“테스트 추가해주세요” → “품절 상품일 때 버튼이 비활성화되고 클릭 핸들러가 호출되지 않는 테스트를 추가해주세요.”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI에게 주는 리뷰 코멘트도 결국 스펙입니다. 구체적인 피드백일수록 결과가 좋아집니다.&lt;/p&gt;
&lt;h2&gt;실무 리뷰 체크리스트&lt;/h2&gt;
&lt;p&gt;AI가 만든 프론트엔드 코드를 머지하기 전 아래 항목을 확인해보면 좋습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 요구사항의 정상 케이스와 예외 케이스를 모두 만족하는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 변경 범위가 요청한 작업 안에 머무르는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 새 패키지나 구조 변경이 꼭 필요한가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; &lt;code&gt;any&lt;/code&gt;나 과도한 optional 처리가 없는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; loading, error, empty 상태가 분리되어 있는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 상태가 중복으로 관리되지 않는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 접근성 기본 속성이 빠지지 않았는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 테스트가 사용자 행동 중심으로 작성되었는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; lint, typecheck, test를 통과하는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; AI에게 다시 전달할 피드백이 구체적인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;AI 코딩 도구는 프론트엔드 개발의 속도를 올려줍니다. 하지만 속도가 빨라진 만큼 리뷰 기준도 더 중요해졌습니다. AI가 만든 코드는 초안으로는 훌륭하지만, 제품 코드가 되려면 요구사항, 타입, 상태, 접근성, 테스트라는 기본 기준을 통과해야 합니다.&lt;/p&gt;
&lt;p&gt;결국 핵심은 AI를 믿지 말자는 것이 아니라, AI가 잘할 수 있는 일과 사람이 반드시 확인해야 하는 일을 나누는 것입니다. 반복 구현은 AI에게 맡기고, 제품의 품질 기준은 개발자가 잡아야 합니다.&lt;/p&gt;</description>
      <category>AI</category>
      <category>React</category>
      <category>typescript</category>
      <category>접근성</category>
      <category>코드리뷰</category>
      <category>테스트</category>
      <category>프론트엔드</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/54</guid>
      <comments>https://mishka.tistory.com/54#entry54comment</comments>
      <pubDate>Thu, 28 May 2026 10:40:51 +0900</pubDate>
    </item>
    <item>
      <title>Agents.md로 AI 코딩 도구에게 팀 컨벤션 알려주기</title>
      <link>https://mishka.tistory.com/53</link>
      <description>&lt;p&gt;AI 코딩 도구를 쓰다 보면 처음에는 꽤 놀랍습니다. 요구사항을 말하면 컴포넌트도 만들고, 테스트도 작성하고, 리팩터링 방향도 제안합니다. 그런데 실무 프로젝트에 조금만 깊게 들어가면 곧 이런 문제가 생깁니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;우리 팀의 폴더 구조를 자꾸 무시한다.&lt;/li&gt;
&lt;li&gt;테스트 실행 방법을 매번 다시 알려줘야 한다.&lt;/li&gt;
&lt;li&gt;사용하지 않는 라이브러리나 패턴을 제안한다.&lt;/li&gt;
&lt;li&gt;작은 수정인데도 너무 넓은 범위를 바꾼다.&lt;/li&gt;
&lt;li&gt;PR에서 리뷰어가 싫어하는 스타일을 반복한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 문제는 AI 성능만의 문제가 아닙니다. AI에게 프로젝트의 기준과 맥락을 전달하는 방식이 아직 정리되지 않았기 때문에 생기는 문제에 가깝습니다. 최근 글에서 다룬 Spec-Driven Development가 “무엇을 만들지”를 명확히 하는 접근이라면, &lt;code&gt;Agents.md&lt;/code&gt;는 “이 프로젝트에서는 어떻게 일해야 하는지”를 AI에게 알려주는 프로젝트 설명서라고 볼 수 있습니다.&lt;/p&gt;
&lt;h2&gt;Agents.md가 필요한 이유&lt;/h2&gt;
&lt;p&gt;개발자는 프로젝트에 들어오면 README, package.json, 폴더 구조, 기존 코드, PR 기록을 보면서 암묵적인 규칙을 익힙니다. 하지만 AI 코딩 도구는 매번 제한된 컨텍스트 안에서 작업합니다. 저장소 전체를 읽을 수 있다고 해도 어떤 정보가 중요한지 스스로 완벽히 판단하지는 못합니다.&lt;/p&gt;
&lt;p&gt;그래서 AI에게는 짧고 명확한 작업 가이드가 필요합니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Agents.md&lt;/code&gt;에는 보통 다음과 같은 내용을 담습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프로젝트의 목적과 기술 스택&lt;/li&gt;
&lt;li&gt;자주 사용하는 명령어&lt;/li&gt;
&lt;li&gt;폴더별 역할&lt;/li&gt;
&lt;li&gt;코드 스타일과 네이밍 규칙&lt;/li&gt;
&lt;li&gt;테스트 작성 기준&lt;/li&gt;
&lt;li&gt;수정하면 안 되는 영역&lt;/li&gt;
&lt;li&gt;PR 전에 확인해야 할 체크리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;중요한 점은 문서를 길게 쓰는 것이 아닙니다. AI가 작업 전에 빠르게 읽고 바로 적용할 수 있어야 합니다.&lt;/p&gt;
&lt;h2&gt;기본 구조 예시&lt;/h2&gt;
&lt;p&gt;프론트엔드 프로젝트라면 아래 정도의 구조로 시작할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-md&quot;&gt;# Project Guide for AI Agents

## Tech Stack
- React 19
- TypeScript
- Vite
- TanStack Query
- CSS Modules

## Commands
- install: `pnpm install`
- dev: `pnpm dev`
- test: `pnpm test`
- lint: `pnpm lint`
- typecheck: `pnpm typecheck`

## Folder Rules
- `src/components`: 재사용 가능한 UI 컴포넌트
- `src/features`: 도메인 단위 기능
- `src/hooks`: 공통 훅
- `src/api`: API 클라이언트와 타입

## Coding Rules
- 모든 새 컴포넌트는 TypeScript로 작성한다.
- props 타입은 `ComponentNameProps` 형식으로 둔다.
- API 호출은 컴포넌트에서 직접 하지 않고 hooks 또는 query 레이어를 사용한다.
- any 사용은 피하고, 필요한 경우 이유를 주석으로 남긴다.

## Before Finishing
- lint, typecheck, test를 실행한다.
- 변경 범위 밖의 파일은 수정하지 않는다.
- 스냅샷 변경이 필요한 경우 이유를 설명한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 정도만 있어도 AI의 작업 품질이 달라집니다. 특히 “명령어”와 “수정하면 안 되는 범위”는 반복 작업에서 큰 차이를 만듭니다.&lt;/p&gt;
&lt;h2&gt;팀 컨벤션은 구체적으로 적기&lt;/h2&gt;
&lt;p&gt;AI에게 “깔끔하게 작성해줘”라고 하면 사람마다 다른 결과가 나옵니다. 대신 팀에서 실제로 리뷰하는 기준을 문장으로 적는 것이 좋습니다.&lt;/p&gt;
&lt;p&gt;예를 들어 다음처럼 작성할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;상태가 3개 이상이면 boolean 여러 개보다 union type을 우선한다.&lt;/li&gt;
&lt;li&gt;컴포넌트 내부에서 날짜 포맷을 직접 만들지 말고 &lt;code&gt;formatDate&lt;/code&gt; 유틸을 사용한다.&lt;/li&gt;
&lt;li&gt;서버 응답 타입은 &lt;code&gt;src/api/types&lt;/code&gt;에 둔다.&lt;/li&gt;
&lt;li&gt;화면에 직접 노출되는 문자열은 추후 i18n을 고려해 상수로 분리한다.&lt;/li&gt;
&lt;li&gt;loading, error, empty 상태를 모두 고려한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이런 규칙은 사람에게는 사소해 보여도 AI에게는 매우 중요한 힌트가 됩니다. AI는 “우리 팀이 당연하게 여기는 것”을 모릅니다. 당연한 것을 적어두는 것이 오히려 핵심입니다.&lt;/p&gt;
&lt;h2&gt;Spec-Driven Development와 함께 쓰기&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Agents.md&lt;/code&gt;는 프로젝트 공통 규칙이고, 스펙 문서는 특정 작업의 요구사항입니다. 둘을 함께 쓰면 AI에게 전달되는 정보가 훨씬 선명해집니다.&lt;/p&gt;
&lt;p&gt;예를 들어 “알림 설정 화면을 만들어줘”라고만 지시하는 대신 다음처럼 작업을 나눌 수 있습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Agents.md&lt;/code&gt;를 읽고 프로젝트 규칙을 확인한다.&lt;/li&gt;
&lt;li&gt;알림 설정 화면의 요구사항을 별도 스펙으로 작성한다.&lt;/li&gt;
&lt;li&gt;스펙에 사용자 시나리오, 예외 상황, API 계약, 테스트 기준을 포함한다.&lt;/li&gt;
&lt;li&gt;구현 전 AI에게 누락된 요구사항을 질문하게 한다.&lt;/li&gt;
&lt;li&gt;구현 후 &lt;code&gt;Agents.md&lt;/code&gt;의 체크리스트 기준으로 검증한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 방식은 Vibe Coding의 빠른 장점은 유지하면서도, 결과물이 프로젝트 규칙에서 벗어나는 문제를 줄여줍니다.&lt;/p&gt;
&lt;h2&gt;너무 많은 규칙은 오히려 방해가 된다&lt;/h2&gt;
&lt;p&gt;주의할 점도 있습니다. &lt;code&gt;Agents.md&lt;/code&gt;를 팀 위키처럼 너무 길게 만들면 AI가 핵심을 놓칠 수 있습니다. 처음부터 완벽한 문서를 만들기보다, 자주 반복되는 실수부터 짧게 적는 편이 좋습니다.&lt;/p&gt;
&lt;p&gt;추천하는 운영 방식은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;처음에는 50~100줄 정도로 시작한다.&lt;/li&gt;
&lt;li&gt;AI가 반복해서 틀리는 내용을 발견하면 한 줄 추가한다.&lt;/li&gt;
&lt;li&gt;오래된 명령어나 사용하지 않는 패턴은 바로 제거한다.&lt;/li&gt;
&lt;li&gt;프로젝트 루트에 두고 README에서 링크한다.&lt;/li&gt;
&lt;li&gt;큰 저장소라면 하위 폴더별 &lt;code&gt;Agents.md&lt;/code&gt;를 추가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;문서는 살아 있어야 합니다. 한 번 만들고 방치하면 오히려 잘못된 지시가 됩니다.&lt;/p&gt;
&lt;h2&gt;실무 체크리스트&lt;/h2&gt;
&lt;p&gt;AI 코딩 도구를 팀에서 사용하고 있다면 아래 항목부터 확인해보면 좋습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 프로젝트 루트에 AI용 작업 가이드가 있는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 설치, 실행, 테스트, 린트 명령어가 최신인가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 폴더 구조와 책임이 설명되어 있는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 팀에서 금지하는 패턴이 명확히 적혀 있는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; PR 전 확인해야 할 기준이 있는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; AI가 반복해서 실수한 내용이 문서에 반영되는가?&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 문서가 너무 길어져 핵심이 흐려지지는 않았는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;AI 코딩 도구를 잘 쓰는 핵심은 더 멋진 프롬프트를 외우는 것이 아니라, 프로젝트의 맥락을 AI가 이해할 수 있는 형태로 정리하는 것입니다. &lt;code&gt;Agents.md&lt;/code&gt;는 그 출발점이 될 수 있습니다.&lt;/p&gt;
&lt;p&gt;처음부터 완벽할 필요는 없습니다. 오늘 당장 프로젝트에서 자주 쓰는 명령어, 폴더 구조, 리뷰 기준만 정리해도 충분합니다. 그리고 AI가 실수할 때마다 문서를 조금씩 업데이트해 보세요. 팀의 암묵지가 문서화될수록 AI는 단순한 코드 생성기를 넘어 프로젝트에 맞춰 일하는 동료에 가까워집니다.&lt;/p&gt;</description>
      <category>Agents.md</category>
      <category>AI</category>
      <category>Spec-Driven Development</category>
      <category>개발문화</category>
      <category>생산성</category>
      <category>프론트엔드</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/53</guid>
      <comments>https://mishka.tistory.com/53#entry53comment</comments>
      <pubDate>Thu, 28 May 2026 10:37:40 +0900</pubDate>
    </item>
    <item>
      <title>AI 코딩 에이전트 작업 단위 쪼개기: 실패를 줄이는 요청 설계법</title>
      <link>https://mishka.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트를 쓰다 보면 처음에는 자연스럽게 큰 요청을 하게 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자 페이지의 회원 관리 기능을 만들어줘.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말로 보면 간단하지만, 실제로는 API 타입 정의, 목록 화면, 검색, 페이지네이션, 권한 처리, 에러 처리, 테스트, 스타일 정리까지 여러 작업이 섞여 있습니다. 사람 개발자라면 중간중간 판단하면서 쪼개서 진행하지만, AI에게 한 번에 맡기면 그 판단을 AI가 대신하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 여기서 시작됩니다. 결과물은 빠르게 나오지만 요구사항 일부가 빠지거나, 기존 패턴과 다른 구조가 생기거나, 리뷰해야 할 파일이 너무 많아집니다. 이전 글에서 AGENTS.md, Spec-Driven Development, 테스트 주도 흐름, AI 코드 리뷰 체크리스트를 다뤘다면 이번 글에서는 그보다 더 작은 단위인 &lt;b&gt;작업을 어떻게 쪼개서 맡길 것인가&lt;/b&gt;를 정리해보겠습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;왜 작업 단위를 쪼개야 할까&quot; data-ke-size=&quot;size26&quot;&gt;왜 작업 단위를 쪼개야 할까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트는 한 번에 많은 파일을 읽고 수정할 수 있습니다. 그래서 오히려 사람이 작업 범위를 명확히 정해주지 않으면 과하게 넓은 변경을 만들기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 요청의 흔한 문제는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항 중 일부만 구현된다.&lt;/li&gt;
&lt;li&gt;정상 케이스는 동작하지만 예외 케이스가 빠진다.&lt;/li&gt;
&lt;li&gt;기존 컴포넌트나 유틸을 재사용하지 않고 새로 만든다.&lt;/li&gt;
&lt;li&gt;테스트가 구현에 맞춰 작성되어 검증력이 약해진다.&lt;/li&gt;
&lt;li&gt;리뷰해야 할 변경 파일이 많아져 사람이 놓치는 부분이 생긴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 단위를 쪼개는 목적은 AI를 느리게 쓰기 위한 것이 아닙니다. &lt;b&gt;검증 가능한 단위로 빠르게 반복하기 위한 것&lt;/b&gt;입니다. 작은 단위로 요청하면 실패했을 때 되돌리기 쉽고, 사람이 의도를 확인하기도 쉽습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;좋은 작업 단위의 기준&quot; data-ke-size=&quot;size26&quot;&gt;좋은 작업 단위의 기준&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업을 쪼갤 때 단순히 &amp;ldquo;작게&amp;rdquo; 나누는 것만으로는 부족합니다. 너무 작게 나누면 매번 설명 비용이 커지고, 너무 크게 나누면 다시 리뷰가 어려워집니다. 실무에서는 다음 네 가지 기준을 사용해볼 수 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;1. 결과를 한 문장으로 설명할 수 있는가&quot; data-ke-size=&quot;size23&quot;&gt;1. 결과를 한 문장으로 설명할 수 있는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 작업 단위는 완료 상태가 분명합니다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;나쁜 예:
- 회원 관리 기능을 개선해줘.

좋은 예:
- 회원 목록 API 응답 타입을 기준으로 `MemberListItem` 타입을 추가하고, 목록 화면에서 해당 타입을 사용하게 바꿔줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 요청은 &amp;ldquo;개선&amp;rdquo;의 의미가 넓습니다. AI가 UI를 바꿀 수도 있고, API 호출 코드를 고칠 수도 있고, 테스트를 추가할 수도 있습니다. 두 번째 요청은 결과가 명확합니다. 타입을 추가하고 목록 화면에 적용하면 됩니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;2. 변경 파일 범위를 예측할 수 있는가&quot; data-ke-size=&quot;size23&quot;&gt;2. 변경 파일 범위를 예측할 수 있는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청하기 전에 어떤 파일이 바뀔지 대략 예상할 수 있어야 합니다. 예상이 안 된다면 작업이 너무 큰 것일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &amp;ldquo;로그인 UX 개선&amp;rdquo;은 범위가 넓지만 다음처럼 나누면 예측 가능합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;로그인 실패 메시지 문구를 상수로 분리한다.&lt;/li&gt;
&lt;li&gt;인증 실패 코드별 메시지 매핑을 추가한다.&lt;/li&gt;
&lt;li&gt;로그인 폼 테스트에 실패 케이스를 추가한다.&lt;/li&gt;
&lt;li&gt;문서에 에러 코드 표를 정리한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단계는 바뀔 파일을 어느 정도 예상할 수 있습니다. AI가 예상 밖의 파일을 수정하면 리뷰에서 바로 확인할 수 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;3. 검증 명령이 있는가&quot; data-ke-size=&quot;size23&quot;&gt;3. 검증 명령이 있는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 맡길 작업은 가능하면 검증 명령과 함께 줘야 합니다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;검증:
- pnpm typecheck
- pnpm test MemberList
- pnpm lint src/features/member
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증 명령이 있으면 AI도 작업 완료 기준을 이해하기 쉽고, 사람도 결과를 확인하기 좋습니다. 모든 작업에 완벽한 자동 검증이 있는 것은 아니지만, 타입 체크나 관련 테스트처럼 최소한의 안전장치는 지정하는 편이 좋습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;4. 되돌릴 수 있는가&quot; data-ke-size=&quot;size23&quot;&gt;4. 되돌릴 수 있는가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 단위가 좋으면 실패했을 때 해당 변경만 되돌릴 수 있습니다. 반대로 여러 목적이 섞인 작업은 되돌리기가 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 두 작업을 한 번에 맡기면 위험합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원 목록 리팩터링&lt;/li&gt;
&lt;li&gt;디자인 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩터링은 잘됐지만 디자인이 마음에 들지 않을 수도 있고, 디자인은 괜찮지만 리팩터링이 기존 동작을 깨뜨릴 수도 있습니다. 두 목적은 커밋도 리뷰도 분리하는 편이 안전합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;실무에서 쓰기 좋은 분해 패턴&quot; data-ke-size=&quot;size26&quot;&gt;실무에서 쓰기 좋은 분해 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트에게 작업을 맡길 때 자주 쓰는 분해 패턴을 정리해보겠습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;패턴 1. 조사 &amp;rarr; 계획 &amp;rarr; 수정&quot; data-ke-size=&quot;size23&quot;&gt;패턴 1. 조사 &amp;rarr; 계획 &amp;rarr; 수정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 수정하게 하지 않고 먼저 조사와 계획을 시키는 방식입니다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;먼저 관련 파일을 읽고 작업 계획만 작성해줘.
아직 파일은 수정하지 마.

목표:
- 회원 목록에서 검색어 필터링 로직을 개선한다.

확인할 것:
- 현재 필터링이 어디에서 수행되는지
- 비슷한 검색 로직이 다른 화면에 있는지
- 테스트 파일이 있는지
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 기존 코드 맥락을 파악해야 하는 작업에 좋습니다. AI가 계획을 제시하면 사람이 범위가 맞는지 확인한 뒤 수정 단계로 넘어갈 수 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;패턴 2. 타입 &amp;rarr; 구현 &amp;rarr; 테스트&quot; data-ke-size=&quot;size23&quot;&gt;패턴 2. 타입 &amp;rarr; 구현 &amp;rarr; 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 구현을 타입, 구현, 테스트 순서로 나누는 방식입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;API 응답 타입과 도메인 타입을 정리한다.&lt;/li&gt;
&lt;li&gt;화면 또는 서비스 로직에 타입을 적용한다.&lt;/li&gt;
&lt;li&gt;정상/빈 상태/에러 케이스 테스트를 추가한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 순서는 프론트엔드와 백엔드 모두에서 유용합니다. 타입을 먼저 정리하면 AI가 구현할 때 기준이 생기고, 테스트를 별도 단계로 두면 구현에 맞춘 약한 테스트를 줄일 수 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;패턴 3. 한 파일 리팩터링 &amp;rarr; 호출부 확장&quot; data-ke-size=&quot;size23&quot;&gt;패턴 3. 한 파일 리팩터링 &amp;rarr; 호출부 확장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 유틸이나 컴포넌트를 바꿀 때는 먼저 한 파일에서 안전하게 리팩터링한 뒤 호출부를 넓히는 편이 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;1단계:
- `formatDate` 함수에 timezone 옵션을 추가한다.
- 기존 호출부는 아직 바꾸지 않는다.
- 단위 테스트만 추가한다.

2단계:
- 관리자 화면의 날짜 표시 3곳만 새 옵션을 사용하도록 바꾼다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 공통 코드 변경의 영향 범위를 나눠서 볼 수 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;패턴 4. 실패 재현 &amp;rarr; 최소 수정 &amp;rarr; 회귀 테스트&quot; data-ke-size=&quot;size23&quot;&gt;패턴 4. 실패 재현 &amp;rarr; 최소 수정 &amp;rarr; 회귀 테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버그 수정은 특히 작게 나누는 것이 좋습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;실패를 재현하는 테스트를 먼저 추가한다.&lt;/li&gt;
&lt;li&gt;테스트가 실패하는 것을 확인한다.&lt;/li&gt;
&lt;li&gt;최소한의 코드만 수정한다.&lt;/li&gt;
&lt;li&gt;관련 테스트를 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 &amp;ldquo;버그 고쳐줘&amp;rdquo;라고만 하면 원인을 우회하거나 테스트를 약하게 만들 수 있습니다. 재현 테스트를 먼저 요구하면 문제의 기준이 분명해집니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;요청 템플릿 예시&quot; data-ke-size=&quot;size26&quot;&gt;요청 템플릿 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 템플릿은 AI 에이전트에게 작은 작업을 맡길 때 바로 사용할 수 있는 형태입니다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;목표:
- [한 문장으로 완료 상태를 설명]

범위:
- 수정 가능: [파일/폴더]
- 수정 금지: [건드리면 안 되는 파일/영역]

진행 방식:
1. 관련 파일을 읽고 현재 구조를 요약한다.
2. 변경 계획을 3줄 이내로 제시한다.
3. 계획이 맞으면 수정한다.
4. 변경 파일과 이유를 정리한다.

검증:
- [typecheck/test/lint 명령]

주의:
- 새 패턴을 만들기보다 기존 패턴을 우선 사용한다.
- 관련 없는 포맷 변경은 하지 않는다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 부분은 &amp;ldquo;수정 금지&amp;rdquo;입니다. AI에게 할 일만 말하면 해결을 위해 주변 파일을 넓게 수정할 수 있습니다. 건드리지 말아야 할 파일을 함께 알려주면 변경 범위가 훨씬 안정됩니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;작업 단위를 나누는 체크리스트&quot; data-ke-size=&quot;size26&quot;&gt;작업 단위를 나누는 체크리스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 요청하기 전에 아래 항목을 확인해보면 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-task=&quot;&quot;&gt;완료 상태를 한 문장으로 말할 수 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;예상 변경 파일을 대략 적을 수 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;수정 금지 영역을 정했는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;검증 명령이 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;테스트와 구현을 한 번에 맡겨도 괜찮은 작업인가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;실패했을 때 되돌리기 쉬운가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;리뷰할 사람이 10분 안에 변경 의도를 이해할 수 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체크가 많이 비어 있다면 바로 구현을 맡기기보다 조사나 계획 단계부터 요청하는 편이 안전합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;결론: AI에게도 작은 PR이 필요하다&quot; data-ke-size=&quot;size26&quot;&gt;결론: AI에게도 작은 PR이 필요하다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트는 큰 일을 빠르게 처리할 수 있지만, 실무에서 중요한 것은 빠른 초안보다 안정적인 반복입니다. 작업 단위를 잘 쪼개면 AI 결과물을 더 쉽게 리뷰할 수 있고, 테스트와 커밋 단위도 자연스럽게 정리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에 AI에게 큰 기능을 맡기고 싶다면 바로 &amp;ldquo;전부 만들어줘&amp;rdquo;라고 하기 전에 먼저 이렇게 물어보면 좋습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업을 검증 가능한 3~5개의 작은 단계로 나눠줘. 각 단계의 예상 변경 파일과 검증 방법도 함께 적어줘.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI를 잘 쓰는 핵심은 더 멋진 프롬프트 한 줄이 아니라, 사람이 리뷰하고 되돌릴 수 있는 단위로 일을 설계하는 데 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/44&quot; data-tooltip-position=&quot;top&quot;&gt;AI 코딩 에이전트 제대로 쓰기: AGENTS.md 작성법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/46&quot; data-tooltip-position=&quot;top&quot;&gt;Vibe Coding에서 Spec-Driven Development로: AI에게 제대로 개발시키는 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/47&quot; data-tooltip-position=&quot;top&quot;&gt;AI 코딩 에이전트와 테스트 주도 개발: 테스트부터 맡기는 실무 흐름&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/49&quot; data-tooltip-position=&quot;top&quot;&gt;AI 코딩 에이전트 결과물 리뷰하기: 실무 코드 리뷰 체크리스트&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI에이전트</category>
      <category>AI코딩</category>
      <category>개발생산성</category>
      <category>실무개발</category>
      <category>코드리뷰</category>
      <category>프롬프트</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/52</guid>
      <comments>https://mishka.tistory.com/52#entry52comment</comments>
      <pubDate>Wed, 27 May 2026 22:40:47 +0900</pubDate>
    </item>
    <item>
      <title>AI 코딩 에이전트에게 큰 일을 맡기기 전에: 작은 PR 단위로 작업 쪼개기</title>
      <link>https://mishka.tistory.com/51</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트를 쓰다 보면 가장 먼저 체감하는 장점은 속도입니다. &amp;ldquo;로그인 기능 만들어줘&amp;rdquo;, &amp;ldquo;관리자 페이지 붙여줘&amp;rdquo;, &amp;ldquo;이 버그 고쳐줘&amp;rdquo;처럼 요청하면 코드 초안이 빠르게 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실무 프로젝트에서는 빠른 생성보다 중요한 것이 있습니다. 바로 &lt;b&gt;수정 범위를 통제하는 것&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 한 번에 너무 큰 작업을 맡으면 다음 문제가 자주 생깁니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구하지 않은 파일까지 수정한다.&lt;/li&gt;
&lt;li&gt;기존 프로젝트 구조와 다른 패턴을 만든다.&lt;/li&gt;
&lt;li&gt;핵심 기능은 만들었지만 예외 처리가 빠진다.&lt;/li&gt;
&lt;li&gt;리뷰해야 할 변경량이 너무 커진다.&lt;/li&gt;
&lt;li&gt;실패했을 때 어디서부터 되돌려야 할지 애매해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 AI 코딩 에이전트를 실무에 잘 쓰려면 &amp;ldquo;큰 기능을 한 번에 맡기는 능력&amp;rdquo;보다 &lt;b&gt;작고 검증 가능한 작업으로 쪼개는 능력&lt;/b&gt;이 더 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 AI 에이전트에게 일을 맡길 때 기능을 작은 PR 단위로 나누는 방법을 정리해보겠습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;왜 작은 PR 단위가 중요할까?&quot; data-ke-size=&quot;size26&quot;&gt;왜 작은 PR 단위가 중요할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람 개발자에게도 큰 PR은 리뷰하기 어렵습니다. 파일이 수십 개 바뀌고, UI&amp;middot;API&amp;middot;상태 관리&amp;middot;테스트&amp;middot;리팩터링이 한 번에 섞이면 변경 의도를 파악하기 힘듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트가 만든 큰 변경은 더 조심해야 합니다. AI는 사용자의 의도를 바탕으로 빈 부분을 추론합니다. 문제는 이 추론이 항상 프로젝트의 실제 규칙과 맞지 않는다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &amp;ldquo;회원 관리 기능을 만들어줘&amp;rdquo;라고 요청하면 AI는 다음 작업을 한 번에 하려고 할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라우트 추가&lt;/li&gt;
&lt;li&gt;페이지 UI 작성&lt;/li&gt;
&lt;li&gt;API 클라이언트 작성&lt;/li&gt;
&lt;li&gt;상태 관리 추가&lt;/li&gt;
&lt;li&gt;테이블 컴포넌트 생성&lt;/li&gt;
&lt;li&gt;권한 체크 추가&lt;/li&gt;
&lt;li&gt;테스트 작성&lt;/li&gt;
&lt;li&gt;기존 폴더 구조 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겉으로는 생산적이어 보이지만, 리뷰하는 입장에서는 위험합니다. 변경 범위가 크면 작은 오류도 숨어들기 쉽고, 마음에 들지 않는 방향으로 구현됐을 때 되돌리기도 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 PR 단위로 나누면 장점이 분명합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AI가 임의로 판단할 영역이 줄어듭니다.&lt;/li&gt;
&lt;li&gt;리뷰할 변경량이 작아집니다.&lt;/li&gt;
&lt;li&gt;테스트와 검증 기준이 명확해집니다.&lt;/li&gt;
&lt;li&gt;실패해도 되돌리기 쉽습니다.&lt;/li&gt;
&lt;li&gt;다음 작업으로 이어가기 편합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 작은 PR은 AI의 속도를 늦추기 위한 장치가 아니라 &lt;b&gt;AI의 결과물을 안전하게 받아들이기 위한 가드레일&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;좋은 작업 단위의 기준&quot; data-ke-size=&quot;size26&quot;&gt;좋은 작업 단위의 기준&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 맡기기 좋은 작업은 &amp;ldquo;작다&amp;rdquo;는 말만으로는 부족합니다. 좋은 작업 단위는 다음 조건을 만족해야 합니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;1. 변경 목적이 하나여야 한다&quot; data-ke-size=&quot;size23&quot;&gt;1. 변경 목적이 하나여야 한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 작업에는 하나의 목적만 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나쁜 예시는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원 목록 페이지를 만들고, API도 붙이고, 권한 처리도 하고, 디자인도 다듬고, 테스트도 작성해줘.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 예시는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Table 컴포넌트를 사용해서 회원 목록 페이지의 정적 UI만 작성해줘. API 연동은 하지 말고 목 데이터로만 구성해줘.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목적이 하나이면 AI가 판단해야 할 범위가 줄어듭니다. 리뷰어도 &amp;ldquo;이번 변경이 UI 구조만 다루는지&amp;rdquo; 확인하면 됩니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;2. 완료 기준이 있어야 한다&quot; data-ke-size=&quot;size23&quot;&gt;2. 완료 기준이 있어야 한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 &amp;ldquo;적당히 만들어줘&amp;rdquo;라고 하면 AI는 그럴듯한 결과를 만듭니다. 하지만 실무에서는 &amp;ldquo;그럴듯함&amp;rdquo;보다 &amp;ldquo;완료 기준&amp;rdquo;이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 UI 작업이라면 완료 기준을 이렇게 적을 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/members 경로에서 회원 목록 화면이 보여야 한다.&lt;/li&gt;
&lt;li&gt;기존 Button, Input, Table 컴포넌트를 재사용해야 한다.&lt;/li&gt;
&lt;li&gt;새 API 호출 코드는 작성하지 않는다.&lt;/li&gt;
&lt;li&gt;반응형은 최소 768px 이상 화면에서 깨지지 않으면 된다.&lt;/li&gt;
&lt;li&gt;변경 파일 목록을 마지막에 요약한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 기준을 주면 AI의 결과물을 검증하기 쉬워집니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;3. 되돌릴 수 있어야 한다&quot; data-ke-size=&quot;size23&quot;&gt;3. 되돌릴 수 있어야 한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 작업 단위는 실패했을 때 되돌리기 쉽습니다. 특정 컴포넌트 하나, 테스트 하나, API 함수 하나처럼 경계가 분명해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 다음 작업은 되돌리기 어렵습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 폴더 구조 변경&lt;/li&gt;
&lt;li&gt;상태 관리 라이브러리 교체&lt;/li&gt;
&lt;li&gt;디자인 시스템 전면 수정&lt;/li&gt;
&lt;li&gt;여러 페이지의 공통 로직 동시 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 작업은 AI에게 맡기더라도 먼저 설계 문서나 변경 계획을 만들고, 실제 구현은 여러 단계로 나누는 것이 좋습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;기능을 작은 PR로 쪼개는 예시&quot; data-ke-size=&quot;size26&quot;&gt;기능을 작은 PR로 쪼개는 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &amp;ldquo;회원 관리 페이지&amp;rdquo;를 만든다고 해보겠습니다. 한 번에 요청하면 너무 큽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 요청은 이렇게 생겼습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자용 회원 관리 페이지를 만들어줘. 회원 목록을 보여주고, 검색하고, 상세 페이지도 볼 수 있게 해줘.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 요청을 작은 작업으로 나누면 다음과 같습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;1단계: 화면 구조만 만들기&quot; data-ke-size=&quot;size23&quot;&gt;1단계: 화면 구조만 만들기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/admin/members 라우트 추가&lt;/li&gt;
&lt;li&gt;페이지 제목, 검색 영역, 테이블 영역 배치&lt;/li&gt;
&lt;li&gt;목 데이터 사용&lt;/li&gt;
&lt;li&gt;API 연동 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 줄 요청 예시는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;회원 관리 기능 중 1단계만 구현해줘.
/admin/members 페이지에 정적 UI를 만든다.
기존 Layout, Button, Input, Table 컴포넌트를 우선 재사용한다.
API 연동은 하지 말고 파일 내부의 목 데이터만 사용한다.
작업 후 변경 파일과 의도하지 않은 수정이 있었는지 요약해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-heading=&quot;2단계: 조회 API 함수 추가&quot; data-ke-size=&quot;size23&quot;&gt;2단계: 조회 API 함수 추가&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;getMembers API 함수 작성&lt;/li&gt;
&lt;li&gt;타입 정의&lt;/li&gt;
&lt;li&gt;에러 응답 타입 정리&lt;/li&gt;
&lt;li&gt;화면 연결은 아직 하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;이번 작업에서는 회원 목록 조회 API 함수만 추가해줘.
UI 파일은 수정하지 않는다.
API 경로는 GET /api/admin/members 를 사용한다.
응답 타입은 MemberListResponse로 정의한다.
테스트 가능한 순수 함수 형태를 유지해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-heading=&quot;3단계: UI와 API 연결&quot; data-ke-size=&quot;size23&quot;&gt;3단계: UI와 API 연결&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 목 데이터 제거&lt;/li&gt;
&lt;li&gt;로딩 상태 추가&lt;/li&gt;
&lt;li&gt;실패 메시지 추가&lt;/li&gt;
&lt;li&gt;성공 시 테이블 렌더링&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-heading=&quot;4단계: 검색 조건 추가&quot; data-ke-size=&quot;size23&quot;&gt;4단계: 검색 조건 추가&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색어 상태 추가&lt;/li&gt;
&lt;li&gt;검색 버튼 클릭 시 API 재호출&lt;/li&gt;
&lt;li&gt;빈 검색어 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-heading=&quot;5단계: 테스트 작성&quot; data-ke-size=&quot;size23&quot;&gt;5단계: 테스트 작성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목록 렌더링 테스트&lt;/li&gt;
&lt;li&gt;로딩 상태 테스트&lt;/li&gt;
&lt;li&gt;API 실패 상태 테스트&lt;/li&gt;
&lt;li&gt;검색 조건 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나누면 각 단계가 하나의 작은 PR이 됩니다. AI가 실수하더라도 어느 단계에서 문제가 생겼는지 찾기 쉽습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;AI에게 작업을 맡길 때 포함하면 좋은 문장&quot; data-ke-size=&quot;size26&quot;&gt;AI에게 작업을 맡길 때 포함하면 좋은 문장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업을 작게 나누는 것만큼 중요한 것이 요청 문장입니다. 다음 문장들은 AI 에이전트에게 일을 맡길 때 유용합니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;수정 범위 제한&quot; data-ke-size=&quot;size23&quot;&gt;수정 범위 제한&lt;/h3&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;이번 작업에서는 아래 파일만 수정해줘.
- src/pages/admin/members.tsx
- src/components/members/MemberTable.tsx
다른 파일 수정이 필요하면 먼저 이유를 설명하고 작업을 멈춰줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-heading=&quot;구현 제외 항목 명시&quot; data-ke-size=&quot;size23&quot;&gt;구현 제외 항목 명시&lt;/h3&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;이번 단계에서는 API 연동, 권한 처리, 테스트 작성은 하지 않는다.
정적 UI 구성만 완료한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-heading=&quot;기존 패턴 우선 사용&quot; data-ke-size=&quot;size23&quot;&gt;기존 패턴 우선 사용&lt;/h3&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;새로운 상태 관리 방식이나 UI 라이브러리를 추가하지 말고,
기존 프로젝트에서 사용 중인 패턴을 먼저 찾아서 그대로 따라줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-heading=&quot;검증 명령 지정&quot; data-ke-size=&quot;size23&quot;&gt;검증 명령 지정&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;작업 후 다음 명령을 실행해줘.
- pnpm typecheck
- pnpm lint
테스트를 실행하지 못했다면 이유를 남겨줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-heading=&quot;결과 요약 요청&quot; data-ke-size=&quot;size23&quot;&gt;결과 요약 요청&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;마지막에 다음 형식으로 요약해줘.
1. 변경 파일
2. 구현한 내용
3. 구현하지 않은 내용
4. 확인한 명령
5. 다음 작업 제안
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문장을 반복해서 사용하면 AI가 더 예측 가능한 방식으로 작업합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;작은 PR 체크리스트&quot; data-ke-size=&quot;size26&quot;&gt;작은 PR 체크리스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트에게 작업을 맡기기 전에 아래 체크리스트를 확인하면 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-task=&quot;&quot;&gt;이번 작업의 목적이 하나인가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;수정 가능한 파일 범위가 정해져 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;구현하지 않을 항목을 명시했는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;완료 기준이 문장으로 적혀 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;검증 명령이 정해져 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;실패했을 때 되돌리기 쉬운가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;다음 작업과의 경계가 분명한가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 2~3개 이상이 애매하다면 아직 AI에게 구현을 맡기기보다, 먼저 작업 계획을 더 쪼개는 편이 좋습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;AGENTS.md와 Spec 문서를 함께 쓰기&quot; data-ke-size=&quot;size26&quot;&gt;AGENTS.md와 Spec 문서를 함께 쓰기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 글에서 다룬 것처럼 AGENTS.md는 AI 에이전트에게 프로젝트의 공통 규칙을 알려주는 파일입니다. 예를 들어 패키지 매니저, 테스트 명령, 코드 스타일, 금지할 작업 등을 적어둘 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 작은 PR 단위의 작업 지시는 특정 기능에 대한 세부 요청입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할을 나누면 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AGENTS.md: 프로젝트 전체 규칙&lt;/li&gt;
&lt;li&gt;기능 Spec 문서: 기능의 요구사항과 완료 기준&lt;/li&gt;
&lt;li&gt;작업 프롬프트: 이번 단계에서 수행할 작은 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트는 이 세 가지가 함께 있을 때 훨씬 안정적으로 동작합니다. 프로젝트 규칙을 알고, 기능 요구사항을 이해하고, 지금 해야 할 작은 작업만 수행하기 때문입니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;결론: AI에게 일을 잘 시키는 핵심은 작업을 작게 만드는 것&quot; data-ke-size=&quot;size26&quot;&gt;결론: AI에게 일을 잘 시키는 핵심은 작업을 작게 만드는 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트는 빠릅니다. 하지만 빠르다는 이유로 큰 작업을 한 번에 맡기면 리뷰와 유지보수 비용이 커질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 중요한 것은 AI가 코드를 많이 쓰게 하는 것이 아니라, &lt;b&gt;작고 검증 가능한 단위로 안전하게 변경하게 만드는 것&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AI에게 큰 기능을 한 번에 맡기면 수정 범위가 커질 수 있습니다.&lt;/li&gt;
&lt;li&gt;작은 PR 단위는 리뷰와 롤백을 쉽게 만듭니다.&lt;/li&gt;
&lt;li&gt;좋은 작업 단위는 목적이 하나이고 완료 기준이 분명해야 합니다.&lt;/li&gt;
&lt;li&gt;구현하지 않을 항목을 명시하면 AI의 과잉 구현을 줄일 수 있습니다.&lt;/li&gt;
&lt;li&gt;AGENTS.md, Spec 문서, 작업 프롬프트를 나누어 관리하면 효과적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에 AI 에이전트에게 기능 구현을 맡길 때는 바로 &amp;ldquo;만들어줘&amp;rdquo;라고 말하기보다, 먼저 이렇게 물어보는 것이 좋습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능을 리뷰 가능한 작은 PR 단위로 나누면 어떻게 될까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문 하나가 AI 코딩의 품질을 크게 바꿀 수 있습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;SEO 메타 설명 후보&quot; data-ke-size=&quot;size26&quot;&gt;SEO 메타 설명 후보&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트가 과도한 파일 수정이나 요구사항 누락을 만들지 않도록, 기능을 작은 PR 단위로 쪼개고 검증하는 실무 방법을 정리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/46&quot; data-tooltip-position=&quot;top&quot;&gt;Vibe Coding에서 Spec-Driven Development로: AI에게 제대로 개발시키는 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/45&quot; data-tooltip-position=&quot;top&quot;&gt;MCP 서버 만들기 입문: AI 에이전트가 내 API를 호출하게 하는 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/&quot; data-tooltip-position=&quot;top&quot;&gt;Mishka's 블로그 메인&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Agents.md</category>
      <category>AI</category>
      <category>ai코딩에이전트</category>
      <category>Spec-DrivenDevelopment</category>
      <category>개발프로세스</category>
      <category>코드리뷰</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/51</guid>
      <comments>https://mishka.tistory.com/51#entry51comment</comments>
      <pubDate>Mon, 25 May 2026 13:58:44 +0900</pubDate>
    </item>
    <item>
      <title>MCP 서버를 만들 때 먼저 정해야 할 것들: 도구 설계와 권한 체크</title>
      <link>https://mishka.tistory.com/50</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;MCP(Model Context Protocol)는 AI 에이전트가 외부 도구와 데이터를 사용할 수 있게 해주는 표준 프로토콜입니다. GitHub 이슈를 조회하거나, 사내 API를 호출하거나, 로컬 문서를 검색하거나, 배포 상태를 확인하는 식의 작업을 AI에게 맡길 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서는 MCP가 무엇인지, 그리고 간단한 MCP 서버를 어떻게 만들 수 있는지 살펴봤습니다. 그런데 실제 프로젝트에 MCP를 붙이려고 하면 코드 작성보다 먼저 고민해야 할 문제가 생깁니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 어떤 도구를 열어줘야 할까?&lt;br /&gt;어디까지 실행하게 해도 안전할까?&lt;br /&gt;실패했을 때는 어떻게 처리해야 할까?&lt;br /&gt;민감한 데이터는 어떻게 막아야 할까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 서버는 단순히 API를 감싸는 코드가 아닙니다. AI 에이전트에게 &amp;ldquo;무엇을 할 수 있는지&amp;rdquo;를 허용하는 경계면입니다. 그래서 처음 설계가 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 MCP 서버를 만들기 전에 정해야 할 도구 설계, 권한, 입력 검증, 로그 기준을 실무 관점에서 정리해보겠습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;MCP 서버 설계에서 가장 위험한 착각&quot; data-ke-size=&quot;size26&quot;&gt;MCP 서버 설계에서 가장 위험한 착각&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP를 처음 접하면 기존 API를 그대로 도구로 노출하면 된다고 생각하기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 사내 관리자 API에 이런 엔드포인트가 있다고 해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;GET /orders
GET /orders/:id
POST /orders/:id/cancel
POST /users/:id/point
DELETE /products/:id
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 API들을 그대로 MCP 도구로 만들면 AI는 주문 조회, 주문 취소, 포인트 지급, 상품 삭제 같은 작업을 할 수 있게 됩니다. 기술적으로는 가능합니다. 하지만 실무에서는 매우 위험합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 사용자의 요청을 해석해서 도구를 호출합니다. 그 과정에서 문맥을 잘못 이해하거나, 애매한 요청을 과하게 해석하거나, 예상보다 넓은 범위의 작업을 시도할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 MCP 서버는 &amp;ldquo;가능한 모든 API를 연결하는 곳&amp;rdquo;이 아니라 &lt;b&gt;AI에게 안전하게 맡길 수 있는 작업만 선별해서 제공하는 곳&lt;/b&gt;이어야 합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;1. 도구 범위부터 작게 정하기&quot; data-ke-size=&quot;size26&quot;&gt;1. 도구 범위부터 작게 정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 MCP 서버를 만들 때는 도구를 작게 시작하는 것이 좋습니다. 특히 쓰기 작업보다 읽기 작업부터 시작하는 편이 안전합니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;안전한 시작 예시&quot; data-ke-size=&quot;size23&quot;&gt;안전한 시작 예시&lt;/h3&gt;
&lt;pre class=&quot;ceylon&quot;&gt;&lt;code&gt;list_recent_orders
get_order_detail
search_customer_by_email
get_deployment_status
list_open_github_issues
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 도구들은 데이터를 조회하는 역할을 합니다. 잘못 호출되더라도 시스템 상태를 바꾸지는 않습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;신중해야 하는 도구 예시&quot; data-ke-size=&quot;size23&quot;&gt;신중해야 하는 도구 예시&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;cancel_order
refund_payment
delete_user
update_product_price
run_production_deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 도구들은 실제 비즈니스 상태를 바꿉니다. MCP로 제공할 수는 있지만, 승인 절차나 권한 체크 없이 바로 실행되면 위험합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 다음 원칙을 추천합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1단계: 조회 도구만 제공한다.&lt;/li&gt;
&lt;li&gt;2단계: 임시 데이터나 개발 환경에서만 쓰기 도구를 테스트한다.&lt;/li&gt;
&lt;li&gt;3단계: 운영 쓰기 도구는 승인/확인 절차를 둔다.&lt;/li&gt;
&lt;li&gt;4단계: 위험 작업은 사람이 최종 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-heading=&quot;2. 도구 이름은 구체적으로 짓기&quot; data-ke-size=&quot;size26&quot;&gt;2. 도구 이름은 구체적으로 짓기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 도구를 잘 사용하려면 도구 이름과 설명이 중요합니다. 이름이 애매하면 AI가 잘못 선택할 가능성이 높아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나쁜 예시는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;handle_order
process_user
manage_product
run_task
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름만 봐서는 무엇을 하는 도구인지 알기 어렵습니다. 반면 다음처럼 구체적으로 짓는 것이 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;ceylon&quot;&gt;&lt;code&gt;list_recent_orders
get_order_detail_by_order_id
search_users_by_email
create_draft_refund_request
check_production_deployment_status
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도구 이름에는 가능하면 다음 정보를 담습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동작: list, get, search, create, update, delete, check&lt;/li&gt;
&lt;li&gt;대상: order, user, issue, deployment&lt;/li&gt;
&lt;li&gt;조건: by_email, by_order_id, recent, open&lt;/li&gt;
&lt;li&gt;위험도: draft, preview, request 같은 단어로 즉시 실행이 아님을 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 환불을 바로 실행하지 않고 요청 초안만 만드는 도구라면 refund_payment보다 create_draft_refund_request가 안전합니다. AI도 이 도구가 즉시 환불을 실행하는 것이 아니라 초안을 만드는 것임을 더 잘 이해할 수 있습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;3. 입력 스키마는 엄격하게 제한하기&quot; data-ke-size=&quot;size26&quot;&gt;3. 입력 스키마는 엄격하게 제한하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 도구의 입력값은 가능한 한 명확하고 제한적으로 정의해야 합니다. 자유 텍스트 하나로 모든 것을 처리하게 하면 AI가 예측하기 어려운 값을 넣을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 스키마는 위험합니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;query&quot;: &quot;string&quot;,
  &quot;action&quot;: &quot;string&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 형태는 유연하지만 너무 넓습니다. 대신 도구 목적에 맞게 입력을 나누는 것이 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;orderId&quot;: &quot;string&quot;,
  &quot;includePayment&quot;: &quot;boolean&quot;,
  &quot;includeCustomerMemo&quot;: &quot;boolean&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 검색 도구라면 다음처럼 제한할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;keyword&quot;: &quot;string&quot;,
  &quot;status&quot;: &quot;open | closed | all&quot;,
  &quot;limit&quot;: &quot;number&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 것은 limit 같은 값에도 상한을 두는 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 번에 10개만 조회&lt;/li&gt;
&lt;li&gt;최대 50개까지만 허용&lt;/li&gt;
&lt;li&gt;날짜 범위는 최대 30일&lt;/li&gt;
&lt;li&gt;민감 필드는 기본 제외&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 실수로 너무 넓은 조회를 하지 않도록 서버 쪽에서 방어해야 합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;4. 권한은 MCP 서버에서 다시 확인하기&quot; data-ke-size=&quot;size26&quot;&gt;4. 권한은 MCP 서버에서 다시 확인하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 API에 권한 체크가 있더라도 MCP 서버에서도 최소한의 권한 정책을 가져가는 것이 좋습니다. MCP 서버는 AI가 사용하는 별도의 진입점이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인해야 할 기준은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 사용자가 이 도구를 호출할 수 있는가?&lt;/li&gt;
&lt;li&gt;이 환경에서 이 도구를 실행해도 되는가?&lt;/li&gt;
&lt;li&gt;이 리소스에 접근할 권한이 있는가?&lt;/li&gt;
&lt;li&gt;쓰기 작업이라면 추가 승인이 필요한가?&lt;/li&gt;
&lt;li&gt;호출 횟수나 조회 범위 제한이 필요한가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 배포 상태 조회는 누구나 가능하지만, 운영 배포 실행은 특정 역할만 가능하게 할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;get_deployment_status: developer 이상 허용
create_deployment_plan: maintainer 이상 허용
run_production_deploy: MCP에서 직접 실행 금지, 승인 링크만 생성
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 것은 AI에게 권한 판단을 맡기지 않는 것입니다. AI가 &amp;ldquo;이 사용자는 아마 권한이 있을 것&amp;rdquo;이라고 판단하게 해서는 안 됩니다. 권한은 항상 서버에서 결정해야 합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;5. 쓰기 작업은 preview와 execute를 분리하기&quot; data-ke-size=&quot;size26&quot;&gt;5. 쓰기 작업은 preview와 execute를 분리하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 환경에서 쓰기 작업을 제공해야 한다면 preview와 execute를 분리하는 패턴이 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 상품 가격 변경을 생각해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;preview_product_price_change
execute_product_price_change
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 도구는 변경될 내용을 계산해서 보여줍니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;productId&quot;: &quot;P1004&quot;,
  &quot;currentPrice&quot;: 30000,
  &quot;newPrice&quot;: 27000,
  &quot;diff&quot;: -3000,
  &quot;affectedOptions&quot;: 3,
  &quot;requiresApproval&quot;: true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 도구는 실제 변경을 실행합니다. 이때는 다음 조건을 요구할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;preview 결과의 requestId가 있어야 한다.&lt;/li&gt;
&lt;li&gt;사용자가 명시적으로 승인해야 한다.&lt;/li&gt;
&lt;li&gt;일정 시간이 지나면 requestId가 만료된다.&lt;/li&gt;
&lt;li&gt;운영 환경에서는 특정 역할만 실행할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 사용하면 AI가 바로 위험한 작업을 실행하는 일을 줄일 수 있습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;6. 로그는 반드시 남기기&quot; data-ke-size=&quot;size26&quot;&gt;6. 로그는 반드시 남기기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 서버는 AI가 외부 시스템과 연결되는 지점이므로 로그가 중요합니다. 나중에 문제가 생겼을 때 &amp;ldquo;AI가 어떤 도구를 어떤 입력으로 호출했는지&amp;rdquo; 추적할 수 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 남기면 좋은 정보는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호출 시간&lt;/li&gt;
&lt;li&gt;사용자 또는 세션 ID&lt;/li&gt;
&lt;li&gt;호출한 도구 이름&lt;/li&gt;
&lt;li&gt;입력 파라미터&lt;/li&gt;
&lt;li&gt;성공/실패 여부&lt;/li&gt;
&lt;li&gt;응답 요약&lt;/li&gt;
&lt;li&gt;실행 시간&lt;/li&gt;
&lt;li&gt;쓰기 작업의 변경 대상&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 로그에 민감한 값을 그대로 남기면 안 됩니다. 토큰, 비밀번호, 개인정보, 결제 정보는 마스킹해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;email: mi***@example.com
accessToken: [REDACTED]
cardNumber: [REDACTED]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그는 디버깅 목적뿐 아니라 보안 감사에도 필요합니다. 특히 AI가 운영 도구를 호출할 수 있다면 로그는 선택이 아니라 필수입니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;7. 실패 메시지는 AI가 이해할 수 있게 작성하기&quot; data-ke-size=&quot;size26&quot;&gt;7. 실패 메시지는 AI가 이해할 수 있게 작성하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 도구가 실패했을 때 단순히 500 error만 반환하면 AI는 다음 행동을 정하기 어렵습니다. 실패 원인을 구조화해서 알려주는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 실패 응답은 다음 정보를 포함합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실패 코드&lt;/li&gt;
&lt;li&gt;사람이 읽을 수 있는 메시지&lt;/li&gt;
&lt;li&gt;재시도 가능 여부&lt;/li&gt;
&lt;li&gt;사용자가 해야 할 조치&lt;/li&gt;
&lt;li&gt;AI가 하면 안 되는 행동&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;ok&quot;: false,
  &quot;errorCode&quot;: &quot;PERMISSION_DENIED&quot;,
  &quot;message&quot;: &quot;현재 사용자는 운영 배포를 실행할 권한이 없습니다.&quot;,
  &quot;retryable&quot;: false,
  &quot;nextAction&quot;: &quot;배포 권한이 있는 관리자에게 승인을 요청하세요. 토큰 입력이나 우회 실행을 시도하지 마세요.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 응답은 AI가 불필요하게 재시도하거나 우회 방법을 찾는 일을 줄여줍니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;MCP 서버 설계 체크리스트&quot; data-ke-size=&quot;size26&quot;&gt;MCP 서버 설계 체크리스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 MCP 서버를 만들기 전에 확인할 체크리스트를 정리해보겠습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;도구 설계&quot; data-ke-size=&quot;size23&quot;&gt;도구 설계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-task=&quot;&quot;&gt;도구가 너무 넓은 역할을 하지 않는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;읽기 도구와 쓰기 도구가 분리되어 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;도구 이름만 봐도 동작을 이해할 수 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;위험 작업은 preview/request/execute 단계로 나뉘어 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;AI에게 필요 없는 API까지 노출하지 않았는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-heading=&quot;입력 검증&quot; data-ke-size=&quot;size23&quot;&gt;입력 검증&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-task=&quot;&quot;&gt;입력 스키마가 구체적인가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;enum, boolean, limit 같은 제한이 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;날짜 범위나 조회 개수에 상한이 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;자유 텍스트 입력을 최소화했는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;서버에서 한 번 더 검증하는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-heading=&quot;권한과 보안&quot; data-ke-size=&quot;size23&quot;&gt;권한과 보안&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-task=&quot;&quot;&gt;사용자 권한을 MCP 서버에서 확인하는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;운영 환경 쓰기 작업은 추가 승인이 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;민감 데이터는 기본 응답에서 제외되는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;토큰과 비밀값이 로그에 남지 않는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;실패 시 인증 우회를 유도하지 않는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-heading=&quot;운영과 관찰&quot; data-ke-size=&quot;size23&quot;&gt;운영과 관찰&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-task=&quot;&quot;&gt;도구 호출 로그가 남는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;실패 원인이 구조화되어 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;호출 횟수 제한이 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;장애 시 안전하게 실패하는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;문제가 생겼을 때 특정 호출을 추적할 수 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-heading=&quot;마무리&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP는 AI 에이전트를 실제 업무 시스템과 연결해주는 강력한 방식입니다. 하지만 강력한 만큼 설계를 대충 하면 위험해질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 MCP 서버는 많은 일을 할 수 있는 서버가 아닙니다. &lt;b&gt;AI에게 맡겨도 되는 일을 명확하게 제한하고, 위험한 작업은 안전한 절차로 감싸는 서버&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 완벽한 MCP 플랫폼을 만들 필요는 없습니다. 작은 조회 도구 하나부터 시작해도 충분합니다. 대신 다음 원칙은 지키는 것이 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도구 범위는 작게 시작한다.&lt;/li&gt;
&lt;li&gt;입력 스키마는 엄격하게 정의한다.&lt;/li&gt;
&lt;li&gt;권한 판단은 AI가 아니라 서버가 한다.&lt;/li&gt;
&lt;li&gt;쓰기 작업은 preview와 execute를 분리한다.&lt;/li&gt;
&lt;li&gt;로그와 실패 메시지를 운영 관점에서 설계한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트가 더 많은 일을 하게 될수록, 개발자는 &amp;ldquo;어떻게 연결할까?&amp;rdquo;뿐 아니라 &amp;ldquo;어디까지 허용할까?&amp;rdquo;를 함께 고민해야 합니다. MCP 서버 설계는 바로 그 경계를 정하는 작업입니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;SEO 메타 설명 후보&quot; data-ke-size=&quot;size26&quot;&gt;SEO 메타 설명 후보&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 서버를 실제 프로젝트에 적용하기 전에 도구 범위, 입력 스키마, 권한 체크, 로그, 실패 정책을 어떻게 설계해야 하는지 실무 체크리스트로 정리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/45&quot; data-tooltip-position=&quot;top&quot;&gt;MCP 서버 만들기 입문: AI 에이전트가 내 API를 호출하게 하는 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/46&quot; data-tooltip-position=&quot;top&quot;&gt;Vibe Coding에서 Spec-Driven Development로: AI에게 제대로 개발시키는 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/44&quot; data-tooltip-position=&quot;top&quot;&gt;AI 코딩 에이전트 제대로 쓰기: AGENTS.md 작성법&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI</category>
      <category>API</category>
      <category>MCP</category>
      <category>권한설계</category>
      <category>보안</category>
      <category>아키텍처</category>
      <category>코딩에이전트</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/50</guid>
      <comments>https://mishka.tistory.com/50#entry50comment</comments>
      <pubDate>Mon, 25 May 2026 13:46:05 +0900</pubDate>
    </item>
    <item>
      <title>AI 코딩 에이전트 결과물 리뷰하기: 실무 코드 리뷰 체크리스트</title>
      <link>https://mishka.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트를 사용하면 개발 속도는 확실히 빨라집니다. 요구사항을 설명하면 컴포넌트, API 호출 코드, 테스트 코드, 리팩토링까지 빠르게 만들어 줍니다. 특히 반복적인 CRUD, 폼 처리, 타입 정의, 테스트 초안처럼 구조가 어느 정도 정해진 작업에서는 체감 효과가 큽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 AI가 만든 코드가 빠르다고 해서 그대로 머지해도 된다는 뜻은 아닙니다. 오히려 AI 코드는 사람이 작성한 코드보다 더 조심해서 봐야 할 때가 많습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항을 일부만 반영했을 수 있습니다.&lt;/li&gt;
&lt;li&gt;기존 프로젝트 규칙과 다른 패턴을 만들 수 있습니다.&lt;/li&gt;
&lt;li&gt;정상 케이스만 처리하고 예외 케이스를 놓칠 수 있습니다.&lt;/li&gt;
&lt;li&gt;테스트는 통과하지만 실제 UX가 어색할 수 있습니다.&lt;/li&gt;
&lt;li&gt;관련 없는 파일을 함께 수정했을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 Vibe Coding의 위험성과 AGENTS.md의 필요성을 이야기했다면, 이번 글에서는 한 단계 더 실무적인 관점에서 &lt;b&gt;AI가 만든 PR을 어떻게 리뷰해야 하는지&lt;/b&gt; 정리해보겠습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;왜 AI 코드 리뷰 기준이 따로 필요할까&quot; data-ke-size=&quot;size26&quot;&gt;왜 AI 코드 리뷰 기준이 따로 필요할까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 작성한 코드 리뷰에서는 보통 로직, 설계, 가독성, 테스트, 성능 등을 확인합니다. AI 코드도 기본적으로는 같은 기준으로 보면 됩니다. 다만 AI 코드에는 몇 가지 특징이 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;1. 그럴듯한 코드가 많이 나온다&quot; data-ke-size=&quot;size23&quot;&gt;1. 그럴듯한 코드가 많이 나온다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 문법적으로 자연스럽고 보기 좋은 코드를 잘 만듭니다. 변수명도 적당하고, 주석도 친절하고, 함수도 그럴듯하게 나눕니다. 그래서 처음 보면 문제가 없어 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제로 확인해보면 요구사항의 핵심 조건이 빠져 있거나, 기존 코드베이스에서는 쓰지 않는 방식으로 구현된 경우가 있습니다. 즉, &lt;b&gt;코드의 모양보다 의도와 조건을 먼저 확인해야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-heading=&quot;2. 기존 맥락을 완전히 이해하지 못할 수 있다&quot; data-ke-size=&quot;size23&quot;&gt;2. 기존 맥락을 완전히 이해하지 못할 수 있다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트가 파일을 읽고 작업하더라도 프로젝트 전체의 역사, 팀의 암묵적인 규칙, 운영 중 발생했던 문제까지 모두 이해하기는 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 기존 프로젝트에서는 특정 API 에러를 공통 핸들러로 처리하고 있는데, AI가 새로운 try/catch를 직접 추가할 수 있습니다. 동작은 할 수 있지만 프로젝트의 일관성은 깨집니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;3. 테스트를 자기 코드에 맞춰버릴 수 있다&quot; data-ke-size=&quot;size23&quot;&gt;3. 테스트를 자기 코드에 맞춰버릴 수 있다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 테스트와 구현을 한 번에 맡기면 테스트가 요구사항을 검증하기보다 구현에 맞춰 작성되는 경우가 있습니다. 이런 테스트는 회귀 방지 효과가 약합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 AI 결과물 리뷰에서는 &amp;ldquo;테스트가 있는가?&amp;rdquo;보다 &lt;b&gt;테스트가 올바른 요구사항을 검증하는가?&lt;/b&gt;를 봐야 합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;리뷰 전에 먼저 확인할 것&quot; data-ke-size=&quot;size26&quot;&gt;리뷰 전에 먼저 확인할 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 만든 변경사항을 리뷰하기 전에 다음 세 가지를 먼저 확인하는 것이 좋습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;1. 변경 파일 범위 확인&quot; data-ke-size=&quot;size23&quot;&gt;1. 변경 파일 범위 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 봐야 할 것은 변경된 파일 목록입니다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;git diff --name-only
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인할 포인트는 단순합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청한 작업과 관련 있는 파일만 수정되었는가?&lt;/li&gt;
&lt;li&gt;설정 파일, lock 파일, 포맷 파일이 불필요하게 바뀌지 않았는가?&lt;/li&gt;
&lt;li&gt;테스트 파일을 삭제하거나 약화시키지 않았는가?&lt;/li&gt;
&lt;li&gt;기존 공통 컴포넌트나 유틸을 과하게 수정하지 않았는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 문제를 해결하려고 하면서 생각보다 넓은 범위의 파일을 수정할 수 있습니다. 특히 &amp;ldquo;테스트를 통과시켜줘&amp;rdquo; 같은 요청을 했을 때 테스트 자체를 바꾸거나, 실패 원인을 우회하는 방식으로 수정하는 경우가 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;2. 요구사항과 결과 비교&quot; data-ke-size=&quot;size23&quot;&gt;2. 요구사항과 결과 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리뷰할 때는 코드부터 보기보다 처음 요청한 요구사항을 다시 읽는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 요구사항이 다음과 같았다고 해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;회원 목록에서 검색어를 입력하면 이름과 이메일 기준으로 필터링한다.
검색어가 비어 있으면 전체 목록을 보여준다.
검색 결과가 없으면 빈 상태 메시지를 보여준다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 리뷰 기준은 다음처럼 바꿀 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름 검색이 되는가?&lt;/li&gt;
&lt;li&gt;이메일 검색이 되는가?&lt;/li&gt;
&lt;li&gt;대소문자 처리는 의도대로 되는가?&lt;/li&gt;
&lt;li&gt;검색어 앞뒤 공백은 처리되는가?&lt;/li&gt;
&lt;li&gt;빈 검색어일 때 전체 목록이 유지되는가?&lt;/li&gt;
&lt;li&gt;결과가 없을 때 빈 상태 UI가 보이는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코드 리뷰는 &amp;ldquo;코드가 잘 짜였는가?&amp;rdquo;보다 먼저 &amp;ldquo;요구사항을 정확히 만족하는가?&amp;rdquo;를 확인해야 합니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;3. 기존 패턴과 비교&quot; data-ke-size=&quot;size23&quot;&gt;3. 기존 패턴과 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 이미 비슷한 코드가 있다면 AI 결과물은 반드시 기존 패턴과 비교해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 호출 방식&lt;/li&gt;
&lt;li&gt;에러 처리 방식&lt;/li&gt;
&lt;li&gt;폼 검증 방식&lt;/li&gt;
&lt;li&gt;상태 관리 방식&lt;/li&gt;
&lt;li&gt;스타일링 방식&lt;/li&gt;
&lt;li&gt;테스트 작성 방식&lt;/li&gt;
&lt;li&gt;파일 네이밍 규칙&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 프로젝트가 React Query를 사용하고 있는데 AI가 useEffect와 fetch로 직접 데이터를 불러오면 동작은 하더라도 유지보수성이 떨어집니다. 기존 컴포넌트가 Storybook 문서화를 하고 있다면 새 컴포넌트도 스토리를 함께 추가하는 것이 좋습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;실무 코드 리뷰 체크리스트&quot; data-ke-size=&quot;size26&quot;&gt;실무 코드 리뷰 체크리스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 체크리스트는 AI가 만든 코드를 PR로 올리기 전, 또는 PR 리뷰 중에 사용할 수 있는 기준입니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;1. 요구사항 체크&quot; data-ke-size=&quot;size23&quot;&gt;1. 요구사항 체크&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-task=&quot;&quot;&gt;요청한 기능이 모두 구현되었는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;명시한 예외 케이스가 빠지지 않았는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;요구하지 않은 기능이 임의로 추가되지 않았는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;UX 문구나 정책이 기존 서비스와 맞는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;사용자가 실제로 겪을 흐름 기준으로 동작하는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 종종 &amp;ldquo;이 정도면 도움이 되겠지&amp;rdquo;라는 방향으로 기능을 추가합니다. 하지만 실무에서는 요구사항 밖의 친절함이 오히려 버그가 될 수 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;2. 변경 범위 체크&quot; data-ke-size=&quot;size23&quot;&gt;2. 변경 범위 체크&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-task=&quot;&quot;&gt;수정 파일이 작업 범위 안에 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;관련 없는 리팩토링이 섞이지 않았는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;공통 유틸이나 디자인 시스템을 불필요하게 바꾸지 않았는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;lock 파일 변경이 필요한 변경인가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;생성된 임시 파일이나 로그 파일이 포함되지 않았는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 작업을 맡길 때는 &amp;ldquo;이번 작업과 무관한 파일은 수정하지 마&amp;rdquo;라고 지시하는 것도 좋습니다. 리뷰 단계에서는 실제로 그 규칙이 지켜졌는지 확인해야 합니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;3. 코드 품질 체크&quot; data-ke-size=&quot;size23&quot;&gt;3. 코드 품질 체크&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-task=&quot;&quot;&gt;함수와 컴포넌트가 너무 많은 책임을 갖고 있지 않은가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;중복 코드가 과하게 생기지 않았는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;변수명과 함수명이 의도를 잘 설명하는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;타입이 적절히 정의되어 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;any, 강제 타입 변환, 무분별한 optional chaining으로 문제를 숨기지 않았는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 타입 에러를 피하기 위해 느슨한 타입을 선택하는 경우가 있습니다. TypeScript 프로젝트라면 특히 타입이 도메인 규칙을 잘 표현하는지 확인해야 합니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;4. 예외 처리 체크&quot; data-ke-size=&quot;size23&quot;&gt;4. 예외 처리 체크&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-task=&quot;&quot;&gt;API 실패 시 사용자에게 적절한 피드백이 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;빈 데이터, null, undefined를 처리하는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;로딩 상태와 중복 제출을 막는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;권한 없음, 인증 만료 같은 케이스를 고려했는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;네트워크 지연이나 실패 상황에서도 UI가 깨지지 않는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 만든 코드는 happy path에 강하고 edge case에 약한 경우가 많습니다. 리뷰에서는 정상 케이스보다 실패 케이스를 더 적극적으로 확인하는 것이 좋습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;5. 테스트 체크&quot; data-ke-size=&quot;size23&quot;&gt;5. 테스트 체크&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-task=&quot;&quot;&gt;테스트가 요구사항을 직접 검증하는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;정상 케이스와 실패 케이스가 모두 있는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;테스트 이름이 의도를 설명하는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;구현 세부사항에 과하게 의존하지 않는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;기존 테스트를 삭제하거나 약화시키지 않았는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 요청 예시는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;테스트를 통과시키기 위해 테스트 파일을 수정하지 마.
실패 원인을 설명한 뒤 구현 파일만 수정해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 한 문장만 추가해도 AI가 테스트를 우회하는 일을 줄일 수 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;6. 접근성과 사용성 체크&quot; data-ke-size=&quot;size23&quot;&gt;6. 접근성과 사용성 체크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 작업이라면 접근성과 사용성도 반드시 봐야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-task=&quot;&quot;&gt;버튼, 링크, 입력 필드의 역할이 올바른가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;키보드로 조작 가능한가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;에러 메시지가 스크린 리더에 전달되는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;loading, disabled 상태가 시각적으로만 표현되지 않는가?&lt;/li&gt;
&lt;li data-task=&quot;&quot;&gt;모바일 화면에서도 흐름이 자연스러운가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 화면을 그럴듯하게 만들지만 실제 사용성까지 완벽히 보장하지는 않습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;AI에게 리뷰를 다시 맡기는 방법&quot; data-ke-size=&quot;size26&quot;&gt;AI에게 리뷰를 다시 맡기는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흥미로운 점은 AI가 만든 코드를 다시 AI에게 리뷰시킬 수도 있다는 것입니다. 다만 이때도 &amp;ldquo;좋아 보여?&amp;rdquo;라고 물으면 좋은 답을 얻기 어렵습니다. 구체적인 기준을 줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음처럼 요청할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;아래 diff를 코드 리뷰해줘.
다음 기준으로만 지적해줘.
1. 요구사항 누락
2. 기존 패턴과 다른 구현
3. 예외 처리 부족
4. 테스트가 약한 부분
5. 불필요한 파일 변경
수정 코드는 아직 작성하지 말고 리뷰 코멘트만 작성해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 AI를 코드 작성자뿐 아니라 1차 리뷰어로 활용할 수 있습니다. 사람 리뷰어는 AI의 리뷰 결과까지 참고해 더 중요한 설계 판단에 집중할 수 있습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;PR에 남기면 좋은 기록&quot; data-ke-size=&quot;size26&quot;&gt;PR에 남기면 좋은 기록&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI를 사용한 작업이라면 PR 설명에도 최소한의 기록을 남기는 것이 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;## 변경 내용
- 회원 목록 검색 기능 추가
- 검색 결과 없음 상태 UI 추가
- 검색 관련 테스트 추가

## AI 사용 범위
- 테스트 케이스 초안 작성
- 검색 필터링 로직 구현 초안 작성
- 최종 코드는 수동 리뷰 후 수정

## 확인한 내용
- npm test 통과
- 이름/이메일 검색 확인
- 빈 검색어/결과 없음 케이스 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기록은 나중에 문제가 생겼을 때 변경 의도를 파악하는 데 도움이 됩니다. 팀에서 AI 사용 정책을 만들 때도 좋은 기준이 됩니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;마무리&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트는 개발 속도를 높여주는 강력한 도구입니다. 하지만 실무에서 중요한 것은 빠르게 코드를 만드는 것이 아니라, &lt;b&gt;안전하게 제품에 반영하는 것&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코드 리뷰의 핵심은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 모양보다 요구사항 충족 여부를 먼저 본다.&lt;/li&gt;
&lt;li&gt;변경 범위가 과하게 넓어지지 않았는지 확인한다.&lt;/li&gt;
&lt;li&gt;기존 프로젝트 패턴과 일관성을 비교한다.&lt;/li&gt;
&lt;li&gt;정상 케이스보다 예외 케이스를 더 꼼꼼히 본다.&lt;/li&gt;
&lt;li&gt;테스트가 구현이 아니라 요구사항을 검증하는지 확인한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 코드를 맡기는 시대일수록 사람 개발자의 역할은 사라지지 않습니다. 오히려 무엇을 만들지 정의하고, 어떤 기준으로 받아들일지 판단하는 역할이 더 중요해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분의 팀에서도 AI 코딩을 사용하고 있다면, 오늘 소개한 체크리스트를 PR 템플릿이나 AGENTS.md에 추가해보는 것을 추천합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;SEO 메타 설명 후보&quot; data-ke-size=&quot;size26&quot;&gt;SEO 메타 설명 후보&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트가 만든 코드를 실무에 안전하게 반영하기 위한 코드 리뷰 체크리스트를 정리합니다. 요구사항, 변경 범위, 테스트, 예외 처리까지 확인해보세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-heading=&quot;SEO 메타 설명 후보&quot; data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/46&quot; data-tooltip-position=&quot;top&quot;&gt;Vibe Coding에서 Spec-Driven Development로: AI에게 제대로 개발시키는 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/44&quot; data-tooltip-position=&quot;top&quot;&gt;AI 코딩 에이전트 제대로 쓰기: AGENTS.md 작성법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mishka.kr/41&quot; data-tooltip-position=&quot;top&quot;&gt;Storybook으로 React 컴포넌트 문서화하기 - 실무 가이드&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Agents.md</category>
      <category>AI</category>
      <category>개발문화</category>
      <category>코드리뷰</category>
      <category>코딩에이전트</category>
      <category>테스트</category>
      <category>프론트엔드</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/49</guid>
      <comments>https://mishka.tistory.com/49#entry49comment</comments>
      <pubDate>Fri, 22 May 2026 17:31:22 +0900</pubDate>
    </item>
    <item>
      <title>MCP 서버 실전 예제: GitHub 이슈를 AI 에이전트에게 연결하기</title>
      <link>https://mishka.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 AI 코딩 도구들은 단순히 질문에 답하는 수준을 넘어 실제 개발 환경과 연결되는 방향으로 발전하고 있습니다. 파일을 읽고, 코드를 수정하고, 테스트를 실행하고, GitHub 이슈나 문서를 참고하면서 작업하는 식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 중요한 개념 중 하나가 &lt;b&gt;MCP(Model Context Protocol)&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP는 AI 에이전트가 외부 도구나 데이터에 접근할 수 있도록 해주는 표준화된 연결 방식입니다. 쉽게 말하면 AI에게 &amp;ldquo;이 도구를 이런 방식으로 사용할 수 있다&amp;rdquo;고 알려주는 인터페이스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 MCP가 무엇인지 개념을 살펴봤다면, 이번 글에서는 조금 더 실전적인 예제로 들어가보겠습니다. 목표는 간단합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트가 GitHub 저장소의 이슈 목록을 조회할 수 있도록 MCP 서버를 만들어본다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-heading=&quot;왜 필요한가&quot; data-ke-size=&quot;size26&quot;&gt;왜 필요한가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 업무에서 GitHub 이슈는 중요한 문맥입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금 어떤 버그가 열려 있는가?&lt;/li&gt;
&lt;li&gt;어떤 기능 요청이 논의 중인가?&lt;/li&gt;
&lt;li&gt;특정 PR이나 커밋이 어떤 이슈와 연결되어 있는가?&lt;/li&gt;
&lt;li&gt;에이전트에게 맡길 만한 작업은 무엇인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람 개발자는 GitHub 화면을 열고 이슈를 확인한 뒤 작업합니다. 하지만 AI 에이전트가 같은 일을 하려면 GitHub에 접근할 수 있는 도구가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 에이전트에게 GitHub CLI를 직접 실행하게 할 수도 있습니다. 하지만 MCP 서버로 감싸면 다음과 같은 장점이 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;1. AI에게 필요한 기능만 노출할 수 있다&quot; data-ke-size=&quot;size23&quot;&gt;1. AI에게 필요한 기능만 노출할 수 있다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub API 전체를 AI에게 열어주는 대신, 예를 들어 list_issues, get_issue, search_issues 같은 필요한 기능만 제공할 수 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;2. 입력과 출력 형식을 명확히 할 수 있다&quot; data-ke-size=&quot;size23&quot;&gt;2. 입력과 출력 형식을 명확히 할 수 있다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 사용할 도구의 파라미터와 응답 구조를 미리 정의할 수 있습니다. 그러면 에이전트가 더 안정적으로 도구를 호출합니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;3. 사내 API나 내부 도구로 확장하기 쉽다&quot; data-ke-size=&quot;size23&quot;&gt;3. 사내 API나 내부 도구로 확장하기 쉽다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub 이슈 조회 예제를 이해하면 나중에는 Jira, Linear, Notion, 사내 관리자 API, 배포 시스템 등으로 확장할 수 있습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;핵심 개념&quot; data-ke-size=&quot;size26&quot;&gt;핵심 개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 서버는 AI 에이전트에게 사용할 수 있는 도구 목록을 제공합니다. 도구 하나는 대략 다음 요소를 가집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름&lt;/li&gt;
&lt;li&gt;설명&lt;/li&gt;
&lt;li&gt;입력 스키마&lt;/li&gt;
&lt;li&gt;실행 로직&lt;/li&gt;
&lt;li&gt;응답 데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 GitHub 이슈를 조회하는 도구라면 다음과 같이 정의할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;도구 이름: list_github_issues
설명: 특정 GitHub 저장소의 열린 이슈 목록을 조회한다.
입력값: owner, repo, state, limit
응답값: issue number, title, url, labels, created_at
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트는 이 설명을 보고 필요할 때 도구를 호출합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;실무 적용 방법&quot; data-ke-size=&quot;size26&quot;&gt;실무 적용 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 예제는 Node.js 기반으로 구성해보겠습니다. 실제 프로젝트에서는 TypeScript를 사용하는 것을 추천하지만, 흐름을 이해하기 위해 구조 위주로 설명하겠습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;1. 프로젝트 생성&quot; data-ke-size=&quot;size23&quot;&gt;1. 프로젝트 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 MCP 서버용 프로젝트를 하나 만듭니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;mkdir github-issues-mcp
cd github-issues-mcp
npm init -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 패키지를 설치합니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json에는 실행 스크립트를 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;tsx src/server.ts&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-heading=&quot;2. GitHub 토큰 준비&quot; data-ke-size=&quot;size23&quot;&gt;2. GitHub 토큰 준비&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub API를 호출하려면 토큰이 필요합니다. 공개 저장소의 공개 이슈만 조회한다면 토큰 없이도 가능하지만, API 제한이나 private 저장소 접근을 고려하면 토큰을 사용하는 편이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경 변수로 관리합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;export GITHUB_TOKEN=ghp_xxx
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 운영에서는 .env 파일이나 비밀 관리 도구를 사용하는 것이 좋습니다. 중요한 점은 토큰을 코드에 직접 넣지 않는 것입니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;3. GitHub 이슈 조회 함수 만들기&quot; data-ke-size=&quot;size23&quot;&gt;3. GitHub 이슈 조회 함수 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 GitHub REST API를 호출하는 함수를 작성합니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// src/github.ts
export async function listIssues(params: {
  owner: string
  repo: string
  state?: &quot;open&quot; | &quot;closed&quot; | &quot;all&quot;
  limit?: number
}) {
  const { owner, repo, state = &quot;open&quot;, limit = 10 } = params
  const token = process.env.GITHUB_TOKEN

  const url = new URL(`https://api.github.com/repos/${owner}/${repo}/issues`)
  url.searchParams.set(&quot;state&quot;, state)
  url.searchParams.set(&quot;per_page&quot;, String(limit))

  const response = await fetch(url, {
    headers: {
      Accept: &quot;application/vnd.github+json&quot;,
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
    },
  })

  if (!response.ok) {
    throw new Error(`GitHub API error: ${response.status} ${response.statusText}`)
  }

  const issues = await response.json()

  return issues
    .filter((issue: any) =&amp;gt; !issue.pull_request)
    .map((issue: any) =&amp;gt; ({
      number: issue.number,
      title: issue.title,
      url: issue.html_url,
      state: issue.state,
      labels: issue.labels.map((label: any) =&amp;gt; label.name),
      created_at: issue.created_at,
    }))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 pull_request가 있는 항목을 제외하는 이유는 GitHub API에서 issues endpoint가 PR도 함께 반환하기 때문입니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;4. MCP 서버 만들기&quot; data-ke-size=&quot;size23&quot;&gt;4. MCP 서버 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 MCP 서버를 작성합니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;// src/server.ts
import { McpServer } from &quot;@modelcontextprotocol/sdk/server/mcp.js&quot;
import { StdioServerTransport } from &quot;@modelcontextprotocol/sdk/server/stdio.js&quot;
import { z } from &quot;zod&quot;
import { listIssues } from &quot;./github&quot;

const server = new McpServer({
  name: &quot;github-issues-mcp&quot;,
  version: &quot;1.0.0&quot;,
})

server.tool(
  &quot;list_github_issues&quot;,
  &quot;특정 GitHub 저장소의 이슈 목록을 조회합니다.&quot;,
  {
    owner: z.string().describe(&quot;GitHub owner 또는 organization 이름&quot;),
    repo: z.string().describe(&quot;GitHub repository 이름&quot;),
    state: z.enum([&quot;open&quot;, &quot;closed&quot;, &quot;all&quot;]).default(&quot;open&quot;),
    limit: z.number().min(1).max(30).default(10),
  },
  async ({ owner, repo, state, limit }) =&amp;gt; {
    const issues = await listIssues({ owner, repo, state, limit })

    return {
      content: [
        {
          type: &quot;text&quot;,
          text: JSON.stringify(issues, null, 2),
        },
      ],
    }
  }
)

const transport = new StdioServerTransport()
await server.connect(transport)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 서버는 표준 입출력(stdio)을 통해 MCP 클라이언트와 통신합니다. Claude Desktop, Cursor, Hermes 같은 MCP 클라이언트는 이 서버를 실행하고 도구 목록을 읽어 사용할 수 있습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;AI 에이전트에 연결하기&quot; data-ke-size=&quot;size26&quot;&gt;AI 에이전트에 연결하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 클라이언트 설정은 도구마다 조금씩 다르지만, 기본적으로는 다음 정보를 등록합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 이름&lt;/li&gt;
&lt;li&gt;실행 명령어&lt;/li&gt;
&lt;li&gt;실행 인자&lt;/li&gt;
&lt;li&gt;환경 변수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 다음과 같은 형태입니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;mcpServers&quot;: {
    &quot;github-issues&quot;: {
      &quot;command&quot;: &quot;npm&quot;,
      &quot;args&quot;: [&quot;run&quot;, &quot;dev&quot;],
      &quot;cwd&quot;: &quot;/path/to/github-issues-mcp&quot;,
      &quot;env&quot;: {
        &quot;GITHUB_TOKEN&quot;: &quot;...&quot;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 설정 파일 위치나 형식은 사용하는 AI 도구에 따라 다릅니다. 중요한 것은 AI 에이전트가 이 MCP 서버를 실행할 수 있고, list_github_issues 도구를 발견할 수 있어야 한다는 점입니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;사용 예시&quot; data-ke-size=&quot;size26&quot;&gt;사용 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결이 완료되면 AI에게 이런 식으로 요청할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;mishka86/example-repo 저장소의 열린 이슈를 조회하고,
프론트엔드 개발자가 먼저 처리하면 좋을 작업 3개를 골라줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 AI는 MCP 도구를 호출해 이슈 목록을 가져온 뒤, 제목과 라벨을 보고 우선순위를 제안할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 다음처럼 사용할 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;최근 열린 버그 이슈를 확인하고,
각 이슈를 재현하기 위해 필요한 정보를 정리해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 흐름이 가능해지면 AI 에이전트는 단순히 코드만 생성하는 도구가 아니라, 프로젝트 관리 문맥까지 참고하는 작업 파트너가 됩니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;에러 처리 고려사항&quot; data-ke-size=&quot;size26&quot;&gt;에러 처리 고려사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 MCP 서버를 만들 때는 에러 처리가 중요합니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;GitHub API 제한&quot; data-ke-size=&quot;size23&quot;&gt;GitHub API 제한&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 없이 호출하면 rate limit에 빨리 걸릴 수 있습니다. 가능하면 토큰을 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;권한 문제&quot; data-ke-size=&quot;size23&quot;&gt;권한 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private repository에 접근하려면 토큰에 적절한 권한이 있어야 합니다. 권한이 부족하면 404처럼 보이는 응답이 올 수도 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;입력값 검증&quot; data-ke-size=&quot;size23&quot;&gt;입력값 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 도구의 입력값은 zod 같은 스키마로 검증하는 것이 좋습니다. AI가 잘못된 파라미터를 넘기더라도 서버가 명확한 에러를 반환할 수 있어야 합니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;응답 크기 제한&quot; data-ke-size=&quot;size23&quot;&gt;응답 크기 제한&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이슈가 너무 많으면 응답이 커질 수 있습니다. limit을 두고 필요한 필드만 반환하는 것이 좋습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;확장 아이디어&quot; data-ke-size=&quot;size26&quot;&gt;확장 아이디어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제는 단순히 이슈 목록을 조회하는 수준이지만, 다음과 같이 확장할 수 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;1. 특정 이슈 상세 조회&quot; data-ke-size=&quot;size23&quot;&gt;1. 특정 이슈 상세 조회&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;get_github_issue(owner, repo, issue_number)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본문, 댓글, 라벨, assignee를 함께 가져올 수 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;2. 라벨 기준 검색&quot; data-ke-size=&quot;size23&quot;&gt;2. 라벨 기준 검색&lt;/h3&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;search_github_issues(owner, repo, labels, state)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bug, good first issue, frontend, priority-high 같은 라벨 기준으로 필터링할 수 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;3. 이슈 요약&quot; data-ke-size=&quot;size23&quot;&gt;3. 이슈 요약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 서버는 데이터를 가져오고, 요약은 AI가 담당하게 할 수 있습니다. 역할을 나누면 서버는 단순하고 안정적으로 유지됩니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;4. Linear나 Jira로 확장&quot; data-ke-size=&quot;size23&quot;&gt;4. Linear나 Jira로 확장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub 이슈 대신 Linear, Jira, Notion 데이터베이스도 같은 방식으로 연결할 수 있습니다. 핵심은 외부 API를 MCP 도구 형태로 감싸는 것입니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;주의할 점&quot; data-ke-size=&quot;size26&quot;&gt;주의할 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 서버를 만들 때 AI에게 너무 많은 권한을 주지 않는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 읽기 전용 도구부터 시작하는 것을 추천합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이슈 목록 조회&lt;/li&gt;
&lt;li&gt;이슈 상세 조회&lt;/li&gt;
&lt;li&gt;댓글 조회&lt;/li&gt;
&lt;li&gt;라벨 조회&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 충분히 안정화되면 쓰기 기능을 추가할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이슈 생성&lt;/li&gt;
&lt;li&gt;댓글 작성&lt;/li&gt;
&lt;li&gt;라벨 변경&lt;/li&gt;
&lt;li&gt;담당자 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 쓰기 기능은 실수했을 때 실제 프로젝트에 영향을 줄 수 있으므로 승인 절차를 두는 것이 안전합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;정리&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP는 AI 에이전트가 외부 시스템과 안전하고 일관된 방식으로 연결될 수 있게 해주는 중요한 프로토콜입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 GitHub 이슈를 조회하는 간단한 MCP 서버 예제를 살펴봤습니다. 핵심 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;GitHub API를 호출하는 함수를 만든다.&lt;/li&gt;
&lt;li&gt;MCP 서버를 생성한다.&lt;/li&gt;
&lt;li&gt;list_github_issues 도구를 정의한다.&lt;/li&gt;
&lt;li&gt;AI 에이전트에 MCP 서버를 등록한다.&lt;/li&gt;
&lt;li&gt;에이전트가 이슈 목록을 조회하고 작업 문맥으로 활용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트를 제대로 활용하려면 단순히 좋은 프롬프트를 작성하는 것만으로는 부족합니다. 프로젝트의 실제 문맥, 이슈, 문서, 배포 상태, 내부 API를 AI가 읽을 수 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP는 그 연결 지점을 표준화해주는 도구입니다. 작은 읽기 전용 MCP 서버부터 만들어보면, AI 에이전트를 실무 환경에 어떻게 안전하게 연결할 수 있을지 감을 잡을 수 있습니다.&lt;/p&gt;</description>
      <category>AI</category>
      <category>API</category>
      <category>github</category>
      <category>MCP</category>
      <category>nodeJS</category>
      <category>typescript</category>
      <category>개발자동화</category>
      <category>코딩에이전트</category>
      <category>프론트엔드</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/48</guid>
      <comments>https://mishka.tistory.com/48#entry48comment</comments>
      <pubDate>Fri, 22 May 2026 17:29:07 +0900</pubDate>
    </item>
    <item>
      <title>AI 코딩 에이전트와 테스트 주도 개발: 테스트부터 맡기는 실무 흐름</title>
      <link>https://mishka.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 도구를 사용하면 기능 구현 속도는 확실히 빨라집니다. Cursor, Claude Code, GitHub Copilot, Codex 같은 도구에게 요구사항을 설명하면 컴포넌트, API 호출 코드, 유틸 함수, 테스트 코드까지 빠르게 생성해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 프로젝트에 적용해보면 속도만큼이나 불안한 부분도 생깁니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항을 일부만 이해하고 구현한다.&lt;/li&gt;
&lt;li&gt;정상 케이스만 처리하고 예외 케이스를 놓친다.&lt;/li&gt;
&lt;li&gt;기존 코드 컨벤션과 다른 방식으로 작성한다.&lt;/li&gt;
&lt;li&gt;동작하는 것처럼 보이지만 실제로는 회귀 버그를 만든다.&lt;/li&gt;
&lt;li&gt;수정 요청을 했더니 관련 없는 파일까지 바꾼다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 AI 코딩을 실무에 안정적으로 적용하려면 &amp;ldquo;코드를 먼저 만들고 사람이 확인하는 방식&amp;rdquo;에서 조금 벗어날 필요가 있습니다. 이때 도움이 되는 접근이 &lt;b&gt;테스트 주도 개발(TDD)&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 AI 코딩 에이전트에게 구현을 바로 시키는 대신, &lt;b&gt;테스트를 먼저 작성하게 하고 그 테스트를 기준으로 구현을 진행하는 흐름&lt;/b&gt;을 정리해보겠습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;왜 필요한가&quot; data-ke-size=&quot;size26&quot;&gt;왜 필요한가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트는 문맥을 잘 이해하는 것처럼 보이지만, 실제로는 요구사항을 엄격하게 보장하지 않습니다. 특히 프론트엔드 개발에서는 다음과 같은 문제가 자주 발생합니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;1. UI는 그럴듯하지만 조건이 빠진다&quot; data-ke-size=&quot;size23&quot;&gt;1. UI는 그럴듯하지만 조건이 빠진다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 버튼 컴포넌트에 disabled, loading, variant, size 같은 옵션이 있다고 해보겠습니다. AI는 기본 화면은 잘 만들지만 다음과 같은 조건을 놓칠 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;loading 상태에서는 클릭 이벤트가 발생하지 않아야 한다.&lt;/li&gt;
&lt;li&gt;disabled 상태에서는 aria 속성이 적절히 들어가야 한다.&lt;/li&gt;
&lt;li&gt;variant가 없을 때 기본값이 적용되어야 한다.&lt;/li&gt;
&lt;li&gt;size 조합에 따라 className이 충돌하지 않아야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈으로 보기에는 멀쩡해도 실제 동작 조건은 빠질 수 있습니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;2. 리팩토링 중 기존 동작이 깨진다&quot; data-ke-size=&quot;size23&quot;&gt;2. 리팩토링 중 기존 동작이 깨진다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 &amp;ldquo;이 컴포넌트 구조를 정리해줘&amp;rdquo;라고 요청하면 코드는 더 깔끔해질 수 있습니다. 하지만 기존에 의존하던 작은 동작이 사라지는 경우가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 기존에는 onChange가 특정 순서로 호출되었는데, 리팩토링 후 호출 타이밍이 바뀌는 식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 없다면 이런 문제는 QA나 운영 중에 발견됩니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;3. 요구사항이 대화 중에 바뀐다&quot; data-ke-size=&quot;size23&quot;&gt;3. 요구사항이 대화 중에 바뀐다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 작업하다 보면 처음 요구사항, 중간 수정 요청, 마지막 예외 조건이 대화 안에 섞입니다. 에이전트는 마지막 요청을 반영하면서 앞에서 말한 조건을 잊을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 테스트는 요구사항을 코드로 고정하는 역할을 합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;핵심 개념&quot; data-ke-size=&quot;size26&quot;&gt;핵심 개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI와 함께 TDD를 한다고 해서 전통적인 TDD 사이클이 완전히 달라지는 것은 아닙니다. 기본 흐름은 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;실패하는 테스트를 먼저 작성한다.&lt;/li&gt;
&lt;li&gt;테스트를 통과하는 최소 구현을 작성한다.&lt;/li&gt;
&lt;li&gt;리팩토링한다.&lt;/li&gt;
&lt;li&gt;테스트를 다시 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 AI 에이전트를 사용할 때는 각 단계에서 사람이 해야 할 역할과 AI에게 맡길 역할을 구분하는 것이 중요합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;실무 적용 방법&quot; data-ke-size=&quot;size26&quot;&gt;실무 적용 방법&lt;/h2&gt;
&lt;h3 data-heading=&quot;1. 구현 요청 전에 테스트 조건부터 정리하기&quot; data-ke-size=&quot;size23&quot;&gt;1. 구현 요청 전에 테스트 조건부터 정리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 바로 이렇게 요청하기 쉽습니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;로그인 폼 컴포넌트 만들어줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 요청은 너무 넓습니다. 대신 테스트 조건을 먼저 정리하는 방식이 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;mercury&quot;&gt;&lt;code&gt;로그인 폼 컴포넌트를 만들기 전에 테스트 케이스를 먼저 작성해줘.
다음 조건을 포함해줘.

- 이메일과 비밀번호 입력 필드가 렌더링된다.
- 이메일이 비어 있으면 제출할 수 없다.
- 비밀번호가 8자 미만이면 에러 메시지를 보여준다.
- 유효한 값이면 onSubmit이 이메일과 비밀번호를 인자로 호출된다.
- 제출 중에는 버튼이 disabled 상태가 된다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 AI는 구현보다 먼저 요구사항을 테스트로 표현하게 됩니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;2. 테스트 파일만 먼저 생성하게 하기&quot; data-ke-size=&quot;size23&quot;&gt;2. 테스트 파일만 먼저 생성하게 하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 구현 파일까지 한 번에 만들게 하면, AI는 테스트와 구현을 동시에 맞춰버리는 경향이 있습니다. 그러면 테스트가 실제 요구사항 검증이라기보다 &amp;ldquo;자기가 만든 코드에 맞춘 테스트&amp;rdquo;가 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 첫 요청은 테스트 파일만 만들게 하는 것이 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;아직 구현 코드는 수정하지 말고 LoginForm.test.tsx 파일만 작성해줘.
프로젝트의 기존 테스트 스타일을 참고해서 작성해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 테스트가 실패하는 것을 확인한 뒤 구현을 요청합니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;방금 작성한 테스트를 통과하도록 LoginForm.tsx를 구현해줘.
테스트 파일은 수정하지 마.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 문장은 &lt;b&gt;&amp;ldquo;테스트 파일은 수정하지 마&amp;rdquo;&lt;/b&gt; 입니다. AI가 테스트를 통과시키기 위해 테스트 자체를 바꿔버리는 일을 막아야 합니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;3. 테스트 실행 결과를 기준으로 수정시키기&quot; data-ke-size=&quot;size23&quot;&gt;3. 테스트 실행 결과를 기준으로 수정시키기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 실패하면 실패 로그를 그대로 AI에게 전달합니다.&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;다음 테스트가 실패했어.
테스트 파일은 수정하지 말고 구현 코드만 수정해줘.

Error: expect(onSubmit).toHaveBeenCalledWith(...)
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 AI는 실제 실패 원인을 기준으로 코드를 수정합니다. 단순히 &amp;ldquo;안 돼 고쳐줘&amp;rdquo;라고 하는 것보다 훨씬 안정적입니다.&lt;/p&gt;
&lt;h3 data-heading=&quot;4. 예외 케이스를 추가 테스트로 고정하기&quot; data-ke-size=&quot;size23&quot;&gt;4. 예외 케이스를 추가 테스트로 고정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능이 동작하기 시작하면 바로 끝내지 말고 예외 케이스를 추가하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 로그인 폼이라면 다음 테스트를 추가할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞뒤 공백이 있는 이메일은 trim 처리되는가?&lt;/li&gt;
&lt;li&gt;엔터 키로 제출할 수 있는가?&lt;/li&gt;
&lt;li&gt;API 에러가 발생하면 에러 메시지를 표시하는가?&lt;/li&gt;
&lt;li&gt;제출 중 중복 클릭이 막히는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 이렇게 요청할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;현재 구현에서 놓칠 수 있는 엣지 케이스를 테스트로 추가해줘.
단, 기존 구현 파일은 수정하지 말고 테스트 파일만 수정해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 다시 구현을 수정하게 합니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;예시 코드&quot; data-ke-size=&quot;size26&quot;&gt;예시 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 React Testing Library 기준의 간단한 예시입니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import { render, screen } from &quot;@testing-library/react&quot;
import userEvent from &quot;@testing-library/user-event&quot;
import { LoginForm } from &quot;./LoginForm&quot;

describe(&quot;LoginForm&quot;, () =&amp;gt; {
  it(&quot;유효한 이메일과 비밀번호를 입력하면 onSubmit을 호출한다&quot;, async () =&amp;gt; {
    const user = userEvent.setup()
    const onSubmit = vi.fn()

    render(&amp;lt;LoginForm onSubmit={onSubmit} /&amp;gt;)

    await user.type(screen.getByLabelText(&quot;이메일&quot;), &quot;test@example.com&quot;)
    await user.type(screen.getByLabelText(&quot;비밀번호&quot;), &quot;password123&quot;)
    await user.click(screen.getByRole(&quot;button&quot;, { name: &quot;로그인&quot; }))

    expect(onSubmit).toHaveBeenCalledWith({
      email: &quot;test@example.com&quot;,
      password: &quot;password123&quot;,
    })
  })

  it(&quot;비밀번호가 8자 미만이면 에러 메시지를 보여준다&quot;, async () =&amp;gt; {
    const user = userEvent.setup()
    const onSubmit = vi.fn()

    render(&amp;lt;LoginForm onSubmit={onSubmit} /&amp;gt;)

    await user.type(screen.getByLabelText(&quot;이메일&quot;), &quot;test@example.com&quot;)
    await user.type(screen.getByLabelText(&quot;비밀번호&quot;), &quot;1234&quot;)
    await user.click(screen.getByRole(&quot;button&quot;, { name: &quot;로그인&quot; }))

    expect(screen.getByText(&quot;비밀번호는 8자 이상이어야 합니다.&quot;)).toBeInTheDocument()
    expect(onSubmit).not.toHaveBeenCalled()
  })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 테스트는 구현 세부사항보다 사용자 관점의 동작을 검증합니다. AI에게 테스트를 작성시킬 때도 내부 state 이름이나 className보다 실제 사용자가 보는 입력, 버튼, 메시지 중심으로 작성하게 하는 것이 좋습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;AI에게 줄 프롬프트 예시&quot; data-ke-size=&quot;size26&quot;&gt;AI에게 줄 프롬프트 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서는 아래와 같은 프롬프트를 템플릿처럼 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;너는 이 프로젝트의 프론트엔드 개발자야.
먼저 테스트 주도 개발 방식으로 진행해줘.

규칙:
1. 처음에는 테스트 파일만 작성한다.
2. 구현 파일은 수정하지 않는다.
3. 테스트는 사용자 행동 중심으로 작성한다.
4. 정상 케이스와 예외 케이스를 모두 포함한다.
5. 이후 내가 테스트 실행 결과를 주면, 테스트 파일은 수정하지 말고 구현만 수정한다.

기능 요구사항:
- ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프롬프트의 핵심은 AI의 작업 범위를 제한하는 것입니다. AI에게 &amp;ldquo;잘 만들어줘&amp;rdquo;라고 하는 것보다 &amp;ldquo;지금은 테스트만 작성해줘&amp;rdquo;, &amp;ldquo;이후에는 구현만 수정해줘&amp;rdquo;처럼 단계별로 나누는 것이 결과가 더 좋습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;주의할 점&quot; data-ke-size=&quot;size26&quot;&gt;주의할 점&lt;/h2&gt;
&lt;h3 data-heading=&quot;테스트도 리뷰해야 한다&quot; data-ke-size=&quot;size23&quot;&gt;테스트도 리뷰해야 한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI가 작성한 테스트라고 해서 항상 올바른 것은 아닙니다. 다음 항목은 사람이 꼭 확인해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요구사항이 빠지지 않았는가?&lt;/li&gt;
&lt;li&gt;테스트 이름이 의도를 잘 설명하는가?&lt;/li&gt;
&lt;li&gt;구현 세부사항에 너무 의존하지 않는가?&lt;/li&gt;
&lt;li&gt;의미 없는 스냅샷 테스트가 남발되지 않았는가?&lt;/li&gt;
&lt;li&gt;비동기 테스트에서 await 처리가 적절한가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-heading=&quot;테스트를 통과시키기 위해 테스트를 바꾸지 못하게 해야 한다&quot; data-ke-size=&quot;size23&quot;&gt;테스트를 통과시키기 위해 테스트를 바꾸지 못하게 해야 한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 에이전트는 실패한 테스트를 통과시키는 과정에서 테스트 코드를 수정하려고 할 수 있습니다. 이때는 명확하게 제한해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;테스트 파일은 수정하지 마.
실패한 테스트를 기준으로 구현 파일만 수정해줘.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-heading=&quot;한 번에 너무 큰 기능을 맡기지 않는다&quot; data-ke-size=&quot;size23&quot;&gt;한 번에 너무 큰 기능을 맡기지 않는다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 큰 기능을 한 번에 맡기면 테스트도 구현도 커지고 검증이 어려워집니다. 기능을 작게 나누고 각 단위마다 테스트를 먼저 만드는 것이 좋습니다.&lt;/p&gt;
&lt;h2 data-heading=&quot;정리&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트는 빠르게 코드를 만들어주는 도구이지만, 실무에서 중요한 것은 단순한 속도가 아니라 &lt;b&gt;검증 가능한 결과&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 먼저 작성하게 하면 AI가 이해한 요구사항을 코드로 확인할 수 있고, 구현 이후에도 회귀 버그를 줄일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추천하는 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요구사항을 테스트 케이스로 정리한다.&lt;/li&gt;
&lt;li&gt;AI에게 테스트 파일만 먼저 작성하게 한다.&lt;/li&gt;
&lt;li&gt;실패하는 테스트를 확인한다.&lt;/li&gt;
&lt;li&gt;테스트를 수정하지 못하게 하고 구현만 작성하게 한다.&lt;/li&gt;
&lt;li&gt;실패 로그를 기준으로 반복 수정한다.&lt;/li&gt;
&lt;li&gt;엣지 케이스를 추가 테스트로 고정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 시대의 개발자는 코드를 직접 덜 작성할 수는 있지만, 무엇이 올바른 동작인지 정의하는 역할은 더 중요해지고 있습니다. 테스트는 그 정의를 가장 명확하게 남기는 방법 중 하나입니다.&lt;/p&gt;</description>
      <category>AI</category>
      <category>Jest</category>
      <category>React</category>
      <category>react testing library</category>
      <category>TDD</category>
      <category>개발생산성</category>
      <category>코딩에이전트</category>
      <category>테스트</category>
      <category>프론트엔드</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/47</guid>
      <comments>https://mishka.tistory.com/47#entry47comment</comments>
      <pubDate>Fri, 22 May 2026 16:41:39 +0900</pubDate>
    </item>
    <item>
      <title>Vibe Coding에서 Spec-Driven Development로: AI에게 제대로 개발시키는 방법</title>
      <link>https://mishka.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 도구를 쓰면 개발 속도는 확실히 빨라집니다.&amp;nbsp;Cursor, Claude Code, GitHub Copilot, Codex 같은 도구에게 요구사항을 말하면 코드 초안이 빠르게 만들어집니다. 간단한 화면이나 프로토타입을 만들 때는 특히 유용합니다.&lt;/p&gt;
&lt;p data-end=&quot;346&quot; data-start=&quot;312&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 프로젝트에 적용해보면 이런 문제가 자주 생깁니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;381&quot; data-start=&quot;348&quot; data-ke-style=&quot;style3&quot;&gt;- &amp;ldquo;처음에는 잘 돌아가는 것처럼 보였는데 &lt;b&gt;요구사항과 다르다&lt;/b&gt;.&amp;rdquo;&lt;br /&gt;- &amp;ldquo;코드는 그럴듯하지만 &lt;b&gt;예외 처리&lt;/b&gt;가 빠져 있다.&amp;rdquo;&lt;br /&gt;- &amp;ldquo;기존 프로젝트 구조를 무시하고 &lt;b&gt;새 패턴&lt;/b&gt;을 만들어버렸다.&amp;rdquo;&lt;br /&gt;- &amp;ldquo;수정해달라고 했더니 &lt;b&gt;관련 없는 파일&lt;/b&gt;까지 바뀌었다.&amp;rdquo;&lt;/blockquote&gt;
&lt;p data-end=&quot;517&quot; data-start=&quot;476&quot; data-ke-size=&quot;size16&quot;&gt;이런 방식의 AI 코딩을 흔히 &lt;b&gt;Vibe Coding&lt;/b&gt;이라고 부릅니다. 대략적인 느낌만 전달하고, AI가 만든 코드를 실행해보면서 되는 것 같으면 넘어가는 방식입니다.&amp;nbsp;작은 개인 프로젝트에서는 괜찮을 수 있지만, 실무 프로젝트에서는 위험합니다.&lt;/p&gt;
&lt;p data-end=&quot;517&quot; data-start=&quot;476&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;517&quot; data-start=&quot;476&quot; data-ke-size=&quot;size16&quot;&gt;AI는 빠르게 코드를 만들 수 있지만, 우리가 원하는 제품의 의도와 제약 조건을 항상 정확히 이해하지는 못하기 때문입니다.&lt;/p&gt;
&lt;p data-end=&quot;763&quot; data-start=&quot;689&quot; data-ke-size=&quot;size16&quot;&gt;그래서 최근에는 AI에게 바로 코드를 작성시키기보다, 먼저 명세를 만들고 그 명세를 기준으로 개발하게 하는 방식이 주목받고 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;822&quot; data-start=&quot;765&quot; data-ke-size=&quot;size16&quot;&gt;이 방식이 바로 &lt;b&gt;Spec-Driven Development&lt;/b&gt;, 즉 &lt;b&gt;명세 기반 개발&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h2 data-end=&quot;855&quot; data-start=&quot;836&quot; data-section-id=&quot;1l5k96h&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Vibe Coding의 문제점&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;884&quot; data-start=&quot;857&quot; data-ke-size=&quot;size16&quot;&gt;Vibe Coding의 장점은 빠르다는 것입니다.&amp;nbsp;예를 들어 AI에게 이렇게 요청할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;929&quot; data-start=&quot;914&quot; data-ke-style=&quot;style3&quot;&gt;&amp;ldquo;로그인 페이지 만들어줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;게시판 CRUD 만들어줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;관리자 대시보드 만들어줘.&amp;rdquo;&lt;/blockquote&gt;
&lt;p data-end=&quot;1004&quot; data-start=&quot;967&quot; data-ke-size=&quot;size16&quot;&gt;AI는 금방 코드를 만들어줍니다. 하지만 문제는 그 다음입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1004&quot; data-start=&quot;967&quot;&gt;로그인 페이지를 만들긴 했지만 인증 실패 케이스가 빠져 있을 수 있습니다.&lt;/li&gt;
&lt;li data-end=&quot;1084&quot; data-start=&quot;1049&quot;&gt;게시판 CRUD를 만들긴 했지만 권한 체크가 없을 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;li data-end=&quot;1084&quot; data-start=&quot;1049&quot;&gt;관리자 대시보드를 만들긴 했지만 기존 디자인 시스템을 사용하지 않았을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1178&quot; data-start=&quot;1134&quot; data-ke-size=&quot;size16&quot;&gt;겉으로는 완성된 것처럼 보여도 내부적으로는 많은 문제가 숨어 있을 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;1209&quot; data-start=&quot;1180&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1209&quot; data-start=&quot;1180&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vibe Coding&lt;/b&gt;의 핵심 문제는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 요구사항이 &lt;b&gt;불명확&lt;/b&gt;하다.&lt;br /&gt;- AI가 빈 부분을 &lt;b&gt;임의&lt;/b&gt;로 가정한다.&lt;br /&gt;- &lt;b&gt;완료 기준&lt;/b&gt;이 없다.&lt;br /&gt;- 수정 범위가 커질 수 있다.&lt;br /&gt;- &lt;b&gt;테스트 기준이 부족&lt;/b&gt;하다.&lt;/blockquote&gt;
&lt;p data-end=&quot;1342&quot; data-start=&quot;1296&quot; data-ke-size=&quot;size16&quot;&gt;즉, Vibe Coding은 &lt;b&gt;속도는 빠르지만 방향과 기준이 약한 방식&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-end=&quot;1342&quot; data-start=&quot;1296&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1377&quot; data-start=&quot;1349&quot; data-section-id=&quot;1a0d2bn&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Spec-Driven Development란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1434&quot; data-start=&quot;1379&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;S&lt;/b&gt;pec-&lt;b&gt;D&lt;/b&gt;riven &lt;b&gt;D&lt;/b&gt;evelopment는 말 그대로 &lt;b&gt;명세를 중심으로 개발하는 방식&lt;/b&gt;입니다. 여기서 명세는 단순한 문서가 아닙니다.&lt;br /&gt;사람과 AI가 함께 바라보는 기준입니다.&amp;nbsp;Vibe Coding은 보통 이렇게 진행됩니다.&lt;/p&gt;
&lt;p data-end=&quot;1549&quot; data-start=&quot;1512&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;1549&quot; data-start=&quot;1512&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아이디어 &amp;rarr; 바로 코드 생성 &amp;rarr; 실행해보기 &amp;rarr; 다시 수정&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-end=&quot;1589&quot; data-start=&quot;1551&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1589&quot; data-start=&quot;1551&quot; data-ke-size=&quot;size16&quot;&gt;반면 Spec-Driven Development는 이렇게 진행됩니다.&lt;/p&gt;
&lt;p data-end=&quot;1656&quot; data-start=&quot;1591&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;1656&quot; data-start=&quot;1591&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아이디어 &amp;rarr; 명세 작성 &amp;rarr; 구현 계획 작성 &amp;rarr; 작업 단위 분해 &amp;rarr; 코드 생성 &amp;rarr; 명세 기준으로 검증&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-end=&quot;1700&quot; data-start=&quot;1658&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1700&quot; data-start=&quot;1658&quot; data-ke-size=&quot;size16&quot;&gt;가장 큰 차이는 &lt;b&gt;코드를 작성하기 전에 기준을 먼저 만든다&lt;/b&gt;는 점입니다.&amp;nbsp;명세에는 다음 내용이 들어갈 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 무엇을 만들 것인가?&lt;br /&gt;- 어떤 사용자가 사용하는가?&lt;br /&gt;- 정상 동작은 무엇인가?&lt;br /&gt;- 예외 상황은 무엇인가?&lt;br /&gt;- 완료 기준은 무엇인가?&lt;/blockquote&gt;
&lt;p data-end=&quot;1899&quot; data-start=&quot;1804&quot; data-ke-size=&quot;size16&quot;&gt;AI는 이 명세를 기준으로 코드를 작성합니다. 그래서 개발자는 AI에게 단순히 &amp;ldquo;만들어줘&amp;rdquo;라고 시키는 사람이 아니라, AI가 따라야 할 기준을 설계하는 사람이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image 2026년 4월 29일 오전 09_42_41.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcZMM1/dJMcaib6iH7/aE5ojc9kNp5UPAseYImKn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcZMM1/dJMcaib6iH7/aE5ojc9kNp5UPAseYImKn0/img.png&quot; data-alt=&quot;Vibe Coding VS Spec Driven Development&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcZMM1/dJMcaib6iH7/aE5ojc9kNp5UPAseYImKn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcZMM1%2FdJMcaib6iH7%2FaE5ojc9kNp5UPAseYImKn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-filename=&quot;ChatGPT Image 2026년 4월 29일 오전 09_42_41.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Vibe Coding VS Spec Driven Development&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-end=&quot;1947&quot; data-start=&quot;1906&quot; data-section-id=&quot;pe42gu&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;1947&quot; data-start=&quot;1906&quot; data-section-id=&quot;pe42gu&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기본 흐름: Specify, Plan, Tasks, Implement&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1994&quot; data-start=&quot;1949&quot; data-ke-size=&quot;size16&quot;&gt;Spec-Driven Development는 다음 네 단계로 이해할 수 있습니다.&lt;/p&gt;
&lt;h2 data-end=&quot;2023&quot; data-start=&quot;1996&quot; data-section-id=&quot;17ys7d5&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Specify: 무엇을 만들지 정의하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2066&quot; data-start=&quot;2025&quot; data-ke-size=&quot;size16&quot;&gt;Specify 단계에서는 구현 방법보다 &lt;b&gt;무엇을 만들지&lt;/b&gt;에 집중합니다.&amp;nbsp;예를 들어 &amp;ldquo;로그인 기능 만들어줘&amp;rdquo;라고만 하면 부족합니다.&lt;/p&gt;
&lt;p data-end=&quot;2123&quot; data-start=&quot;2102&quot; data-ke-size=&quot;size16&quot;&gt;조금 더 좋은 명세는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;2268&quot; data-start=&quot;2125&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;사용자는 이메일과 비밀번호로 로그인할 수 있어야 한다. &lt;br /&gt;이메일 형식이 잘못되면 입력 단계에서 안내 메시지를 보여준다. &lt;br /&gt;로그인 실패 시 &amp;lsquo;이메일 또는 비밀번호를 확인해주세요&amp;rsquo;라는 메시지를 보여준다. &lt;br /&gt;로그인 성공 시 사용자는 /dashboard로 이동한다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-end=&quot;2304&quot; data-start=&quot;2270&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 작성하면 AI가 구현해야 할 범위가 훨씬 명확해집니다.&amp;nbsp;명세는 길 필요가 없습니다. 대신 &lt;b&gt;구체적&lt;/b&gt;이어야 합니다.&lt;/p&gt;
&lt;p data-end=&quot;2304&quot; data-start=&quot;2270&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2370&quot; data-start=&quot;2345&quot; data-section-id=&quot;1jvbube&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Plan: 어떻게 구현할지 계획하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2405&quot; data-start=&quot;2372&quot; data-ke-size=&quot;size16&quot;&gt;Plan 단계에서는 명세를 바탕으로 구현 전략을 정리합니다.&amp;nbsp;예를 들어 로그인 기능이라면 다음을 정할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 로그인 페이지 경로&lt;br /&gt;- 사용할 API&lt;br /&gt;- 폼 검증 방식&lt;br /&gt;- 에러 처리 방식&lt;br /&gt;- 기존 컴포넌트 재사용 여부&lt;br /&gt;- 테스트 방식&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2523&quot; data-start=&quot;2510&quot; data-ke-size=&quot;size16&quot;&gt;예시는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 로그인 페이지는 /login 경로에 만든다.&lt;br /&gt;- 폼 검증은 zod를 사용한다.&lt;br /&gt;- API 호출은 src/api/auth.ts에 작성한다.&lt;br /&gt;- UI는 기존 Button, Input 컴포넌트를 재사용한다.&lt;br /&gt;- 테스트는 로그인 성공, 실패, 잘못된 이메일 입력 케이스를 포함한다.&lt;/blockquote&gt;
&lt;p data-end=&quot;2736&quot; data-start=&quot;2692&quot; data-ke-size=&quot;size16&quot;&gt;이 단계의 목적은 AI가 코드를 만들기 전에 구현 방향을 먼저 맞추는 것입니다.&lt;/p&gt;
&lt;p data-end=&quot;2736&quot; data-start=&quot;2692&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2766&quot; data-start=&quot;2743&quot; data-section-id=&quot;1ql6sr8&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Tasks: 작업 단위로 나누기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2800&quot; data-start=&quot;2768&quot; data-ke-size=&quot;size16&quot;&gt;Tasks 단계에서는 구현 계획을 작은 작업으로 나눕니다.&amp;nbsp;AI에게 큰 작업을 한 번에 맡기면 수정 범위가 커집니다.&lt;br /&gt;그래서 작고 검증 가능한 단위로 나누는 것이 좋습니다.&amp;nbsp;예를 들어 로그인 기능은 다음처럼 나눌 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 로그인 페이지 UI 작성&lt;br /&gt;2. 이메일/비밀번호 입력 검증 추가&lt;br /&gt;3. 로그인 API 함수 작성&lt;br /&gt;4. 로그인 성공 시 라우팅 처리&lt;br /&gt;5. 로그인 실패 에러 메시지 처리&lt;br /&gt;6. 테스트 코드 작성&lt;/blockquote&gt;
&lt;p data-end=&quot;3026&quot; data-start=&quot;3008&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3026&quot; data-start=&quot;3008&quot; data-ke-size=&quot;size16&quot;&gt;나쁜 Task는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;3026&quot; data-start=&quot;3008&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;로그인 기능 전체 구현&amp;rdquo;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-end=&quot;3062&quot; data-start=&quot;3044&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3062&quot; data-start=&quot;3044&quot; data-ke-size=&quot;size16&quot;&gt;좋은 Task는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;3127&quot; data-start=&quot;3064&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;기존 Input, Button 컴포넌트를 사용해 로그인 폼 UI만 작성한다. 아직 API 연동은 하지 않는다.&amp;rdquo;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-end=&quot;3165&quot; data-start=&quot;3129&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3165&quot; data-start=&quot;3129&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 나누면 AI가 불필요한 파일을 수정할 가능성이 줄어듭니다.&lt;/p&gt;
&lt;p data-end=&quot;3165&quot; data-start=&quot;3129&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;3200&quot; data-start=&quot;3172&quot; data-section-id=&quot;k13zom&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Implement: 기준에 따라 구현하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;3234&quot; data-start=&quot;3202&quot; data-ke-size=&quot;size16&quot;&gt;Implement 단계에서 AI가 실제 코드를 작성합니다.&lt;/p&gt;
&lt;p data-end=&quot;3305&quot; data-start=&quot;3236&quot; data-ke-size=&quot;size16&quot;&gt;중요한 점은 AI에게 자유롭게 맡기는 것이 아니라, 앞에서 만든 명세와 작업 목록을 기준으로 구현하게 해야 한다는 것입니다.&lt;/p&gt;
&lt;p data-end=&quot;3305&quot; data-start=&quot;3236&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3323&quot; data-start=&quot;3307&quot; data-ke-size=&quot;size16&quot;&gt;좋은 요청은 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;3412&quot; data-start=&quot;3325&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;위 명세와 작업 목록 중 1번 작업만 구현해줘. API 연동은 하지 말고 UI만 작성해줘. &lt;br /&gt;기존 컴포넌트 구조를 따르고, 작업 후 변경 파일을 요약해줘.&amp;rdquo;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-end=&quot;3534&quot; data-start=&quot;3451&quot; data-ke-size=&quot;size16&quot;&gt;AI가 라우트, 상태 관리, API, 토큰 저장 방식, UI 컴포넌트, 테스트 방식까지 모두 임의로 결정할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;3602&quot; data-start=&quot;3536&quot; data-ke-size=&quot;size16&quot;&gt;Spec-Driven Development에서 AI는 단독 개발자가 아니라, 명세를 따라 구현하는 실행자에 가깝습니다.&lt;/p&gt;
&lt;p data-end=&quot;3602&quot; data-start=&quot;3536&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;4322&quot; data-start=&quot;4301&quot; data-section-id=&quot;p1w2j9&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AGENTS.md와 함께 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;4383&quot; data-start=&quot;4324&quot; data-ke-size=&quot;size16&quot;&gt;앞선 글에서 다룬 AGENTS.md는 Spec-Driven Development와 함께 쓰기 좋습니다. (&lt;a href=&quot;https://mishka.tistory.com/44&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;AI 코딩 에이전트 제대로 쓰기: AGENTS.md 작성법&lt;/b&gt;&lt;/a&gt;)&amp;nbsp;AGENTS.md에는 프로젝트 공통 규칙을 적습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 패키지 매니저는 pnpm을 사용한다.&lt;br /&gt;- 새 파일은 TypeScript로 작성한다.&lt;br /&gt;- 기존 컴포넌트를 우선 재사용한다.&lt;br /&gt;- 테스트를 삭제하지 않는다.&lt;br /&gt;- 작업 후 typecheck, lint, test를 실행한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4590&quot; data-start=&quot;4559&quot; data-ke-size=&quot;size16&quot;&gt;반면 Spec 문서에는 특정 기능의 요구사항을 적습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 로그인 실패 시 에러 메시지를 표시한다.&lt;br /&gt;- 요청 중에는 버튼을 비활성화한다.&lt;br /&gt;- 성공 시 /dashboard로 이동한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4703&quot; data-start=&quot;4683&quot; data-ke-size=&quot;size16&quot;&gt;즉, 역할을 나누면 다음과 같습니다.&amp;nbsp;AGENTS.md는 프로젝트 전체 규칙입니다.&lt;/p&gt;
&lt;p data-end=&quot;4755&quot; data-start=&quot;4734&quot; data-ke-size=&quot;size16&quot;&gt;Spec 문서는 기능별 요구사항입니다.&amp;nbsp;AI 에이전트는 이 두 가지를 함께 참고할 때 더 좋은 결과를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;4755&quot; data-start=&quot;4734&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4755&quot; data-start=&quot;4734&quot; data-ke-size=&quot;size16&quot;&gt;AI 코딩 도구는 매우 강력합니다.&amp;nbsp;하지만 AI에게 &amp;ldquo;알아서 만들어줘&amp;rdquo;라고만 요청하면, 빠르지만 불안정한 결과가 나올 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;4981&quot; data-start=&quot;4892&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vibe Coding&lt;/b&gt;은 프로토타입에는 유용하지만, 실무 개발에서는 요구사항 누락, 잘못된 가정, 과도한 수정 범위, 테스트 부족 같은 문제를 만들 수 있습니다.&amp;nbsp;&lt;b&gt;Spec-Driven Development&lt;/b&gt;는 이 문제를 줄이기 위한 방법입니다.&lt;/p&gt;
&lt;p data-end=&quot;5043&quot; data-start=&quot;5029&quot; data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- Vibe Coding은 느낌 중심으로 바로 코드를 만드는 방식입니다.&lt;br /&gt;- Spec-Driven Development는 명세를 먼저 만들고, 그 명세를 기준으로 코드를 만드는 방식입니다.&lt;br /&gt;- 핵심 흐름은 Specify, Plan, Tasks, Implement입니다.&lt;br /&gt;- 명세는 사람과 AI가 함께 바라보는 기준입니다.&lt;br /&gt;- 작업을 작게 나누면 AI의 실수를 줄이고 리뷰하기 쉬워집니다.&lt;br /&gt;- AGENTS.md와 함께 사용하면 프로젝트 규칙과 기능 요구사항을 함께 관리할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-end=&quot;5422&quot; data-start=&quot;5317&quot; data-ke-size=&quot;size16&quot;&gt;앞으로 AI 코딩 도구를 더 적극적으로 사용할수록 개발자의 역할은 단순히 코드를 직접 치는 것에서, &lt;b&gt;명확한 요구사항을 정의하고 AI의 결과물을 검증하는 것&lt;/b&gt;으로 바뀔 가능성이 큽니다.&lt;/p&gt;
&lt;p data-end=&quot;5472&quot; data-start=&quot;5424&quot; data-ke-size=&quot;size16&quot;&gt;AI에게 코드를 잘 쓰게 하고 싶다면, 먼저 AI가 따라야 할 기준을 잘 써야 합니다.&lt;/p&gt;
&lt;p data-end=&quot;5489&quot; data-start=&quot;5474&quot; data-ke-size=&quot;size16&quot;&gt;그 기준이 바로 명세입니다.&lt;/p&gt;
&lt;p data-end=&quot;5489&quot; data-start=&quot;5474&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-turn-start-message=&quot;true&quot; data-message-model-slug=&quot;gpt-5-5-thinking&quot; data-message-id=&quot;3a8b8bd3-5d20-454b-abc6-b1e609cb5f84&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h2 data-end=&quot;5504&quot; data-start=&quot;5496&quot; data-section-id=&quot;3c0sv5&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;5673&quot; data-start=&quot;5506&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Blog - Spec-driven development with AI&lt;br /&gt;&lt;a href=&quot;https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/&quot;&gt;https://github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai-get-started-with-a-new-open-source-toolkit/&lt;/a&gt;&lt;/p&gt;
&lt;p data-end=&quot;5727&quot; data-start=&quot;5675&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Spec Kit&lt;br /&gt;&lt;a href=&quot;https://github.com/github/spec-kit&quot;&gt;https://github.com/github/spec-kit&lt;/a&gt;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;5852&quot; data-start=&quot;5729&quot; data-ke-size=&quot;size16&quot;&gt;Martin Fowler - Understanding Spec-Driven Development&lt;br /&gt;&lt;a href=&quot;https://martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html&quot;&gt;https://martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Programming</category>
      <category>Agents.md</category>
      <category>AI</category>
      <category>SDD</category>
      <category>Spec-Driven Development</category>
      <category>vibecoding</category>
      <category>개발</category>
      <category>바이브코딩</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/46</guid>
      <comments>https://mishka.tistory.com/46#entry46comment</comments>
      <pubDate>Wed, 29 Apr 2026 09:49:04 +0900</pubDate>
    </item>
    <item>
      <title>MCP 서버 만들기 입문: AI 에이전트가 내 API를 호출하게 하는 방법</title>
      <link>https://mishka.tistory.com/45</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 AI 개발 도구는 단순히 질문에 답하는 수준을 넘어섰습니다.&amp;nbsp;이제 AI는 코드를 읽고, 파일을 수정하고, 테스트를 실행하고, GitHub 이슈를 확인하고, 외부 API를 호출하는 방향으로 발전하고 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;297&quot; data-start=&quot;276&quot; data-ke-size=&quot;size16&quot;&gt;그런데 여기서 중요한 문제가 생깁니다.&lt;/p&gt;
&lt;p data-end=&quot;297&quot; data-start=&quot;276&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;346&quot; data-start=&quot;299&quot; data-ke-size=&quot;size16&quot;&gt;AI가 외부 도구나 데이터에 안전하고 일관된 방식으로 접근하려면 어떻게 해야 할까요?&lt;/p&gt;
&lt;p data-end=&quot;381&quot; data-start=&quot;348&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 AI에게 이런 작업을 시키고 싶다고 해보겠습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;408&quot; data-start=&quot;383&quot; data-ke-style=&quot;style3&quot;&gt;&amp;ldquo;내 프로젝트의 GitHub 이슈를 조회해줘&amp;rdquo;&lt;br /&gt;&amp;ldquo;고객 주문 API에서 최근 주문 목록을 가져와줘&amp;rdquo;&lt;br /&gt;&amp;ldquo;로컬 문서에서 특정 내용을 찾아줘&amp;rdquo;&lt;br /&gt;&amp;ldquo;사내 데이터베이스에서 프로젝트 상태를 확인해줘&amp;rdquo;&lt;/blockquote&gt;
&lt;p data-end=&quot;524&quot; data-start=&quot;491&quot; data-ke-size=&quot;size16&quot;&gt;이런 작업을 하려면 AI가 외부 시스템과 연결되어야 합니다.&amp;nbsp;기존 방식이라면 각 AI 도구마다 별도의 연동 코드를 만들어야 했습니다.&lt;/p&gt;
&lt;p data-end=&quot;665&quot; data-start=&quot;568&quot; data-ke-size=&quot;size16&quot;&gt;Claude용 GitHub 연동을 만들고, Cursor용 GitHub 연동을 만들고, ChatGPT용 GitHub 연동을 만들고, 사내 에이전트용 연동을 또 만드는 식입니다.&lt;/p&gt;
&lt;p data-end=&quot;698&quot; data-start=&quot;667&quot; data-ke-size=&quot;size16&quot;&gt;도구가 늘어날수록 같은 연동을 반복해서 만들어야 합니다. 이 문제를 해결하기 위해 등장한 표준이 바로 &lt;b&gt;MCP(Model Context Protocol)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-end=&quot;698&quot; data-start=&quot;667&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;783&quot; data-start=&quot;775&quot; data-section-id=&quot;xewhac&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MCP란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;824&quot; data-start=&quot;785&quot; data-ke-size=&quot;size16&quot;&gt;MCP는 &lt;b&gt;Model Context Protocol&lt;/b&gt;의 약자입니다.&lt;/p&gt;
&lt;p data-end=&quot;887&quot; data-start=&quot;826&quot; data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 &lt;b&gt;AI 애플리케이션이 외부 도구와 데이터를 사용할 수 있도록 연결해주는 표준 프로토콜&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-end=&quot;933&quot; data-start=&quot;889&quot; data-ke-size=&quot;size16&quot;&gt;공식 문서에서는 MCP를 AI 애플리케이션을 위한 USB-C 포트에 비유합니다.&amp;nbsp;USB-C가 노트북, 모니터, 충전기, 외장하드 같은 다양한 장치를 하나의 표준으로 연결하듯이, MCP는 AI 에이전트가 파일 시스템, 데이터베이스, GitHub, 사내 API 같은 외부 시스템과 연결될 수 있게 합니다.&amp;nbsp;즉, MCP는 &lt;b&gt;AI와 외부 도구 사이의 공통 연결 방식&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-end=&quot;933&quot; data-start=&quot;889&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1118&quot; data-start=&quot;1101&quot; data-section-id=&quot;1h5fnlo&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MCP를 왜 알아야 할까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1170&quot; data-start=&quot;1120&quot; data-ke-size=&quot;size16&quot;&gt;AI 서비스를 만들거나 AI 코딩 도구를 사용하다 보면 결국 다음 단계로 넘어가게 됩니다.&amp;nbsp;처음에는 AI에게 질문만 합니다. 그다음에는 AI에게 코드를 작성하게 합니다. 그리고 어느 순간부터는 AI에게 실제 작업을 시키고 싶어집니다.&lt;/p&gt;
&lt;p data-end=&quot;1270&quot; data-start=&quot;1255&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면 이런 작업입니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1302&quot; data-start=&quot;1272&quot; data-ke-style=&quot;style3&quot;&gt;&amp;ldquo;이 GitHub 이슈를 보고 관련 파일을 수정해줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;이 프로젝트의 테스트 실패 원인을 찾아줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;DB에서 최근 주문 데이터를 보고 오류 패턴을 분석해줘.&amp;rdquo;&lt;br /&gt;&amp;ldquo;내 API 문서를 읽고 SDK 예시 코드를 만들어줘.&amp;rdquo;&lt;/blockquote&gt;
&lt;p data-end=&quot;1482&quot; data-start=&quot;1399&quot; data-ke-size=&quot;size16&quot;&gt;이런 작업을 하려면 AI가 단순히 대화만 해서는 안 됩니다. 외부 시스템에 접근하고, 필요한 데이터를 가져오고, 도구를 호출할 수 있어야 합니다.&amp;nbsp;MCP는 이 연결 방식을 표준화합니다. 그래서 앞으로 AI 에이전트, AI 코딩 도구, 사내 AI 업무 자동화 도구를 만들 때 MCP는 중요한 기반 기술이 될 가능성이 큽니다.&lt;/p&gt;
&lt;p data-end=&quot;1482&quot; data-start=&quot;1399&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1603&quot; data-start=&quot;1590&quot; data-section-id=&quot;gaghuf&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MCP의 기본 구조&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1633&quot; data-start=&quot;1605&quot; data-ke-size=&quot;size16&quot;&gt;MCP는 크게 세 가지 요소로 이해할 수 있습니다.&lt;/p&gt;
&lt;h4 data-end=&quot;1657&quot; data-start=&quot;1635&quot; data-ke-size=&quot;size20&quot;&gt;첫 번째는 &lt;b&gt;MCP Host&lt;/b&gt;입니다.&lt;/h4&gt;
&lt;p data-end=&quot;1762&quot; data-start=&quot;1659&quot; data-ke-size=&quot;size16&quot;&gt;MCP Host는 사용자가 직접 사용하는 AI 애플리케이션입니다.&lt;br /&gt;예를 들면 Claude Desktop, Cursor, AI IDE, 사내 AI 챗봇 같은 도구가 여기에 해당합니다.&lt;/p&gt;
&lt;p data-end=&quot;1762&quot; data-start=&quot;1659&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1788&quot; data-start=&quot;1764&quot; data-ke-size=&quot;size20&quot;&gt;두 번째는 &lt;b&gt;MCP Client&lt;/b&gt;입니다.&lt;/h4&gt;
&lt;p data-end=&quot;1882&quot; data-start=&quot;1790&quot; data-ke-size=&quot;size16&quot;&gt;MCP Client는 Host 안에서 MCP Server와 통신하는 역할을 합니다.&lt;br /&gt;사용자가 직접 다루기보다는 AI 애플리케이션 내부에서 동작한다고 보면 됩니다.&lt;/p&gt;
&lt;p data-end=&quot;1882&quot; data-start=&quot;1790&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1908&quot; data-start=&quot;1884&quot; data-ke-size=&quot;size20&quot;&gt;세 번째는 &lt;b&gt;MCP Server&lt;/b&gt;입니다.&lt;/h4&gt;
&lt;p data-end=&quot;2042&quot; data-start=&quot;1910&quot; data-ke-size=&quot;size16&quot;&gt;MCP Server는 실제 외부 도구나 데이터와 연결되는 서버입니다.&lt;br /&gt;예를 들어 GitHub 이슈를 조회하는 MCP Server, 로컬 파일을 읽는 MCP Server, 사내 API를 호출하는 MCP Server를 만들 수 있습니다. 구조를 간단히 표현하면 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;2126&quot; data-start=&quot;2068&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;사용자 &amp;rarr; AI 애플리케이션 &amp;rarr; MCP Client &amp;rarr; MCP Server &amp;rarr; 외부 시스템&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-end=&quot;2238&quot; data-start=&quot;2128&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2238&quot; data-start=&quot;2128&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 사용자가 AI에게 &amp;ldquo;최근 GitHub 이슈를 정리해줘&amp;rdquo;라고 말하면, AI 애플리케이션은 MCP Server를 통해 GitHub API를 호출하고, 그 결과를 바탕으로 답변할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;2238&quot; data-start=&quot;2128&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2274&quot; data-start=&quot;2245&quot; data-section-id=&quot;1l7e7yr&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MCP Server는 무엇을 제공할 수 있을까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2306&quot; data-start=&quot;2276&quot; data-ke-size=&quot;size16&quot;&gt;MCP Server는 보통 세 가지 기능을 제공합니다.&lt;/p&gt;
&lt;h4 data-end=&quot;2327&quot; data-start=&quot;2308&quot; data-ke-size=&quot;size20&quot;&gt;첫 번째는 &lt;b&gt;Tools&lt;/b&gt;입니다.&lt;/h4&gt;
&lt;p data-end=&quot;2359&quot; data-start=&quot;2329&quot; data-ke-size=&quot;size16&quot;&gt;Tools는 AI가 호출할 수 있는 함수나 액션입니다.&amp;nbsp; 예를 들어 다음과 같은 도구를 만들 수 있습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;2432&quot; data-start=&quot;2390&quot; data-ke-style=&quot;style3&quot;&gt;get_github_issues&lt;br /&gt;최근 GitHub 이슈 목록을 조회한다.&lt;br /&gt;&lt;br /&gt;get_order_list&lt;br /&gt;최근 주문 목록을 조회한다.&lt;/blockquote&gt;
&lt;p data-end=&quot;2568&quot; data-start=&quot;2535&quot; data-ke-size=&quot;size16&quot;&gt;즉, Tools는 &lt;b&gt;AI가 실제로 실행할 수 있는 기능&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-end=&quot;2568&quot; data-start=&quot;2535&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;2593&quot; data-start=&quot;2570&quot; data-ke-size=&quot;size20&quot;&gt;두 번째는 &lt;b&gt;Resources&lt;/b&gt;입니다.&lt;/h4&gt;
&lt;p data-end=&quot;2625&quot; data-start=&quot;2595&quot; data-ke-size=&quot;size16&quot;&gt;Resources는 AI가 읽을 수 있는 데이터입니다.&amp;nbsp;예를 들어 다음과 같은 리소스가 있을 수 있습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;2712&quot; data-start=&quot;2657&quot; data-ke-style=&quot;style3&quot;&gt;프로젝트 README&lt;br /&gt;API 문서&lt;br /&gt;로그 파일&lt;br /&gt;데이터베이스 테이블 정보&lt;br /&gt;고객 정책 문서&lt;/blockquote&gt;
&lt;p data-end=&quot;2760&quot; data-start=&quot;2714&quot; data-ke-size=&quot;size16&quot;&gt;Tools가 &amp;ldquo;행동&amp;rdquo;이라면 Resources는 &amp;ldquo;읽을 수 있는 정보&amp;rdquo;에 가깝습니다.&lt;/p&gt;
&lt;p data-end=&quot;2760&quot; data-start=&quot;2714&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;2783&quot; data-start=&quot;2762&quot; data-ke-size=&quot;size20&quot;&gt;세 번째는 &lt;b&gt;Prompts&lt;/b&gt;입니다.&lt;/h4&gt;
&lt;p data-end=&quot;2830&quot; data-start=&quot;2785&quot; data-ke-size=&quot;size16&quot;&gt;Prompts는 AI가 특정 작업을 더 잘 수행하도록 미리 정의한 프롬프트입니다.&amp;nbsp;예를 들어 다음과 같은 프롬프트를 제공할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;2925&quot; data-start=&quot;2864&quot; data-ke-style=&quot;style3&quot;&gt;버그 리포트 분석 프롬프트&lt;br /&gt;API 문서 요약 프롬프트&lt;br /&gt;코드 리뷰 프롬프트&lt;br /&gt;장애 보고서 작성 프롬프트&lt;/blockquote&gt;
&lt;p data-end=&quot;2977&quot; data-start=&quot;2927&quot; data-ke-size=&quot;size16&quot;&gt;이 세 가지 중에서 입문 단계에서는 &lt;b&gt;Tools&lt;/b&gt;를 먼저 이해하는 것이 가장 좋습니다.&lt;/p&gt;
&lt;p data-end=&quot;2977&quot; data-start=&quot;2927&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;2977&quot; data-start=&quot;2927&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MCP Tool을 설계할 때 중요한 점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP Tool을 만들 때는 단순히 &amp;ldquo;기능을 열어준다&amp;rdquo;가 아니라, AI에게 어떤 권한을 줄 것인지 신중하게 설계해야 합니다.&lt;/p&gt;
&lt;p data-end=&quot;4433&quot; data-start=&quot;4417&quot; data-ke-size=&quot;size16&quot;&gt;특히 다음 기준이 중요합니다.&lt;/p&gt;
&lt;p data-end=&quot;4460&quot; data-start=&quot;4435&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;4460&quot; data-start=&quot;4435&quot; data-ke-size=&quot;size20&quot;&gt;첫 번째, 도구 이름은 &lt;b&gt;구체적&lt;/b&gt;으로 작성합니다.&lt;/h4&gt;
&lt;p data-end=&quot;4499&quot; data-start=&quot;4462&quot; data-ke-size=&quot;size16&quot;&gt;get_data보다는 get_project_status가 좋습니다.&lt;/p&gt;
&lt;p data-end=&quot;4499&quot; data-start=&quot;4462&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;4530&quot; data-start=&quot;4501&quot; data-ke-size=&quot;size20&quot;&gt;두 번째, 설명은 AI가 &lt;b&gt;판단&lt;/b&gt;할 수 있게 작성합니다.&lt;/h4&gt;
&lt;p data-end=&quot;4592&quot; data-start=&quot;4532&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;프로젝트 정보를 가져온다&amp;rdquo;보다는 &amp;ldquo;프로젝트 ID를 기준으로 진행률, 상태, 담당자를 조회한다&amp;rdquo;가 좋습니다.&lt;/p&gt;
&lt;p data-end=&quot;4592&quot; data-start=&quot;4532&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;4617&quot; data-start=&quot;4594&quot; data-ke-size=&quot;size20&quot;&gt;세 번째, 입력값은 &lt;b&gt;최소한&lt;/b&gt;으로 제한합니다.&lt;/h4&gt;
&lt;p data-end=&quot;4664&quot; data-start=&quot;4619&quot; data-ke-size=&quot;size16&quot;&gt;AI에게 너무 많은 입력 자유도를 주면 의도하지 않은 요청이 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;4664&quot; data-start=&quot;4619&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;4691&quot; data-start=&quot;4666&quot; data-ke-size=&quot;size20&quot;&gt;네 번째, &lt;b&gt;반환값&lt;/b&gt;은 필요한 정보만 포함합니다.&lt;/h4&gt;
&lt;p data-end=&quot;4733&quot; data-start=&quot;4693&quot; data-ke-size=&quot;size16&quot;&gt;내부 ID, 토큰, 민감한 고객 정보 등은 반환하지 않는 것이 좋습니다.&lt;/p&gt;
&lt;p data-end=&quot;4733&quot; data-start=&quot;4693&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;4761&quot; data-start=&quot;4735&quot; data-ke-size=&quot;size20&quot;&gt;다섯 번째, &lt;b&gt;쓰기 작업&lt;/b&gt;은 더 신중하게 다룹니다.&lt;/h4&gt;
&lt;p data-end=&quot;4807&quot; data-start=&quot;4763&quot; data-ke-size=&quot;size16&quot;&gt;조회 작업은 상대적으로 안전하지만, 생성&amp;middot;수정&amp;middot;삭제 작업은 위험할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;4838&quot; data-start=&quot;4809&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 도구는 특히 주의해야 합니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;4936&quot; data-start=&quot;4840&quot; data-ke-style=&quot;style3&quot;&gt;delete_user&lt;br /&gt;update_payment_status&lt;br /&gt;send_email_to_customer&lt;br /&gt;deploy_production&lt;br /&gt;run_sql_query&lt;/blockquote&gt;
&lt;p data-end=&quot;4979&quot; data-start=&quot;4938&quot; data-ke-size=&quot;size16&quot;&gt;이런 도구는 반드시 권한 체크, 입력 검증, 승인 흐름을 고려해야 합니다.&lt;/p&gt;
&lt;p data-end=&quot;4979&quot; data-start=&quot;4938&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;4979&quot; data-start=&quot;4938&quot; data-ke-size=&quot;size16&quot;&gt;MCP는 AI 시대에 점점 중요해질 가능성이 높은 기술입니다. 이전의 AI 도구가 &amp;ldquo;질문에 답하는 도구&amp;rdquo;였다면,&lt;/p&gt;
&lt;p data-end=&quot;6860&quot; data-start=&quot;6776&quot; data-ke-size=&quot;size16&quot;&gt;MCP를 활용한 AI 에이전트는 &lt;b&gt;&amp;ldquo;외부 시스템과 연결되어 실제 작업을 수행하는 도구&amp;rdquo;&lt;/b&gt;에 가까워집니다.&lt;/p&gt;
&lt;p data-end=&quot;6860&quot; data-start=&quot;6776&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;6876&quot; data-start=&quot;6862&quot; data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;6913&quot; data-start=&quot;6878&quot; data-ke-style=&quot;style3&quot;&gt;- MCP는 Model Context Protocol의 약자입니다.&lt;br /&gt;- AI 애플리케이션이 외부 도구와 데이터를 사용할 수 있도록 연결하는 표준입니다.&lt;br /&gt;- MCP Server는 AI가 호출할 수 있는 Tools, 읽을 수 있는 Resources, 재사용 가능한 Prompts를 제공할 수 있습니다.&lt;br /&gt;- 입문 단계에서는 Tools부터 이해하는 것이 좋습니다.&lt;br /&gt;- MCP Tool은 이름, 설명, 입력값, 권한 범위를 명확하게 설계해야 합니다.&lt;br /&gt;- 쓰기 작업이나 민감한 정보 접근은 반드시 보안과 승인 흐름을 고려해야 합니다.&lt;/blockquote&gt;
&lt;p data-end=&quot;6959&quot; data-start=&quot;6915&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;7236&quot; data-start=&quot;7165&quot; data-ke-size=&quot;size16&quot;&gt;앞으로 AI 코딩 에이전트나 사내 AI 업무 자동화 도구를 만들 계획이 있다면, MCP는 한 번쯤 꼭 이해해둘 만한 기술입니다.&lt;/p&gt;
&lt;p data-end=&quot;7303&quot; data-start=&quot;7238&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 거창한 자동화보다 &lt;b&gt;&amp;ldquo;AI가 안전하게 조회할 수 있는 작은 도구 하나&amp;rdquo;&lt;/b&gt;를 만들어보는 것부터 시작하면 좋습니다.&lt;/p&gt;
&lt;p data-end=&quot;7303&quot; data-start=&quot;7238&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;7318&quot; data-start=&quot;7310&quot; data-section-id=&quot;3c0sv5&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;7409&quot; data-start=&quot;7320&quot; data-ke-size=&quot;size16&quot;&gt;Model Context Protocol 공식 문서&lt;br /&gt;&lt;a href=&quot;https://modelcontextprotocol.io/docs/getting-started/intro&quot;&gt;https://modelcontextprotocol.io/docs/getting-started/intro&lt;/a&gt;&lt;/p&gt;
&lt;p data-end=&quot;7486&quot; data-start=&quot;7411&quot; data-ke-size=&quot;size16&quot;&gt;MCP TypeScript SDK&lt;br /&gt;&lt;a href=&quot;https://github.com/modelcontextprotocol/typescript-sdk&quot;&gt;https://github.com/modelcontextprotocol/typescript-sdk&lt;/a&gt;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;7560&quot; data-start=&quot;7488&quot; data-ke-size=&quot;size16&quot;&gt;Anthropic MCP 소개&lt;br /&gt;&lt;a href=&quot;https://www.anthropic.com/news/model-context-protocol&quot;&gt;https://www.anthropic.com/news/model-context-protocol&lt;/a&gt;&lt;/p&gt;</description>
      <category>Programming</category>
      <category>AI</category>
      <category>API</category>
      <category>MCP</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/45</guid>
      <comments>https://mishka.tistory.com/45#entry45comment</comments>
      <pubDate>Tue, 28 Apr 2026 15:19:02 +0900</pubDate>
    </item>
    <item>
      <title>AI 코딩 에이전트 제대로 쓰기: AGENTS.md 작성법</title>
      <link>https://mishka.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 개발 방식은 빠르게 바뀌고 있다.&amp;nbsp;이전에는 ChatGPT에게 코드를 물어보고, 답변을 복사해서 프로젝트에 붙여 넣는 방식이 많았다.&lt;br /&gt;하지만 이제는 Cursor, Claude Code, GitHub Copilot, Codex 같은 AI 코딩 에이전트가 실제 코드베이스를 읽고, 파일을 수정하고, 테스트를 실행하고, PR 단위 작업까지 수행하는 방향으로 발전하고 있다. 이때 중요한 문제가 생긴다&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;AI는 우리 프로젝트의 규칙을 어떻게 알 수 있을까?&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사람 개발자는 README를 읽고, 기존 코드를 보고, 팀 컨벤션을 파악하면서 작업한다. &lt;/span&gt;&lt;br /&gt;&lt;span&gt;하지만 AI 에이전트에게는 프로젝트 규칙을 더 명확하게 알려줘야 한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;이때 사용하는 파일이 바로 `&lt;b&gt;AGENTS.md`&lt;/b&gt;다. &lt;/span&gt;쉽게 말해 &lt;b&gt;AI 에이전트를 위한 작업 지침서&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README에는 보통 프로젝트 소개, 설치 방법, 실행 방법이 들어간다.&lt;br /&gt;반면 AGENTS.md에는 AI가 코드를 수정할 때 지켜야 할 규칙을 적는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 패키지 매니저는 pnpm을 사용한다.&lt;br /&gt;- 새 파일은 TypeScript로 작성한다.&lt;br /&gt;- any 타입은 되도록 사용하지 않는다.&lt;br /&gt;- 코드 수정 후 pnpm typecheck, pnpm lint, pnpm test를 실행한다.&lt;br /&gt;- 기존 테스트를 삭제하지 않는다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, AGENTS.md는 AI에게 &amp;ldquo;이 프로젝트에서는 이렇게 일해줘&amp;rdquo;라고 알려주는 문서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 AGENTS.md가 필요할까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 도구는 점점 똑똑해지고 있지만, 프로젝트마다 다른 규칙까지 자동으로 정확히 알지는 못한다.&lt;/p&gt;
&lt;p data-end=&quot;1336&quot; data-start=&quot;1294&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 어떤 프로젝트는 npm을 쓰고, 어떤 프로젝트는 pnpm을 쓴다.&lt;/p&gt;
&lt;pre id=&quot;code_1777346854838&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install
// or
pnpm install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 의존성을 설치하는 명령어지만, 프로젝트에 따라 하나만 맞는 경우가 많다.&lt;/p&gt;
&lt;p data-end=&quot;1468&quot; data-start=&quot;1435&quot; data-ke-size=&quot;size16&quot;&gt;또 어떤 팀은 default export를 선호할 수 있고, 다른 팀은 named export만 사용할 수도 있다.&lt;/p&gt;
&lt;p data-end=&quot;1468&quot; data-start=&quot;1435&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1468&quot; data-start=&quot;1435&quot; data-ke-size=&quot;size16&quot;&gt;AI가 이런 규칙을 모르면 기존 코드 스타일과 다른 코드를 만들 수 있다.&amp;nbsp;결과적으로 다음과 같은 문제가 생긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1722&quot; data-start=&quot;1701&quot;&gt;실행되지 않는 명령어를 사용한다.&lt;/li&gt;
&lt;li data-end=&quot;1722&quot; data-start=&quot;1701&quot;&gt;기존 코드 스타일과 다른 코드를 만든다.&lt;/li&gt;
&lt;li data-end=&quot;1722&quot; data-start=&quot;1701&quot;&gt;테스트 없이 코드를 수정한다.&lt;/li&gt;
&lt;li data-end=&quot;1722&quot; data-start=&quot;1701&quot;&gt;프로젝트 구조와 다른 위치에 파일을 만든다.&lt;/li&gt;
&lt;li data-end=&quot;1722&quot; data-start=&quot;1701&quot;&gt;사용하지 않는 라이브러리를 새로 추가한다.&lt;/li&gt;
&lt;li data-end=&quot;1722&quot; data-start=&quot;1701&quot;&gt;실패하는 테스트를 삭제하려고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AGENTS.md는 이런 실수를 줄이기 위한 최소한의 안전장치다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1939&quot; data-start=&quot;1914&quot; data-section-id=&quot;1e6vbzp&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AGENTS.md에 무엇을 적어야 할까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 길고 복잡하게 작성할 필요는 없다. 오히려 짧고 구체적인 문서가 더 좋다.&lt;/p&gt;
&lt;p data-end=&quot;2023&quot; data-start=&quot;1991&quot; data-ke-size=&quot;size16&quot;&gt;AGENTS.md에는 최소한 다음 항목을 넣는 것이 좋다.&lt;/p&gt;
&lt;blockquote data-end=&quot;2023&quot; data-start=&quot;1991&quot; data-ke-style=&quot;style2&quot;&gt;1. 프로젝트 개요&lt;br /&gt;2. 기술 스택&lt;br /&gt;3. 자주 사용하는 명령어&lt;br /&gt;4. 코드 작성 규칙&lt;br /&gt;5. 테스트 규칙&lt;br /&gt;6. 금지 사항&lt;br /&gt;7. 작업 완료 전 체크리스트&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 항목은 AI가 실제로 작업할 때 판단 기준으로 사용할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기본 AGENTS.md 예시&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 루트에 AGENTS.md 파일을 만든다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;my-project&lt;br /&gt;├─ src&lt;br /&gt;├─ package.json&lt;br /&gt;├─ README.md&lt;br /&gt;└─ AGENTS.md&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음처럼 작성할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;# AGENTS.md &lt;br /&gt;## Project Overview &lt;br /&gt;This project is a TypeScript-based web application. &lt;br /&gt;&lt;br /&gt;## Tech Stack &lt;br /&gt;- TypeScript &lt;br /&gt;- React &lt;br /&gt;- pnpm &lt;br /&gt;- Vitest &lt;br /&gt;&lt;br /&gt;## Commands &lt;br /&gt;- Install dependencies: `pnpm install` &lt;br /&gt;- Start dev server: `pnpm dev` &lt;br /&gt;- Type check: `pnpm typecheck` &lt;br /&gt;- Lint: `pnpm lint` &lt;br /&gt;- Test: `pnpm test` &lt;br /&gt;&lt;br /&gt;## Coding Rules &lt;br /&gt;- Use TypeScript for all new files. &lt;br /&gt;- Do not use `any` unless necessary. &lt;br /&gt;- Follow the existing folder structure. &lt;br /&gt;- Reuse existing components before creating new ones. &lt;br /&gt;- Keep changes focused on the requested task. &lt;br /&gt;&lt;br /&gt;## Testing Rules &lt;br /&gt;- Add tests when changing business logic. &lt;br /&gt;- Update tests when behavior changes. &lt;br /&gt;- Do not remove tests just to make the test suite pass. &lt;br /&gt;&lt;br /&gt;## Do Not &lt;br /&gt;- Do not use `npm install` or `yarn add`. &lt;br /&gt;- Do not add new dependencies without approval. &lt;br /&gt;- Do not modify generated files. &lt;br /&gt;- Do not change public API contracts without updating tests.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 AGENTS.md는 길지 않아도 된다. 대신 &lt;b&gt;구체적이어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;작성시 주의할점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;첫 번째, 너무 길게 쓰지 않는다. &lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span&gt;처음에는 명령어, 코드 스타일, 테스트 규칙, 금지 사항 정도만 작성해도 충분하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 번째, 추상적인 표현을 피한다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;좋은 코드를 작성하세요. ❌&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 이렇게 적는다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Do not use `any` unless necessary. ✅&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세 번째, AI가 마음대로 판단하면 안 되는 부분을 명시한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;네 번째, 실제로 존재하는 명령어만 적는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 에이전트는 앞으로 개발 과정에서 더 많이 사용될 가능성이 높다.&lt;/p&gt;
&lt;p data-end=&quot;6380&quot; data-start=&quot;6326&quot; data-ke-size=&quot;size16&quot;&gt;하지만 AI에게 아무런 지침 없이 작업을 맡기면 프로젝트 규칙과 맞지 않는 코드가 나올 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;6425&quot; data-start=&quot;6382&quot; data-ke-size=&quot;size16&quot;&gt;AGENTS.md는 이런 문제를 줄이기 위한 간단하지만 효과적인 방법이다.&lt;/p&gt;
&lt;p data-end=&quot;6425&quot; data-start=&quot;6382&quot; data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-end=&quot;6425&quot; data-start=&quot;6382&quot; data-ke-style=&quot;style3&quot;&gt;- AGENTS.md는 AI 에이전트를 위한 작업 지침서다. &lt;br /&gt;- README.md와 역할이 다르다. &lt;br /&gt;- 명령어, 코드 스타일, 테스트 규칙, 금지 사항을 구체적으로 적어야 한다. &lt;br /&gt;- 너무 길게 쓰기보다 짧고 명확하게 작성하는 것이 좋다. &lt;br /&gt;- AGENTS.md는 테스트, 린트, CI와 함께 사용할 때 더 효과적이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 코딩 도구를 제대로 쓰고 싶다면, 먼저 프로젝트 루트에 AGENTS.md 파일 하나를 추가해보자.&lt;/p&gt;
&lt;p data-end=&quot;6731&quot; data-start=&quot;6691&quot; data-ke-size=&quot;size16&quot;&gt;작은 문서 하나가 AI가 작성하는 코드의 품질을 꽤 많이 바꿀 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;6731&quot; data-start=&quot;6691&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;6731&quot; data-start=&quot;6691&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고자료&lt;/b&gt;&lt;/h4&gt;
&lt;p data-end=&quot;6731&quot; data-start=&quot;6691&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6763&quot; data-start=&quot;6743&quot; data-section-id=&quot;1gmyv7f&quot;&gt;&lt;a href=&quot;https://agents.md/&quot;&gt;https://agents.md/&lt;/a&gt;&lt;/li&gt;
&lt;li data-end=&quot;6818&quot; data-start=&quot;6764&quot; data-section-id=&quot;1jdvqak&quot;&gt;&lt;a href=&quot;https://developers.openai.com/codex/guides/agents-md&quot;&gt;https://developers.openai.com/codex/guides/agents-md&lt;/a&gt;&lt;/li&gt;
&lt;li data-end=&quot;6918&quot; data-start=&quot;6819&quot; data-section-id=&quot;1lm8qzw&quot;&gt;&lt;a href=&quot;https://docs.github.com/copilot/customizing-copilot/adding-custom-instructions-for-github-copilot&quot;&gt;https://docs.github.com/copilot/customizing-copilot/adding-custom-instructions-for-github-copilot&lt;/a&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/44</guid>
      <comments>https://mishka.tistory.com/44#entry44comment</comments>
      <pubDate>Tue, 28 Apr 2026 12:39:30 +0900</pubDate>
    </item>
    <item>
      <title>모던 CSS 완벽 가이드: JS 없이 구현하는 마법, Nesting과 :has()</title>
      <link>https://mishka.tistory.com/43</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;UI 상태 하나 바꾸겠다고 오늘도 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;useState&lt;/span&gt;와 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;onMouseEnter&lt;/span&gt;를 번갈아 가며 자바스크립트 코드를 짜셨나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 복잡한 CSS 계층 구조를 잡기 위해 프로젝트 시작부터 습관적으로 Sass(SCSS) 환경부터 세팅하고 계시진 않나요?&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;이제 무거운 자바스크립트 연산과 외부 전처리기에서 벗어날 때가 되었습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;2026년 현재, 순수 네이티브 CSS만으로도 우리가 JS로 힘들게 구현했던 수많은 로직들을 단 몇 줄의 코드로 우아하게 처리할 수 있는 시대가 열렸습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;불필요한 렌더링을 줄이고 프론트엔드의 성능과 가독성을 한 단계 끌어올려 줄 모던 CSS의 두 가지 마법.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;이제는 선택이 아닌 필수가 될 수도 있는&amp;nbsp;&lt;b data-index-in-node=&quot;76&quot; data-path-to-node=&quot;5&quot;&gt;Nesting&lt;/b&gt;과 &lt;b data-index-in-node=&quot;85&quot; data-path-to-node=&quot;5&quot;&gt;:has()&lt;/b&gt; 선택자의 진짜 위력을 실무 예제와 함께 살펴보겠습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;CSS Nesting 심화: 명시도(Specificity)의 이해&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;과거에는 중첩된 HTML 구조를 스타일링하기 위해 클래스명을 길게 나열하거나 전처리기를 꼭 써야 했습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;이제는 네이티브 CSS만으로 계층 구조를 직관적으로 표현할 수 있죠.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;하지만 단순히 코드가 깔끔해지는 것을 넘어, 실무에 적용할 때 반드시 알아야 할 점은 바로 &lt;b&gt;명시도(Specificity)&lt;/b&gt;의 작동 방식입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;내부적으로 &lt;b&gt;Nesting&lt;/b&gt;은 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;:is()&lt;/span&gt; 의사 클래스로 감싸진 것처럼 동작합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771812218036&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 작성한 CSS */
.card {
  h2, .title {
    color: red;
  }
}

/* 브라우저가 해석하는 방식 */
.card :is(h2, .title) {
  color: red;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;:is()&lt;/span&gt;는 괄호 안의 선택자 중 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;10&quot;&gt;가장 높은 명시도&lt;/b&gt;를 따릅니다. 따라서 위 예시에서 h2에만 스타일을 주더라도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;.title&lt;/span&gt;의 명시도(클래스 선택자)가 반영되어 예상보다 우선순위가 높아질 수 있으므로 구조 설계 시 이 점을 반드시 유의해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;:has() 선택자 심화: 관계형 선택자의 진가&lt;/h3&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;그동안 CSS의 가장 큰 한계 중 하나는 자식 요소의 상태에 따라 부모 요소의 스타일을 변경할 수 없다는 것이었습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;:has()&lt;/span&gt; 의사 클래스는 이 문제를 완벽하게 해결합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;하지만 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;:has()&lt;/span&gt;를 '부모 선택자'로만 부르는 것은 이 기능의 절반만 이해한 것입니다. 부모뿐만 아니라 &lt;b data-index-in-node=&quot;59&quot; data-path-to-node=&quot;13&quot;&gt;이전 형제(Previous Sibling) 요소&lt;/b&gt;나 &lt;b data-index-in-node=&quot;87&quot; data-path-to-node=&quot;13&quot;&gt;전체 컨텍스트의 상태&lt;/b&gt;를 스타일링할 수 있는 진정한 의미의 '관계형 선택자'입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14&quot;&gt;실전 예제: JS 없는 형제 요소 하이라이트 효과 (Hover Spotlight)&lt;/b&gt; 특정 카드에 마우스를 올렸을 때, 주변의 다른 카드들은 흐리게 만드는 효과를 오직 CSS만으로 구현할 수 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1771812300457&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* .grid 컨테이너 안에 마우스가 올라간 .card가 '하나라도 있다면' */
.grid:has(.card:hover) .card:not(:hover) {
  opacity: 0.5;
  filter: blur(2px);
  transition: all 0.3s ease;
}

/* 마우스가 올라간 본인 요소 */
.card:hover {
  transform: scale(1.05);
  box-shadow: 0 10px 20px rgba(0,0,0,0.2);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거라면 요소마다 마우스 이벤트를 걸고 React 상태로 관리하여 전체 리렌더링을 유발해야 했던 효과를 이렇게 단 몇 줄로 끝낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;성능 고려사항 (Performance Considerations)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;'DOM 트리를 역추적하거나 복잡하게 탐색하면 렌더링 성능이 떨어지지 않을까?' 하는 걱정이 드실 수 있습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;하지만 최신 브라우저 엔진은 캐싱 및 무효화(Invalidation) 휴리스틱을 극도로 고도화하여 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;:has()&lt;/span&gt;의 성능을 매우 최적화했습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;극단적으로 깊고 복잡한 DOM 구조가 아니라면, 오히려 JS로 상태를 관리하며 리렌더링 파이프라인을 가동하는 것보다&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;브라우저 네이티브 단에서 처리하는 것이 성능 면에서 훨씬 효율적입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;마무리하며: 이제는 다시 '바닐라 CSS'를 돌아볼 때&lt;/h3&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;수많은 프레임워크와 CSS-in-JS 라이브러리들이 쏟아지는 와중에도, 웹 표준은 묵묵히, 그리고 아주 강력하게 발전해 왔습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;Nesting과&lt;span style=&quot;background-color: #dddddd;&quot;&gt; :has()&lt;/span&gt;는 그 발전의 정점이라 할 수 있죠. 이 두 가지만 잘 활용해도 우리가 습관적으로 작성하던 불필요한 자바스크립트 코드의 절반은 덜어낼 수 있습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23&quot;&gt;여러분은 현재 프로젝트에서 어떤 CSS 방식을 주로 사용하고 계신가요?&lt;/b&gt; 여전히 Sass나 Styled-components의 강력함에 기대고 계신가요, 아니면 점진적으로 모던 순수 CSS나 Tailwind 같은 유틸리티 클래스로 넘어가고 계신가요?&lt;/p&gt;</description>
      <category>Programming</category>
      <category>css</category>
      <category>CSSNesting</category>
      <category>has선택자</category>
      <category>ModernCSS</category>
      <category>UI개발</category>
      <category>웹표준</category>
      <category>프론트엔드최적화</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/43</guid>
      <comments>https://mishka.tistory.com/43#entry43comment</comments>
      <pubDate>Mon, 23 Feb 2026 11:16:28 +0900</pubDate>
    </item>
    <item>
      <title>React Server Components (RSC) 완벽 이해하기: 렌더링 아키텍처의 혁신</title>
      <link>https://mishka.tistory.com/42</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React 개발자라면 요즘 매일같이 듣는 단어가 있을 겁니다. 바로 &lt;b&gt;'React Server Components(RSC)'&lt;/b&gt;죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&quot;Next.js 13 이후로 그냥 기본으로 쓰는 거 아니야?&quot; 혹은 &quot;기존 SSR(서버 사이드 렌더링)이랑 이름만 다르고 비슷한 거겠지&quot;라고 생각하며 무심코 넘어가셨나요? 만약 여전히 단순한 텍스트나 정적 데이터를 띄우기 위해 수백 KB의 자바스크립트 번들을 통째로 브라우저에 내려보내고 있다면, 여러분은 지금 React 렌더링 패러다임의 가장 거대한 변화를 놓치고 있는지도 모릅니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;'클라이언트'와 '서버'의 경계를 허물고, 번들 사이즈 0(Zero)의 기적을 만들어내는 &lt;b&gt;RSC&lt;/b&gt;. 오늘은 매번 헷갈리던 RSC의 진짜 정체와, 왜 이것이 단순한 기능 추가를 넘어 프론트엔드 아키텍처의 완전한 혁신인지 살펴보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존 SSR과 RSC의 결정적 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 개발자가 SSR과 RSC를 혼동합니다. Next.js의 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;getServerSideProps&lt;/span&gt; 같은 기존 SSR은 서버에서 HTML을 완성하여 내려주지만, 상호작용을 위해서는 결국 전체 자바스크립트 번들을 다운로드하고 하이드레이션(Hydration) 과정을 거쳐야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, RSC는 렌더링 결과를 HTML이 아닌 &lt;b&gt;직렬화된 특별한 JSON 형태(React Flight 포맷)&lt;/b&gt;로 스트리밍합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;- SSR: 완성된 '그림(HTML)'을 주고, 나중에 '생명(JS)'을 불어넣음.&lt;br /&gt;- RSC: 서버에서 연산이 끝난 정적인 부분은 JS 코드를 아예 클라이언트로 보내지 않음. 오직 인터랙션이 필요한 부분(Client Component)만 분리하여 로드.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;핵심 개념: &quot;use client&quot;와 렌더링 경계 (Boundary)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&quot;use client&quot;&lt;/span&gt; 지시어는 컴포넌트를 클라이언트에서 렌더링하라는 뜻이 아닙니다. 정확히는 &lt;b&gt;&quot;이 지점부터 서버와 클라이언트의 경계를 나눈다&quot;&lt;/b&gt;는 선언입니다.&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;9&quot; data-ke-style=&quot;style2&quot;&gt;- 서버 컴포넌트는 클라이언트 컴포넌트를 import 할 수 있습니다. (트리의 잎사귀 역할)&lt;br /&gt;- 주의할 점: 클라이언트 컴포넌트 내부에서는 서버 컴포넌트를 직접 import 할 수 없습니다. 단, &lt;b&gt;children prop&lt;/b&gt;을 통해 서버 컴포넌트를 전달받아 렌더링하는 것은 가능합니다. (Composition 패턴)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;실무 활용: 데이터 페칭(Data Fetching)과 Streaming&lt;/h3&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;RSC의 진가는 비동기 데이터 처리에서 나타납니다. &lt;span style=&quot;background-color: #dddddd;&quot;&gt;useEffect&lt;/span&gt;나 React Query 없이, 컴포넌트 자체를 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;async/await&lt;/span&gt;로 선언하여 서버에서 직접 DB나 API를 호출할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771811526595&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// app/users/page.tsx (Server Component)
import { Suspense } from 'react';
import UserList from './UserList'; //   Client Component
import Loading from './Loading';

// 비동기 컴포넌트
export default async function UsersPage() {
  // 브라우저가 아닌 서버 환경에서 직접 DB 쿼리 실행
  const users = await db.query('SELECT * FROM users'); 

  return (
    &amp;lt;main&amp;gt;
      &amp;lt;h1&amp;gt;사용자 목록&amp;lt;/h1&amp;gt;
      {/* 데이터가 로딩되는 동안 UI를 먼저 스트리밍 */}
      &amp;lt;Suspense fallback={&amp;lt;Loading /&amp;gt;}&amp;gt;
        {/* 직렬화 가능한 데이터(users)만 Client Component로 전달 */}
        &amp;lt;UserList initialData={users} /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/main&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;lt;Suspense&amp;gt;&lt;/span&gt;와 결합된 RSC는 데이터가 준비되는 대로 UI 조각을 클라이언트에 점진적으로 스트리밍하므로, TTFB(Time To First Byte)와 FCP(First Contentful Paint) 지표를 획기적으로 개선합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;마무리하며: RSC, 프론트엔드의 새로운 표준이 될까?&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;React Server Components가 가져다주는 이점&amp;mdash;&lt;b data-index-in-node=&quot;34&quot; data-path-to-node=&quot;3&quot;&gt;제로 번들 사이즈, 직관적인 서버 데이터 페칭, 그리고 빠른 초기 로딩&lt;/b&gt;&amp;mdash;은 분명 강력합니다. 단순히 컴포넌트를 그리는 방식을 넘어, 브라우저와 서버가 어떻게 협력해야 하는지에 대한 근본적인 패러다임 전환이죠.&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;물론 은탄환은 아닙니다. RSC를 실무에 도입하다 보면 서버와 클라이언트의 렌더링 경계(Boundary)를 나누는 설계 고민부터, 머리를 아프게 하는 복잡한 캐싱(Caching) 전략까지 기존과는 전혀 다른 낯선 문제들을 마주하게 됩니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 초기 러닝 커브를 넘어서고 나면, 이전의 렌더링 방식으로 돌아가기 힘들 만큼 효율적인 아키텍처를 경험하실 수 있을 것입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;여러분의 프로젝트는 지금 어디쯤 있나요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;이미 Next.js App Router와 RSC를 실무에 적극적으로 도입해 보셨나요, 아니면 아직은 기존 Page Router나 순수 SPA에 머물며 도입 시기를 재고 계신가요?&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;RSC를 적용하며 겪었던 뼈아픈 트러블슈팅 경험이나(특히 캐싱 이슈  !), 반대로 &quot;이건 정말 신세계다!&quot; 싶었던 점이 있다면 &lt;b data-index-in-node=&quot;73&quot; data-path-to-node=&quot;6&quot;&gt;아래 댓글로 자유롭게 공유해 주세요.&lt;/b&gt; 다양한 환경에서 고민하는 개발자분들의 생생한 이야기가 궁금합니다!&lt;/p&gt;</description>
      <category>Programming</category>
      <category>nextjs</category>
      <category>React</category>
      <category>ReactServerComponents</category>
      <category>rsc</category>
      <category>SSR</category>
      <category>렌더링패러다임</category>
      <category>프론트엔드아키텍처</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/42</guid>
      <comments>https://mishka.tistory.com/42#entry42comment</comments>
      <pubDate>Mon, 23 Feb 2026 10:58:55 +0900</pubDate>
    </item>
    <item>
      <title>Storybook으로 React 컴포넌트 문서화하기 - 실무 가이드</title>
      <link>https://mishka.tistory.com/41</link>
      <description>&lt;h1&gt;Storybook으로 React 컴포넌트 문서화하기 - 실무 가이드&lt;/h1&gt;
&lt;h2&gt;  왜 Storybook인가?&lt;/h2&gt;
&lt;p&gt;프론트엔드 개발을 하다 보면 이런 상황을 겪게 됩니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;이 버튼 컴포넌트 어떻게 쓰는 거지?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;props가 뭐가 있더라?&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;디자이너님, 이 상태 확인해 주세요&amp;quot; (매번 로컬 서버 켜기...)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Storybook&lt;/strong&gt;은 이 모든 문제를 해결합니다. 컴포넌트를 독립적으로 개발하고, 문서화하고, 테스트할 수 있는 워크샵이죠.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;  5분 만에 시작하기&lt;/h2&gt;
&lt;h3&gt;설치&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 기존 React 프로젝트에 추가
npx storybook@latest init
설치가 끝나면 자동으로 .storybook 폴더와 예제 스토리가 생성됩니다.

실행

npm run storybook
# http://localhost:6006 에서 확인&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;첫 번째 Story 작성하기&lt;/h3&gt;
&lt;h4&gt;Button 컴포넌트 예제&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Button.tsx

interface ButtonProps {
  variant: &amp;#39;primary&amp;#39; | &amp;#39;secondary&amp;#39; | &amp;#39;danger&amp;#39;;
  size: &amp;#39;sm&amp;#39; | &amp;#39;md&amp;#39; | &amp;#39;lg&amp;#39;;
  children: React.ReactNode;
  disabled?: boolean;
  onClick?: () =&amp;gt; void;
}

export const Button = ({ 
  variant = &amp;#39;primary&amp;#39;, 
  size = &amp;#39;md&amp;#39;, 
  children, 
  disabled,
  onClick 
}: ButtonProps) =&amp;gt; {
  return (
    &amp;lt;button
      className={`btn btn-${variant} btn-${size}`}
      disabled={disabled}
      onClick={onClick}
    &amp;gt;
      {children}
    &amp;lt;/button&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Button.stories.tsx

import type { Meta, StoryObj } from &amp;#39;@storybook/react&amp;#39;;
import { Button } from &amp;#39;./Button&amp;#39;;

const meta: Meta&amp;lt;typeof Button&amp;gt; = {
  title: &amp;#39;Components/Button&amp;#39;,
  component: Button,
  tags: [&amp;#39;autodocs&amp;#39;], // 자동 문서 생성!
  argTypes: {
    variant: {
      control: &amp;#39;select&amp;#39;,
      options: [&amp;#39;primary&amp;#39;, &amp;#39;secondary&amp;#39;, &amp;#39;danger&amp;#39;],
      description: &amp;#39;버튼 스타일&amp;#39;,
    },
    size: {
      control: &amp;#39;radio&amp;#39;,
      options: [&amp;#39;sm&amp;#39;, &amp;#39;md&amp;#39;, &amp;#39;lg&amp;#39;],
      description: &amp;#39;버튼 크기&amp;#39;,
    },
    onClick: { action: &amp;#39;clicked&amp;#39; }, // 클릭 이벤트 로깅
  },
};

export default meta;
type Story = StoryObj&amp;lt;typeof Button&amp;gt;;

// 기본 버튼
export const Primary: Story = {
  args: {
    variant: &amp;#39;primary&amp;#39;,
    children: &amp;#39;확인&amp;#39;,
  },
};

// 위험 버튼
export const Danger: Story = {
  args: {
    variant: &amp;#39;danger&amp;#39;,
    children: &amp;#39;삭제&amp;#39;,
  },
};

// 비활성화 상태
export const Disabled: Story = {
  args: {
    variant: &amp;#39;primary&amp;#39;,
    children: &amp;#39;비활성화&amp;#39;,
    disabled: true,
  },
};

// 모든 사이즈 한눈에
export const AllSizes: Story = {
  render: () =&amp;gt; (
    &amp;lt;div style={{ display: &amp;#39;flex&amp;#39;, gap: &amp;#39;1rem&amp;#39;, alignItems: &amp;#39;center&amp;#39; }}&amp;gt;
      &amp;lt;Button variant=&amp;quot;primary&amp;quot; size=&amp;quot;sm&amp;quot;&amp;gt;Small&amp;lt;/Button&amp;gt;
      &amp;lt;Button variant=&amp;quot;primary&amp;quot; size=&amp;quot;md&amp;quot;&amp;gt;Medium&amp;lt;/Button&amp;gt;
      &amp;lt;Button variant=&amp;quot;primary&amp;quot; size=&amp;quot;lg&amp;quot;&amp;gt;Large&amp;lt;/Button&amp;gt;
    &amp;lt;/div&amp;gt;
  ),
};&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;실무 활용 팁&lt;/h1&gt;
&lt;h2&gt;1. MDX로 풍부한 문서 작성&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;Button.mdx

import { Meta, Story, Canvas, Controls } from &amp;#39;@storybook/blocks&amp;#39;;
import * as ButtonStories from &amp;#39;./Button.stories&amp;#39;;

&amp;lt;Meta of={ButtonStories} /&amp;gt;

# Button 컴포넌트

사용자 인터랙션을 위한 기본 버튼 컴포넌트입니다.

## 사용법

import { Button } from &amp;#39;@/components/Button&amp;#39;;

&amp;lt;Button variant=&amp;quot;primary&amp;quot; size=&amp;quot;md&amp;quot;&amp;gt;
  클릭하세요
&amp;lt;/Button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;예제&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;Canvas of={ButtonStories.Primary} /&amp;gt;

Props

&amp;lt;Controls /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;디자인 가이드라인&lt;/h3&gt;
&lt;p&gt;• Primary: 주요 액션에 사용 (저장, 확인, 제출)&lt;br&gt;• Secondary: 보조 액션에 사용 (취소, 이전)&lt;br&gt;• Danger: 삭제, 경고 등 주의가 필요한 액션&lt;/p&gt;
&lt;h3&gt;2. 폴더 구조 추천&lt;/h3&gt;
&lt;p&gt;src/&lt;br&gt;├── components/&lt;br&gt;│   ├── Button/&lt;br&gt;│   │   ├── Button.tsx&lt;br&gt;│   │   ├── Button.stories.tsx&lt;br&gt;│   │   ├── Button.test.tsx&lt;br&gt;│   │   └── index.ts&lt;br&gt;│   ├── Input/&lt;br&gt;│   └── Card/&lt;/p&gt;
&lt;h3&gt;3. 자동 배포 (GitHub Pages)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# .github/workflows/storybook.yml
name: Deploy Storybook

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: &amp;#39;20&amp;#39;
      - run: npm ci
      - run: npm run build-storybook
      - uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./storybook-static&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Storybook은 단순한 문서화 도구를 넘어, &lt;em&gt;컴포넌트 기반 개발의 핵심 인프라&lt;/em&gt;입니다.&lt;br&gt;오늘 바로 시작해 보세요!&lt;/p&gt;</description>
      <category>Programming</category>
      <category>개발</category>
      <category>스토리북</category>
      <category>실무활용</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/41</guid>
      <comments>https://mishka.tistory.com/41#entry41comment</comments>
      <pubDate>Wed, 11 Feb 2026 14:15:26 +0900</pubDate>
    </item>
    <item>
      <title>Monorepo에서 Multi-stage Docker Builds 활용하여 NodeJS microservices 최적화된 이미지 만들기 (with. TurboRepo와 PNPM)</title>
      <link>https://mishka.tistory.com/40</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;원문: &lt;a href=&quot;https://fintlabs.medium.com/optimized-multi-stage-docker-builds-with-turborepo-and-pnpm-for-nodejs-microservices-in-a-monorepo-c686fdcf051f&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://fintlabs.medium.com/optimized-multi-stage-docker-builds-with-turborepo-and-pnpm-for-nodejs-microservices-in-a-monorepo-c686fdcf051f&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 번역글은 원문 글쓴이의 허가를 받고 진행한 번역본입니다. 원문 번역 이후에 실제로 프로젝트에 반영하면서 수정, 추가 한 내용들 남기겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1E0d1/btsHpsItDaL/gSKxx4ydEEFGMSWKMjgIgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1E0d1/btsHpsItDaL/gSKxx4ydEEFGMSWKMjgIgK/img.png&quot; data-alt=&quot;이미지출처: https://fintlabs.medium.com/optimized-multi-stage-docker-builds-with-turborepo-and-pnpm-for-nodejs-microservices-in-a-monorepo-c686fdcf051f&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1E0d1/btsHpsItDaL/gSKxx4ydEEFGMSWKMjgIgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1E0d1%2FbtsHpsItDaL%2FgSKxx4ydEEFGMSWKMjgIgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;394&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지출처: https://fintlabs.medium.com/optimized-multi-stage-docker-builds-with-turborepo-and-pnpm-for-nodejs-microservices-in-a-monorepo-c686fdcf051f&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;microservices와 monorepo의 세계에서 Docker 빌드를 최적화 하는 것은 효율성과 성능에 중요합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;소개 (Introduction)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.docker.com/build/building/multi-stage/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Multi-stage Docker builds&lt;/a&gt;는 가벼우면서 효율적인 컨테이너를 생성하는 기술입니다. 그러나 Turborepo와 PNPM를 함께 사용할 때 &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;microservice&lt;/span&gt;용 최적화된 Dockerfile을 얻는 것이 어려울 수 있습니다. &lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 글에서는 &lt;b&gt;TurboRepo&lt;/b&gt;와 &lt;b&gt;PNPM&lt;/b&gt;을 활용하여 &lt;span style=&quot;text-align: left;&quot;&gt;monorepo&lt;/span&gt; 내 &lt;/span&gt;NodeJS microservices의&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;빌드 프로세스를 &lt;/span&gt;Multi-stage builds 기술을 사용하여 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;최적화하는 방법을 살펴보겠습니다.&lt;/span&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;432d&quot; style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;목표(Goal)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;monorepo 내의 모든 NodeJS 기반 microservices를 빌드하기 위해 사용할 수 있는 단일 &lt;b&gt;Dockerfile&lt;/b&gt;을 원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 다음과 같은 요구사항을 준수해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빌드를 사용자 정의할 수 있도록 빌드인자를 사용합니다.(NodeJS 버전, 빌드할 프로젝트, 노출한 포트)&lt;/li&gt;
&lt;li&gt;특정 마이크로서비스에 필요한 종속성만 설치합니다.&lt;/li&gt;
&lt;li&gt;특정 마이크로서비스의 소스코드(및 종속성 패키지)만 빌드합니다.&lt;/li&gt;
&lt;li&gt;최소한의 필수 종속성 및 번들이 포함된 가벼운 Docker 이미지를 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;전제조건(Prerequisites)&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker가 설치되어 있고 &lt;a href=&quot;https://docs.docker.com/build/buildkit/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BuildKit&lt;/a&gt;이 활성화되어 있습니다.&lt;/li&gt;
&lt;li&gt;Turborepo로 관리되는 monorepo 또는 새로운 것을 설정하는 방법을 알고 있습니다. 그렇지 않은 경우 &lt;a href=&quot;https://turbo.build/repo/docs/getting-started/create-new&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;를 참고하세요&lt;/li&gt;
&lt;li&gt;PNPM Workspace를 이해하고 있습니다. 그렇지 않은경우 다음 링크를 참고하세요 (&lt;a href=&quot;https://pnpm.io/workspaces&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pnpm.io/workspaces&lt;/a&gt;, &lt;a href=&quot;https://turbo.build/repo/docs/handbook/workspaces&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://turbo.build/repo/docs/handbook/workspaces&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최종 multi-stage Dockerfile&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multi-stage builds는 빌드 프로세스를 여러 스테이지로 분리하여 최적화된 Docker이미지를 생성하는 기술입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 스테이지는 빌드의 특정 측면에 초점을 맞춰 더 작고 빠른 컨테이너를 만들어냅니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 최종 Dockerfile을 보여드리겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715838331171&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ARG NODE_VERSION=18.18.0

# Alpine 이미지
FROM node:${NODE_VERSION}-alpine AS alpine
RUN apk update
RUN apk add --no-cache libc6-compat

# Alpine 베이스에 pnpm 및 turbo 설정
FROM alpine as base
RUN npm install pnpm turbo --global
RUN pnpm config set store-dir ~/.pnpm-store

# Prune projects
FROM base AS pruner
ARG PROJECT

WORKDIR /app
COPY . .
RUN turbo prune --scope=${PROJECT} --docker

# 프로젝트 빌드
FROM base AS builder
ARG PROJECT

WORKDIR /app

# 프로젝트의 pnpm-lock.yaml 및 package.json 복사
COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
COPY --from=pruner /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY --from=pruner /app/out/json/ .

# 종속성 먼저 설치
RUN --mount=type=cache,id=pnpm,target=~/.pnpm-store pnpm install --frozen-lockfile

# 프로젝트 소스 코드 복사
COPY --from=pruner /app/out/full/ .

RUN turbo build --filter=${PROJECT}
RUN --mount=type=cache,id=pnpm,target=~/.pnpm-store pnpm prune --prod --no-optional
RUN rm -rf ./**/*/src

# Final image
FROM alpine AS runner
ARG PROJECT

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nodejs
USER nodejs

WORKDIR /app
COPY --from=builder --chown=nodejs:nodejs /app .
WORKDIR /app/apps/${PROJECT}

ARG PORT=8080
ENV PORT=${PORT}
ENV NODE_ENV=production
EXPOSE ${PORT}

CMD node dist/main&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;주요 단계 설명(Explanation of significant steps)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 Dockerfile을 이해하기 위해 각 단계가 무엇을 하는지 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;빌드인자(Build arguments)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 여러 프로젝트를 빌드하는 데 사용할 수 있는 단일 Dockerfile을 갖기 위해 Docker 빌드 인자를 활용해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;NODE_VERSION&lt;/b&gt;(선택) - 베이스 이미지 버전을 지정합니다. 여기서는 &lt;b&gt;alpine&lt;/b&gt; 이미지를 사용하여 가능한 최소한의 최종 이미지를 얻습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PROJECT&lt;/b&gt;(필수) - 빌드할 프로젝트를 지정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PORT&lt;/b&gt;(선택) - Docker 이미지를 노출하는 포트를 지정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;빌드를 위한 베이스 이미지 (Base image for build)&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1715838661677&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Alpine 베이스에 pnpm 및 turbo 설정
FROM alpine as base
RUN npm install pnpm turbo --global
RUN pnpm config set store-dir ~/.pnpm-store&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;alpine&lt;/b&gt; 이미지를 베이스로 합니다. npm을 사용하여 전역으로 &lt;b&gt;turbo&lt;/b&gt;와 &lt;b&gt;pnpm&lt;/b&gt;을 설치합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 &lt;b&gt;pnpm&lt;/b&gt;의 저장소 디렉토리를 캐싱에 사용하기 위해 설정합니다.&lt;/p&gt;
&lt;h4 id=&quot;2549&quot; style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Pruning&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1715838809321&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RUN turbo prune --scope=${PROJECT} --docker&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령에 대한 설명은 Turborepo의 &lt;a href=&quot;https://turbo.build/repo/docs/reference/command-line-reference/prune&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;를 참고 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;prune&lt;/b&gt; 명령은 다음을 포함하는 &lt;b&gt;out&lt;/b&gt; 폴더를 생성합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;prune 된 &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;workspace&lt;/span&gt;의 &lt;b&gt;package.json&lt;/b&gt; 파일이 있는 &lt;b&gt;json&lt;/b&gt; 폴더&lt;/li&gt;
&lt;li&gt;prune 된 &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;workspace&lt;/span&gt;의 전체 소스코드가 포함된 &lt;b&gt;full&lt;/b&gt; 폴더, 하지만 빌드 대상에 필요한 내부 패키지만 포함됩니다.&lt;/li&gt;
&lt;li&gt;실제로 사용되는 &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;workspace&lt;/span&gt;의 패키지에서 사용되는 원래 루트 lockfile의 prune된 하위 집합만 포함하는 새로 prune 된 &lt;b&gt;lockfile&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;종속성 설치(Installing dependencies)&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1715839274330&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 프로젝트의 pnpm-lock.yaml 및 package.json 복사
COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
COPY --from=pruner /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY --from=pruner /app/out/json/ .

# 종속성 먼저 설치
RUN --mount=type=cache,id=pnpm,target=~/.pnpm-store pnpm install --frozen-lockfile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 prune 된 lockfile 및 prune된 &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;workspace의 &lt;b&gt;package.json&lt;/b&gt; 파일을 복사하고 &lt;b&gt;devDependencies&lt;/b&gt; 및 &lt;b&gt;dependencies&lt;/b&gt;를 모두 설치합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;&lt;b&gt;RUN&lt;/b&gt; 명령은 &lt;b&gt;BuildKit&lt;/b&gt;의 특수한 캐시 마운트 기능을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;빌드(&lt;/span&gt;Building&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1715839464015&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 프로젝트 소스 코드 복사
COPY --from=pruner /app/out/full/ .

RUN turbo build --filter=${PROJECT}
RUN --mount=type=cache,id=pnpm,target=~/.pnpm-store pnpm prune --prod --no-optional
RUN rm -rf ./**/*/src&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 &lt;b&gt;pruner&lt;/b&gt;이미지에서 prune된 소스 코드를 &lt;b&gt;COPY&lt;/b&gt; 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 &lt;b&gt;RUN&lt;/b&gt; 명령에서는 turbo &lt;b&gt;build&lt;/b&gt;를 사용하여 대상 프로젝트를 지정하고 먼저 모든 종속성을 빌드한 다음, 대상 프로젝트를 자체를 빌드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 &lt;b&gt;RUN&lt;/b&gt; 명령은 &lt;b&gt;pnpm prune --prod&lt;/b&gt;를 사용하여 더 이상 필요하지 않는 &lt;b&gt;devDependencies&lt;/b&gt;를 &lt;b&gt;node_modules&lt;/b&gt;에서 제거합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 &lt;b&gt;RUN&lt;/b&gt; 명령은 모든 &lt;b&gt;src&lt;/b&gt; 폴더를 제거하여 소스 파일이 최종 이미지로 복사되지 않도록 합니다.&lt;/p&gt;
&lt;h4 id=&quot;f853&quot; style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Final production image&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1715839754791&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Final image
FROM alpine AS runner
ARG PROJECT

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nodejs
USER nodejs

WORKDIR /app
COPY --from=builder --chown=nodejs:nodejs /app .
WORKDIR /app/apps/${PROJECT}

ARG PORT=8080
ENV PORT=${PORT}
ENV NODE_ENV=production
EXPOSE ${PORT}

CMD node dist/main&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 이미지에는 최소한의 종속성이 설치된 node-alipne 베이스 이미지가 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 컨테이너를 root로 실행하는 것은 좋지 않으므로 비루트 사용자를 만듭니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;COPY --from=builder --chown=nodejs:nodejs /app .&lt;br /&gt;WORKDIR /app/apps/${PROJECT}&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분은 이전 스테이지에서 번들화된 앱 및 해당 종속성을 복사하고 현재 작업 디렉토리를 &lt;b&gt;PROJECT&lt;/b&gt;로 지정된 대상 마이크로서비스의 폴더로 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 역방향 프로시 도구는 포트를 노출하는 정보에 의존하기 때문에 포트를 &lt;b&gt;EXPOSE&lt;/b&gt;하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 방법은 프로적션 이미지에&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt; NODE_ENV=production&lt;/b&gt; 을 설정하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다음 명령을 사용하여 최종 이미지를 빌드할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1715840010767&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build -t api:latest --build-arg PROJECT=api .&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;api&lt;/b&gt;는 당신의 마이크로서비스의 이름입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론(Conclusion)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multi-stage Docker builds는 &lt;b&gt;Turborepo&lt;/b&gt;와 &lt;b&gt;PNPM&lt;/b&gt;과 결합하여 모노레포 내의 마이크로서비스에 강력한 솔루션을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문서에서 제공된 기술을 채택하면 다음과 같은 중요한 개선사항을 확인할 수 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저장 공간 및 네트워크 비용을 줄이는 더 작은 Docker 이미지&lt;/li&gt;
&lt;li&gt;최적화된 레이어 및 캐싱으로 인한 더 빠른 빌드 시간&lt;/li&gt;
&lt;li&gt;모노레포 설정에서의 자원 효율적인 사용&lt;/li&gt;
&lt;li&gt;컨테이너의 개선된 보안 및 전반적인 성능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;추가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적으로 위의 번역글에 설정은 아주 훌륭합니다. 프로젝트에 적용하면서 크게 변경할 점이 많지는 않았지만 사용 환경에 따라 업데이트해본 몇가지 사항들이 추가해 보았습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. &lt;a href=&quot;https://nextjs.org/docs/advanced-features/output-file-tracing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Next.js의 standalone&lt;/a&gt; 사용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 기능을 사용하려면 먼저 &lt;b&gt;next.config.js&lt;/b&gt;에 다음과 같이 옵션을 적용시킵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715842593849&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// next.config.js
module.exports = {
  output: 'standalone'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 번역글에서는 standalone 옵션을 사용하지 않아서 간단하게 /app 폴더를 전부 복사한것을 아래과 같이 변경하여 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715842896606&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;COPY --from=builder /app/apps/${PROJECT}/next.config.js .
COPY --from=builder /app/apps/${PROJECT}/package.json .

# Automatically leverage output traces to reduce image size
COPY --from=builder --chown=nextjs:nodejs /app/apps/${PROJECT}/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/${PROJECT}/.next/static ./apps/${PROJECT}/.next/static
COPY --from=builder --chown=nextjs:nodejs /app/apps/${PROJECT}/public ./apps/${PROJECT}/public&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;standalone에 포함되지 않는 next.config와 package.json은 따로 복사하여 주고 standalone 과 static. public 폴더를 복사하여 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 실행 명령어를 next.js 에 맞게 변경합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715843039309&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CMD [&quot;node&quot;, &quot;server.js&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. .env 파일을 사용하여 여러가지 개발 환경 구성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pruner &lt;/b&gt;이미지에서 전체 이미지를 COPY하고 turbo prune을 하기 전에 아래 내용을 추가해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715843224381&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Prune projects
FROM base AS pruner
ARG PROJECT
ARG ENV_FILE

WORKDIR /app
COPY . .
# ENV 환경에 맞게 세팅
RUN find . -name &quot;.env.*&quot; -exec rm {} \;
COPY apps/${PROJECT}/${ENV_FILE} apps/${PROJECT}
RUN mv apps/${PROJECT}/${ENV_FILE} apps/${PROJECT}/.env.production

RUN turbo prune --scope=${PROJECT} --docker&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 설정은 프로젝트를 build했을때 production 환경으로만 빌드되는 것을 이용하여 build전에 사용자가 원하는 환경으로 build할수 있게 다른 .env 설정을 덮어 씌워 주는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 모든 추가 설정들을 적용한 &lt;b&gt;최종 Dockerfile&lt;/b&gt;입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715843700994&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ARG NODE_VERSION=20.11.0

# Alpine image
FROM node:${NODE_VERSION}-alpine AS alpine
RUN apk update
RUN apk add --no-cache libc6-compat

# Alpine Base에서 pnpm과 turbo 설정
FROM alpine as base
RUN npm install pnpm turbo --global
RUN pnpm config set store-dir ~/.pnpm-store

# Prune projects
FROM base AS pruner
ARG PROJECT

WORKDIR /app
COPY . .
# ENV 환경에 맞게 세팅
RUN find . -name &quot;.env.*&quot; -exec rm {} \;
COPY apps/${PROJECT}/${ENV_FILE} apps/${PROJECT}
RUN mv apps/${PROJECT}/${ENV_FILE} apps/${PROJECT}/.env.production

RUN turbo prune --scope=${PROJECT} --docker

# 프로젝트 빌드
FROM base AS builder
ARG PROJECT

WORKDIR /app

# 프로젝트의 pnpm-lock.yaml 및 package.json 복사
COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
COPY --from=pruner /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY --from=pruner /app/out/json/ .

# 종속성 먼저 설치
RUN --mount=type=cache,id=pnpm,target=~/.pnpm-store pnpm install --frozen-lockfile

# 프로젝트 소스 코드 복사
COPY --from=pruner /app/out/full/ .

RUN turbo build --filter=${PROJECT}
RUN --mount=type=cache,id=pnpm,target=~/.pnpm-store pnpm prune --prod --no-optional
RUN rm -rf ./**/*/src

# Final image
FROM alpine AS runner
ARG PROJECT

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nodejs
USER nodejs

WORKDIR /app

COPY --from=builder /app/apps/${PROJECT}/next.config.mjs .
COPY --from=builder /app/apps/${PROJECT}/package.json .

# Automatically leverage output traces to reduce image size
COPY --from=builder --chown=nextjs:nodejs /app/apps/${PROJECT}/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/${PROJECT}/.next/static ./apps/${PROJECT}/.next/static
COPY --from=builder --chown=nextjs:nodejs /app/apps/${PROJECT}/public ./apps/${PROJECT}/public

WORKDIR /app/apps/${PROJECT}

ARG PORT=3000
ENV PORT=${PORT}
ARG NODE_ENV
ENV NODE_ENV=${NODE_ENV}
EXPOSE ${PORT}

CMD [&quot;node&quot;, &quot;server.js&quot;]&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Programming</category>
      <category>doccker</category>
      <category>monorepo</category>
      <category>nodeJS</category>
      <category>pnpm</category>
      <category>turborepo</category>
      <category>도커</category>
      <category>모노레포</category>
      <category>터보레포</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/40</guid>
      <comments>https://mishka.tistory.com/40#entry40comment</comments>
      <pubDate>Thu, 16 May 2024 16:28:34 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드에서 타임존 다루기 (Day.js)</title>
      <link>https://mishka.tistory.com/39</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 프로젝트에서 시간 관련해서 다룰일이 있어서 날짜와 시간 다루는 방법들을 자세하게 알아보게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;타임존(Timezone) ?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타임존은 동일한 로컬 시간을 따르는 지역을 의미합니다. 주로 해당 국가에 의해 법적으로 지정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;GMT? UTC ? 오프셋?&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;GMT&lt;/b&gt;는 &lt;b&gt;G&lt;/b&gt;reenwuch&lt;b&gt; M&lt;/b&gt;ean&lt;b&gt; T&lt;/b&gt;ime의 약자로 그리니치 평균시를 의미하는데 경도 0도에 위치한 영구 그리니치 천문대를 기준으로 하는 태양 시간을 의미한다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;UTC&lt;/b&gt;는 &lt;b&gt;C&lt;/b&gt;oordinated &lt;b&gt;U&lt;/b&gt;niversal &lt;b&gt;T&lt;/b&gt;ime/&lt;b&gt;U&lt;/b&gt;niversal &lt;b&gt;T&lt;/b&gt;ime &lt;b&gt;C&lt;/b&gt;oordinated 의 약자로 지구 자전 주기의 흐름이 늦어지고 있는 문제를 해결하기 위해 세슘 원자의 진동수에 기반한 국제 원자시 기준시간을 의미한다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;오프셋&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협정세계시를 기준으로 조정된시간의 차이를 &lt;b&gt;오프셋&lt;/b&gt;이라고 하는데 UTC+09:00 의 의미는 UTC기준시간보다 9시간이 빠르다는 것을 의미합니다. 이 오프셋을 기준시로 하여 국가별로 자신들이 사용하는 타임존에 고유한 이름을 부여합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 대한민국의 타임존은 &lt;b&gt;KST&lt;/b&gt;(&lt;b&gt;K&lt;/b&gt;orea &lt;b&gt;S&lt;/b&gt;tandard &lt;b&gt;T&lt;/b&gt;ime)라고 부릅니다. 오프셋과 타임존의 이름은 1:1로 매칭되는 것이 아닙니다. +09:00 오프셋의 경우 한국 뿐 아니라 일보, 인도네시아 같은 지역에서도 사용하고 있으므로 1:N의 관계입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;타임존 다루기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발에서 타임존을 다룰때 고려해야하는 상황은 크게 2가지 입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;파싱(Parsing)&lt;/b&gt;: &lt;b&gt;클라이언트의 날짜 데이터&lt;/b&gt;(사용자의 날짜 입력 값)를 &lt;b&gt;타임존 데이터 형태&lt;/b&gt;로 &lt;b&gt;변환&lt;/b&gt;하는 상황&lt;br /&gt;&lt;b&gt;포매팅(Formatting)&lt;/b&gt;: &lt;b&gt;서버에서 저장된 날짜 데이터&lt;/b&gt;를 &lt;b&gt;사용자의 타임존&lt;/b&gt;에 맞게 &lt;b&gt;변환&lt;/b&gt;하는 상황&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에 저장되는 데이터는 다양한 클라이언트 환경을 지원하기위해 저장되는 데이터가 타임존에 영향을 받지 않는 절대값이어야 합니다. 서버 환경에 따라 다르지만 데이터는 날짜 및 시간 정보가 동일한 오프셋(UTC)에 맞춰져 있거나 해당 클라이언트 환경의 타임존 정보까지 포함된 값이어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;자바스크립트에서 Date&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 날짜나 시간과 관련된 작업은 Date 객체를 사용합니다. Array나 Function과 같이 ECMAScript스펙에 정의 되어있는 네이티브 객체이며, 주로 C++과 같은 네이티브 코드로 구현되어 있다고 합니다. Java의 영향을 받아서인지 불변데이터(Immutable)가 아니라는 점, Month가 0으로 부터 시작하는 등의 안좋은 특징도 그대로 가지고 있다고 합니다. 자세한 내용은 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;API문서&lt;/a&gt;를 참고하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Date 객체는 내부적으로 유닉스 시간과 같은 절대값으로 시간 데이터를 관리합니다. 그러나 생성자 Date()나 함수 Parse()나 메소드들 getHour(), setHour()은 모두 클라이언트의 로컬타임존(브라우저가 실행되는 운영체제에 설정된 타임존)에 영향을 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 타임존이 서울로 설정된 기기에서 2024년 3월 25일 오전 11시 30분을 입력했고 이 입력값을 년도, 월, 일, 시&amp;nbsp; 분 단위로 각각의 숫자 형태의 2024, 2, 25, 11, 30으로 저장했다고 가정하고 생성자를 이용해서 Date 객체를 생성한다면&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;const time = new Date(2024, 2, 25, 11, 30)&lt;br /&gt;time.toString();&amp;nbsp; // Mon Mar 25 2024 11:30:00 GMT+0900 (한국 표준시)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 반환되는 값은 KST를 기준으로 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 로컬 타임존을 직접 변경하는 것은 어렵습니다. 오프셋을 이용하여 날짜를 계산할 수 있지만, 정확한 타임존 변경을 위해서는 상세한 데이터베이스가 필요합니다.(서머타임등의 계산이 필요) 또는 데이터베이스에 전체 타임존을 저장하고 있다가 Date객체에서 날짜나 시간데이터를 가져올 때마다 데이터베이스에서 해당 날짜와 타임존에 맞는 오프셋을 알아낸 다음, 연산을 통해 결과를 반환하는 과정을 거쳐야합니다. 또한 이를 검증하기 위한 테스트도 정확히 작성해야합니다. 이를 대체하기 위해 잘 만들어진 라이브러리들을 사용하기를 권장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Day.js&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 간단히 &lt;a href=&quot;https://mishka.kr/21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Moment.js&lt;/a&gt;의 관한 포스트를 작성하였었는데 이제는 &lt;a href=&quot;https://momentjs.com/docs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Deprecated&lt;/a&gt; 되었습니다 ㅠㅜ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그로인에 새로운 라이브러리를 찾던중 Day.js 를 발견하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 사용하던 Moment.js와 거의 사용법이 유사하고 번들크기도 작으며&lt;span style=&quot;color: #212529; text-align: start; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt; immutable 하다는 장점도 가지고 있습니다.&lt;/span&gt;&lt;span style=&quot;color: #ececec; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;설치하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1711294128368&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install dayjs
// or
yarn add dayjs&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;현재 날짜 또는 특정날짜 시간 객체 생성&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1711294219576&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import dayjs from &quot;dayjs&quot;;

dayjs(); // 현재시간
dayjs(&quot;2024-03-25&quot;); // 2024-03-24 T00:00:00+09:00&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;날짜 형태 지정 (Formatting)&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1711294680607&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const date = dayjs(&quot;2023-12-01 10:30:25&quot;)

date.format(); // 2023-12-01T10:30:25+09:00
date.format(&quot;YY-MM-DD&quot;); // 23-12-01
date.format(&quot;DD/MM/YY&quot;); // 01/12/23
date.format(&quot;YYYY.MM.DD HH:mm:ss&quot;); // 2023.12.01 10:30:25&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 다양한&amp;nbsp;&lt;a href=&quot;https://day.js.org/docs/en/display/format&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;포맷 형식&lt;/a&gt;은 공식문서를 참고하세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;날짜 더하기/빼기 (add/subtact)&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1711294987720&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 년도 
dayjs().add(1, 'year');
dayjs().subtract(1, 'year');

// 월 
dayjs().add(1, 'month');
dayjs().subtract(1, 'month');

// 일
dayjs().add(1, 'day');
dayjs().subtract(1, 'day');

// 시간
dayjs().add(1, 'hour');
dayjs().subtract(1, 'hour');

// 분 
dayjs().add(1, 'minute');
dayjs().subtract(1, 'minute');

// 초
dayjs().add(1, 'second');
dayjs().subtract(1, 'second');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;타임스탬프 변환&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dayjs()에 타임스탬프 값을 넣고 format을 하여 날짜로 변화할수 있고, &lt;span style=&quot;background-color: #f8f8f8; color: #333333; text-align: left;&quot;&gt;unix()&lt;/span&gt; 함수를 사용하여 타임스탬프로 변환 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1711295162724&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dayjs(1548381600).format(); // 1970-01-19T07:06:21+09:00
dayjs('2019-01-25').unix(); // 1548381600&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study</category>
      <category>timezone</category>
      <category>UTC</category>
      <category>타임존</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/39</guid>
      <comments>https://mishka.tistory.com/39#entry39comment</comments>
      <pubDate>Mon, 25 Mar 2024 00:50:03 +0900</pubDate>
    </item>
    <item>
      <title>nvm을 사용하여 프로젝트별 node버전 자동설정하기</title>
      <link>https://mishka.tistory.com/38</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;nvm?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nvm이란 &lt;b&gt;N&lt;/b&gt;ode &lt;b&gt;V&lt;/b&gt;ersion &lt;b&gt;M&lt;/b&gt;anager로, Node.js의 버전을 관리하는 도구입니다. 회사에서 프로젝트를 하다보면 프로젝트별로 다른 Node 버전을 사용하고 있는 경우가 발생하는데 이럴때 사용할 버전을 쉽게 전환 할수 있도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/nvm-sh/nvm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NVM문서&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704349524305&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;homebrew 사용&lt;/p&gt;
&lt;pre id=&quot;code_1704349583817&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install nvm&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 안정화된 노드 최신 버전도 설치해볼께요&lt;/p&gt;
&lt;pre id=&quot;code_1704349644671&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nvm install stable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사용방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 버전의 node 설치&lt;/p&gt;
&lt;pre id=&quot;code_1704349752405&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nvm install [node version]

# 예시
nvm install v18&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내컴퓨터에 설치된 node버전 확인&lt;/p&gt;
&lt;pre id=&quot;code_1704349800018&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nvm ls
# or
nvm list&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치한 노드 버전 중 사용&lt;/p&gt;
&lt;pre id=&quot;code_1704349844908&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nvm use [node version]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;자동설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 기본 설정만을 해서 사용할경우 프로젝트별로 다른 node버전을 사용 할 경우가 생기는데 nvm은 &lt;b&gt;shell별&lt;/b&gt;로 다른 node버전을 가져가도록 설정이 되어있어서 터미널을 새로 열때 &lt;b&gt;default&lt;/b&gt;로 되어 있는 node 버전이 선택된 상태로 실행이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 매번 nvm use 명령어를 사용해서 node버전을 변경 해줘야 합니다. 이러한 과정들을 자동설정을 통해서 줄여보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 프로젝트 루트 위치에 &lt;b&gt;.nvmrc&lt;/b&gt; 파일을 생성해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당파일에 사용할 node 버전을 적어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704350288770&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; v18.19.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 설정을 하게 되면 &lt;b&gt;nvm use&lt;/b&gt; 명령어만으로 node version없이 해당 버전이 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 해당 node 버전을 일일히 기억하지 않아도 되서 편하지만 번거로운 것은 사실입니다. nvm use 명령어를 매번 입력해 주어야 하기 때문입니다. 많은 분들도 이러한 것에 불편함을 느끼셨는지 shell에 설정을 추가하여 사용할수 있도록 하는 방법이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각자 사용하는 shell에 맞춰서 아래 가이드대로 실행하시면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;bash&lt;/b&gt;를 사용하는 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;vi ~/.bachrc&lt;/b&gt; 를 실행하여 설정파일을 열고 아래의 내용을 입력후에 저장해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704350940585&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cdnvm() {
    command cd &quot;$@&quot; || return $?
    nvm_path=&quot;$(nvm_find_up .nvmrc | command tr -d '\n')&quot;

    # If there are no .nvmrc file, use the default nvm version
    if [[ ! $nvm_path = *[^[:space:]]* ]]; then

        declare default_version
        default_version=&quot;$(nvm version default)&quot;

        # If there is no default version, set it to `node`
        # This will use the latest version on your machine
        if [ $default_version = 'N/A' ]; then
            nvm alias default node
            default_version=$(nvm version default)
        fi

        # If the current version is not the default version, set it to use the default version
        if [ &quot;$(nvm current)&quot; != &quot;${default_version}&quot; ]; then
            nvm use default
        fi
    elif [[ -s &quot;${nvm_path}/.nvmrc&quot; &amp;amp;&amp;amp; -r &quot;${nvm_path}/.nvmrc&quot; ]]; then
        declare nvm_version
        nvm_version=$(&amp;lt;&quot;${nvm_path}&quot;/.nvmrc)

        declare locally_resolved_nvm_version
        # `nvm ls` will check all locally-available versions
        # If there are multiple matching versions, take the latest one
        # Remove the `-&amp;gt;` and `*` characters and spaces
        # `locally_resolved_nvm_version` will be `N/A` if no local versions are found
        locally_resolved_nvm_version=$(nvm ls --no-colors &quot;${nvm_version}&quot; | command tail -1 | command tr -d '\-&amp;gt;*' | command tr -d '[:space:]')

        # If it is not already installed, install it
        # `nvm install` will implicitly use the newly-installed version
        if [ &quot;${locally_resolved_nvm_version}&quot; = 'N/A' ]; then
            nvm install &quot;${nvm_version}&quot;;
        elif [ &quot;$(nvm current)&quot; != &quot;${locally_resolved_nvm_version}&quot; ]; then
            nvm use &quot;${nvm_version}&quot;;
        fi
    fi
}

alias cd='cdnvm'
cdnvm &quot;$PWD&quot; || exit&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;zsh&lt;/b&gt;를 사용하는 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vi ~/.zshrc 를 실행하여 설정파일을 열고 아래의 내용을 입력후에 저장해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1704351024054&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# place this after nvm initialization!
autoload -U add-zsh-hook

load-nvmrc() {
  local nvmrc_path
  nvmrc_path=&quot;$(nvm_find_nvmrc)&quot;

  if [ -n &quot;$nvmrc_path&quot; ]; then
    local nvmrc_node_version
    nvmrc_node_version=$(nvm version &quot;$(cat &quot;${nvmrc_path}&quot;)&quot;)

    if [ &quot;$nvmrc_node_version&quot; = &quot;N/A&quot; ]; then
      nvm install
    elif [ &quot;$nvmrc_node_version&quot; != &quot;$(nvm version)&quot; ]; then
      nvm use
    fi
  elif [ -n &quot;$(PWD=$OLDPWD nvm_find_nvmrc)&quot; ] &amp;amp;&amp;amp; [ &quot;$(nvm version)&quot; != &quot;$(nvm version default)&quot; ]; then
    echo &quot;Reverting to nvm default version&quot;
    nvm use default
  fi
}

add-zsh-hook chpwd load-nvmrc
load-nvmrc&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 설정을 해주면 프로젝트로 이동했을때 nvm use 명령어를 실행하지 않아도 자동으로 &lt;b&gt;버전을 변경&lt;/b&gt;하고 해당 버전이 설치되어 있지 않다면 &lt;b&gt;설치&lt;/b&gt;까지 진행해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동설정을 통해서 쾌적한 개발환경 구성을 해봅시다.&lt;/p&gt;</description>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/38</guid>
      <comments>https://mishka.tistory.com/38#entry38comment</comments>
      <pubDate>Thu, 4 Jan 2024 16:03:00 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 개발자도 알면 좋은 Docker</title>
      <link>https://mishka.tistory.com/37</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;도커 (Docker)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커는 &lt;b&gt;컨테이너 기반&lt;/b&gt;의 &lt;b&gt;오픈소스&lt;/b&gt; &lt;b&gt;가상화 플랫폼&lt;/b&gt;입니다. 도커는 2013년 3월 산타클라라에서 열린 Pycon Conference에서 Solomon Hykes가 발표한 &lt;a href=&quot;https://www.youtube.com/watch?v=wW9CAH9nSLs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;The future of Linux Containers&lt;/a&gt;라는 세션에서 처음 세상에 알려지게 되었고, 고(go)언어로 개발되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 기반이라고 했는데 무엇일까요?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;컨테이너 (Container)&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드 (1).jpeg&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crDRdF/btsoz9fbIQa/vaWAuEcLjD3MK3qPyeiEZ0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crDRdF/btsoz9fbIQa/vaWAuEcLjD3MK3qPyeiEZ0/img.jpg&quot; data-alt=&quot;컨테이너&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crDRdF/btsoz9fbIQa/vaWAuEcLjD3MK3qPyeiEZ0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrDRdF%2Fbtsoz9fbIQa%2FvaWAuEcLjD3MK3qPyeiEZ0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;403&quot; height=&quot;226&quot; data-filename=&quot;다운로드 (1).jpeg&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;168&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;컨테이너&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘날 컨테이너라고 하면 주로 선박 운송용 컨테이너를 지칭합니다. 선박 운송시 모든 선적물은 거대한 상자모양의 &lt;b&gt;컨테이너를 통해 패키징&lt;/b&gt; 된 후 화물선에 선적됩니다. 컨테이너는 국제적으로 표준화, 규격화 된 크기를 가지고 있습니다. 그래서 컨테이너와 관련된 보관, 운송과 관련된 장비, 제도, 프로세스 등은 모두 국제 표준에 맞게 설계되어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 표준화를 통해 &lt;b&gt;해상 운송업자는 컨테이너 안에 어떤 물건이 담겨저 있는지 전혀 알 필요가 없고&lt;/b&gt;, 그저 컨테이너 단위로 선적하여 운송하고 하역하고 보관하면 됩니다. 만약 표준화가 되어 있지 않다면 제각기 다른 물건의 크기, 모양 등에 맞춰 서로 다른 운송장비, 보관방법, 프로세스 등이 필요하게 될것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;docker.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhEgii/btsoyVBlS7n/hHlSHSotAefHyniFJlHb9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhEgii/btsoyVBlS7n/hHlSHSotAefHyniFJlHb9K/img.png&quot; data-alt=&quot;도커 컨테이너&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhEgii/btsoyVBlS7n/hHlSHSotAefHyniFJlHb9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhEgii%2FbtsoyVBlS7n%2FhHlSHSotAefHyniFJlHb9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;423&quot; height=&quot;329&quot; data-filename=&quot;docker.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;389&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;도커 컨테이너&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커의 컨테이너도 이와 같은 컨셉을 가지고 있습니다. 도커는 서비스를 운용하는데 필요한 실행환경, 라이브러리, 소프트웨어, 시스템 도구, 코드 등을 &lt;b&gt;컨테이너라는 표준화된 단위로 추상화&lt;/b&gt;합니다. 이렇게 만들어진 컨테이너는 컴퓨팅 환경에 상관없이 서비스가 실행될 수 있도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 컨테이너의 특성처럼 서비스 관리자는 도커라이징(Dockerizing)된 컨테이너가 어떤 런타임을 필요로 하는지, 어떤 라이브러리와 코드를 필요로 하는지 전혀 알 필요가 없고, 그저 컨테이너를 어딘가에서 가져와서 서비스를 운영한 컴퓨팅 환경에서 실행하기만 하면 됩니다. 실행된 서비스는 컴퓨팅 환경과 &lt;b&gt;독립된 가상의 환경&lt;/b&gt;에서 실행되며, &lt;b&gt;일관된 결과를 보장&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;도커 이미지(Docker Image)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 이미지는 컨테이너 실행에 필요한 파일과 설정값등을 포함하고 있는 것으로 상태값을 가지지 않고 변하지 않습니다(Immutable).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너를 생성하기 위해 필요한 &lt;b&gt;설계도&lt;/b&gt;라고 할 수 있습니다. 컨테이너는 이미지를 실행한 상태라고 볼 수 있고, 추가되거나 변하는 값은 컨테이너에 저장됩니다. 같은 이미지에서 여러개의 컨테이너를 생성할 수 있고 컨테이너의 상태가 바뀌거나 컨테이너가 삭제되더라고 이미지는 변하지 않고 그대로 남아 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 설명하면 도커가 찍어놓은(빌드한) 사진(Image)을 인화(실행)하면 컨테이너가 나오는 것입니다. 사진은 여러번 인화 할 수 있고, 똑같은 사진이 인화 되는 것처럼 다른 서버에서 도커가 이미지를 사용해 컨테이너를 만들면 똑같은 환경을 구축 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 이미지는 도커 파일(Docker File)로 만들 수 있습니다. 도커 파일은 도커가 어떻게 이미지를 만들지 이해하도록 적은 파일 입니다. 도커 파일에 Image를 어떻게 빌드할지 적어놓으면 도커가 그것을 읽고 이미지를 생성하게 됩니다(Dockerizing).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Docker로 애플리케이션 배포하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Homebrew를 사용해서 Docker를 설치 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--cask 옵션을 사용해서 설치를 해주면 Docker Desktop on Mac을 설치하고 docker-compose, docker-machine도 같이 설치해주어 편하게 사용할수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;a href=&quot;https://velog.io/@jaryeonge/Docker-Mac%EC%97%90-Homebrew%EB%A1%9C-docker-%EC%84%A4%EC%B9%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Docker - Mac에 Homebrew로 docker 설치&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1690175329016&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 설치
brew install --cask docker

// 버전확인
docker --version
docker-compose --version&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Dockerfile&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커파일은 컨테이너 이미지는 만드는데 사용되는 단순한 &lt;b&gt;텍스트 기반 지침 스크립트&lt;/b&gt;입니다. 이미지를 생성하기 위한 컨테이너에 설치해야하는 패키지, 추가해야하는 소스코드, 실행해야 하는 명령어와 셸 스크립트등 을 Dockerfile에 기록해줍니다. 도커는 Dockerfile을 읽어 컨테이너에서 작업을 수행한 뒤 이미지로 만들어냅니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Dockerfile 작성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;root위치에 Dockerfile이라는 이름을 파일을 만들어줍니다. 만약 VsCode를 사용하신다면 Docker extension을 설치하여 자동완성 기능을 사용할 수도 있습니다. Docker 파일은 &lt;b&gt;한 줄이 하나의 명령어&lt;/b&gt;가 됩니다(Layer 형식). 명령어는 소문자로 표기해도 상관없지만 &lt;b&gt;일반적으로 대문자&lt;/b&gt;로 표기합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 예시파일을 통해 명령어들을 살펴보겠습니다. (아래 예시들은 next.js, yarn 환경입니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1690178259185&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:16

WORKDIR /app

COPY . .

RUN yarn install
RUN yarn build

EXPOSE 3000

CMD [&quot;yarn&quot;, &quot;start&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Dockerfile 기본 명령어&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FROM&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1690178362336&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM &amp;lt;image&amp;gt;:&amp;lt;tag&amp;gt;
FROM node:16&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;베이스 이미지를 지정합니다. 반드지 지정해야 하며 어떤 이미지도 베이스 이미지가 될 수 있습니다. tag는 가능하면 lastest보다 구체적인 버전을 지정하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WORKDIR&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1690178935161&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WORKDIR &amp;lt;path&amp;gt;
WORKDIR /app&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RUN, CMD, ADD, COPY등이 이루어질 기본 디렉토리를 설정합니다. 각 명령어의 현재 디렉토리는 한 줄 한 줄 마다 초기화 되기 때문에 RUN cd /parh 를 하더라고 다음 줄에서는 위치가 초기화 됩니다. 같은 디렉토리에서 작업하기위해 &lt;b&gt;WORKDIR&lt;/b&gt;을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;COPY&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1690178985270&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;COPY &amp;lt;src&amp;gt;... &amp;lt;dest&amp;gt;
COPY . .&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일이나 디렉토리를 이미지로 복사합니다. 일반적으로 소스를 복사하는데 사용합니다. target 디렉토리가 없다면 자동으로 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RUN&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1690179063253&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RUN &amp;lt;command&amp;gt;
RUN [&quot;executable&quot;. &quot;param1&quot;, &quot;param2&quot;]
RUN yarn install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어를 그대로 실행합니다. 내부적으로 &lt;b&gt;/bin/sh -c&lt;/b&gt; 위에 명령어를 실행하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;EXPOSE&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1690179222814&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EXPOSE &amp;lt;port&amp;gt; [&amp;lt;port&amp;gt;...]
EXPOSE 3000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 컨테이너가 실행되었을 때 요청을 기다리고 있는(Listen) 포트를 지정합니다. 여러개의 포트를 지정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CMD&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1690179402931&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CMD [&quot;executable&quot;, &quot;param1&quot;, &quot;param2&quot;]
CMD command param1 param2
CMD [&quot;yarn&quot;, &quot;start&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 컨테이너가 실행되었을때 실행되는 명령어를 정의합니다. 빌드할 때는 실행되지 않으며 여러개의 CMD 가 존재할 경우 가장 마지막 CMD만 실행됩니다. 한꺼번에 여러 개의 프로그램을 실행하고 싶은 경우 run.sh 파일을 작성하여 데몬을 실행하거나 별도의 프로그램을 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이미지 생성(&lt;b&gt;Dockerfile 빌드&lt;/b&gt;)&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1690179745692&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build &amp;lt;option&amp;gt; .
docker build -t &amp;lt;태그명&amp;gt; .&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-t : 생성될 이미지의 이름을 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;. : build 명령어 끝에는 Dockerfile이 저장된 경로를 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이미지로 컨테이너 실행&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1690186186688&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run &amp;lt;옵션&amp;gt; &amp;lt;이미지명:태그&amp;gt; &amp;lt;명령어&amp;gt; &amp;lt;인자&amp;gt;
docker run -d --name &amp;lt;컨테이너명&amp;gt; -p &amp;lt;호스트포트&amp;gt;:&amp;lt;컨테이너포트&amp;gt; &amp;lt;이미지명:태그&amp;gt; 
docker run -d --name dockerContainer -p 3000:3000 nextjs-docker&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-d : 컨테이너를 백그라운드에서 실행합니다(Detached Mode).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--name &amp;lt;컨테이너명&amp;gt; : 컨테이너의 이름을 설정 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-p &amp;lt;호스트포트&amp;gt;:&amp;lt;컨테이너포트&amp;gt; : 호스트포트와 컨테이너 내부의 포트를 바인드 합니다(포트포워딩).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(컨테이너는 호스트 환경과 격리된 파일 시스템과 네트워크를 가지기 때문에 호스트에서 컨테이너로 접근 가능하도록 포트포워딩을 시켜줘야 합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;a href=&quot;https://velog.io/@oneook/Docker%EB%A1%9C-React-%EA%B0%9C%EB%B0%9C-%EB%B0%8F-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0?utm_source=oneoneone&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프론트엔드 개발자를 위한 Docker로 React 개발 및 배포하기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Docker Multi stage&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Multi stage란? 컨테이너 이미지를 만들면서 빌드 등에는 필요하지만 최종 컨테이너 이미지에는 필요없는 환경을 제거할 수 있도록 &lt;b&gt;단계를 나누어서 기반 이미지는 만드는 방법&lt;/b&gt;입니다. 하나의 Dockerfile안에 &lt;b&gt;여러개의 FROM 이미지&lt;/b&gt;를 정의하여 최종 생성될 &lt;b&gt;이미지의 크기를 줄일수 있습니다.&lt;/b&gt; (17.05 버전 이상)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multi state 방식이 나오기 전에는 하나의 Dockerfile이 아닌 두 가지의 Dockerfile을 유지 하는 builder-patten이나 여러 명령어를 실행하는데 분리하지 않고 &amp;amp;&amp;amp; \ 를 통해 하나의 Layer에서 처리 하는 방식등을 사용하였다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 예제를 통해 살펴보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1690189063628&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:16-alpine AS builder
WORKDIR /app

COPY . .
RUN yarn install
RUN yarn build

FROM node:16-alpine AS runner
WORKDIR /app

COPY --from=builder /app ./

EXPOSE 3000
CMD [&quot;yarn&quot;, &quot;start&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Dockerfile과 다르게 2개의 FROM을 통해 2개의 이미지가 명시되어 있습니다. 두번째 FROM아래에서 사용된 COPY 명령어는첫 번째 FROM에서 사용된 이미지의 최종 상태에 존재하는 /app 의 파일을 복사해서 사용합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;COPY --from의 역할&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;COPY&lt;/b&gt; instruction에서 &lt;b&gt;--from&lt;/b&gt;옵션을 사용하면 이전 스테이지에서 Build를 통해 생성된 결과만을 복사할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;AS &amp;lt;Alias-Name&amp;gt;의 역할&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 빌드 스테이지의 &lt;b&gt;별칭(alias)&lt;/b&gt;을 정할 수 있습니다. &lt;b&gt;FROM&lt;/b&gt; instruction 레이어에서 사용 가능하고, &lt;b&gt;COPY&lt;/b&gt;의 &lt;b&gt;--from=&amp;lt;name&amp;gt;&lt;/b&gt;옵션으로 참조합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/detail/732/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도커(Docker)란 무엇이고, 왜 사용하나요?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hudi.blog/about-docker/?fbclid=IwAR2BDz4QPdTsslq9uv9Mv48eIbdBCyXRA2B28T-ClGwi-Hrfy9wSjhXNqHk&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이론과 실습을 통해 이해하는 Docker 기초&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;초보를 위한 도커 안내서 - 도커란 무엇인가?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@jaryeonge/Docker-Mac%EC%97%90-Homebrew%EB%A1%9C-docker-%EC%84%A4%EC%B9%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Docker - Mac에 Homebrew로 docker 설치&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://code-masterjung.tistory.com/133&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Docker] 도커 입문하기 4 -도커 이미지만들기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://e-juhee.tistory.com/entry/Dockerfile?category=1095271&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Docker] Dockerfile로 이미지 생성하기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://subicura.com/2017/02/10/docker-guide-for-beginners-create-image-and-deploy.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;초보를 위한 도커 안내서 -이미지 만들고 배포하기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gptjs409.github.io/infra/2019/10/23/image-build.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Docker] Dockerfile로 이미지 빌드하고 컨테이너 띄워보기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nesoy.github.io/articles/2020-11/Docker-multi-stage-build&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Docker Multi Stage란?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://e-juhee.tistory.com/entry/docker-multi-stage-build?category=1095271&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Docker] 멀티 스테이지 빌드로 이미지 크기 줄이기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile&lt;/a&gt;&lt;/p&gt;</description>
      <category>Programming</category>
      <category>container</category>
      <category>docker</category>
      <category>image</category>
      <category>Multi-stage</category>
      <category>도커이미지</category>
      <category>도커파일</category>
      <category>컨테이너</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/37</guid>
      <comments>https://mishka.tistory.com/37#entry37comment</comments>
      <pubDate>Tue, 25 Jul 2023 13:47:43 +0900</pubDate>
    </item>
    <item>
      <title>Zod를 사용한 유효성 검증</title>
      <link>https://mishka.tistory.com/36</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Zod는 TypeScript를 주로 사용하는 스키마 선언 및 유효성 검사 라이브러리 중 하나입니다. 비슷한 걸로는 &lt;a href=&quot;https://www.npmjs.com/package/yup&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Yup&lt;/a&gt;, &lt;a href=&quot;https://joi.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Joi&lt;/a&gt;&amp;nbsp;등의 라이브러리가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zod에서 &quot;Schema(스키마)&quot; 라고 하는 용어는 단순한 문자열부터 복잡하게 중청된 객체까지 모든 데이터 유형을 포괄적으로 나타내기 위해 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;라이브러리 설치&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1687180059407&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ yarn add zod&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zod 패키지에서 z 를 불러와 사용 할 수 있으며, z 하나로 Zod의 모든 기능을 활용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687180146512&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { z } from &quot;zod&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스키마 정의&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zod를 사용하기 위해서는 먼저 스키마를 정의해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이름, 나이, 나이공개여부로 이루어진 사용자 객체를 나타내는 스키마를 만들어보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687180487833&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const User = z.object({
  name: z.string(),
  age: z.number(),
  open: z.boolean()
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;z.object()를 사용해서 User 스키마가 객체의 형태이고 z.string()으로 name의 속성을 문자열, z.number()를 사용해서 age 속성을 숫자, open의 속성은 z.boolean()을 사용해서 불리언 형태라고 나타낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;유효성 검증&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마를 활용해서 다양한 작업을 할 수 있는데 유효성 검증을 먼저 해보겠습니다. 스키마의 parde() 함수를 사용해서 검증하고 싶은 값을 넘겨서 호출 하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687180814233&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;User.parse({
  name: &quot;홍길동&quot;,
  age: 20,
  open: true
}) // 유효성 검증 통과

User.parse({
  email: &quot;홍길동&quot;,
  age: &quot;20&quot;
}) // 유효성 검증 실패&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증이 실패하게되면 parse() 함수는 아래와 같이 error를 발생합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687180924772&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// console

ZodError: [
  {
    code: &quot;invalid_type&quot;,
    expected: &quot;number&quot;,
    received: &quot;string&quot;,
    path: [&quot;age&quot;],
    message: &quot;Expected number, received string&quot;
  },
  {
    code: &quot;invalid_type&quot;,
    expected: &quot;boolean&quot;,
    received: &quot;undefined&quot;,
    path: [&quot;open&quot;],
    message: &quot;Required&quot;
  },
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 검증이 성공했을 경우 parse() 함수가 반환하는 객체에는 검증이 통과한 속성만 포함됩니다. 스키마에 정의 되지 않은 속성이 입력 객체에 포함된 경우 parse() 함수가 반환한 결과 객체에는 해당 속성이 제외되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 parse() 함수의 반환 타입이 정의된 스키마에 의해서 결정되기 때문에 일어나는 현상입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;타입추론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zod는 정의한 스키마를 기준으로 TypeScript 타입을 추론 할 수도 있습니다. 이 기능을 잘 활용하면 별도의 타입을 작성할 필요없이 스키마를 활용해서 타입추론이 가능해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 User를 TypeScript로 작성하려면 아래와 같이 타입을 작성해줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687181335138&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface User {
  name: string
  age: number
  open: boolean
}

function processUser(user: User) {
  User.parse(user) // 유효성 검증
  // 로직
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zod의 infer 기능을 활용하면 스키마에서 타입을 가져올 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687181449631&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type User = z.infer&amp;lt;typeof User&amp;gt;

function process(user: User) {
  User.parse(user) // 유효성검증
  // 로직
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 작성한 타입과 스키마가 항상 일치하도록 관리 하는 것은 여간 어려운 일이 아닙니다. infer를 활용하면 항상 스키마와 타입을 일치하게 관리 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;다양한 스키마정의&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zod 스키마에 포함된 모든 속성을 필수 입력입니다. 이를 선택입력으로 바꾸려면 optional() 검증자를 사용하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687181874635&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const User = z.object({
  name: z.string(),
  age: z.number(),
  open: z.boolean().optional()
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 스키마를 타입추론을 해보면 open속성 뒤에 ? 를 붙이고 타입을 boolean 또는 undifined로 선언한 것과 마찬가지로 표현됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687182048806&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type User = z.infer&amp;lt;typeof User&amp;gt;
// { name:string; age:number; open?: boolean | undefined; }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기본값 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유효성 검증에서 값이 누락되어 있는 속성에 기본값을 주고 싶을때는 default() 검증자를 사용하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687182184443&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const User = z.object({
  name: z.string(),
  age: z.number(),
  open: z.boolean().default(false)
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 작성하면 open 속성이 없으면 false로 설정할수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Referense&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://zod.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://zod.dev/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.daleseo.com/zod/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.daleseo.com/zod/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.daleseo.com/zod-schema/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.daleseo.com/zod-schema/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Programming</category>
      <category>Schema</category>
      <category>Zod</category>
      <category>스키마</category>
      <category>유효성</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/36</guid>
      <comments>https://mishka.tistory.com/36#entry36comment</comments>
      <pubDate>Mon, 19 Jun 2023 22:57:09 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드에서 아키텍처 바라보기</title>
      <link>https://mishka.tistory.com/35</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드에서 아키텍처를 어떻게 바라보고 또한 어떻게 발전해 왔는가를 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러글들을 참고하여 작성하였고 계속해서 발전시켜 나가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 프론트엔드에서 바라보는 아키텍처를 논하기 전에 우선적으로 아키텍처 자체에 대해 한번 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;영어 단어로 아키텍처는 &lt;br /&gt;&lt;b&gt;&quot;건축학&quot;&lt;/b&gt; &lt;br /&gt;이라는 뜻입니다.&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;br /&gt;Wiki에서는 시스템 아키텍처를&lt;/span&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;시스템 목적을 달성하기 위해 &lt;br /&gt;시스템의 상호작용들의 &lt;br /&gt;시스템디자인에 대한 제약 및 설계이다.&quot;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;라고 정의하고 있습니다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프론트엔드 아키텍처&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드에서도 아키텍처를 신경써야 하는 이유는 &lt;b&gt;프론트엔드 프로젝트는 충분히 복잡&lt;/b&gt;하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡하다는 것은 개발자가 프론트엔드 프로그램을 봤을 때 &lt;b&gt;인지적인 한계&lt;/b&gt;에 부딪히게 된다는 사실을 의미하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사실은 개발을 진행할때 뿐만 아니라 유지보수시에 들어가는 &lt;b&gt;비용이 증가&lt;/b&gt;하는 것을 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 문제를 해결하기 위해서는 잘 설계된 프로그램을 만들어야 하는데 잘 설계 되었다는 건 &lt;b&gt;'한 번에 한 부분을 제대로 집중할 수 있게 프로그램을 구성하는 것'&lt;/b&gt; 그리고 &lt;b&gt;'간단하고 이해하기 쉽게 구성하는 것'&lt;/b&gt;을 의미 합니다. 즉 &lt;b&gt;관심사를 잘 분리하고 코드를 잘 이해할 수 있게 만들어야 합니다.&lt;/b&gt; 이렇게 프로그램을 설계하는데 '아키텍처'라는 방법을 사용합니다. 그렇기 때문에 아키텍처는 프로그램을 잘 설계해서 만들고 유지하는데 투입하는 비용을 최소화 하는 목표를 갖습니다.&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;좋은 아키텍처??&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 클린 아키텍처에서는 아래와 같이 좋은 아이텍처를 정의하고 있습니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;좋은 아키텍처는 세부사항을 정책으로부터 신중하게 가려내고, 둘이 결합되지 않도록 엄격하게 분리한다.&lt;br /&gt;좋은 아키텍처는 의존성의 방향이 컴포넌트 수준을 기반으로 연결되도록 만들어야 한다.&lt;br /&gt;좋은 아키텍트는 결정되지 않은 사항의 수를 최대화한다(향후 시스템에 변경이 필요할 때 어떤 방향으로든 쉽게 변경할 수 있도록 한다.)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 인식들을 접하는 것이 중요하다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프론트엔드 아키텍처의 히스토리&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MVC&amp;nbsp; 아키텍처&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Model&lt;/b&gt; + &lt;b&gt;View&lt;/b&gt; + &lt;b&gt;Controller &lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Model&lt;/b&gt;: 화면에 보여줄 데이터를 담당하는 영역. Model의 정의는 주어진 환경에 따라서 다를수 있습니다. (Javascript의 Object, 서버의 DB, 서버에서 API로 요청한 데이터 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;View&lt;/b&gt;: 실제 사용자에게 보여지는 화면 (HTML + CSS 로 만들어지는 결과물)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Controller&lt;/b&gt;: Model의 데이터를 받아서 화면에 그리고, 화면으로부터 사용자의 동작을 받아서 Model을 변경, Model과 View사이의 중간 역할을 하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 MVC를 나눈 이유는&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1. 화면을 다루는 문제와 데이터를 다루는 문제의 성격이 달라서 분리&lt;br /&gt;2. Model과 View 간의 의존 관계를 최소화 해서 화면의 수정이 데이터 수정에 영향을 미치지 않고 데이터 수정이 화면의 수정에 영향을 미치지 않으려고&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시대에 따라 MVC의 개념은 조금씩 바껴왔는데 &lt;b&gt;웹서비스 초창기&lt;/b&gt;에는&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;데이터베이스를 Model&lt;br /&gt;HTML, CSS, Javascript까지 포함한 클라이언트 영역을 View&lt;br /&gt;가운데서 라우터를 통해 데이터를 처리하고 새로운 HTML을 만들어서 보여주는 백엔드 영역을 Controller&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;jQuery 시절의 MVC&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;ajax로 부터 받는 데이터를 Model&lt;br /&gt;HTML과 CSS로 만들어지는 화면을 View&lt;br /&gt;Javascript가 중간에서 서버의 데이터를 받아서 화면을 바꾸고 이벤트를 처리해서 서버에 데이터를 전달하는 Controller&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당시의 가장 중요한 패러다임은 관점의 분리로 Model과 View의 종속성을 최대한 분리하는 원칙으로 HTML과 jQuery를 따로 관리하는 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MVVM 아키텍처&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jQuery로 작업을 하다보니 불편한 점을 발견하게 되는데&amp;nbsp; 데이터를 수정하고 이벤트를 연결하고 수정하는 과정이 계속 반복된다는 것입니다. 서버에서 개발을 할 때에는 HTML이 전체적으로 렌더링되다보니 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;{{ }}, &amp;lt;%= %&amp;gt;&lt;/span&gt; 와 같은 치환자의 사용이 가능한 반면 jQuery는 수정해야 할 부분을 일일히 찾아서 수정을 해줘야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 불편함을 해소하기 위해 &lt;b&gt;Angular&lt;/b&gt;가 등장합니다. 앵귤러에서는 &lt;b&gt;템플릿&lt;/b&gt;과 &lt;b&gt;바인딩&lt;/b&gt;이라하는 중요한 개념들이 등장하였고 이후로 웹 개발하는 방식의 패러다임이 바뀌게 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Model이 변하면 View를 수정하고 View에서 이벤트를 받아서 Model을 변경하다는 Controller의 역할은 그대로인데 이를 구현하는 방식이 jQuery와 같은 DOM 조작에서 &lt;b&gt;템플릿&lt;/b&gt;과 &lt;b&gt;바인딩&lt;/b&gt;을 통한 &lt;b&gt;선언적인 방법&lt;/b&gt;으로 변하게 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서 DOM을 조작하는 코드가 사라지고 이 기능들은 프레임워크가 담당하게 됩니다. 이제 개발자는 화면에 그려져야할 데이터만 만들어서 프레임워크에 전달해주면 프레임워크가 알아서 그려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 View를 그리는 Model만 다루게 되었다는 의미로 &lt;b&gt;ViewModel&lt;/b&gt;이라고 부르며 이 방식을 &lt;b&gt;MVVM&lt;/b&gt;이라고 부르게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 등장한 프레임워크인 React, Vue, Angular2, Svelte등 어떤 방식의 템플릿과 바인딩 문법을 쓰느냐 방식만 다를뿐 MVVM 아키텍처는 그대로 유지됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;MVC에 MVVM으로 오면서 달라진 부분&lt;br /&gt;- 컨트롤러의 반복적인 기능이 선언적인 방식으로 개선&lt;br /&gt;- Model과 View의 관점을 분리하려 하지 않고 하나의 템플릿으로 관리하려는 방식을 발전(기존에는 class나 id등으로 간접적으로 HTML에 접근하려고 했다면 이제는 직접적으로 HTML에 접근하는 방법으로 확장)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Component 그리고 Container-Presenter 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVVM은 웹의 DOM API를 알지 못하더라고 비즈니스 로직에만 집중하면 서비스를 만들어 줄 수 있게 해주었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Page 단위가 아니라 Page안에 여러가지 모듈이 있고 Model이나 여러 화면들이 하나의 화면에서 구성 될 수 있도록 발전하게 됩니다. 그래서 MVVM이 화면단위가 아니라 조금 더 작게 재사용 할 수 있는 단위로 만들어서 조립하는 방식으로 발전하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식이 &lt;b&gt;Component 패턴&lt;/b&gt;입니다. 하지만 컴포넌트 패턴에 비즈니스 로직이 들어가게 되면 컴포넌트의 재사용성은 떨어지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;비즈니스 로직을 포함하고 있는 컴포넌트를 container&lt;/b&gt; &lt;b&gt;컴포넌트,&lt;/b&gt; &lt;b&gt;데이터만 뿌려주는 형태의 컴포넌트를 Presenter&lt;/b&gt; &lt;b&gt;컴포넌트&lt;/b&gt;로 &lt;b&gt;분리&lt;/b&gt;하여 최상단 혹은 1depth에 Container를 두고 비즈니스 로직을 관리하는 &lt;b&gt;Container-Presenter 아키텍처&lt;/b&gt;가 만들어집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btOOGo/btr9n6jDVld/rFao2vmQW58PflKH0Jm9m1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btOOGo/btr9n6jDVld/rFao2vmQW58PflKH0Jm9m1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btOOGo/btr9n6jDVld/rFao2vmQW58PflKH0Jm9m1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtOOGo%2Fbtr9n6jDVld%2FrFao2vmQW58PflKH0Jm9m1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;857&quot; height=&quot;396&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 만들게 되었을때 컴포넌트 구조가 복잡해짐에 따라 하위 컴포넌트에 특정 값을 전달하기 위해서 중간레벨에 있는 컴포넌트들이 전부 그 props를 가지고 있어야 하는 &lt;b&gt;Props Drilling&lt;/b&gt; 문제가 발생하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFzCsk/btr9mSGaXZW/WEYd4R7d3WchRQXyhv31H1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFzCsk/btr9mSGaXZW/WEYd4R7d3WchRQXyhv31H1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFzCsk/btr9mSGaXZW/WEYd4R7d3WchRQXyhv31H1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFzCsk%2Fbtr9mSGaXZW%2FWEYd4R7d3WchRQXyhv31H1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;406&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;FLUX 패턴과 Redux&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FLUX 패턴은 MVC 패턴에서 벗어나 &lt;b&gt;단방향 아키텍처&lt;/b&gt;(uni-directional architecture)를 만들자는 아이디어에서 시작합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;691&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHSKCL/btr9n7phJCz/JfgadZPuyDsnwDhG2yWNEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHSKCL/btr9n7phJCz/JfgadZPuyDsnwDhG2yWNEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHSKCL/btr9n7phJCz/JfgadZPuyDsnwDhG2yWNEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHSKCL%2Fbtr9n7phJCz%2FJfgadZPuyDsnwDhG2yWNEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;691&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;691&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC 패턴에서 애플리케이션의 규모가 커져서 위와 같은 구조를 가지게 되었을때 View가 다양한 상호작용을 위해 여러 개의 Model을 동시에 업데이트 하고 Model 역시 여러개의 View에 데이터를 전달하는 상황이 발생합니다. 한 Model이 업데이트 되면 그에 따라 View가 업데이트되고, 업데이트된 View가 또 다른 Model을 업데이트 하는 식의 복잡한 데이터 흐름을 가지게 됩니다. 이렇게 많은 의존성을 가지게 되면 Model의 개수가 많아질수록 각 Model에서 발생한 이벤트가 애플리케이션 전체로 퍼져나갈때 이를 예측하기 힘들어 집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 페이스북에서 이를 해결하기 위해 &lt;b&gt;Flux 패턴&lt;/b&gt;을 제안합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flux는 사용자 입력을 기반으로 &lt;b&gt;Action&lt;/b&gt;을 만들고 Action을 &lt;b&gt;Dispatcher&lt;/b&gt;에 전달하여 &lt;b&gt;Store(Model)&lt;/b&gt;의 데이터를 변경한 뒤 &lt;b&gt;View&lt;/b&gt;에 반영하는 &lt;b&gt;단방향 흐름으로 애플리케이션을 만드는 아키텍처&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XNQQy/btsaKxHgmXQ/7R8L2eBIDbsXG3xjndvbyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XNQQy/btsaKxHgmXQ/7R8L2eBIDbsXG3xjndvbyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XNQQy/btsaKxHgmXQ/7R8L2eBIDbsXG3xjndvbyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXNQQy%2FbtsaKxHgmXQ%2F7R8L2eBIDbsXG3xjndvbyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;281&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Redux&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 Flux 패턴을 이용한 구현체로 &lt;b&gt;Redux&lt;/b&gt;가 탄생합니다. 기존의 Props Drilling Problem의 문제점을 해결하고 Store, Dispatch, Reducer에 대한 개념을 정확하게 다시 정리해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flux 패턴은 View를 각각의 MVC 컴포넌트 관점으로 보는 것이 아니라 하나의 큰 View로 이해하고 View에서는 Dispatch를 통해서 Action을 전달하면 Action은 Reducer를 통해서 Data 가 Store에 보관되고 Store에 들어있는 데이터는 다시 View로 연결되는 방식을 지향합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기존의 컴포넌트 단위의 MVC 개념에서 완전히 비즈니스로직과 view를 분리하면서 이 분리된 개념을 &lt;b&gt;상태관리(State Management)&lt;/b&gt;라고 부르게 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flux 패턴의 한계로는 &lt;b&gt;높은 학습곡선&lt;/b&gt;과 &lt;b&gt;장황한 문법&lt;/b&gt;이 지적되었습니다. 간단한 구조에서는 Props Drilling 문제가 치명적이지 않았고 상태 관리를 위해 Action, Dispatch, Reducer를 만들고 관리하는데 부수적인 코드가 많아지면서 관리가 어려워진다는 문제가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Observer-Observable 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BGLSW/btr93nEnvsC/7MVSQw7q0zy5SyfxUj0dR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BGLSW/btr93nEnvsC/7MVSQw7q0zy5SyfxUj0dR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BGLSW/btr93nEnvsC/7MVSQw7q0zy5SyfxUj0dR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBGLSW%2Fbtr93nEnvsC%2F7MVSQw7q0zy5SyfxUj0dR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1128&quot; height=&quot;792&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props Drilling Problem 문제를 Flux와는 다른 시각으로 해결해보고자 하는 &lt;b&gt;Observer-Observable 패턴&lt;/b&gt;이 등장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flux에서 Dispatch와 Action을 배제하고 값이 바뀌면 바뀐 값을 모두에게 전달한다는 개념입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초창기 Mobx 가 이러한 방식을 기반으로 작성되었다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Angular에서는 &lt;a href=&quot;https://rxjs.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RxJS&lt;/a&gt;를 받아들이고 Flux 패턴까지 결합하여 Observable과 Flux가 혼합된 상태관리를 만들기도 하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MVI 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹프론트에서느 &lt;a href=&quot;https://cycle.js.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cycle.js&lt;/a&gt; 라이브러리에서 먼저 소개된 개념입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgB8yy/btr9QVbYPqW/7SmPm8xGsoBmmUuF9EWtok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgB8yy/btr9QVbYPqW/7SmPm8xGsoBmmUuF9EWtok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgB8yy/btr9QVbYPqW/7SmPm8xGsoBmmUuF9EWtok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgB8yy%2Fbtr9QVbYPqW%2F7SmPm8xGsoBmmUuF9EWtok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;960&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Flux 패턴에서 Dispatch와 Action과 Update의 인터페이스를 전부 Observable를 이용한 Stream의 하나의 방식으로 만들어 비동기 문제를 해결하고 장황한 문법을 하나의 인터페이스로 만든점이 인상적이라고 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Model: 모델은 상태를 나타냅니다. MVI의 모델은 아키텍처의 다른 레이어와의 단방향 데이터 흐름을 보장하기 위해 변경이 불가능해야 합니다.&lt;br /&gt;View: View를 나타내며 하나 이상의 Activity나 Fragment로 구현됩니다.&lt;br /&gt;Intent: 사용자 또는 앱내 발생하는 Action을 나타냅니다. 모든 Action에 대해 View는 intent를 수신합니다.Presenter는 Intent를 관찰하고 Model은 새로운 상태로 변환합니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최근 프론트엔드 아키텍처 방향성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드가 계속해서 발전하면서 여러가지 도전적인 아이디어들이 나오고 있는데 이에 대해 살펴보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Context 와 Hook&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vWO1z/btr94yFTacK/R0fL8D5GVk2LGZYxRa12qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vWO1z/btr94yFTacK/R0fL8D5GVk2LGZYxRa12qK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vWO1z/btr94yFTacK/R0fL8D5GVk2LGZYxRa12qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvWO1z%2Fbtr94yFTacK%2FR0fL8D5GVk2LGZYxRa12qK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;786&quot; height=&quot;340&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Props Drilling Problem 이 문제라면 새로운 개념을 만들기보다 Props만 Drilling되지 않는 방법을 생각해보자 하는 시각입니다. 컴포넌트 트리에서 Context라는 거대한 공통 조상을 만들고 그 Context로 부터 데이터를 제공받는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로는 별도의 Store를 두는 Flux와 비슷한 개념이라 복잡한 문법을 사용하는 Redux를 React가 제공하는 Context API를 사용하겠다는 움직임이 생기고 있다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Svelte에서도 React와 동일한 Context라는 개념을 제공하고 별도의 Props를 선언하지 않고 모든 Props를 자식으로 전달해주는 기능들도 제공하고 있다고 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Atomic 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View와 Store의 분리는 그대로 가져가되 Action, Dispatch, Store 와 같은 복잡한 구조를 해결해보기 위한 시각으로 만들어진 패턴입니다. (Recoil, Svelte Store, Vue Composition, Jotai)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 문법으로 컴포넌트 외부에서 공통의 데이터를 set, get 을 할 수 있게 하면서 동시에 동기화를 할 수 있는 방향성입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 더불어 computed, derived, select와 같은 반응형 기능을 제공하여 관련된 데이터의 동시 업데이트를 제공하고 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;View와 Model은 분리한다.&lt;br /&gt;중간의 과정은 자율에 맡기고 간단하게 Model에 접근하는 법만 제공하자.&lt;br /&gt;동기화, 동시성 처리가 중요&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MVC의 확대 (React-Query)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발은 서버데이터를 CRUD 하고 시각으로 그리는 것에 중점되어 있는데 Flux 나 Atomic은 너무 복잡한 방법이라는 시각에서 React-Qurey에서는 고전적인 ajax 데이터를 Model로 간주 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKeW7u/btsatUKmnmQ/AbIxkwhYflpunn8qorAVGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKeW7u/btsatUKmnmQ/AbIxkwhYflpunn8qorAVGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKeW7u/btsatUKmnmQ/AbIxkwhYflpunn8qorAVGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKeW7u%2FbtsatUKmnmQ%2FAbIxkwhYflpunn8qorAVGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;364&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;React Query&lt;br /&gt;- 서버와의 fetch 영역을 Model로 간주&lt;br /&gt;- View는 React&lt;br /&gt;- Controller는 query와 mutation이라는 2가지의 인터페이스를 통해서 서버의 데이터의 상태를 관리하고 캐싱, 동기화, refetch 등을 관리해주는 역할&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 아키텍처들과 발전에 대해 살펴보았습니다. 여러가지 아케텍처들의 큰 공통점은 View와 Business Logic 분리인것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 좋은 아키텍처는 무엇이고 좋은 아키텍처에 프로젝트를 맞추는 것이 아키텍처를 사용하는 방법이라는 생각을 가지고 있었는데.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 아키텍처들을 보고 발전하는 방향을 보았을때 특정 아키텍처를 정하고 거기에 프로젝트를 맞추는 것이 아니라 프로젝트 상황에 따라 아키텍처는 지속적으로 발전해야 좋은 아키텍처 설계가 아닐까 하는 생각을 가져봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@teo/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%97%90%EC%84%9C-MV-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프론트엔드에서 MV* 아키텍쳐란 무엇인가요?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dkrnfls.tistory.com/370&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; Javascript 프론트엔드 MV* 아키텍처&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tuhbm.github.io/2019/04/24/architecture/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;아키텍처란 무엇인가?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.kimcoder.io/blog/clean-frontend-architecture&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;클린한&amp;nbsp;프론트엔드&amp;nbsp;아키텍처를&amp;nbsp;향한&amp;nbsp;첫&amp;nbsp;걸음&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/@shinbaek89/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-business-logic%EC%9D%98-%EB%B6%84%EB%A6%AC-adc10ae881ab&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프론트엔드 아키텍처: Business Logic의 분리&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@andy0011/Flux-%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%9E%80&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Flux 패턴이란?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jaehochoe.medium.com/%EB%B2%88%EC%97%AD-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C%EB%A5%BC-%EC%9C%84%ED%95%9C-mvi-model-view-intent-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-165bda9dfbe7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;안드로이드를 위한 MVI 아키텍쳐 튜토리얼: 시작하기&lt;/a&gt;&lt;/p&gt;</description>
      <category>Programming</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/35</guid>
      <comments>https://mishka.tistory.com/35#entry35comment</comments>
      <pubDate>Fri, 12 May 2023 17:33:56 +0900</pubDate>
    </item>
    <item>
      <title>React - ref, forwardRef 사용해 값 전달하기</title>
      <link>https://mishka.tistory.com/34</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ref&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 에서 &lt;b&gt;ref&lt;/b&gt; prop은 HTML Element에 직접 접근하기 위해 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 아래 &lt;b&gt;&amp;lt;Form&amp;gt;&lt;/b&gt; 컴포넌트에서는 &lt;b&gt;useRef&lt;/b&gt; 훅으로 생성한 &lt;b&gt;inputRef&lt;/b&gt; 를 &lt;b&gt;&amp;lt;input&amp;gt;&lt;/b&gt; 엘리먼트의 &lt;b&gt;ref&lt;/b&gt; prop으로 넘기고 있습니다. 이렇게 해주면 &lt;b&gt;inputRef&lt;/b&gt; 객체에 접근해서 &lt;b&gt;current&lt;/b&gt;속성에 &lt;b&gt;&amp;lt;input&amp;gt;&lt;/b&gt; 엘리먼트에 레퍼런스가 할당되고 이를 통해 &lt;b&gt;handleFocus&lt;/b&gt; 이벤트 핸들러에서 &lt;b&gt;&amp;lt;input&amp;gt;&lt;/b&gt; 엘리먼트의 &lt;b&gt;focus&lt;/b&gt; 함수를 호출할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675690407975&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Form = () =&amp;gt; {
  const inputRef = useRef(null)

  const handleFocus = () =&amp;gt; {
    inputRef.current.focus()
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;input type=&quot;text&quot; ref={inputRef} /&amp;gt;
      &amp;lt;button onClick={handleFocus}&amp;gt;인풋 포커스&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;자식 컴포넌트에 접근하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 부모컴포넌트에서 자식컴포넌트 내부에 있는 input에 접근 하려면 어떻게 해야 할까요??&lt;/p&gt;
&lt;pre id=&quot;code_1675691597604&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Parent = () =&amp;gt; {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Child/&amp;gt;
    &amp;lt;/&amp;gt;
  )
}

const Child = () =&amp;gt; {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;input/&amp;gt;
    &amp;lt;/&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 부모/자식 구조에서 Parent에서 어떤 이벤트가 발생했을 때 Child의 input에 focus를 줘야한다면 어떻게 해야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 부모컴포넌트인 &lt;b&gt;&amp;lt;Parent&amp;gt;&lt;/b&gt;는 &lt;b&gt;useRef&lt;/b&gt; 훅 함수로 생성한 &lt;b&gt;inputRef&lt;/b&gt; 객체를 자식인 &lt;b&gt;&amp;lt;Child&amp;gt;&lt;/b&gt; 컴포넌트에 &lt;b&gt;ref&lt;/b&gt; prop으로 넘깁니다. 그러면 자식인 &lt;b&gt;Child&lt;/b&gt; 컴포넌트는 이 &lt;b&gt;ref&lt;/b&gt; prop으로 넘어온 &lt;b&gt;inputRef&lt;/b&gt; 객체를 다시 내부에 있는 &lt;b&gt;&amp;lt;input&amp;gt;&lt;/b&gt; 엘리먼트의 &lt;b&gt;ref&lt;/b&gt; prop으로 넘겨줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675692321629&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Child = ({ ref }) =&amp;gt; {
  return &amp;lt;input type=&quot;text&quot; ref={ref} /&amp;gt;
}

const Parent = () =&amp;gt; {
  const inputRef = useRef(null)

  const handleFocus = () =&amp;gt; {
    inputRef.current.focus()
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;Child ref={inputRef} /&amp;gt;
      &amp;lt;button onClick={handleFocus}&amp;gt;인풋 포커스&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언뜻 보기에는 괜찮아보이는 위의 코드는 실제 브라우저에서 실행하면 콘솔에서 아래와 같은 에러 메시지를 출력합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675696565469&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Warning: Child: `ref` is not a prop. Trying to access it will result in `undefined` being returned. 
If you need to access the same value within the child component, you should pass it as a different prop.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 해석해보면 &lt;b&gt;ref&lt;/b&gt;는 prop이 아니라서 &lt;b&gt;undefined&lt;/b&gt;가 반환 될것이고, 그래서 다른 prop을 사용 해야 한다고 알려주고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 가이드도 같이 보여주고 있는데요&lt;/p&gt;
&lt;pre id=&quot;code_1675696836108&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Child = ({ inheritRef }) =&amp;gt; {
  return &amp;lt;input type=&quot;text&quot; ref={inheritRef} /&amp;gt;
}

const Parent = () =&amp;gt; {
  const inputRef = useRef(null)

  const handleFocus = () =&amp;gt; {
    inputRef.current.focus()
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;Child inheritRef={inputRef} /&amp;gt;
      &amp;lt;button onClick={handleFocus}&amp;gt;인풋 포커스&amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가이드와 같이 ref 대신 inheritRef와 같이 다른 이름의 prop를 사용하도록 컴포넌트를 수정하여 이 문제를 해결 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, React에서 공식적으로 ref 라는 props를 지원하는데 굳이 다른 이름으로 전달 할 수 밖에 없는지 의문을 가질수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;fowardRef 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서는 위와 같은 상황을 해결하기 위해 forwardRef를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에서는 forwarRef를 아래와 같이 설명하고 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675698442385&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;forwardRef lets your component expose a DOM node to parent component with a ref.
//(의역) forwardRef 는 자식 컴포넌트의 DOM node와 ref를 부모 컴포넌트에 노출 할 수 있도록 해줍니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용방법은 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675698511357&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Parent = () =&amp;gt; {
  const inputRef = useRef&amp;lt;HTMLInputElement&amp;gt;(null)
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Child ref={inputRef}/&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}


const Child = React.forwardRef&amp;lt;HTMLInputElement&amp;gt;((_, inputRef) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input ref={inputRef}/&amp;gt;
    &amp;lt;/div&amp;gt;
  )
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 정적 메서드인 forwardRef의 인자로 기존 컴포넌트를 넣어주기만 하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Child 컴포넌트에서의 매개변수 선언부를 보면 const Child = (_, inpurRef) =&amp;gt; {} 와 같은 형태로 두번째 매개변수를 명시했는데, 해당 매개변수가 바로 부모 컴포넌트에서 ref props로 넘겨준 ref 변수가 됩니다. 전달받은 ref 변수를 특정 element와 연결 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 컴포넌트에서 prop를 받기 위한 타입정의를 했다면 아래와 같이 사용 하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675698776192&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;React.forwardRef&amp;lt;HTMLInputElement, Props&amp;gt;((props, ref) =&amp;gt; {
// ...
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트에서 자식 컴포넌트 내부에 DOM을 직접 조작 하는 것은 일반적인 컴포넌트 관계에서는 지양하는 패턴이라고 합니다. 여러 컴포넌트로 나누어서 제작하는 것은 세부 기능을 숨기는 추상화를 하기 위한 목적이 있기 때문에 다른 컴포넌트에서 자식 컴포넌트의 특성을 너무 쉽게 변경 할수 있다면 추상화가 잘 안되어 있다고 볼수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, input이나 button 처럼 외부에서 직접 DOM을 컨트롤 해야 하는 상황에서는 forwardRef를 사용해서 관리하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@dev_2dong/React%EC%9D%98-forwardRef%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-%ED%95%98%EC%9C%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-element-%EC%B0%B8%EC%A1%B0%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@dev_2dong/React%EC%9D%98-forwardRef%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-%ED%95%98%EC%9C%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-element-%EC%B0%B8%EC%A1%B0%ED%95%98%EA%B8%B0&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.daleseo.com/react-forward-ref/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.daleseo.com/react-forward-ref/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.daleseo.com/react-refs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.daleseo.com/react-refs/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://beta.reactjs.org/reference/react/forwardRef&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://beta.reactjs.org/reference/react/forwardRef&lt;/a&gt;&lt;/p&gt;</description>
      <category>Programming</category>
      <category>forwardRef</category>
      <category>ref</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/34</guid>
      <comments>https://mishka.tistory.com/34#entry34comment</comments>
      <pubDate>Tue, 7 Feb 2023 00:58:48 +0900</pubDate>
    </item>
    <item>
      <title>TypeScript - 유틸리티 타입(Utility types) 사용</title>
      <link>https://mishka.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서는 Type Transformation을 유연하게 도와 주는 여러 &lt;b&gt;유틸리티 타입&lt;/b&gt;을 제공하고 있습니다. (&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/utility-types.html#excludetype-excludedunion&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TypeScript Utility Types&lt;/a&gt;) 그 중에 실무에서 유용한 몇가지 타입을 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서는 조건부 형식으로 타입을 정의 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675086455439&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;T extends U ? X : Y&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 형태를 취하는데 조건식 결과에 따라 X가 될 수도 Y 가 될 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트는 용도에 맞게 조건부 타입을 활용한 새로운 타입들을 미리 정의해두고 이것을 &lt;b&gt;&quot;Predefined conditional types&quot;&lt;/b&gt; 라고 하며 그 중 하나가 &lt;b&gt;Exculude&lt;/b&gt;&amp;nbsp; 입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Exclude&amp;lt;T, U&amp;gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Exclude&lt;/b&gt; 타입은 2개의 제네릭 타입을 받을 수 있으며, 첫번재 제네릭 타입 T 중 두번째 제네릭 타입 U와 겹치는 타입을 제외한 타입을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675086319353&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Exclude&amp;lt;T, U&amp;gt; = T extends U ? never : T;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 T에 오는 타입들 중 U에 오는 것들은 제외하겠다는 의미가 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675086884932&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type OnlyNumber = Exclude&amp;lt;string|number, string&amp;gt;;
// OnlyNumber는 number 타입이 됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말해 두번째 제네릭 타입 U에 대해 첫번째 제네릭 타입 T가 할당 가능한 타입인지 판단하고, 할당 가능한 타입을 제외한 나머지 타입들을 이용하여 타입을 정의 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Exculude는 보통 어떤 유니온 타입에서 특정한 타입들을 제외하려고 할 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 사용도 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675087410910&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Player {
  name: string;
  rank: string;
  contry: string;
  playTime: number;
}

type noNamePlayer = Exclude&amp;lt;keyof Player, &quot;name&quot;&amp;gt;;
// type noNamePlayer = &quot;rank&quot; | &quot;contry&quot; | &quot;playTime&quot; 와 같다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keyof Player 를 통해서 &lt;span style=&quot;background-color: #dddddd; color: #e83e8c;&quot;&gt;&lt;/span&gt;Player 객체의 key 값만을 모으고 exclude를 사용하여 제외할 값을(name) 지정 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Pick &amp;lt;T, K&amp;gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;T 타입으로 부터 K 프로퍼티만 추출 합니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;type Pick&amp;lt;T, K extends keyof T&amp;gt; = {
    [P in K]: T[P];
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pick&lt;/b&gt;은 어떤 정의된 객체 형태의 타입에서 특정한 프로퍼티들만 골라서 새로운 타입으로 만들어 줍니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick&amp;lt;Todo, &quot;title&quot; | &quot;completed&quot;&amp;gt;;
// TodoPreview 타입은 Todo 타입의 프로퍼티들 중에서 title, completed만 골라낸 타입이 됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Omit &amp;lt;T,&amp;nbsp; K&amp;gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Omit&lt;/b&gt; 타입은 두개의 제네릭 타입을 받으며 T에서 모든 프로퍼티를 선택한 다음 K를 제거한 타입을 구성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675088012664&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Omit&amp;lt;T, K extends keyof any&amp;gt; = Pick&amp;lt;T, Exclude&amp;lt;keyof T, K&amp;gt;&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Omit&lt;/b&gt;은 어떤 정의된 객체 형태의 타입에서 특정한 프로퍼티들을 제외시켜 줍니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;interface Match {
  accountNo: number;
  matchRank: number;
  name: string;
  age: number;
}

type PersonInfo = Omit&amp;lt;Match, &quot;accountNo&quot; | &quot;matchRank&quot;&amp;gt;;
// PersonInfo 타입은 Match 타입의 프로퍼티들중 accountNo,matchRank를 제외한 나머지로 이루어지게 됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Partial&amp;lt;T&amp;gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Partial&lt;/b&gt;은 제네릭 타입 T에 대해서 모든 프로퍼티들을 &lt;b&gt;Optional&lt;/b&gt;하게 변경합니다.&lt;/p&gt;
&lt;pre class=&quot;elm&quot;&gt;&lt;code&gt;type Partial&amp;lt;T&amp;gt; = {    
  [P in keyof T]?: T[P];
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭 타입의 프로퍼티들에 대해 기존의 타입은 유지하되, 각각의 프로퍼티들을 optional 타입으로 변경해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1675089073558&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Product {
    id: number;
    seller: number;
    category: number;
    name: string;
    price: string;
}

type SearchProduct = Partial&amp;lt;Product&amp;gt;;

/*
type SearchProduct {
    id?: number;
    seller?: number;
    category?: number;
    name?: string;
    price?: string;
}
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필수 타입과 optional 타입을 구분해서 사용해야 하는 경우 아래와 같이 쓸 수도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;type UserInformation = RequiredUserInformation &amp;amp; Partial&amp;lt;OptionalUserInformation&amp;gt;;

interface RequiredUserInformation {
  id: string;
  uid: string;
  name: string;
}

interface OptionalUserInformation {
  age: number;
  profile: string;
  phone: string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 유틸리티 타입들을 간단하게 정리하면&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Exculde&lt;/b&gt;: 어떤 타입(보통 유니온)에서 &lt;b&gt;특정한 타입들을 제외&lt;/b&gt;시켜 정의하고 싶을 때&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pick&lt;/b&gt;: 정의된 객체 유형의 타입에서 &lt;b&gt;특정 프로퍼티를 선택&lt;/b&gt;한 새로운 타입을 정의 하고 싶을 때&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Omit&lt;/b&gt;: 정의된 객체 유형의 타입에서 &lt;b&gt;특정 프로퍼티를 제외&lt;/b&gt;한 타입을 정의하고 싶을 때&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Partial&lt;/b&gt;: 모든 프로퍼티를 &lt;b&gt;Optional 하게 변경&lt;/b&gt;하고 싶을&amp;nbsp;때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://chanhuiseok.github.io/posts/ts-3/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://chanhuiseok.github.io/posts/ts-3/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jaehoney.tistory.com/105&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jaehoney.tistory.com/105&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ggong/Typescript%EC%9D%98-%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0-%ED%83%80%EC%9E%85-1-Record-Extract-Pick&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@ggong/Typescript%EC%9D%98-%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0-%ED%83%80%EC%9E%85-1-Record-Extract-Pick&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ggong/Typescript%EC%9D%98-%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0-%ED%83%80%EC%9E%85-2-Partial-Required-ReadOnly-Omit-NonNullable-ReturnType&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@ggong/Typescript%EC%9D%98-%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0-%ED%83%80%EC%9E%85-2-Partial-Required-ReadOnly-Omit-NonNullable-ReturnType&lt;/a&gt;&lt;/p&gt;</description>
      <category>Study</category>
      <category>exclude</category>
      <category>omit</category>
      <category>Partial</category>
      <category>pick</category>
      <category>typescript</category>
      <category>Utility type</category>
      <category>유틸리티타입</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/33</guid>
      <comments>https://mishka.tistory.com/33#entry33comment</comments>
      <pubDate>Mon, 30 Jan 2023 23:41:37 +0900</pubDate>
    </item>
    <item>
      <title>Next.js 기초부터 알아보기</title>
      <link>https://mishka.tistory.com/32</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Next.js 는 &lt;b&gt;React 라이브러리의 프레임워크&lt;/b&gt; 입니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;서버사이드 렌더링&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pre-reloading을 통해 미리 데이터가 렌더링된 페이지를 가져올수 있게 해주므로 사용자에게 더 좋은 경험을 주면서, 검색 엔진에 잘 노출 될 수 있도록 해주는 SEO의 장점을 가질수 있습니다. pre-reloading은 SSR 뿐만 아니라 SSG(Static Site Generate - 정적 사이트 생성)도 가능하게 해줍니다. 또한 SSR 과 CSR도 혼합하여 사용 할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;SEO 문제 - 클라이언트 사이드의 경우 자바스크립트가 로드 되지 않은 경우 아무런 정보가 보이지 않습니다. 구글의 검색엔진의 경우 자바스트립트가 로드되지 않은 페이지를 검색엔진으로 스캔함으로 결론적으로 검색에 아무 페이지도 걸리지 않는 문제가 발생합니다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Hot reloading &lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 중 저장 되는 코드는 자동으로 새로고침 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Automatic Routing&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 기반 라우팅 시스템이 제공 됩니다. pages 폴더에 있는 파일은 해당 이름으로 라우팅 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 /user/[id] 같은 dynamic route도 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Client Side Navigation&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js는 &amp;lt;Link /&amp;gt; 컴포넌트를 통해서 페이지간의 빠르고 매끄러운 이동이 가능합니다. HTML의 a 태그와 달리 페이지를 리로딩 하지 않고 페이지간 이동이 가능합니다. 이럴수 있는 것은 link 컴포넌트가 뷰포트에 보였을 때 관련 페이지를 백그라운드에서 미리 가져다 놓기 때문입니다. 이를 통해 사용자가 링크를 클릭했을 때 빠르게 해당 페이지로 이동할 수 있게 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Code Splitting&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dynamic import를 이용하면 손쉽게 Code Splitting이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 스플리팅은 원하는 페이지에서 원하는 자바스크립트와 라이브러리를 렌더링 하는 것입니다. 모든 번들이 하나로 묶이지 않고, 각각 나뉘어 보다 효율적으로 자바스크립트 로딩 시간을 개선할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1673280354855&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// index.js
import React from 'react'
import dynamic from 'next/dynamic'

// index 라우트에서 사용할 루트 컴포넌트(HomePage)를 dynamic import로 불러온다.
const Home = dynamic(() =&amp;gt; import('components/pages/HomePage')) 

class index extends React.Component {
  render() {
    return &amp;lt;Home&amp;gt;&amp;lt;/Home&amp;gt;
  }
}

export default index&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;구성&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;_app.tsx&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1673281025724&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { AppProps } from 'next/app'

import Layout from '@/components/Layout'
import base from '@/styles/base'

export default function App({ Component, pageProps }: AppProps) {
  return (
    &amp;lt;Layout&amp;gt;
      &amp;lt;Component {...pageProps} /&amp;gt;
    &amp;lt;/Layout&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초로 실행되는 것이 _app.tsx 입니다. _app.tsx 에서 렌더링 하는 값은 모든 페이지에 영향을 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_app.tsx는 클라이언트에서 띄우기 바라는 전체 컴포넌트는 공통 레이아웃임으로 최초 실행되며 내부에 컴포넌트들을 실행합니다.(위와 같이 Layout 컴포넌트를 별도로 만들어서 사용 하는 방법도 있습니다.) 내부에 컴포넌트가 있다면 전부 실행하고 HTML의 body로 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Componet, pageProps를 받습니다. 여기서 props로 받은 Component는 요청한 페이지이고, GET&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; /&amp;nbsp;&lt;/span&gt; 요청을 보냈다면 Component에는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;/page/index.js&amp;nbsp;&lt;/span&gt; 파일이 props로 내려오게 됩니다. pageProps는 페이지 getInitialProps를 통해 내려받은 props들을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 _document.tsx 가 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_app.tsx에서 cosole.log 실행시 client, server 둘다 콘솔이 찍힙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;_document.tsx&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;meta태그를 정의하거나, 전체 페이지에 관련한 컴포넌트입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1673281811386&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from &quot;next/document&quot;;

export default class MyDocument extends Document {
  render() {
    return (
      &amp;lt;Html&amp;gt;
        &amp;lt;Head&amp;gt;
          &amp;lt;meta property=&quot;custom&quot; content=&quot;123123&quot; /&amp;gt;
        &amp;lt;/Head&amp;gt;
        &amp;lt;body&amp;gt;
          &amp;lt;Main /&amp;gt;
        &amp;lt;/body&amp;gt;
        &amp;lt;NextScript /&amp;gt;
      &amp;lt;/Html&amp;gt;
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_document를 작성할 때는 Document 클래스를 상속받는 클래스 컴포넌트로 작성해야만 하며, 렌더 함수는 꼭 &amp;lt;Html&amp;gt;, &amp;lt;Head&amp;gt;, &amp;lt;Main&amp;gt;, &amp;lt;NextScript&amp;gt; 요소를 리턴해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_document에서 사용하는 &amp;lt;Head&amp;gt; 태그는 next/head가 아닌 next/document 모듈에서 불러와햐 하고, _document의 &amp;lt;Head&amp;gt;태그에는 모든 문서에서 공통적으로 적용될 내용이 들어가야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_document 는 언제나 서버에서 실행되므로 브라우저 api 또는 이벤트 핸들러가 포함된 코드는 실행되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &amp;lt;Main /&amp;gt; 부분을 제외한 부분은 브라우저에서 실행되지 않으므로 비지니스 로직을 추가해서는 안됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Server Side Lifecycle 훑어보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Next.js 서버가 &lt;b&gt;GET&lt;/b&gt; 요청을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;GET&lt;/b&gt; 요청에 맞는 &lt;b&gt;pages/Componet&lt;/b&gt; 를 찾는다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;b&gt;_app.tsx&lt;/b&gt; 의 &lt;b&gt;getInitialProps&lt;/b&gt; 가 있다면 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. route에 맞는 페이지의 &lt;b&gt;Componet&lt;/b&gt;의 &lt;b&gt;getInitialProps&lt;/b&gt; 가 있다면 실행하고&amp;nbsp;&lt;b&gt;pageProps&lt;/b&gt; 들을 받아온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. &lt;b&gt;_document.tsx&lt;/b&gt; 의 &lt;b&gt;getInitialProps&lt;/b&gt; 가 있다면 실행하고 &lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;pageProps&lt;/b&gt; 들을 받아온다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 모든 props들을 구성하고&lt;b&gt; _app.tsx&lt;/b&gt; &amp;gt;&amp;gt; &lt;b&gt;page Component&lt;/b&gt; 순으로 렌더링한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 모든 Content를 구성하고 &lt;b&gt;_document.tsx&lt;/b&gt; 를 실행하여 HTML 형태로 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getinitialProps 가 궁금하다면 아래글을 참고해보세요.&lt;/p&gt;
&lt;figure id=&quot;og_1673282821989&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Next js 구동방식 과 getInitialProps&quot; data-og-description=&quot;Next js가 React Project의 SSR을 가능하게 한다. 라고는 하는데, 어떤 방식으로 SSR을 가능하게 할까, SSR과 CSR의 구분은 어떻게 되어 있을까.이 궁금증을 해결하기 위해, 먼저 알아야 할 것은 Next js의 구&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@cyranocoding/Next-js-%EA%B5%AC%EB%8F%99%EB%B0%A9%EC%8B%9D-%EA%B3%BC-getInitialProps&quot; data-og-url=&quot;https://velog.io/@cyranocoding/Next-js-구동방식-과-getInitialProps&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ZO0cP/hyRdPma3jv/wzvPEYH4qEYew1dUe5LGm1/img.png?width=389&amp;amp;height=129&amp;amp;face=0_0_389_129,https://scrap.kakaocdn.net/dn/biK4AI/hyReM9efNd/ufYdF8dARcQkRxrZVwFBIk/img.png?width=389&amp;amp;height=129&amp;amp;face=0_0_389_129&quot;&gt;&lt;a href=&quot;https://velog.io/@cyranocoding/Next-js-%EA%B5%AC%EB%8F%99%EB%B0%A9%EC%8B%9D-%EA%B3%BC-getInitialProps&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@cyranocoding/Next-js-%EA%B5%AC%EB%8F%99%EB%B0%A9%EC%8B%9D-%EA%B3%BC-getInitialProps&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ZO0cP/hyRdPma3jv/wzvPEYH4qEYew1dUe5LGm1/img.png?width=389&amp;amp;height=129&amp;amp;face=0_0_389_129,https://scrap.kakaocdn.net/dn/biK4AI/hyReM9efNd/ufYdF8dARcQkRxrZVwFBIk/img.png?width=389&amp;amp;height=129&amp;amp;face=0_0_389_129');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Next js 구동방식 과 getInitialProps&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Next js가 React Project의 SSR을 가능하게 한다. 라고는 하는데, 어떤 방식으로 SSR을 가능하게 할까, SSR과 CSR의 구분은 어떻게 되어 있을까.이 궁금증을 해결하기 위해, 먼저 알아야 할 것은 Next js의 구&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kyounghwan01.github.io/blog/React/next/basic/#next-js%E1%84%80%E1%85%A1-%E1%84%8C%E1%85%A6%E1%84%80%E1%85%A9%E1%86%BC%E1%84%92%E1%85%A1%E1%84%82%E1%85%B3%E1%86%AB-%E1%84%8C%E1%85%AE%E1%84%8B%E1%85%AD-%E1%84%80%E1%85%B5%E1%84%82%E1%85%B3%E1%86%BC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;next.js 기본 개념 알아보기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@syoung125/Next.js-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-1-Next.js-%EB%9E%80-Next.js%EB%A5%BC-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C-Next.js%EC%9D%98-%EC%9E%A5%EC%A0%90%EC%9D%80&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[next.js] 기본개념: Next.js 란? Next.js를 왜 사용할까? Next.js의 장점은?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.rhostem.com/posts/2020-05-10-nextjs-and-dynamic-import&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dynamic import로 줄여본 next.js 앱의 최초 로딩시간&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study</category>
      <category>next.js</category>
      <category>Server Side Lifecycle</category>
      <category>SSR</category>
      <category>_app.tsx</category>
      <category>장점</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/32</guid>
      <comments>https://mishka.tistory.com/32#entry32comment</comments>
      <pubDate>Tue, 10 Jan 2023 01:49:20 +0900</pubDate>
    </item>
    <item>
      <title>스토리북으로 개발하기</title>
      <link>https://mishka.tistory.com/31</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스토리북?&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;스토리북은 독자적인 UI 컴포넌트 개발을 위한 업계표준 컴포넌트 탐색기 입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 개발자들을 위한 문서화(Documentations)에 사용할 수도 있고, 외부 공개용 디자인 시스템(Design System)을 개발하기 위한 기본 플랫폼으로도 사용할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 로직 없이 독립적인 환경에서 컴포넌트를 개발을 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;재사용을 위한 컴포넌트 들을 Story 에서 조합해 테스트 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;컴포넌트들을 문서화 할 수도 있고 디자인 시스템에 적용해 피그마의 컴포넌트들과 동기화할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;초기세팅&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1671455477117&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Storybook 설치
npx storybook init

# Storybook 실행
yarn storybook&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;폴더구조&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 .storybook 폴더와 stories 폴더가 생성되는데, .storybook 폴더 내부의 파일들은 전역적인 설정 관련&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stories 폴더 내부에는 예시가 되는 컴포넌트와 스토리들이 위치합니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;.storybook&lt;span&gt;&amp;nbsp;폴더&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;main.js&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stories를 위한 config 설정들이 위치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;stories: stories 파일이 어디에 있는지 경로설정&lt;/li&gt;
&lt;li&gt;addon: 확장 프로그램 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;preview.js&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 스토리에 글로벌하게 적용되는 포맷세팅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스토리?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토리는 쉽게말해 &lt;b&gt;하나의 컴포넌트가 실행가능한 하나의 케이스를 의미&lt;/b&gt;합니다. 특정한 props를 넘겼을 때, 그 자체가 하나의 스토리가 되고 그렇게 다양한 스토리들을 정의하면 스토리북에서 스토리를 직관적으로 확인 할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;기본 형태&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1671458630771&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default {
  title: 스토리북에 올릴 component폴더 계층 구조,
  component: 스토리를 만들 컴포넌트 이름
}

export const 스토리이름 = () =&amp;gt; 해당스토리에서 테스트할 인자가 담긴 컴포넌트&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;적용예시&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1671498047312&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Button.tsx
import React from 'react';
import styled from '@emotion/styled';
interface ButtonProps {
    label: string;
    size?: 'small' | 'large';
    onClick?: () =&amp;gt; void;
}

export default function Button({label, size, ...props}:ButtonProps) {
    return (
        &amp;lt;Styled.Button size={size}&amp;gt;
            &amp;lt;div className=&quot;button&quot; role=&quot;button&quot; {...props}&amp;gt;
                {label}
            &amp;lt;/div&amp;gt;
        &amp;lt;/Styled.Button&amp;gt;
    )
}

const Styled = {
    Button:styled.div&amp;lt;{size: 'small'|'large'}&amp;gt;`
        display: flex; 
        justify-content: center;
        align-items: center;
        width: ${({ size }) =&amp;gt; (size === 'small' ? '48px' : '76px')};
        border-bottom: 1px solid #FFFFFF;
        .button{
            padding-top: 11px;
            padding-bottom: 11px;
            cursor: pointer;
            color: #FFFFFF;
            text-align: center;
            font-family: JejuMyeongjo;
            font-size: 14px;
        }
    `,
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1671498127276&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Button.stories.tsx
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';

import Button  from './Button';

// 어떤 컴포넌트의 story인지, 어떤 설정으로 렌더링할지 정의 
export default {
  title: 'stories/Button',
  component: Button,
} as ComponentMeta&amp;lt;typeof Button&amp;gt;;

// 기본 포맷을 정해두고 bind로 제어 
const Template: ComponentStory&amp;lt;typeof Button&amp;gt; = (args) =&amp;gt; &amp;lt;Button {...args} /&amp;gt;;

// 각각이 새로운 스토리들
// export const Small = () =&amp;gt; &amp;lt;Button size=&quot;small&quot; label=&quot;button&quot; /&amp;gt;; 얘와 같음
export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 스토리 작성하는 법에 대해 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@devstone/%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%B6%81-%EC%A0%9C%EB%8C%80%EB%A1%9C-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@devstone/%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%B6%81-%EC%A0%9C%EB%8C%80%EB%A1%9C-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://storybook.js.org/tutorials/design-systems-for-developers/react/ko/build/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://storybook.js.org/tutorials/design-systems-for-developers/react/ko/build/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.daleseo.com/storybook/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.daleseo.com/storybook/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Study</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/31</guid>
      <comments>https://mishka.tistory.com/31#entry31comment</comments>
      <pubDate>Tue, 20 Dec 2022 10:03:45 +0900</pubDate>
    </item>
    <item>
      <title>Jest로 테스트코드 작성하기</title>
      <link>https://mishka.tistory.com/30</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트의 종류&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;단위테스트&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;단위 테스트는 응용 프로그램에서 테스트 가능한 가장 작은 소프트웨어를 실행하여 예상대로 동작하는지 확인 하는 테스트&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위테스트에서 테스트 대상 단위의 크기는 엄격하게 정해져 있지 않습니다. 하지만 일반적으로 클래스 또는 메소드 수준으로 정하여 테스트합니다. 단위의 크기가 작을 수록 복잡성이 낮아집니다. 따라서 단위 테스트를 활용하여 동작을 표현하기 더 쉬워집니다. 즉, 테스트 대상 단위의 크기를 작게 설정해서 단위 테스트를 최대한 간단하고 디버깅하기 쉽게 작성해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어를 개발할 때, 소프트웨어 내부 구조나 구현 방법을 고려하여 개발자 관점에서 테스트합니다. 그러므로 단위 테스트는 소프트웨어 내부 코드에 관련한 지식을 반드시 알고 있어야 하는 화이트박스 테스트입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;통합테스트&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;통합테스트는 단위 테스트보다 더 큰 동작을 달성하기 위해 여러 모듈들을 모아 이들이 의도대로 협력하는지 확인하는 테스트&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통합테스트는 단위테스트와 달리 개발자가 변경할 수 없는 부분(ex. 외부 라이브러리)까지 묶어서 검증할 때 사용합니다. 이는 DB에 접근하거나 전체 코드와 다양한 환경이 제대로 동작하는지 확인하는데 필요한 모든 작업을 수행 할 수 있습니다. 그러나 통합테스트가 응용 프로그램이 완전하게 동작하는지를 무조건 증명하지는 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통합테스트의 장점은 단위테스트에서 발견하기 어려운 버그를 찾을 수 있다는 점입니다. 한편 통합테스트의 단점은 단위 테스트보다 더 많은 코드를 테스트하기 때문에 신뢰성이 떨어질 수 있다는 점. 또한 어디에서 에러가 발생했는지 확인하기 쉽지 않아 유지보수가 힘들다는 점이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인수테스트&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;인수테스트는 사용자 스토리(시나리오)에 맞춰 수행하는 테스트&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스쪽에 초점을 둔 테스트입니다. 프로젝트에 참여하는 사람들(ex. 기획자, 클라이언트 대표, 개발자 등)이 토의해서 시나리오를 만들고, 개발자는 이에 의거하여 코드를 작성합니다. 개발자가 직접 시나리오를 제작할 수도 있지만 다른 의사소통집단으로부터 시나리오를 받아(인수) 개발한다는 의미를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인수테스트는 애자일 개발 방법론에서 파생했습니다. 특히 익스트림 프로그래밍에서 사용하는 용어입니다. 이는 시나리오가 정상적으로 동작하는지를 테스트 하기 때문에 통합테스트와는 분류가 다릅니다. 시나리오에서 요구하는 것은 누가, 어떤 목적으로, 무엇을 하는가 입니다. 개발을 하다보면 이런 기능은 API를 통해 드러나는데 그렇기에 인수테스트는 주로 이 API를 확인하는 방식으로 이뤄집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국, 인수테스트는 소프트웨어 인수를 목적으로 하는 테스트입니다. 소프트웨어를 인수하기 전에 명세한 요구 사항(인수조건)대로 잘 작동하는지 검증을 합니다. 소프트웨어를 인수할 때, 소프트웨어 내부 구조나 구현 방법을 고려하기보다는 실제 사용자 관점에서 테스트하는 경우가 많다. 따라서. 인수테스트는 소프트웨어 내부 코드에 관심을 가지지 않는 블랙박스 테스트 입니다. 실제 사용자 관점에서 테스트 할 때 주로 E2E 형식을 이용해서 확인을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트코드란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어의 제품 또는 서비스의 품질을 확인하거나 소프트웨어의 버그를 찾을 때 작성하는 코드를 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트코드를 작성하는 이유&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.코드가 정상적으로 동작하는지 확인할 수 있다&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 작성하고 일일이 실행해보면 기능을 확인 하는 번거로움을 줄일 수 있습니다. 처음 코드를 작성하고 직접확인 하는 일은 크게 어렵지 않지만 코드의 수정사항이 발생하여 다시 테스트를 해야하는 일이 생기거나 코드를 작성후에 일정기간의 시간이 흐르고 나면 코드에 대한 기억이 사라지고 없기 때문에 테스트를 하는데 어려움을 겪습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능에 대한 명세를 작성하고 문서를 계속해서 최신화 한다면 비교적 간단히 테스트를 할 수 도 있지만 이것마저 쉬운일은 아닙니다. 테스트 코드는 기능코드의 가장 가까운 곳에서 가장 비슷한 언어로 쓰인 코드의 명세입니다. 테스트코드를 잘 작성하였다면 함수, 클래스, 인터페이스 등의 코드가 어떤 역할과 기능을 하는지 파악하는것이 아주 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 코드의 결함을 사전에 발견할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 기능에 대한 테스트코드를 작성해 놓았다면 기존 코드를 잘못 작성 했을 때 오류가 발생하므로 결함을 사전에 발견할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 코드에 대한 의존성을 분리할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드를 작성하면 기능 코드의 의존성을 쉽게 파악하고 분리할 수 있습니다. 테스트 케이스 환경에서는 같은 입력에 대해서는 같은 결과가 나와야 의미 있는 테스트입니다. 그렇게 하려면 기능 코드의 의존성을 제거하고 외부에서 주입하는 형태로 코드를 작성할 수 밖에 없습니다. 테스트 케이스 환경에서 그러한 의존성을 주입하여서 기능 코드의 의존성을 제어하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신뢰할만한 테스트 코드를 작성하려면 기능 코드의 의존성을 파악하고, 테스트 환경에서 의존성을 주입하거나 적어도 제어 할 수 있는 방식으로 기능 코드를 작성하게 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Refactoring을 부담없이 할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 코드를 다른 코드로 리팩토링 할 때 기존의 소스와 동일한 동작을 하는지에 대한 걱정을 줄일수 있습니다. 코드를 수정하고 테스트 코드를 실행했을 때 오류가 나지 않는다면 코드의 안정성을 확보 할수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 문서로 작용 할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트코드를 잘 작성해 놓으면 코드 작성자의 의도, 사용법, 주의사항 등이 드러나게 되어 별도의 문서가 없어도 문서화의 효과도 누릴수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Jest?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jest는 페이스북에서 만들어진 ALL-IN-ONE 테스팅 라이브러리 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jest 이전에 자바스크립트 코드를 테스트 하려면 여러가지 테스팅 라이브러리가 필요했었습니다. 예를들어 Mocha나 Jasmin을 Test runner로 사용하고 Chai 나 Expect와 같은 Test Mathcher를 사용하였고, Sinon과 Testdouble 같은 Test Mock 라이브러리도 필요하였습니다. 이러한 라이브러리들은 유사하지만 살짝씩 다른부분들이 있어서 개발자들에게 혼란을 야기하였습니다. 하지만 Jest는 Jest하나의 설치로 이러한 역할들을 모두 할수 있는 &lt;span&gt;ALL-in-one 테스팅&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;라이브러리 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Jest 라이브러리 설치&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm 또는 yarn을 사용하여 Jest를 설치 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1670849484188&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// npm
npm i -D jest

// yarn
yarn add --dev jest&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;script 추가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 내용을 package.json 에 추가하세요&lt;/p&gt;
&lt;pre id=&quot;code_1670850565364&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// package.json
&quot;scripts&quot;: {
  &quot;test&quot;: &quot;jest&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용을 script에 추가하면 npm jest 혹은 yarn jest 를 터미널에 입력 함으로 jest 커멘드를 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트 코드 작성&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1670850749838&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;test(&quot;테스트 설명&quot;, () =&amp;gt; {
  expect(&quot;검증 대상&quot;).toXxx(&quot;기대 결과&quot;)
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 형식으로 테스트 코드를 작성할 수 있습니다. 간단한 테스트 코드를 작성해 보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1670850801170&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;test(&quot;1 is 1&quot;, () =&amp;gt; {
  expect(1).toBe(1)
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 작성하고 터미널에 npm jest 혹은 yarn jest 라고 입력하면 초록색 글씨로 테스트가 통과 했다는 메시지를 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1670850912723&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;gt; jest

 PASS  ./test.js
  ✓ 1 is 1 (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.158 s, estimated 1 s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;toBe 부분은 Test Matcher 라고 부르는데 여러가지 방법들이 있습니다. toBe 함수의 경우에는 숫자나 문자와 같은 객체가 아닌 기본형 값을 비교 할때 사용하는 Matcher입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 jest를 실행하면 프로젝트 내에 모든 테스트 파일을 찾아서 테스트를 실행해줍니다. test.js로 끝나거나 __test__ 디텍터리 안에 있는 파일들은 모두 테스트 파일로 인식합니다. 만약 특정 테스트 파일만 실행하고 싶을 경우에는 npm test &amp;lt;파일명 이나 경로&amp;gt; 를 입력하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://tecoble.techcourse.co.kr/post/2021-05-25-unit-test-vs-integration-test-vs-acceptance-test/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tecoble.techcourse.co.kr/post/2021-05-25-unit-test-vs-integration-test-vs-acceptance-test/&lt;/a&gt;&lt;a href=&quot;https://jestjs.io/docs/getting-started&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://jestjs.io/docs/getting-started&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.daleseo.com/jest-basic/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.daleseo.com/jest-basic/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/JEST-%F0%9F%93%9A-jest-%EB%AC%B8%EB%B2%95-%EC%A0%95%EB%A6%AC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://inpa.tistory.com/entry/JEST-%F0%9F%93%9A-jest-%EB%AC%B8%EB%B2%95-%EC%A0%95%EB%A6%AC&lt;/a&gt;&lt;/p&gt;</description>
      <category>Study</category>
      <category>Jest</category>
      <category>단위테스트</category>
      <category>인수테스트</category>
      <category>코드테스트</category>
      <category>테스트</category>
      <category>테스트종류</category>
      <category>통합테스트</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/30</guid>
      <comments>https://mishka.tistory.com/30#entry30comment</comments>
      <pubDate>Mon, 12 Dec 2022 22:40:54 +0900</pubDate>
    </item>
    <item>
      <title>Emotion의 배경지식 / 사용법 (CSS in JS)</title>
      <link>https://mishka.tistory.com/29</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Emotion?&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Emotion은 &lt;b&gt;JavaScript&lt;/b&gt;로 &lt;b&gt;css 스타일&lt;/b&gt;을 작성하도록 설계된 라이브러리입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적으로 Emotion에 대해 알아보기 이전에 간단히&amp;nbsp;다양한 웹 스타일링 기술을 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다양한 웹 스타일링 기술&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CSS&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;CSS(Cascading Style Sheets)는 HTML 이나 XML로 작성된 문서의 표시 방법을 기술하기 위한 스타일 시트 언어입니다.&lt;br /&gt;CSS는 요소가 화면, 종이, 음성이나 다른 매체 상에 어떻게 렌더링 되어야 하는지 지정합니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CSS의 문제점 &lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;- &lt;b&gt;Global namespace&lt;/b&gt;: 모든 스타일이 global에 선언되어 중복되지 않는 class 이름을 적용해야 한다.&lt;br /&gt;- &lt;b&gt;Dependencies&lt;/b&gt;: CSS간의 의존 관계를 관리 하기 힘들다&lt;br /&gt;- &lt;b&gt;Dead Code Elimination&lt;/b&gt;: 기능 추가, 변경, 삭제 과정에서 불필요한 CSS를 제거하기 어렵다.&lt;br /&gt;- &lt;b&gt;Minification&lt;/b&gt;: 클래스 이름의 최소화가 어렵다.&lt;br /&gt;- &lt;b&gt;Sharing Constants&lt;/b&gt;: JS 코드와 상태값을 공유 할수 없다.&lt;br /&gt;- &lt;b&gt;Non-deterministic Resolution&lt;/b&gt;: CSS 로드 순서에 따라 스타일 우선 순위가 달라진다.&lt;br /&gt;- &lt;b&gt;Isolation&lt;/b&gt;: CSS와 JS가 분리된 탓에 상속에 따른 격리가 어렵다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;feat. Christopher Chedeau(일명 Vjeux)- 페이스북 Front-end engineer&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;* CSS-in-CSS&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CSS 모듈(Module)&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;CSS를 사용 할 때 클래스 이름을 고유한 값으로 자동으로 만들어서 컴포넌트 스타일 클래스 이름이 중첨되는 현상을 방지해 주는 기술&lt;br /&gt;- CSS 모듈을 이용하면 클래스명이 충돌하는 단점을 극복할 수 있다.&lt;br /&gt;- CSS 모듈은 컴포넌트 단위로 스타일을 적용할 때 유용하다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CSS 전처리기(Preprocessor)&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;자신만의 특별한 Syntax를 가지고 CSS를 생성하도록 하는 프로그램. CSS의 문제점을 프로그래밍방식(변수, 함수, 상속 등)을 사용 보완하였습니다. CSS 전처리기에는 다양한 모듈이 존재하는데 Sass, Less, Stylus 가 대표적입니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;* CSS-in-JS&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS-in-JS는 단어 그대로 자바스크립트 코드에서 CSS를 작성하는 방식을 말합니다. 2014년 페이스북 개발자인 Christopher Chedeau aka Vjeux가 처음 소개하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Styled Components, Emotion가 대표적입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Styled-components&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;기존 돔을 만드는 방식인 CSS, SCSS 파일을 밖에 두고, 태그나 Id, class 이름으로 가져와 쓰지 않고, 동일한 컴포넌트에서 컴포넌드 이름을 쓰듯 스타일을 지정하는 것을 styled-components 라고 부릅니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Emotion 사용법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emotion.js는 주로 Framwork Agnostic 과 React 두가지 방식으로 사용합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;설치&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1669957429114&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Framework Agnostic
$ npm install @emotion/css
or
$ yarn add @emotion/css

# React
$ npm install @emotion/react
or
$ yarn add @emotion/react&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;사용 (&lt;a href=&quot;https://emotion.sh/docs/introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서 예문&lt;/a&gt;)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ccs() 함수는 CSS 스타일 선언 내용을 인자로 받는데 문자형과 객체형으로 넘길수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669958472692&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 문자형 스타일
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'

const color = 'white'

render(
  &amp;lt;div
    css={css`
      padding: 32px;
      background-color: hotpink;
      font-size: 24px;
      border-radius: 4px;
      &amp;amp;:hover {
        color: ${color};
      }
    `}
  &amp;gt;
    Hover to change color.
  &amp;lt;/div&amp;gt;
)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1669960319758&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 객체형 스타일
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'

const color = 'white'

render(
  &amp;lt;div
    css={css({
      padding: '32px',
      backgroundColor: 'hotpink',
      fontSize: '24px',
      borderRadius: '4px',
      cursor: 'pointer',
      '&amp;amp;:hover': {
        color: `${color}`,
      },
    })}
  &amp;gt;
    Hover to change color.
  &amp;lt;/div&amp;gt;
)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;JSX pragma&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emotion의 css prop을 제대로 사용하기 위해서는 파일 상단에 pragma 를 선언해 주어야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1669962072495&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pragma란 컴파일러에게 하는 전처리 명령이라고 생각하면 되는데 babel 트랜스파일러에게 JSX코드를 변환할때 React의 jsx() 함수 대신 Emotion의 jsx() 함수를 사용하라고 알려주는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React v16 이하 의 오래된 버전을 사용하고 있는 프로젝트에서는 약간 다른 형태로 사용하고 jsx()함수도 불러와야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1669961994763&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// React v16 이하
/** @jsx jsx */
import { css, jsx } from &quot;@emotion/react&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pragma 지우기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 사용할 때 마다 pragma를 사용하는건 무척 번거로운 일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하는 버전에 따라 @emotion/babel-prest-css-prop 나 @babel/preset-react 의 importSource옵션 변경을 통하여 pragma 없이 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 같은 경우에는 Next.js 세팅을 하고 있는데 Next.js의 경우는 tsconfig.json 에 아래 옵션만 추가해주시면 간단히 해결됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1669968439135&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// tsconfig.json
&quot;jsxImportSource&quot;: &quot;@emotion/react&quot;,&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;일반적인 스타일링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;button&amp;gt; 요소의 CSS prop을 통해서 다양한 CSS 속성 정의를 객체로 넘길 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1670251601628&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// EmotionBtn.js

function EmotionBtn({ children }) {
  return (
    &amp;lt;button
      css={{
        border: '1px solid gray',
        borderRadius: '6px',
        color: 'black',
        fontSize: '14px',
        padding: '10px 16px',
        cursor: 'pointer',
      }}
    &amp;gt;
      {children}
    &amp;lt;/button&amp;gt;
  )
}

export default EmotionBtn&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만든 컴포넌트를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1670251949203&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import EmotionBtn from 'EmotionBtn'

function App() {
  return &amp;lt;EmotionBtn&amp;gt;Button&amp;lt;/EmotionBtn&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 버튼을 브라우저에서 확인해보면 &amp;lt;button&amp;gt; 요소에 Emotion이 자동으로 생성해준 클래스 이름이 붙어 있는 것을 확인하실 수 있습니다. (클래스 이름은 랜덤으로 생성됩니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1670252120870&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;button class=&quot;css-1ayjb79-Btn&quot;&amp;gt;Button&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일도 확인해 보면 선언한내용과 같은것을 확인 할 수 있습니다. 선언한 것과 다른것이 있다면 Emotion에서 자동으로 브라우저별 필요한 vendor prefixing을 해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1670252405910&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.css-1ayjb79-Btn {
  border: 1px solid gray;
  border-radius: 6px;
  color: black;
  font-size: 14px
  padding: 10px 16px;
  cursor: pointer;
  -webkit-appearance: none;
  -moz-appearance: none;
  -ms-appearance: none;
  appearance: none;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Prop을 이용한 스타일링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prop에 따라 스타일의 변화를 주는 스타일링을 할 수 있습니다. &amp;lt;VariableBtn&amp;gt; 컴포넌트에 variant 라는 prop을 추가하고 변화를 주어 스타일링을 해보도록 하겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1670270574760&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// VariableBtn.js

const colors = {
  default: 'black',
  danger: 'red',
  outline: 'blue',
}

function VariableBtn({ children, variant }) {
  return (
    &amp;lt;button
      css={{
        border: '1px solid gray',
        borderRadius: '6px',
        color: colors[variant],
        fontSize: '14px',
        padding: '10px 16px',
        cursor: 'pointer',
        appearance: 'none',
        userSelect: 'none',
      }}
    &amp;gt;
      {children}
    &amp;lt;/button&amp;gt;
  )
}

export default VariableBtn&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 때에는 아래와 같이 porp에 값을 변경시켜 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1670270765551&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import VariableBtn from 'VariableBtn'

function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;VariableBtn variant=&quot;default&quot;&amp;gt;default&amp;lt;/VariableBtn&amp;gt;
      &amp;lt;VariableBtn variant=&quot;danger&quot;&amp;gt;danger&amp;lt;/VariableBtn&amp;gt;
      &amp;lt;VariableBtn variant=&quot;outline&quot;&amp;gt;outline&amp;lt;/VariableBtn&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prop을 이용해서 스타일링의 변화를 줄때 하나의 속성만이 아니라 여러개를 이용 할 수도 있습니다. 이런 경우에는 CSS속성을 객체로 만들어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1670271568635&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MultiVariableBtn.js

const colors = {
  default: 'black',
  danger: 'red',
  outline: 'blue',
}

const sizeStyle = {
  sm: {
    fontSize: '12px',
    padding: '3px 12px',
  },
  md: {
    fontSize: '14px',
    padding: '5px 16px',
  },
  lg: {
    fontSize: '16px',
    padding: '9px 20px',
  },
}

function MultiVariableBtn({ children, size = 'md', variant = 'default' }) {
  return (
    &amp;lt;button
      css={{
        border: '1px solid gray',
        borderRadius: '6px',
        color: colors[variant],
        ...sizeStyle[size],
        cursor: 'pointer',
        appearance: 'none',
        userSelect: 'none',
      }}
    &amp;gt;
      {children}
    &amp;lt;/button&amp;gt;
  )
}

export default MultiVariableBtn&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 때에는 간단히 props를 전달해주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1670271676704&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import MultiVariableBtn from 'MultiVariableBtn'

function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;MultiVariableBtn size=&quot;sm&quot; variant=&quot;defalt&quot;&amp;gt;
        Sm Size
      &amp;lt;/MultiVariableBtn&amp;gt;
      &amp;lt;MultiVariableBtn size=&quot;md&quot; variant=&quot;danger&quot;&amp;gt;
        Md Size
      &amp;lt;/MultiVariableBtn&amp;gt;
      &amp;lt;MultiVariableBtn size=&quot;lg&quot; variant=&quot;outline&quot;&amp;gt;
        Lg Size
      &amp;lt;/MultiVariableBtn&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Global 스타일링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역에 스타일링이 필요한 경우에도 간편하게 스타일링을 추가 할수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1670272896623&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Global.js

import { Global } from '@emotion/react'

const GlobalStyle = () =&amp;gt; (
  &amp;lt;Global
    styles={{
      '*': {
        color: 'blue',
      },
      a: {
        color: 'red',
      },
    }}
  /&amp;gt;
)

export default GlobalStyle&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1670272932527&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// index.js

import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom/client&quot;;
import App from &quot;./App&quot;;
import GlobalStyle from &quot;./global&quot;;

const root = ReactDOM.createRoot(
  document.getElementById(&quot;root&quot;) as HTMLElement
);
root.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;GlobalStyle /&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://emotion.sh/docs/introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://emotion.sh/docs/introduction&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.daleseo.com/emotion/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.daleseo.com/emotion/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gong-check.github.io/dev-blog/FE/%EC%98%A8%EC%8A%A4%ED%83%80/emotion%20%EC%A0%81%EC%9A%A9%EA%B8%B0/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gong-check.github.io/dev-blog/FE/%EC%98%A8%EC%8A%A4%ED%83%80/emotion%20%EC%A0%81%EC%9A%A9%EA%B8%B0/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tech.osci.kr/2022/06/14/%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%97%90-%EC%8A%A4%ED%83%80%EC%9D%BC-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0-with-emotion/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tech.osci.kr/2022/06/14/%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%97%90-%EC%8A%A4%ED%83%80%EC%9D%BC-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0-with-emotion/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@nightowl094/Next.js-Emotion-css-prop-%EC%84%A4%EC%A0%95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@nightowl094/Next.js-Emotion-css-prop-%EC%84%A4%EC%A0%95&lt;/a&gt;&lt;/p&gt;</description>
      <category>Study</category>
      <category>css</category>
      <category>CSS in CSS</category>
      <category>css in js</category>
      <category>emotion</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/29</guid>
      <comments>https://mishka.tistory.com/29#entry29comment</comments>
      <pubDate>Tue, 6 Dec 2022 05:48:55 +0900</pubDate>
    </item>
    <item>
      <title>React Query</title>
      <link>https://mishka.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;React Query&lt;/b&gt;는 React 어플리케이션에서 서버의 상태를 불러오고, 캐싱하며, 지속적으로 동기화 하고 업데이트 하는 작업을 도와 주는 라이브러리 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;* 캐싱&lt;br /&gt;* Boilerplate 코드의 감소&lt;br /&gt;* get 한 데이터에 update 가 발생하면 자동으로 get을 다시 수행한다.&lt;br /&gt;* 동일 데이터 여러번 요청시 한번만 요청한다 (옵션에 따라 중복호출 허용 시간 조절 가능)&lt;br /&gt;* 데이터가 오래되었다고 판단하면 다시 get (invalidateQueries)&lt;br /&gt;* API 요청 수행을 위한 규격화된 방식 제공&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;사용&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1669552709277&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx create-react-app my-app
cd my-app
yarn add react-query
yarn &amp;amp;&amp;amp; yarn start&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;세팅&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1669552768299&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// src/index.js
import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom&quot;;
import App from &quot;./App&quot;;
import { QueryClient, QueryClientProvider } from &quot;react-query&quot;;
import { ReactQueryDevtools } from &quot;react-query/devtools&quot;;

const queryClient = new QueryClient();

ReactDOM.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;QueryClientProvider client={queryClient}&amp;gt;
      {/* devtools */}
      &amp;lt;ReactQueryDevtools initialIsOpen={true} /&amp;gt;
      &amp;lt;App /&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;,
  document.getElementById(&quot;root&quot;)
);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;useQuery&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터를 get 하기 위한 api / post,update에는 useMutation을 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 첫번째 파라미터로 unique Key가 들어가고, 두번째 파라미터로 비동기 함수(api 호출함수)가 들어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- unique Key는 다른 컴포넌트에서도 해당 키를 사용하면 호출이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; unique Key는 string 과 배열을 받는데 배열로 넘기면 0번 값은 string 값으로 다른 컴포넌트에서 부를 값이 들어가고 두번째 값을 넣으면 query 함수 내부에 파라미터로 해당 값이 전달됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- return 값은 api의 성공,실패 여부. 즉 api return값을 포함한 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- useQuery는 비동기로 작동합니다. 여러개의 비동기 query가 있다면 useQueries를 권장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- enabled를 사용하면 useQuery를 동기적으로 사용가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예제&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1669560374644&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Todos = () =&amp;gt; {
  const { isLoading, isError, data, error } = useQuery(&quot;todos&quot;, fetchTodoList, {
    refetchOnWindowFocus: false, // react-query는 사용자가 사용하는 윈도우가 다른 곳을 갔다가 다시 화면으로 돌아오면 이 함수를 재실행합니다. 그 재실행 여부 옵션 입니다.
    retry: 0, // 실패시 재호출 몇번 할지
    onSuccess: data =&amp;gt; {
      // 성공시 호출
      console.log(data);
    },
    onError: e =&amp;gt; {
      // 실패시 호출 (401, 404 같은 error가 아니라 정말 api 호출이 실패한 경우만 호출됩니다.)
      // 강제로 에러 발생시키려면 api단에서 throw Error 날립니다. (참조: https://react-query.tanstack.com/guides/query-functions#usage-with-fetch-and-other-clients-that-do-not-throw-by-default)
      console.log(e.message);
    }
  });

  if (isLoading) {
    return &amp;lt;span&amp;gt;Loading...&amp;lt;/span&amp;gt;;
  }

  if (isError) {
    return &amp;lt;span&amp;gt;Error: {error.message}&amp;lt;/span&amp;gt;;
  }

  return (
    &amp;lt;ul&amp;gt;
      {data.map(todo =&amp;gt; (
        &amp;lt;li key={todo.id}&amp;gt;{todo.title}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* status 로 isLoding, isSucess를 한번에 처리 할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1669560419482&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Todos() {
  const { status, data, error } = useQuery(&quot;todos&quot;, fetchTodoList);

  if (status === &quot;loading&quot;) {
    return &amp;lt;span&amp;gt;Loading...&amp;lt;/span&amp;gt;;
  }

  if (status === &quot;error&quot;) {
    return &amp;lt;span&amp;gt;Error: {error.message}&amp;lt;/span&amp;gt;;
  }

  return (
    &amp;lt;ul&amp;gt;
      {data.map(todo =&amp;gt; (
        &amp;lt;li key={todo.id}&amp;gt;{todo.title}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;useQuery의 동기적 실행&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enabled 옵션을 사용하면 useQuery를 동기적으로 사용 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useQuery의 3번째 인자로는 옵션값이 들어가는데 enabled값을 true일때 useQuery를 실행합니다. 이를 이용하면 동기적으로 함수를 실행 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;useQueries&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useQuery를 비동기로 여러개 실행할 경우 어려움을 겪을수 있습니다. 이를 방지하기 위해 promise.all처럼 useQuery를 하나로 묶을수 있는 기능이 useQueries 입니다. promise.all처럼 하나의 배열에 각 쿼리에 대한 상태 값이 객체로 들어옵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1669560755158&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 아래 예시는 롤 룬과, 스펠을 받아오는 예시입니다.
const result = useQueries([
  {
    queryKey: [&quot;getRune&quot;, riot.version],
    queryFn: () =&amp;gt; api.getRunInfo(riot.version)
  },
  {
    queryKey: [&quot;getSpell&quot;, riot.version],
    queryFn: () =&amp;gt; api.getSpellInfo(riot.version)
  }
]);

useEffect(() =&amp;gt; {
  console.log(result); // [{rune 정보, data: [], isSucces: true ...}, {spell 정보, data: [], isSucces: true ...}]
  const loadingFinishAll = result.some(result =&amp;gt; result.isLoading);
  console.log(loadingFinishAll); // loadingFinishAll이 false이면 최종 완료
}, [result]);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;useMutation&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 바꿀때 사용하는 api입니다. return 값은 useQuery와 동일하다.&lt;/p&gt;
&lt;pre id=&quot;code_1669560911366&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useContext, useEffect } from &quot;react&quot;;
import loginApi from &quot;api&quot;;
import { useMutation } from &quot;react-query&quot;;

const Index = () =&amp;gt; {
  const [id, setId] = useState(&quot;&quot;);
  const [password, setPassword] = useState(&quot;&quot;);

  const loginMutation = useMutation(loginApi, {
    onMutate: variable =&amp;gt; {
      console.log(&quot;onMutate&quot;, variable);
      // variable : {loginId: 'xxx', password; 'xxx'}
    },
    onError: (error, variable, context) =&amp;gt; {
      // error
    },
    onSuccess: (data, variables, context) =&amp;gt; {
      console.log(&quot;success&quot;, data, variables, context);
    },
    onSettled: () =&amp;gt; {
      console.log(&quot;end&quot;);
    }
  });

  const handleSubmit = () =&amp;gt; {
    loginMutation.mutate({ loginId: id, password });
  };

  return (
    &amp;lt;div&amp;gt;
      {loginMutation.isSuccess ? &quot;success&quot; : &quot;pending&quot;}
      {loginMutation.isError ? &quot;error&quot; : &quot;pending&quot;}
      &amp;lt;input type=&quot;text&quot; value={id} onChange={e =&amp;gt; setId(e.target.value)} /&amp;gt;
      &amp;lt;input
        type=&quot;password&quot;
        value={password}
        onChange={e =&amp;gt; setPassword(e.target.value)}
      /&amp;gt;
      &amp;lt;button onClick={handleSubmit}&amp;gt;로그인&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Index;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;update후에 get 실행&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mutation 함수가 성공 할때, unique Key 로 맵핑된 get 함수로 invalidateQueries 에 넣어주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1669561016433&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const mutation = useMutation(postTodo, {
  onSuccess: () =&amp;gt; {
    // postTodo가 성공하면 todos로 맵핑된 useQuery api 함수를 실행합니다.
    queryClient.invalidateQueries(&quot;todos&quot;);
  }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mutation에서 return된 값을 이용해서 get 함수의 파라미터를 변경해야 될 경우에는 setQueryData를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1669561080134&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const queryClient = useQueryClient();

const mutation = useMutation(editTodo, {
  onSuccess: data =&amp;gt; {
    // data가 fetchTodoById로 들어간다
    queryClient.setQueryData([&quot;todo&quot;, { id: 5 }], data);
  }
});

const { status, data, error } = useQuery([&quot;todo&quot;, { id: 5 }], fetchTodoById);

mutation.mutate({
  id: 5,
  name: &quot;nkh&quot;
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;react Suspense 와 Error boundary 같이 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Suspense 사용하여 loading을 Error boundary를 사용하여 에러 핸들링을 더욱 직관적으로 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;suspense를 사용하기 위해 QueryClient에 옵션을 하나 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1669561493724&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// src/index.js
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 0,
      suspense: true
    }
  }
});

ReactDOM.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;QueryClientProvider client={queryClient}&amp;gt;
      &amp;lt;App /&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;,
  document.getElementById(&quot;root&quot;)
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수에 suspense를 사용하는 예시&lt;/p&gt;
&lt;pre id=&quot;code_1669561526585&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { data } = useQurey(&quot;test&quot;, testApi, { suspense: true });

// 사용
return (
  // isLoading이 true이면 Suspense의 fallback 내부 컴포넌트가 보여집니다.
  // isError가 true이면 ErrorBoundary의 fallback 내부 컴포넌트가 보여집니다.
  &amp;lt;Suspense fallback={&amp;lt;div&amp;gt;loading&amp;lt;/div&amp;gt;}&amp;gt;
    &amp;lt;ErrorBoundary fallback={&amp;lt;div&amp;gt;에러 발생&amp;lt;/div&amp;gt;}&amp;gt;
      &amp;lt;div&amp;gt;{data}&amp;lt;/div&amp;gt;
    &amp;lt;/ErrorBoundary&amp;gt;
  &amp;lt;/Supense&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://kyounghwan01.github.io/blog/React/react-query/basic/#react-suspense%E1%84%8B%E1%85%AA-react-query-%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kyounghwan01.github.io/blog/React/react-query/basic/#react-suspense%E1%84%8B%E1%85%AA-react-query-%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tech.kakaopay.com/post/react-query-1/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tech.kakaopay.com/post/react-query-1/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Study</category>
      <category>React</category>
      <category>react-query</category>
      <category>Study</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/28</guid>
      <comments>https://mishka.tistory.com/28#entry28comment</comments>
      <pubDate>Mon, 28 Nov 2022 00:12:48 +0900</pubDate>
    </item>
    <item>
      <title>React 상태관리 라이브러리 1탄 (Redux)</title>
      <link>https://mishka.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React 에는 많은 상태관리 라이브러리들이 있습니다. 그중에 가장 많이 사용하는 Redux를 먼저 정리해 보았습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Redux의 역사&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MVC 패턴&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redux의 역사는 MVC 패턴에서 시작합니다. MVC 패턴에서 Cotroller는 Model에 정의된 데이터를 조회하거나 업데이트하는 역할을 하고, 변경된 Model의 데이터를 View에 반영해 줍니다. 또한 사용자는 View를 통해 데이터를 입력하고 Model에 반영되며, View 와 Model은 데이터를 양방향으로 주고받는 형태입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MoRpQ/btrRC0aFXJG/wsIKXnbrKZCSRTYeXRk7qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MoRpQ/btrRC0aFXJG/wsIKXnbrKZCSRTYeXRk7qk/img.png&quot; data-alt=&quot;출처: Flux 공식문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MoRpQ/btrRC0aFXJG/wsIKXnbrKZCSRTYeXRk7qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMoRpQ%2FbtrRC0aFXJG%2FwsIKXnbrKZCSRTYeXRk7qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1300&quot; height=&quot;286&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: Flux 공식문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 규모가 커질수록 수많은 View와 Model들이 생겨났기 때문에 데이터가 어디로 흐르는지 파악하기 어렵다는 문제점이 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;553&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qXQSN/btrRBIH12xT/Am5naMgoVDlJCLLZtFExQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qXQSN/btrRBIH12xT/Am5naMgoVDlJCLLZtFExQ1/img.png&quot; data-alt=&quot;출처: Fulx 공식문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qXQSN/btrRBIH12xT/Am5naMgoVDlJCLLZtFExQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqXQSN%2FbtrRBIH12xT%2FAm5naMgoVDlJCLLZtFExQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;553&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;553&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: Fulx 공식문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 기능을 추가할때 마다 크고 작은 문제가 생겼고, 예상 할 수 없는 결과(Side Effect)가 일어났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Flux의 시작&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제들을 해결하기 위해 페이스북 개발팀에서 새로운 아키텍처를 적용하기로 하여 나온것이 Flux 패턴입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RfjEp/btrRFeMWtfJ/uu8AF9O0UWsFUaCYXSZUXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RfjEp/btrRFeMWtfJ/uu8AF9O0UWsFUaCYXSZUXK/img.png&quot; data-alt=&quot;출처: Flux 공식문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RfjEp/btrRFeMWtfJ/uu8AF9O0UWsFUaCYXSZUXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRfjEp%2FbtrRFeMWtfJ%2Fuu8AF9O0UWsFUaCYXSZUXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1300&quot; height=&quot;393&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;393&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: Flux 공식문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flux 패턴은 어떤 Action이 발생하면 dispatcher에 의해 store에 변경된 사항이 저장되고 그 저장된 데이터들에 의해 view가 변경되는 단방향 패턴입니다. 이러한 패턴의 가장 큰 장점은 양방향으로 흐르던 MVC 패턴과 반대로 단방향으로 흐르기 때문에 흐름을 파악하기 쉽고 예측 가능하다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 Flux 패턴을 적용한 구현체들이 많이 있는데 그 중 하나가 바로 &lt;b&gt;Redux&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Redux의 핵심 키워드&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Store&lt;/b&gt;&lt;br /&gt;Store는 애플리케이션의 상태 트리를 가지고 있는 객체.&lt;br /&gt;Redux 앱에는 단 하나의 저장소만 있어야 합니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;State&lt;br /&gt;&lt;/b&gt;State는 넓은 의미의 단어이지만, Redux API 에서는 보통 저장소에 의해 관리되고 getState()에 의해 반환되는 하나의 상태 값을 지칭. state는 Redux 애플리케이션의 전체 상태를 나타내며, 보통 깊게 중첩되어 있는 객체입니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Action&lt;/b&gt;&lt;br /&gt;Action은 상태를 변화시키려는 의도를 표현하는 평범한 객체.&lt;br /&gt;action으 store에 데이터를 넣는 유일한 방법입니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Action Creator&lt;/b&gt;&lt;br /&gt;Action Creator는 단지 Action을 만드는 함수 입니다.&lt;br /&gt;action creator를 호출하면 Action을 만들어낼 뿐 dispatch하지는 않습니다.&lt;br /&gt;action creator를 호출해 그 결과를 Store인스턴스로 바로 dispatch하는 함수를 바인드된 액션 생성자 라고 부르기도 합니다.&amp;nbsp;&lt;br /&gt;action creator가 현재 상태를 읽어야 하거나 API 호출을 실행하거나, 라우트 전환같은 부수효과를 일으켜야 한다면, action대신 asyncAction을 반환해야 합니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Reducer&lt;/b&gt;&lt;br /&gt;Reducer는 누적값과 값을 받아서 새로운 누적값을 반환하는 함수.&lt;br /&gt;이들은 값들의 컬렉션을 받아서 하나의 값을 줄이는데 사용됩니다. reducer는 Redux만의 개념은 아닙니다.&lt;br /&gt;Redux에서 누적값은 상태 객체이고, 누적될 값은 액션입니다. &lt;br /&gt;리듀서는 주어진 이전 상태와 액션에서 새로운 상태를 계산합니다. &lt;br /&gt;=&amp;gt; 현재(이전)의 state와 action을 인자로 받아 store에 접근해 action에 맞춰 state를 변경 합니다.&lt;br /&gt;이들은 반드시 같은 입력이 있으면 같은 출력을 반환하는 &lt;b&gt;순수 함수&lt;/b&gt; 여야만 합니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Dispatch&lt;/b&gt;&lt;br /&gt;Dispatch는 store의 내장 함수 중 하나로, action이나 async action을 받는 함수.&lt;br /&gt;기본 dispatch 함수는 반드시 동기적으로 store에 reducer에 action을 보내야 합니다. 그러면 reducer는 Store가 반환한 이전 상태와 함께 새 상태를 계산합니다. reducer가 사용하기 위해서 action은 평범한 객체여야 합니다.&lt;br /&gt;middleware는 기본 dispatch 함수를 감쌉니다. middleware를 통해 dispatch 함수는 action뿐만 아니라 async action을 처리 할 수 있습니다. middleware는 action이나 async action을 다음 middleware에 넘기기 전에 변환하거나, 지연시키거나, 무시하거나, 해석할 수 있습니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Async Action&lt;br /&gt;&lt;/b&gt;Async Action은 dispatch로 보내지는 값이지만. 아직 reducer에게 받아들려질 준비가 되어 있지는 않았습니다. async action은 기본 dispatch함수로 전달되기 전에 middleware를 통해 action등으로 바뀌어야 합니다. async action은 여러분이 사용하는 middleware에 따라 서로 다른 타입이 될 수 있습니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Middleware&lt;/b&gt;&lt;br /&gt;Middleware는 dispatch함수를 결합해서 새 dispatch함수를 반환하는 고차함수.&lt;br /&gt;middleware는 함수 결합을 통해 서로 결합할 수 있습니다. 이는 action을 로깅하거나, 라우팅과 같은 부수효과를 일으키거나, 비동기 API 호툴을 일련의 동기 액션으로 바꾸는데 유용합니다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Subscribe&lt;br /&gt;&lt;/b&gt;Subscribe는 store의 내장 함수 중 하나로, 특정 함수를 전달해주면 action이 dispatch 되었을 때마다 전달된 함수가 호출 됩니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Redux의 세가지 원칙&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Single source of truth&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;애플리케이션의 모든 상태는 하나의 저장소 안에 하나의 개체 트리 구조로 저장됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1c1e21;&quot;&gt;이를 통해 범용적인 애플리케이션(universal application, 하나의 코드 베이스로 다양한 환경에서 실행 가능한 코드)을 만들기 쉽게 만들 수 있습니다. 서버로부터 가져온 상태는 시리얼라이즈되거나(serialized) 수화되어(hydrated) 전달되며 클라이언트에서 추가적인 코딩 없이도 사용할 수 있습니다. 또한 하나의 상태 트리만을 가지고 있기 때문에 디버깅에도 용이할 것입니다. 빠른 개발 사이클을 위해 개발중인 애플리케이션의 상태를 저장해놓을 수도 있습니다. 하나의 상태 트리만을 가지고 있기 때문에 이전에는 굉장히 구현하기 어려웠던 기능인 실행취소/다시실행(undo/redo)을 손쉽게 구현할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. State is read-only&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상태를 변화시키는 유일한 방법은 무슨 일이 벌어지는 지를 묘사하는 액션 객체를 전달하는 방법 뿐이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1c1e21;&quot;&gt;이를 통해서 뷰나 네트워크 콜백에서 결코 상태를 직접 바꾸지 못 한다는 것을 보장할 수 있습니다. 모든 상태 변화는 중앙에서 관리되며 모든 액션은 엄격한 순서에 의해 하나하나 실행되기 때문에, 신경써서 관리해야할 미묘한 경쟁 상태는 없습니다. 액션은 그저 평범한 객체입니다. 따라서 기록을 남길 수 있고, 시리얼라이즈할 수 있으며, 저장할 수 있고, 이후에 테스트나 디버깅을 위해서 재현하는 것도 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Changes are made with pure functions&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;액션에 의해 상태 트리가 어떻게 변화하는 지를 지정하기 위해 프로그래머는 순수&lt;span&gt;&amp;nbsp;&lt;/span&gt;리듀서를 작성해야합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1c1e21;&quot;&gt;리듀서는 그저 이전 상태와 액션을 받아 다음 상태를 반환하는 순수 함수입니다. 이전 상태를 변경하는 대신 새로운 상태 객체를 생성해서 반환해야한다는 사실을 기억해야 합니다. 처음에는 하나의 리듀서만으로 충분하지만, 애플리케이션이 성장해나가면 상태 트리의 특정한 부분들을 조작하는 더 작은 리듀서들로 나누는 것도 가능합니다. 리듀서는 그저 함수이기 때문에 호출되는 순서를 정하거나 추가적인 데이터를 넘길 수도 있습니다. 심지어 페이지네이션과 같이 일반적인 재사용 가능한 리듀서를 작성하는 것도 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1c1e21;&quot;&gt;Redux flow&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1c1e21;&quot;&gt;1. UI에서 컴포넌트 내에 존재하는 이벤트가 호출됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1c1e21;&quot;&gt;2. 이벤트와 연결된 &lt;b&gt;Action Creator&lt;/b&gt;가 호출됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1c1e21;&quot;&gt;3. Action Creator에서 생성된 &lt;b&gt;Action&lt;/b&gt;이 호출됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1c1e21;&quot;&gt;4. Action이 &lt;b&gt;Reducer&lt;/b&gt;로 전달 됩니다. 이 과정을 &lt;b&gt;Dispatch&lt;/b&gt;에서 담당합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1c1e21;&quot;&gt;5. Reducer에서 Dispatch된 Action에 따라 상태 값을 변경합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1c1e21;&quot;&gt;6. 변경사항이 렌더링되어 UI에 나타납니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study</category>
      <category>flux</category>
      <category>MVC</category>
      <category>Redux</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/27</guid>
      <comments>https://mishka.tistory.com/27#entry27comment</comments>
      <pubDate>Mon, 21 Nov 2022 22:38:14 +0900</pubDate>
    </item>
    <item>
      <title>ESLint,  Prettier 무엇이 다르고 어떻게 Setting 해야 할까?</title>
      <link>https://mishka.tistory.com/26</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;'ESLint'&lt;/b&gt;는 자바스크립트 코드에서 발견된 문제를 식별하기 위한 &lt;b&gt;정적 코드 분석 도구&lt;/b&gt;입니다. 이 도구는 2013년에 니콜라스 C. 자카스에 의해 개발되었습니다. ESLint의 규칙들은 구성이 가능하며, 사용자는 지정한 규칙을 정의하고 로드할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 중 특정 기능을 구현할 때, 그 기능을 구현하기 위한 여러가지 방법이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들자면 함수를 정의 할 때, function 키워드를 사용하여 할수도 있고 arrow function을 쓸 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 배열의 반복문을 사용할 때, for문을 사용할 수도 있지만, forEach, map 등 Array 내장 함수를 사용 할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 여러가지 방식들을 &lt;b&gt;일관성있는 방식으로 구현할 수 있도록 도와주고 고쳐주는 것&lt;/b&gt;이 ESLint 의 역할입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;'Prettier'&lt;/b&gt;는 코드를 분석하여 깔끔하고 일관된 코드스타일을 유지시켜주게 도와주는 &lt;b&gt;코드 포멧터 &lt;/b&gt;입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prettier 는 ESLint 처럼 '코드 구현 방식'이 아닌 줄 바꿈, 공백, 들여 쓰기 등의 에디터에서 '텍스트'가 일관되게 작성되도록 도와 주는 역할을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1665552455696&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const foo = () =&amp;gt; {
  const a = [1, 2, 3] // 스콮프 내부 작성시 두 공배 들여쓰기
}
  // &amp;lt;= 빈 줄이 한 줄 이상 안됨.
foo()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ESLint Setting&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ESLint 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1665552694401&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm install -D eslint
// or
$ yarn add -D eslint&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESlint 는 설치만 한다고 바로 프로젝트에서 사용할 수 없고, &lt;b&gt;ESLint extension&lt;/b&gt;을 같이 &lt;b&gt;설치&lt;/b&gt; 해주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-12 오후 2.33.02.png&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;1102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chtChk/btrOnLni7Nc/fT4RRZrefRxuFtFHCHryz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chtChk/btrOnLni7Nc/fT4RRZrefRxuFtFHCHryz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chtChk/btrOnLni7Nc/fT4RRZrefRxuFtFHCHryz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchtChk%2FbtrOnLni7Nc%2FfT4RRZrefRxuFtFHCHryz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1636&quot; height=&quot;1102&quot; data-filename=&quot;스크린샷 2022-10-12 오후 2.33.02.png&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;1102&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESLint Extionsin의 설명도 같이 살펴보면 해당 워크스페이스에서 ESLint 가 설치되어 있는지 확인하고, 없으면 글로벌 ESLint 를 참조한다. 그리고 필요에 따라 .eslintrc 파일이 필요할 수도 있다고 설명하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESLint 를 사용하려면 &lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;ESLint Library&lt;/span&gt;만 설치하거나 ESLint extension만 설치 하는 것이 아니라 둘다 설치 및 세팅이 되어 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;.eslintrc 구성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://eslint.org/docs/latest/user-guide/configuring/configuration-files&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;eslint Doc&lt;/a&gt; 에 나와 있는 example 을 살펴보자&lt;/p&gt;
&lt;pre id=&quot;code_1665554199116&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .eslintrc
{
    &quot;root&quot;: true,
    &quot;extends&quot;: [
        &quot;eslint:recommended&quot;,
        &quot;plugin:@typescript-eslint/recommended&quot;
    ],
    &quot;parser&quot;: &quot;@typescript-eslint/parser&quot;,
    &quot;parserOptions&quot;: { &quot;project&quot;: [&quot;./tsconfig.json&quot;] },
    &quot;plugins&quot;: [
        &quot;@typescript-eslint&quot;
    ],
    &quot;rules&quot;: {
        &quot;@typescript-eslint/strict-boolean-expressions&quot;: [
            2,
            {
                &quot;allowString&quot; : false,
                &quot;allowNumber&quot; : false
            }
        ]
    },
    &quot;ignorePatterns&quot;: [&quot;src/**/*.test.ts&quot;, &quot;src/frontend/generated/*&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;root&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default 값은 true, 값이 true가 아니면, eslintrc 파일을 찾을 때, 해당 프로젝트 디텍토리 뿐 아니라, 내 PC의 root 파일 시스템 root 디텍토리까지 eslint를 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;extends&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eslint rule 설정이 저장되어 잇는 외부 file을 extends 하는 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extends에 eslint:recommended, plugin:@typescript-eslint/recommended를 추가하면, 사용하려는 해당 플러그인에서 기본적으로 제공하는 rule set이 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;parser&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 코드 파일을 검사할 파서를 설정합니다. 기본 설정은 Espree이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Babel 과 함께 사용되는 파서로는 babel-eslint 가 있고 Typescript 구문 분석을 위해 사용되는 @typescript-eslint/parser 가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;parserOptions&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESLint 사용을 위해 지원하려는 javascript 언어 옵션을 지정할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ecmaVersion: 사용할 ECMAScript 버전을 설정&lt;/li&gt;
&lt;li&gt;sourceType: parser의 export 형태를 설정&lt;/li&gt;
&lt;li&gt;ecmaFeatres: ECMAScript의 언어 확장 기능을 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;globalReturn: 전역 스코프의 사용 여부 (node, commonjs 환경에서 최상위 스코프는 module)&lt;/li&gt;
&lt;li&gt;impliedStric: strict mode 사용 여부&lt;/li&gt;
&lt;li&gt;jsx: ECMAScript 규격의 JSX 사용 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;plugins&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESLint는 서드파티 플러그인을 지원합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 필요한 각 플러그인들은 npm 이나 yarn을 통해서 설치 하고, 해당 플러그인을 plugins 에 추가하여 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인은 일련의 규칙의 집합이며, 플러그인을 추가하여도 규칙은 적용되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;plugin 종류 몇가지를 살펴보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-ke-style=&quot;style2&quot;&gt;eslint-config-airbnb-base: 에어비엔비 린트 플러그인&lt;/li&gt;
&lt;li data-ke-style=&quot;style2&quot;&gt;eslint-config-next: Next.js 전용 린트 플러그인&lt;/li&gt;
&lt;li data-ke-style=&quot;style2&quot;&gt;eslint-plugin-react: 리액트 전용 플러그인&lt;/li&gt;
&lt;li data-ke-style=&quot;style2&quot;&gt;eslint-plugin-prettier: 린트 위에 사용할 프리티어 플러그인&lt;/li&gt;
&lt;li data-ke-style=&quot;style2&quot;&gt;eslint-config-prettier: 린트 설정과 중복되는 부분이 있으면 프리티어 룰에서 제외하는 플러그인&lt;/li&gt;
&lt;li data-ke-style=&quot;style2&quot;&gt;@typescript-eslint/eslint-plugin: : 타입스크립트 전용 린트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 @typescript-eslint/eslint-plugin을 사용 할 려면, eslintrc 파일의 plugins 배열에 해당 모듈에서 제공하는 @typescript-eslint를 추가하고, 다른 모듈도 같이 쓸거면 배열에 같이 추가하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인을 추가할 때,&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp; eslint-plugin-&amp;nbsp;&lt;/span&gt; 접두사를 생략 할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;rules&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 lint rule을 적용합니다.&amp;nbsp;extends로 자동설정된 rules 중에 특정 rule을 끄거나 변경하여 설정을 바꿀 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 방법으로 설정해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;off&quot; 또는 0: 규칙을 사용하지 않음&lt;/li&gt;
&lt;li&gt;&quot;warn&quot; 또는 1: 규칙을 경고로 사용&lt;/li&gt;
&lt;li&gt;&quot;error&quot; 또는 2: 규칙을 오류로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규칙에 추가 옵션이 있는 경우에는 배열 리터럴 구문을 사용하여 지정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인에서 규칙을 지정할 때는&lt;span style=&quot;background-color: #dddddd;&quot;&gt; eslint-plugin- &lt;/span&gt;를 반드시 생략해야 합니다. ESLint는 내부적으로 접두가 없이 이름을 사용하여 규칙을 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ignorePatterns&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ignorePatterns 필드 또는 eslintignore 파일을 작성하여 파일 및 디텍토리를 제외하도록 지정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그외 옵션들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;processor&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 형식의 파일로 부터 Javascript 코드를 추출해내고, 추출한 코드를 대상으로 Lint를 수행하는 전처리기와 후처리기를 작성하는 용도로 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1665566983529&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;plugins&quot;: [a-plugin],
&quot;processor&quot;: &quot;a-plugin/a-processor&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많이 사용되는 processor로 eslint-plugin-markdown은 eslint 의 preprocessor 와 postprocessor 를 구현하여 markdown 문서와 문서의 Javascript 코드블록에 대해서 정적분석을 수행하고 린팅을 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;env&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사전 정의된 전역 변수 사용을 정의 합니다. 자주 사용되는 설정으로는 browser, node 가 있습니다. (사전 정의된 전역변수는 공식 문서에서 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1665567271149&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;env&quot;: {
  &quot;browser&quot;: true,
  &quot;node&quot;: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;browser, node 설정을 하지 않을 경우 console, require 같은 사전 정의된 전역변수 환경에 있는 static 메서드를 인식할 수 없어서 에러가 발생합니다. 이외에도 선언되지 않은 전역변수를 사용하는 경우 ESLint 경고가 발생하지 않도록, globals 를 이용하여 사용자 전역 변수를 추가할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1665567466056&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;globals&quot;: {
  &quot;$&quot;: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# &lt;a href=&quot;https://eslint.org/play/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ESLint 데모&lt;/a&gt;를 사용하면 UI를 이용해서 ESLint 설정을 테스트 해볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Prettier Setting&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prettier를 세팅하는 방법은 2가지가 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;1. 별도의 Prittier 관련 플러그인을 npm, yarn으로 설치하지 않고 VSCode의 extiension을 설치&lt;br /&gt;2. Prettier 플러그인을 직접 설치 후 eslintrc에 세팅&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 방법은 VSCode 에디터 자체에 prettier rule 을 세팅하는 것입니다. 따라서 내 환경의 VSCode에서만 해당 Prettier 방식이 적용 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 방법은 프로젝트 자체에 prettier rule 을 세팅하는 것으로, 해당 프로젝트를 다른 환경에서 돌려도 동일하게 prettier rule을 적용해서 사용할 수 있는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;VSCode의 Prettier extiension 설치 (Prettier - Code formatter)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Preitt&lt;span&gt;ier - Code fomatter 는 설치하는 순간 바로 적용됩니다. 상세한 설정 방식은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://code.visualstudio.com/docs/getstarted/settings&quot;&gt;docs&lt;/a&gt;를 참고 하시면 자세히 안내되어 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-13 오후 5.53.02.png&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;1028&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7Re4k/btrOxnMuh22/iKnKFjmTc9nJeRIhBQhLOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7Re4k/btrOxnMuh22/iKnKFjmTc9nJeRIhBQhLOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7Re4k/btrOxnMuh22/iKnKFjmTc9nJeRIhBQhLOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7Re4k%2FbtrOxnMuh22%2FiKnKFjmTc9nJeRIhBQhLOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1636&quot; height=&quot;1028&quot; data-filename=&quot;스크린샷 2022-10-13 오후 5.53.02.png&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;1028&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cmd + shift + p 를 눌러서 검색 창에 Open User Setting으로 들어가서 prettier를 검색하면, 설정할수 있는 리스트가 나타납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VSCose Settings 는 현재 PC의 VSCode 환경 세팅이기 때문에 VSCode Extension으로 설치한 Prettier 플러그인에만 적용이 가능하고, npm 이나 yarn 으로 직접 설치한 prettier 플러그인에는 적용되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접설치 했다면 &lt;b&gt;'반드시 .prettierrc'&lt;/b&gt; 파일을 이용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;적용 우선 순위&lt;/b&gt;&lt;br /&gt;VSCode Extension 으로 설치 한 경우에는 VSCode Settings 와 .prettierrc 파일 둘다에서 설정이 가능하지만, .prettierrc 파일이 있으면 VSCode Settings의 설정은 무시되고 .prettierrc&amp;nbsp;파일의 룰이 우선 적용됩니다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;직접 설치 (npm 또는 Yarn)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Prettier 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1665639031560&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm install -D prettier
// or
$ yarn add -D prettier&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 설정 방식은 &lt;a href=&quot;https://prettier.io/docs/en/install.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Prettier Docs&lt;/a&gt; 를 참고 하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;자동 저장 Setting&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장 시 자동으로 린트를 잡아주는 기능은 VSCode 가 동작하는 기능입니다.&amp;nbsp; 따라서 VSCode Settings 에서 설정을 해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cmd + shift + p를 누르고 Open Settins.json 을 선택하면 Settings 설정이 JSON 형식으로 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 자동 저장 기능을 설정하는 옵션이 두가지가 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;editor.codeActionsOnSave&lt;/h4&gt;
&lt;pre id=&quot;code_1665639739020&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;editor.codeActionsOnSave&quot;: {
  &quot;source.fixAll.eslint&quot;: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이밍에서 알 수 있듯이 Save 할 때 코드를 동작하고, 코드 단에서 eslint rule 에 의해 error 로 판단되는 부분들을 lint rule 에 맞게 알아서 수정을 해주는 설정입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;editor.formatOnSave&lt;/h4&gt;
&lt;pre id=&quot;code_1665639895441&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;editor.formatOnSave&quot;: true,
&quot;editor.defaultFormatter&quot;: &quot;esbenp.prettier-vscode&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설정은 Save 할때 VSCode extension으로 설정된 Prettier 환경에 맞게 코드를 수정해주는 옵션입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'editor.codeActionsOnSave'와 다르게 ESLint 에러를 보고 수정하는 것이 아니라 에디터에 있는 텍스트들을 prettier rule에 맞게 정리해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Reference&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/ESLint&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ko.wikipedia.org/wiki/ESLint&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://helloinyong.tistory.com/325&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://helloinyong.tistory.com/325&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@kyusung/eslint-config-2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@kyusung/eslint-config-2&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Setting</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/26</guid>
      <comments>https://mishka.tistory.com/26#entry26comment</comments>
      <pubDate>Thu, 13 Oct 2022 15:30:33 +0900</pubDate>
    </item>
    <item>
      <title>이것만 알고가자 피그마 (feat. 개발자)</title>
      <link>https://mishka.tistory.com/25</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;피그마(Figma)는?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클라우드 기반으로 웹 브라우저로 동작하는&lt;b&gt; UI 디자인 툴&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피그마는 웹 브라우저 기반이기때문에 작업 환경에 대한 걱정 없이 사용할수 있습니다. 다시 말하자면 설치할 필요가 없고 운영체제와 상관 없이 사용할 수 있다는 장점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 피그마는 디자인이 클라우드에 있고 고유한 URL 이 존재하기 때문에 개발자가 항상 최신 버전의 디자인을 빠르게 확인 할 수 있고(사실 개발자 뿐만 아니라 사용하는 사용자 모두) 내장된 여러가지 기능을 통해 디자이너와 개발자 사이에 커뮤니케이션을 원할하게 해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;The design lives in the cloud and has a unique URL, so it serves as the source of truth for the entire team. -&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://www.figma.com/blog/under-the-hood-of-figmas-infrastructure/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Under the hood of Figma&amp;rsquo;s infrastructure&lt;/a&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개발자 핸드오프 (Handoff)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핸드오프란?&lt;/b&gt; 디자인 단계에서 개발 단계로 전달하는 과정을 뜻합니다. 피그마가 인기 있는 이유 중의 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 디자인 툴들은 핸드오프 과정에서 많은 작업들이 필요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인팀은 개발팀에게 대량의 디자인 이미지와 추출한 애셋파일 그리고 여러 페이지의 워드 문서를 첨부해서 공유 했습니다. 거기에 디자인 파일에 접근하기 위해 비싼 디자인 소프트웨어의 라이센스가 필요했습니다. 그리고 디자인이 새롭게 업데이트 되면 관련된 모든 사람에게 변경 내용을 공유해야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 피그마 이전에 제플린, 인비전 같은 툴의 발전으로 이전보다 과정이 간소화 되고, 개발자들이 디자인파일에 대해 더 좋은 접근 권한을 가지게 되었습니다. 사용자들이 최신 버전의 디자인을 쉽게 찾을수 있게 되었지만 디자이너들은 아직도 별도의 툴을 써야 했고 최신 버전을 맞추기 위해 추가적인 작업이 필요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 피그마는 피그마 하나로 전체 디자인, 이미지 파일, 폰트, 간격, 사용자 인터렉션 등 많은 것들을 전달 할 수가 있습니다. 또한 코멘트를 주고 받을수도 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;피그마 더 알아보기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 피그마를 접하게 되면 브라우저로 열지, 데스크탑 용 앱을 다운로드 할지 선택 할 수 있습니다. (윈도우용, 맥OS용 모두 지원하고 있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데스크탑용 앱은 네이티브로 개발된 것은 아니고 크로스 플랫폼을 지원하는 일렉트론 앱 입니다. 브라우저와 데스크탑 버전의 기능은 데스크탑 앱은 로컬 폰트 지원이 내장되어 있는 반면 브라우전 버전은 로컬 폰트를 사용하기 전에 피그마 폰트 도우미(&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;Figma Font Helper&lt;/span&gt;)를 설치 해야 한다는 것을 제외하면 대부분 동일합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피그마의 인터페이스는 크게 세 부분으로 나뉘어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽에는 레이어와 에셋, 파일의 페이지를 볼수 있는&lt;b&gt; '사이드바 영역'&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가운데는 디자인의 모든 요소가 놓이는 &lt;b&gt;'캔버스 영역'&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽에는 파일요소에 대한 정보를 볼수 있는 &lt;b&gt;'툴바 영역'&lt;/b&gt;으로 나눌 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일은 여러개의 페이지로 구성 될 수 있으면 모든 페이지는 한 개의 캔버스로 구성됩니다. 디자이너의 따라 종종 디자인 시스템, 아이콘, 에셋파일들을 별도의 페이지로 만들어서 내용을 정리할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기본사용 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 열면 캔버스 영역에 프레임의 맞게 가장 크게 확대된 비율로 디자인이 보입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화면 확대/축소는&lt;span style=&quot;background-color: #dddddd;&quot;&gt; &lt;span style=&quot;color: #222222;&quot;&gt;Cmd ⌘&lt;/span&gt;(ctrl) + 마우스 휠&lt;/span&gt;을 하거나 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;+&amp;nbsp;&lt;/span&gt; 나&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; -&amp;nbsp;&lt;/span&gt; 키를 눌러서 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;화면 이동은 스페이스바 + 마우스 드래그로 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;빠른 확대는 단일 프레임이나 요소를 선택한 상태에서&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; Shift + 2 &lt;/span&gt;를 누르면 됩니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;캔버스에 모든 요소가 보이도록 하는 빠른 전체화면은&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; Shift + 1 &lt;/span&gt;을 누르면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그외 단축키 들은&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; Ctrl + Shift + ?&amp;nbsp;&lt;/span&gt; 를 눌러서 확인 할 수 있다. 좋은점은 내가 사용했던 단축키들이 불이 들어와서 처음 사용해보지 않은 단축키들을 알아볼 수 있다는 점이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이미지 추출&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222;&quot;&gt;피그마에서는 다양한 파일 형식으로 이미지를 추출 할 수 있다. 원하는 요소를 선택하면 오른쪽 툴바 영역에서 요소에 대한 정보들이 뜹니다. 그 중 아래에 있는 내보내기(export) 버튼을 통해서 저장 할수 있습니다. 또한 이미지를 배수로 저장할 수도 있고, 파일 유형을 선택 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;원하는 요소를 오른쪽 클릭해서 빠르게 추출 할 수 도 있고, 오른쪽 클릭 후 표시되는 Copy/Paste 메뉴에서 선택해서 이미지나 SVG 코드로도 복사할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추출 가능한 모든 이미지를 한 번에 내보낼 수도 있습니다. 왼쪽 상단의 피그마 메인메뉴 &amp;gt; File &amp;gt; Export 를 클릭하거나&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;Shift + &lt;span style=&quot;color: #222222;&quot;&gt;Cmd ⌘ + e&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #222222;&quot;&gt; 누르면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222;&quot;&gt;그럼 파일에서 추출할 애셋으로 표시한 모든 애셋 항목이 목록으로 표시됩니다. 표시된 목록의 이미지 파일 유형과 수치를 확인 하고 필요없는 파일은 제외 할수 있으며, 썸네일 이미지에 마우스를 올려놓으면 저장 할 때의 파일명과 유형을 확인 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #222222;&quot;&gt;디자인 요소 정보 확인&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222;&quot;&gt;디자인 요소를 클릭하면 최상위 단계의 요소만 선택됩니다. 특정 레이어를 선택하려면&amp;nbsp; &lt;span style=&quot;background-color: #dddddd; color: #222222;&quot;&gt;&amp;nbsp;Cmd ⌘ &lt;/span&gt;&lt;span style=&quot;color: #222222;&quot;&gt;를 누른 상태에서 클릭하거나 요소를 오른쪽 클릭해서 모든 중첩된 레이어를 볼 수 있는 메뉴를 열어 원하는 레이어를 선택 할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222;&quot;&gt;&lt;span style=&quot;color: #222222;&quot;&gt;만일 요소를 더블 클릭하면 더블 클릭할 때마다 한 단계 아래의 요소가 선택됩니다. 원하는 항목을 선택할 때까지 계속 들어갈 수 있습니다. 요소를 선택하면 오른쪽 툴바영역에서 &lt;b&gt;Inspect&lt;/b&gt;에 &lt;b&gt;Code&lt;/b&gt;에서 선택한 요소에 대한 CSS 정보를 볼 수 있습니다. CSS 정보는 자동으로 생성되는 것으로 표시되는 정보가 정확하지 않을 수 있으니 가이드 정도로 이용하는 것이 좋습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #222222;&quot;&gt;요소들의 간격 확인&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222;&quot;&gt;디자인 요소와 요소 사이의 거리를 측정하고 싶을때는 시작점의 요소를 선택한 후&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; Option(Alt)&amp;nbsp;&lt;/span&gt; 키를 누른 상태로 마우스를 다은 요소에 올리면 치수를 측정할 수 있습니다.&lt;/span&gt;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;피그마는 요소 사이의 거리를 빨간 선으로 표시하고 거리를 픽셀 단위로 보여줍니다. 다른 그룹 또는 프레임의 특정 하위 요소 까지의 거리를 측정하려면 해당 내부 요소를 선택하는 것과 마찬가지로 &lt;span style=&quot;background-color: #dddddd; color: #222222;&quot;&gt;Cmd ⌘ &lt;/span&gt;&lt;span style=&quot;color: #222222;&quot;&gt;키를 누르면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #222222;&quot;&gt;코멘트 기능&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222;&quot;&gt;코멘트 기능을 활용하여 디자이너와 소통 할수 있습니다. 상단 툴바에 있는 채팅버플 모양의 아이콘을 클릭하거나&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; C&amp;nbsp;&lt;/span&gt; 를 눌러서 코멘트를 달수 있습니다. 클릭한 상태에서 드래그 하면 영역을 지정해서 코멘트를 달수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222;&quot;&gt;코멘트를 모두 작성하고 나면&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; V &lt;/span&gt;&amp;nbsp;또는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;Esc&amp;nbsp;&lt;/span&gt; 를 눌러서 다시 일반 커서 모드로 돌아오면 됩니다. 댓글을 달때 알림을 원한다면 &lt;b&gt;@&lt;/b&gt; 를 활용해서 멘션을 달아주면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222;&quot;&gt;코멘트를 읽고 더 이상 필요없거나 구현이 완료되면 &lt;b&gt;완료(resolve) 표시&lt;/b&gt;를 눌러서 코멘트가 쌓이는 것을 방지 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #222222;&quot;&gt;Referense&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://help.figma.com/hc/en-us&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://help.figma.com/hc/en-us&lt;/a&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://webactually.com/2021/01/18/%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-%ED%94%BC%EA%B7%B8%EB%A7%88%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83/&quot;&gt;개발자가 알아야 할 피그마의 모든 것 - Webactually&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://onlydev.tistory.com/142&quot;&gt;피그마 사용법과 협업하기(개발자 시점)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming</category>
      <category>figma</category>
      <category>개발자</category>
      <category>피그마</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/25</guid>
      <comments>https://mishka.tistory.com/25#entry25comment</comments>
      <pubDate>Mon, 3 Oct 2022 22:28:57 +0900</pubDate>
    </item>
    <item>
      <title>이름 정의 규칙 (Naming Convention)</title>
      <link>https://mishka.tistory.com/24</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. CamelCase&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-1. UpperCamelCase&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 식별자의 첫번째 문자를 대문자로 표기 해야하며, 만약 여러 단어가 포함되어 있는 경우, 각 단어를 구분짓기 위해 마찬가지로 대문자로 표기한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규칙자체 이름에서 보여지듯 식별자의 첫단어는 대문자(U)로 지정되어 있고, 그 뒤에 Camel, Case 같이 서로 상이한 단어들을 구분짓기 위해 각 단어의 시작을 대문자로 표기한다. 나머지 문자는 모두 소문자로 표기한다.&lt;/p&gt;
&lt;pre id=&quot;code_1653975927605&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//예시
UserName
BackgroundColor&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-2. lowerCamelCase&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 식별자의 첫번째 문자는 소문자로 표기해야 하며, 나머지 규칙은 UpperCamelCase 와 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규칙자체 이름에서 보여지듯 식별자의 첫 단어를 소문자(l)f로 지정하였고, 나머지 규칙은 UpperCamelCase 와 동일하다.&lt;/p&gt;
&lt;pre id=&quot;code_1653976089859&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//예시
userName
backgroundColor&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;kebab-case&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어와 단어 사이에 하이픈(-)을 사용하여 구분한다. 케밥이 꼬챙이에 꽃힌 모습과 비슷하다하여 케밥케이스라 명명되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1653976534007&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예시
user-name
background-color&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Snake_Case&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어와 단어 사이에 언더바(_)를 사용하여 구분한다. 언더바가 들어있는 표현 방식이 뱀처럼 생겼다고 하여 스네이크 케이스라 명명되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1653981953611&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 예시
user_name
background_color&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Programming</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/24</guid>
      <comments>https://mishka.tistory.com/24#entry24comment</comments>
      <pubDate>Tue, 31 May 2022 14:48:47 +0900</pubDate>
    </item>
    <item>
      <title>Pinia - Vuex 를 대체 할 수 있을까??</title>
      <link>https://mishka.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;Pinia&amp;nbsp;&lt;/b&gt;&lt;/span&gt; 는 Vue의 새로운 상태 관리 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pinia 는 2019년 11월경 Composition API 를 사용하여 Store for Vue가 어떻게 생겼는지 재설계하기 위한 실험으로 시작되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이후 초기 원칙은 동일하지만&amp;nbsp; Pinia 는 Vue2와 Vue3 모두에서 작동하며 Composition API 를 사용할 필요는 없습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VueConf Toronton 2021 에서 Vue의 창시자 Evan You가 상태 관리 플러그인으로 vuex가 아닌 Pinia를 추천&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Pinia([piːnjʌ] 영어로 &quot;peenya&quot; 로 발음됨)&amp;nbsp;는 유효한 패키지 이름인 pi&amp;ntilde;a(스페인어로 파인애플) 에 가장 가까운 단어입니다.&amp;nbsp;&lt;br /&gt;파인애플은 실제로 여러개의 과일을 만들기 위해 결합된 개별 꽃의 그룹입니다. Store 와 마찬가지로 하나하나가 개별적으로 태어나지만 격국에는 모두 연결되어 있음을 의미합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Pinia를 사용해야 하는 이유?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pinia 는 Vue의 구성 요소/페이지 간에 상태를 공유할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Composition API 에 익숙하다면 아래와 같은 형태의 간단한 내보내기로 전역 상태를 공유 할수 있다고 생각 할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1652330683092&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const state = reative({})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 단일 페이지 애플리케이션에 해당하지만 서버 측에서 랜더링 되는 경우 애플리케이션을 보안 취약성에 노출 시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 작은 단일 페이지 어플리케이션에서도 Pinia 를 사용하면 많은 것을 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Mutations 이 없습니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태변경을 위해 mutations 를 정의하고 commit 하는 과정이 필요없습니다.&lt;/li&gt;
&lt;li&gt;vue 인스턴스의 state 값을 변경 할 때 처름 read/write 하면됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Typescipt 를 지원&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Typescipt 를 통한 유형 추론을 최대한 활용할 수 있게 만들어져 있어서 복잡한 래핑을 하지 않아도 된다.&lt;/li&gt;
&lt;li&gt;타입을 별도로 지정해주지 않아도 타입 추론이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Namespace modules 없음&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Devtools 지원&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버 측 렌더링 지원&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Pinia 와 Vuex의 다른점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&lt;b&gt;Pinia&lt;/b&gt;&amp;nbsp;&lt;/span&gt; 의 Store 선언 구문&lt;/p&gt;
&lt;pre id=&quot;code_1652344693229&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () =&amp;gt; ({ count: 0 }),
  actions: {
    increment() {
      this.count++
    }
  },
  getters: {
    doubleCount(state) {
      return state.count * 2
    }
  }
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vuex 와의 가장 큰 차이점이라고 할 수 있는 것은&amp;nbsp; &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;&lt;b&gt;mutations&lt;/b&gt;&amp;nbsp;&lt;/span&gt; 의 유무이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vuex 에 존재하던 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;mutations&lt;/b&gt;&amp;nbsp;&lt;/span&gt; 선언 필요 없이 &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;actions&amp;nbsp;&lt;/span&gt;&lt;/b&gt; 에서 값을 변화 시킬 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 때는 아래와 같은 형태로 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1652345282647&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useCounterStore } from '@/stores/counter'

export default {
  setup() {
    const counter = useCounterStore()

    const onClickAdd = () =&amp;gt; {
      // actions 를 직접선언
      counter.increment()
      // 이런식으로 Composition API 사용하는식으로 사용도 가능
      counter.count++
      // 내부 API 를 사용 가능하고 (with autocompletion ✨)
      counter.$patch({ count: counter.count + 1 })
      
    }
    
    return {
      onClickAdd,
      doubleValue: computed(() =&amp;gt; counter.doubleCount),
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;Composition API&lt;/span&gt;&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에서 사용형태와 비슷하며 쉽고 간편하게 Store 에 접근이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;Vuex&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에서 Store 정의하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;mutation&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;actions&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; getters&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&amp;nbsp; 순서대로 작성하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;dispatch&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 이용하여 개발하던 것보다 훨씬 간편해졌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 computed 선언을 하지 않고 구조분해할당을 하고 싶다면&lt;/p&gt;
&lt;pre id=&quot;code_1652680278980&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.vue
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

export default {
  setup() {
    const counter = useCounterStore()
    const { doubleCount } = storeToRefs(counter)

    const onClickAdd = () =&amp;gt; {
      counter.count++
    }
    
    return {
      onClickAdd,
      doubleCount
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 하면 쉽고 간편하게 사용할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Vue3의 반응형 시스템 내부에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment&quot;&gt;구조분해할당&lt;/a&gt;을 사용하는 경우 반응형이 작동하지 않기 때문에 Vue3 에서 구조분해할당을 사용하시 위해 반응형 객체를 toRefs 로 묶어서 반응형을 유지할 수 있도록 지원한다.&lt;br /&gt;&lt;a href=&quot;https://v3.ko.vuejs.org/guide/reactivity-fundamentals.html#%E1%84%87%E1%85%A1%E1%86%AB%E1%84%8B%E1%85%B3%E1%86%BC%E1%84%92%E1%85%A7%E1%86%BC-%E1%84%89%E1%85%A1%E1%86%BC%E1%84%90%E1%85%A2-%E1%84%80%E1%85%AE%E1%84%8C%E1%85%A9-%E1%84%87%E1%85%AE%E1%86%AB%E1%84%92%E1%85%A2%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5-destructuring&quot;&gt;참조&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp;Composition API &lt;/span&gt;&lt;/b&gt;&amp;nbsp;에 익숙 하다면 아래와 같은 형태로 사용할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1652680278981&quot; class=&quot;typescript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// stores/counter.js
export const useCounterStore = defineStore('counter', () =&amp;gt; {
  const count = ref(0)
  function increment() {
    count.value++
  }
  const doubleCount = computed(() =&amp;gt; count.value * 2)

  return { count, increment, doubleCount }
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 상태관리 라이브러리인 vuex 와 비교를 해보았을때 Pinia 가 매력적인 몇가지 사항들이 있는데, mutations 가 없어진 것과 Typescript 지원 그리고 devtools 의 공식 지원이다. 이번 새로운 프로젝트에 Pinia 도입 해서 사용해 보았는데 Composition API, Typescript 과의 궁합도 잘맞았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vuex 와 동시 사용도 가능 하다고 하니 다들 한번 사용해 보시면 좋을것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;참조&lt;br /&gt;&lt;/b&gt;&lt;a href=&quot;https://pinia.vuejs.org/introduction.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;피니아&lt;/a&gt; &lt;a href=&quot;https://pinia.vuejs.org/introduction.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://velog.io/@eggplantiny/Pinia-Vuex-%EB%A5%BC-%EB%8C%80%EC%B2%B4%ED%95%A0-%EC%83%88%EB%A1%9C%EC%9A%B4-Store&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Pinia - Vuex 를 대체할 새로운 Store!&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://intrepidgeeks.com/tutorial/pinia&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Pinia&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://blog.logrocket.com/pinia-vs-vuex/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Pinia vs. Vuex: Which state management library is best for Vue?&lt;/a&gt;&lt;/p&gt;</description>
      <category>Vue</category>
      <category>composition api</category>
      <category>devtools</category>
      <category>mutations</category>
      <category>pinia</category>
      <category>typescript</category>
      <category>Vuex</category>
      <category>피니아</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/23</guid>
      <comments>https://mishka.tistory.com/23#entry23comment</comments>
      <pubDate>Mon, 16 May 2022 14:51:34 +0900</pubDate>
    </item>
    <item>
      <title>Vue에서 Moment.js 사용하기(vue-moment)</title>
      <link>https://mishka.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 날짜 또는 시간을 다루기 위해서는 기본적으로 Date 객체를 사용합니다.&lt;br /&gt;이를보다 편리하고 간단하게 활용할수 있는 라이브러리에 대해 정리해 보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Moment.js는 Date 형식의 데이터 파싱,검증,조작 등을 간편하게 할수 있게 해주는 유용한 라이브러리이다.&lt;br /&gt;이를 Vue에서 사용하기 쉽도록 수정 배포된 버전이 vue-moment 이다.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설치&lt;/h2&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;npm install vue-moment&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Vue.use(require('vue-moment'))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;require를 사용하지 않는다면&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;import VueMoment from 'vue-moment'
Vue.use(VueMoment)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위과 같이 설치하고 나면 moment를 호출하여 사용할 수 있다.&lt;br /&gt;&lt;code&gt;Vue.$moment&lt;/code&gt; 의 형태로 사용한다. 스크립트에서는 &lt;code&gt;this.$moment&lt;/code&gt; 탬플릿에서는 &lt;code&gt;$moment&lt;/code&gt; 의 형태로 사용한다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;// 현재시간
{{$moment().format('YYYY-MM-DD')}} 

// 데이터 입력 시간
{{$moment(time).format('YYYY-MM-DD')}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;필터링(filtering) 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;format(포맷팅)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;format 을 사용해서 원하는 형태의 시간정보를 생성할수 있다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;moment().format();                                // &quot;2014-09-08T08:02:17-05:00&quot; (ISO 8601, no fractional seconds)
moment().format('YYYY-MM-DD');                    // &quot;2014-09-08&quot;
moment().format('dddd, MMMM Do YYYY, h:mm:ss a'); // &quot;Sunday, February 14th 2010, 3:25:50 pm&quot;
moment().format('ddd, hA');                       // &quot;Sun, 3PM&quot;
moment().format('[Today is] dddd');               // &quot;Today is Sunday&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://momentjs.com/docs/#/displaying/format/&quot;&gt;더 다양한 방법 보기 - format&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;add(더하기)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 시간에 원하는 시간을 추가하여 기존 시간을 변경할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;this.$moment(someDate).add(7, 'days')  // 7일이 추가된 시간&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://momentjs.com/docs/#/manipulating/add/&quot;&gt;더 다양한 방법 보기 - add&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;subtract(빼기)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 시간에 원하는 시간을 빼서 기존 시간을 변경할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;this.$moment(someDate).subtract(7, 'days')  // 7일 뺀 시간&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://momentjs.com/docs/#/manipulating/subtract/&quot;&gt;더 다양한 방법 보기 - subtract&lt;/a&gt;&lt;/p&gt;</description>
      <category>Vue</category>
      <category>Moment</category>
      <category>Vue</category>
      <category>Vue.js</category>
      <category>시간</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/21</guid>
      <comments>https://mishka.tistory.com/21#entry21comment</comments>
      <pubDate>Fri, 27 Aug 2021 08:45:50 +0900</pubDate>
    </item>
    <item>
      <title>늦어버린 2020년 돌아보기</title>
      <link>https://mishka.tistory.com/22</link>
      <description>&lt;p&gt;이런 저런 이유(다 핑계일뿐이다 ㅠ)로 회고를 미루고 미뤘더니 지난 회고를 쓰고 1년 6개월 이라는 시간이 지나 버렸다.&lt;br&gt;더 늦기전에 되돌아보고자 한다.&lt;/p&gt;
&lt;!-- more --&gt;

&lt;h2&gt;프론트엔드 개발자로의 시작&lt;/h2&gt;
&lt;p&gt;2019년 LSP를 퇴사하고 결론적으론 퍼블리셔에서 프론트엔드 개발자로의 전직을 성공했다. 퍼블리셔도 물론 재미있는 직종이었고 업무의 방향이&lt;br&gt;확실하긴 했지만 그동안 느낌 점은 퍼블리셔로는 한계가 느껴졌다. 그래서 프로트엔드 개발자로의 전향을 꿈꾸도 이것 저것 공부를 위해 노력을 했다.&lt;br&gt;배운것들을 정리하고 공유하기 위해 시작한 기술블로그도  그 노력의 일환이었다. 노력이 통하였는지 다행히 전 직장 퇴사가 결정되고 3곳의 회사에서&lt;br&gt;러브콜이 와서 행복한 고민에 빠졌었다. 그중에 선택한곳이 현재 재직중인 &amp;#39;(주)테이블링&amp;#39; 이다. 입사할때는 (주)밀랑 이었는데 중간에 사명이 변경되었다.&lt;br&gt;테이블링은 현재 외식사업의 전반적인 서비스를 관리 하는 곳이다. 매장의 예약/대기 관리 시스템과 유저들이 외식을 간편하게 이용할 수 있도록 불편함을&lt;br&gt;줄여주는 서비스를 운영하고 있다.&lt;/p&gt;
&lt;p&gt;면접에서 대표님의 비전에 대한 열망이 정말 좋았고 회사를 운영하는 방식 같은 것들이 함께 꿈을 꾸기에 충분한 느낌을 받았다.&lt;br&gt;또한 부족한 개발 실력을 같이 할 수 있는 사수도 있어서 큰 고민없이 회사를 선택할 수 있었다. &lt;/p&gt;
&lt;p&gt;처음 겪어보는 프론트엔드 개발 환경은 많이 낯설었다. 스터디를 통해서 큰 흐름은 배웠지만 스터디와 실전의 차이는 명확했기에&lt;br&gt;약간의 두려움도 생겼다. 옆에서 사수 친절히 알려주셔서 다행히 어려움을 극복할 수 있었다.&lt;/p&gt;
&lt;h2&gt;공부는 죽을때 까지...&lt;/h2&gt;
&lt;p&gt;프론트엔드 개발자로 취업을 성공했지만 부족한 점을 많이 느끼고 있었기에 공부를 멈출수는 없었다.&lt;br&gt;아직 배워야 할것들이 많았고 처리해야하는 일은 나를 기다려주지 않았다. 업무 후에 여가시간을 모두 공부하는 시간에 투자 하였다.&lt;br&gt;기본적인 JavaScript 뿐만아니라. TypeScript, React 등등 여러가지 스터디들을 순차적으로 또 산발적으로 중간중간 필요한 것들을&lt;br&gt;찾아보면서 부족한 부분들을 보충하는 시간들을 가졌다. 개발자를 하면서 &amp;#39;공부는 늙어 죽을 때까지 해도 다 못한다&amp;#39;는 속담이 더 크게 와닿는것 같다.&lt;br&gt;배울수록 새롭게 알아가는 것도 많고 이전에 알고 있던 것들도 새롭게 다가오는 부분들도 많아서 좋은 시간들이었다. &lt;/p&gt;
&lt;h2&gt;사이드 프로젝트팀 &amp;#39;Awesome&amp;#39;&lt;/h2&gt;
&lt;p&gt;여러가지 스터디를 하던 도중에 이론적인 부분뿐만 아니라 배운 것들을 실무적으로 적용시켜보자는 마음에 사이드 프로젝트에 관심을 가지고 알아보게 되었다.&lt;br&gt;그러던 중 이전에 같이 스터디 멤버의 추천을 받아서 현재 Awesome 팀에 합류하게 되었다. 제가 참여할때는 Awesome의 2번째 프로젝트의 시작이었는데&lt;br&gt;Awesome 1번째 프로젝트는 개발자분들이 없어서 기획과 디자인 단계에서 마무리한 프로젝트였고 다음에는 실제로 구현해보고자하는 니즈가 생겨서&lt;br&gt;개발자와 함께 프로젝트를 해보자하고 구하던 찰나에 시기가 맞물려 합류하게 되었다.&lt;br&gt;팀에 합류 했을때는 이미 코로나로 모임이 힘든 시점이라 오프라인 모임이 아닌 구글밋을 통한 온라인 모임위주로 프로젝트를 진행하였다.&lt;br&gt;현재 프로젝트는 기획 마무리 단계를 거쳐 디자인작업을 하고 있다.&lt;br&gt;이 프로젝트에 관한 회고는 따로 진행하는게 좋을것 같아 자세한 사항은 완성된 후에 정리해 보겠다. &lt;/p&gt;
&lt;p&gt;약 1년 6개월의 시간을 돌이켜 보면 많은것들을 한것 같기는 한데 또 돌이켜보면 부족한 부분들도 많이 보이는것 같아서 아쉬운 부분이 생긴다.&lt;br&gt;가장 큰 사건은 프로트엔드개발자로서 전직을 무사히 성공했고 성장해 나가고 있다는 데에서 스스로에서 박수를 보내고 싶다.&lt;br&gt;아직은 부족한 점이 많은 개발자 이지만 계속 성장해 나감으로써 스스로에게 부끄럽지 않은 모습이 되었으면 좋겠다.&lt;/p&gt;</description>
      <category>회고</category>
      <category>Awesome</category>
      <category>회고</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/22</guid>
      <comments>https://mishka.tistory.com/22#entry22comment</comments>
      <pubDate>Thu, 26 Aug 2021 16:44:37 +0900</pubDate>
    </item>
    <item>
      <title>Webstorm 과 jira 연동해서 사용하기</title>
      <link>https://mishka.tistory.com/20</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;webstorm을 사용하고 업무를 진행할 때 &lt;code&gt;jira&lt;/code&gt;를 사용하다보니 jira를 별도의 창으로 띄워놓고 이슈를 생성하고, webstorm에서 작업하다가 커밋하면서&lt;br /&gt;jira 티켓번호 생성하고 푸시한 다음에 티켓을 이동시켜주고 하는 반복작업이 많아졌습니다. 어떻게 보면 별거아닌 작업일 수 있지만 까먹게 되는 일이&lt;br /&gt;많고 바쁠때는 모아서 하기도 하다보니 이슈 트레킹 하는데 문제가 있어보여 찾아보다가 연동하는 방법을 찾아 정리해 보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단축키는 &lt;code&gt;Mac OS&lt;/code&gt;를 기준으로 정리하였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webstorm에서 &lt;code&gt;preferences &amp;gt; Tasks &amp;gt; Servers&lt;/code&gt; 로 이동합니다. 화면에서 &lt;b&gt;+&lt;/b&gt; 버튼을 클릭해서 &lt;b&gt;JIRA&lt;/b&gt;를 선택합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;723&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPsaUE/btrdkaPNu98/n8zLPtsuFdwbhMiVOJkMR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPsaUE/btrdkaPNu98/n8zLPtsuFdwbhMiVOJkMR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPsaUE/btrdkaPNu98/n8zLPtsuFdwbhMiVOJkMR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPsaUE%2FbtrdkaPNu98%2Fn8zLPtsuFdwbhMiVOJkMR1%2Fimg.png&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;723&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JIRA 외에 다른 이슈 트레커들도 연동해서 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JIRA 버튼을 누르면 설정창이 뜨는데 설정창에서 Server URL 에는 JIRA 주소를 입력합니다. 그리고 계정/비밀번호를 입력해 줍니다.&lt;br /&gt;저는 회사계정으로 입력했더니 비밀번호 입력하는 칸이 &lt;code&gt;API Token&lt;/code&gt; 을 입력하는 칸으로 변경되더라구요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;206&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YkqEo/btrdffq2Mxy/zXGQq58QDSTVBckIrT0Dvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YkqEo/btrdffq2Mxy/zXGQq58QDSTVBckIrT0Dvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YkqEo/btrdffq2Mxy/zXGQq58QDSTVBckIrT0Dvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYkqEo%2Fbtrdffq2Mxy%2FzXGQq58QDSTVBckIrT0Dvk%2Fimg.png&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;206&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 토큰 생성 방법은 atlassian 사이트를 참고 하셔서 발급 받으시면됩니다. -&lt;a href=&quot;https://confluence.atlassian.com/cloud/api-tokens-938839638.html&quot;&gt;사이트 링크&lt;/a&gt;&lt;br /&gt;API token은 한번 발급 받으면 다시 볼수 없으니 계속해서 사용하고 싶으시면 개인저장소에 저장해놓거나 잃어버리면 새로 발급 받아야합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Search 입력란은 Task 목록을 방식을 설정하는 방식을 이야기하는데 기본값으로 최신정렬순으로 되어있으니 필요하신분만 수정하시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;720&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmMf3z/btrdcOHKzg1/sTvoKZVBh9gSk5TZZcSqDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmMf3z/btrdcOHKzg1/sTvoKZVBh9gSk5TZZcSqDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmMf3z/btrdcOHKzg1/sTvoKZVBh9gSk5TZZcSqDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmMf3z%2FbtrdcOHKzg1%2FsTvoKZVBh9gSk5TZZcSqDk%2Fimg.png&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;720&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정이 끝나면 우측 하단에 있는 &lt;code&gt;Test&lt;/code&gt; 버튼을 클릭하여 테스트를 해볼수 있습니다. &lt;b&gt;Connection is successful&lt;/b&gt; 이라는 메세지가 나오면 됩니다.&lt;br /&gt;그리고 나서 Apply &amp;gt; OK 눌러주시면 설정은 끝났습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용해본 결과 현재(2020.03.30일 기준 2019.3 버전) webstorm에서는 JIRA 티켓 생성은 지원하고 있지 않았습니다.&lt;br /&gt;기존에 되어있는 티켓을 Task 가져오는게 아니라면 티켓 생성은 JIRA 사이트에서 해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Tools &amp;gt; Tasks &amp;amp; contexts &amp;gt; Open Task&lt;/code&gt; 로 &lt;b&gt;본인에게 할당된 티켓 목록&lt;/b&gt;을 볼 수 있습니다. 단축키는 &lt;code&gt;option+shift+n&lt;/code&gt; 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;217&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lWt22/btrdevVd7rR/wda0PoxkjOLm52tlIAuzj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lWt22/btrdevVd7rR/wda0PoxkjOLm52tlIAuzj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lWt22/btrdevVd7rR/wda0PoxkjOLm52tlIAuzj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlWt22%2FbtrdevVd7rR%2Fwda0PoxkjOLm52tlIAuzj1%2Fimg.png&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;217&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;img src=&quot;/img/webstorm/jira-ticket.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 위해 티켓을 별도로 하나 생성했습니다. FE-66 티켓을 선택해 주면 아래와 같은 창이 열립니다.&lt;br /&gt;update issue state는 티켓의 state를 원하는 상태로 변경할 수 있습니다. clear current context는 현재 편집기에 열려있는 모든 탭을 닫습니다.&lt;br /&gt;create branch 를 하면 원하는 브런치를 생성 할수도 있고, gitflow 메뉴를 이용하면 gitflow 정책대로 브런치를 생성 할 수도 있습니다.gitflow 정책에 대해서는 지난 블로그 내용을 참고 바랍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;669&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwWi2I/btrdcPfBLO6/obSysiKIYrU27VbvTfLqUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwWi2I/btrdcPfBLO6/obSysiKIYrU27VbvTfLqUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwWi2I/btrdcPfBLO6/obSysiKIYrU27VbvTfLqUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwWi2I%2FbtrdcPfBLO6%2FobSysiKIYrU27VbvTfLqUK%2Fimg.png&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;669&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발이 완료되면 커밋을 해줍니다. 단축키는 &lt;code&gt;command+k&lt;/code&gt; 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;791&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRZXlG/btrdkPdzYHO/wsmUTC0pKw07tURPHUuowK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRZXlG/btrdkPdzYHO/wsmUTC0pKw07tURPHUuowK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRZXlG/btrdkPdzYHO/wsmUTC0pKw07tURPHUuowK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRZXlG%2FbtrdkPdzYHO%2FwsmUTC0pKw07tURPHUuowK%2Fimg.png&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;791&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋 메세지는 자동생성되는데 기본 옵션으로 &lt;code&gt;티켓ID + 제목&lt;/code&gt; 으로 되어있고 설정을 변경할 수 있습니다.&lt;br /&gt;&lt;code&gt;Preferences &amp;gt; Tasks &amp;gt; Servers&lt;/code&gt; &lt;b&gt;Commit Message 탭&lt;/b&gt;을 클릭해서 원하는 규칙으로 수정 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;344&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJrbcn/btrdewGFcgT/DpXvGce9YCkzqopFrwI6i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJrbcn/btrdewGFcgT/DpXvGce9YCkzqopFrwI6i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJrbcn/btrdewGFcgT/DpXvGce9YCkzqopFrwI6i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJrbcn%2FbtrdewGFcgT%2FDpXvGce9YCkzqopFrwI6i0%2Fimg.png&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;344&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;티켓 상태도 상황에 맞게 변경 할 수 있습니다. 단축키는 &lt;code&gt;option+shift+w&lt;/code&gt; 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;215&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bROK8j/btrdeXqGrud/sTKR5EFXigTu1oUvKNj24K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bROK8j/btrdeXqGrud/sTKR5EFXigTu1oUvKNj24K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bROK8j/btrdeXqGrud/sTKR5EFXigTu1oUvKNj24K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbROK8j%2FbtrdeXqGrud%2FsTKR5EFXigTu1oUvKNj24K%2Fimg.png&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;215&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추가 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Task에서 생성해주는 브랜치 명은 티켓ID 로 자동할당됩니다. 생성 템플릿을 변경하려면&lt;br /&gt;&lt;code&gt;Preferences &amp;gt; Tasks&lt;/code&gt;에서 &lt;b&gt;Feature branch name format&lt;/b&gt;을 원하는 규칙으로 수정하면됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;718&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBwL6H/btrdd8lChpI/F5AuxSyiUfBHpkUBUrgQjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBwL6H/btrdd8lChpI/F5AuxSyiUfBHpkUBUrgQjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBwL6H/btrdd8lChpI/F5AuxSyiUfBHpkUBUrgQjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBwL6H%2Fbtrdd8lChpI%2FF5AuxSyiUfBHpkUBUrgQjk%2Fimg.png&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;718&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jojoldu.tistory.com/260&quot;&gt;IntelliJ를 JIRA와 연동해서 사용하기&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://www.jetbrains.com/help/idea/managing-tasks-and-context.html#&quot;&gt;IntelliJ IDEA&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://confluence.atlassian.com/cloud/api-tokens-938839638.html&quot;&gt;API tokens&lt;/a&gt;&lt;/p&gt;</description>
      <category>Setting</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/20</guid>
      <comments>https://mishka.tistory.com/20#entry20comment</comments>
      <pubDate>Thu, 26 Aug 2021 16:33:28 +0900</pubDate>
    </item>
    <item>
      <title>Webstorm에서 gitflow 사용하기</title>
      <link>https://mishka.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;vscode 에서 webstorm으로 갈아타면서 플러그인으로 사용하던 몇가지 기능들을 webstorm 에서도 사용해 보고 싶었습니다.&lt;br /&gt;그 중에 git 을 사용하다 보면 대부분의 경우 &lt;b&gt;&lt;a href=&quot;/2020/03/30/gitflow&quot;&gt;git-flow&lt;/a&gt;&lt;/b&gt; 를 따라서 작업을 진행하게 됩니다. 물론 cli 를 이용해서&lt;br /&gt;정책만을 따라 가며 작업할 수 있지만 번거로운 작업들도 있고 급할때는 까먹기도 하기 때문에 편하게 사용하는 방법을 찾아보았습니다.&lt;br /&gt;&lt;b&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/7315-git-flow-integration&quot;&gt;IntelliJ Git Flow Integration&lt;/a&gt;&lt;/b&gt;를 이용한 방법입니다.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;gitflow 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 전에 gitflow 를 사용할때는 별도로 gitflow 를 설치하지 않았었는데 webstorm에서 gitflow 를 사용하려면 &lt;code&gt;avh&lt;/code&gt; 버전으로 설치가 필요하다.&lt;br /&gt;mac OS의 경우 &lt;b&gt;&lt;a href=&quot;/2019/09/27/zsh-setting&quot;&gt;homebrew&lt;/a&gt;&lt;/b&gt; 를 통해 gitflow 를 설치해줍니다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;brew install git-flow-avh&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 OS 환경에서의 설치 방법은 &lt;a href=&quot;https://github.com/petervanderdoes/gitflow-avh/wiki/Installation&quot;&gt;링크&lt;/a&gt;를 참고하세요&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gitflow 를 설치하고 나면 preferences &amp;gt; plugins &amp;gt; marketplace 에서 &lt;code&gt;Git Flow Integration&lt;/code&gt; 를 검색하여 설치해 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;748&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbDJuP/btrdevnlxqf/TQrQummdW5aZxwk7BJSGtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbDJuP/btrdevnlxqf/TQrQummdW5aZxwk7BJSGtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbDJuP/btrdevnlxqf/TQrQummdW5aZxwk7BJSGtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbDJuP%2Fbtrdevnlxqf%2FTQrQummdW5aZxwk7BJSGtk%2Fimg.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;748&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webstorm을 재시작하면 설치가 완료됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인 설치 후 Webstorm 우측 하단을 보시면 아래그림과 같이 No Gitflow 를 보이시면 정상적으로 설치가 완료된것입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/webstorm/gitflow-nogitflow.png&quot; alt=&quot;&quot; /&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;303&quot; data-origin-height=&quot;147&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFFgeH/btrdewzOpmO/Z9QaYfmV9apaPJuWEwTLyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFFgeH/btrdewzOpmO/Z9QaYfmV9apaPJuWEwTLyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFFgeH/btrdewzOpmO/Z9QaYfmV9apaPJuWEwTLyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFFgeH%2FbtrdewzOpmO%2FZ9QaYfmV9apaPJuWEwTLyK%2Fimg.png&quot; data-origin-width=&quot;303&quot; data-origin-height=&quot;147&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;No Gitflow 를 클릭하셔셔 init Repo 를 선택하면 아래와 같은 설정화면이 나옵니다.&lt;/p&gt;
&lt;center&gt;&lt;/center&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;413&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RWbi6/btrdetXBKzt/KJ9BbAHUFml4YVKL0uQZRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RWbi6/btrdetXBKzt/KJ9BbAHUFml4YVKL0uQZRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RWbi6/btrdetXBKzt/KJ9BbAHUFml4YVKL0uQZRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRWbi6%2FbtrdetXBKzt%2FKJ9BbAHUFml4YVKL0uQZRk%2Fimg.png&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;413&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 옵션이 git-flow 규칙을 따라 설정되어있어서 똑같은 규칙으로 사용하시다면 별도의 수정없이 OK 눌러 주시면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 옵션을 설정하고 나면 No gitflow 에서 gitflow 로 변경되고 gitflow 를 클릭하면 원하시는 동작을 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;112&quot; data-origin-height=&quot;199&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eAgNLx/btrdiwlAqrs/lwNTRgXpjRHV27KulVSeGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eAgNLx/btrdiwlAqrs/lwNTRgXpjRHV27KulVSeGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eAgNLx/btrdiwlAqrs/lwNTRgXpjRHV27KulVSeGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeAgNLx%2FbtrdiwlAqrs%2FlwNTRgXpjRHV27KulVSeGk%2Fimg.png&quot; data-origin-width=&quot;112&quot; data-origin-height=&quot;199&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;img src=&quot;/img/webstorm/gitflow-start.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Start Feature를 눌러 feature 브런치를 생성하고 finish feature를 하면 자동으로 develop 브런치에 merge 가 이루어집니다.&lt;br /&gt;Git flow 전략에 따라 버튼만 선택해주면 모든 이벤트가 자동으로 진행됩니다. 별도로 브랜치를 체크아웃하면서 merge, remove, tag 등을 할 필요가 없어서 반복작업의 시간을 줄일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;merge의 기본옵션은 fast-forward 입니다. 이를 변경하여 merge도 하나의 커밋으로 취급하고 싶으신 분들은 prefereces &amp;gt; Other Settings &amp;gt;&lt;br /&gt;gitflow 에서 Do not fast-forward when merging, always create commit(--no-ff) 옵션에 체크 하시면됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;718&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUDZBr/btrdgSCmlYF/3jh3nfUuuczfnc11zsnRHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUDZBr/btrdgSCmlYF/3jh3nfUuuczfnc11zsnRHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUDZBr/btrdgSCmlYF/3jh3nfUuuczfnc11zsnRHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUDZBr%2FbtrdgSCmlYF%2F3jh3nfUuuczfnc11zsnRHK%2Fimg.png&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;718&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;img src=&quot;/img/webstorm/gitflow-fast-forward.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reference&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jojoldu.tistory.com/268&quot;&gt;Git Flow Integration으로 Git Flow 심플하게 운영하기&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/7315-git-flow-integration&quot;&gt;Git Flow Integration&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://k39335.tistory.com/82&quot;&gt;Gitflow로 branch를 관리하자!!&lt;/a&gt;&lt;/p&gt;</description>
      <category>Setting</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/19</guid>
      <comments>https://mishka.tistory.com/19#entry19comment</comments>
      <pubDate>Thu, 26 Aug 2021 16:31:03 +0900</pubDate>
    </item>
    <item>
      <title>gitflow 란? git-flow 를 사용한 브랜치 전략</title>
      <link>https://mishka.tistory.com/18</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;GitFlow?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃플로우(git-flow) 전략은 소프트웨어의 소스코드를 관리하고 출시하기 위한 &lt;b&gt;'브랜치 관리 전략(branch management strategy)'&lt;/b&gt;중 하나이다.&lt;br /&gt;git-flow 전략외에도 &lt;a href=&quot;https://guides.github.com/introduction/flow/&quot;&gt;github flow&lt;/a&gt; 와 &lt;a href=&quot;https://about.gitlab.com/blog/2014/09/29/gitlab-flow/&quot;&gt;gitlab flow&lt;/a&gt; 전략등도 있다. 각자에게 맞는 전략을 선택해서 사용하는게 가장 중요하다.&lt;br /&gt;git-flow 는 Vicent Driessen 이 제안한 git 의 workflow 디자인에 기반한 브랜칭 모델이다. git-flow 에서 사용하는 브랜치의 종류는 5가지이며,&lt;br /&gt;크게 항상 유지되는 메인브렌치(master, develop)와 일정 기간 유지되는 보조 브랜치(feature, realease, hotfix)로 나뉜다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Master - 제품으로 출시 되는 브랜치&lt;/li&gt;
&lt;li&gt;Develop - 다음 출시 버전을 개발하는 브랜치&lt;/li&gt;
&lt;li&gt;Feature - 기능을 개발하는 브랜치&lt;/li&gt;
&lt;li&gt;Realease - 이번 출시 버전을 준비하는 브랜치&lt;/li&gt;
&lt;li&gt;Hotfix - 출시 버전에서 발생한 버그를 수정하는 브랜치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 그림들 중에 역시 아래의 그림이 git-flow를 한눈에 이해하기에는 가장 좋은 것 같아서 가져왔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;1524&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BhhFg/btrdjed3S0c/kJWx80BpJJpE07dLfdjmV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BhhFg/btrdjed3S0c/kJWx80BpJJpE07dLfdjmV1/img.png&quot; data-alt=&quot;git-flow-model&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BhhFg/btrdjed3S0c/kJWx80BpJJpE07dLfdjmV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBhhFg%2Fbtrdjed3S0c%2FkJWx80BpJJpE07dLfdjmV1%2Fimg.png&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;1524&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;git-flow-model&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발 흐름&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림을 토대로 개발 흐름을 보자면 처음에 &lt;code&gt;Master&lt;/code&gt; 와 &lt;code&gt;Develop&lt;/code&gt; 브랜치를 만드는데,&lt;br /&gt;Develop는 Master에서 부터 시작되는 브랜치입니다. 위 두 브랜치는 항상 존재하는 브랜치 입니다.&lt;br /&gt;새로운 추가 작업이 있는 경우 &lt;b&gt;Develop&lt;/b&gt; 에서 &lt;code&gt;Feature&lt;/code&gt; 브랜치를 생성한다.&lt;br /&gt;Feature는 언제나 Develop에서 시작해야 합니다. 기능추가 작업이 완료되면 Feature는 Develop로 &lt;b&gt;Merge&lt;/b&gt;한다.&lt;br /&gt;QA를 위해 &lt;b&gt;Develop&lt;/b&gt;에서 &lt;code&gt;Release&lt;/code&gt; 브랜치를 생성한다. QA를 진행 하면서 발생한 버그들은 Release에 수정된다.&lt;br /&gt;QA가 끝나면 Release 브랜치를 &lt;b&gt;Develop&lt;/b&gt; 와 &lt;b&gt;Master&lt;/b&gt; 브랜치로 각각 &lt;b&gt;Merge&lt;/b&gt; 한다.&lt;br /&gt;&lt;code&gt;Hotfix&lt;/code&gt; 브랜치는 언제나 Master에서 시작해야 합니다. 작업이 완료되면 Hotfix는 &lt;b&gt;Master&lt;/b&gt; 와 &lt;b&gt;Develop&lt;/b&gt; 브랜치로 각각 &lt;b&gt;Merge&lt;/b&gt; 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더욱 자세한 내용은 &lt;a href=&quot;https://nvie.com/posts/a-successful-git-branching-model/&quot;&gt;A successful Git branching model&lt;/a&gt;를 참고하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git 을 여러사람이 함께 사용하다보면 필연적으로 브랜치 전략을 세워서 git을 관리해야됩니다. 어떤 브랜치 전략을 사용하는지는 해당 팀의 성격에 맞게 선택하고 함께 공유하는게 중요하다고 생각합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nvie.com/posts/a-successful-git-branching-model/&quot;&gt;A successful Git branching model&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://woowabros.github.io/experience/2017/10/30/baemin-mobile-git-branch-strategy.html&quot;&gt;우린 Git-flow를 사용하고 있어요 - 우아한형제들 기술 블로그&lt;/a&gt;&lt;/p&gt;</description>
      <category>Git</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/18</guid>
      <comments>https://mishka.tistory.com/18#entry18comment</comments>
      <pubDate>Thu, 26 Aug 2021 16:28:22 +0900</pubDate>
    </item>
    <item>
      <title>Vue 에서 Axios를 사용하여 서버통신 해보기</title>
      <link>https://mishka.tistory.com/17</link>
      <description>&lt;h2&gt;Axios 란?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Axios&lt;/code&gt;는 &lt;strong&gt;HTTP 클라이언트 라이브러리&lt;/strong&gt; 중 하나이다.&lt;br&gt;비동기 방식으로 HTTP 데이터 요청을 실행하고 또한 IE8 이상을 포함한 모든 최신 브라우저를 지원한다.&lt;br&gt;&lt;code&gt;Axios&lt;/code&gt;는 &lt;strong&gt;Promise&lt;/strong&gt;를 기반의 자바스크립트 비동기 처리 방식을 사용합니다.&lt;/p&gt;
&lt;!-- more --&gt;

&lt;h2&gt;설치&lt;/h2&gt;
&lt;p&gt;보통 npm을 통해 설치를 진행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install axios&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이밖에도 yarn,bower,CDN 을 통해 설치도 가능합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;// yarn
yarn add axios

// bower
bower install axios

// Using unpkg CDN
&amp;lt;script src=&amp;quot;https://unpkg.com/axios/dist/axios.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;사용방법&lt;/h2&gt;
&lt;p&gt;Axios는 여러가지 &lt;strong&gt;별칭 method&lt;/strong&gt;를 제공하고 있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;axios.get(url[, config])&lt;br&gt;axios.post(url[, data[, config]])&lt;br&gt;axios.patch(url[, data[, config]])&lt;br&gt;axios.delete(url[, config])&lt;br&gt;위에 자주 쓰이는 4가지 이외에도 여러가지 를 지원 하고 있다.&lt;br&gt;axios.request(config)&lt;br&gt;axios.head(url[, config])&lt;br&gt;axios.options(url[, config])&lt;br&gt;axios.put(url[, data[, config]])&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;별칭으로 사용하는 경우에는 url, method 및 data 특성을 구성에 지정할 필요가 없습니다.&lt;/p&gt;
&lt;h2&gt;GET (불러오기)&lt;/h2&gt;
&lt;p&gt;GET은 말 그대로 서버에서 데이터를 가져오는데 사용합니다. 많이 사용하는 명령어 중에 하나입니다.&lt;br&gt;서버 주소 &lt;code&gt;/api&lt;/code&gt;로 부터 값을 가져올때는 아래와 같이 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;axios.get(&amp;#39;/api&amp;#39;)
  .then(res =&amp;gt; {
    console.log(res); 
  })
  .catch(err) =&amp;gt; {
    console.log(err);
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;axios 요청할 때 메소드의 두번째 인자인 config 객체에 요청값을 같이 넘길 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;axios.get(&amp;#39;/api&amp;#39;, {
  params: { title: &amp;#39;타이틀&amp;#39; },
  headers: { &amp;#39;Content-Type&amp;#39;: &amp;#39;application/json&amp;#39; },
  timeout: 1000  
}).then(res =&amp;gt; {
  console.log(res);
  })&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;POST (입력하기)&lt;/h2&gt;
&lt;p&gt;서버에 값을 입력 할 때 사용합니다. 서버의 데이터 리스트의 마지막에 넘기는 정보를 추가합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;axios.post(&amp;#39;/api&amp;#39;, { title: &amp;#39;타이틀&amp;#39; })
  .then(res =&amp;gt; {
  console.log(res);  
  })&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PATCH (수정하기)&lt;/h2&gt;
&lt;p&gt;서버의 특정 데이터를 수정합니다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;axios.patch(&amp;#39;/api&amp;#39;, { title: &amp;#39;타이틀변경&amp;#39; })
  .then(res =&amp;gt; {
    console.log(res);  
  })&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;DELETE (삭제하기)&lt;/h2&gt;
&lt;p&gt;서버의 특정 값을 삭제합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;axios.delete(&amp;#39;/api/val&amp;#39;) //val = 특정 값
  .then(res =&amp;gt; {
    console.log(res);  
  })&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Vue 에서 Axios 사용하기&lt;/h2&gt;
&lt;p&gt;vue 에서 axios 를 사용하려면 Vue.prototype에 axios를 추가하면 됩니다. main.js에 아래와 같은 내용을 추가한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import Vue from &amp;#39;vue&amp;#39;
import App from &amp;#39;./App.vue&amp;#39;
import axios from &amp;#39;.axios&amp;#39; // import axios

Vue.prototype.$axios = axios; // prototype에 axios 추가

Vue.config.productionTip = false

new Vue({
    render: h =&amp;gt; h(App),
}).$mount(&amp;#39;#app&amp;#39;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같이 작성하면 Vue 인스턴스 내부에서 axios를 따로 import 하지 않아도 &lt;code&gt;this.$axios&lt;/code&gt;를 이용해서 사용 할 수 있다.&lt;/p&gt;
&lt;h2&gt;Reference&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/axios/axios&quot;&gt;https://github.com/axios/axios&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://tuhbm.github.io/2019/03/21/axios/&quot;&gt;https://tuhbm.github.io/2019/03/21/axios/&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://ux.stories.pe.kr/138&quot;&gt;https://ux.stories.pe.kr/138&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://luji.tistory.com/83&quot;&gt;https://luji.tistory.com/83&lt;/a&gt;&lt;/p&gt;</description>
      <category>Vue</category>
      <category>axios</category>
      <category>Vue.js</category>
      <category>서버통신</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/17</guid>
      <comments>https://mishka.tistory.com/17#entry17comment</comments>
      <pubDate>Thu, 26 Aug 2021 16:18:01 +0900</pubDate>
    </item>
    <item>
      <title>[VScode] VScode Extension - 확장프로그램 추천 및 설치방법</title>
      <link>https://mishka.tistory.com/16</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;VScode는 비교적 가벼운 에디터 입니다. 기본적으로는 필수적인 기능들만을 제공 하고 있습니다.&lt;br /&gt;대신 마켓플레이스(Marketplace)를 통해서 많은 확장프로그램(Extension)들을 설치하여 즉시 사용 할수 있습니다.&lt;br /&gt;확장프로그램을 통해 보다 편리하게 코드를 작성 할 수 있습니다.&lt;br /&gt;그 중에서 많이 사용하는 것들을 한번 알아보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;확장프로그램 찾아보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VScode 내에서 확장기능을 찾아서 설치할 수 있습니다. VScode를 실행하면 왼쪽에 여러가지 아이콘들이 있습니다.&lt;br /&gt;그 중에 가장 아래에 있는 네모모양의 아이콘 또는 명령어(&lt;code&gt;Ctrl+Shift+X&lt;/code&gt;)를 실행하여 확장프로그램들을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;48&quot; data-origin-height=&quot;248&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9egy0/btrdfHHOEfY/00DHp19APEKtC7ADdkfbwk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9egy0/btrdfHHOEfY/00DHp19APEKtC7ADdkfbwk/img.jpg&quot; data-alt=&quot;Extension&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9egy0/btrdfHHOEfY/00DHp19APEKtC7ADdkfbwk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9egy0%2FbtrdfHHOEfY%2F00DHp19APEKtC7ADdkfbwk%2Fimg.jpg&quot; data-origin-width=&quot;48&quot; data-origin-height=&quot;248&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Extension&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 검색창의 검색을 통해서 간단하게 설치 할수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;확장프로그램 리스트 (Extension List)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Korean Language Pack for Visual Studio Code&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTKMwx/btrdgRpTzMI/e0sDIH7BORvEJKLnTrfTlk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTKMwx/btrdgRpTzMI/e0sDIH7BORvEJKLnTrfTlk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTKMwx/btrdgRpTzMI/e0sDIH7BORvEJKLnTrfTlk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTKMwx%2FbtrdgRpTzMI%2Fe0sDIH7BORvEJKLnTrfTlk%2Fimg.jpg&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=MS-CEINTL.vscode-language-pack-ko&quot;&gt;Korean Language Pack for Visual Studio Code&lt;/a&gt;는 VScode의 언어의 한국어 팩입니다. VScode의 언어를 한국어로 변경해 줍니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Git History&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cck890/btrdjeE9kG7/3tztXUpat5aRnFdEz5MH41/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cck890/btrdjeE9kG7/3tztXUpat5aRnFdEz5MH41/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cck890/btrdjeE9kG7/3tztXUpat5aRnFdEz5MH41/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcck890%2FbtrdjeE9kG7%2F3tztXUpat5aRnFdEz5MH41%2Fimg.jpg&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistoryg&quot;&gt;Git history&lt;/a&gt;는 Git으로 관리되는 프로젝트의 Git log. file History, branch, commit 등을 편리하게 확인 할수 있는 확장프로그램입니다.&lt;br /&gt;파일을 열고 &lt;code&gt;F1&lt;/code&gt;키를 누르고 &lt;code&gt;Git: View History&lt;/code&gt;, &lt;code&gt;Git: View File History&lt;/code&gt;, &lt;code&gt;Git: View Line History&lt;/code&gt;를 입력하여 사용합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Active File In StatusBar&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buKnhK/btrdcQsdFRy/7VO7UdqGD76KRm9kyHVxCk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buKnhK/btrdcQsdFRy/7VO7UdqGD76KRm9kyHVxCk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buKnhK/btrdcQsdFRy/7VO7UdqGD76KRm9kyHVxCk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuKnhK%2FbtrdcQsdFRy%2F7VO7UdqGD76KRm9kyHVxCk%2Fimg.jpg&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=RoscoP.ActiveFileInStatusBar&quot;&gt;Active File In StatusBar&lt;/a&gt;는 Vscode 상태 표시 줄에 현재 활성 파일의 전체 경로를 표시하는 확장 프로그램입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/RoscoP/ActiveFileInStatusBar/raw/master/media/ActiveFileInStatusBar.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;현재 활성 파일의 경로를 하단 상태 막대에 표시합니다. 이 경로를 클릭하면 클립보드에 복사하여 사용 할수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Auto Rename Tag&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nPGyd/btrdcRxRG2J/41CVSJWbrc3gbHWGD5g601/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nPGyd/btrdcRxRG2J/41CVSJWbrc3gbHWGD5g601/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nPGyd/btrdcRxRG2J/41CVSJWbrc3gbHWGD5g601/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnPGyd%2FbtrdcRxRG2J%2F41CVSJWbrc3gbHWGD5g601%2Fimg.jpg&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-rename-tag&quot;&gt;Auto Rename Tag&lt;/a&gt;는 HTML/XML 태그의 이름을 자동으로 바꿉니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/formulahendry/vscode-auto-rename-tag/raw/master/images/usage.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Guides&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0kndE/btrdd44NUG0/5JkI0b3AdL6McfiI1mJDuk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0kndE/btrdd44NUG0/5JkI0b3AdL6McfiI1mJDuk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0kndE/btrdd44NUG0/5JkI0b3AdL6McfiI1mJDuk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0kndE%2Fbtrdd44NUG0%2F5JkI0b3AdL6McfiI1mJDuk%2Fimg.jpg&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=spywhere.guides&quot;&gt;Guides&lt;/a&gt;은 안내선을 표시해 주는 확장프로그램입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;indent-rainbow&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbRJrM/btrdeYC4EyN/UabmBzc7NoVfEXkmCD9e51/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbRJrM/btrdeYC4EyN/UabmBzc7NoVfEXkmCD9e51/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbRJrM/btrdeYC4EyN/UabmBzc7NoVfEXkmCD9e51/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbRJrM%2FbtrdeYC4EyN%2FUabmBzc7NoVfEXkmCD9e51%2Fimg.jpg&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=oderwat.indent-rainbow&quot;&gt;indent-rainbow&lt;/a&gt;는 텍스트 앞의 들여쓰기를 각 단계에서 네가지 색상으로 번갈아 보여주어 들여쓰기를 보다 쉽게 읽을 수 있게 해주는 확장프로그램입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/oderwat/vscode-indent-rainbow/master/assets/example.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Rainbow Brackets&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnSffC/btrdesYHAp4/1uLlCAvXgBuHMkHkqA8olK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnSffC/btrdesYHAp4/1uLlCAvXgBuHMkHkqA8olK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnSffC/btrdesYHAp4/1uLlCAvXgBuHMkHkqA8olK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnSffC%2FbtrdesYHAp4%2F1uLlCAvXgBuHMkHkqA8olK%2Fimg.jpg&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=2gua.rainbow-brackets&quot;&gt;Rainbow Brackets&lt;/a&gt;은 괄호(bracket)마다 다른 컬러를 제공합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Settings Sync&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biSbxY/btrdgRwE7bo/jkUQiTBLzi3lPfGPs9xnk1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biSbxY/btrdgRwE7bo/jkUQiTBLzi3lPfGPs9xnk1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biSbxY/btrdgRwE7bo/jkUQiTBLzi3lPfGPs9xnk1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiSbxY%2FbtrdgRwE7bo%2FjkUQiTBLzi3lPfGPs9xnk1%2Fimg.jpg&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync&quot;&gt;Settings Sync&lt;/a&gt;는 Github Gist를 사용하여 VScode의 설정(확장플러그인 포함)을 동기화 시켜주는 도구 입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Waka Time&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QrZGJ/btrdkKXCIem/phOUSiMk7K13ezfOm8965k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QrZGJ/btrdkKXCIem/phOUSiMk7K13ezfOm8965k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QrZGJ/btrdkKXCIem/phOUSiMk7K13ezfOm8965k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQrZGJ%2FbtrdkKXCIem%2FphOUSiMk7K13ezfOm8965k%2Fimg.jpg&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;165&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=WakaTime.vscode-wakatime&quot;&gt;Waka Time&lt;/a&gt;은 VScode에서 어떤 프로젝트에서 어떤 언어로 작성했는지 그에 따른 비율 시간을 측정하여 줍니다.&lt;br /&gt;별도의 &lt;a href=&quot;https://wakatime.com&quot;&gt;회원가입&lt;/a&gt;이 필요합니다. VScode 플러그인에 API kye를 입력해서 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 확장프로그램들은 제가 사용하는것 위주로 정리하였습니다. 추천 프로그램 있으면 댓글로 남겨주세요^^&lt;/p&gt;</description>
      <category>Setting</category>
      <category>extension</category>
      <category>vscode</category>
      <category>확장프로그램</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/16</guid>
      <comments>https://mishka.tistory.com/16#entry16comment</comments>
      <pubDate>Thu, 26 Aug 2021 16:09:44 +0900</pubDate>
    </item>
    <item>
      <title>[VScode] Visual Studio Code에서 터미널을 git bash 기본으로 설정하기</title>
      <link>https://mishka.tistory.com/15</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Visual Studio Code(이하 VScode)에서 터미널을 같이 사용 할수 있습니다. 별도의 창으로 작업을 하면 비효율 적이기도 하고 탭을 계속해서 눌러주어야 하는 불편함을 감수해야 합니다.&lt;br /&gt;VScode의 기본 터미널 사용값은 powershell입니다. 설정변경을 통해서 gitbash를 사용 할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;git bash로 변경하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VScode를 실행해서 &lt;code&gt;ctrl&lt;/code&gt; + &lt;code&gt;,&lt;/code&gt; 를 눌러 설정에 들어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 검색칸에 &lt;code&gt;terminal.integrated.shell.windows&lt;/code&gt;를 입력합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;99&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AzQwu/btrdiwlxR4U/t9HZ7yiu6GVAjGu1VUoEc0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AzQwu/btrdiwlxR4U/t9HZ7yiu6GVAjGu1VUoEc0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AzQwu/btrdiwlxR4U/t9HZ7yiu6GVAjGu1VUoEc0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAzQwu%2FbtrdiwlxR4U%2Ft9HZ7yiu6GVAjGu1VUoEc0%2Fimg.jpg&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;99&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 검색결과가 뜹니다.&lt;br /&gt;&lt;code&gt;settings.json에서 편집&lt;/code&gt;을 누릅니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;settings.json 편집&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;177&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckf6Xo/btrdjPLQsik/1UjtNkOqsuKppI5KisR6Uk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckf6Xo/btrdjPLQsik/1UjtNkOqsuKppI5KisR6Uk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckf6Xo/btrdjPLQsik/1UjtNkOqsuKppI5KisR6Uk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fckf6Xo%2FbtrdjPLQsik%2F1UjtNkOqsuKppI5KisR6Uk%2Fimg.jpg&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;177&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 문구를 추가해 줍니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;terminal.integrated.shell.windows&quot;: &quot;C:\\Program Files\\Git\\bin\\bash.exe&quot;,
    &quot;workbench.startupEditor&quot;: &quot;newUntitledFile&quot;,
    &quot;editor.renderIndentGuides&quot;: false,
    &quot;editor.minimap.enabled&quot;: false,
    &quot;editor.renderWhitespace&quot;: &quot;boundary&quot;,
    &quot;window.zoomLevel&quot;: 0,
    &quot;workbench.iconTheme&quot;: &quot;vscode-icons&quot;,
    &quot;files.autoGuessEncoding&quot;: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&quot;C:\\Program Files\\Git\\bin\\bash.exe&quot;&lt;/code&gt;에는 설치경로가 다르다면 &lt;code&gt;자신의 git 설치경로&lt;/code&gt;를 입력해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장을 해주고 &lt;code&gt;ctrl&lt;/code&gt;+&lt;code&gt;shift&lt;/code&gt;+&lt;code&gt;`&lt;/code&gt; 을 눌러서 새 터미널을 열어 확인합니다.&lt;br /&gt;바로 반영이 안되면 VScode를 한번 껏다가 켜줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;터미널 단축키 바꾸기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널 단축키를 편하기 변경 할 수 있습니다.&lt;br /&gt;&lt;code&gt;ctrl + k&lt;/code&gt; , &lt;code&gt;ctrl + s&lt;/code&gt; 눌러서 바로 가기 키 설정에 들어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색창에 &lt;code&gt;터미널&lt;/code&gt;을 검색합니다.&lt;br /&gt;더블클릭하면 새로운 단축키를 설정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;750&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BAYN9/btrdjPdZKBB/qEi3cKUct98KmEKHlUkHT0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BAYN9/btrdjPdZKBB/qEi3cKUct98KmEKHlUkHT0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BAYN9/btrdjPdZKBB/qEi3cKUct98KmEKHlUkHT0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBAYN9%2FbtrdjPdZKBB%2FqEi3cKUct98KmEKHlUkHT0%2Fimg.jpg&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;750&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Setting</category>
      <category>gitbash</category>
      <category>Visual Studio Code</category>
      <category>VS Code</category>
      <category>터미널</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/15</guid>
      <comments>https://mishka.tistory.com/15#entry15comment</comments>
      <pubDate>Thu, 26 Aug 2021 16:06:08 +0900</pubDate>
    </item>
    <item>
      <title>지난 1년6개월의 회고</title>
      <link>https://mishka.tistory.com/14</link>
      <description>&lt;p&gt;2019-08 작성글&lt;/p&gt;
&lt;p&gt;이번에 정들었던 &lt;strong&gt;&lt;code&gt;(주)라이프스타일프로젝트(이하 LSP)&lt;/code&gt;&lt;/strong&gt; 를 떠나면서 그동안의 회사생활 및 1년6개월 동안의 생활을 회고해보고 달라진 점들을 정리해본다. 사실 지금 글을 쓰고있는 &lt;code&gt;오늘(8월29일)&lt;/code&gt;이 마지막 출근날이다.&lt;/p&gt;
&lt;h2&gt;뜻밖의 연락&lt;/h2&gt;
&lt;p&gt;현재 다니고있고 곧 퇴사(8월31일부)하게 되는 LSP와의 인연은 &lt;code&gt;2018년 2월&lt;/code&gt;에 시작됐다.&lt;br&gt;그 당시 이직을 준비하고 있는 상태였던 필자는 취업사이트에 이력서를 올려놓고 여러군데에 면접을 보고 있는 상황이었다.&lt;br&gt;그러던중 지금의 회사 인사팀에서 먼저 필자의 이력서를 보고 연락이 왔다. 그당시 회사명은 &lt;code&gt;(주)유앤김파트너스&lt;/code&gt; 였다.&lt;/p&gt;
&lt;p&gt;그때까지만 해도 이력서를 넣지 않은 곳에서 직접적으로 연락이 온적은 없어서 일단 신기했고 또 그 당시 회사 주력브랜드였던 &lt;code&gt;미프(미남프로젝트)&lt;/code&gt;의 제품을 우연히 정글의 법칙에서 김병만이 사용하는 것을 본 후라 그런지 더욱 신기했었다.&lt;/p&gt;
&lt;p&gt;그렇게 면접제의를 받고 면접을 보게 되었고 1:3의 면접이었다. 면접관으로는 지금 같이 일하고 있는 팀장님, 인사담당자, 부사장님 이렇게 세분이 들어오셨었다. 몇가지 기술적인 질문이 있었으나 크게 어려운 질문은 없었고 상당히 즐거운 분위기에서 면접은 마무리되었다. 기억나는게 한가지 있는데 면접 마지막에 언제 연락을 줄 수 있냐는 필자의 질문에 회사측에서 필자가 첫번째 면접자라고 시간이 조금 걸릴것 같다고 했는데 그 대답을 듣고 필자가 호기롭게 &lt;code&gt;&amp;quot;첫번째가 가장좋은거 아시죠??&amp;quot;&lt;/code&gt;하며 너스레를 떨었던 것이 기억이 난다. ㅎㅎㅎ 지금 생각해도 참..... 그 덕분인지는 모르겠지만 면접 다음날 바로 연락이 왔는데 다른 사람을 추가면접보지 않고 바로 필자를 합격자로 정했다는 연락이었다. 연봉제안도 필자가 제시했던 금액보다 좋은 조건을 제시해주어서 여러가지 망설임이 있었지만 입사를 결정하게 되었다.&lt;/p&gt;
&lt;h2&gt;두근두근 첫 출근&lt;/h2&gt;
&lt;p&gt;입사는 설이 지나고였다. 명절을 보내고 새로운 마음으로 회사에 첫 출근을 하였다. 부서는 &lt;code&gt;크리에이티브팀&lt;/code&gt;이었고, 필자를 제외한 모든 부서원들은 디자이너들이었다. 필자의 업무는 기존에 계시던 분이 인수인계를 해주셨는데, 회사가 원래는 판교에서 지금의 역삼역근처로 이사를 오면서 거리도 멀어지고 건강도 안좋아지셔서 어쩔수 없이 퇴사를 하게 되었다고 하셨다.&lt;/p&gt;
&lt;p&gt;그렇게 인수인계 받은 기존의 사이트는 &lt;code&gt;카페24 쇼핑몰 솔루션&lt;/code&gt;을 통해 작업이 되어있었고 대부분이 이미지로 코딩이 되어있는 상태였다. 기존에 계시던분이 원래 디자이너 출신이어서 간단하게 코딩이 이루어져 있었다. 추후에 대대적인 리뉴얼이 이루어졌다.&lt;br&gt;그때까지만 해도 필자는 카페25 쇼핑몰 솔루션은 로그인만 해서 보았던게 전부였고 그 전에 하드코딩을 했었어서 카페25를 통한 코딩에 막연한 두려움을 약간을 가지고 있었으나 기존에 하던것에서 약간의 환경의 변화만 있었을 뿐 어려움 없이 적응 할 수 있었다.&lt;br&gt;또한 인수인계를 해주고 떠나시는 분을 제외하고는 회사에서 코딩 및 개방을 할 수 있는 사람이 한 사람도 없어서 막막하기도 하였다.&lt;/p&gt;
&lt;h2&gt;하면된다.&lt;/h2&gt;
&lt;p&gt;짧은 인수인계 기간이 끝나고 본격적으로 홀로 업무가 시작되었다.&lt;br&gt;그때 급한 업무를 처음으로 처리했던 기억이 나는데 기존에 계시던 분도 처음하는 일이었도 필자도 처음 듣는 업무여서 약간 당황했덨던 기억이난다. 업무내용은 그 때 당시 미프 모바일 사이트를 패키징하여 어플출시를 앞두고 있었는데 그 어플 출시를 위해서 애플 개발자 등록을 해야하고 결재를 해야되는데 그 단계에서 애플과 메일을 주고받고 전화를 받고 거기에서 받은 인증번호를 다시 입력하고 하는 등의 복잡한 프로세스가 있었는데 전에 계신던 분도 몇번 시도를 하다가 중간에 문제가 생겨서 해결을 못하시고 퇴사를 하시면서 필자에게 넘어왔던 업무였다. 중간에 무엇을 잘못했는지 중간 프로세스에서 막혀있는 상태였고 그렇게 상당한 시간이 흘러서 앱스토어 어플 출시 자체가 기약없이 미뤄지고 있는 상황이었다. 필자도 처음 겪는 업무여서 현재의 상황과 구글링 그리고 앱스토어의 문의를 통해 예상보다 빠르고 쉽게 앱스토어 어플 출시를 이루어낼 수 있었다. 오랜기간 멈춰져있던 업무를 필자가 잘 마무리 해서 입사한 후에 업무에 적응하는데 큰 도움이 되었던 사건이었다.&lt;/p&gt;
&lt;h2&gt;일을 하면서&lt;/h2&gt;
&lt;p&gt;1년6개월 동안 LSP를 다니면서 많은 것들을 배우고 성장하는 시간이었다. 회사의 기초가 화장품제조 및 판매이다보니 쇼핑몰 사이트의 개발과 운영에 대해 많은 것들을 배우게 되었고 쇼핑몰의 기초가 되는 카페24 쇼핑몰 솔루션에 대한 깊은 이해를 가질수 있었다. 그와 더불어 기존에 다니넌 에이전시와는 다르게 여유시간들을 갖게되어 아무래도 별도의 클라이언트가 있는것이 아니라 자사의 제품들만 다루기 때문에 일정과 같은 부분에서 회사 내부에 사정에 맞게 조율이 가능한 부분이 있었다. 많은 커뮤니티활동과 더불어 학원에도 다닐수 있는 기회가 있었고 스터디에도 참여해서 많은 것들을 배우는 시간을 가질 수 있었다.&lt;/p&gt;
&lt;p&gt;필자는 크리에이티브팀에 소속되어있었다. 암묵적으로 &lt;code&gt;디자인팀&lt;/code&gt;으로 불리운다 그 이유는 앞에서 잠깐 언급했듯이 필자를 제외한 모든 팀원이 디자이너로 구성되어있기 때문이었다. 필자도 퍼블려서로 전향하기 이전에는 무대디자인을 하였기에 디자이너들과의 소통 및 협업에는 큰 문제가 없었다. 그리고 화장품회사의 특성(?)상 남자직원들보다는 여자직원들의 비중이 더 높았었는데, 이전 무대일을 할때에도 디자인 관련 부서에서 일했기에 여자직원들이 더 많은 환경이었다보니 자연스레 섞여서 생활을 했었고 그러다보니 커뮤니케이션 같은 부분에서는 전혀 어려움이 없었으나 코딩 및 개발 업무를 알고 있는 사람은 전혀 없어서 어떤한 문제가 생겼을 때 혼자서 해쳐나가야하는 어려움은 늘 존재했었고 같이 문제에 대해 고민해 줄 사람의 필요가 느껴졌었다. 당시 회사 사정상 여러 개발자를 두는것은 무리가 있었고 그러다 보니 배움과 공유에 대한 갈증이 생겼고 자연스럽게 커뮤니티활동과 스터디로 그 갈증을 푸는 계기가 되었다.&lt;/p&gt;
&lt;h2&gt;공부, 스터디&lt;/h2&gt;
&lt;p&gt;처음부터 스터디를 시작한 것은 아니었다. 스터디에 대한 정보도 없었고 그러한 것들이 활발히 진행되고 있는 것도 모르는 상황이었다. 그래서 처음에는 익숙한 컴퓨터학원을 통해 부족하다고 생각했던 개발에 대한 부분들의 수업을 듣기 시작했다. 그렇게 몇가지 수업들을 들었으나 만족할 만한 실력향상은 나타나지 않았던것 같다. 그러던중 우연히 &lt;code&gt;&amp;#39;하코사&amp;#39;&lt;/code&gt;라는 커뮤니티를 접하게 되었고 스터디들이 활발히 진행되고 있는 것을 알게 되었다. 필자와 같은 고민을 하는 분들이 많이 있었으며 자신의 어려움을 질문하고 답을 얻어가고 함께 공부하는 커뮤니티였다. 그렇게 여러가지 정보를 얻어가던 중에 한 &lt;code&gt;살롱&lt;/code&gt;에 참여하게 되었고 그곳에서 들은 이야기들은 지금까지 내가 알고 있던 내용들과는 많이 다른 내용들이었다. &amp;#39;프론트엔드개발자가 되어야지&amp;#39;, &amp;#39;지금 하고 있는 업무에서 추가적인 것들을 배우면서 커리어를 쌓으면 할 수 있는 걸 거야&amp;#39; 와 같은 막연한 계획들이 있었는데 그 계획들을 처음부터 다시 생각하게 되는 살롱이었다. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;살롱 : 18세기 중반 프랑스에서 지성인과 예술가가 모여 토론을 펼치고 지식을 나누던 사교 모임&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;살롱에 참여하기 전까지는 프론트엔드 개발자로 전향을 하려면 지금 하던 퍼블리셔 업무에서 추가적으로 약간의 자바스크립트를 배우고 동적인 UI개발을 더 익히면 되겠지 라는 수준이었는데 참여하고 난 후에는 생각이 완전히 바뀌었다. 우선 퍼블리셔와 프론트엔드개발자는 겹치는 부분도 물론 있었지만 근본적으로 다른 직군에 속해 있는 것이라서 추구하는 방향도 다르고 그렇기에 공부해야 하는 방향 자체도 다르다는 것이었다. 대략적으로 퍼블리셔거 보여지는 뷰단에서 UI적인 개발을 주로 한다면 프로트엔드개발자는 프로든단에서 DATA를 다루어 보여지는 뷰단을 선계해 가는 느낌이다. 그렇다보니 접근하는 방식에서부터 차이가 있고 생각하는 방법도 다르게 해야 올바르게 개발자로 거듭날 수 있다는 생각이 들었다. &lt;/p&gt;
&lt;h2&gt;책 집필스터디에 참여&lt;/h2&gt;
&lt;p&gt;그렇게 살롱다녀와서 거기서 알게된 인연으로 정말 좋은 기회인 책집필을 위한 스터디에 참여하는 기회가 찾아왔다.&lt;br&gt;&lt;code&gt;Vue.js&lt;/code&gt;에 관한 책을 집할 하면서 그 내용들을 가지고 구현해 보고 스터디였는데 피자는 당시 Vue.js는 당연히 처음접하였고 프레임워크언어자체가 대한 개념 및 개발을 처음 시작하는 단계였다. 그래서인지 스터디에서 배우는 하나하나가 너무나 값진 경험이었다. 처음 배우는 내용들이다보니 질문도 많았고 이것저것 안되는 것들도 그리고 모르는 것들도 많았는데 집필하시는 분들에게는 이부분이 오히려 도움이 많이 되었다고 하셨다. 서로에게 여러가지를 주고 받을수 있는 윈윈이었으니 잘 마무리 되었다. ㅎㅎㅎ 스터디가 끝나고 간단한 메모어플과 게시판어플을 구현해볼수 있었다. 현재는 책은 무시히 출판되었고 필자는 간단하게나마 추천사로 책 한부분에 남을수 있어서 좋았다. 궁금해 하실 분들을 위해 책제목을 남겨본다. 책 제목은 &lt;code&gt;&amp;quot;커피한잔 마시며 끝내는 Vue.js&amp;quot;&lt;/code&gt; 이다.&lt;/p&gt;
&lt;p&gt;그렇게 Vue.js 스터디를 끝나마치고 더 배워야 한다는 생각에 git스터디, javajscript스터디에 참여하게 되었고 그러다보니 필자에게 맞는 새로운 스터디를 기획해서 스터디를 진행하게 되었다. 현재는 JS ES6를 중점적으로 스터디를 하고 잇으며 8월 31일에 마지막 스터디 모임을 앞두고 있는 상황이다. 이번 스터디에 함께한 스터디원분들 중 많은 분들이 다음 스터디로 함께 했으면 좋겠다는 의사를 밝혀주셔서 현재는 함께 기획하고 있는 중에 있다.&lt;/p&gt;
&lt;p&gt;이렇게 회사 생활을 하면서 커뮤니티와 많은 스터디들을 통해서 부족한 것들을 채우고 발전해나가는 시간들을 가실 수 있었던 것에 대해서는 긍적적으로 생각한다.&lt;/p&gt;
&lt;h2&gt;마치며&lt;/h2&gt;
&lt;p&gt;지난 1년 6개월 동안 LSP에 지내면서 함께한 모든 분들에게 감사의 인사를 전하고 싶다.&lt;br&gt;비록 더 많은 시간들을 함께 하지는 못하지만 어디에서든지 각자의 몫을 하며 빛나고 있기를 소망한다.&lt;/p&gt;</description>
      <category>회고</category>
      <category>라이프스타일프로젝트</category>
      <category>퍼블리셔</category>
      <category>프론트엔드</category>
      <category>회고</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/14</guid>
      <comments>https://mishka.tistory.com/14#entry14comment</comments>
      <pubDate>Thu, 26 Aug 2021 15:39:32 +0900</pubDate>
    </item>
    <item>
      <title>Github SSH key 생성 및 적용하기</title>
      <link>https://mishka.tistory.com/13</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;많은 Git 서버들은 SSH 공개키로 인증을 합니다. 또한 Github 연결시마다 계정정보를 입력해야 하는 번거로움을 제거해 준다. 이번에 입사한 회사에서도 서버에서 SSH를 사용해서 인증하는 시스템을 가지고 있어서 이 기회에 세팅을하면서 적용방법을 정리해 보았다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SSH 공개키 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 공개키를 사용하려면 공개키를 만들어야 한다. 그 전에 공개키가 있는지 확인이 필요하다. 기본적으로 사용자의 SSH키들은 사용자의 &lt;code&gt;~/.ssh&lt;/code&gt; 디텍토리에 저장한다. 디텍토리의 파일을 살펴서 공개키가 있는지 확인 할 수 있다.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;cd ~/.ssh
ls&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 &lt;code&gt;id_dsa&lt;/code&gt;나 &lt;code&gt;id_rsa&lt;/code&gt;라고 되어 있다. 그 중 &lt;code&gt;.pub&lt;/code&gt; 파일이 &lt;b&gt;공개키&lt;/b&gt;이고 다른 파일은 &lt;b&gt;개인키&lt;/b&gt; 입니다.&lt;br /&gt;이 파일이 없거나 .ssh 디텍토리가 없으면 &lt;code&gt;ssh-keygen&lt;/code&gt; 프로그램으로 키를 생성하여 준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;터미널 실행하여 다음 명령어를 실행&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;ssh-keygen -t rsa -b 4096 -C &quot;your_email@example.com&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적은 이메일을 레이블로 사용해서 새 SSH 키를 작성한다는 메세지가 출력됩니다.(아래와 같은 메세지가 나온다면 성공입니다.)&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;&amp;gt; Generating public/private rsa key pair.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSH Key 저장위치 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;키를 저정할 파일을 입력하십시오&quot; 라는 프로프트가 표시되면 Enter를 누르십시오.(기본위치로 지정)&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;&amp;gt; Enter a file in which to save the key (/Users/you/.ssh/id_rsa): [Press enter]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSH Key 비밀번호 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호 없이 설정할경우 엔터 두번을 눌러주면된다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;&amp;gt; Enter passphrase (empty for no passphrase): [Type a passphrase]
&amp;gt; Enter same passphrase again: [Type passphrase again]&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SSH Key 등록&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성한 SSH Key를 등록해 봅시다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;터미널을 실행하여 아래 명령어를 실행하여 백그라운드에서 ssh-agent를 시작&lt;/h3&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;eval &quot;$(ssh-agent -s)&quot;
&amp;gt; Agent pid 59566&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ssh의 config 파일에 아래 text를 입력&lt;/h3&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;vi ~/.ssh/config&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Host *&lt;br /&gt;AddKeysToAgent yes&lt;br /&gt;UseKeychain yes&lt;br /&gt;IdentityFile ~/.ssh/id_rsa&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아래 명령어를 입력하여 SSH Key 값을 ssh-agent에 추가&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;ssh-add -K ~/.ssh/id_rsa&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SSH Key를 Github 계정에 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github 로그인후 우측상단 메뉴에서 &lt;code&gt;Setting&lt;/code&gt;을 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;848&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzekTa/btrdjOF6oMq/vdpoiQ6wjgk3X0lOXK0EgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzekTa/btrdjOF6oMq/vdpoiQ6wjgk3X0lOXK0EgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzekTa/btrdjOF6oMq/vdpoiQ6wjgk3X0lOXK0EgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzekTa%2FbtrdjOF6oMq%2FvdpoiQ6wjgk3X0lOXK0EgK%2Fimg.png&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;848&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌측의 &lt;b&gt;Personal settings&lt;/b&gt; 메뉴 중에 &lt;code&gt;SSH and GPG keys&lt;/code&gt; 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;848&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzekTa/btrdjOF6oMq/vdpoiQ6wjgk3X0lOXK0EgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzekTa/btrdjOF6oMq/vdpoiQ6wjgk3X0lOXK0EgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzekTa/btrdjOF6oMq/vdpoiQ6wjgk3X0lOXK0EgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzekTa%2FbtrdjOF6oMq%2FvdpoiQ6wjgk3X0lOXK0EgK%2Fimg.png&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;848&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;New SSH Key&lt;/code&gt;를 눌러서 Title과 Key를 입력후 &lt;code&gt;Add SSH Key&lt;/code&gt; 버튼을 눌러준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key 에는 아래명령어를 사용해서 .pub 의 공개키를 입력해 준다.&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;cat ~/.ssh/id_rsa.pub&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;id_rsa.pub 예시&lt;/b&gt;&lt;br /&gt;ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU&lt;br /&gt;GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3&lt;br /&gt;Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA&lt;br /&gt;t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/En&lt;br /&gt;mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx&lt;br /&gt;NrRFi9wrf+M7Q== &lt;a href=&quot;mailto:schacon@agadorlaptop.local&quot;&gt;schacon@agadorlaptop.local&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github SSH key 생성 및 적용하기를 해보았다. Github 연결시 계정정보를 입력해야 하는 번거로움에서 해방돠었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SSH 연결 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH 를 연결하고 나서 직접 커밋하기 전에 테스트를 해보는 방법을 알아보았다. 연결테스트를 하기 전에 아래 작업들을 모두 완료해야한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSH 공개키 생성&lt;/li&gt;
&lt;li&gt;SSH Key 등록&lt;/li&gt;
&lt;li&gt;SSH Key를 Github 계정에 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;b&gt;터미널을 엽니다.&lt;/b&gt; 그리고 아래 문구를 입력해 줍니다.&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;$ ssh -T git@github.com&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 아래와 같은 경고가 경고문구를 보실 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;vbnet&quot;&gt;&lt;code&gt;The authenticity of host 'github.com (IP ADDRESS)' can't be established.
RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
Are you sure you want to continue connecting (yes/no)?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는&lt;/p&gt;
&lt;pre class=&quot;vbnet&quot;&gt;&lt;code&gt;The authenticity of host 'github.com (IP ADDRESS)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no)?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 같은 메시지중 하나와 일치하는지 확인 후 &lt;b&gt;yes&lt;/b&gt; 를 입력해 줍니다. 아래와 같은 멘트가 나오면 성공입니다.&lt;/p&gt;
&lt;pre class=&quot;irpf90&quot;&gt;&lt;code&gt;Hi username! You've successfully authenticated, but GitHub does not
provide shell access.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 아래 링크를 통해 확인 하실 수 있습니다.&lt;br /&gt;&lt;a href=&quot;https://help.github.com/en/github/authenticating-to-github/testing-your-ssh-connection&quot;&gt;https://help.github.com/en/github/authenticating-to-github/testing-your-ssh-connection&lt;/a&gt;&lt;/p&gt;</description>
      <category>Setting</category>
      <category>github</category>
      <category>SSH</category>
      <category>ssh key</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/13</guid>
      <comments>https://mishka.tistory.com/13#entry13comment</comments>
      <pubDate>Thu, 26 Aug 2021 15:33:47 +0900</pubDate>
    </item>
    <item>
      <title>Github 블로그에 Custom도메인 연결하기</title>
      <link>https://mishka.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;*아래글은 Github 블로그 운영당시에 작성한 글입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hexo를 이용해 github 블로그를 만들고 SEO를&lt;br /&gt;적용시켜보기도 하고 댓글 시스템도 연결하고 스타일도 조금씩 건들여보고 하다보니 결국에 나만의 도메인을 만들어서 연결해 보고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 여러가지를 알아보다보니 Github 자체에서 Custom Domain을 간편하게 연결할수 있도록 해주고 있었다.&lt;br /&gt;기본적으로 Github를 통해서 정적페이지를 호스팅 하게되면 기본도메인이 주어는데 아래와 같은 형식으로 생성됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;USERNAME.github.io&lt;br /&gt;그래서 저는 &lt;a href=&quot;mishka86.github.io&quot;&gt;mishka86.github.io&lt;/a&gt; 로 설정되었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;!-- more --&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 도메인을 연결하려면 원하시는 도메인 주소를 구입하신 후에 도메인 연결을 하시면 됩니다.&lt;br /&gt;저는 &lt;code&gt;mishka.kr&lt;/code&gt; 도메인을 &lt;a href=&quot;https://www.hosting.kr/&quot;&gt;hosting.kr&lt;/a&gt;을 통해서 구입했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DNS 설정 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 hosting.kr 설정을 위주로 설명하겠지만 다른 도메인 서비스에서도 비슷한 루트로 변경이 가능 하니 참고하시기 바랍니다.&lt;br /&gt;도메인의 네임서버를 변경해 주어야합니다. hosting.kr에서는 부가서비스로 구분이 되어있네요.&lt;br /&gt;Home &amp;gt; 도메인 &amp;gt; 부가서비스 &amp;gt; 네임서버 설정 관리 들어가서 github에서 요구 하는데로 변경을 시켜줍니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;Type&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;서브 도메인&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;IP주소 / 레코드 값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;A Record&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;185.199.108.153&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;A Record&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;185.199.109.153&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;A Record&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;185.199.110.153&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;A Record&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;185.199.111.153&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;CName Record&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;www&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;USERNAME.github.io&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A Record는 Domain을 물리적인 IP 주소로 연결 할 수 있도록 합니다.&lt;br /&gt;CName은 물리적인 IP 주소가 아닌 다른 Domain을 연결 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A Record의 경우 위에 것에서 하나를 선택해서 입력해 주면됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;745&quot; data-origin-height=&quot;120&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IF4HZ/btrdffkcq85/0kt5YAsCdRMD1lxF1Ubqp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IF4HZ/btrdffkcq85/0kt5YAsCdRMD1lxF1Ubqp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IF4HZ/btrdffkcq85/0kt5YAsCdRMD1lxF1Ubqp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIF4HZ%2Fbtrdffkcq85%2F0kt5YAsCdRMD1lxF1Ubqp1%2Fimg.png&quot; data-origin-width=&quot;745&quot; data-origin-height=&quot;120&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 A Record와 CName을 위와 같이 세팅 해주었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Github Pages 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github에서 블로그가 있는 Repository 로 이동합니다.&lt;br /&gt;메뉴에서 Settings &amp;gt; Options 에 &lt;b&gt;Gihub Pages&lt;/b&gt; 항목에서&lt;br /&gt;Costom domain 을 자신이 가지고 있는 도메인 주소로 변경하고 Save 버튼을 눌러줍니다.&lt;br /&gt;HTTPS주소를 사용하시려면 하단에 Enfoce HTTPS 항목을 체크해 주시면 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2018년 5월 1일 &lt;a href=&quot;https://github.blog/2018-05-01-github-pages-custom-domains-https/&quot;&gt;Github 공식 블로그&lt;/a&gt; 에서 정식 지원 소식이 올라왔습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;231&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UJ5pl/btrdj6Nh4dz/rGMrWcYGqKHISaCTXRouG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UJ5pl/btrdj6Nh4dz/rGMrWcYGqKHISaCTXRouG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UJ5pl/btrdj6Nh4dz/rGMrWcYGqKHISaCTXRouG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUJ5pl%2Fbtrdj6Nh4dz%2FrGMrWcYGqKHISaCTXRouG0%2Fimg.png&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;231&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 설정을 하면 이후에 USERNAME.github.io 로 접근되는 요청이 설정하신 도메인으로 Redirect 됩니다.&lt;br /&gt;이렇게 설정을 하면 설정하신 도메인으로 &lt;b&gt;정상적으로 노출&lt;/b&gt;이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 진행을 했는데 저는 문제가 한가지 발생했는데요 블로그를 배포하면 Custom domain 설정이 날아가는거였습니다.&lt;br /&gt;혹은 CNAME 파일이 정상적으로 생성되지 않은 경우도 발생 할 수도 있다고 하는데요 그럴 경우에 아래와 같은 해결법이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hexo의 CNAME 생성을 위한 패키지를 설치 합니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;npm install hexo-generator-cname --save&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;_config.yml&lt;/b&gt; 파일에서 아래와 같이 플러그인 설정을 해줍니다.&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;plugins: hexo-generator-cname&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;_config.yml&lt;/b&gt; 에서 url 이름도 적용한 도메인과 같게 변경해 주세요.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;url: https://mishka.kr&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Github 블로그에 Custom Domain을 연결해 주었습니다. 여러분들도 자신만의 도메인을 연결해서 자신만의 블로그에 특징을 더해보세요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.holaxprogramming.com/2017/05/15/github-page-and-custom-domain/&quot;&gt;Github Pages에 Custom Domain 적용하기&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://blog.chulgil.me/how-to-make-blog-using-github-3/&quot;&gt;블로그 만들기 GitHub 심화 3편 - 커스텀 도메인&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://blog.gaerae.com/2018/05/github-pages-custom-domains-https.html&quot;&gt;Github Pages 개인 도메인도 무료로 HTTPS 지원 시작!&lt;/a&gt;&lt;/p&gt;</description>
      <category>Programming</category>
      <category>blog</category>
      <category>github</category>
      <category>hexo</category>
      <category>도메인연결</category>
      <category>블로그</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/12</guid>
      <comments>https://mishka.tistory.com/12#entry12comment</comments>
      <pubDate>Thu, 26 Aug 2021 14:56:31 +0900</pubDate>
    </item>
    <item>
      <title>Mac os에 zsh 세팅하기</title>
      <link>https://mishka.tistory.com/11</link>
      <description>&lt;h2&gt;ZSH 란?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Z 셸(Z shell, zsh)&lt;/strong&gt;은 상호작용 로그인 셸이자 셸 스크립트를 위한 강력한 명령 줄 &lt;strong&gt;인터프리터&lt;/strong&gt;로 사용할 수 있는 유닉스 셸이다.&lt;br&gt;&lt;strong&gt;Zsh&lt;/strong&gt;는 bash, ksh, tcsh의 일부 기능을 포함하여 수많은 개선 사항이 갖추어진 &lt;strong&gt;확장형 본 셸&lt;/strong&gt;이다. &lt;a href=&quot;https://ko.wikipedia.org/wiki/Z_%EC%85%B8&quot;&gt;위키백과&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;ZSH 기능 살펴보기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;경로 자동 추론&lt;/li&gt;
&lt;li&gt;타이핑 교정&lt;/li&gt;
&lt;li&gt;명령어 추천&lt;/li&gt;
&lt;li&gt;다양한 플러그인&lt;/li&gt;
&lt;li&gt;이쁜 디자인이 핵심이다 ㅋㅋㅋ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;개발속도를 올려줄 수 있는 간편한 기능들이 많이 있다. &lt;/p&gt;
&lt;h2&gt;ZSH 설치&lt;/h2&gt;
&lt;p&gt;본격적으로 ZSH를 설치 해보자&lt;br&gt;먼저 macOS 용 패키지 관리자인 &lt;code&gt;Homebrew&lt;/code&gt;를 설치한다.&lt;br&gt;설치방법은 &lt;a href=&quot;https://brew.sh/index_ko&quot;&gt;Homebrew 사이트&lt;/a&gt;에 자세히 나와있으니 이번 포스팅에서는 생략한다.&lt;/p&gt;
&lt;p&gt;먼저 아래 명령으로 zsh 가 설치되어있나 확인작업을 해준다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zsh --version
zsh 5.7.1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;설치되어 있지 않다면 아래의 명령으로 설치를 해준다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install zsh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;설치가 끝났다면 기본 쉘을 chsh을 사용하여 변경해준다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chsh -s `which zsh`&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Oh My Zsh&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ohmyz.sh/&quot;&gt;Oh My Zsh&lt;/a&gt;는 ZSH 구성 관리를 위한 오픈 소스 커뮤니티 중심 프레임 워크 입니다. 여기에는 수천가지의 유용한 기능, 도우미, 플러그인, 테마 및 몇가지 소리가 제공됩니다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ sh -c &amp;quot;$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sh -c &amp;quot;$(wget https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ZSH 설치가 완료되었다. 이렇게만 설치하고 사용하여도 상관없지만 이왕 설치한 김에 몇가지 편리한 기능들을 추가로 세팅해보기로 했다.&lt;/p&gt;
&lt;h2&gt;iTerm2 설치&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.iterm2.com/&quot;&gt;iTerm2&lt;/a&gt;는 터미널의 부족한 기능들을 보안해주는 &lt;code&gt;터미널 에뮬레이터&lt;/code&gt;이다. iTerm2에서 제공하는 많은 기능들 중에 유용한 기능들은 아래와 같다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;자동완성 기능 (Cmd + ;)&lt;/li&gt;
&lt;li&gt;터미널 분할 창 기능 (Split Panes)&lt;/li&gt;
&lt;li&gt;터미널 내에서 찾기 기능&lt;/li&gt;
&lt;li&gt;마우스 없이 복사와 붙여넣기&lt;/li&gt;
&lt;li&gt;더 많은 기능 살펴보기 : &lt;a href=&quot;https://www.iterm2.com/features.html&quot;&gt;https://www.iterm2.com/features.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;iTerm2 설치는 &lt;a href=&quot;https://www.iterm2.com/&quot;&gt;iTerm2홈페이지&lt;/a&gt;에서 다운로드 받은 후에 프로그램을 어플리케이션으로 옮겨주기만 하면된다.&lt;/p&gt;
&lt;h2&gt;iTerm2 테마설치(선택)&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/mbadolato/iTerm2-Color-Schemes#installation-instructions&quot;&gt;iTerm Color Schemes&lt;/a&gt;에 접속하여 컬러 스킨을 다운로드 받는다.&lt;br&gt;다운을 받으면 iTerm2를 실행하여 &lt;code&gt;cmd + ,&lt;/code&gt;를 눌러서 환경설정 창을 띄워준다. Profile &amp;gt; colors 메뉴에 들어가서 &lt;code&gt;Color Presets&lt;/code&gt;를 눌러서 하단의 imports를 누른다. 다운로드 받은 폴더 schemes의 테마 중 원하는 테마를 선택한다.(취향존중)&lt;/p&gt;
&lt;h2&gt;ZSH 테마 설치&lt;/h2&gt;
&lt;p&gt;많은 테마들 중에 필자는 &lt;a href=&quot;https://github.com/denysdovhan/spaceship-prompt&quot;&gt;&lt;strong&gt;Spaceship ZSH&lt;/strong&gt;&lt;/a&gt; 테마를 설치해 보았다.&lt;/p&gt;
&lt;p&gt;아래 명령어를 사용해서 설치를 진행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 복사
git clone https://github.com/denysdovhan/spaceship-prompt.git &amp;quot;$ZSH_CUSTOM/themes/spaceship-prompt&amp;quot;
# 심볼릭링크(심볼릭 링크는 원본파일을 가리키도록 링크만 시켜둔 것 - 윈도우의 바로가기)
ln -s &amp;quot;$ZSH_CUSTOM/themes/spaceship-prompt/spaceship.zsh-theme&amp;quot; &amp;quot;$ZSH_CUSTOM/themes/spaceship.zsh-theme&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;.zshrc&lt;/strong&gt; 파일에서 &lt;strong&gt;ZSH_THEME&lt;/strong&gt; 내용을 변경해준다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;vi ~/.zshrc
또는 open ~/.zshrc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 내용을 실행&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;source ~/.zshrc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 테마 적용시 폰트가 깨져서 원하던 모습을 볼수 없을 경우 폰트 설치를 진행하여 준다.&lt;/p&gt;
&lt;h2&gt;Powerline fonts 설치&lt;/h2&gt;
&lt;p&gt;자세한 설치 방법은 &lt;a href=&quot;https://github.com/powerline/fonts&quot;&gt;Powerline fonts&lt;/a&gt;을 참고 하기 바라며 간단한 설치 방법을 공유 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 복사
git clone https://github.com/powerline/fonts.git --depth=1
# 설치
cd fonts
./install.sh
# 지우기
cd ..
rm -rf fonts&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;설치가 완료되면 아까와 같이 iTerm2 에서 설정을 &lt;code&gt;cmd + ,&lt;/code&gt;를 눌러 환경설정에서 Profiles &amp;gt; Text 에서 Powerline폰트를 선택주면된다.&lt;br&gt;&lt;strong&gt;ZSH&lt;/strong&gt;로 터미널과 조금 더 친해져 보자~!!&lt;/p&gt;</description>
      <category>Setting</category>
      <category>iTerm2</category>
      <category>MAC OS</category>
      <category>Oh My ZSH</category>
      <category>zsh</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/11</guid>
      <comments>https://mishka.tistory.com/11#entry11comment</comments>
      <pubDate>Thu, 26 Aug 2021 00:35:49 +0900</pubDate>
    </item>
    <item>
      <title>Hexo 포스팅 스타일 설정하기</title>
      <link>https://mishka.tistory.com/10</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;hexo를 통해 포스팅을 하다보니 테마커스텀에 대한 욕구가 스멀스멀 솟아올랐다. ㅎㅎㅎ;;&lt;br /&gt;메인 이미지도 그리고 아이콘이나 위젯들의 위치도 이곳 저곳 옮겨보기도 하면서&lt;br /&gt;나름 나만의 스타일로 하나씩 바꿔나가는 재미가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 내용들은 현재 제가 사용하고 있는 icarus 테마를 기준으로 작성한것입니다.&lt;br /&gt;다른 테마에서는 다른 방법이 있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 블로그를 조금씩 꾸미고 여러가지 포스팅들도 올리고 하다가 한 가지 의문이 생겼다.&lt;br /&gt;바로 포스트 리스트에서 사용하는 위젯들을 &lt;code&gt;_config.yml&lt;/code&gt; 에서 설정으로 잡아 주었는데&lt;br /&gt;포스트 상세페이지에서도 똑같이 노출이 되다보니 포스트 본문의 가로 넓이가 너무 적게 보인다는 점이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;code&gt;_config.yml&lt;/code&gt;에 있는 설정들을 하나씩 뜯어보고 해당 기능이 있는지 찾아보았지만&lt;br /&gt;원하는 기능은 없는것 같아서 기운없이 여러 블로그를 돌아다니고 있는 도중에 내가 사용하는 테마를 같이 사용하는&lt;br /&gt;블로그에서 리스트와 포스트 상세페이지에 스타일이 다른 것을 발견하고는 다시 이것 저것 세팅을 바꿔보고&lt;br /&gt;해당 블로그와 코드도 비교해 보면서 방법을 찾기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 정말 우연히 내 포스트와 다른 점을 발견하게 됐고 방법을 찾았다.&lt;br /&gt;나와 같은 필요를 느끼고 있는 분들을 위해 그 방법을 간단하게 정리해 보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 포스팅방법이 궁금하신 분들은 지난번에 작성한 hexo 포스팅 방법을 참고하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 실험끝에 발견한 방법은 그러한 세팅이 따로 존재하는 것이 아니라 포스팅을 작성할때&lt;br /&gt;머리말(Front matter)에 해당 파일의 정보를 입력해주면 되는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머리말은 지난 포스트에서 설명했듯이 포스트 생성시 포스트 최상단에 자동으로 생성이 됩니다.&lt;br /&gt;이곳에는 카테고리,태그,썸네일등을 지정하여 포스트마다 다르게 노출되는 값들을 입력하는 곳이라 생각하였고&lt;br /&gt;머리말을 이용하여 위젯이나 레이아웃을 변경할 수 있을것이라고는 미처 생각하지 못했었다.&lt;br /&gt;그렇게 한참 다른 곳을 해메이다가 우연히 머리말의 입력값이 다른 것을 발견하고는 유레카를 외쳤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스트작성시 머리글에 카탈로그,카테고리,태그 클라우드 위젯을 추가하고 싶었고&lt;br /&gt;포스팅 우측에 고정으로 놓고 싶었다. 그래서 아래와 같은 설정을 머리말에 추가해 주었다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;toc: true
widgets:
  - type: toc
    position: right
  - type: category
    position: right
  - type: tagcloud
    position: right
sidebar:
  right:
    sticky: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 옵션을 살펴보면&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;toc: true
# 카탈로그 옵션을 사용하기 위해 true옵션을 사용하였다.

widgets:
  - type: toc
    position: right
  - type: category
    position: right
  - type: tagcloud
    position: right  
# 위젯의 타입을 지정하여주고 위치를 오른쪽으로 지정하였다.   

sidebar:
  right:
    sticky: true
# sticky 옵션은 true로 하여 우측에 고정을 시켜주었다.    &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 내용들을 머리말에 추가하여 원하는 스타일로 꾸밀 수 있었다. 그리고 매번 포스팅을 할때&lt;br /&gt;위의 내용을 작성하면 번거롭기 때문에 스캐폴드에 해당내용을 추가해서 자동으로 추가되게 세팅하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상으로 hexo 포스팅 스타일 설정하기 포스팅을 마칩니다. 해당글로 많은 분들이 조금이나마 쉽게 hexo 블로그를 접하고 꾸밀 수 있기를 바랍니다. 도움이 되었다면 댓글을 살포시 남겨주시면 감사하겠습니다.&lt;/p&gt;</description>
      <category>Programming</category>
      <category>hexo</category>
      <category>포스팅스타일</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/10</guid>
      <comments>https://mishka.tistory.com/10#entry10comment</comments>
      <pubDate>Thu, 26 Aug 2021 00:20:54 +0900</pubDate>
    </item>
    <item>
      <title>Hexo에 Disqus를 사용하여 댓글 기능 세팅하기</title>
      <link>https://mishka.tistory.com/9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 블로그의 세팅중 한가지인 댓글 기능을 세팅하는 방법에 대해 포스팅해보겠습니다. 사실 hexo 테마들의 대부분은 여러 댓글 시스템들을 간편하게 세팅 할 수 있도록 되어있습니다.&lt;br /&gt;그 중에 &lt;code&gt;디스커스(Disqus)&lt;/code&gt;를 사용하여 &lt;span style=&quot;background-color: #9d9d9d;&quot;&gt;댓글&lt;/span&gt; 기능을 세팅해보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디스커스(Disqus)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스커스는 &lt;code&gt;소셜 댓글 서비스&lt;/code&gt;의 하나입니다. 소셜 댓글 서비스란 소셜미디어(SNS)를 활용한 댓글 시스템으로 페이스북,트위터 와 같은 SNS와 연동해서 댓글을 달 수 있게 만들어 주는 서비스입니다. 소셜 댓글 서비스를 활용하여 댓글을 달면 동시에 해당 댓글이 자신이 연동한 SNS에도 발행이 됩니다.&lt;br /&gt;별도의 댓글시스템을 구현할 필요없이 디스커스에서 제공하는 위젯을 설치함으로 사용 할 수 있는 것이 장점입니다.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설치순서&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Disqus &lt;b&gt;회원가입&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Settings &amp;gt; &lt;b&gt;profile, Account&lt;/b&gt; 수정&lt;/li&gt;
&lt;li&gt;Add Disqus To Site &amp;gt; &lt;b&gt;I Want to comment on site&lt;/b&gt; 사이트 추가&lt;/li&gt;
&lt;li&gt;Account &amp;gt; &lt;b&gt;Username&lt;/b&gt; 확인 (shortname)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;_config.yml&lt;/b&gt; 설정 변경&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회원가입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Disqus 회원이 아니라면 회원가입이 필요합니다.&lt;br /&gt;&lt;a href=&quot;https://disqus.com&quot;&gt;Disqus 사이트&lt;/a&gt;에서 회원가입을 진행합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/disqus_main.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정보 수정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Settings 에 &lt;code&gt;profile&lt;/code&gt; 과 &lt;code&gt;Account&lt;/code&gt;에서 필요한 정보들을 수정해 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/disqus_settings.jpg&quot; alt=&quot;&quot; /&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;231&quot; data-origin-height=&quot;358&quot; data-filename=&quot;disqus_settings.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5ubjW/btrdaF4HD8F/DqQPD38sUOkmQvdXh1s3O0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5ubjW/btrdaF4HD8F/DqQPD38sUOkmQvdXh1s3O0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5ubjW/btrdaF4HD8F/DqQPD38sUOkmQvdXh1s3O0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5ubjW%2FbtrdaF4HD8F%2FDqQPD38sUOkmQvdXh1s3O0%2Fimg.jpg&quot; data-origin-width=&quot;231&quot; data-origin-height=&quot;358&quot; data-filename=&quot;disqus_settings.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사이트 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측 상단의 &lt;code&gt;Add Disqus To Site&lt;/code&gt; 에 들어가서 &lt;code&gt;I Want to comment on site&lt;/code&gt; 를 클릭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 메인에서 &lt;code&gt;GET STARTED&lt;/code&gt; 에 들어가서 &lt;code&gt;I Want to comment on site&lt;/code&gt; 를 클릭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 정보들을 입력 후 &lt;code&gt;Select a plan&lt;/code&gt; 에서 &lt;code&gt;Basic&lt;/code&gt; 을 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;1069&quot; data-origin-height=&quot;828&quot; data-filename=&quot;disqus_select_plan.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4EUmB/btrc8Aiba38/pdA3pwq5PvlPIi1FVr9bmk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4EUmB/btrc8Aiba38/pdA3pwq5PvlPIi1FVr9bmk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4EUmB/btrc8Aiba38/pdA3pwq5PvlPIi1FVr9bmk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4EUmB%2Fbtrc8Aiba38%2FpdA3pwq5PvlPIi1FVr9bmk%2Fimg.jpg&quot; data-origin-width=&quot;1069&quot; data-origin-height=&quot;828&quot; data-filename=&quot;disqus_select_plan.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;img src=&quot;/img/disqus_select_plan.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Select Platform&lt;/code&gt; 단계에서는 맨 아래의 &lt;code&gt;universal Code&lt;/code&gt;를 선택해 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/disqus_universal_code.jpg&quot; alt=&quot;&quot; /&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;140&quot; data-filename=&quot;disqus_universal_code.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZKuEP/btrc7UVq9O9/6I28jDfuEvADcholxfb8Jk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZKuEP/btrc7UVq9O9/6I28jDfuEvADcholxfb8Jk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZKuEP/btrc7UVq9O9/6I28jDfuEvADcholxfb8Jk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZKuEP%2Fbtrc7UVq9O9%2F6I28jDfuEvADcholxfb8Jk%2Fimg.jpg&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;140&quot; data-filename=&quot;disqus_universal_code.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기는 테마마다 조금 다릅니다. 디스커스를 지원하는 테마라면 &lt;code&gt;configure&lt;/code&gt; 누르고 사이트 세팅을 마친다음&lt;br /&gt;_config.yml 의 설정만 변경 해주면 되고 안되어 있다면 소스를 추가해 주어야 합니다. 그 때 아래와 같은 코드를 사용합니다.&lt;br /&gt;&lt;a href=&quot;https://disqus.com/admin/install/platforms/universalcode/&quot;&gt;Installation instructions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;331&quot; data-filename=&quot;disqus_code.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6XqTG/btrc7OuCAim/vaXfhKQcKdDgGfJO2DiusK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6XqTG/btrc7OuCAim/vaXfhKQcKdDgGfJO2DiusK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6XqTG/btrc7OuCAim/vaXfhKQcKdDgGfJO2DiusK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6XqTG%2Fbtrc7OuCAim%2FvaXfhKQcKdDgGfJO2DiusK%2Fimg.jpg&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;331&quot; data-filename=&quot;disqus_code.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;img src=&quot;/img/disqus_code.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;계정정보 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Account 에서 &lt;code&gt;Username(shortname)&lt;/code&gt;을 확인하여 줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;_config.yml 설정&lt;/h2&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;comment:
    type: disqus
    shortname: disqus 계정이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 현재 제가 사용하는 &lt;code&gt;icarus&lt;/code&gt; 테마의 _config.yml의 양식이고 테마마다 조금씩 다를수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배포&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;hexo d -g&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포를 시켜주고 아래와 같이 나온다면 성공입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;380&quot; data-filename=&quot;disqus_comment.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7NaM8/btrc8hwBlLy/mjUmj633RXZkl5DKJ7axh0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7NaM8/btrc8hwBlLy/mjUmj633RXZkl5DKJ7axh0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7NaM8/btrc8hwBlLy/mjUmj633RXZkl5DKJ7axh0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7NaM8%2Fbtrc8hwBlLy%2FmjUmj633RXZkl5DKJ7axh0%2Fimg.jpg&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;380&quot; data-filename=&quot;disqus_comment.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오류체크&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;icarus&lt;/code&gt;테마는 기본적으로 disqus 댓글 시스템을 지원하고 있어서 위와같이 세팅하면 정상적으로 댓글기능이 작동해야 하지만&lt;br /&gt;무엇이 문제였는지 시스템 자체는 연결되었는데 댓글을 작성하는 위젯이 제대로 작동하지 않았다 ㅠㅜ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;155&quot; data-filename=&quot;disqus_error.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXqbSV/btrdevmAI3I/uT1BV1FAJlD04KoRAfTp9K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXqbSV/btrdevmAI3I/uT1BV1FAJlD04KoRAfTp9K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXqbSV/btrdevmAI3I/uT1BV1FAJlD04KoRAfTp9K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXqbSV%2FbtrdevmAI3I%2FuT1BV1FAJlD04KoRAfTp9K%2Fimg.jpg&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;155&quot; data-filename=&quot;disqus_error.jpeg&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;icarus&lt;/code&gt;테마에서 제공하는 포스팅에서도 comment Plugin 의 설정만을 변경해 주면된다고 나오는데 말이다.&lt;br /&gt;&lt;a href=&quot;https://blog.zhangruipeng.me/hexo-theme-icarus/Plugins/Comment/disqus-comment-plugin/&quot;&gt;Disqus Comment Plugin&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지를 시도해 보다 결국 소스코드 자체를 변경하는 것으로 해결하였다.&lt;br /&gt;icarus 테마의 경우 &lt;code&gt;disqus.ejs&lt;/code&gt; 파일의 내용을 &lt;code&gt;universal Code&lt;/code&gt;에서 제공하는 코드로 변경하니 정상적으로 작동되었다.&lt;br /&gt;테마마다 약간의 설정이 다를수 있으니 참고정도만 하시면 되겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언어선택 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 disqus에서는 한글을 지원하지 않는 것으로 변경되었다고 합니다.&lt;br /&gt;그래서 편법으로 설정하는 방법이 있는데 편법이라 방법이 나와있는 포스팅을 공유하는 것으로 대체하겠습니다.&lt;br /&gt;&lt;a href=&quot;https://jungjoongi.com/2018/10/30/disqus-korean-setting/&quot;&gt;디스커스 한글 세팅을 해보자&lt;/a&gt;&lt;/p&gt;</description>
      <category>Programming</category>
      <category>Disqus</category>
      <category>hexo</category>
      <author>Mishka</author>
      <guid isPermaLink="true">https://mishka.tistory.com/9</guid>
      <comments>https://mishka.tistory.com/9#entry9comment</comments>
      <pubDate>Thu, 26 Aug 2021 00:09:01 +0900</pubDate>
    </item>
  </channel>
</rss>