<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>parkMoen.log</title>
        <link>https://velog.io/</link>
        <description>게시글에 잘못된 부분이 있으면 댓글로 알려주시면 빠르게 수정 및 수용도 하겠습니다. 🥲</description>
        <lastBuildDate>Thu, 23 Jul 2026 02:24:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>parkMoen.log</title>
            <url>https://images.velog.io/images/park-moen/profile/55a9c80a-70f1-4815-bb4d-749032dbdba8/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. parkMoen.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/park-moen" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[인성 면접 질문 리스트]]></title>
            <link>https://velog.io/@park-moen/%EC%9D%B8%EC%84%B1-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%A6%AC%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@park-moen/%EC%9D%B8%EC%84%B1-%EB%A9%B4%EC%A0%91-%EC%A7%88%EB%AC%B8-%EB%A6%AC%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Thu, 23 Jul 2026 02:24:54 GMT</pubDate>
            <description><![CDATA[<h2 id="⭐-이-면접의-핵심-반드시-기억">⭐ 이 면접의 핵심 (반드시 기억)</h2>
<blockquote>
<p>서비스 개발 본부장(2차 면접에 들어오는 인물)이 직무 인터뷰 Q10 &quot;본부 내 신규채용을 할 때 자주 하는 질문은?&quot;에서 밝힌 채용 절차·평가 기준 원문입니다. <strong>이 문서 전체가 아래 두 축(의사소통·인성)을 근거로 구성되었습니다.</strong></p>
</blockquote>
<h3 id="채용-절차-본부장-원문">채용 절차 (본부장 원문)</h3>
<ul>
<li>&quot;본부 신규 채용 절차는 면접이 두 번 있어요. 1차는 실무진 면접, 2차에서는 임원진 면접인데 저는 보통 2차 면접으로 들어가고요.&quot;</li>
<li>&quot;1차에선 실무진들이 함께 일을 할 수 있는 사람인가 아닌가 판단하고, <strong>피드백이 좋을 경우 2차에서는 거의 채용을 하긴 해요.</strong>&quot;</li>
</ul>
<p>→ 나는 이미 1차를 통과했으므로, 2차는 &quot;기술 재검증&quot;이 아니라 아래 두 가지를 확인하는 자리입니다. <strong>위축될 필요 없이, 대화가 되는 사람이라는 것만 보여주면 됩니다.</strong></p>
<h3 id="2차에서-보는-두-가지-우선순위-순">2차에서 보는 두 가지 (우선순위 순)</h3>
<ol>
<li><strong>의사소통 (첫 번째)</strong> — &quot;임원진 면접을 볼 때 첫번째로 주요 깊게 보려고 하는 것은 의사소통 부분을 보는 것 같아요. 저는 대화를 통해서 문제점을 찾아가고 해결을 해나가려고 하는데 대화가 안 되는 분이라고 판단되면 실무진 면접에서 좋은 평가를 받았다고 하더라도, 과감하게 (탈락 의견을) 실무진들에게 이야기해요.&quot;
→ <strong>1차 기술 평가가 좋아도 &quot;대화가 안 되면&quot; 결과가 뒤집힐 수 있는 유일한 요소.</strong> 답변의 화려함보다 &quot;질문을 정확히 이해하고, 명확하게 대화가 통하는가&quot;가 최우선입니다.</li>
<li><strong>인성 (두 번째)</strong> — &quot;두 번째는 인성 질문을 많이 하는 것 같아요.&quot;
→ 이 문서의 1~2순위 질문 대부분이 인성 질문인 이유입니다.</li>
</ol>
<h3 id="그래서-답변할-때-의사소통-관점">그래서 답변할 때 (의사소통 관점)</h3>
<ul>
<li>&quot;논리적으로 대화가 되는 사람&quot;이라는 인상이 최우선입니다 — 화려한 답보다 명확한 전달력.</li>
<li>질문의 의도를 못 알아들었으면 되묻는 것이 &quot;대화가 안 되는 것&quot;보다 낫습니다.</li>
</ul>
<h3 id="본부장이-원하는-개발자상-같은-인터뷰-q11-직무-희망자에게-한마디-원문">본부장이 원하는 개발자상 (같은 인터뷰 Q11 &quot;직무 희망자에게 한마디&quot; 원문)</h3>
<p>의사소통·인성과 함께, 본부장은 이상적인 개발자상을 구체적으로 밝혔습니다. <strong>이 인재상에 부합하는 답변을 하면 강한 인상을 줄 수 있습니다.</strong></p>
<ul>
<li>&quot;개발자도 분류가 되는 부분이 있거든요. <Developer vs Coder>. 보통 신입사원들을 보면 시키는 것들 위주로만 많이 해요. … 그것에만 익숙해지면 더 이상의 성장은 없거든요.&quot;</li>
<li>&quot;제가 1년 차 때 혼자서 프로젝트를 처음부터 끝까지 해보다 보니까 그 순간에 오는 깨우침 같은 것들이 있더라고요.&quot;</li>
<li>&quot;연차가 적을 때는 실패해도 좋으니까, 욕먹어도 좋으니까 이것저것 시도해 보는 것이 좋아요. … 배우는 기간일 때 혼자서 이것저것 많이 경험하며, 실패도 경험을 해보고 다양한 경험을 얻는 개발자가 되면 좋겠다.&quot;</li>
<li>&quot;항상 모든 순간이 어렵고, 두렵거든요. 그 두려움을 잘 극복할 수 있으면 좋을 것 같아요.&quot;</li>
</ul>
<p>→ <strong>본부장이 원하는 사람 = 시키는 것만 하는 Coder가 아니라, 주도적으로 다양하게 시도하고 실패를 두려워하지 않는 Developer.</strong></p>
<p><strong>이 개발자상을 답변에 활용하는 법</strong></p>
<ul>
<li><strong>공백기(Q8)를 강점으로 전환</strong>: 공백기를 &quot;혼자서 이것저것 시도하고 실패도 겪으며 다양하게 경험한 기간&quot;으로 프레이밍하면, 약점이 오히려 본부장의 인재상과 정확히 맞아떨어집니다. &quot;시키는 것만 익숙해지면 성장이 없다&quot;는 본부장의 말과, 스스로 프로젝트를 파고든 공백기 경험을 연결하세요.</li>
<li><strong>주도성의 근거 제시</strong>: UNIBUSK 렌더링 문제를 스스로 측정해 파고든 것, Dessert 모노레포 전환을 먼저 제안한 것 등이 &quot;시키는 것만 하는 Coder가 아니다&quot;라는 직접 증거입니다 (<code>project-experience-notes.md</code>).</li>
<li><strong>정면으로 겨냥할 질문</strong>: Q14(지시 vs 스스로 찾아서), Q15(잇뉴에 맞는 이유)에서 이 개발자상을 직접 조준해 답하세요.</li>
<li>실패·시행착오는 숨기지 마세요 — 본부장이 직접 권장한 태도입니다.</li>
</ul>
<h2 id="🔴-1순위--반드시-준비">🔴 1순위 — 반드시 준비</h2>
<p>잇뉴 인터뷰 최빈출 + 첫 5분 안에 거의 확실히 나오는 질문입니다.</p>
<h3 id="q1-1분-자기소개">Q1. 1분 자기소개</h3>
<p>&quot;무엇을 했는지&quot;보다 &quot;왜 그렇게 판단했는지&quot;가 잘 드러나는지 점검. 상세한 실무진(1차)용 버전은 <code>cover-letter.md</code> 참고.</p>
<p><strong>2차 임원용 초안</strong> (기술 용어를 덜어내고 사용자·팀 영향과 주도성을 강조한 버전)</p>
<blockquote>
<p>안녕하세요. 저는 문제를 추측으로 넘기지 않고, 원인을 직접 측정해서 확인한 뒤 개선하는 신입 개발자 박명재입니다.</p>
<p>UNIBUSK 프로젝트의 고도화 단계에서 페이지별로 렌더링 성능을 측정하던 중, 공연 등록 폼에서 글자 하나를 입력할 때마다 관련 없는 화면 전체가 불필요하게 다시 그려지는 문제를 발견했습니다. 측정 도구로 정확히 어느 부분에서 문제가 발생하는지 확인했고, 원인을 짚어 고쳤습니다. 그 결과 불필요한 리렌더링 없이 입력값만 반영되게 됐습니다.</p>
<p>다른 프로젝트에서는 두 개의 서비스를 함께 운영하다 보니 팀원마다 코드를 다르게 작성하는 문제가 있었습니다. 저는 공통으로 쓰는 UI를 하나로 묶고, 파일을 어디에 둘지에 대한 기준을 문서로 만들어 팀이 같은 방식으로 코드를 읽고 고칠 수 있게 했습니다.</p>
<p>두 프로젝트를 진행하면서 스스로 문제를 발견해 원인을 확인하기도 하고, 팀원들과 논의해 기준을 만들기도 했습니다. 그 과정에서, 문제를 해결하는 것만큼 그 해결을 팀이 함께 유지할 수 있는 형태로 남기는 것도 중요하다는 걸 배웠습니다. 잇뉴에서도 이런 방식으로, 사용자에게 도움이 되는 부분을 확인하고 팀과 함께 개선해나가는 개발자가 되고 싶습니다.</p>
</blockquote>
<h3 id="q2-잇뉴에-지원한-이유--어떤-점에-매력을-느꼈나요">Q2. 잇뉴에 지원한 이유 / 어떤 점에 매력을 느꼈나요?</h3>
<p><strong>2차 임원용 초안</strong> (오래된 회사 이해를 README.md 기준으로 갱신하고, 나열식 문장을 줄인 버전)</p>
<blockquote>
<p>잇뉴에 지원한 이유는, 복잡한 기술을 실제 사람들이 쓰는 서비스 문제로 풀어내는 방향이 인상적이었기 때문입니다.</p>
<p>제가 이해한 바로는 제주 당일배송 서비스 ERLN은 물류 운영을 다루고, MemoryMint는 증서를 만들고 발급·관리하는 서비스이며, MinTrigger는 그 블록체인 기술을 다른 서비스에 API 형태로 제공하는 역할을 합니다.</p>
<p>2차 면접을 준비하면서 포트폴리오와 블로그를 더 찾아봤는데, 두 가지가 인상 깊었습니다. 하나는 포트폴리오였는데, 자체 기술을 만들어보는 데 그치지 않고 공공기관이나 기업과의 프로젝트를 통해 실제 운영 문제를 해결하는 데까지 이어간다는 점이었습니다. 다른 하나는 블로그의 직무 인터뷰였는데, 직급 없이 대화하는 분위기와 구성원들이 겪은 일을 솔직하게 이야기하는 모습을 보면서 이 팀과 함께 만들어가고 싶다는 마음이 들었습니다.</p>
<p>저는 UNIBUSK와 Real World에서 인증 처리, 서버 데이터와 화면 상태를 연결하는 일을 했고, Dessert Front Dashboard에서는 기능 구현보다 모노레포 구조 설계와 온보딩 자료 작성에 집중했습니다. 공용 UI 패키지를 분리하고 FSD 컨벤션을 문서화해서, 새로 합류한 팀원이 코드를 빠르게 이해하고 같은 기준으로 작업할 수 있게 했습니다.</p>
<p>잇뉴에서도 기술이 실제로 사용자나 운영자에게 도움이 되는 형태로 전달되는지 확인하고, 이 팀과 함께 만들어가고 싶습니다.</p>
</blockquote>
<p><strong>꼬리 질문 — 블록체인이 무엇인가요?</strong></p>
<blockquote>
<p>블록체인은 여러 참여자가 같은 기록을 공유하고 검증하는 분산형 기록 구조라고 이해하고 있습니다. 기록은 이전 기록과 암호학적으로 연결돼 있어서, 이미 쌓인 기록을 임의로 바꾸면 변경 사실을 검증할 수 있습니다.</p>
<p>그래서 여러 기관이나 사용자가 같은 기록을 신뢰해야 하는 인증서 발급, 이력 추적, 위변조 검증에 활용할 수 있습니다. 잇뉴의 MemoryMint나 MinTrigger도 이런 신뢰와 검증의 문제를 해결하는 데 블록체인을 활용하는 것으로 이해하고 있습니다.</p>
<p>다만 블록체인에 기록했다고 해서 처음 입력한 정보가 항상 사실이라는 뜻은 아니고, 비용·성능·개인정보 측면의 제약도 있습니다. 그래서 중앙 데이터베이스로 충분한 문제인지, 여러 주체가 기록을 함께 검증해야 하는 문제인지를 먼저 판단해야 한다고 생각합니다.</p>
</blockquote>
<h3 id="q3-개발자가-되기로-한-이유는-무엇인가요">Q3. 개발자가 되기로 한 이유는 무엇인가요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>개발을 하게 된 계기는 코드를 작성한 결과가 화면에 바로 반영되는 경험이 재미있었기 때문입니다. 작은 변경도 사용자가 보는 화면과 동작을 바꿀 수 있다는 점에서, 제가 만든 결과를 즉각 확인하고 개선할 수 있다는 매력을 느꼈습니다.</p>
<p>프로젝트를 진행하면서는 단순히 화면 하나를 만드는 데서 더 나아가, 서비스를 직접 만들고 실제 사용자의 흐름을 경험해보고 싶다는 목표가 생겼습니다. 사용자가 서비스를 어떻게 이용하는지, 어디에서 불편을 느끼는지, 사용량이 늘어날 때 어떤 문제가 생기는지를 보고 개선하는 개발자가 되고 싶습니다.</p>
<p>아직 많은 사용자가 사용하는 서비스를 운영해본 경험은 없지만, 그래서 더욱 실제 사용자와 트래픽이 있는 환경에서 기능을 만들고 개선하는 경험을 쌓고 싶습니다. 프론트엔드는 제가 만든 변화가 사용자 경험으로 가장 빠르게 드러나는 영역이라 생각해 계속 성장하고 싶은 분야입니다.</p>
</blockquote>
<h3 id="q4-최근이전-프로젝트에서-어떤-업무를-담당하셨나요">Q4. 최근(이전) 프로젝트에서 어떤 업무를 담당하셨나요?</h3>
<p><code>project-experience-notes.md</code> — UNIBUSK/Dessert/Real World 담당 범위. 팀 규모·역할은 이력서·<code>source/portfolio.md</code> 원문 기준으로 정확히 확인.</p>
<p><strong>2차 임원용 초안</strong> (잇뉴 직무 인터뷰의 &quot;개념 먼저 → 자연스러운 예시&quot; 구조를 참고하되, 격식체로 정리한 버전)</p>
<blockquote>
<p>프로젝트마다 제가 맡은 역할은 조금씩 달랐습니다. 화면을 직접 만드는 역할을 맡기도 했고, 팀이 함께 일할 수 있는 구조를 만드는 역할을 맡기도 했습니다.</p>
<p>UNIBUSK는 버스킹 공연 장소를 찾고 홍보하는 서비스로, 8명 팀에서 프론트엔드 두 명 중 한 명으로 참여해 공연 등록·상세 화면과 카카오 로그인, 프로필 영역을 개발했습니다.</p>
<p>Dessert Front Dashboard는 역할이 달랐습니다. 12명 규모의 팀에서 저는 화면 개발보다 모노레포 구조를 설계하고 팀이 지킬 컨벤션을 문서화하는 역할을 주로 맡았고, 그 위에서 관리자 대시보드와 공용 패키지 개발도 함께 진행했습니다.</p>
<p>Real World는 블로그 플랫폼으로, 프론트엔드부터 백엔드까지 혼자 설계하고 개발한 프로젝트입니다.</p>
</blockquote>
<p><strong>cover-letter.md/이전 초안과 달라진 점</strong>:</p>
<ul>
<li>프로젝트를 나열하고 &quot;정리하면&quot;으로 봉합하는 보고서식 구조 제거 — 잇뉴 직무 인터뷰(김은정 Q4, 이재현 Q2)는 역할을 개념으로 먼저 정의하고 경험을 자연스럽게 예시로 흘려보내는 구조였음</li>
<li>다만 블로그 인터뷰 특유의 캐주얼한 구어체(&quot;<del>것 같아요&quot;, &quot;</del>했어요&quot;)는 가져오지 않음 — 편한 분위기에서 나온 발언처럼 들려 면접에서는 확신 없어 보일 위험. <strong>구조는 가져오되 어투는 면접용 격식체 유지</strong></li>
<li>팀 규모(8명/12명/1인)를 먼저 말해 전체를 혼자 한 것처럼 들리는 과장을 피함</li>
</ul>
<blockquote>
<p><strong>말할 때</strong>: 세부 문제 해결 과정(렌더링 최적화, Hydration Pattern 등)은 Q5·Q13 같은 꼬리질문에서 풀 것.</p>
</blockquote>
<h3 id="q5-가장-기억에-남거나-인상-깊었던-프로젝트는">Q5. 가장 기억에 남거나 인상 깊었던 프로젝트는?</h3>
<p><code>project-experience-notes.md</code> 참고. Q1(자기소개)에서 이미 UNIBUSK 얘기를 썼으므로 Q5는 Dessert Front Dashboard로 잡되, 실제로 있었던 팀원과의 논의(초기 아키텍처 설계 중 FSD widgets 레이어를 두고 의견이 갈렸던 것)를 중심으로 구성. 처음엔 본인도 공식 문서 기준이 맞다고 생각했지만, 팀원과의 대화 후 판단이 실제로 바뀐 경험이라 &quot;듣고 그냥 수긍&quot;이 아니라 &quot;대화를 통해 문제를 함께 풀어간&quot; 사례로 다룸.</p>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>가장 기억에 남는 프로젝트는 Dessert Front Dashboard에서 팀 컨벤션 문서를 만들어간 과정입니다.</p>
<p>초기 폴더 아키텍처를 설계할 시점에, 저는 FSD 공식 문서를 기준으로 widgets 레이어까지 포함한 구조가 맞다고 생각했습니다. 그런데 팀원이, 지금 프로젝트 규모에서는 widgets 레이어에 실제로 들어갈 내용이 많지 않을 텐데 지금부터 나누면 폴더 구조만 복잡해지고 관리 부담만 커질 수 있다는 의견을 제시했습니다.</p>
<p>이 의견에 대해서 다른 팀원들의 의견을 들어보고, 저 역시 지금 규모와 관리 비용을 고려했을 때 팀원의 의견이 맞다고 판단했습니다. 그래서 widgets 레이어를 제거한 구조로 조정했고, 변경된 기준을 온보딩 자료·컨벤션 문서에 담아 팀 전체가 같은 구조로 코드를 작성할 수 있게 했습니다.</p>
<p>이 경험을 통해, 팀이 함께 유지할 수 있는 기준을 만들려면 제가 알고 있는 원칙만큼 팀원들의 실제 상황 판단도 함께 반영해야 한다는 걸 배웠습니다.</p>
</blockquote>
<p><strong>이 프로젝트를 고른 이유</strong>:</p>
<ul>
<li>Q1과 다른 프로젝트라 &quot;레퍼토리가 하나&quot;라는 인상을 피할 수 있음</li>
<li>widgets 레이어 결정 하나로 범위를 좁히지 않고, &quot;컨벤션 문서를 만드는 과정에서 팀원 의견을 반영한 결과&quot;라는 더 큰 흐름으로 프레이밍해, 일회성 사건이 아니라 일관된 협업 태도로 보이게 함</li>
<li>&quot;제 생각이 항상 맞는 건 아니다&quot;라는 자기 의심형 문장 대신, &quot;팀 전체가 같은 기준으로 코드를 작성하게 된 결과&quot;를 앞세워 더 자신감 있는 톤으로 전달</li>
<li>&quot;공식 문서가 틀렸다&quot;가 아니라 &quot;기준은 맞지만 상황에 맞게 적용하는 방식이 다르다&quot;는 구조라, 원칙을 존중하면서도 유연하게 판단한다는 인상을 줄 수 있음</li>
<li>팀원 의견을 듣고 재고하는 과정이 들어 있어, 본부장이 말한 &quot;대화를 통해 문제를 찾고 해결해나가는&quot; 의사소통 정의에 부합함</li>
</ul>
<p><strong>이전 초안과 달라진 점</strong>:</p>
<ul>
<li>이전 버전은 &quot;팀원이 의견을 냈고 저는 동의했다&quot;는 즉시 수긍형 구조라 단조롭다는 피드백을 반영해, 본인의 초기 입장(공식 문서 기준)과 그 입장이 바뀐 계기(팀 규모 고려)를 모두 넣어 실제 대화·재고 과정이 드러나게 했음</li>
<li>그 다음 &quot;제가 확신했던 방식도 대화로 더 나은 방향으로 바뀌었다&quot;는 표현은 공식 문서가 틀렸다는 뉘앙스를 풍겨서, &quot;공식 문서는 여전히 맞는 기준이지만 프로젝트 규모에 맞게 유연하게 적용했다&quot;는 쪽으로 수정 — 정답을 아는 것과 상황에 맞게 적용하는 것을 별개 역량으로 구분</li>
<li>도입부·결론이 &quot;초기 아키텍처 설계&quot;로 좁아져 있던 것을 수정: 실제 기억은 &quot;아키텍처 설계와 컨벤션 문서를 만드는 과정 전체에서, 내 의견만 고집하지 않고 팀원 의견을 수용한 것&quot;이므로, widgets 레이어는 그 과정의 대표 예시(&quot;예를 들어&quot;)로 위치를 옮기고 도입부·결론은 &quot;과정 전체&quot;로 넓힘</li>
<li><strong>&quot;제가 처음 생각한 방식이 항상 맞는 건 아니어서&quot;라는 자기 의심형 도입부를 수정</strong>: 겸손을 강조할 필요 없이, &quot;팀원과 논의해 팀 전체가 같은 기준으로 코드를 작성할 수 있게 만든 결과&quot;를 앞세우는 쪽이 더 자연스럽다는 피드백을 반영. widgets 레이어 논의는 그 결과를 만들어낸 과정의 한 예시로 유지</li>
<li>모노레포·공유 패키지 관련 세부 내용은 이 답변에서 제외 — Q11(협업 방식)나 Q15(잇뉴에 맞는 이유) 같은 꼬리질문에서 다룰 것</li>
</ul>
<blockquote>
<p><strong>말할 때</strong>: &quot;공식 문서가 틀렸다&quot;는 인상을 주지 않도록 주의. 핵심은 &quot;컨벤션 문서를 만드는 과정에서, 내가 세운 기준이라도 팀원 의견을 듣고 반영해서 팀 전체가 같은 기준을 갖게 했다&quot;는 결과 중심 톤 유지. 꼬리질문으로 &quot;왜 그 의견이 맞다고 판단했나요?&quot;가 들어오면 &quot;프로젝트 초기라 레이어를 세분화할 만큼 화면·기능이 많지 않았고, 오히려 관리 부담만 커질 수 있어 동의했다&quot; 정도로 답할 것.</p>
</blockquote>
<h3 id="q6-본인의-강점은-무엇인가요">Q6. 본인의 강점은 무엇인가요?</h3>
<p>인성면접 필수 질문 + 본부장이 &quot;인성 질문 많이 한다&quot;고 언급</p>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>저의 강점은 문제가 생겼을 때 바로 해결책부터 적용하기보다, 먼저 가설을 세우고 검증하면서 원인을 좁혀가는 점입니다.</p>
<p>UNIBUSK에서 카카오 로그인은 배포 환경에서는 정상 동작했지만, 로컬 환경에서는 쿠키가 저장되지 않는 문제가 있었습니다. 저는 두 환경의 차이가 도메인과 요청 흐름에 있을 것이라고 가설을 세웠습니다. 이후 로그인 전후 요청과 쿠키 저장 위치를 비교해, 프론트엔드와 백엔드의 도메인이 달라 백엔드가 내려준 쿠키가 로컬 환경의 클라이언트에 저장되지 않는다는 점을 검증했습니다.</p>
<p>확인한 내용을 백엔드 팀원에게 공유하고, 쿠키를 바로 전달하던 기존 흐름 대신 임시 인증 코드를 전달하도록 설계를 함께 수정했습니다. 이후 프론트엔드에서 같은 출처의 요청으로 인증 코드를 교환해, 로컬과 배포 환경 모두에서 정상적으로 로그인되는 것을 확인했습니다.</p>
<p>저는 혼자 빠르게 답을 정하기보다, 검증한 사실을 바탕으로 관련된 사람과 문제를 다시 정의하고 해결 방향을 만드는 방식을 중요하게 생각합니다.</p>
</blockquote>
<h3 id="q7-본인의-약점은-무엇인가요">Q7. 본인의 약점은 무엇인가요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>제 약점은 새로운 문제를 만나면 해결에 필요한 범위보다 더 깊게 파고들어 작업 시간이 길어질 수 있다는 점입니다. Real World 프로젝트에서 처음으로 백엔드를 직접 구현할 때 이 점을 경험했습니다. 3주 안에 MVP를 완성하려 했지만, 백엔드 구조와 구현 방식을 충분히 이해하려는 과정에 예상보다 많은 시간을 썼고, 결과적으로 일정이 약 1주 늦어졌습니다. 결과물의 완성도도 중요하지만, 정해진 시간 안에 사용자에게 필요한 핵심 기능을 먼저 전달하는 판단이 부족했다는 점을 배웠습니다.</p>
<p>이후 UNIBUSK처럼 마감이 명확한 팀 프로젝트에서는 개발 중 더 깊게 학습하고 싶은 주제가 생기면 바로 파고들기보다 핵심 용어와 참고 링크를 Notion에 기록해 두었습니다. 우선순위 작업과 프로젝트 기간이 끝난 뒤 그 내용을 다시 학습하고 블로그 글로 정리했습니다.</p>
<p>지금은 학습을 미루는 것이 아니라, 현재 일정에 필요한 학습과 이후에 확장할 학습을 구분하려고 합니다. 덕분에 팀 일정에 영향을 주지 않으면서도 배움을 계속 이어갈 수 있었습니다.</p>
</blockquote>
<h3 id="q8-경력-공백기-2023-데브코스-수료--202512">Q8. 경력 공백기 (2023 데브코스 수료 ~ 2025.12)</h3>
<p>본인 특수사항이라 반드시 하나의 스토리로 준비. 아래 세부 질문으로 쪼개서 물어볼 수 있음.</p>
<ul>
<li>이 기간 동안 무엇을 하셨나요?</li>
<li>취업까지 시간이 걸린 이유는 무엇이라고 생각하시나요?</li>
<li>그 기간 동안 실력이 뒤처졌다고 느낀 부분이 있었나요? 어떻게 보완하셨나요?</li>
<li>그 시기에 다른 회사 지원 경험이 있었나요? 결과가 어땠고 왜 그랬다고 생각하나요?</li>
<li>그 시기의 실패나 시행착오가 있었다면 무엇인가요? (숨기지 않아도 됨 — 본부장 발언 참고)</li>
<li>공백기 이후 다시 개발자로 도전하게 된 계기나 전환점은 무엇인가요?</li>
</ul>
<p><strong>버전 A — 솔직한 상세 답변</strong></p>
<blockquote>
<p>데브코스 수료 후 공백이 길어진 가장 큰 이유는, 현업에 들어가기 전에 가능한 많은 기술을 완벽하게 익혀야 한다고 생각했기 때문입니다. 충분히 준비되지 않은 상태로 팀에 들어가면 적응하지 못하거나 다른 사람에게 부담이 될 수 있다는 두려움이 있었습니다.</p>
<p>그래서 학습 범위를 계속 넓히고 프로젝트의 완성도를 높이는 데 집중했습니다. 하지만 기술은 공부할수록 더 배울 것이 생겼고, &#39;이 정도면 준비됐다&#39;고 판단하는 기준을 계속 미루게 됐습니다. 지금 돌아보면 현업 적응은 모든 기술을 미리 아는 것이 아니라, 모르는 것을 빠르게 확인하고 우선순위에 맞춰 팀과 해결해가는 능력이라는 점을 충분히 이해하지 못했습니다.</p>
<p>이 생각을 바꾸기 위해 완벽한 준비를 기다리기보다, 정해진 기간 안에 결과를 만드는 경험을 의도적으로 늘렸습니다. Real World를 통해 서비스 전체 흐름을 직접 경험했고, UNIBUSK와 Dessert에서는 다른 직군·팀원과 함께 마감과 우선순위 안에서 문제를 해결했습니다. 특히 더 깊게 공부하고 싶은 내용은 현재 작업을 멈추지 않도록 Notion에 기록해 두고, 우선순위 작업이 끝난 뒤 학습하고 블로그로 정리하는 방식으로 바꿨습니다.</p>
<p>공백이 길어진 점은 제 판단이 부족했던 결과라고 생각합니다. 다만 그 경험을 통해 완벽함을 기다리기보다, 현재 필요한 일을 책임 있게 수행하고 모르는 것은 빠르게 배우며 팀과 소통하는 방식이 더 중요하다는 점을 배웠습니다. 이제는 부족한 부분을 숨기거나 혼자 완벽히 채우려 하기보다, 학습 범위와 우선순위를 관리하며 팀에 기여하는 개발자가 되려고 합니다.</p>
</blockquote>
<p><strong>버전 B — 1분 압축 답변</strong></p>
<blockquote>
<p>공백이 길어진 이유는 당시 제가 준비의 기준을 잘못 세웠기 때문입니다. 현업에 들어가기 전 더 많은 기술을 알아야 한다고 생각했고, 그 결과 학습과 프로젝트 완성도에만 시간을 쓰며 지원과 협업 경험을 충분히 늘리지 못했습니다.</p>
<p>하지만 프로젝트를 진행하면서 현업 적응은 모든 것을 미리 아는 것이 아니라, 모르는 부분을 빠르게 확인하고 우선순위를 정해 팀과 해결하는 능력이라는 점을 배웠습니다. 이후에는 마감이 있는 팀 프로젝트에 참여했고, 학습이 필요한 내용은 현재 작업과 분리해 기록한 뒤 후속 학습으로 이어가는 방식으로 바꿨습니다.</p>
<p>공백이 길어진 점은 제 판단이 부족했던 결과입니다. 다만 같은 기준으로 다시 준비하지 않도록, 지금은 완벽한 준비를 기다리기보다 맡은 일을 끝까지 수행하고 부족한 부분은 빠르게 배우며 팀에 기여하는 방식으로 바꿨습니다.</p>
</blockquote>
<blockquote>
<p><strong>사용 기준</strong>: 첫 질문에는 버전 B로 답하고, 면접관이 공백기의 구체적 이유·생각 변화·보완 행동을 물으면 버전 A의 내용을 이어서 설명할 것.</p>
</blockquote>
<h3 id="q9-잇뉴가-어떤-회사라고-이해하고-계신가요-지원동기와-겹침-참고용">Q9. 잇뉴가 어떤 회사라고 이해하고 계신가요? (지원동기와 겹침, 참고용)</h3>
<p><code>README.md</code> 서비스 3개(ERLN·MemoryMint·MinTrigger) 참고. 2차 회사 이해도 확인</p>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>잇뉴는 AI와 블록체인 기술을 자체 기술로만 보유하는 데 그치지 않고, 제주 지역의 실제 운영 문제에 연결하는 회사라고 이해하고 있습니다. ERLN은 당일배송 운영에서 스마트 배차와 경로 최적화를 다루고, MemoryMint는 행사 운영과 인증서 발급을, MinTrigger는 인증·검증·이력 추적 기능을 다른 서비스가 API로 활용할 수 있게 제공합니다.</p>
<p>특히 물류, 행사·인증, 공공·민간 프로젝트처럼 도메인은 달라도 데이터에서 판단을 만들고 그것을 현장 실행으로 연결한다는 공통점이 인상적이었습니다. 공개 자료로는 각 제품의 실제 적용 범위까지 단정하기 어려운 부분도 있어, 입사 후에는 어떤 사용자와 운영 문제를 가장 우선하고 있는지 먼저 이해하며 기여하고 싶습니다.</p>
</blockquote>
<h2 id="🟡-2순위--준비-권장">🟡 2순위 — 준비 권장</h2>
<p>인터뷰 2~3회 등장 or 잇뉴 평가 기준과 직결되는 질문입니다.</p>
<h3 id="q10-지금까지-개발하면서-가장-보람-있거나-뿌듯했던-순간은">Q10. 지금까지 개발하면서 가장 보람 있거나 뿌듯했던 순간은?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>제가 가장 뿌듯함을 느끼는 순간은 문제가 발생했을 때 겉으로 보이는 현상만 고치는 것이 아닌, 원인을 찾아 의도한 흐름이 정상적으로 동작하게 만들었을 때입니다.</p>
<p>UNIBUSK에서 공연 등록 화면의 입력 문제를 발견했을 때도, 단순히 화면이 느리다는 현상에서 멈추지 않고 어떤 입력이 어느 부분에 영향을 주는지 확인해 원인을 찾았습니다. 이후 필요한 부분만 수정해, 사용자가 입력할 때 관련된 화면만 자연스럽게 반응하도록 개선했습니다.</p>
<p>특히 제가 해결한 문제가 사용자에게는 불편 없이 기능을 사용할 수 있는 경험으로, 팀에는 이후에도 참고할 수 있는 문제 해결 과정으로 남을 때 가장 보람을 느낍니다.</p>
</blockquote>
<h3 id="q11-이전-팀에서는-다른-직군과-어떻게-협업하셨나요">Q11. 이전 팀에서는 다른 직군과 어떻게 협업하셨나요?</h3>
<p><code>cover-letter.md</code> 협업 Q1~Q8 참고. 본부장 &quot;의사소통&quot; 핵심</p>
<p><strong>버전 A — 직군별 관점 조율 중심</strong></p>
<blockquote>
<p>직군마다 중요하게 보는 기준이 다르다는 점을 먼저 확인하려고 했습니다. UNIBUSK의 공연 등록 플로우를 논의할 때 기획에서는 사용자가 복잡하지 않게 입력하는 흐름을 중요하게 봤고, 개발에서는 이전 단계 이동이나 중간 이탈 시 입력값을 어떻게 보존할지가 우려였습니다.</p>
<p>그래서 어느 쪽 방식이 맞는지부터 주장하기보다, 사용자가 등록을 끝까지 완료하려면 어떤 상태가 언제 유지돼야 하는지로 논의 기준을 바꿨습니다. 그 결과 단계별 입력 흐름은 유지하면서, 입력 상태·단계 간 스냅샷·서버 상태를 분리해 구현했습니다.</p>
<p>저는 기술 용어로 설득하기보다, 그 선택이 사용자 경험·일정·수정 비용에 어떤 영향을 주는지로 설명하려고 합니다. 합의한 내용은 문서나 이슈로 남겨 다음 작업에서도 같은 이해를 유지하는 것을 중요하게 생각합니다.</p>
</blockquote>
<p><strong>버전 B — 일정·중간 공유·점진적 조율 중심</strong></p>
<blockquote>
<p>다른 직군과 협업할 때는 먼저 이번 작업의 일정, 사용자에게 필요한 결과, 그리고 함께 결정해야 할 항목을 맞추려고 합니다. 완성된 결과물을 기다리기보다, 사용자 흐름이나 아직 결정되지 않은 쟁점을 중간에 공유해 방향이 맞는지 확인하는 방식을 선호합니다.</p>
<p>UNIBUSK의 공연 등록 플로우를 논의할 때도 기획에서는 사용자가 복잡하지 않게 입력하는 흐름을 중요하게 봤고, 개발에서는 이전 단계로 돌아가거나 중간에 이탈했을 때 입력 내용을 어떻게 다룰지 확인이 필요했습니다. 저는 어느 방식이 더 낫다고 바로 주장하기보다, 사용자가 등록을 끝까지 완료하려면 무엇을 먼저 결정해야 하는지로 논의 기준을 바꿨습니다. 그 결과 단계별 입력 흐름은 유지하되, 이전 단계 이동과 입력값 유지가 필요한 조건을 함께 정리할 수 있었습니다.</p>
<p>이 경험을 통해 협업은 처음부터 완벽한 계획을 만드는 것보다, 일정 안에서 핵심 흐름과 남은 쟁점을 계속 공유하며 조정하는 과정이라고 배웠습니다. 합의한 내용과 다음 확인 항목은 문서나 이슈로 남겨, 다음 작업에서도 같은 이해를 유지하려고 합니다.</p>
</blockquote>
<h3 id="q12-ai-툴chatgptcopilot-등을-어떻게-쓰나요-장단점은">Q12. AI 툴(ChatGPT/Copilot 등)을 어떻게 쓰나요? 장단점은?</h3>
<p><code>cover-letter.md</code> AI 코딩 에이전트 답변, <code>job-requirements-qa.md</code> Q10~Q14 참고. 잇뉴는 AI-native를 강조하므로 중요한 질문</p>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>저는 AI를 코드 생성 도구로만 쓰기보다, 작업을 시작하기 전에 이슈를 구조화하고 놓친 조건을 찾는 보조 도구로 사용했습니다.</p>
<p>구체적으로는 공용 프롬프트 양식에 이슈의 목표, 우선순위, 요구사항, 관련 문서와 제약 조건을 넣고, 작업 범위와 구현 초안, 확인이 필요한 질문, 예외 상황을 정리해 달라고 요청했습니다.</p>
<p>다만 AI가 만든 초안을 그대로 구현하지는 않았습니다. 기획 의도와 다르게 해석한 부분은 없는지, 실제 코드 구조와 맞는지, 사용자가 실패할 수 있는 예외 상황이 빠지지 않았는지를 직접 확인했습니다. 필요한 경우 기획자나 팀원에게 다시 질문한 뒤 구현 범위를 정했습니다.</p>
<p>1차 면접을 통해 좋은 프롬프트 하나만으로는 결과 품질을 보장할 수 없다는 점도 더 분명히 인식했습니다. 최근에는 프롬프트 자체보다 AI가 참고할 맥락, 사용할 도구, 검증 방식, 사람이 최종 판단하는 지점을 함께 설계하는 하네스 엔지니어링과 루프 엔지니어링 개념을 학습하고 있습니다. 아직 제가 이 구조를 직접 설계한 경험은 없지만, 잇뉴에서 실제로 운영하는 방식을 이해하고 제 작업에도 적용해보고 싶습니다.</p>
<p>결국 AI를 잘 쓴다는 것은 생성 속도보다, 제안의 전제와 한계를 설명하고 최종 결과에 책임질 수 있는지라고 생각합니다.</p>
</blockquote>
<h3 id="q13-프로젝트하면서-힘들었던-점과-극복-방법은">Q13. 프로젝트하면서 힘들었던 점과 극복 방법은?</h3>
<p><code>project-experience-notes.md</code> — 렌더링 문제, Cross-Domain Cookie 등</p>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>가장 어려웠던 점은 UNIBUSK MVP 단계에서 일정과 각 직군의 작업 진행도를 충분히 함께 관리하지 못했던 경험입니다. 당시에는 각자 맡은 기능을 구현하는 데 집중했고, 작업 간 의존 관계나 현재 막힌 지점을 팀 전체가 제때 파악할 수 있는 구조가 부족했습니다. 그 결과 협업 과정에서 다음 작업을 기다리거나 조율해야 하는 상황이 생겼습니다.</p>
<p>고도화 단계에서는 이 문제를 보완하기 위해 제가 Notion 칸반 보드를 관리하며 일정과 작업 상태를 수동으로 정리했습니다. 팀원이 데일리 계획을 작성하지 않은 경우에는 개별적으로 확인해 업데이트를 요청했고, 초반에는 작업 현황을 공유하는 데 도움이 됐습니다. 다만 고도화 기간이 끝나갈수록 수동 관리의 부담이 커졌고, 일부 작업의 상태가 제때 갱신되지 않는 한계도 확인했습니다.</p>
<p>이 경험을 통해 Jira 같은 도구와 자동화를 활용하면 반복적인 현황 수집 부담은 줄일 수 있다는 점을 배웠습니다. 동시에 도구만으로 팀의 참여를 만들 수는 없다고 생각합니다. 다음에는 관리 방식부터 정하기보다, 왜 상태 공유가 필요한지와 갱신 주기·담당자를 팀과 먼저 합의하고, 입력 부담을 최소화한 뒤 정기적으로 막힌 지점을 함께 점검하겠습니다.</p>
</blockquote>
<h3 id="q14-업무를-지시받는-것이-편한가요-스스로-찾아서-하는-것이-편한가요">Q14. 업무를 지시받는 것이 편한가요, 스스로 찾아서 하는 것이 편한가요?</h3>
<p>&quot;Developer vs Coder&quot; 발언과 직결되는 질문</p>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>처음에는 업무의 목표와 우선순위를 충분히 이해하는 것이 먼저이고, 그 안에서 개선할 문제는 스스로 찾는 방식이 가장 좋다고 생각합니다. 방향을 확인하지 않은 주도성은 팀의 우선순위와 어긋날 수 있기 때문입니다.</p>
<p>예를 들어 UNIBUSK에서는 기능 구현 후에도 공연 등록 폼의 실제 렌더링을 측정해 불필요한 갱신을 발견했고, Dessert에서는 새 서비스가 추가되는 상황에서 공용 자원과 컨벤션을 어떻게 유지할지 먼저 제안했습니다. 다만 이런 제안도 혼자 결정하지 않고, 문제의 근거와 대안을 공유해 팀의 판단으로 만들었습니다.</p>
<p>맡은 일을 정확히 완료하는 것을 기본으로 하되, 사용성과 운영에 도움이 되는 다음 문제까지 발견해 대화로 풀어가는 개발자가 되고 싶습니다.</p>
</blockquote>
<h3 id="q15-본인이-잇뉴에-잘-맞는-사람이라고-생각하는-이유는">Q15. 본인이 잇뉴에 잘 맞는 사람이라고 생각하는 이유는?</h3>
<p><code>README.md</code> 개발 문화 섹션의 인재상(I-T-N-E-W)과 연결</p>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>저는 주어진 화면을 구현하는 데서 멈추기보다, 사용자가 실제로 불편한 지점과 팀이 반복해서 비용을 쓰는 지점을 찾아보려는 편입니다. 다만 그 문제를 제 방식으로만 풀기보다, 근거를 공유하고 다른 직군·팀원의 관점을 반영해 함께 유지할 수 있는 형태로 만드는 것을 중요하게 생각합니다.</p>
<p>성능 문제를 측정해 개선한 경험, 팀 규모에 맞춰 FSD 구조를 조정하고 컨벤션을 문서화한 경험이 그 예입니다. 잇뉴가 여러 기술을 현장 서비스와 운영 문제에 연결하는 것처럼, 저도 기술 선택을 사용자·운영자·동료에게 어떤 도움이 되는지 기준으로 판단하고 싶습니다.</p>
<p>신입으로서 모르는 영역은 빠르게 질문하고 배우되, 단순히 지시를 기다리기보다 관찰한 문제와 개선안을 먼저 꺼내는 구성원이 되겠습니다.</p>
</blockquote>
<h3 id="q16-앞으로-3개월-6개월-1년-동안-어떤-것을-공부할-계획인가요-그-이유는요">Q16. 앞으로 3개월, 6개월, 1년 동안 어떤 것을 공부할 계획인가요? 그 이유는요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>입사 초기 3개월에는 새 기술을 넓게 늘리기보다 제품, 사용자, 배포 흐름, 팀의 의사결정 방식을 정확히 이해하는 데 집중하겠습니다. 작은 기능이라도 요구사항부터 운영 지표·장애 대응까지 연결해 보며, 팀의 코드와 업무 언어를 빠르게 익히겠습니다.</p>
<p>6개월에는 제가 맡은 영역에서 반복되는 품질 문제를 하나 이상 발견해 개선해보고 싶습니다. 프론트엔드에서는 성능, API 상태 관리, 컴포넌트 재사용성, 테스트 중 실제 서비스의 병목에 맞는 주제를 선택하겠습니다.</p>
<p>1년에는 기능을 안정적으로 맡는 것을 넘어, 새로 합류한 사람도 이해할 수 있는 문서나 컨벤션으로 팀에 기여하고 싶습니다. 계획 자체보다 제품의 우선순위에 맞춰 학습 주제를 조정하는 것이 중요하다고 생각합니다.</p>
</blockquote>
<h3 id="q17-여가·취미-생활은-어떻게-보내시나요">Q17. 여가·취미 생활은 어떻게 보내시나요?</h3>
<p>잇뉴 인터뷰 5회 등장. 답변 부담은 적으니 가볍게 준비</p>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>여가에는 웹소설을 읽는 것을 좋아합니다. 개발할 때는 문제를 오래 고민하고 해결책을 판단해야 하는 경우가 많아서, 쉬는 시간에는 별도의 결정이나 깊은 고민을 요구하지 않는 가벼운 이야기를 읽으며 머리를 환기합니다.</p>
<p>반대로 생각이 복잡하거나 정리가 필요한 일이 있을 때는 짧게 산책합니다. 잠시 작업에서 거리를 두고 걸으면서 여러 선택지를 다시 살펴보고, 무엇을 먼저 해야 할지 우선순위를 정리하려고 합니다.</p>
<p>저에게 웹소설은 충분히 쉬는 시간이고, 산책은 복잡한 생각을 차분히 정리하는 시간입니다.</p>
</blockquote>
<blockquote>
<p><strong>꼬리질문 준비</strong>: 실제로 좋아하는 웹소설 장르와 최근 재미있게 읽은 작품 한두 개를 준비할 것.</p>
</blockquote>
<h3 id="q18-최근에-관심-있는-기술이나-이슈가-있나요">Q18. 최근에 관심 있는 기술이나 이슈가 있나요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>최근에는 프론트엔드에서 자바스크립트로 직접 처리하던 인터랙션과 애니메이션을, CSS만으로 더 간결하고 선언적으로 구현할 수 있는 흐름에 관심이 있습니다. 다만 새 기능이라는 이유만으로 바로 적용하기보다, 지원 브라우저 범위와 점진적 향상 가능 여부, 접근성, 운영 환경에서의 안전성을 먼저 확인해야 한다고 생각합니다.</p>
<p>백엔드 쪽에서는 NestJS와 Drizzle을 사용하는 모노레포에서 Zod 스키마를 기준으로 프론트엔드와 백엔드의 입출력 타입을 함께 관리하는 방식에도 관심이 있습니다. API 계약을 한곳에서 관리하면 타입 불일치로 생기는 오류를 줄일 수 있기 때문입니다.</p>
<p>AI 쪽에서는 프롬프트 하나를 잘 작성하는 것보다, AI가 참고할 맥락과 검증 방식을 어떻게 설계해야 결과를 신뢰할 수 있는지에 관심이 있습니다. 하네스·루프 엔지니어링도 이 맥락에서 학습하고 있지만, 아직 직접 설계한 경험은 없어 개념을 이해하고 실제 활용 사례와 연결하는 단계입니다.</p>
<p>프론트엔드 생태계는 Korean FE Article 커뮤니티를 중심으로 보고, AI 관련 동향은 GeekNews를 통해 확인하고 있습니다.</p>
</blockquote>
<h2 id="⚪-3순위--여유-되면">⚪ 3순위 — 여유 되면</h2>
<p>주로 &quot;가정형 상황 대처&quot; 질문이라 잇뉴 인터뷰엔 거의 없고, 답변 원칙만 잡아두면 즉석 대응이 가능합니다.</p>
<h3 id="상황-대처">상황 대처</h3>
<h3 id="q19-서비스-오픈-한-달-전에-치명적인-오류를-발견했다면-어떻게-하시겠습니까">Q19. 서비스 오픈 한 달 전에 치명적인 오류를 발견했다면 어떻게 하시겠습니까?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>먼저 혼자 수정부터 하기보다 오류의 재현 조건, 사용자 영향, 데이터 손상·보안 위험, 우회 가능 여부를 빠르게 확인하겠습니다. 치명적이라는 판단 근거를 제품·개발·운영 담당자에게 즉시 공유하고, 오픈 유지·범위 축소·일정 조정 중 어떤 선택이 가능한지 함께 결정하겠습니다.</p>
<p>수정 방향은 임시 차단이나 우회책과 근본 해결책을 나눠 제시하겠습니다. 특히 데이터 정합성이나 보안 문제라면 일정 압박보다 안전을 우선하겠습니다. 조치 뒤에는 회귀 테스트와 모니터링 기준을 정하고, 원인과 재발 방지 항목을 기록하겠습니다.</p>
</blockquote>
<h3 id="q20-마감일이-임박했는데-완료하지-못한-상황이라면-어떻게-하시겠습니까">Q20. 마감일이 임박했는데 완료하지 못한 상황이라면 어떻게 하시겠습니까?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>완료하지 못할 가능성을 알게 된 시점에 바로 공유하겠습니다. 마감 직전에 알려야 하는 상황을 만들지 않기 위해, 현재 완료율과 막힌 이유, 남은 작업을 작게 나눠 지속적으로 보겠습니다.</p>
<p>공유할 때는 단순히 늦는다고 말하기보다 사용자 핵심 흐름을 지키는 최소 범위, 줄일 수 있는 부가 범위, 추가 지원이 필요한 부분을 함께 제안하겠습니다. 합의 없이 품질을 낮추거나 무리하게 약속하지 않고, 팀이 판단할 수 있는 정보를 제공하는 것이 제 역할이라고 생각합니다.</p>
</blockquote>
<h3 id="q21-희망하지-않는-업무나-부서에-배치된다면-어떻게-하시겠습니까">Q21. 희망하지 않는 업무나 부서에 배치된다면 어떻게 하시겠습니까?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>우선 그 업무가 제품과 팀에 왜 필요한지, 제가 어떤 역할을 기대받는지부터 이해하겠습니다. 선호와 무관하게 맡은 업무에서 신뢰를 만드는 것이 먼저라고 생각합니다.</p>
<p>다만 장기적으로 프론트엔드 역량을 어떻게 활용·성장시킬 수 있는지는 리더와 솔직하게 대화하겠습니다. 현재 업무를 성실히 수행하면서도, 제가 잘할 수 있는 영역과 배우고 싶은 방향을 공유해 팀의 필요와 제 성장 방향이 만나는 지점을 찾겠습니다.</p>
</blockquote>
<h3 id="q22-긴급한-업무가-두-건-동시에-생긴다면-어떻게-처리하시겠습니까">Q22. 긴급한 업무가 두 건 동시에 생긴다면 어떻게 처리하시겠습니까?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>두 일을 동시에 붙잡기보다 사용자 영향, 보안·데이터 위험, 서비스 중단 범위, 마감 시점을 기준으로 우선순위를 빠르게 정하겠습니다. 혼자 판단하기 애매하면 관련 담당자에게 두 이슈의 영향도를 확인해 결정 근거를 맞추겠습니다.</p>
<p>한 업무를 처리하는 동안 다른 업무가 방치되지 않도록 현황·예상 대응 시간·필요한 도움을 공유하고, 가능하면 역할을 나누겠습니다. 긴급 상황일수록 빠르게 처리하는 것과 동시에 팀이 같은 상황 인식을 갖는 것이 중요합니다.</p>
</blockquote>
<h3 id="q23-회사의-중요-업무와-개인적인-일이-동시에-생긴다면-어떻게-하시겠습니까">Q23. 회사의 중요 업무와 개인적인 일이 동시에 생긴다면 어떻게 하시겠습니까?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>회사의 중요한 업무는 책임 있게 대응하되, 개인적인 일이 피할 수 없는 사안이라면 가능한 빨리 상황과 영향 범위를 공유하겠습니다. 숨기거나 무리해서 두 가지를 모두 처리하겠다고 약속하는 것은 오히려 팀에 리스크를 줄 수 있다고 생각합니다.</p>
<p>인수인계 가능한 상태로 작업을 정리하고, 대체 담당이나 일정 조정이 가능한지 리더와 협의하겠습니다. 평소에도 진행 상황과 결정 맥락을 남겨두는 습관이 이런 상황에서 팀의 부담을 줄인다고 생각합니다.</p>
</blockquote>
<h3 id="근무-조건·조직-적합성">근무 조건·조직 적합성</h3>
<h3 id="q24-야근이-잦다면-괜찮으신가요">Q24. 야근이 잦다면 괜찮으신가요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>서비스 이슈나 마감처럼 꼭 필요한 상황에서는 책임 있게 대응할 수 있습니다. 다만 야근이 반복된다면 개인의 의지 문제로만 보지 않고, 요구사항 변경, 우선순위, 인력 배분, 기술 부채 중 무엇이 원인인지 팀과 점검해야 한다고 생각합니다.</p>
<p>단기적으로는 최선을 다하되, 장기적으로는 같은 긴급 상황이 반복되지 않도록 일하는 방식을 개선하는 것이 더 건강한 협업이라고 생각합니다.</p>
</blockquote>
<h3 id="q25-워라밸에-대해-어떻게-생각하시나요">Q25. 워라밸에 대해 어떻게 생각하시나요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>워라밸은 일을 적게 한다는 의미보다, 업무 시간에 집중해 지속적으로 좋은 판단을 할 수 있는 상태를 만드는 것이라고 생각합니다. 개발은 문제를 오래 들여다보는 일이 많아서, 재충전 없이 계속 밀어붙이면 코드 품질과 소통 품질도 함께 떨어질 수 있습니다.</p>
<p>맡은 결과에는 책임을 지되, 일정과 리스크를 일찍 공유하고 업무를 지속 가능한 방식으로 운영하는 팀에서 더 좋은 성과가 난다고 생각합니다.</p>
</blockquote>
<h3 id="q26-조직-문화에-적응하는-본인만의-방법이-있나요">Q26. 조직 문화에 적응하는 본인만의 방법이 있나요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>처음에는 기존 방식이 왜 만들어졌는지 이해하는 데 시간을 쓰겠습니다. 코드 구조, PR, 이슈, 회의에서 반복되는 표현을 보며 팀이 중요하게 여기는 품질 기준과 의사결정 방식을 파악하겠습니다.</p>
<p>이해가 안 되는 부분은 혼자 추측하지 않고 작은 단위로 질문하겠습니다. 이후에는 제가 관찰한 개선점을 바로 바꾸기보다, 기존 방식의 장점과 변경 비용을 먼저 확인하고 제안하겠습니다. 적응은 제 방식을 관철하는 것이 아니라, 팀의 맥락을 이해한 뒤 더 나은 방법을 함께 만드는 과정이라고 생각합니다.</p>
</blockquote>
<h3 id="q27-신입-개발자들이-퇴사하는-이유가-뭐라고-생각하시나요">Q27. 신입 개발자들이 퇴사하는 이유가 뭐라고 생각하시나요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>이유는 하나로 단정하기 어렵지만, 기대 역할과 실제 업무가 다르거나, 질문하고 피드백받을 통로가 부족할 때 어려움을 크게 느낄 수 있다고 생각합니다. 신입은 모르는 것이 자연스러운데, 무엇을 어느 수준까지 스스로 해결해야 하는지 알기 어려울 수 있기 때문입니다.</p>
<p>그래서 저는 모르는 것을 숨기기보다 제가 확인한 내용과 막힌 지점을 정리해 질문하고, 받은 피드백은 다음 작업에 반영해 신뢰를 쌓으려 합니다. 회사도 구성원도 서로 기대를 투명하게 맞추는 대화가 중요하다고 생각합니다.</p>
</blockquote>
<h3 id="협업·리딩">협업·리딩</h3>
<h3 id="q28-실력이-부족한-팀원과-함께-일해야-한다면-어떻게-하시겠습니까">Q28. 실력이 부족한 팀원과 함께 일해야 한다면 어떻게 하시겠습니까?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>먼저 실력이 부족하다고 단정하기보다, 현재 과제에서 어떤 정보나 경험이 부족한지 확인하겠습니다. 설명이 부족했거나 업무 범위가 너무 크거나, 낯선 도메인일 수도 있기 때문입니다.</p>
<p>제가 아는 내용은 작은 단위로 나누어 맥락과 함께 공유하고, 코드 리뷰에서는 정답을 바로 제시하기보다 왜 그렇게 판단했는지 대화하겠습니다. 다만 일정이나 품질에 영향이 큰 부분은 혼자 감당하지 않고 리더와 공유해 역할과 지원 방식을 조정하겠습니다. 목표는 평가가 아니라 팀이 결과를 내고 함께 성장하는 것입니다.</p>
</blockquote>
<h3 id="q29-후배나-신입-팀원을-리딩해야-한다면-어떻게-하시겠습니까">Q29. 후배나 신입 팀원을 리딩해야 한다면 어떻게 하시겠습니까?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>신입에게는 결과물뿐 아니라 왜 이 작업을 하는지, 무엇을 먼저 확인해야 하는지를 함께 설명하겠습니다. 바로 답을 주기보다 본인이 생각한 접근과 막힌 지점을 먼저 듣고, 필요한 만큼만 힌트를 드리는 방식이 성장에 도움이 된다고 생각합니다.</p>
<p>Dessert에서 온보딩 자료와 컨벤션을 정리했던 경험처럼, 개인의 기억에 의존하지 않고 문서·예시·리뷰 기준으로 반복 가능한 도움을 만들겠습니다. 저도 신입이기 때문에 권위로 이끌기보다, 함께 확인하고 배우는 태도를 유지하겠습니다.</p>
</blockquote>
<h3 id="q30-성향이-맞지-않는-사람과-팀이-된다면-어떻게-하시겠습니까">Q30. 성향이 맞지 않는 사람과 팀이 된다면 어떻게 하시겠습니까?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>성향이 다르다는 사실과 함께 일할 수 없다는 것은 다르다고 생각합니다. 먼저 상대가 어떤 방식으로 일하고 무엇을 중요하게 보는지 파악하겠습니다. 예를 들어 빠른 실행을 중시하는지, 충분한 검토를 중시하는지에 따라 갈등의 원인이 달라질 수 있습니다.</p>
<p>개인적인 호불호 대신 업무의 목표·근거·마감·결정 기준을 명확히 하며 소통하겠습니다. 계속 어려움이 있다면 감정적으로 쌓아두지 않고, 구체적인 상황을 기준으로 1:1 대화나 리더의 도움을 요청하겠습니다.</p>
</blockquote>
<h3 id="q31-피드백을-주거나-받았던-경험이-있나요">Q31. 피드백을 주거나 받았던 경험이 있나요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>Dessert에서 FSD 구조를 설계할 때 제가 widgets 레이어까지 포함하는 방향을 생각한 적이 있습니다. 팀원은 현재 규모에서는 실제 내용보다 폴더 구조와 관리 부담이 먼저 커질 수 있다고 피드백했습니다.</p>
<p>처음 기준을 세운 사람이 저였기 때문에 방어적으로 반응할 수도 있었지만, 다른 팀원의 의견과 현재 기능 범위를 함께 확인했습니다. 그 결과 widgets 레이어를 두지 않는 방향이 더 적절하다고 판단했고, 변경한 기준을 컨벤션 문서에 반영했습니다.</p>
<p>이 경험을 통해 피드백은 제 의견의 옳고 그름을 판정하는 일이 아니라, 팀이 유지할 수 있는 결정을 만드는 입력이라는 점을 배웠습니다. 저도 리뷰할 때는 사람 평가가 아니라 코드의 영향 범위와 대안을 중심으로 말씀드리려 합니다.</p>
</blockquote>
<h3 id="부가-질문">부가 질문</h3>
<h3 id="q32-개발자가-되지-않았다면-어떤-일을-하고-싶었나요">Q32. 개발자가 되지 않았다면 어떤 일을 하고 싶었나요?</h3>
<p><strong>사실 확인 후 사용할 답변 틀</strong></p>
<blockquote>
<p>개발자가 아니었다면 <strong>[실제 관심 분야 또는 경험]</strong>와 관련된 일을 했을 것 같습니다. 제가 그 분야에 관심을 갖는 이유는 <strong>[사람·문제·과정 중 실제로 중요하게 보는 지점]</strong> 때문입니다.</p>
<p>다만 지금은 그 관심을 가장 직접적으로 결과물로 만들 수 있는 일이 개발이라고 느낍니다. 개발을 통해 문제를 구조화하고 사람들과 해결책을 실험해볼 수 있다는 점이 저에게 가장 잘 맞습니다.</p>
</blockquote>
<blockquote>
<p><strong>확인 메모</strong>: 그럴듯한 직업을 만들지 말고, 실제 관심사 한 가지로 교체할 것.</p>
</blockquote>
<h3 id="q33-리더에-가까운-편인가요-팔로워에-가까운-편인가요">Q33. 리더에 가까운 편인가요, 팔로워에 가까운 편인가요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>상황에 따라 둘 다 필요하다고 생각합니다. 목표와 방향이 이미 정해진 상황에서는 맡은 역할을 정확히 수행하고, 모르는 부분은 빠르게 확인하는 팔로워가 되어야 합니다. 반대로 기준이 없거나 반복 문제가 보이는 상황에서는 문제를 정리하고 논의를 시작하는 역할을 할 수 있습니다.</p>
<p>Dessert에서 컨벤션과 작업 흐름을 정리한 경험은 후자에 가깝지만, 그 과정도 제가 답을 정해 전달한 것이 아니라 팀원 의견을 모아 합의 가능한 기준을 만든 일이었습니다. 직함보다 지금 팀에 필요한 역할을 맡는 사람이 되고 싶습니다.</p>
</blockquote>
<h3 id="q34-혼자-개발하는-것과-팀으로-일하는-것-중-어느-쪽이-편한가요">Q34. 혼자 개발하는 것과 팀으로 일하는 것 중 어느 쪽이 편한가요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>혼자 개발할 때는 빠르게 시도하고 전체 흐름을 깊게 경험할 수 있다는 장점이 있습니다. Real World를 혼자 만들며 프론트엔드와 백엔드의 연결을 이해한 경험이 그 예입니다.</p>
<p>다만 실제 서비스를 더 잘 만들기 위해서는 팀 개발이 더 중요하다고 생각합니다. 서로 다른 관점을 통해 제가 놓친 사용자·운영·디자인·기술 리스크를 발견할 수 있고, 결과를 한 사람이 아니라 팀이 계속 유지할 수 있기 때문입니다. 그래서 저는 혼자서도 책임 있게 파고들되, 중요한 판단은 팀과 투명하게 나누는 방식을 선호합니다.</p>
</blockquote>
<h3 id="q35-주변-사람들은-본인을-어떤-사람으로-평가하나요">Q35. 주변 사람들은 본인을 어떤 사람으로 평가하나요?</h3>
<p><strong>사실 확인 후 사용할 답변 틀</strong></p>
<blockquote>
<p>주변에서는 저를 <strong>[실제 평가: 예, 끝까지 확인하는 사람 / 약속한 일을 정리해 공유하는 사람]</strong>이라고 말하는 편입니다. 프로젝트에서 <strong>[실제 사례]</strong>처럼, 문제가 생기면 감으로 넘기기보다 원인과 다음 행동을 정리해 공유하려 했기 때문이라고 생각합니다.</p>
<p>저도 그런 평가를 유지하기 위해, 막힌 점을 혼자 오래 끌기보다 제가 확인한 내용과 필요한 도움을 구체적으로 말하려고 합니다.</p>
</blockquote>
<blockquote>
<p><strong>확인 메모</strong>: 친구·팀원의 실제 표현 한 문장을 넣으면 가장 자연스럽다.</p>
</blockquote>
<h3 id="q36-프론트엔드-개발자에게-가장-중요한-역량은-무엇이고-본인은-그것을-갖췄다고-생각하나요">Q36. 프론트엔드 개발자에게 가장 중요한 역량은 무엇이고, 본인은 그것을 갖췄다고 생각하나요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>프론트엔드 개발자에게 가장 중요한 역량은 사용자 요구를 정확히 이해하고, 이를 여러 직군이 함께 판단할 수 있는 화면·상태·우선순위로 풀어내는 소통 능력이라고 생각합니다. 구현 기술은 중요하지만, 사용자가 무엇을 해야 하는지와 그 과정에서 어떤 실패가 생길 수 있는지를 놓치면 좋은 화면을 만들기 어렵습니다.</p>
<p>저는 UNIBUSK에서 기획과 개발의 우려를 사용자 등록 흐름으로 다시 정리해 합의한 경험이 있고, Dessert에서는 팀이 같은 구조를 이해하도록 컨벤션을 문서화한 경험이 있습니다. 아직 배우는 단계이지만, 문제를 정확히 질문하고 근거를 공유하며 함께 결정하는 태도는 갖추고 있다고 생각합니다.</p>
</blockquote>
<h3 id="q37-프론트엔드해당-직무의-매력이-무엇이라-생각하나요">Q37. 프론트엔드(해당 직무)의 매력이 무엇이라 생각하나요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>프론트엔드는 기술적인 결정이 사용자의 경험으로 바로 드러난다는 점이 매력적입니다. 입력 한 번이 불필요하게 화면 전체를 갱신하는 문제나, 로그인 흐름이 한 환경에서만 실패하는 문제처럼 작은 구현 차이가 사용자의 신뢰와 편의에 직접 영향을 줍니다.</p>
<p>동시에 기획, 디자인, 백엔드와 가장 자주 연결되는 위치라서, 서로 다른 요구를 하나의 일관된 경험으로 만드는 역할을 할 수 있습니다. 저는 그 과정에서 사용자에게 보이는 결과와 팀이 유지할 구조를 함께 고민할 수 있다는 점에 보람을 느낍니다.</p>
</blockquote>
<h3 id="q38-입사하신다면-잇뉴에서-이루고-싶은-목표가-있나요">Q38. 입사하신다면 잇뉴에서 이루고 싶은 목표가 있나요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>첫 목표는 빠르게 신뢰받는 동료가 되는 것입니다. 맡은 기능을 일정 안에 구현하는 데 그치지 않고, 요구사항의 모호한 부분과 사용자 영향, 운영상 예외를 미리 확인해 팀이 안심하고 함께 일할 수 있게 하겠습니다.</p>
<p>이후에는 제가 맡은 제품에서 반복되는 사용자 불편이나 개발·운영 비용을 하나 이상 발견해 개선하고 싶습니다. 성능, 상태 관리, 디자인 시스템, 테스트 중 무엇이 우선인지는 입사 후 제품의 실제 문제를 파악한 뒤 정하겠습니다. 공개 자료로 본 잇뉴의 제품들이 현장 실행을 중시하는 만큼, 저도 측정 가능한 사용자·운영 개선으로 기여하고 싶습니다.</p>
</blockquote>
<h3 id="q39-입사-후-35년-뒤-본인이-어떤-개발자가-되어-있길-바라나요">Q39. 입사 후 3~5년 뒤 본인이 어떤 개발자가 되어 있길 바라나요?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>3~5년 뒤에는 맡은 기능을 안정적으로 끝내는 개발자를 넘어, 문제를 발견했을 때 여러 직군과 함께 해결 방향을 만들고 끝까지 운영할 수 있는 개발자가 되고 싶습니다.</p>
<p>기술적으로는 프론트엔드 성능과 구조 설계 역량을 깊게 가져가되, API·데이터·배포 흐름까지 이해해 더 나은 질문을 할 수 있는 사람이 되고 싶습니다. 또한 제가 배운 기준을 코드 리뷰와 문서로 남겨, 새로 합류한 동료의 시행착오를 줄이는 데도 기여하겠습니다.</p>
</blockquote>
<h3 id="q40-도움이-되었던-대외활동이나-경험이-있다면">Q40. 도움이 되었던 대외활동이나 경험이 있다면?</h3>
<p><strong>2차 임원용 초안</strong></p>
<blockquote>
<p>SWYP 활동이 가장 도움이 됐습니다. 기획·디자인·개발 직군이 함께 제한된 기간 안에 서비스를 만들어야 했기 때문에, 기술적으로 가능한지뿐 아니라 사용자 흐름과 일정 안에서 무엇을 우선해야 하는지를 함께 논의하는 경험을 했습니다.</p>
<p>특히 UNIBUSK를 통해 기능을 구현한 뒤에도 성능과 인증 문제를 사용자 관점에서 다시 점검했고, 직군 간 의견이 다를 때는 각자의 제약을 사용자 목표로 다시 정리해 합의하는 방식을 배웠습니다. 혼자 공부할 때는 얻기 어려운 협업의 책임감과 소통 방식을 익힌 경험입니다.</p>
</blockquote>
<h3 id="q41-본인의-전공배경이-이-직무에-어떻게-도움이-된다고-생각하나요">Q41. 본인의 전공/배경이 이 직무에 어떻게 도움이 된다고 생각하나요?</h3>
<p><strong>사실 확인 후 사용할 답변 틀</strong></p>
<blockquote>
<p>제 전공·이전 경험에서 얻은 <strong>[실제 강점]</strong>은 개발에서도 도움이 된다고 생각합니다. 특히 <strong>[구체적 행동 또는 경험]</strong>을 통해, 문제를 구조화하고 상대가 이해할 수 있게 설명하는 방식을 배웠습니다.</p>
<p>전공이 개발과 직접 일치하지 않더라도, 개발에서는 기술 지식뿐 아니라 사용자·동료와 문제를 정확히 맞추는 역량이 중요하다고 생각합니다. 저는 그 배경에서 얻은 강점을 개발 경험과 연결해 계속 보완해왔습니다.</p>
</blockquote>
<blockquote>
<p><strong>확인 메모</strong>: 전공과 이전 경력이 제공된 자료에 명시돼 있지 않아 초안으로 단정하지 않았다. 실제 전공·이전 경험을 한 문장으로 알려주면 면접용 답변으로 구체화할 수 있다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[포트폴리오 프로젝트 경험 면접 노트]]></title>
            <link>https://velog.io/@park-moen/%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B2%BD%ED%97%98-%EB%A9%B4%EC%A0%91-%EB%85%B8%ED%8A%B8</link>
            <guid>https://velog.io/@park-moen/%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B2%BD%ED%97%98-%EB%A9%B4%EC%A0%91-%EB%85%B8%ED%8A%B8</guid>
            <pubDate>Wed, 15 Jul 2026 06:52:26 GMT</pubDate>
            <description><![CDATA[<h2 id="0-프로젝트-답변-원칙">0. 프로젝트 답변 원칙</h2>
<p>면접에서는 기술명을 먼저 나열하기보다 다음 구조로 답합니다.</p>
<pre><code class="language-text">문제 → 원인 분석 → 대안 비교 → 선택한 해결책 → 검증 방법 → 한계</code></pre>
<p>ItNew는 AI·모빌리티·데이터 연결, React/Next.js, REST API, RDBMS, 도메인 모델링, AI 코드 검토를 함께 보는 공고입니다. 따라서 프론트엔드 프로젝트라도 “화면만 구현했습니다”가 아니라 데이터 흐름, API 계약, 캐시, 보안, 팀 구조까지 연결해서 설명합니다.</p>
<h2 id="1-프로젝트별-2분-요약">1. 프로젝트별 2분 요약</h2>
<h3 id="unibusk">UNIBUSK</h3>
<p>UNIBUSK는 버스킹 공연 장소 탐색과 공연 홍보를 위한 플랫폼입니다. 저는 공연 정보, 공연 등록·상세, Kakao OAuth2, 프로필 영역을 담당했습니다.</p>
<p>가장 큰 성과는 렌더링, 초기 로딩, 서버 데이터 패칭 구조를 측정 기반으로 개선한 것입니다. 공연 등록 폼에서는 <code>watch()</code>와 <code>formState.errors</code> 구독 범위가 넓어 입력 한 번에 불필요한 렌더링이 발생했고, <code>useWatch()</code>와 <code>useFormState({ name })</code>로 구독 위치를 분리해 렌더 함수 실행 횟수를 75회에서 1회로 줄였습니다.</p>
<p>초기 로딩 측면에서는 <code>next/dynamic</code> 적용 기준을 정하고, LCP 요소는 지연 로딩 대상에서 제외했습니다. barrel import 때문에 청크 분리가 의도대로 되지 않는 문제도 Network 탭과 청크 요청 흐름으로 확인해 실제 컴포넌트 경로를 직접 import하도록 수정했습니다.</p>
<p>데이터 패칭 측면에서는 공연 목록·상세 페이지에 TanStack Query Hydration Pattern을 적용해 클라이언트 API 요청 2건을 0건으로 줄이고 Slow 3G 환경의 2초 이상 대기 시간을 제거했습니다. 반면 개인화 데이터가 있는 인증 페이지에는 캐시 보안 위험 때문에 적용하지 않았습니다.</p>
<h3 id="dessert-front-dashboard">Dessert Front Dashboard</h3>
<p>Dessert Front Dashboard는 판매자와 관리자 서비스를 하나의 모노레포로 운영하기 위한 대시보드 프로젝트입니다. 저는 모노레포 설계, 공용 UI·아이콘 패키지 분리, FSD 컨벤션 가이드, 코드 리뷰, 관리자 대시보드 개발을 담당했습니다.</p>
<p>Seller와 Admin을 별도 저장소로 운영하면 디자인 시스템 컴포넌트, 아이콘, 디자인 토큰을 중복 관리해야 하는 문제가 있었습니다. 공유 패키지를 npm으로 배포하는 방식도 검토했지만, 같은 팀이 빠르게 변경하는 상황에서는 버전 배포와 각 저장소 업데이트 비용이 크다고 판단했습니다.</p>
<p>그래서 Yarn Workspaces와 Turborepo 기반 모노레포를 구성했습니다. Yarn Workspaces는 워크스페이스 의존성 연결, Turborepo는 작업 실행 순서와 캐싱을 담당하도록 역할을 나눴습니다. <code>@dessert/ui</code>, <code>@dessert/icons</code> 패키지를 분리했고, 캐시 미사용 시 약 9초 걸리던 작업이 캐시 히트 시 36~64ms에 복원되는 것을 확인했습니다.</p>
<p>또한 FSD Segment 기준이 팀원마다 달라지는 문제를 해결하기 위해 suffix 규칙, 단방향 의존성, Public API, Cross-import 금지 원칙을 문서화했습니다. 이 경험은 ItNew처럼 여러 서비스와 팀이 함께 움직이는 환경에서 구조와 협업 기준을 만드는 역량으로 연결할 수 있습니다.</p>
<h3 id="real-world">Real World</h3>
<p>Real World는 React Router v7 Data Mode를 활용한 1인 풀스택 블로그 플랫폼입니다. 프론트엔드와 백엔드를 모두 설계하고 구현했습니다.</p>
<p>프론트엔드에서는 컴포넌트 마운트 이후 <code>useEffect</code>에서 데이터 패칭이 시작되어 route 이동 시 request waterfall이 발생하는 문제가 있었습니다. React Router v7.5의 object-based <code>route.lazy</code> API를 사용해 loader와 Component를 독립적으로 lazy load하도록 분리했고, 컴포넌트 청크 다운로드와 데이터 패칭을 더 일찍 병렬화했습니다. 그 결과 Slow 4G에서 5,789ms에서 4,087ms로, 3G에서 20,506ms에서 15,306ms로 개선했습니다.</p>
<p>인증 흐름에서는 Access Token 만료 시 여러 API가 동시에 401을 받아 Refresh Token 요청이 4회 중복되는 문제가 있었습니다. Axios interceptor에 Promise 기반 대기 큐를 구현해 첫 번째 요청만 갱신을 수행하고, 나머지는 대기 후 새 토큰으로 재시도하도록 했습니다. 결과적으로 토큰 갱신 요청을 4회에서 1회로 줄였습니다.</p>
<p>백엔드에서는 NestJS, PostgreSQL, Prisma를 사용해 Controller, Service, Repository의 책임을 나눴습니다. Java 또는 Kotlin 기반 Spring Boot를 주 스택으로 사용한 경험과는 별개로, 계층 분리, DI, 트랜잭션, API 계약을 직접 구현해 본 경험을 ItNew의 백엔드·도메인 모델링 요구와 연결할 수 있습니다.</p>
<h2 id="2-unibusk-예상-질문">2. UNIBUSK 예상 질문</h2>
<h3 id="q1-공연-등록-폼-렌더링-문제를-어떻게-발견하고-해결했나요">Q1. 공연 등록 폼 렌더링 문제를 어떻게 발견하고 해결했나요?</h3>
<p><strong>답변</strong></p>
<p>처음에는 React Hook Form의 비제어 컴포넌트 패턴을 사용했기 때문에 입력 시 리렌더가 적을 것이라고 예상했습니다. 하지만 React DevTools Profiler로 팀 이름 입력 과정을 확인해보니, 글자 수 표시를 위해 사용한 <code>watch(&#39;teamName&#39;)</code> 때문에 상위 컴포넌트가 폼 값을 구독하고 하위 트리 전체가 영향을 받고 있었습니다.</p>
<p>이를 해결하기 위해 실제로 값이 필요한 <code>TeamNameCharCount</code> 컴포넌트 내부로 <code>useWatch()</code>를 옮겼습니다. 이후에도 일부 필드에서 리렌더가 남아 있었는데, 원인은 각 필드가 <code>formState.errors</code> 전체 객체를 구독하고 있었기 때문입니다. 최종적으로 <code>useFormState({ name })</code>로 각 필드가 자기 에러만 구독하도록 했습니다.</p>
<p>검증은 Profiler와 <code>console.count</code>를 함께 사용했습니다. Profiler로 영향 범위를 확인하고, 실제 렌더 함수 실행 여부는 <code>console.count</code>로 확인했습니다. 그 결과 렌더 함수 실행 횟수가 75회에서 1회로 줄었습니다.</p>
<p><strong>꼬리 질문</strong></p>
<ul>
<li>왜 <code>React.memo()</code>가 근본 해결책이 아니었나요?</li>
<li>Profiler와 <code>console.count</code>를 함께 사용한 이유는 무엇인가요?</li>
<li>이 결과가 사용자 체감 성능 개선을 의미한다고 단정할 수 있나요?</li>
</ul>
<p><strong>꼬리 질문 답변</strong></p>
<p><code>React.memo()</code>는 props가 같을 때 자식 컴포넌트 렌더를 건너뛸 수 있게 도와주는 도구입니다. 하지만 당시 문제는 상위 컴포넌트가 폼 값을 구독하면서 상태 변경의 시작점이 너무 위에 잡힌 구조였습니다. 그래서 메모이제이션으로 결과를 막기보다 값이 필요한 컴포넌트에서만 구독하도록 위치를 옮기는 것이 원인에 맞는 해결책이었습니다.</p>
<p>75회에서 1회는 렌더 함수 실행 횟수가 줄었다는 의미입니다. 사용자 체감 성능까지 검증하려면 production build와 CPU throttling 환경에서 INP 후보나 Long Task를 추가로 확인해야 합니다.</p>
<h3 id="q2-code-splitting-적용-기준을-설명해-주세요">Q2. Code Splitting 적용 기준을 설명해 주세요.</h3>
<p><strong>답변</strong></p>
<p>저는 <code>next/dynamic</code>을 모든 컴포넌트에 적용하지 않고 세 가지 기준으로 판단했습니다.</p>
<p>첫째, 초기 화면에서 바로 필요하지 않고 조건부로 렌더링되는지입니다. 둘째, 해당 컴포넌트에서만 사용하는 비교적 무거운 의존성이 있는지입니다. 셋째, 현재 페이지의 LCP 요소가 아닌지입니다.</p>
<p><code>RegisterModal</code>과 <code>ProfileEditModal</code>은 이 조건을 만족해 지연 로딩 대상으로 봤습니다. 반대로 <code>KakaoMapView</code>는 사용자가 처음 보는 주요 콘텐츠, 즉 LCP 요소였기 때문에 제외했습니다. 실제로 지도 컴포넌트를 lazy load했을 때 LCP가 17% 악화되어 TBT 개선보다 초기 주요 콘텐츠 노출 손실이 더 크다고 판단했습니다.</p>
<p><strong>꼬리 질문</strong></p>
<ul>
<li>barrel import가 왜 Code Splitting을 방해했나요?</li>
<li>Code Splitting과 Tree Shaking은 어떻게 다른가요?</li>
<li>TBT와 LCP가 충돌하면 무엇을 우선하나요?</li>
</ul>
<h3 id="q3-tanstack-query-hydration-pattern을-어떤-기준으로-적용했나요">Q3. TanStack Query Hydration Pattern을 어떤 기준으로 적용했나요?</h3>
<p><strong>답변</strong></p>
<p>Hydration Pattern은 클라이언트에서 발생하던 데이터 대기 시간을 서버의 TTFB 구간으로 옮기는 방식이라고 이해했습니다. 요청 비용이 사라지는 것이 아니라 요청 시점과 사용자가 보는 경험이 달라지는 것입니다.</p>
<p>UNIBUSK의 공연 목록과 상세 페이지는 사용자가 진입하자마자 데이터가 필요하고 SEO도 중요했습니다. 그래서 서버에서 데이터를 먼저 가져온 뒤 TanStack Query 캐시를 클라이언트로 넘겼습니다. 그 결과 클라이언트에서 발생하던 동일 API 요청을 2건에서 0건으로 줄였고, Slow 3G 환경의 2초 이상 대기 시간을 제거했습니다.</p>
<p>반대로 인증 페이지에는 적용하지 않았습니다. 사용자별 개인화 데이터는 캐시 정책을 잘못 설정하면 다른 사용자에게 노출될 위험이 있고, 인증 이후 접근하는 페이지라 SEO 이점도 작다고 판단했습니다.</p>
<p><strong>꼬리 질문</strong></p>
<ul>
<li><code>prefetchQuery</code>와 <code>setQueryData</code>는 어떤 기준으로 구분했나요?</li>
<li>TTFB 증가를 감수한 이유는 무엇인가요?</li>
<li>개인화 데이터에서 어떤 캐시 위험이 있나요?</li>
</ul>
<h3 id="q4-kakao-oauth2-cross-domain-cookie-문제를-어떻게-해결했나요">Q4. Kakao OAuth2 Cross-Domain Cookie 문제를 어떻게 해결했나요?</h3>
<p><strong>답변</strong></p>
<p>처음에는 CORS나 클라이언트 코드 문제처럼 보였지만, 인증 흐름을 로그인 요청, Kakao 리다이렉트, 쿠키 저장, 후속 API 요청 단계로 나눠 확인했습니다.</p>
<p>핵심 원인은 프론트엔드와 백엔드 도메인이 분리된 환경에서 브라우저가 쿠키를 도메인별로 관리한다는 점이었습니다. 백엔드 도메인에 저장된 쿠키를 프론트엔드 도메인에서 자신의 쿠키처럼 사용할 수 없었습니다.</p>
<p>Route Handler, 터널링, IP 기반 쿠키 도메인, Middleware 접근 등을 검토했지만 각각 한계가 있었습니다. 최종적으로 백엔드가 로그인 성공 후 임시 Authorization Code를 프론트엔드 콜백 URL로 전달하고, 프론트엔드는 동일 출처의 Proxy를 통해 이 코드를 토큰으로 교환하는 방식으로 변경했습니다.</p>
<p>이 방식은 쿠키 문제를 클라이언트에서 억지로 우회한 것이 아니라, 인증 흐름 자체를 동일 출처 중심으로 재구성한 접근입니다.</p>
<h2 id="3-dessert-front-dashboard-예상-질문">3. Dessert Front Dashboard 예상 질문</h2>
<h3 id="q5-모노레포를-선택한-이유는-무엇인가요">Q5. 모노레포를 선택한 이유는 무엇인가요?</h3>
<p><strong>답변</strong></p>
<p>Seller 프로젝트만 개발하던 시점에 Admin 프로젝트 기획이 확정됐습니다. 별도 저장소로 분리하면 디자인 시스템 컴포넌트, SVGR 아이콘, 디자인 토큰을 두 저장소에서 각각 관리해야 하는 문제가 예상됐습니다.</p>
<p>공유 컴포넌트를 npm 패키지로 배포하는 방식도 검토했습니다. 하지만 같은 팀이 Seller와 Admin을 빠르게 함께 개발하는 상황에서는 수정할 때마다 버전 업데이트, 배포, 각 저장소 패키지 업그레이드 과정이 필요해 비용이 크다고 판단했습니다.</p>
<p>그래서 Yarn Workspaces와 Turborepo 기반 모노레포를 선택했습니다. Yarn Workspaces는 워크스페이스 간 의존성 연결을 담당하고, Turborepo는 작업 실행 순서와 캐싱을 담당하도록 역할을 나눴습니다.</p>
<p><strong>꼬리 질문</strong></p>
<ul>
<li>Yarn Workspaces와 Turborepo의 역할은 어떻게 다른가요?</li>
<li>모노레포의 단점은 무엇인가요?</li>
<li>FSD의 <code>shared</code>와 모노레포의 <code>packages/</code>는 어떻게 다른가요?</li>
</ul>
<h3 id="q6-fsd-컨벤션을-왜-문서화했나요">Q6. FSD 컨벤션을 왜 문서화했나요?</h3>
<p><strong>답변</strong></p>
<p>FSD 폴더 구조는 있었지만 Segment 분류 기준이 문서화되어 있지 않아 팀원마다 해석이 달랐습니다. 같은 <code>features</code> 레이어 안에서도 어떤 슬라이스는 <code>components</code>, <code>hooks</code>, <code>utils</code>로 나뉘고 어떤 슬라이스는 파일만 나열되어 있었습니다.</p>
<p>이로 인해 새 파일을 추가할 때마다 위치 결정 논의가 반복됐고, PR 리뷰에서도 구조 논의가 계속 발생했습니다. 그래서 파일 suffix 규칙, 단방향 의존성, Public API, Cross-import 금지 원칙을 문서화했습니다.</p>
<p>또 TanStack Query 관련 파일은 읽기와 쓰기의 성격으로 배치 기준을 나눴습니다. 여러 기능에서 재사용되는 <code>useQuery</code> 관련 파일은 <code>entities</code>, 특정 사용자 행동과 강하게 연결되는 <code>useMutation</code> 관련 파일은 <code>features</code>에 두도록 정리했습니다.</p>
<p><strong>면접 포인트</strong></p>
<p>문서화 자체가 목적이 아니라 반복되는 의사결정 비용을 줄이고, 리뷰에서 비즈니스 로직에 집중하기 위한 장치였다고 말합니다.</p>
<h3 id="q7-redux-saga-구조를-왜-tanstack-query-중심으로-바꿨나요">Q7. Redux Saga 구조를 왜 TanStack Query 중심으로 바꿨나요?</h3>
<p><strong>답변</strong></p>
<p>기존 구조는 Redux Toolkit, Redux Saga, 내부 유틸리티인 <code>reduxMaker</code>를 함께 사용했습니다. <code>reduxMaker</code>는 반복 코드를 줄이는 장점이 있었지만 이 프로젝트 내부에서만 통용되는 비표준 API였습니다. 따라서 공식 문서를 학습해도 실제 사용법을 이해하려면 내부 구현을 별도로 분석해야 했습니다.</p>
<p>또 Redux Saga가 담당하던 비동기 처리의 대부분은 API 요청과 서버 상태 저장이었습니다. 복잡한 비동기 플로우 제어보다 서버 상태의 캐싱, 최신성, 재요청, mutation 이후 동기화가 더 중요한 요구사항이었습니다.</p>
<p>그래서 서버 상태는 TanStack Query, 클라이언트 상태는 Zustand, 폼 상태는 React Hook Form과 Zod로 역할을 분리했습니다. 전환 후에는 <code>queryKey</code>, <code>queryFn</code>, <code>mutationFn</code>이 명시적으로 드러나 요청 흐름을 추적하기 쉬워졌고, <code>invalidateQueries</code>로 데이터 변경 후 재조회 흐름을 선언적으로 표현할 수 있었습니다.</p>
<h2 id="4-real-world-예상-질문">4. Real World 예상 질문</h2>
<h3 id="q8-react-router-loader로-request-waterfall을-어떻게-줄였나요">Q8. React Router loader로 Request Waterfall을 어떻게 줄였나요?</h3>
<p><strong>답변</strong></p>
<p>기존 구조에서는 컴포넌트가 마운트된 후 <code>useEffect</code>에서 데이터 패칭이 시작됐습니다. 이 방식은 화면 렌더링 이후 API 요청이 시작되므로 route 이동 시 컴포넌트 로드와 데이터 요청이 순차적으로 이어지는 waterfall이 발생했습니다.</p>
<p>React Router v7.5의 object-based <code>route.lazy</code> API를 사용해 <code>loader</code>와 <code>Component</code>를 독립적으로 lazy load하도록 분리했습니다. 기존처럼 하나의 <code>lazy()</code> 함수에서 loader와 Component를 함께 반환하면 route module 다운로드가 끝난 뒤에야 loader가 실행될 수 있습니다. 반면 object-based 방식은 loader 모듈이 먼저 준비되면 데이터 요청을 더 일찍 시작할 수 있습니다.</p>
<p>적용 결과 route 이동 후 데이터 표시까지 걸리는 시간이 Slow 4G에서 5,789ms에서 4,087ms로, 3G에서 20,506ms에서 15,306ms로 개선됐습니다.</p>
<p><strong>꼬리 질문</strong></p>
<ul>
<li><code>useEffect</code> 데이터 패칭이 항상 나쁜가요?</li>
<li>Loader로 옮기면 어떤 트레이드오프가 있나요?</li>
<li>측정 환경과 반복 측정 여부는 어떻게 관리했나요?</li>
</ul>
<h3 id="q9-refresh-token-중복-호출-문제를-어떻게-해결했나요">Q9. Refresh Token 중복 호출 문제를 어떻게 해결했나요?</h3>
<p><strong>답변</strong></p>
<p>Access Token이 만료된 상태에서 대시보드를 로드할 때, 동시에 실행된 4개 API 요청이 모두 401 응답을 받는 상황이 있었습니다. 기존 구조에서는 각 요청이 독립적으로 토큰 재발급을 시도해 Refresh Token 요청이 4번 중복 발생했습니다.</p>
<p>Axios Response Interceptor에 <code>TokenManager</code>를 구현해 토큰 재발급 요청을 단일화했습니다. <code>refreshState</code>로 재발급 진행 여부를 관리해 첫 번째 401 요청만 실제 재발급을 시작하고, 이후 들어온 요청은 Promise 기반 대기 큐에 추가했습니다.</p>
<p>재발급이 완료되면 새 Access Token을 적용해 대기 중이던 요청을 일괄 재시도했습니다. 재발급 후에도 401이 반복되는 경우를 대비해 <code>_retry</code> 플래그로 재시도를 1회로 제한했습니다. 결과적으로 Refresh Token 요청을 4회에서 1회로 줄였습니다.</p>
<p><strong>꼬리 질문</strong></p>
<ul>
<li>재발급 요청 자체가 실패하면 대기 중인 요청은 어떻게 처리하나요?</li>
<li>무한 루프는 어떻게 막나요?</li>
<li>여러 탭에서 동시에 만료되면 어떻게 될 수 있나요?</li>
</ul>
<h3 id="q10-백엔드-경험을-어떻게-설명하겠나요">Q10. 백엔드 경험을 어떻게 설명하겠나요?</h3>
<p><strong>답변</strong></p>
<p>Java 또는 Kotlin 기반 Spring Boot를 주력 스택으로 사용해 온 개발자는 아닙니다. 다만 Real World에서 NestJS, PostgreSQL, Prisma를 사용해 Controller, Service, Repository 계층 분리와 DI 기반 서버 구조를 직접 구현했습니다.</p>
<p>이 답변은 ItNew 공고 자격요건에서 가장 중요한 압박 질문이므로, 상세 답변과 꼬리 질문은 별도 문서로 옮겨 정리했습니다.</p>
<ul>
<li>상세 답변: <a href="job-requirements-qa.md#q1-java-%EB%98%90%EB%8A%94-kotlin-%EA%B8%B0%EB%B0%98-spring-boot-%EC%8B%A4%EB%AC%B4-%EA%B2%BD%ED%97%98%EC%9D%B4-%EC%9A%94%EA%B5%AC%EB%90%98%EB%8A%94%EB%8D%B0-%EB%B3%B8%EC%9D%B8%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%B3%B4%EC%99%84%ED%95%A0-%EC%88%98-%EC%9E%88%EB%82%98%EC%9A%94">Java 또는 Kotlin 기반 Spring Boot 실무 경험이 요구되는데, 본인은 어떻게 보완할 수 있나요?</a></li>
<li>꼬리 질문: <a href="job-requirements-qa.md#q2-ioc%EC%99%80-di%EC%9D%98-%EC%B0%A8%EC%9D%B4%EB%A5%BC-%EC%84%A4%EB%AA%85%ED%95%B4-%EC%A3%BC%EC%84%B8%EC%9A%94">IoC와 DI의 차이를 설명해 주세요</a>, <a href="job-requirements-qa.md#q3-aop%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-nestjs-%EA%B2%BD%ED%97%98%EA%B3%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%97%B0%EA%B2%B0%ED%95%B4%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%96%88%EB%82%98%EC%9A%94">AOP가 무엇이고, NestJS 경험과 어떻게 연결해서 이해했나요?</a></li>
</ul>
<p><strong>꼬리 질문</strong></p>
<ul>
<li>IoC와 DI의 차이를 설명해 주세요.</li>
<li>Spring의 어노테이션 기반 구성과 NestJS의 데코레이터 기반 구조를 어떻게 연결해서 이해했나요?</li>
<li>AOP가 무엇이고, NestJS에서는 어떤 계층이 비슷한 역할을 하나요?</li>
<li>Spring AOP와 NestJS Interceptor가 완전히 같다고 볼 수 있나요?</li>
<li>Controller, Service, Repository를 왜 나누나요?</li>
<li>Spring Boot를 실제로 더 학습해야 할 부분은 무엇인가요?</li>
</ul>
<h3 id="q11-cicd를-사용해봤고-어떻게-구축했나요">Q11. CI/CD를 사용해봤고, 어떻게 구축했나요?</h3>
<p><strong>답변</strong></p>
<p>Real World 백엔드 저장소에서 GitHub Actions 기반 CI를 구성해 봤습니다. 완전한 의미의 자동 배포까지 연결한 CD 파이프라인을 운영했다고 말하기는 어렵고, 현재 경험은 코드 변경 후 테스트와 품질 검사를 자동화하는 CI에 더 가깝습니다.</p>
<p>구성은 두 단계로 나눴습니다. <code>ci-mini.yml</code>은 <code>main</code> 브랜치 push와 pull request에서 실행되도록 두고, checkout, Node.js 20 설정, npm 캐시, <code>npm ci</code>, <code>npm run lint</code>까지 빠르게 확인하게 했습니다. 목적은 PR 단계에서 기본적인 문법, 포맷, 린트 문제를 빨리 잡는 것이었습니다.</p>
<p><code>ci.yml</code>은 수동 실행인 <code>workflow_dispatch</code>로 두고, 더 무거운 검증을 수행하도록 구성했습니다. PostgreSQL 16 service container를 띄우고, GitHub Secrets로 DB와 JWT 환경변수를 주입한 뒤, <code>prisma migrate deploy</code>, lint, unit test, e2e test, coverage, Codecov 업로드 순서로 실행했습니다. E2E 테스트는 실제 DB와 인증 환경변수가 필요하기 때문에 단순 유닛 테스트와 분리해서 봤습니다.</p>
<p>면접에서는 &quot;자동 배포까지 완성한 CD 경험은 제한적이지만, GitHub Actions로 CI 파이프라인을 구성해 코드 변경 시 의존성 설치, 린트, 테스트, DB 마이그레이션, E2E 검증을 자동화해 본 경험이 있다&quot;고 답하겠습니다.</p>
<p><strong>꼬리 질문</strong></p>
<ul>
<li>CI와 CD의 차이는 무엇인가요?</li>
<li>GitHub Actions에서 workflow, job, step, runner는 각각 무엇인가요?</li>
<li>왜 빠른 CI와 무거운 CI를 분리했나요?</li>
<li>CI에서 DB가 필요한 E2E 테스트는 어떻게 실행했나요?</li>
<li>Secrets를 사용하는 이유는 무엇인가요?</li>
</ul>
<p><strong>꼬리 질문 답변</strong></p>
<p>CI는 코드 변경이 기존 기능을 깨뜨리지 않는지 자동으로 확인하는 과정입니다. 보통 의존성 설치, 정적 검사, 테스트, 빌드가 포함됩니다. CD는 CI를 통과한 결과물을 staging이나 production 환경에 배포하는 과정입니다. 저는 BE-realworld에서 CI 검증은 구성했지만, production 배포까지 자동화한 경험은 아직 제한적이라고 구분해서 말하겠습니다.</p>
<p>GitHub Actions에서 workflow는 <code>.github/workflows</code>에 있는 YAML 자동화 단위이고, event는 push, pull request, workflow_dispatch처럼 workflow를 실행시키는 조건입니다. job은 runner에서 실행되는 작업 묶음이고, step은 job 안에서 실행되는 개별 명령이나 action입니다. runner는 이 job을 실제로 실행하는 가상 머신입니다.</p>
<p>빠른 CI와 무거운 CI를 나눈 이유는 피드백 속도 때문입니다. 모든 PR마다 DB service, migration, E2E, coverage까지 돌리면 검증은 강해지지만 대기 시간이 길어집니다. 그래서 기본 PR에서는 lint 중심의 빠른 검사를 먼저 수행하고, DB가 필요한 전체 검증은 별도 workflow로 실행하는 구조로 나눴습니다.</p>
<h2 id="5-itnew-공고와-프로젝트-연결-포인트">5. ItNew 공고와 프로젝트 연결 포인트</h2>
<table>
<thead>
<tr>
<th>ItNew 공고 키워드</th>
<th>연결할 경험</th>
</tr>
</thead>
<tbody><tr>
<td>React, Next.js 기반 프론트엔드</td>
<td>UNIBUSK의 Next.js App Router, Code Splitting, Hydration Pattern</td>
</tr>
<tr>
<td>RESTful API 설계·개발 경험</td>
<td>Real World의 API 설계, UNIBUSK·Dessert의 REST API 연동</td>
</tr>
<tr>
<td>RDBMS 기반 DB 설계·운영</td>
<td>Real World의 PostgreSQL, Prisma, 관계형 데이터 모델링</td>
</tr>
<tr>
<td>도메인 모델링·아키텍처</td>
<td>Dessert의 FSD 컨벤션, Real World의 3계층 구조</td>
</tr>
<tr>
<td>CI/CD·자동 검증</td>
<td>BE-realworld의 GitHub Actions, PostgreSQL service container, Prisma migrate, unit/e2e test</td>
</tr>
<tr>
<td>서비스 품질 개선</td>
<td>UNIBUSK 렌더링·TBT·클라이언트 API 요청 개선</td>
</tr>
<tr>
<td>AI 코드 품질·보안 검토</td>
<td>AI 제안을 가설로 보고 타입, 테스트, 보안, 아키텍처 기준으로 검증하는 방식</td>
</tr>
<tr>
<td>사용자 경험 고려</td>
<td>LCP 요소 lazy load 제외, 개인화 데이터 Hydration 제외, 폼 렌더링 범위 축소</td>
</tr>
</tbody></table>
<h2 id="6-압박-질문-대비">6. 압박 질문 대비</h2>
<h3 id="q-프론트엔드-중심-경험인데-itnew는-백엔드와-db도-요구합니다-괜찮나요">Q. 프론트엔드 중심 경험인데 ItNew는 백엔드와 DB도 요구합니다. 괜찮나요?</h3>
<p><strong>답변</strong></p>
<p>Java 또는 Kotlin 기반 Spring Boot를 실무 프로젝트의 주 스택으로 사용한 경험은 제한적입니다. 다만 프론트엔드에서도 API 계약, 인증, 캐시, 데이터 정합성을 이해해야 올바른 UI를 만들 수 있다고 생각해 Real World에서 NestJS와 PostgreSQL로 백엔드 구조를 직접 구현해 봤습니다.</p>
<p>Spring Boot 기초 강의를 통해 IoC/DI, 어노테이션 기반 구성, AOP를 통한 횡단 관심사 분리 개념을 학습했고, NestJS 프로젝트에서 Controller, Service, Repository 계층 분리와 DI 컨테이너 기반 의존성 주입을 적용했습니다. Spring과 NestJS가 같은 프레임워크는 아니지만, 선언적으로 컴포넌트의 역할을 드러내고 DI로 결합도를 낮추며, 인증·로깅·예외 처리 같은 공통 관심사를 별도 계층으로 분리한다는 설계 관점은 연결해서 이해하고 있습니다.</p>
<p>Spring Boot 생태계의 JPA, 트랜잭션 전파, Spring Security 같은 세부 경험은 보완해야 하지만, 계층 분리와 REST API, RDBMS 기반 서버 설계 경험을 바탕으로 팀 코드베이스에 빠르게 적응하겠습니다.</p>
<h3 id="q-성능-수치가-실제-사용자-경험-개선을-의미하나요">Q. 성능 수치가 실제 사용자 경험 개선을 의미하나요?</h3>
<p><strong>답변</strong></p>
<p>수치마다 의미하는 범위를 구분해야 한다고 생각합니다. 예를 들어 UNIBUSK의 75회에서 1회는 렌더 함수 실행 횟수가 줄었다는 의미이지, 곧바로 모든 사용자의 체감 성능이 개선됐다는 의미는 아닙니다.</p>
<p>반면 클라이언트 API 요청 2건에서 0건은 Network 탭에서 동일 API 요청이 제거된 것을 확인한 결과이고, Slow 3G 환경의 대기 시간 제거와 연결해 설명할 수 있습니다. 사용자 체감까지 더 엄밀히 보려면 production build, CPU throttling, INP, Long Task 같은 지표를 함께 봐야 합니다.</p>
<h3 id="q-ai가-작성한-코드를-어느-정도-신뢰하나요">Q. AI가 작성한 코드를 어느 정도 신뢰하나요?</h3>
<p><strong>답변</strong></p>
<p>AI의 답변은 결론이 아니라 가설로 봅니다. 반복 코드 초안이나 대안 탐색에는 유용하지만, 요구사항·보안·아키텍처 판단은 개발자가 해야 합니다.</p>
<p>특히 인증, 캐시, DB, 권한처럼 잘못 설계하면 사용자 데이터나 서비스 안정성에 영향을 주는 영역은 AI 코드를 그대로 적용하지 않습니다. 타입체크, 테스트, 리뷰, 실제 동작 검증, 성능 측정을 통해 확인합니다.</p>
<h3 id="q-본인이-팀-프로젝트에서-실제로-기여한-범위는-어디까지인가요">Q. 본인이 팀 프로젝트에서 실제로 기여한 범위는 어디까지인가요?</h3>
<p><strong>답변</strong></p>
<p>UNIBUSK에서는 공연 정보, 공연 등록·상세, Kakao OAuth2, 프로필 영역을 담당했고, 렌더링 최적화와 Code Splitting, Hydration Pattern 검증은 제가 직접 분석하고 적용한 범위입니다.</p>
<p>Dessert Front Dashboard에서는 모노레포 설계, 공용 패키지 분리, FSD 컨벤션 문서화, 코드 리뷰, 관리자 대시보드·공통 패키지 개발을 담당했습니다. 다만 팀 전체의 성과와 제 개인 기여는 구분해서 말하겠습니다. 예를 들어 모노레포 전환 방향과 문서화에는 주도적으로 기여했지만, 모든 기능 구현을 혼자 담당한 것은 아닙니다.</p>
<h2 id="7-면접-직전-체크리스트">7. 면접 직전 체크리스트</h2>
<ul>
<li><input disabled="" type="checkbox"> UNIBUSK 렌더링 최적화: <code>watch()</code> → <code>useWatch()</code>, <code>formState.errors</code> → <code>useFormState({ name })</code></li>
<li><input disabled="" type="checkbox"> Code Splitting: 조건부 렌더링, 무거운 의존성, LCP 제외, barrel import 문제</li>
<li><input disabled="" type="checkbox"> Hydration Pattern: 요청 비용 제거가 아니라 대기 시간 위치 이동, TTFB 트레이드오프</li>
<li><input disabled="" type="checkbox"> OAuth2: CORS와 Cookie 저장 도메인 문제 구분</li>
<li><input disabled="" type="checkbox"> Dessert: 모노레포 선택 이유, Yarn Workspaces와 Turborepo 역할 구분</li>
<li><input disabled="" type="checkbox"> FSD: suffix 규칙, 단방향 의존성, Public API, Cross-import 금지</li>
<li><input disabled="" type="checkbox"> Real World: Request Waterfall 제거, Refresh Token 대기 큐</li>
<li><input disabled="" type="checkbox"> BE-realworld: GitHub Actions CI, PostgreSQL service, Prisma migrate, unit/e2e test</li>
<li><input disabled="" type="checkbox"> ItNew 연결: AI·모빌리티·데이터, React/Next.js, REST API, RDBMS, AI 코드 검토</li>
</ul>
<h2 id="참고한-공개-자료">참고한 공개 자료</h2>
<ul>
<li>ItNew 원티드 채용공고: <a href="https://www.wanted.co.kr/wd/358179">https://www.wanted.co.kr/wd/358179</a></li>
<li>ItNew 공식 홈페이지: <a href="https://itnewcorp.com/">https://itnewcorp.com/</a></li>
<li>ItNew 원티드 기업정보: <a href="https://www.wanted.co.kr/company/41725">https://www.wanted.co.kr/company/41725</a></li>
<li>THE VC ItNew 기업정보: <a href="https://thevc.kr/itnew">https://thevc.kr/itnew</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자격요건 중심 예상 질문과 답변]]></title>
            <link>https://velog.io/@park-moen/%EC%9E%90%EA%B2%A9%EC%9A%94%EA%B1%B4-%EC%A4%91%EC%8B%AC-%EC%98%88%EC%83%81-%EC%A7%88%EB%AC%B8%EA%B3%BC-%EB%8B%B5%EB%B3%80-h727ztwq</link>
            <guid>https://velog.io/@park-moen/%EC%9E%90%EA%B2%A9%EC%9A%94%EA%B1%B4-%EC%A4%91%EC%8B%AC-%EC%98%88%EC%83%81-%EC%A7%88%EB%AC%B8%EA%B3%BC-%EB%8B%B5%EB%B3%80-h727ztwq</guid>
            <pubDate>Wed, 15 Jul 2026 06:51:53 GMT</pubDate>
            <description><![CDATA[<h2 id="0-답변-전략">0. 답변 전략</h2>
<p>자격요건은 Java/Kotlin Spring Boot 경험을 직접 요구하지만, 현재 실제 백엔드 프로젝트 경험은 NestJS, TypeScript, Prisma, PostgreSQL 기반의 Real World 구현입니다. 따라서 Spring Boot 실무 경험이 있는 것처럼 말하지 않고, 아래처럼 연결해서 답합니다.</p>
<ol>
<li>Spring Boot 주력 경험은 없다고 먼저 인정합니다.</li>
<li>대신 NestJS에서 Controller, Service, Repository, Module, DI를 사용해 서버를 구현한 경험을 설명합니다.</li>
<li>PostgreSQL/Prisma 스키마, Refresh Token, 트랜잭션, DTO 검증, E2E 테스트처럼 저장소에서 확인 가능한 근거를 중심으로 말합니다.</li>
<li>Java/Kotlin과 Spring Boot는 입사 후 보완할 영역으로 분리하고, 빠르게 따라갈 수 있는 기반 역량을 제시합니다.</li>
<li>AI Native 역량은 &quot;AI를 썼다&quot;가 아니라 &quot;반복 작업을 위임하되, 보안/인증/DB/아키텍처는 사람이 검증한다&quot;는 태도로 답합니다.</li>
</ol>
<h2 id="1-기본-개발-역량">1. 기본 개발 역량</h2>
<h3 id="q1-java-또는-kotlin-기반-spring-boot-실무-경험이-요구되는데-본인은-어떻게-보완할-수-있나요">Q1. Java 또는 Kotlin 기반 Spring Boot 실무 경험이 요구되는데, 본인은 어떻게 보완할 수 있나요?</h3>
<p><strong>답변</strong></p>
<p>Java나 Kotlin 기반 Spring Boot를 주력으로 실무 프로젝트를 진행한 경험은 아직 없습니다. 대신 서버 애플리케이션의 기본 구조와 문제 해결 방식은 NestJS 기반 프로젝트에서 직접 경험했습니다.</p>
<p>다만 Real World 백엔드 프로젝트에서 NestJS, TypeScript, Prisma, PostgreSQL을 사용해 블로그 서비스에 필요한 사용자, 게시글, 댓글, 태그, 팔로우, 좋아요, 인증 기능을 구현했습니다. NestJS에서도 Controller가 HTTP 요청을 받고, Service가 유스케이스를 처리하며, Repository가 Prisma를 통해 데이터 접근을 담당하도록 책임을 나눴습니다. 또한 Module에 provider를 등록하고, 필요한 Service나 Repository를 생성자에서 주입받는 방식으로 의존성을 연결했습니다.</p>
<p>Spring Boot와 NestJS가 같은 프레임워크라는 뜻은 아닙니다. 하지만 계층 분리, DI, REST API, RDBMS 모델링, 인증/인가, 트랜잭션, 테스트 같은 서버 애플리케이션의 기본 문제는 연결해서 이해하고 있습니다. 입사 후에는 팀의 Spring Boot 코드베이스를 Controller-Service-Repository 흐름부터 읽고, JPA, 트랜잭션, Spring Security, Bean 생명주기 순서로 빠르게 보완하겠습니다.</p>
<p><strong>꼬리 질문</strong></p>
<ul>
<li>Spring Boot 경험이 없는데 서버 코드를 읽을 수 있다고 판단하는 근거는 무엇인가요?</li>
<li>NestJS의 DI와 Spring의 DI는 어떤 점이 유사하고, 어떤 점은 다르다고 보나요?</li>
<li>입사 후 Spring Boot를 학습한다면 어떤 순서로 학습하겠습니까?</li>
</ul>
<h3 id="q2-nestjs-프로젝트에서-계층-분리를-어떻게-적용했나요">Q2. NestJS 프로젝트에서 계층 분리를 어떻게 적용했나요?</h3>
<p><strong>답변</strong></p>
<p>Real World에서는 기능별로 <code>user</code>, <code>auth</code>, <code>article</code>, <code>comment</code>, <code>profile</code>, <code>tag</code>, <code>favorite</code>, <code>follow</code> 모듈을 나누고, 각 도메인 안에서 Controller, Service, Repository의 책임을 분리했습니다.</p>
<p>예를 들어 사용자 흐름에서는 Controller가 회원가입, 로그인, 토큰 갱신 같은 HTTP 엔드포인트를 받고, UserService가 이메일 중복 확인, 비밀번호 검증, 토큰 발급 요청 같은 유스케이스를 처리합니다. UserRepository는 Prisma를 통해 실제 DB 조회와 변경을 담당합니다.</p>
<p>게시글 쪽에서는 ArticleService가 slug 생성, 작성자 권한 확인, 응답 조립을 담당하고, ArticleTransaction이 게시글 생성/수정과 태그 연결을 하나의 트랜잭션으로 묶습니다. 이렇게 나눈 이유는 HTTP 계층, 비즈니스 흐름, 데이터 접근 방식이 각각 다른 이유로 변경되기 때문입니다.</p>
<h3 id="q3-ioc와-di를-실제-코드에서-어떻게-사용했나요">Q3. IoC와 DI를 실제 코드에서 어떻게 사용했나요?</h3>
<p><strong>답변</strong></p>
<p>IoC는 객체 생성과 의존성 연결의 제어권을 개발자가 직접 갖지 않고 프레임워크 컨테이너에 맡기는 원칙이고, DI는 그 원칙을 구현하는 대표적인 방식입니다.</p>
<p>Real World에서는 Service나 Repository를 직접 <code>new</code>로 생성하지 않았습니다. 예를 들어 <code>UserService</code>는 <code>AuthService</code>와 <code>UserRepository</code>를 생성자에서 주입받고, <code>ArticleTransaction</code>은 <code>TransactionService</code>, <code>ArticleRepository</code>, <code>TagRepository</code>, <code>ArticleToTagRepository</code>를 주입받습니다. 필요한 의존성을 NestJS Module의 provider로 등록해 컨테이너가 연결하도록 했습니다.</p>
<p>이 구조의 장점은 변경과 테스트입니다. Service가 구체적인 생성 방식에 의존하지 않기 때문에 테스트에서는 mock repository나 test factory로 대체하기 쉽고, 실제 구현에서는 모듈 단위로 의존성을 추적할 수 있습니다.</p>
<h3 id="q4-restful-api를-설계할-때-무엇을-고려하나요">Q4. RESTful API를 설계할 때 무엇을 고려하나요?</h3>
<p><strong>답변</strong></p>
<p>REST API는 먼저 리소스를 기준으로 URI를 설계하고, HTTP Method로 행위를 표현해야 한다고 생각합니다. Real World에서는 사용자, 프로필, 게시글, 댓글, 태그처럼 리소스가 비교적 명확한 도메인을 다뤘습니다.</p>
<p>또 하나 중요하게 본 것은 프론트엔드가 처리 가능한 응답 구조입니다. 단순히 성공/실패만 내려주는 것이 아니라, 인증 실패, 권한 없음, 유효성 검증 실패, 리소스 없음 같은 상황을 구분할 수 있어야 UI에서 로그인 유도, 폼 에러 표시, 빈 상태, 재시도 흐름을 다르게 처리할 수 있습니다.</p>
<p>그래서 DTO에 <code>class-validator</code>를 사용해 입력값을 검증했고, 도메인별 Error 클래스를 만들어 이메일 중복, 비밀번호 불일치, 게시글 없음, 권한 없음 같은 상황을 구분하려고 했습니다.</p>
<p><strong>꼬리 질문</strong></p>
<ul>
<li><code>PUT</code>과 <code>PATCH</code>의 차이를 어떻게 설명하겠습니까?</li>
<li>멱등성(idempotent)이 무엇이고, API 설계에서 왜 중요한가요?</li>
<li>좋은 에러 응답은 프론트엔드 개발에 어떤 영향을 주나요?</li>
<li>DTO가 무엇인가요?</li>
<li>DAO 또는 Data Access 계층을 아나요?</li>
</ul>
<p><strong>꼬리 질문 답변</strong></p>
<h4 id="put과-patch의-차이를-어떻게-설명하겠습니까"><code>PUT</code>과 <code>PATCH</code>의 차이를 어떻게 설명하겠습니까?</h4>
<p><code>PUT</code>과 <code>PATCH</code>는 둘 다 리소스 수정에 사용하지만 의미가 다릅니다. <code>PUT</code>은 리소스 전체를 교체하는 의미에 가깝고, <code>PATCH</code>는 리소스의 일부 필드만 수정하는 의미에 가깝습니다. 예를 들어 사용자 프로필 전체를 동일한 형태로 다시 저장한다면 <code>PUT</code>이 어울리고, <code>bio</code>나 <code>image</code>처럼 일부 값만 바꾸는 요청이라면 <code>PATCH</code>가 더 자연스럽습니다.</p>
<h4 id="그렇다면-get-post-delete-options는-뭔가요">그렇다면 <code>GET</code>, <code>POST</code>, <code>DELETE</code>, <code>OPTIONS</code>는 뭔가요?</h4>
<p><code>GET</code>은 리소스를 조회할 때 사용하며 서버 상태를 변경하지 않는 안전한(safe) 메서드입니다. 여러 번 호출해도 결과가 같아야 하는 멱등성(idempotent)도 만족합니다. <code>POST</code>는 새 리소스를 생성하거나 멱등성을 보장할 수 없는 처리(예: 로그인, 검색 실행)에 사용합니다. 호출할 때마다 결과가 달라질 수 있어 멱등하지 않습니다. <code>DELETE</code>는 리소스를 삭제하는 메서드입니다. 같은 요청을 반복해도 결과 상태는 &quot;리소스가 없음&quot;으로 동일하므로 멱등하다고 봅니다. <code>OPTIONS</code>는 특정 리소스가 지원하는 HTTP 메서드나 CORS 관련 정보를 서버에 사전 확인(preflight)할 때 사용합니다. 실제로는 브라우저가 CORS 요청 전에 자동으로 보내는 경우가 많습니다.</p>
<h4 id="멱등성idempotent이-무엇이고-api-설계에서-왜-중요한가요">멱등성(idempotent)이 무엇이고, API 설계에서 왜 중요한가요?</h4>
<p>멱등성은 같은 요청을 한 번 보내든 여러 번 보내든 서버의 최종 상태가 동일하게 유지되는 성질을 말합니다. <code>GET</code>은 조회만 하므로 당연히 멱등하고, <code>PUT</code>은 리소스를 특정 값으로 통째로 교체하기 때문에 여러 번 보내도 최종 상태가 같아 멱등합니다. <code>DELETE</code>도 이미 삭제된 리소스에 같은 요청을 다시 보내도 결과 상태는 &quot;리소스 없음&quot;으로 동일하므로 멱등하다고 봅니다. 반면 <code>POST</code>는 호출할 때마다 새 리소스가 생성되거나(예: 게시글 생성 시 매번 새 id 부여) 상태가 달라질 수 있어 멱등하지 않습니다. 멱등성이 API 설계에서 중요한 이유는 네트워크 재시도 때문입니다. 클라이언트가 타임아웃으로 응답을 못 받아 같은 요청을 재전송했을 때, 멱등한 메서드라면 중복 실행되어도 안전하지만, 멱등하지 않은 <code>POST</code>는 게시글이 중복 생성되는 것 같은 부작용이 생길 수 있습니다. 그래서 결제처럼 중복 실행이 치명적인 <code>POST</code> 요청에는 별도의 idempotency key를 사용해 서버가 같은 요청을 구분하도록 설계하기도 합니다.</p>
<h4 id="좋은-에러-응답은-프론트엔드-개발에-어떤-영향을-주나요">좋은 에러 응답은 프론트엔드 개발에 어떤 영향을 주나요?</h4>
<p>Real World 프로젝트에서는 사용자 조회는 <code>GET</code>, 회원가입/로그인/게시글 생성은 <code>POST</code>, 프로필/게시글 수정은 <code>PATCH</code>, 게시글·댓글 삭제는 <code>DELETE</code>로 구현했습니다. <code>OPTIONS</code>는 직접 라우트 핸들러를 작성하지는 않았지만, NestJS의 CORS 설정을 통해 브라우저의 preflight 요청에 응답하도록 처리했습니다.</p>
<p>좋은 에러 응답은 프론트엔드가 사용자에게 어떤 행동을 안내해야 하는지 판단할 수 있게 해줍니다. 예를 들어 401은 로그인 만료나 인증 필요, 403은 권한 없음, 404는 리소스 없음, 400 계열의 validation error는 폼 필드별 메시지 표시로 연결될 수 있습니다. 에러 구조가 모호하면 프론트엔드는 모든 실패를 같은 토스트로 처리하게 되고, 사용자가 무엇을 수정해야 하는지 알기 어렵습니다.</p>
<h4 id="dto가-무엇인가요">DTO가 무엇인가요?</h4>
<p>DTO는 Data Transfer Object의 약자로, 계층 간 데이터를 주고받기 위한 객체입니다. NestJS에서는 주로 요청 body, query, params의 형태를 정의하고 검증하는 데 사용했습니다. 예를 들어 회원가입 DTO에는 email, username, password가 어떤 타입이고 어떤 제약을 가져야 하는지 표현할 수 있습니다. DTO를 쓰면 API 계약이 코드에 드러나고, 잘못된 입력을 Service 로직에 들어가기 전에 걸러낼 수 있습니다.</p>
<h4 id="dao-또는-data-access-계층을-아나요">DAO 또는 Data Access 계층을 아나요?</h4>
<p>DAO는 Data Access Object의 약자로, 데이터베이스 접근 로직을 캡슐화하는 객체입니다. 제가 사용한 NestJS 프로젝트에서는 파일명을 DAO라고 두지는 않았지만, <code>Repository</code> 계층이 비슷한 역할을 했습니다. Controller나 Service가 Prisma 쿼리를 직접 많이 알게 하기보다, UserRepository나 ArticleRepository처럼 데이터 접근 책임을 분리했습니다. 이렇게 하면 DB 접근 방식이 바뀌거나 조회 조건이 복잡해져도 영향 범위를 Repository 계층으로 줄일 수 있습니다.</p>
<h3 id="q5-rdbms-기반-db-설계-경험은-어떻게-설명할-수-있나요">Q5. RDBMS 기반 DB 설계 경험은 어떻게 설명할 수 있나요?</h3>
<p><strong>답변</strong></p>
<p>Real World에서 PostgreSQL과 Prisma를 사용해 사용자, 게시글, 댓글, 태그, 좋아요, 팔로우, Refresh Token 데이터를 모델링했습니다.</p>
<p>예를 들어 <code>User</code>는 <code>Article</code>, <code>Comment</code>, <code>Favorite</code>, <code>Follow</code>, <code>RefreshToken</code>과 관계를 가지고, <code>Article</code>은 작성자, 댓글, 태그, 좋아요와 관계를 가집니다. 태그는 게시글과 다대다 관계이기 때문에 <code>ArticleTag</code> 조인 모델을 두었고, 좋아요와 팔로우도 중복을 막기 위해 복합 기본키를 사용했습니다.</p>
<p>또한 조회 패턴을 고려해 <code>Article</code>에는 <code>authorId, createdAt</code>, <code>createdAt</code> 인덱스를 두고, <code>Comment</code>에는 <code>articleId, createdAt</code>, <code>RefreshToken</code>에는 <code>userId</code>, <code>expiresAt</code> 인덱스를 두었습니다. 운영 DB를 장기간 관리한 경험은 아직 부족하지만, 관계, 제약 조건, 인덱스, 마이그레이션을 고려하며 설계한 경험은 있습니다.</p>
<p><strong>꼬리 질문</strong></p>
<ul>
<li>1대1, 1대다, 다대다 관계는 각각 어떻게 구현했나요?</li>
<li>인덱스는 왜 필요하고, 어떤 기준으로 어디에 적용했나요?</li>
<li>JOIN의 종류와 실제 적용한 JOIN 경험을 설명해주세요.</li>
</ul>
<p><strong>꼬리 질문 답변</strong></p>
<h4 id="1대1-1대다-다대다-관계는-각각-어떻게-구현했나요">1대1, 1대다, 다대다 관계는 각각 어떻게 구현했나요?</h4>
<p>1대다 관계는 외래키(FK)를 &quot;다&quot; 쪽 테이블에 두어 표현합니다. Real World에서 User와 Article은 1대다 관계로, <code>Article</code> 테이블에 <code>authorId</code>를 두어 한 사용자가 여러 게시글을 작성할 수 있게 했습니다. Comment도 마찬가지로 <code>articleId</code>, <code>authorId</code> FK를 가집니다.</p>
<p>1대1 관계는 한쪽(또는 양쪽) 테이블의 FK에 유니크 제약을 걸어 표현합니다. Real World에는 명시적인 1대1 관계를 직접 모델링한 경우는 없었지만, User와 Profile을 분리해야 했다면 <code>Profile.userId</code>에 unique 제약을 걸어 한 사용자당 하나의 프로필만 존재하도록 설계했을 것입니다.</p>
<p>다대다 관계는 두 테이블을 FK로 직접 연결할 수 없기 때문에 조인 테이블(중간 테이블)을 둡니다. Article과 Tag는 게시글 하나가 여러 태그를 가질 수 있고 태그 하나도 여러 게시글에 쓰일 수 있는 다대다 관계라 <code>ArticleTag</code> 조인 모델을 만들어 <code>articleId</code>, <code>tagId</code>를 함께 저장했습니다. Favorite(좋아요)과 Follow(팔로우)도 User-Article, User-User 사이의 다대다 관계로 보고, 중복 좋아요·팔로우를 막기 위해 두 FK를 묶은 복합 기본키(composite primary key)를 사용했습니다.</p>
<h4 id="복합-기본키와-외래키fk는-어떤-관계인가요">복합 기본키와 외래키(FK)는 어떤 관계인가요?</h4>
<p>복합 기본키는 별개의 개념이 아니라, 두 개 이상의 외래키를 묶어서 기본키로 지정하는 방식입니다. 예를 들어 <code>Favorite</code> 테이블의 <code>userId</code>, <code>articleId</code>는 각각 User와 Article을 참조하는 FK이면서, 동시에 <code>(userId, articleId)</code> 조합 전체가 PK가 됩니다. Prisma에서는 <code>@@id([userId, articleId])</code>로 선언했습니다.</p>
<p>이 구조가 주는 이점은 두 가지입니다. 첫째, FK이므로 존재하지 않는 user나 article을 참조할 수 없어 참조 무결성이 보장됩니다. 둘째, 복합 PK이므로 같은 <code>(userId, articleId)</code> 조합이 두 번 저장될 수 없어, DB 레벨에서 중복 좋아요·중복 팔로우를 막을 수 있습니다.</p>
<p>만약 복합 PK 대신 별도의 auto-increment <code>id</code>를 PK로 쓰고 <code>userId</code>, <code>articleId</code>는 단순 FK로만 뒀다면, 중복 방지는 DB가 보장해주지 않고 애플리케이션 로직이나 별도의 <code>unique(userId, articleId)</code> 제약에 의존해야 합니다. 복합 PK를 쓰면 이 제약이 곧 기본키이기 때문에 별도 unique 인덱스를 추가할 필요가 없다는 점도 차이입니다. 다만 복합 키는 다른 테이블이 이 조인 테이블을 다시 FK로 참조해야 하는 상황에서는 참조하는 쪽도 두 컬럼을 함께 들고 있어야 해서 스키마가 다소 번거로워질 수 있습니다. Real World에서는 Favorite, Follow를 다른 테이블이 참조할 일이 없었기 때문에 이 단점은 크게 문제되지 않았습니다.</p>
<h4 id="인덱스는-왜-필요하고-어떤-기준으로-어디에-적용했나요">인덱스는 왜 필요하고, 어떤 기준으로 어디에 적용했나요?</h4>
<p>인덱스가 필요한 이유는 테이블 전체를 처음부터 끝까지 스캔(full table scan)하지 않고 조건에 맞는 행의 위치를 빠르게 찾기 위해서입니다. 데이터가 적을 때는 차이가 크지 않지만, 게시글이나 댓글처럼 행이 계속 쌓이는 테이블에서는 인덱스가 없으면 조회 시간이 데이터 양에 비례해 늘어납니다.</p>
<p>적용 기준은 실제 조회 패턴이었습니다. WHERE 조건이나 정렬(ORDER BY)에 자주 쓰이는 컬럼을 우선으로 봤습니다. 예를 들어 특정 작성자의 게시글을 최신순으로 보여주는 조회가 많을 것으로 예상해 <code>Article</code>에 <code>authorId, createdAt</code> 복합 인덱스를 뒀고, 전체 최신 게시글 목록을 위해 <code>createdAt</code> 단일 인덱스도 뒀습니다. <code>Comment</code>는 게시글 하나의 댓글을 최신순으로 조회하는 패턴이 많아 <code>articleId, createdAt</code> 인덱스를, <code>RefreshToken</code>은 토큰 갱신 시 사용자와 만료 여부를 함께 확인하므로 <code>userId</code>, <code>expiresAt</code>에 인덱스를 뒀습니다.</p>
<p>다만 인덱스를 무조건 많이 두지는 않았습니다. 인덱스는 조회를 빠르게 하는 대신 INSERT/UPDATE/DELETE마다 함께 갱신되어야 해서 쓰기 비용이 늘어나기 때문에, 실제로 자주 조회되는 컬럼 조합에만 선택적으로 적용했습니다.</p>
<h4 id="join의-종류와-실제-적용한-join-경험을-설명해주세요">JOIN의 종류와 실제 적용한 JOIN 경험을 설명해주세요.</h4>
<p>JOIN은 여러 테이블에 나뉘어 있는 데이터를 특정 조건으로 연결해 하나의 결과로 조회하는 방법입니다. 대표적으로 INNER JOIN은 두 테이블에서 조건이 일치하는 행만 반환하고, LEFT JOIN(OUTER JOIN)은 왼쪽 테이블의 모든 행을 반환하면서 오른쪽 테이블에 일치하는 데이터가 없으면 NULL로 채웁니다. 이 외에 RIGHT JOIN, FULL OUTER JOIN도 있지만 실무에서는 INNER JOIN과 LEFT JOIN을 가장 많이 씁니다.</p>
<p>Real World에서는 Prisma의 <code>include</code>/<code>select</code>로 JOIN에 해당하는 쿼리를 작성했습니다. 예를 들어 게시글 상세 조회에서는 Article을 기준으로 작성자(User), 태그(ArticleTag → Tag), 좋아요 수(Favorite count)를 함께 가져와야 했는데, 게시글은 반드시 존재하고 작성자도 항상 존재하므로 Article-User는 INNER JOIN 성격으로, 태그가 없는 게시글도 보여줘야 하므로 Article-ArticleTag-Tag는 LEFT JOIN 성격으로 동작하도록 관계를 설계했습니다. 팔로우 여부처럼 로그인하지 않은 사용자에게는 존재하지 않을 수 있는 관계도 LEFT JOIN에 가까운 방식으로 처리해, 데이터가 없어도 조회 자체는 실패하지 않도록 했습니다.</p>
<h3 id="q6-트랜잭션은-어떤-상황에서-사용했고-왜-필요했나요">Q6. 트랜잭션은 어떤 상황에서 사용했고, 왜 필요했나요?</h3>
<p><strong>답변</strong></p>
<p>게시글 생성과 수정에서 트랜잭션을 사용했습니다. 게시글을 생성할 때는 Article만 저장하는 것이 아니라, 태그가 있으면 Tag를 생성하거나 조회하고, Article과 Tag를 연결하는 <code>ArticleTag</code> 데이터까지 함께 만들어야 합니다.</p>
<p>이 작업 중간에 일부만 성공하면 게시글은 있는데 태그 연결이 없거나, 태그는 생겼는데 게시글 연결이 깨지는 상태가 될 수 있습니다. 그래서 <code>TransactionService</code>에서 Prisma의 <code>$transaction</code>을 감싸고, <code>ArticleTransaction</code>에서 게시글 생성/수정, 태그 생성, 연결 테이블 갱신, 최종 관계 조회를 하나의 작업 단위로 묶었습니다.</p>
<p>면접에서는 &quot;트랜잭션은 여러 DB 변경이 하나의 비즈니스 작업으로 묶여야 할 때 사용했고, 실패 시 전체 롤백되어 데이터 정합성을 지키는 것이 목적이었다&quot;고 답하겠습니다.</p>
<h3 id="q7-인증과-refresh-token은-어떻게-구현했나요">Q7. 인증과 Refresh Token은 어떻게 구현했나요?</h3>
<p><strong>답변</strong></p>
<p>Real World에서는 Access Token과 Refresh Token을 분리했습니다. Access Token은 짧은 수명의 인증 수단으로 발급하고, Refresh Token은 DB에 해시로 저장했습니다. Refresh Token 원문을 그대로 저장하지 않고 bcrypt로 해시한 뒤 <code>RefreshToken</code> 테이블의 <code>tokenHash</code>에 저장한 이유는 DB가 노출되더라도 토큰 원문이 바로 재사용되는 위험을 줄이기 위해서입니다.</p>
<p>토큰 갱신 시에는 쿠키에서 Refresh Token을 읽고, JWT 검증으로 <code>sub</code>, <code>jti</code>를 확인한 뒤 DB 레코드 존재 여부, revoke 여부, 만료 여부, 해시 비교를 순서대로 검증했습니다. 검증이 통과하면 새로운 Access Token과 Refresh Token을 발급합니다.</p>
<p>또한 E2E 테스트에서 유효한 Refresh Token 갱신, 새 쿠키 발급, HttpOnly/SameSite 옵션, DB 레코드 증가, 토큰 없음, 잘못된 토큰, 만료된 토큰 케이스를 검증했습니다.</p>
<p><strong>주의해서 말할 점</strong></p>
<p>프론트엔드 Real World 경험의 &quot;동시 401 요청을 대기 큐로 묶어 Refresh 요청을 줄인 경험&quot;과 이 BE-realworld 저장소의 서버 구현은 구분해서 말해야 합니다. 이 문서에서는 BE-realworld 기준으로 서버의 토큰 발급, 저장, 검증, E2E 테스트 경험을 중심으로 답합니다.</p>
<h3 id="q8-git-기반-협업-환경-경험은-어떻게-설명하겠습니까">Q8. Git 기반 협업 환경 경험은 어떻게 설명하겠습니까?</h3>
<p><strong>답변</strong></p>
<p>BE-realworld 저장소에는 GitHub Flow를 기준으로, 기능 단위 feature branch에서 작업한 뒤 PR을 열어 리뷰와 CI 검증을 거쳐 main에 병합하는 워크플로우를 정리했습니다. 브랜치를 오래 유지하면 충돌이 커지고 통합 시점의 리스크가 커지기 때문에, 작은 단위로 작업하고 PR 단위로 빠르게 통합하는 방향을 의도했습니다.</p>
<p>또한 PR 템플릿, GitHub Actions CI, Husky pre-commit, lint-staged, Jest 테스트 스크립트를 구성해 변경 전에 기본 검증을 하도록 했습니다. <code>ci-mini.yml</code>은 push와 pull request에서 checkout, Node.js 설정, <code>npm ci</code>, lint를 빠르게 실행하도록 했고, 별도의 <code>ci.yml</code>은 수동 실행으로 PostgreSQL service container, Prisma migration, unit test, e2e test, coverage, Codecov 업로드까지 수행하도록 구성했습니다.</p>
<p>다만 production 배포까지 자동화한 CD를 운영했다고 말하기는 어렵습니다. 제 경험은 자동 배포보다는 코드 변경 시 품질을 검증하는 CI 구축 경험에 가깝습니다. 실무 규모의 팀 운영 경험이라고 과장할 수는 없지만, 협업에서 중요한 것은 브랜치 이름보다 변경 단위, 리뷰 가능성, 자동 검증이라고 이해하고 있습니다.</p>
<p><strong>꼬리 질문</strong></p>
<ul>
<li>CI와 CD의 차이는 무엇인가요?</li>
<li>GitHub Actions workflow는 어떤 단위로 동작하나요?</li>
<li>왜 빠른 lint 중심 workflow와 DB가 필요한 전체 검증 workflow를 분리했나요?</li>
<li>CI에서 GitHub Secrets를 사용한 이유는 무엇인가요?</li>
</ul>
<p><strong>꼬리 질문 답변</strong></p>
<p>CI는 코드 변경이 들어올 때 자동으로 lint, test, build 같은 검증을 수행해 기존 코드와 잘 통합되는지 확인하는 과정입니다. CD는 검증을 통과한 결과물을 배포 가능한 상태로 만들거나 실제 환경에 자동 배포하는 과정입니다. 저는 BE-realworld에서 CI는 구성했지만, 운영 환경 자동 배포까지 완성했다고 말하지는 않겠습니다.</p>
<p>GitHub Actions는 <code>.github/workflows</code> 아래의 YAML 파일로 정의합니다. push, pull request, 수동 실행 같은 event가 workflow를 실행하고, workflow 안의 job이 runner에서 실행되며, job 안의 step이 실제 명령이나 action을 수행합니다.</p>
<p>빠른 workflow와 전체 검증 workflow를 나눈 이유는 피드백 속도입니다. PR마다 DB migration, E2E, coverage까지 모두 실행하면 안정성은 높지만 대기 시간이 길어집니다. 그래서 기본 변경 검증은 빠르게 하고, DB와 환경변수가 필요한 검증은 별도 workflow로 분리했습니다.</p>
<h3 id="q9-서비스-도메인-분석을-통한-객체지향-설계-경험을-어떻게-설명할-수-있나요">Q9. 서비스 도메인 분석을 통한 객체지향 설계 경험을 어떻게 설명할 수 있나요?</h3>
<p><strong>답변</strong></p>
<p>객체지향 설계를 클래스를 많이 만드는 것으로 보지는 않습니다. 제가 이해한 핵심은 도메인의 책임과 변경 이유를 나누는 것입니다.</p>
<p>Real World에서는 사용자, 인증, 게시글, 댓글, 태그, 팔로우, 좋아요를 별도 모듈과 서비스로 분리했습니다. 예를 들어 인증은 토큰 발급, 해시, 검증, 쿠키 옵션을 담당하고, 사용자는 회원가입/로그인/수정 흐름을 담당합니다. 게시글은 slug 생성, 작성자 권한 확인, 태그 연결, 좋아요 메타데이터 조립 같은 책임을 갖습니다.</p>
<p>아직 도메인 주도 설계를 깊게 적용했다고 말하기는 어렵습니다. 다만 기능을 파일로만 나누는 것이 아니라, 어떤 변경이 어느 책임에 속하는지 고민하며 계층과 모듈을 나눴다고 설명하겠습니다.</p>
<h2 id="2-ai-native-역량">2. AI Native 역량</h2>
<h3 id="q10-claude-code-cursor-같은-ai-코딩-에이전트를-어떻게-활용해-봤나요">Q10. Claude Code, Cursor 같은 AI 코딩 에이전트를 어떻게 활용해 봤나요?</h3>
<p><strong>답변</strong></p>
<p>AI 코딩 에이전트는 주로 반복 작업 초안, 테스트 케이스 목록화, 문서 구조화, 코드 리뷰 관점 점검에 활용했습니다. 예를 들어 어떤 API의 예외 케이스를 정리하거나, 이미 정해진 패턴에 맞춰 DTO/테스트 초안을 만드는 작업은 AI가 빠르게 도와줄 수 있습니다.</p>
<p>다만 인증, 토큰, DB 트랜잭션, 권한, 개인정보처럼 실수했을 때 위험이 큰 영역은 AI 결과를 그대로 사용하지 않습니다. BE-realworld에서도 Refresh Token은 DB 저장 방식, 만료 검증, 쿠키 옵션, E2E 테스트까지 개발자가 기준을 세우고 검증해야 하는 영역입니다.</p>
<h3 id="q11-ai가-생성한-코드의-품질을-어떻게-판단하나요-수정-필요">Q11. AI가 생성한 코드의 품질을 어떻게 판단하나요? (수정 필요)</h3>
<p><strong>답변</strong></p>
<p>먼저 요구사항을 실제로 만족하는지 확인합니다. 그다음 타입, 테스트, 예외 처리, 보안, 기존 아키텍처와의 일관성을 봅니다.</p>
<p>예를 들어 AI가 Refresh Token 갱신 코드를 제안했다면 &quot;토큰이 유효하면 새 토큰을 준다&quot;에서 끝내면 안 됩니다. 토큰이 없을 때, 형식이 잘못됐을 때, 만료됐을 때, DB에 없는 jti일 때, revoke된 토큰일 때, 해시 비교가 실패할 때를 나눠야 합니다. 또한 Refresh Token을 원문으로 저장하지 않는지, 쿠키에 HttpOnly/SameSite 옵션이 있는지, 테스트로 검증되는지도 확인해야 합니다.</p>
<p>즉 AI 코드의 품질은 동작 여부만이 아니라, 실패 상황과 유지보수 비용까지 포함해서 판단합니다.</p>
<h3 id="q12-ai에게-무엇을-어떻게-위임할지-어떻게-판단하나요-수정-필요">Q12. AI에게 무엇을 어떻게 위임할지 어떻게 판단하나요? (수정 필요)</h3>
<p><strong>답변</strong></p>
<p>저는 작업을 위험도와 검증 가능성으로 나눕니다. 정답 기준이 비교적 명확하고 사람이 빠르게 검토할 수 있는 작업은 AI에게 초안을 맡길 수 있습니다. 예를 들어 테스트 케이스 후보, 문서 목차, DTO 예시, 반복적인 mapper 초안은 AI의 도움을 받을 수 있습니다.</p>
<p>반대로 인증, 권한, 트랜잭션, DB 마이그레이션, 배포 설정처럼 잘못되면 서비스 안정성이나 사용자 데이터에 영향을 주는 영역은 AI에게 최종 판단을 맡기지 않습니다. 이 경우 AI는 대안 탐색 도구로 쓰고, 최종 설계와 검증은 개발자가 책임지는 방식이 맞다고 생각합니다.</p>
<h3 id="q13-ai가-제안한-해결책이-틀렸던-경우-어떻게-대응하나요-수정-필요">Q13. AI가 제안한 해결책이 틀렸던 경우 어떻게 대응하나요? (수정 필요)</h3>
<p><strong>답변</strong></p>
<p>AI가 틀릴 수 있다는 전제에서 시작합니다. 먼저 제안이 해결하려는 문제가 실제 원인과 맞는지 확인합니다. API 문제라면 요청/응답, 상태 코드, DB 상태, 테스트 실패 지점을 확인하고, 성능 문제라면 Profiler나 Network 같은 측정 도구로 확인합니다.</p>
<p>틀린 제안이라면 왜 틀렸는지 분류합니다. 요구사항을 놓친 것인지, 보안 조건을 빠뜨린 것인지, 기존 구조와 맞지 않는 것인지, 테스트가 없는 것인지 구분합니다. 그런 다음 AI에게 더 좁은 맥락과 제약을 주고 다시 대안을 요청하거나, 사람이 설계한 방향을 기준으로 구현합니다.</p>
<h3 id="q14-새로운-ai-도구나-워크플로우를-팀에-접목한다면-어떻게-하겠습니까-수정-필요">Q14. 새로운 AI 도구나 워크플로우를 팀에 접목한다면 어떻게 하겠습니까? (수정 필요)</h3>
<p><strong>답변</strong></p>
<p>처음부터 팀 전체의 개발 방식을 바꾸기보다, 위험이 낮고 효과를 확인하기 쉬운 영역부터 적용하겠습니다. PR 설명 초안, 테스트 케이스 후보 정리, 문서 요약, 반복 코드 초안처럼 사람이 리뷰하기 쉬운 영역이 적합합니다.</p>
<p>그리고 팀 규칙이 필요합니다. AI가 작성한 코드는 그대로 머지하지 않고, 인증/권한/DB/배포 설정은 별도 리뷰를 거치며, 타입체크와 테스트를 통과해야 한다는 기준을 세워야 합니다. AI 도입의 목적은 속도만 높이는 것이 아니라, 팀의 품질 기준 안에서 반복 비용을 줄이는 것이라고 생각합니다.</p>
<h2 id="3-협업-및-문제-해결">3. 협업 및 문제 해결</h2>
<h3 id="q15-서비스-리뉴얼-또는-신규-서비스-런칭-경험을-어떻게-설명할-수-있나요">Q15. 서비스 리뉴얼 또는 신규 서비스 런칭 경험을 어떻게 설명할 수 있나요?</h3>
<p><strong>답변</strong></p>
<p>UNIBUSK를 예로 들 수 있습니다. FE 2명, BE 3명, 디자인 2명, 기획 1명으로 구성된 팀에서 2025년 12월부터 2026년 2월까지 MVP를 개발해 실제 운영 서비스로 런칭했고, 이후 2026년 4월부터 고도화를 진행하고 있습니다.</p>
<p>신규 서비스 개발 단계에서는 공연 등록, 공연 상세, Kakao OAuth2 인증, 프로필 영역을 담당했습니다. 예를 들어 Kakao 인증 콜백에서 발급되는 쿠키가 프론트엔드와 다른 도메인에서 설정되어 인증 상태가 유지되지 않는 Cross-Domain Cookie 문제를, Proxy를 두고 Authorization Code를 서버 간에 교환하는 방식으로 해결했습니다. 신규 서비스이기 때문에 처음부터 드러난 이런 구조적 문제를 초기에 다뤘습니다.</p>
<p>MVP 이후 고도화 단계에서는 실제 사용자 트래픽을 기준으로 렌더링 성능, 번들 크기, 초기 데이터 로딩 구조를 측정하고 개선했습니다. 신규 런칭과 이후 고도화를 함께 경험하면서, 초기에는 기능 구현 속도가 중요했고 이후에는 측정 기반의 개선이 중요해지는 흐름을 직접 겪었습니다.</p>
<h3 id="q16-문제-해결을-통해-서비스나-제품을-개선해-본-경험이-있나요">Q16. 문제 해결을 통해 서비스나 제품을 개선해 본 경험이 있나요?</h3>
<p><strong>답변</strong></p>
<p>UNIBUSK 공연 등록 폼의 리렌더링 문제를 예로 들 수 있습니다. React Hook Form의 비제어 컴포넌트 패턴을 적용하면 리렌더가 줄어들 것이라 예상했지만, React DevTools Profiler로 확인한 결과 글자 수 표시를 위해 사용한 <code>watch(&#39;teamName&#39;)</code> 한 줄 때문에 상위 컴포넌트인 RegisterModal 전체가 리렌더되고 있었습니다.</p>
<p>원인은 <code>watch()</code>의 구독이 <code>useForm</code>을 호출한 컴포넌트에 등록된다는 점이었습니다. <code>useWatch()</code>로 구독 범위를 글자 수 표시 컴포넌트로 좁히고, <code>useFormContext().formState.errors</code>가 전체 errors 객체를 구독하던 부분도 <code>useFormState({ name })</code>으로 필드 단위 구독으로 바꿨습니다. 그 결과 입력 시 렌더 함수 실행 횟수가 <code>console.count</code> 기준 75회에서 1회로 줄었습니다.</p>
<p>같은 관점의 문제 해결 경험은 Dessert Front Dashboard에도 있습니다. 내부 유틸리티 <code>reduxMaker</code>가 학습 비용이 높고 서버 상태 캐싱 전략이 없다는 문제를 발견해, 서버 상태는 TanStack Query, 클라이언트 상태는 Zustand, 폼 상태는 RHF+Zod로 역할을 분리하는 전환을 제안하고 진행했습니다.</p>
<h3 id="q17-비즈니스-요구사항을-이해하고-기술적-해결-방안을-제시한-경험이-있나요">Q17. 비즈니스 요구사항을 이해하고 기술적 해결 방안을 제시한 경험이 있나요?</h3>
<p><strong>답변</strong></p>
<p>UNIBUSK에서 TanStack Query Hydration Pattern을 페이지마다 다르게 적용한 경험을 예로 들 수 있습니다. 공연 목록·상세 페이지는 검색 노출(SEO)이 중요하고 누구에게나 같은 데이터를 보여줘야 하는 페이지였기 때문에, 서버에서 데이터를 prefetch해 초기 HTML에 포함시키고 클라이언트 <code>useQuery</code>가 이어받도록 설계했습니다.</p>
<p>반면 인증 페이지는 사용자별로 다른 개인화 데이터를 다루기 때문에, 같은 패턴을 그대로 적용하면 캐시된 응답이 다른 사용자에게 노출될 수 있는 보안 위험이 있었고, SEO 노출이 필요한 페이지도 아니었습니다. 그래서 인증 페이지는 Hydration Pattern 적용 대상에서 제외했습니다.</p>
<p>이 판단은 단순한 성능 최적화 기법 선택이 아니라, &quot;이 페이지가 검색 노출이 필요한가&quot;, &quot;이 데이터가 사용자별로 달라지는가&quot;라는 비즈니스·보안 요구사항을 먼저 확인한 뒤 페이지별로 다른 기술적 해결책을 적용한 사례입니다.</p>
<h3 id="q18-사용자-경험을-고려한-개발을-어떻게-실천했나요">Q18. 사용자 경험을 고려한 개발을 어떻게 실천했나요?</h3>
<p><strong>답변</strong></p>
<p>UNIBUSK에서 Code Splitting을 적용할 때 LCP 요소인 <code>KakaoMapView</code>는 의도적으로 <code>next/dynamic</code> 적용 대상에서 제외했습니다. TBT를 줄이는 데는 유리했지만 실제로 lazy load를 적용해 측정해보니 LCP가 17% 악화됐기 때문입니다. 번들 크기나 TBT 같은 지표만 보고 최적화 범위를 정하면 오히려 사용자가 체감하는 초기 로딩이 느려질 수 있다는 것을 확인한 경험이었습니다.</p>
<p>같은 맥락에서 TanStack Query Hydration Pattern도 인증 페이지는 개인화 데이터를 다루기 때문에 서버 prefetch 대상에서 제외했습니다. 성능 개선보다 사용자의 데이터가 다른 사용자에게 노출되지 않는 것이 우선이라고 판단했습니다.</p>
<p>두 경우 모두 &quot;기술적으로 적용 가능한가&quot;보다 &quot;이 요소가 사용자가 실제로 체감하는 지표에 어떤 영향을 주는가&quot;, &quot;이 데이터가 노출되어도 안전한가&quot;를 먼저 확인한 뒤 최적화 범위를 결정한 사례입니다.</p>
<h3 id="q19-다른-직군과-의견-충돌이-발생하면-어떻게-해결하나요">Q19. 다른 직군과 의견 충돌이 발생하면 어떻게 해결하나요?</h3>
<p><strong>답변</strong></p>
<p>정확히는 디자이너나 기획자와의 충돌보다, 개발팀 내부에서 해석이 갈렸던 경험을 먼저 말씀드리겠습니다. Dessert Front Dashboard에서 FSD 레이어 위치를 팀원마다 다르게 해석해 PR마다 &quot;이 파일을 어디에 둬야 하나요?&quot;라는 논의가 반복됐습니다. 이때 누구의 해석이 맞는지 논쟁하기보다, suffix 규칙·단방향 의존성·Public API·Query와 Mutation의 배치 기준처럼 판단 기준 자체를 문서로 만들어 팀에 제안했습니다. 기준이 생긴 뒤에는 구조 논쟁이 줄고 리뷰에서 비즈니스 로직 자체에 더 집중할 수 있었습니다.</p>
<p>디자이너나 기획자와 직접 부딪힌 사례를 과장해서 말씀드리기보다, 제가 실제로 확인한 것은 &quot;의견 차이는 대부분 각자가 보는 제약이 다르기 때문에 생긴다&quot;는 점이었습니다. 그래서 직군이 다른 경우에도 같은 방식으로 접근하겠습니다. 먼저 상대가 지키려는 제약(일정, 사용성, 안정성)을 확인하고, 반드시 지켜야 할 조건과 조정 가능한 조건을 나눈 뒤, 판단 기준을 문서나 코드로 남겨 같은 논쟁이 반복되지 않도록 하겠습니다.</p>
<h3 id="q20-요구사항이-모호할-때-어떻게-대응하나요">Q20. 요구사항이 모호할 때 어떻게 대응하나요?</h3>
<p><strong>답변</strong></p>
<p>Dessert Front Dashboard의 FSD 구조가 좋은 예시입니다. &quot;이 파일은 어느 레이어에 둬야 하는가&quot; 자체가 모호한 요구사항 같은 성격이었습니다. 같은 <code>features</code> 레이어 안에서도 슬라이스마다 구조가 달라, 파일 이름만으로는 API 호출인지 UI인지 상태 관리인지 알기 어려웠습니다.</p>
<p>이런 모호함을 만났을 때는 케이스별로 하나씩 판단하지 않고, 먼저 반복되는 질문의 패턴을 모았습니다. suffix로 파일 역할을 드러내고, 레이어 간 참조 방향과 Public API 원칙을 정하고, TanStack Query의 Query/Mutation을 재사용 범위 기준으로 배치 레이어를 나누는 세 가지 기준을 문서화했습니다. 모호함을 개별 파일마다 해결하는 대신, 다음에도 재사용할 수 있는 규칙으로 바꾼 것이 핵심이었습니다.</p>
<p>같은 원칙은 BE-realworld의 <code>UpdateUserDto</code> 설계에서도 적용했습니다. &quot;회원 정보를 수정할 수 있어야 한다&quot;는 요구만으로는 부족해서, 어떤 필드가 선택값인지, 비밀번호 변경 시 기존 비밀번호가 필요한지 같은 정책을 먼저 명확히 한 뒤 구현했습니다.</p>
<h3 id="q21-빠른-출시와-기술-부채가-충돌하면-어떻게-판단하나요">Q21. 빠른 출시와 기술 부채가 충돌하면 어떻게 판단하나요?</h3>
<p><strong>답변</strong></p>
<p>Dessert Front Dashboard의 모노레포 전환에서 이 판단을 했습니다. Admin 프로젝트 기획이 확정되자마자 모든 공통 컴포넌트를 <code>@dessert/ui</code>로 옮길 수도 있었지만, 그렇게 하면 전환 작업 자체가 커져서 정작 필요한 기능 개발이 늦어질 위험이 있었습니다.</p>
<p>그래서 처음부터 전체를 이관하지 않고, Seller와 Admin에서 모두 사용되면서 서비스별 도메인 로직에 의존하지 않는 컴포넌트만 선별해 점진적으로 옮기는 방식을 택했습니다. 아이콘처럼 변경 주기가 다르고 UI 로직과 의존성이 없는 자원은 <code>@dessert/icons</code>로 먼저 분리하고, 나머지는 필요해질 때마다 이동했습니다.</p>
<p>빠른 출시가 필요한 영역은 최소한의 구조만 잡고 진행하되, 여러 서비스가 공유하는 자원의 관리 방식처럼 나중에 바꾸기 어려운 결정(모노레포 구조, 패키지 경계)은 미루지 않고 먼저 확정했습니다. BE-realworld에서도 같은 기준으로, DTO를 Service에서 직접 쓰는 것 같은 되돌리기 쉬운 결정은 MVP 단계에서 허용하되, 인증·트랜잭션·비밀번호 해시 방식처럼 되돌리기 어려운 부분은 처음부터 기준을 지켰습니다.</p>
<h3 id="q22-팀워크를-중시한다는-것을-어떤-경험으로-설명할-수-있나요">Q22. 팀워크를 중시한다는 것을 어떤 경험으로 설명할 수 있나요?</h3>
<p><strong>답변</strong></p>
<p>Dessert Front Dashboard에서 새로 합류한 팀원이 짧은 시간 안에 기존 코드베이스를 이해해야 하는 상황이 있었습니다. 온보딩 자료를 준비하면서 내부 유틸리티 <code>reduxMaker</code>의 학습 비용을 직접 확인했고, 이를 표준 생태계(TanStack Query, Zustand, RHF+Zod)로 전환하자고 팀에 제안해 진행했습니다. 또한 FSD 레이어 해석이 팀원마다 달라 반복되던 논의를 줄이기 위해 suffix 규칙과 레이어 배치 기준을 문서로 만들어 공유했습니다.</p>
<p>UNIBUSK에서는 FE 2명, BE 3명, 디자인 2명, 기획 1명 규모의 팀에서 공연 등록, 상세, 인증, 프로필 영역을 맡아 다른 파트와 API 계약, 인증 흐름을 맞춰가며 개발했습니다.</p>
<p>BE-realworld는 개인 프로젝트라 협업 경험의 핵심 근거로 쓰기는 어렵지만, PR 템플릿, CI, 테스트 스크립트, GitHub Flow 문서를 구성해 협업 가능한 구조를 의식했다는 점은 보조적으로 언급할 수 있습니다. 팀워크를 뒷받침하는 핵심 사례는 Dessert와 UNIBUSK이고, BE-realworld는 &quot;혼자 구현했지만 협업 가능성을 고려한 구조를 만들었다&quot; 정도로 구분해서 말하겠습니다.</p>
<h2 id="4-면접-직전-압축-답변-5개">4. 면접 직전 압축 답변 5개</h2>
<h3 id="1-spring-boot-공백-압축-답변">1. Spring Boot 공백 압축 답변</h3>
<p>Spring Boot를 주력으로 사용한 실무 경험은 아직 없습니다. 다만 NestJS 기반 Real World 백엔드에서 Controller-Service-Repository 계층 분리, Module과 DI, PostgreSQL/Prisma 모델링, JWT/Refresh Token 인증, 트랜잭션, DTO 검증, Jest/Supertest 테스트를 구현했습니다. Spring과 NestJS가 같다는 뜻은 아니지만, 서버 애플리케이션의 기본 구조와 문제 해결 방식은 연결해서 이해하고 있고, 입사 후 JPA, 트랜잭션, Spring Security를 우선순위로 빠르게 보완하겠습니다.</p>
<h3 id="2-rest-api-압축-답변">2. REST API 압축 답변</h3>
<p>REST API는 리소스 중심 URI, HTTP Method, 상태 코드, 에러 응답 구조를 함께 봐야 한다고 생각합니다. Real World에서는 사용자, 게시글, 댓글, 태그, 프로필 같은 리소스를 기준으로 API를 구현했고, DTO 검증과 도메인 에러를 통해 클라이언트가 인증 실패, 권한 없음, 유효성 오류, 리소스 없음 같은 상태를 구분할 수 있도록 의식했습니다.</p>
<h3 id="3-rdbms-압축-답변">3. RDBMS 압축 답변</h3>
<p>PostgreSQL과 Prisma로 User, Article, Comment, Tag, ArticleTag, Favorite, Follow, RefreshToken 모델을 설계했습니다. 다대다 관계는 조인 모델과 복합 기본키로 표현했고, 조회 패턴을 고려해 authorId, createdAt, articleId, expiresAt 등에 인덱스를 두었습니다. 운영 DB 경험은 더 보완해야 하지만, 관계, 제약 조건, 인덱스, 트랜잭션을 고려해 설계한 경험은 있습니다.</p>
<h3 id="4-인증-압축-답변">4. 인증 압축 답변</h3>
<p>Access Token과 Refresh Token을 분리하고, Refresh Token은 DB에 원문이 아니라 bcrypt 해시로 저장했습니다. 갱신 시에는 JWT 검증, DB 레코드 확인, revoke 여부, 만료 여부, 해시 비교를 거친 뒤 새 토큰을 발급합니다. E2E 테스트로 정상 갱신, 새 쿠키 발급, HttpOnly/SameSite 옵션, 토큰 없음, 잘못된 토큰, 만료된 토큰을 검증했습니다.</p>
<h3 id="5-ai-native-압축-답변">5. AI Native 압축 답변</h3>
<p>AI 코딩 에이전트는 반복 코드 초안, 테스트 케이스 후보, 문서 구조화처럼 사람이 빠르게 검토할 수 있는 작업에 활용합니다. 다만 인증, 권한, DB, 트랜잭션, 배포 설정처럼 위험도가 높은 영역은 AI에게 최종 판단을 맡기지 않고, 요구사항 충족 여부, 타입과 테스트, 보안, 기존 아키텍처 적합성을 개발자가 검증해야 한다고 생각합니다.</p>
<h2 id="5-내일-우선순위">5. 내일 우선순위</h2>
<ol>
<li>Q1 Spring Boot 경험 공백을 과장 없이 연결해서 답하기</li>
<li>Q2/Q3 NestJS 계층 분리와 DI를 코드 흐름으로 설명하기</li>
<li>Q5 PostgreSQL/Prisma 모델링과 관계 설명하기</li>
<li>Q6 게시글-태그 트랜잭션 필요성 설명하기</li>
<li>Q7 Refresh Token 저장, 검증, E2E 테스트 설명하기</li>
<li>Q8 GitHub Actions CI 경험과 CD 경험의 경계 설명하기</li>
<li>Q11 AI 코드 품질 판단 기준 설명하기</li>
<li>Q17 UNIBUSK Hydration Pattern 적용 기준을 비즈니스 요구사항과 연결해 설명하기</li>
<li>Q21 Dessert 모노레포 점진적 이관 결정을 기술 부채 기준으로 설명하기</li>
<li>Q22 Dessert/UNIBUSK 팀 경험과 BE-realworld 개인 프로젝트 경험의 경계 분명히 하기</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[CS·기술 면접 노트]]></title>
            <link>https://velog.io/@park-moen/CS%EA%B8%B0%EC%88%A0-%EB%A9%B4%EC%A0%91-%EB%85%B8%ED%8A%B8</link>
            <guid>https://velog.io/@park-moen/CS%EA%B8%B0%EC%88%A0-%EB%A9%B4%EC%A0%91-%EB%85%B8%ED%8A%B8</guid>
            <pubDate>Wed, 15 Jul 2026 06:51:26 GMT</pubDate>
            <description><![CDATA[<h2 id="0-itnew-맞춤-준비-방향">0. ItNew 맞춤 준비 방향</h2>
<p>주요 업무에 WEB/APP 서비스 개발, React/Next.js 프론트엔드, Spring 백엔드, 서비스 도메인 모델링, DB 설계와 성능 최적화, 비즈니스 요구사항 분석, AI가 생성한 코드의 품질·보안·아키텍처 검토가 함께 적혀 있습니다.</p>
<p>따라서 답변은 다음 순서로 연결합니다.</p>
<pre><code class="language-text">기본 CS 원리 → 프론트엔드 구현 판단 → 서버/API/DB와의 연결 → 프로젝트에서 검증한 경험 → 한계</code></pre>
<p>우선순위는 다음과 같습니다.</p>
<ol>
<li>JavaScript 실행 모델, React 렌더링, 상태 관리, 성능 진단</li>
<li>REST API, 인증·쿠키·CORS, 서버 상태 캐시, RDBMS·트랜잭션</li>
<li>Next.js 렌더링·Hydration, Code Splitting, SEO</li>
<li>AI 코딩 에이전트 활용 시 검토 기준</li>
</ol>
<h2 id="1-javascript">1. JavaScript</h2>
<h3 id="q1-실행-컨텍스트와-스코프-체인을-설명해-주세요">Q1. 실행 컨텍스트와 스코프 체인을 설명해 주세요.</h3>
<p><strong>핵심 답변</strong></p>
<p>실행 컨텍스트는 JavaScript 코드가 실행될 때 변수, 함수, <code>this</code>, 외부 렉시컬 환경 참조를 관리하는 실행 단위입니다. 전역 코드가 실행되면 전역 실행 컨텍스트가 만들어지고, 함수가 호출되면 함수 실행 컨텍스트가 콜 스택에 추가됩니다.</p>
<p>식별자를 찾을 때는 현재 렉시컬 환경에서 먼저 찾고, 없으면 함수가 선언된 위치의 외부 렉시컬 환경을 따라 올라갑니다. 이 연결이 스코프 체인입니다. JavaScript는 호출 위치가 아니라 선언 위치를 기준으로 상위 스코프가 결정되는 렉시컬 스코프 언어입니다.</p>
<p><strong>꼬리 질문 대비</strong></p>
<ul>
<li><code>var</code>는 평가 단계에서 <code>undefined</code>로 초기화되지만, <code>let</code>과 <code>const</code>는 초기화 전까지 TDZ에 있어 접근하면 오류가 발생합니다.</li>
<li>함수 선언문은 평가 단계에서 함수 객체까지 초기화되지만, 함수 표현식은 할당문 실행 시점에 값이 들어갑니다.</li>
</ul>
<h3 id="q2-클로저란-무엇이고-어디에-사용할-수-있나요">Q2. 클로저란 무엇이고 어디에 사용할 수 있나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>클로저는 함수가 선언될 때의 렉시컬 환경을 기억해, 외부 함수 실행이 끝난 뒤에도 해당 변수에 접근할 수 있는 특성입니다. 상태 은닉, 함수 팩토리, 이벤트 핸들러, 비동기 콜백에서 이전 값을 유지할 때 사용합니다.</p>
<p>Real World의 Refresh Token 대기 큐에서는 토큰 갱신 중 들어온 요청의 <code>resolve</code>, <code>reject</code>를 큐에 저장했습니다. 갱신이 끝난 뒤 저장된 함수를 호출해 대기 중이던 요청을 이어서 처리했습니다. 이때 각 Promise의 처리 함수가 생성 당시 상태와 연결되는 점이 클로저와 관련됩니다.</p>
<p><strong>주의할 점</strong></p>
<p>클로저가 값을 복사한다고 말하지 않습니다. 렉시컬 환경의 바인딩을 참조합니다. 또한 클로저 자체가 메모리 누수는 아니고, 더 이상 필요 없는 큰 객체를 참조하는 콜백이 이벤트 리스너나 타이머에 남아 있을 때 문제가 됩니다.</p>
<h3 id="q3-이벤트-루프와-microtask-queue-task-queue의-실행-순서를-설명해-주세요">Q3. 이벤트 루프와 Microtask Queue, Task Queue의 실행 순서를 설명해 주세요.</h3>
<p><strong>핵심 답변</strong></p>
<p>JavaScript는 콜 스택에서 동기 코드를 처리합니다. 타이머, 네트워크, 사용자 이벤트 같은 비동기 작업은 브라우저 런타임에서 처리되고, 완료된 콜백은 큐에 들어갑니다.</p>
<p>현재 Task의 동기 코드가 끝나고 콜 스택이 비면, 이벤트 루프는 Microtask Queue를 먼저 비울 때까지 처리합니다. Promise <code>then/catch/finally</code>와 <code>queueMicrotask</code>가 Microtask이고, <code>setTimeout</code>, DOM 이벤트 콜백 등은 Task로 처리됩니다.</p>
<pre><code class="language-ts">console.log(&#39;start&#39;);

setTimeout(() =&gt; console.log(&#39;timeout&#39;), 0);
Promise.resolve().then(() =&gt; console.log(&#39;promise&#39;));

console.log(&#39;end&#39;);</code></pre>
<p>출력 순서는 <code>start → end → promise → timeout</code>입니다. Promise executor는 생성 즉시 동기로 실행되고, 후속 handler가 Microtask로 실행된다는 점을 구분해야 합니다.</p>
<h3 id="q4-promise와-asyncawait의-에러-처리를-설명해-주세요">Q4. Promise와 async/await의 에러 처리를 설명해 주세요.</h3>
<p><strong>핵심 답변</strong></p>
<p>Promise는 비동기 작업의 대기, 성공, 실패 상태를 표현합니다. <code>then</code>은 새 Promise를 반환하므로 체이닝이 가능하고, 중간에서 <code>throw</code>하거나 rejected Promise를 반환하면 이후 <code>catch</code>로 전파됩니다.</p>
<p><code>async</code> 함수는 항상 Promise를 반환합니다. <code>await</code>는 전체 스레드를 막는 것이 아니라 해당 async 함수의 나머지 실행을 중단하고, Promise가 처리되면 Microtask에서 이어서 실행합니다.</p>
<p>에러는 <code>try/catch</code>로 처리하되 복구할 수 없는 에러를 무조건 삼키면 안 됩니다. 병렬 요청이 서로 독립적이면 순차 <code>await</code>보다 먼저 Promise를 만들고 <code>Promise.all</code> 등을 사용합니다.</p>
<h2 id="2-react">2. React</h2>
<h3 id="q5-react-렌더링-과정과-reconciliation을-설명해-주세요">Q5. React 렌더링 과정과 Reconciliation을 설명해 주세요.</h3>
<p><strong>핵심 답변</strong></p>
<p>React 업데이트는 render 단계와 commit 단계로 나눌 수 있습니다. Render 단계에서는 컴포넌트 함수를 실행해 React element를 만들고, 기존 Fiber와 비교해 필요한 변경을 계산합니다. 이 비교 과정이 Reconciliation입니다. Commit 단계에서는 계산된 변경을 실제 DOM에 반영하고 effect를 실행합니다.</p>
<p>컴포넌트 함수 실행, Fiber 비교, 실제 DOM 변경은 서로 다릅니다. UNIBUSK에서는 최적화 후 Profiler에서 일부 컴포넌트가 표시됐지만 <code>console.count</code>로는 함수 실행이 없었습니다. 이는 React가 이전 결과를 재사용한 bail-out으로 해석했습니다.</p>
<p><strong>프로젝트 연결</strong></p>
<p>공연 등록 폼에서 <code>watch()</code> 구독 위치가 상위 컴포넌트에 있어 입력 한 번에 넓은 트리가 영향을 받았습니다. <code>useWatch()</code>와 <code>useFormState({ name })</code>로 구독 범위를 좁혀 렌더 함수 실행 횟수를 75회에서 1회로 줄였습니다.</p>
<h3 id="q6-useeffect는-언제-사용하나요">Q6. <code>useEffect</code>는 언제 사용하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p><code>useEffect</code>는 컴포넌트를 외부 시스템과 동기화할 때 사용합니다. 외부 시스템은 네트워크 연결, 구독, 타이머, 브라우저 API 같은 React 바깥의 상태를 의미합니다.</p>
<p>사용자 클릭으로 바로 실행되는 로직은 이벤트 핸들러에 두고, props와 state로 계산 가능한 값은 렌더 중 계산합니다. 의존성 배열은 실행하고 싶은 시점을 임의로 고르는 목록이 아니라 effect 내부에서 사용하는 반응형 값의 목록입니다.</p>
<p><strong>프로젝트 연결</strong></p>
<p>Real World에서는 컴포넌트 마운트 이후 <code>useEffect</code>에서 데이터를 패칭하던 구조를 Router loader로 옮겼습니다. 데이터 패칭은 항상 effect의 책임이 아니며, 라우터나 서버 계층에서 더 일찍 시작할 수 있으면 waterfall을 줄일 수 있습니다.</p>
<h3 id="q7-usememo-usecallback-reactmemo는-언제-사용하나요">Q7. <code>useMemo</code>, <code>useCallback</code>, <code>React.memo</code>는 언제 사용하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p><code>useMemo</code>는 계산 결과를, <code>useCallback</code>은 함수 참조를, <code>React.memo</code>는 props가 같을 때 컴포넌트 렌더를 건너뛸 기회를 제공합니다. 비용이 큰 계산이 실제 병목이거나 memoized 자식에게 안정적인 참조를 전달해야 할 때 유용합니다.</p>
<p>하지만 습관적으로 적용하면 비교 비용, 의존성 관리, 코드 복잡성이 늘어납니다. UNIBUSK의 폼 문제는 props 참조가 아니라 상위 컴포넌트의 구독 위치가 원인이었으므로 <code>React.memo()</code>보다 구독 위치를 옮기는 것이 근본 해결책이었습니다.</p>
<h3 id="q8-제어-컴포넌트와-비제어-컴포넌트의-차이는-무엇인가요">Q8. 제어 컴포넌트와 비제어 컴포넌트의 차이는 무엇인가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>제어 컴포넌트는 입력값을 React state가 원본으로 관리하고 <code>value</code>와 <code>onChange</code>로 동기화합니다. 값에 따라 UI를 즉시 제어하기 쉽지만 입력마다 상태 업데이트와 렌더가 발생할 수 있습니다.</p>
<p>비제어 컴포넌트는 DOM이 입력값을 보관하고 필요할 때 ref나 Form API로 읽습니다. React Hook Form은 비제어 입력을 중심으로 효율적으로 동작하지만, <code>watch()</code>나 <code>formState</code>를 높은 위치에서 넓게 구독하면 불필요한 렌더가 생길 수 있습니다.</p>
<h3 id="q9-서버-상태-클라이언트-상태-폼-상태를-어떻게-구분하나요">Q9. 서버 상태, 클라이언트 상태, 폼 상태를 어떻게 구분하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>서버 상태는 원본이 서버에 있고 비동기 조회, 캐싱, 최신성, 재요청, mutation 이후 동기화가 중요합니다. TanStack Query가 적합합니다.</p>
<p>클라이언트 상태는 모달 열림, 현재 스텝처럼 브라우저 안에서 의미가 있는 상태입니다. 한 컴포넌트에만 필요하면 <code>useState</code>, 여러 영역에서 공유하거나 언마운트 이후 유지해야 하면 Zustand 같은 store를 검토합니다.</p>
<p>폼 상태는 입력값, touched, dirty, field error와 제출 생명주기가 중요합니다. UNIBUSK 멀티 스텝 폼에서는 현재 입력은 RHF, 스텝 간 스냅샷은 Zustand, 서버 데이터는 TanStack Query로 나눴습니다.</p>
<h2 id="3-브라우저·네트워크·보안">3. 브라우저·네트워크·보안</h2>
<h3 id="q10-url을-입력하면-어떤-일이-일어나나요">Q10. URL을 입력하면 어떤 일이 일어나나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>브라우저는 URL을 해석하고 캐시와 보안 정책을 확인한 뒤 DNS로 서버 IP를 찾습니다. TCP 연결을 맺고 HTTPS라면 TLS 핸드셰이크로 서버 인증과 암호화 키를 협상합니다. 이후 HTTP 요청을 보내고 응답을 받습니다.</p>
<p>HTML을 받으면 DOM을 만들고, CSS로 CSSOM을 만듭니다. DOM과 CSSOM을 바탕으로 Render Tree를 구성하고 Layout, Paint, Compositing을 거쳐 화면에 표시합니다. JavaScript는 로딩 방식에 따라 HTML 파싱을 차단하거나 DOM과 스타일 변경으로 렌더링 비용을 만들 수 있습니다.</p>
<h3 id="q11-cors와-cookie-문제는-어떻게-다른가요">Q11. CORS와 Cookie 문제는 어떻게 다른가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>CORS는 브라우저가 다른 출처로 요청하는 것을 서버가 허용하는지의 문제입니다. Cookie는 어느 도메인에 저장되고 어떤 요청에 자동으로 포함되는지의 문제입니다.</p>
<p>요청이 CORS 정책상 허용되더라도 쿠키의 <code>Domain</code>, <code>Path</code>, <code>SameSite</code>, <code>Secure</code>, <code>credentials</code> 조건이 맞지 않으면 인증 상태가 이어지지 않을 수 있습니다.</p>
<p><strong>프로젝트 연결</strong></p>
<p>UNIBUSK Kakao OAuth2 문제에서는 단순 CORS 문제가 아니라 브라우저의 도메인별 쿠키 저장 정책이 핵심이었습니다. 백엔드 도메인에 저장된 쿠키를 프론트엔드 도메인에서 자신의 쿠키처럼 사용할 수 없었기 때문에, Proxy와 Authorization Code 교환 방식으로 인증 흐름을 재구성했습니다.</p>
<h3 id="q12-access-token과-refresh-token-저장-위치를-어떻게-판단하나요">Q12. Access Token과 Refresh Token 저장 위치를 어떻게 판단하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>Access Token은 짧은 수명을 전제로 API 요청에 사용되는 토큰이고, Refresh Token은 더 긴 수명으로 Access Token을 재발급하는 데 사용됩니다. Refresh Token은 탈취 영향이 크므로 JavaScript에서 접근할 수 없는 HttpOnly Cookie에 두는 방식이 일반적으로 더 안전합니다.</p>
<p>Access Token을 메모리에 두면 XSS 노출 위험을 줄일 수 있지만 새로고침 시 유지가 어렵습니다. Local Storage에 두면 유지가 쉽지만 XSS에 취약합니다. 저장 위치는 보안 요구, 사용자 경험, 서버 구조를 함께 보고 판단해야 합니다.</p>
<p><strong>프로젝트 연결</strong></p>
<p>Real World에서는 여러 API가 동시에 401을 받았을 때 Refresh Token 요청이 4회 발생하던 문제를 Promise 대기 큐로 단일화했습니다. 첫 번째 요청만 갱신을 수행하고 나머지는 대기했다가 새 Access Token으로 재시도했습니다.</p>
<h3 id="q13-xss와-csrf는-무엇이고-어떻게-방어하나요">Q13. XSS와 CSRF는 무엇이고 어떻게 방어하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>XSS(Cross-Site Scripting)는 공격자가 악성 스크립트를 신뢰된 웹 페이지에 주입해, 다른 사용자의 브라우저에서 그 스크립트가 실행되도록 만드는 공격입니다. 게시글, 댓글, 프로필 소개처럼 사용자 입력이 그대로 HTML로 렌더링되는 지점이 주요 통로입니다. 스크립트가 실행되면 쿠키(HttpOnly가 아닌 경우) 탈취, 세션 하이재킹, 페이지 위변조가 가능해집니다.</p>
<p>방어는 입력이 아니라 출력 시점에 집중해야 합니다. 사용자 입력을 HTML로 렌더링할 때 이스케이프 처리를 하고, <code>dangerouslySetInnerHTML</code>이나 <code>innerHTML</code>처럼 원본 HTML을 그대로 꽂는 API는 꼭 필요한 경우에만 sanitize 라이브러리(DOMPurify 등)를 거쳐 사용해야 합니다. React는 JSX로 렌더링하는 값을 기본적으로 이스케이프하기 때문에 <code>{value}</code> 형태로만 출력하면 XSS 위험이 크게 줄어듭니다. 서버 쪽에서는 Content-Security-Policy 헤더로 허용된 출처의 스크립트만 실행되도록 제한하는 것도 보조 방어선이 됩니다.</p>
<p>CSRF(Cross-Site Request Forgery)는 사용자가 이미 로그인된 상태를 이용해, 공격자가 만든 페이지에서 사용자 브라우저가 대신 요청을 보내게 만드는 공격입니다. 브라우저는 쿠키 기반 인증에서 요청이 어디서 시작됐는지와 상관없이 해당 도메인의 쿠키를 자동으로 실어 보내기 때문에, 사용자가 악성 페이지를 열기만 해도 로그인된 서비스에 의도치 않은 요청(비밀번호 변경, 송금, 게시글 삭제 등)이 전송될 수 있습니다.</p>
<p>방어는 요청이 실제로 우리 서비스 페이지에서 시작됐는지를 서버가 검증하는 방식입니다. CSRF 토큰을 서버가 발급해 폼이나 요청 헤더에 담아 보내고, 서버는 세션과 토큰이 일치하는지 확인합니다. 쿠키에 <code>SameSite=Strict</code> 또는 <code>Lax</code>를 설정하면 다른 출처에서 시작된 요청에는 쿠키가 자동으로 실리지 않아 CSRF 위험을 크게 줄일 수 있습니다.</p>
<p><strong>XSS와 CSRF의 핵심 차이</strong></p>
<p>XSS는 공격자의 스크립트가 우리 사이트 안에서(브라우저 컨텍스트 안에서) 직접 실행되는 것이고, CSRF는 스크립트 실행 없이 브라우저가 자동으로 실어 보내는 쿠키를 이용해 사용자 대신 요청만 위조하는 것입니다. 그래서 방어 지점도 다릅니다. XSS는 &quot;우리 페이지에 악성 코드가 실행되지 않게&quot; 막는 것이고, CSRF는 &quot;요청이 진짜 우리 페이지에서 온 것인지&quot; 검증하는 것입니다.</p>
<p><strong>프로젝트 연결</strong></p>
<p>Real World와 UNIBUSK 모두 Refresh Token을 HttpOnly Cookie에 저장했는데, 이는 XSS로 스크립트가 실행되더라도 JavaScript에서 <code>document.cookie</code>로 토큰을 직접 읽어갈 수 없게 만드는 방어이기도 합니다. 다만 HttpOnly Cookie는 CSRF에는 취약할 수 있어, 실제로는 <code>SameSite</code> 옵션(예: <code>Lax</code>)을 함께 설정해 다른 출처에서의 자동 전송을 제한하는 방향으로 보완해야 한다고 이해하고 있습니다. 사용자 입력값(게시글 본문, 프로필 소개)은 별도의 HTML 렌더링 라이브러리를 쓰지 않고 텍스트로만 출력해 XSS 표면을 줄였습니다.</p>
<p><strong>꼬리 질문 대비</strong></p>
<ul>
<li><code>SameSite=Strict</code>와 <code>SameSite=Lax</code>의 차이: Strict는 다른 출처에서 시작된 요청에는 어떤 경우에도 쿠키를 보내지 않고, Lax는 사용자가 링크를 클릭해 이동하는 것 같은 최상위 GET 네비게이션에는 쿠키를 허용합니다.</li>
<li>CSRF 토큰과 SameSite 쿠키를 둘 다 쓰는 이유: SameSite는 브라우저 지원 여부와 설정 실수에 의존하는 방어이므로, 민감한 동작에는 서버 측 토큰 검증을 함께 두는 것이 더 안전합니다.</li>
<li>localStorage에 토큰을 저장하면 CSRF에는 안전하지만 XSS에는 더 취약해지는 이유: localStorage는 도메인이 다르면 자동 전송되지 않아 CSRF 위험은 없지만, JavaScript로 직접 접근 가능해 XSS 스크립트가 토큰을 그대로 읽어갈 수 있습니다.</li>
</ul>
<h2 id="4-nextjs·성능">4. Next.js·성능</h2>
<h3 id="q14-ssr-rsc-hydration의-차이를-설명해-주세요">Q14. SSR, RSC, Hydration의 차이를 설명해 주세요.</h3>
<p><strong>핵심 답변</strong></p>
<p>SSR은 서버에서 HTML을 만들어 초기 응답으로 내려주는 방식입니다. 사용자는 초기 콘텐츠를 빨리 볼 수 있지만, 클라이언트 상호작용을 위해서는 hydration이 필요합니다.</p>
<p>RSC는 React Server Components로, 서버에서만 실행되는 컴포넌트를 통해 클라이언트 번들에 포함될 필요 없는 로직을 분리할 수 있습니다. DB 접근이나 서버 전용 로직을 클라이언트로 보내지 않는 장점이 있습니다.</p>
<p>Hydration은 서버에서 만들어진 HTML에 클라이언트 React가 이벤트 핸들러와 상태를 연결해 상호작용 가능하게 만드는 과정입니다. Hydration 비용이 크면 초기 상호작용이 늦어질 수 있습니다.</p>
<h3 id="q15-tanstack-query-hydration-pattern은-언제-적용하나요">Q15. TanStack Query Hydration Pattern은 언제 적용하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>Hydration Pattern은 클라이언트에서 발생하던 데이터 대기 시간을 서버의 TTFB 구간으로 옮기는 방식입니다. 요청 비용을 없애는 것이 아니라 요청 시점과 사용자 경험을 바꿉니다.</p>
<p>초기 데이터가 즉시 필요하고 SEO 가치가 있으며 사용자별 민감 데이터가 아닌 페이지에는 유리합니다. 반대로 인증 이후 개인화 데이터는 캐시 정책을 잘못 설정하면 다른 사용자에게 노출될 수 있어 조심해야 합니다.</p>
<p><strong>프로젝트 연결</strong></p>
<p>UNIBUSK 공연 목록과 상세 페이지에는 적용해 클라이언트 API 요청 2건을 0건으로 줄였고, Slow 3G 환경의 2초 이상 대기 시간을 제거했습니다. 인증 페이지에는 적용하지 않았습니다.</p>
<h3 id="q16-code-splitting-적용-기준과-검증-방법을-설명해-주세요">Q16. Code Splitting 적용 기준과 검증 방법을 설명해 주세요.</h3>
<p><strong>핵심 답변</strong></p>
<p>Code Splitting은 필요한 시점에 코드를 나눠 로드하는 전략입니다. 적용 기준은 초기 화면에 필요하지 않은지, 조건부로 렌더링되는지, 해당 컴포넌트에서만 쓰는 무거운 의존성이 있는지, LCP 요소가 아닌지입니다.</p>
<p>검증은 번들 분석과 Network 탭으로 합니다. 초기 로드 시 분리한 청크가 요청되지 않는지, 실제 상호작용 이후 해당 청크가 요청되는지 확인합니다.</p>
<p><strong>프로젝트 연결</strong></p>
<p>UNIBUSK에서 <code>RegisterModal</code>, <code>ProfileEditModal</code>에는 적용했지만 <code>KakaoMapView</code>는 LCP 요소라 제외했습니다. barrel import를 경유한 dynamic import가 청크 분리를 방해해 실제 컴포넌트 파일 경로로 변경했습니다.</p>
<h3 id="q17-웹-성능-문제를-어떤-순서로-진단하나요">Q17. 웹 성능 문제를 어떤 순서로 진단하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>먼저 사용자가 느끼는 증상을 구체화합니다. 초기 진입이 느린지, 입력 반응이 느린지, 화면 이동이 끊기는지에 따라 볼 지표가 다릅니다.</p>
<p>초기 로딩은 Network와 Performance 패널에서 TTFB, 리소스 waterfall, LCP를 확인합니다. 상호작용 지연은 Long Task와 INP 후보를 봅니다. React 내부 렌더링이 의심되면 Profiler로 영향을 받는 컴포넌트와 commit 시간을 확인합니다. 번들 비용은 번들 분석과 Coverage로 확인합니다.</p>
<p>가설을 세운 뒤 한 요소만 바꾸고 같은 빌드·네트워크·CPU 조건에서 전후를 비교합니다.</p>
<h2 id="5-rest-api·db·아키텍처">5. REST API·DB·아키텍처</h2>
<h3 id="q18-restful-api를-설계할-때-무엇을-고려하나요">Q18. RESTful API를 설계할 때 무엇을 고려하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>REST API는 리소스를 중심으로 URI를 설계하고, HTTP Method로 행위를 표현합니다. <code>GET</code>은 조회, <code>POST</code>는 생성, <code>PUT/PATCH</code>는 수정, <code>DELETE</code>는 삭제에 사용합니다. 상태 코드는 성공, 클라이언트 오류, 서버 오류를 구분해 클라이언트가 적절히 대응할 수 있게 해야 합니다.</p>
<p>프론트엔드 입장에서는 API 계약이 UI 상태와 직접 연결됩니다. 로딩, 빈 상태, 에러, 권한 없음, 유효성 오류를 구분할 수 있도록 응답 구조와 에러 타입이 명확해야 합니다.</p>
<h3 id="q19-관계형-데이터베이스를-선택하는-이유와-한계는-무엇인가요">Q19. 관계형 데이터베이스를 선택하는 이유와 한계는 무엇인가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>데이터 간 관계가 명확하고 스키마 제약, 조인, 트랜잭션이 중요하면 관계형 데이터베이스가 적합합니다. Real World에서는 사용자, 게시글, 댓글처럼 관계와 일관성이 중요한 데이터가 있어 PostgreSQL과 Prisma를 사용했습니다.</p>
<p>한계도 있습니다. 스키마가 매우 유동적이거나 특정 대규모 분산 접근 패턴에서는 복잡한 조인과 수평 확장 비용이 부담이 될 수 있습니다. DB 선택은 익숙함보다 데이터 모델, 일관성 요구, 조회 패턴, 운영 조건을 함께 봐야 합니다.</p>
<h3 id="q20-트랜잭션의-acid를-설명해-주세요">Q20. 트랜잭션의 ACID를 설명해 주세요.</h3>
<p><strong>핵심 답변</strong></p>
<p>원자성은 묶인 작업이 전부 성공하거나 전부 실패해야 한다는 의미입니다. 일관성은 트랜잭션 전후에 제약 조건이 유지되는 것입니다. 격리성은 동시에 실행되는 트랜잭션이 서로의 중간 상태에 부적절하게 영향을 주지 않도록 하는 성질이고, 지속성은 커밋된 결과가 장애 후에도 유지되는 성질입니다.</p>
<p>프론트엔드도 트랜잭션을 이해해야 합니다. 등록, 결제, 인증처럼 일부만 성공하면 안 되는 요청에서는 중복 제출 방지, 재시도, 실패 복구 UX, 에러 메시지 설계가 서버의 일관성 규칙과 연결되기 때문입니다.</p>
<h3 id="q21-도메인-모델링과-아키텍처-설계-경험을-어떻게-설명하겠나요">Q21. 도메인 모델링과 아키텍처 설계 경험을 어떻게 설명하겠나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>실무 수준의 대규모 백엔드 도메인 모델링 경험이 있다고 과장하지 않습니다. 대신 Real World에서 NestJS 3계층 구조와 DI를 적용해 Controller, Service, Repository의 책임을 나눈 경험을 말합니다.</p>
<p>프론트엔드에서는 도메인 모델링을 UI 상태와 API 응답 구조에 반영했습니다. Dessert Front Dashboard에서 Query는 여러 기능에서 재사용되는 읽기 데이터로 보고 <code>entities</code>에 두고, Mutation은 특정 사용자 행동과 연결되는 쓰기 흐름으로 보고 <code>features</code>에 배치했습니다. 이처럼 데이터의 성격과 사용 범위를 기준으로 구조를 정했습니다.</p>
<h2 id="6-devops·cicd">6. DevOps·CI/CD</h2>
<h3 id="q22-ci와-cd의-차이를-설명해-주세요">Q22. CI와 CD의 차이를 설명해 주세요.</h3>
<p><strong>핵심 답변</strong></p>
<p>CI는 Continuous Integration, 즉 지속적 통합입니다. 개발자가 push하거나 PR을 만들 때 자동으로 의존성 설치, lint, test, build 같은 검증을 수행해 변경 사항이 기존 코드와 잘 통합되는지 확인합니다.</p>
<p>CD는 Continuous Delivery 또는 Continuous Deployment로 설명할 수 있습니다. Delivery는 배포 가능한 산출물을 만들고 승인 후 배포할 수 있는 상태까지 자동화하는 것이고, Deployment는 검증을 통과한 변경을 실제 운영 환경까지 자동 배포하는 의미로 쓰입니다.</p>
<p>면접에서는 경험 범위를 구분해서 말해야 합니다. BE-realworld에서는 GitHub Actions로 lint, unit test, e2e test, Prisma migration, coverage를 실행하는 CI는 구성했지만, production 환경으로 자동 배포되는 CD까지 운영했다고 말하지는 않겠습니다.</p>
<h3 id="q23-github-actions는-어떻게-동작하나요">Q23. GitHub Actions는 어떻게 동작하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>GitHub Actions는 저장소의 <code>.github/workflows</code> 디렉터리에 있는 YAML 파일로 자동화 흐름을 정의합니다. workflow는 push, pull request, 수동 실행 같은 event에 의해 실행됩니다. workflow 안에는 하나 이상의 job이 있고, 각 job은 runner라는 실행 환경에서 동작합니다. job 안의 step은 shell command를 실행하거나 <code>actions/checkout</code>, <code>actions/setup-node</code> 같은 재사용 가능한 action을 실행합니다.</p>
<p>BE-realworld에서는 <code>ci-mini.yml</code>을 push와 pull request에서 실행되게 만들고, <code>checkout → Node.js 20 설정 → npm ci → npm run lint</code> 순서로 빠른 검증을 수행했습니다. 별도의 <code>ci.yml</code>은 수동 실행으로 두고, PostgreSQL service container, Prisma migration, lint, unit test, e2e test, coverage, Codecov 업로드까지 수행하게 했습니다.</p>
<p><strong>꼬리 질문 대비</strong></p>
<ul>
<li><code>workflow</code>: 자동화 전체 단위</li>
<li><code>event</code>: workflow를 실행시키는 조건</li>
<li><code>job</code>: runner에서 실행되는 작업 묶음</li>
<li><code>step</code>: job 안에서 실행되는 명령 또는 action</li>
<li><code>runner</code>: job을 실제로 실행하는 가상 머신 또는 서버</li>
<li><code>secrets</code>: DB URL, JWT secret처럼 코드에 노출하면 안 되는 값을 안전하게 주입하는 저장소 설정</li>
</ul>
<h3 id="q24-cicd-파이프라인을-설계할-때-무엇을-고려하나요">Q24. CI/CD 파이프라인을 설계할 때 무엇을 고려하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>첫째, 피드백 속도와 검증 강도의 균형을 봅니다. 모든 PR마다 무거운 E2E 테스트와 coverage까지 돌리면 안정성은 올라가지만 대기 시간이 길어집니다. 그래서 빠른 lint/type/test와 무거운 통합 검증을 나누는 전략을 사용할 수 있습니다.</p>
<p>둘째, 실제 서비스와 가까운 환경에서 검증해야 합니다. 백엔드 E2E 테스트는 DB가 필요하기 때문에 BE-realworld에서는 PostgreSQL service container를 띄우고, Prisma migration을 적용한 뒤 테스트를 실행하도록 구성했습니다.</p>
<p>셋째, 민감 정보 관리를 고려해야 합니다. <code>DATABASE_URL</code>, <code>JWT_ACCESS_SECRET</code>, <code>JWT_REFRESH_SECRET</code> 같은 값은 workflow 파일에 직접 적지 않고 GitHub Secrets로 주입해야 합니다.</p>
<p>마지막으로 CD까지 간다면 배포 대상 환경, rollback 전략, migration 실패 대응, 환경별 secrets, 승인 절차를 함께 설계해야 합니다.</p>
<h2 id="7-ai-native-역량">7. AI Native 역량</h2>
<h3 id="q25-ai-코딩-에이전트를-사용할-때-무엇을-검토하나요">Q25. AI 코딩 에이전트를 사용할 때 무엇을 검토하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>AI가 만든 코드는 바로 신뢰하지 않고 네 가지를 검토합니다.</p>
<ol>
<li>요구사항을 실제로 만족하나요?</li>
<li>타입과 테스트로 검증 가능한가요?</li>
<li>보안상 민감한 데이터나 인증 흐름을 잘못 다루지 않나요?</li>
<li>기존 아키텍처와 컨벤션을 깨지 않나요?</li>
</ol>
<p>AI에게는 반복 작업, 초안 작성, 대안 탐색을 위임할 수 있습니다. 하지만 최종 판단은 개발자가 해야 합니다. 특히 인증, 캐시, DB, 권한, 사용자 데이터가 얽힌 부분은 보안과 아키텍처 관점에서 직접 검토해야 합니다.</p>
<h3 id="q26-ai가-만든-코드의-품질을-어떻게-판단하나요">Q26. AI가 만든 코드의 품질을 어떻게 판단하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>동작 여부만 보지 않습니다. 먼저 문제를 해결하는 위치가 적절한지 봅니다. 예를 들어 React 렌더링 문제에서 AI가 <code>React.memo()</code>를 제안하더라도 원인이 상위 구독이면 해결 위치가 맞지 않습니다.</p>
<p>그다음 타입 안정성, 에러 처리, 테스트 가능성, 기존 코드 스타일, 경계 조건을 확인합니다. 마지막으로 실제 측정이나 테스트로 결과를 검증합니다. 성능 개선이라면 Lighthouse 점수 하나가 아니라 Network, Performance, React Profiler, <code>console.count</code> 등 도구가 검증한 범위를 구분해야 합니다.</p>
<h2 id="8-be·os·db·infra-추가-질문">8. BE·OS·DB·Infra 추가 질문</h2>
<h3 id="q27-프로세스와-스레드의-차이는-무엇인가요">Q27. 프로세스와 스레드의 차이는 무엇인가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>프로세스는 실행 중인 프로그램의 독립적인 실행 단위입니다. 각 프로세스는 코드, 데이터, 힙, 스택 같은 메모리 공간을 독립적으로 가집니다. 그래서 한 프로세스가 죽어도 다른 프로세스에 직접 영향을 덜 주지만, 프로세스 간 통신은 상대적으로 비용이 큽니다.</p>
<p>스레드는 프로세스 안에서 실행되는 더 작은 작업 단위입니다. 같은 프로세스의 스레드들은 메모리 공간을 공유하기 때문에 데이터 공유가 쉽고 생성 비용도 상대적으로 낮습니다. 대신 공유 자원에 동시에 접근하면 race condition이 생길 수 있어 lock, mutex 같은 동기화가 필요합니다.</p>
<p>면접에서는 &quot;프로세스는 격리된 실행 단위, 스레드는 프로세스 내부의 실행 흐름이며 메모리를 공유한다&quot;고 먼저 말하고, 안정성과 통신 비용의 trade-off를 덧붙이면 됩니다.</p>
<h3 id="q28-동시성과-병렬성은-어떻게-다른가요">Q28. 동시성과 병렬성은 어떻게 다른가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>동시성은 여러 작업을 같은 시간대에 다루는 구조입니다. 실제로 동시에 실행되지 않더라도 작업을 잘게 나눠 전환하면서 여러 작업이 진행되는 것처럼 처리할 수 있습니다. 단일 코어에서 이벤트 루프가 여러 I/O 작업을 다루는 것이 동시성의 예입니다.</p>
<p>병렬성은 여러 작업이 실제로 같은 시점에 실행되는 것입니다. 여러 CPU 코어나 여러 스레드가 각각 작업을 동시에 수행할 때 병렬성이 생깁니다.</p>
<p>Node.js 서버를 예로 들면, 이벤트 루프는 많은 I/O 요청을 동시성으로 처리하기 좋습니다. 하지만 CPU를 오래 점유하는 작업은 이벤트 루프를 막을 수 있으므로 worker thread, 별도 프로세스, job queue 등으로 분리하는 것을 검토해야 합니다.</p>
<h3 id="q29-cpu-bound-작업과-io-bound-작업의-차이는-무엇인가요">Q29. CPU-bound 작업과 I/O-bound 작업의 차이는 무엇인가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>CPU-bound 작업은 계산 자체가 병목인 작업입니다. 이미지 변환, 암호화 연산, 대량 데이터 계산처럼 CPU를 오래 사용하는 작업이 여기에 가깝습니다. 이런 작업은 CPU 코어 수, 알고리즘 효율, 병렬 처리 전략이 중요합니다.</p>
<p>I/O-bound 작업은 네트워크, 파일 시스템, DB 응답처럼 외부 입출력을 기다리는 시간이 병목인 작업입니다. API 요청, DB 조회, 파일 읽기 같은 작업은 실제 CPU 계산보다 대기 시간이 더 클 수 있습니다.</p>
<p>웹 서버에서는 대부분 요청이 I/O-bound인 경우가 많기 때문에 비동기 I/O와 커넥션 풀을 잘 쓰는 것이 중요합니다. 반대로 CPU-bound 작업을 요청 처리 스레드나 이벤트 루프에서 오래 실행하면 다른 요청까지 지연될 수 있습니다.</p>
<h3 id="q30-nodejs는-싱글-스레드인데-어떻게-여러-요청을-처리하나요">Q30. Node.js는 싱글 스레드인데 어떻게 여러 요청을 처리하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>&quot;Node.js는 싱글 스레드&quot;라는 말은 JavaScript 코드가 기본적으로 하나의 메인 스레드 이벤트 루프에서 실행된다는 의미에 가깝습니다. 하지만 모든 일이 한 스레드에서만 처리된다는 뜻은 아닙니다.</p>
<p>Node.js는 이벤트 루프를 통해 비동기 작업의 완료 콜백을 처리하고, 가능한 I/O 작업은 운영체제 커널에 위임합니다. 파일 시스템, DNS, 일부 암호화 작업처럼 스레드가 필요한 작업은 libuv의 worker pool을 사용할 수 있습니다. 즉 메인 스레드는 요청마다 블로킹해서 기다리지 않고, 완료된 작업의 콜백을 큐에서 처리합니다.</p>
<p>중요한 한계는 메인 스레드에서 오래 걸리는 동기 계산을 하면 이벤트 루프가 막힌다는 점입니다. 그래서 CPU-bound 작업은 worker thread, 별도 서버, queue 기반 background job으로 분리하는 것이 좋습니다.</p>
<p><strong>프로젝트 연결</strong></p>
<p>Real World의 Refresh Token 대기 큐는 여러 401 요청을 동시에 다루되, 실제 Refresh 요청은 하나만 수행하도록 만든 동시성 제어 사례로 설명할 수 있습니다.</p>
<h3 id="q31-커넥션-풀이-필요한-이유는-무엇인가요">Q31. 커넥션 풀이 필요한 이유는 무엇인가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>커넥션 풀은 DB나 외부 서비스와의 연결을 미리 만들어 두고 재사용하는 구조입니다. 요청마다 DB 연결을 새로 만들면 TCP 연결, 인증, 세션 초기화 비용이 반복되고 응답 시간이 늘어납니다. 풀을 사용하면 기존 연결을 빌려 쓰고 반납하므로 비용을 줄일 수 있습니다.</p>
<p>또 하나의 목적은 DB 보호입니다. 애플리케이션 요청이 갑자기 늘어났을 때 무제한으로 DB 연결을 만들면 DB가 먼저 병목이 되거나 장애가 날 수 있습니다. 커넥션 풀은 동시에 DB에 접근하는 연결 수를 제한해 서버와 DB 사이의 압력을 조절합니다.</p>
<p>다만 풀 크기를 크게 잡는다고 항상 좋은 것은 아닙니다. DB가 처리할 수 있는 연결 수, 애플리케이션 인스턴스 수, 쿼리 시간, 트랜잭션 유지 시간을 함께 봐야 합니다.</p>
<h3 id="q32-인덱스는-왜-조회를-빠르게-만들고-항상-좋은가요">Q32. 인덱스는 왜 조회를 빠르게 만들고, 항상 좋은가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>인덱스는 테이블 전체를 처음부터 끝까지 찾지 않고, 특정 컬럼 기준으로 빠르게 위치를 찾을 수 있게 돕는 자료구조입니다. 책의 목차나 색인처럼, 조건에 맞는 행을 찾기 위해 읽어야 하는 범위를 줄여줍니다.</p>
<p>하지만 항상 좋은 것은 아닙니다. 인덱스도 별도 저장 공간을 사용하고, INSERT, UPDATE, DELETE가 발생하면 인덱스도 함께 갱신해야 합니다. 그래서 쓰기 성능이 떨어질 수 있고, 너무 많은 인덱스는 오히려 운영 비용을 늘립니다.</p>
<p>인덱스는 조회 조건, 정렬 조건, 조인 조건, 데이터 분포도를 보고 설계해야 합니다. 예를 들어 BE-realworld의 <code>Article</code>은 작성자별 최신 글 조회를 고려해 <code>authorId, createdAt</code> 인덱스를 둘 수 있고, <code>Comment</code>는 게시글별 댓글 조회를 위해 <code>articleId, createdAt</code> 인덱스가 의미 있습니다.</p>
<h3 id="q33-트랜잭션-격리-수준이-필요한-이유는-무엇인가요">Q33. 트랜잭션 격리 수준이 필요한 이유는 무엇인가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>트랜잭션 격리 수준은 여러 트랜잭션이 동시에 실행될 때 서로의 중간 상태를 어디까지 볼 수 있는지 정하는 기준입니다. 격리 수준이 낮으면 동시 처리 성능은 좋아질 수 있지만, dirty read, non-repeatable read, phantom read 같은 이상 현상이 생길 수 있습니다.</p>
<p>반대로 격리 수준을 높이면 데이터 일관성은 강해지지만 lock 경합이 늘고 성능이 떨어질 수 있습니다. 그래서 모든 상황에서 가장 높은 격리 수준을 쓰는 것이 정답은 아닙니다.</p>
<p>면접에서는 &quot;트랜잭션 격리 수준은 동시성과 일관성 사이의 trade-off를 조절하는 장치&quot;라고 답하면 됩니다. 예를 들어 주문, 결제, 재고처럼 중간 상태가 노출되면 안 되는 영역은 더 강한 일관성이 필요하고, 단순 목록 조회는 성능과 최신성 요구를 함께 봐야 합니다.</p>
<h3 id="q34-쿠키-세션-jwt의-차이는-무엇인가요">Q34. 쿠키, 세션, JWT의 차이는 무엇인가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>쿠키는 브라우저에 저장되고 요청 시 조건에 맞으면 자동으로 서버에 전송되는 저장 메커니즘입니다. 인증 방식 그 자체라기보다 값을 저장하고 전달하는 수단입니다.</p>
<p>세션은 서버가 로그인 상태를 저장하고, 클라이언트는 세션 ID만 쿠키로 들고 있는 방식입니다. 서버에서 상태를 관리하므로 강제 로그아웃이나 세션 무효화가 쉽지만, 서버 확장 시 세션 저장소를 공유해야 합니다.</p>
<p>JWT는 토큰 자체에 사용자 식별자와 만료 시간 같은 claim을 담고 서명하는 방식입니다. 서버가 매 요청마다 세션 저장소를 조회하지 않아도 토큰 검증이 가능하지만, 한 번 발급된 토큰을 즉시 무효화하기 어렵고 탈취 시 만료 전까지 위험할 수 있습니다.</p>
<p>프로젝트 답변에서는 Access Token은 짧게, Refresh Token은 더 조심스럽게 다뤄야 한다고 연결하면 좋습니다. BE-realworld에서는 Refresh Token을 DB에 원문이 아니라 해시로 저장하고, HttpOnly Cookie 옵션을 사용한 경험으로 설명할 수 있습니다.</p>
<h3 id="q35-cors는-왜-발생하고-어떻게-해결하나요">Q35. CORS는 왜 발생하고 어떻게 해결하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>CORS는 브라우저의 Same-Origin Policy 때문에 발생합니다. 브라우저는 보안상 한 출처의 웹 페이지가 다른 출처의 리소스에 마음대로 접근하지 못하게 제한합니다. 다른 출처로 요청하려면 서버가 <code>Access-Control-Allow-Origin</code> 같은 응답 헤더로 해당 출처를 허용해야 합니다.</p>
<p>인증 정보가 포함된 요청이면 더 조심해야 합니다. 서버는 허용할 origin을 명시해야 하고, 클라이언트는 credentials 옵션을 맞춰야 하며, 쿠키의 <code>SameSite</code>, <code>Secure</code>, <code>Domain</code> 조건도 함께 맞아야 합니다.</p>
<p>CORS 에러가 보인다고 항상 서버 CORS 설정만 문제인 것은 아닙니다. UNIBUSK Kakao OAuth2 문제처럼 실제 원인은 쿠키가 저장된 도메인과 프론트엔드 도메인이 달라 인증 상태가 이어지지 않는 것일 수 있습니다. 그래서 요청 출처, 응답 헤더, 쿠키 저장 위치, credentials 설정을 단계별로 확인해야 합니다.</p>
<h3 id="q36-docker를-왜-사용하나요">Q36. Docker를 왜 사용하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>Docker는 애플리케이션과 실행에 필요한 의존성, 설정을 이미지로 묶어 컨테이너에서 실행할 수 있게 해줍니다. 로컬, CI, 배포 환경의 Node.js 버전이나 OS 패키지 차이로 생기는 문제를 줄이고, 같은 이미지를 여러 환경에서 재현할 수 있게 하는 것이 핵심 장점입니다.</p>
<p>VM은 하드웨어를 가상화해 각 VM이 별도 OS를 가지는 방식에 가깝고, 컨테이너는 호스트 OS 커널을 공유하면서 프로세스를 격리합니다. 그래서 컨테이너는 일반적으로 더 가볍고 시작이 빠릅니다.</p>
<p>BE-realworld에서는 Dockerfile과 docker-compose를 통해 NestJS 서버와 PostgreSQL 환경을 맞추고, 배포 전 Docker 이미지 빌드와 실행을 검증하는 체크리스트를 정리했습니다. 면접에서는 &quot;Docker를 배포 자체의 마법 도구로 본 것이 아니라, 실행 환경을 재현 가능하게 만들기 위한 도구로 이해했다&quot;고 답하면 좋습니다.</p>
<h2 id="9-자료구조·알고리즘-기본">9. 자료구조·알고리즘 기본</h2>
<h3 id="q37-배열과-연결-리스트의-차이는-무엇인가요">Q37. 배열과 연결 리스트의 차이는 무엇인가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>배열은 메모리상 연속된 공간에 데이터를 저장하는 구조입니다. 인덱스를 알고 있으면 <code>arr[i]</code>처럼 바로 접근할 수 있어 조회가 빠릅니다. 일반적으로 인덱스 접근은 <code>O(1)</code>입니다. 대신 중간에 값을 삽입하거나 삭제하면 뒤의 요소들을 이동해야 해서 비용이 커질 수 있습니다.</p>
<p>연결 리스트는 각 노드가 값과 다음 노드의 참조를 가지는 구조입니다. 중간 삽입과 삭제는 해당 위치의 노드를 알고 있다면 포인터만 바꾸면 되므로 유리할 수 있습니다. 하지만 특정 인덱스에 바로 접근할 수 없고, 앞에서부터 따라가야 하므로 탐색은 <code>O(n)</code>입니다.</p>
<p>실무에서는 JavaScript 배열을 가장 많이 쓰지만, 이 차이를 이해하면 &quot;조회가 많은지&quot;, &quot;중간 삽입/삭제가 많은지&quot;, &quot;순차 접근이면 충분한지&quot; 같은 선택 기준을 설명할 수 있습니다.</p>
<h3 id="q38-stack과-queue는-어디에-사용되나요">Q38. Stack과 Queue는 어디에 사용되나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>Stack은 나중에 들어온 데이터가 먼저 나가는 LIFO 구조입니다. 함수 호출 스택, 실행 취소, 브라우저 뒤로 가기, DFS 같은 상황에 사용할 수 있습니다. 가장 최근 상태부터 되돌아가야 하는 문제에 잘 맞습니다.</p>
<p>Queue는 먼저 들어온 데이터가 먼저 나가는 FIFO 구조입니다. 작업 대기열, 메시지 큐, 이벤트 처리, BFS 같은 상황에 사용됩니다. 요청이나 작업을 들어온 순서대로 처리해야 할 때 적합합니다.</p>
<p>프로젝트와 연결하면, Real World의 Refresh Token 대기 큐는 토큰 갱신 중 들어온 요청들을 잠시 보관했다가 갱신이 끝난 뒤 이어서 처리하는 큐 성격의 구조로 설명할 수 있습니다.</p>
<h3 id="q39-hash-table은-어떻게-동작하고-충돌은-어떻게-처리하나요">Q39. Hash Table은 어떻게 동작하고, 충돌은 어떻게 처리하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>Hash Table은 key를 hash function에 넣어 배열의 index로 바꾸고, 그 위치에 값을 저장하는 자료구조입니다. 잘 설계되면 key 기반 조회, 삽입, 삭제가 평균적으로 <code>O(1)</code>에 가깝습니다.</p>
<p>하지만 서로 다른 key가 같은 index로 매핑되는 충돌이 생길 수 있습니다. 충돌 처리 방식에는 같은 버킷에 여러 값을 연결해 저장하는 chaining, 다른 빈 위치를 찾아 저장하는 open addressing 같은 방식이 있습니다.</p>
<p>실무에서는 JavaScript의 <code>Map</code>, <code>Set</code>, 객체 기반 lookup, id 기준 캐시에서 이 개념이 자주 등장합니다. 예를 들어 게시글 id를 key로 좋아요 수나 favorited 여부를 빠르게 찾고 싶을 때 배열을 매번 순회하기보다 map 형태로 바꾸면 조회 비용을 줄일 수 있습니다.</p>
<h3 id="q40-javascript에서-array와-object의-차이는-무엇인가요">Q40. JavaScript에서 Array와 Object의 차이는 무엇인가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>JavaScript에서 Array는 순서가 있는 목록 데이터를 표현하기 위한 객체입니다. 숫자 index와 <code>length</code>를 중심으로 동작하고, <code>map</code>, <code>filter</code>, <code>reduce</code>, <code>forEach</code> 같은 배열 전용 메서드를 통해 순차 처리에 적합합니다.</p>
<p>Object는 key-value 형태의 속성 집합을 표현하기 위한 객체입니다. 특정 entity의 속성, API 응답 객체, 설정 값처럼 이름이 있는 필드를 표현할 때 자연스럽습니다.</p>
<p>중요한 점은 JavaScript Array도 객체라는 것입니다. 하지만 의미와 사용 목적이 다릅니다. 목록처럼 순서와 반복이 중요하면 Array를 쓰고, 이름 있는 속성으로 하나의 대상을 표현하거나 key 기반 접근이 중요하면 Object를 씁니다.</p>
<p>예를 들어 주문 목록은 Array가 자연스럽고, 주문 하나의 <code>id</code>, <code>status</code>, <code>totalPrice</code> 같은 필드는 Object가 자연스럽습니다. 배열을 객체처럼 임의 속성 저장소로 쓰거나, 객체를 배열처럼 순서 기반으로 다루면 의도가 흐려지고 유지보수가 어려워집니다.</p>
<h3 id="q41-map과-object의-차이는-무엇인가요">Q41. Map과 Object의 차이는 무엇인가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>Object는 JavaScript의 기본 객체로, 문자열 또는 Symbol key를 중심으로 속성을 저장합니다. 일반적인 데이터 구조나 JSON 형태를 표현할 때 자연스럽습니다.</p>
<p>Map은 key-value 저장을 목적으로 만든 자료구조입니다. 문자열뿐 아니라 객체, 함수, 숫자 같은 다양한 값을 key로 사용할 수 있고, 삽입 순서를 보장합니다. 또한 <code>size</code>, <code>has</code>, <code>get</code>, <code>set</code>, <code>delete</code> 같은 명확한 API가 있어 동적인 key-value 컬렉션에 적합합니다.</p>
<p>면접에서는 &quot;고정된 형태의 데이터 모델은 Object가 자연스럽고, 동적으로 key가 추가·삭제되거나 key 타입이 다양하고 lookup이 중요한 컬렉션은 Map이 적합하다&quot;고 답하면 됩니다.</p>
<h3 id="q42-set은-언제-사용하나요">Q42. Set은 언제 사용하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>Set은 중복 없는 값의 집합을 표현할 때 사용합니다. 어떤 값이 이미 있는지 빠르게 확인하거나, 배열에서 중복을 제거할 때 유용합니다.</p>
<p>예를 들어 태그 목록에서 중복 태그를 제거하거나, 선택된 id 목록을 관리하거나, 이미 처리한 요청 id를 기록할 때 사용할 수 있습니다. <code>includes</code>로 배열을 매번 순회하면 <code>O(n)</code>이지만, Set의 <code>has</code>는 평균적으로 <code>O(1)</code>에 가깝습니다.</p>
<p>다만 순서가 중요한 목록 UI나 중복이 의미 있는 데이터에는 Set이 적합하지 않을 수 있습니다. &quot;중복 제거&quot;와 &quot;빠른 포함 여부 확인&quot;이 핵심일 때 선택합니다.</p>
<h3 id="q43-tree와-graph는-어떤-상황에서-사용하나요">Q43. Tree와 Graph는 어떤 상황에서 사용하나요?</h3>
<p><strong>핵심 답변</strong></p>
<p>Tree는 계층 구조를 표현하는 자료구조입니다. 하나의 root에서 시작해 부모-자식 관계로 내려가며, 일반적으로 cycle이 없습니다. DOM 트리, 파일 시스템, 조직도, 댓글의 대댓글 구조, 라우팅 트리 같은 예시가 있습니다.</p>
<p>Graph는 정점과 간선으로 관계를 표현하는 구조입니다. 방향이 있을 수도 있고 없을 수도 있으며, cycle이 있을 수 있습니다. 지하철 노선, 지도 경로, 사용자 팔로우 관계, 패키지 의존성, 서비스 간 호출 관계처럼 복잡한 연결 관계를 표현할 때 사용합니다.</p>
<p>ItNew 맥락에서는 위치 기반 경로, 배송지 간 연결, 서비스 의존성, 사용자·행사·인증 데이터의 관계를 graph 관점으로도 생각해볼 수 있습니다. 다만 면접에서는 직접 구현 경험을 과장하지 말고, 어떤 문제를 표현하는 데 적합한지 중심으로 말하면 됩니다.</p>
<h3 id="q44-bfs와-dfs의-차이는-무엇인가요">Q44. BFS와 DFS의 차이는 무엇인가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>BFS는 너비 우선 탐색입니다. 시작점에서 가까운 노드부터 단계별로 탐색하며, 보통 queue를 사용합니다. 가중치가 없는 그래프에서 최단 거리나 최소 단계 수를 찾을 때 적합합니다.</p>
<p>DFS는 깊이 우선 탐색입니다. 한 경로를 끝까지 들어간 뒤 되돌아오며, 재귀나 stack을 사용합니다. 모든 경로를 탐색하거나, 트리 구조를 순회하거나, 연결 여부를 확인할 때 사용할 수 있습니다.</p>
<p>면접에서는 &quot;BFS는 가까운 것부터, DFS는 깊은 것부터&quot;라고 시작하고, BFS는 queue, DFS는 stack 또는 재귀와 연결해서 설명하면 됩니다. 프론트엔드에서도 메뉴 트리, 라우트 트리, 댓글 트리, 의존성 탐색 같은 문제에 연결할 수 있습니다.</p>
<h3 id="q45-시간-복잡도와-공간-복잡도는-왜-중요한가요">Q45. 시간 복잡도와 공간 복잡도는 왜 중요한가요?</h3>
<p><strong>핵심 답변</strong></p>
<p>시간 복잡도는 입력 크기가 커질 때 실행 시간이 얼마나 증가하는지 보는 기준입니다. 공간 복잡도는 입력 크기가 커질 때 추가 메모리를 얼마나 사용하는지 보는 기준입니다.</p>
<p>작은 데이터에서는 어떤 방식이든 빨라 보일 수 있지만, 데이터가 커지면 <code>O(n^2)</code>과 <code>O(n log n)</code>, <code>O(n)</code>의 차이가 크게 드러납니다. 그래서 코드가 현재 데이터에서만 동작하는지, 데이터가 늘어나도 버틸 수 있는지 판단하려면 복잡도를 봐야 합니다.</p>
<p>실무에서는 무조건 가장 낮은 복잡도를 고르는 것이 아니라, 데이터 크기, 코드 가독성, 메모리 사용량, 실제 병목 여부를 함께 봅니다. 예를 들어 배열을 매번 순회해 id를 찾는 코드가 작을 때는 괜찮지만, 반복 렌더링이나 큰 목록에서 자주 실행된다면 Map으로 바꿔 lookup 비용을 줄일 수 있습니다.</p>
<h2 id="10-마지막-점검-질문">10. 마지막 점검 질문</h2>
<ul>
<li><input disabled="" type="checkbox"> ItNew가 AI·모빌리티·데이터 연결을 강조하는 회사라는 점을 지원동기와 연결할 수 있나요?</li>
<li><input disabled="" type="checkbox"> React/Next.js 경험을 Spring·RDBMS·아키텍처 요구와 분리하지 않고 서비스 전체 흐름으로 설명할 수 있나요?</li>
<li><input disabled="" type="checkbox"> 프로세스/스레드, 동시성/병렬성, CPU-bound/I/O-bound를 서버 요청 처리와 연결해 설명할 수 있나요?</li>
<li><input disabled="" type="checkbox"> Node.js 싱글 스레드 표현의 의미와 이벤트 루프가 막히는 상황을 구분할 수 있나요?</li>
<li><input disabled="" type="checkbox"> 커넥션 풀, 인덱스, 트랜잭션 격리 수준을 DB 성능과 일관성 관점에서 설명할 수 있나요?</li>
<li><input disabled="" type="checkbox"> 배열/연결 리스트, JS Array/Object, Stack/Queue, Hash Table/Map/Set의 선택 기준을 설명할 수 있나요?</li>
<li><input disabled="" type="checkbox"> Tree/Graph, BFS/DFS를 서비스 데이터 구조나 탐색 문제와 연결해 설명할 수 있나요?</li>
<li><input disabled="" type="checkbox"> 시간 복잡도와 공간 복잡도를 실제 코드 선택 기준으로 설명할 수 있나요?</li>
<li><input disabled="" type="checkbox"> 쿠키/세션/JWT, CORS, Docker를 프로젝트 경험과 연결해 답할 수 있나요?</li>
<li><input disabled="" type="checkbox"> CI/CD 질문에서 CI 경험과 자동 배포 CD 경험의 범위를 정확히 구분할 수 있나요?</li>
<li><input disabled="" type="checkbox"> AI 코딩 에이전트 활용을 “사용했습니다”가 아니라 “무엇을 위임하고 어떻게 검증했는지”로 설명할 수 있나요?</li>
<li><input disabled="" type="checkbox"> 수치를 말할 때 측정 도구, 측정 환경, 검증 범위를 함께 말할 수 있나요?</li>
<li><input disabled="" type="checkbox"> 모르는 백엔드·DB 운영 경험은 정직하게 구분하고, 학습·연결 가능한 범위를 말할 수 있나요?</li>
</ul>
<h2 id="참고한-공개-자료">참고한 공개 자료</h2>
<ul>
<li>ItNew 원티드 채용공고: <a href="https://www.wanted.co.kr/wd/358179">https://www.wanted.co.kr/wd/358179</a></li>
<li>ItNew 원티드 기업정보: <a href="https://www.wanted.co.kr/company/41725">https://www.wanted.co.kr/company/41725</a></li>
<li>ItNew 공식 홈페이지: <a href="https://itnewcorp.com/">https://itnewcorp.com/</a></li>
<li>THE VC ItNew 기업정보: <a href="https://thevc.kr/itnew">https://thevc.kr/itnew</a></li>
<li>Node.js 이벤트 루프 공식 문서: <a href="https://nodejs.org/learn/asynchronous-work/event-loop-timers-and-nexttick">https://nodejs.org/learn/asynchronous-work/event-loop-timers-and-nexttick</a></li>
<li>Node.js 이벤트 루프/Worker Pool 주의사항: <a href="https://nodejs.org/learn/asynchronous-work/dont-block-the-event-loop">https://nodejs.org/learn/asynchronous-work/dont-block-the-event-loop</a></li>
<li>Docker 공식 Overview: <a href="https://docs.docker.com/get-started/docker-overview/">https://docs.docker.com/get-started/docker-overview/</a></li>
<li>GitHub Actions 공식 문서: <a href="https://docs.github.com/en/actions/get-started/understand-github-actions">https://docs.github.com/en/actions/get-started/understand-github-actions</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자기소개서·구두 답변 초안]]></title>
            <link>https://velog.io/@park-moen/%EC%9E%90%EA%B8%B0%EC%86%8C%EA%B0%9C%EC%84%9C%EA%B5%AC%EB%91%90-%EB%8B%B5%EB%B3%80-%EC%B4%88%EC%95%88</link>
            <guid>https://velog.io/@park-moen/%EC%9E%90%EA%B8%B0%EC%86%8C%EA%B0%9C%EC%84%9C%EA%B5%AC%EB%91%90-%EB%8B%B5%EB%B3%80-%EC%B4%88%EC%95%88</guid>
            <pubDate>Wed, 15 Jul 2026 06:17:54 GMT</pubDate>
            <description><![CDATA[<h2 id="1-1분-자기소개">1. 1분 자기소개</h2>
<p>안녕하세요. 저는 성능 문제의 원인을 측정하고, 검증한 범위 안에서 개선하며, 팀이 일관되게 유지할 수 있는 구조를 만드는 개발자 박명재입니다.</p>
<p>UNIBUSK에서는 공연 등록 폼에서 입력 하나가 넓은 컴포넌트 트리를 리렌더시키는 문제를 발견했습니다. React DevTools Profiler와 <code>console.count</code>로 원인을 확인했고, <code>watch()</code>와 <code>formState.errors</code>의 구독 범위를 <code>useWatch()</code>와 <code>useFormState({ name })</code>로 분리해 렌더 함수 실행 횟수를 75회에서 1회로 줄였습니다.</p>
<p>Dessert Front Dashboard에서는 Seller와 Admin을 함께 운영하기 위한 모노레포와 공용 UI 패키지 구조를 설계했고, FSD 파일 배치와 의존성 규칙을 문서화했습니다. 기능 구현뿐 아니라 팀원이 같은 기준으로 코드를 읽고 수정할 수 있는 환경을 만들고자 했습니다.</p>
<p>ItNew에서도 문제를 추측으로 해결하기보다 사용자 영향과 원인을 확인하고, 서비스의 데이터 흐름과 AI 기반 개발 워크플로우를 팀이 유지할 수 있는 형태로 개선하는 개발자가 되고 싶습니다.</p>
<h2 id="2-지원동기">2. 지원동기</h2>
<p>ItNew에 지원한 이유는 AI·위치 데이터·인증 기술을 실제 운영 서비스의 문제 해결로 연결하는 방향이 인상적이었기 때문입니다.</p>
<p>공식 홈페이지와 채용공고를 보며, 제주 당일배송 서비스 ERLN은 물류·모빌리티 운영을, MemoryMint와 MinTrigger는 행사 운영과 인증·검증 흐름을 서비스화하는 방향으로 이해했습니다. 단일 기술을 보여주는 데 그치기보다, 기술이 실제 운영 흐름 안에서 쓰이도록 만드는 점에 관심이 갔습니다.</p>
<p>저는 UNIBUSK와 Real World에서 API 요청 흐름, 인증 처리, 서버 데이터와 UI 상태의 연결, 성능 병목 개선을 다뤘습니다. Dessert Front Dashboard에서는 Seller 주문 대시보드를 개발하며 주문 상태, 결제 금액, 필터 조건처럼 운영자가 판단에 사용하는 데이터가 화면에서 일관되게 전달되도록 신경 썼습니다.</p>
<p>ItNew에서도 기술의 결과가 API와 UI, 운영 흐름 안에서 신뢰할 수 있게 전달되도록 구현하고 검증하는 개발자로 기여하고 싶습니다.</p>
<h3 id="지원동기-꼬리-질문-대비">지원동기 꼬리 질문 대비</h3>
<h4 id="q-location-ai가-무엇이라고-이해했나요">Q. Location AI가 무엇이라고 이해했나요?</h4>
<p>Location AI를 깊게 구현해 본 경험은 없지만, ItNew의 서비스 맥락에서는 위치 데이터를 기반으로 운영 의사결정을 돕는 AI 기술로 이해했습니다. 예를 들어 물류 서비스인 ERLN에서는 수거지와 배송지, 차량 위치, 이동 거리, 시간 제약, 배송 우선순위 같은 데이터를 바탕으로 배차나 경로를 더 효율적으로 판단하는 데 활용될 수 있다고 봤습니다.</p>
<p>제가 Location AI 모델 자체를 직접 설계해 본 경험이 있는 것은 아니지만, 이 포지션에서는 위치 기반 판단 결과가 API, 데이터 모델, 운영 화면으로 어떻게 이어지는지 이해하는 역량이 중요하다고 생각합니다. 예를 들어 추천 경로, 예상 시간, 배송 상태, 예외 상황 같은 정보는 단순히 화면에 표시되는 값이 아니라, 서버의 판단 결과와 운영 정책이 사용자에게 전달되는 지점입니다.</p>
<p>그래서 저는 프론트엔드 구현에만 한정해서 보기보다, API 응답 구조와 상태 관리, 예외 처리, 사용자가 신뢰할 수 있는 UI 표현까지 함께 고려하는 방식으로 기여할 수 있다고 봅니다.</p>
<h4 id="q-블록체인이-무엇이라고-이해하고-있나요">Q. 블록체인이 무엇이라고 이해하고 있나요?</h4>
<p>블록체인은 데이터를 블록 단위로 연결하고, 여러 참여자가 같은 기록을 검증할 수 있게 만든 분산 원장 기술로 이해하고 있습니다. 핵심은 한 번 기록된 데이터의 위변조를 어렵게 만들고, 특정 기록이 언제 생성됐고 어떤 상태였는지 검증 가능하게 만드는 데 있다고 봅니다.</p>
<p>ItNew 서비스 맥락에서는 MinTrigger나 MemoryMint에서 인증서, 참여 기록, 검증 이력처럼 신뢰성이 중요한 데이터를 다룰 때 블록체인을 활용할 수 있다고 이해했습니다. 다만 제가 블록체인 프로토콜을 직접 구현한 경험이 있는 것은 아니기 때문에, 면접에서는 구현 경험을 과장하기보다 인증·검증·이력 추적을 위한 신뢰 계층으로 이해하고 있다고 답하겠습니다.</p>
<h4 id="q-erln-memorymint-mintrigger를-각각-어떻게-이해했나요">Q. ERLN, MemoryMint, MinTrigger를 각각 어떻게 이해했나요?</h4>
<p>ERLN은 제주 당일배송을 중심으로 한 물류 서비스로 이해했습니다. 여기서는 AI 기반 스마트 배차, 경로 최적화, 배송 상태 관리처럼 위치와 운영 데이터가 실제 배송 흐름으로 이어지는 점이 중요해 보였습니다.</p>
<p>MemoryMint는 행사 운영 자동화 서비스로 이해했습니다. QR 기반 입장, 출석 관리, 인증서 발급처럼 행사 현장에서 반복되는 운영 업무를 디지털화하고, 참여 이력이나 인증 정보를 관리하는 서비스라고 봤습니다.</p>
<p>MinTrigger는 블록체인 기반 인증·검증 API 또는 B2B SaaS에 가까운 서비스로 이해했습니다. 특정 데이터나 이벤트의 신뢰성을 검증하고, 외부 서비스가 이를 API로 활용할 수 있게 하는 방향이라고 이해했습니다.</p>
<h4 id="q-이-기술들을-왜-프론트엔드-지원동기와-연결했나요">Q. 이 기술들을 왜 프론트엔드 지원동기와 연결했나요?</h4>
<p>제가 관심을 가진 지점은 특정 기술 이름 자체보다, 복잡한 데이터를 사용자가 이해하고 행동할 수 있는 서비스 흐름으로 바꾸는 부분입니다. ERLN에서는 배차·경로·배송 상태 데이터가 운영 화면이나 사용자 화면으로 표현되어야 하고, MemoryMint나 MinTrigger에서는 인증·검증 결과가 사용자가 신뢰할 수 있는 UI와 API 흐름으로 연결되어야 합니다.</p>
<p>저는 UNIBUSK와 Real World에서 데이터 패칭 구조, 인증 흐름, 상태 분리, 에러 처리 같은 부분을 다뤘습니다. 그래서 ItNew에서도 AI나 블록체인 자체를 직접 연구하는 역할보다는, 그 결과가 실제 서비스 화면과 API 흐름에서 안정적으로 전달되도록 만드는 프론트엔드 개발자로 기여할 수 있다고 연결했습니다.</p>
<h2 id="3-가장-자신-있는-경험">3. 가장 자신 있는 경험</h2>
<p>가장 자신 있게 설명할 수 있는 경험은 UNIBUSK 공연 등록 폼의 렌더링 문제를 측정하고 해결한 과정입니다.</p>
<p>처음에는 React Hook Form의 비제어 컴포넌트 패턴을 사용했기 때문에 입력 시 리렌더가 적을 것이라고 예상했습니다. 하지만 Profiler로 확인해보니 <code>watch(&#39;teamName&#39;)</code> 한 줄 때문에 상위 컴포넌트가 폼 값을 구독하고, 하위 트리 전체가 영향을 받고 있었습니다.</p>
<p>이를 해결하기 위해 글자 수 표시만 담당하는 하위 컴포넌트로 <code>useWatch()</code>를 옮겼습니다. 이후에도 일부 필드에서 리렌더가 남아 있었고, 원인은 <code>formState.errors</code> 전체 객체 구독이었습니다. 최종적으로 <code>useFormState({ name })</code>로 각 필드가 자기 에러만 구독하도록 바꿨습니다.</p>
<p>검증은 Profiler와 <code>console.count</code>를 함께 사용했습니다. Profiler로 영향 범위를 보고, 실제 렌더 함수 실행 여부는 <code>console.count</code>로 확인했습니다. 그 결과 렌더 함수 실행 횟수가 75회에서 1회로 줄었습니다.</p>
<p>이 경험에서 중요한 점은 특정 API를 사용했다는 사실보다, 문제를 추측하지 않고 측정한 뒤 원인에 맞는 위치에서 해결했다는 점입니다.</p>
<h2 id="4-복잡하거나-예측하지-못했던-문제-해결-경험">4. 복잡하거나 예측하지 못했던 문제 해결 경험</h2>
<p>UNIBUSK의 Kakao OAuth2 로그인 구현 중 로컬 환경에서만 인증 쿠키가 저장되지 않는 문제가 있었습니다.</p>
<p>처음에는 CORS 설정이나 클라이언트 요청 옵션 문제라고 생각했지만, 로그인 요청, Kakao 리다이렉트, 쿠키 저장, 후속 API 요청 흐름을 단계별로 나눠 확인했습니다. Chrome 개발자 도구로 리다이렉트 전후 요청과 쿠키 저장 위치를 확인한 결과, 로그인 성공 후 전달된 쿠키가 백엔드 도메인에 저장되어 프론트엔드 요청에 기대한 방식으로 포함되지 않는다는 점을 확인했습니다.</p>
<p>Route Handler, 터널링, IP 기반 쿠키 도메인, Middleware 접근 등을 검토했지만 각각 한계가 있었습니다. 최종적으로 백엔드가 로그인 성공 후 임시 Authorization Code를 프론트엔드 콜백 URL로 전달하고, 프론트엔드는 동일 출처 Proxy를 통해 이 코드를 토큰으로 교환하는 방식으로 인증 흐름을 바꿨습니다.</p>
<p>이 경험을 통해 서버가 쿠키를 내려준다는 사실만으로 인증이 보장되는 것이 아니라, 브라우저의 도메인별 쿠키 저장 정책과 요청이 이어지는 실행 흐름까지 함께 봐야 한다는 점을 배웠습니다.</p>
<h2 id="5-좋은-코드란-무엇이라고-생각하나요">5. 좋은 코드란 무엇이라고 생각하나요?</h2>
<p>제가 생각하는 좋은 코드는 세 가지 기준을 만족하는 코드입니다.</p>
<p>첫째, 다른 팀원이 열었을 때 의도를 빠르게 파악할 수 있어야 합니다. UNIBUSK에서 글자 수 표시 로직을 <code>TeamNameCharCount</code>로 분리한 것처럼, 이름과 책임이 일치하면 코드를 읽는 비용이 줄어듭니다.</p>
<p>둘째, 결합도는 낮고 응집도는 높아야 합니다. Dessert Front Dashboard에서 FSD 컨벤션을 정리할 때 단방향 의존성, Public API, Cross-import 금지 원칙을 세워 슬라이스 내부 구현이 외부에서 직접 참조되지 않도록 했습니다.</p>
<p>셋째, 팀이 반복해서 지킬 수 있어야 합니다. 좋은 구조를 한 사람이 알고 있는 것과 팀 전체가 같은 기준으로 유지하는 것은 다릅니다. 그래서 파일 suffix, Query와 Mutation 배치 기준, 작업 파이프라인을 문서화했습니다.</p>
<h3 id="좋은-코드-꼬리-질문-대비">좋은 코드 꼬리 질문 대비</h3>
<h4 id="q-결합도와-응집도가-무엇인가요">Q. 결합도와 응집도가 무엇인가요?</h4>
<p>결합도는 한 모듈이나 컴포넌트가 다른 모듈에 얼마나 의존하고 있는지를 의미합니다. 결합도가 높으면 한쪽 구현이 바뀔 때 다른 곳까지 함께 수정해야 할 가능성이 커집니다.</p>
<p>응집도는 하나의 모듈이나 컴포넌트 안의 코드들이 같은 목적과 책임을 중심으로 모여 있는 정도입니다. 응집도가 높다는 것은 하나의 파일이나 컴포넌트가 명확한 하나의 역할을 수행한다는 뜻입니다.</p>
<p>예를 들어 UNIBUSK에서 글자 수 표시 로직을 상위 폼 컴포넌트에 섞어두면 폼 전체가 입력값 변화에 영향을 받았습니다. 반대로 <code>TeamNameCharCount</code>처럼 글자 수 표시 책임을 가진 컴포넌트로 분리하면, 필요한 값만 구독하고 책임도 더 명확해집니다.</p>
<h4 id="q-결합도가-높고-응집도가-낮은-코드는-어떤-문제가-생기나요">Q. 결합도가 높고 응집도가 낮은 코드는 어떤 문제가 생기나요?</h4>
<p>가장 큰 문제는 변경 영향 범위가 예측하기 어려워진다는 점입니다. 하나의 수정이 관련 없는 화면이나 기능까지 깨뜨릴 수 있고, 테스트해야 할 범위도 넓어집니다.</p>
<p>응집도가 낮으면 한 파일이나 컴포넌트 안에 여러 책임이 섞입니다. 예를 들어 UI 표시, API 호출, 폼 검증, 권한 처리, 데이터 변환이 한곳에 모이면 코드를 읽을 때 어디가 어떤 책임인지 파악하기 어렵고, 일부만 재사용하거나 교체하기도 어렵습니다.</p>
<p>그래서 저는 좋은 코드를 &quot;동작하는 코드&quot;에서 끝내지 않고, 변경 이유가 비슷한 것끼리 모여 있고 외부와의 의존 방향이 명확한 코드로 보려고 합니다. Dessert Front Dashboard에서 단방향 의존성, Public API, Cross-import 금지 원칙을 정리한 것도 이런 변경 비용을 줄이기 위한 목적이었습니다.</p>
<h2 id="6-본인의-약점과-보완-방식">6. 본인의 약점과 보완 방식</h2>
<p>저의 약점은 익숙한 문제를 만나면 원인을 충분히 확인하기 전에 알고 있는 해결책부터 떠올리는 경향이 있다는 점입니다.</p>
<p>UNIBUSK의 렌더링 문제에서도 처음에는 <code>React.memo()</code>를 적용하면 해결될 것이라고 생각했습니다. 하지만 Profiler로 확인한 실제 원인은 props 변경이 아니라 상위 컴포넌트에서 폼 값을 구독하던 구조였습니다. 따라서 메모이제이션보다 구독 위치를 필요한 하위 컴포넌트로 옮기는 것이 더 적절했습니다.</p>
<p>이후에는 바로 해결책을 적용하기보다 문제가 발생하는 조건과 영향을 받는 범위를 먼저 확인합니다. 적어도 하나 이상의 대안을 검토하고, 각각의 장단점과 검증 방법을 비교한 뒤 구현하는 방식으로 보완하고 있습니다.</p>
<h2 id="7-ai-코딩-에이전트-활용에-대한-답변">7. AI 코딩 에이전트 활용에 대한 답변</h2>
<p>AI 코딩 에이전트는 생산성을 높일 수 있지만, 최종 책임은 개발자에게 있다고 생각합니다.</p>
<p>제가 AI에게 위임할 수 있다고 보는 영역은 반복 코드 초안 작성, 대안 탐색, 문서 요약, 테스트 케이스 아이디어 정리입니다. 반면 인증, 권한, 캐시, 사용자 데이터, DB 정합성처럼 보안과 아키텍처에 영향을 주는 부분은 AI의 제안을 그대로 적용하지 않고 직접 검증해야 합니다.</p>
<p>검증 기준은 네 가지입니다. 요구사항을 실제로 만족하는지, 타입과 테스트로 검증 가능한지, 보안상 위험한 데이터 흐름이 없는지, 기존 코드베이스의 구조와 컨벤션을 깨지 않는지입니다.</p>
<p>예를 들어 React 렌더링 문제에서 AI가 <code>React.memo()</code>를 제안하더라도, 실제 원인이 상위 구독이라면 문제 위치가 맞지 않습니다. 그래서 저는 AI의 답을 결론으로 받아들이기보다 가설 중 하나로 보고, Profiler, Network, 테스트, 타입체크 같은 도구로 검증하는 방식을 선호합니다.</p>
<h2 id="8-itnew에서-기여할-수-있는-부분">8. ItNew에서 기여할 수 있는 부분</h2>
<p>제가 바로 기여할 수 있는 부분은 세 가지입니다.</p>
<p>첫째, React·Next.js 기반 화면에서 렌더링과 초기 로딩 병목을 측정하고 개선하는 일입니다. UNIBUSK에서 폼 렌더링, Code Splitting, Hydration Pattern을 직접 측정하고 적용한 경험이 있습니다.</p>
<p>둘째, API와 UI 상태의 경계를 명확히 나누는 일입니다. 서버 상태, 클라이언트 상태, 폼 상태를 구분하고 TanStack Query, Zustand, React Hook Form을 각각 목적에 맞게 사용한 경험이 있습니다.</p>
<p>셋째, 팀이 유지할 수 있는 구조와 문서를 만드는 일입니다. Dessert Front Dashboard에서 모노레포, 공용 UI 패키지, FSD 컨벤션, 작업 파이프라인을 정리한 경험이 있습니다.</p>
<p>Java 또는 Kotlin 기반 Spring Boot를 실무 프로젝트의 주 스택으로 사용한 경험은 제한적입니다. 다만 백엔드 구조 자체를 처음 접하는 상태는 아니고, Spring Boot 기초 강의를 통해 IoC/DI, 어노테이션 기반 구성, AOP를 통한 횡단 관심사 분리 같은 핵심 개념을 학습했습니다.</p>
<p>이후 Real World 프로젝트에서는 NestJS로 Controller, Service, Repository의 책임을 나누고, Service를 직접 생성하지 않고 DI 컨테이너를 통해 주입받는 구조로 서버를 설계했습니다. Spring Boot의 Bean 생명주기, JPA, 트랜잭션 전파, Spring Security 같은 생태계 세부 경험은 보완해야 하지만, 계층 분리, DI 중심 설계, REST API, RDBMS 기반 데이터 모델링 경험을 바탕으로 팀의 Spring Boot 코드베이스를 빠르게 읽고 연결해 나가겠습니다.</p>
<h2 id="9-협업-및-갈등-해결">9. 협업 및 갈등 해결</h2>
<h3 id="q1-다른-직군과-의견-충돌이-발생했던-경험이-있나요-어떻게-해결했나요">Q1. 다른 직군과 의견 충돌이 발생했던 경험이 있나요? 어떻게 해결했나요?</h3>
<p>UNIBUSK에서 공연 등록 폼을 설계할 때, 사용자가 한 번에 모든 정보를 입력하는 방식과 단계별로 나눠 입력하는 방식 사이에서 논의가 있었습니다. 기획 측면에서는 입력 흐름을 단순하게 유지하고 싶었고, 개발 측면에서는 공연 정보, 장소, 이미지, 팀 정보처럼 상태의 성격이 달라 한 화면에서 모두 관리하면 검증과 상태 복구가 복잡해질 수 있다고 봤습니다.</p>
<p>저는 먼저 제 의견을 바로 설득하려고 하기보다, 기획자가 중요하게 보는 지점과 개발에서 우려하는 지점을 나눠서 이야기했습니다. 기획 쪽에서는 사용자가 복잡하게 느끼지 않는 흐름이 중요했고, 개발 쪽에서는 이전 단계로 돌아가거나 중간에 이탈했을 때 입력값을 어떻게 유지할지가 중요했습니다.</p>
<p>그래서 논의의 기준을 &quot;어느 방식이 더 좋은가&quot;가 아니라 &quot;사용자가 공연 등록을 끝까지 완료하려면 어떤 정보가 언제 유지되어야 하는가&quot;로 바꿨습니다. 이 기준으로 단계별 입력 흐름은 유지하되, 이전 단계 이동과 입력값 유지가 필요한 조건을 함께 정리했습니다. 이후 기술적으로는 현재 입력값, 단계 간 스냅샷, 서버 데이터를 분리해 구현했습니다.</p>
<p>그 결과 기획 의도였던 단계별 입력 흐름을 유지하면서도 개발 측면의 상태 관리 리스크를 줄일 수 있었습니다. 이 경험을 통해 직군 간 의견 차이는 누가 맞는지를 가르는 문제가 아니라, 각자가 중요하게 보는 제약을 드러내고 사용자 흐름 기준으로 다시 합의하는 과정이라고 느꼈습니다.</p>
<h3 id="q2-기획자나-디자이너를-설득할-때-기술적인-내용을-어떤-방식으로-풀어서-설명하나요">Q2. 기획자나 디자이너를 설득할 때 기술적인 내용을 어떤 방식으로 풀어서 설명하나요?</h3>
<p>Dessert Front Dashboard에서 초기 디자인 시안을 프론트엔드에 적용할 때 비슷한 경험이 있었습니다. 초기 시안에서는 typography, color, spacing 값이 페이지마다 개별적으로 지정되어 있었고, 같은 역할의 텍스트나 버튼도 조금씩 다른 값이 사용되고 있었습니다.</p>
<p>개발 입장에서는 이 상태로 구현하면 Tailwind 설정이나 공통 스타일로 재사용하기 어렵고, 페이지가 늘어날수록 같은 UI를 매번 따로 맞춰야 했습니다. 다만 디자이너에게 &quot;Tailwind config로 관리하기 어렵습니다&quot;라고만 말하면 개발자의 내부 문제처럼 들릴 수 있다고 생각했습니다.</p>
<p>그래서 저는 이를 &quot;같은 의미의 제목이나 버튼이 화면마다 조금씩 다르면 사용자 입장에서도 일관성이 떨어질 수 있고, 이후 스타일을 수정할 때 모든 페이지를 각각 고쳐야 해서 디자인 일관성을 유지하기 어렵다&quot;는 식으로 설명했습니다.</p>
<p>그 후 typography, color, spacing을 역할 기준으로 정리해 공통 스타일로 재사용할 수 있게 맞추는 방향을 제안했습니다. 이 경험을 통해 기술적인 제약을 설명할 때는 도구 이름보다, 디자인 일관성, 수정 비용, 사용자 인지 관점으로 바꿔 말하는 것이 더 효과적이라고 느꼈습니다.</p>
<h3 id="q3-요구사항이-모호할-때-어떻게-대응하나요">Q3. 요구사항이 모호할 때 어떻게 대응하나요?</h3>
<p>요구사항이 모호할 때는 바로 구현부터 시작하지 않고, 먼저 사용자의 행동 흐름과 예외 상황을 질문으로 정리합니다. 예를 들어 “공연 등록을 할 수 있어야 한다”는 요구만 있으면 정상 등록뿐 아니라 중간 이탈, 이전 단계 이동, 필수값 누락, 서버 실패, 이미지 업로드 실패 같은 조건을 함께 확인해야 합니다.</p>
<p>그다음 기획자나 디자이너에게 “이 경우 사용자가 어떤 상태를 기대하나요?”, “저장되지 않은 입력값은 유지되어야 하나요?”, “실패 시 다시 시도할 수 있어야 하나요?”처럼 판단이 필요한 질문을 구체적으로 드립니다. 이렇게 해야 개발자가 임의로 정책을 정하지 않고, 제품 의도에 맞는 구현을 할 수 있다고 생각합니다.</p>
<p>면접에서 이 질문을 받으면, 저는 요구사항을 기술 작업으로 바로 변환하기보다 먼저 상태, 예외, 우선순위를 정리한다고 답하겠습니다. 특히 ItNew처럼 여러 직군이 함께 서비스를 만드는 환경에서는 요구사항의 빈칸을 조용히 추측해서 채우는 것보다, 빠르게 드러내고 합의하는 방식이 중요하다고 생각합니다.</p>
<h3 id="q4-디자이너와-구현-가능성-또는-일정-때문에-의견이-다르면-어떻게-하나요">Q4. 디자이너와 구현 가능성 또는 일정 때문에 의견이 다르면 어떻게 하나요?</h3>
<p>먼저 디자인 의도를 확인합니다. 특정 UI가 단순히 예쁘기 때문에 들어간 것인지, 사용자의 실수를 줄이거나 정보를 더 잘 이해시키기 위한 의도인지에 따라 대안이 달라지기 때문입니다.</p>
<p>그다음 구현 비용과 리스크를 구체적으로 설명합니다. “어렵습니다”라고 말하기보다, 어떤 부분이 비용을 만드는지, 일정 안에서 가능한 대안은 무엇인지, 이번 배포에서 반드시 필요한 범위와 이후 개선 가능한 범위를 나눠 이야기하려고 합니다.</p>
<p>예를 들어 복잡한 애니메이션이나 상호작용이 일정상 부담된다면, 첫 배포에서는 핵심 정보 구조와 접근성을 유지한 정적 UI로 구현하고, 이후 사용자 반응이나 일정에 따라 인터랙션을 추가하는 방식을 제안할 수 있습니다. 중요한 것은 디자인을 줄이는 것이 아니라, 디자인 의도를 해치지 않는 현실적인 구현 범위를 함께 찾는 것이라고 생각합니다.</p>
<h3 id="q5-기획자나-po가-빠른-출시를-요구하지만-기술-부채가-우려되면-어떻게-설득하나요">Q5. 기획자나 PO가 빠른 출시를 요구하지만 기술 부채가 우려되면 어떻게 설득하나요?</h3>
<p>먼저 출시 목표 자체를 반대하지 않습니다. 일정이 중요한 상황에서는 모든 구조를 이상적으로 만들 수 없기 때문에, 사용자에게 바로 영향을 주는 핵심 경로와 나중에 갚아도 되는 부채를 구분해야 한다고 생각합니다.</p>
<p>설득할 때는 “이 구조는 안 좋습니다”처럼 추상적으로 말하지 않고, 어떤 위험이 언제 비용으로 돌아오는지 설명합니다. 예를 들어 API 응답 타입이 느슨하거나 상태 소유권이 불명확하면 지금은 빨라 보여도 이후 기능 추가 때 같은 데이터를 여러 곳에서 수정해야 하고, 버그 원인을 추적하기 어려워질 수 있습니다.</p>
<p>현실적인 방식은 최소한의 안전장치를 남기는 것입니다. 타입 정의, 에러 처리, 핵심 상태의 소유권, TODO가 아닌 후속 이슈 기록처럼 나중에 팀이 이어받을 수 있는 흔적은 남기고, 나머지는 일정에 맞춰 범위를 줄입니다. 빠른 출시와 유지보수성은 항상 대립하는 것이 아니라, 지금 반드시 지켜야 할 기준을 정하는 문제라고 생각합니다.</p>
<h3 id="q6-시간이-촉박한-상황에서는-무엇을-최우선으로-하나요">Q6. 시간이 촉박한 상황에서는 무엇을 최우선으로 하나요?</h3>
<p>시간이 촉박할수록 먼저 사용자가 반드시 통과해야 하는 핵심 흐름을 확인합니다. 모든 기능을 같은 중요도로 보지 않고, 결제, 주문, 인증, 등록처럼 실패하면 사용자가 목적을 달성하지 못하는 경로를 우선합니다.</p>
<p>그다음 반드시 지켜야 할 안전장치와 줄일 수 있는 범위를 나눕니다. 예를 들어 타입 정의, 필수 에러 처리, 권한 검증, 데이터 정합성처럼 장애나 잘못된 데이터로 이어질 수 있는 부분은 최소한으로라도 지켜야 합니다. 반면 부가적인 인터랙션, 고급 필터, 세부 애니메이션, 내부 구조 개선 일부는 후속 작업으로 분리할 수 있습니다.</p>
<p>면접에서 답한다면, 저는 &quot;일정을 맞추기 위해 품질을 포기한다&quot;가 아니라 &quot;사용자와 서비스에 직접 영향을 주는 핵심 품질을 먼저 지키고, 나머지는 범위를 조정한다&quot;고 말하겠습니다. 그리고 미룬 작업은 기억에 의존하지 않고 후속 이슈나 문서로 남겨 팀이 다시 이어갈 수 있게 하겠습니다.</p>
<h4 id="꼬리-질문-실제-프로젝트에서-이런-판단을-해본-적이-있나요">꼬리 질문: 실제 프로젝트에서 이런 판단을 해본 적이 있나요?</h4>
<p>UNIBUSK에서 비슷한 경험이 있었습니다. MVP를 6주 안에 기획, 디자인, 개발까지 진행해야 했기 때문에 모든 기능을 같은 깊이로 구현하기는 어려웠습니다. 그래서 먼저 사용자가 서비스를 사용하는 데 반드시 필요한 핵심 흐름을 기준으로 우선순위를 정했습니다.</p>
<p>예를 들어 사용자가 공연을 탐색하고, 공연 상세를 확인하고, 공연을 등록할 수 있는 흐름을 우선했습니다. 공연 등록에서는 입력값 유지, 필수값 검증, 서버에서 받아오는 데이터와 사용자가 입력 중인 상태의 분리를 먼저 신경 썼습니다. 이 흐름이 불안정하면 부가 기능이 있어도 사용자가 실제 목적을 달성하기 어렵다고 판단했기 때문입니다.</p>
<p>반면 핵심 흐름을 해치지 않는 세부 인터랙션이나 내부 구조 개선은 MVP 이후에도 개선할 수 있는 영역으로 봤습니다. 이 경험을 통해 시간이 촉박할수록 더 많은 것을 한 번에 하려 하기보다, 사용자 목적 달성에 직접 연결되는 기능과 최소 안전장치를 먼저 지키는 것이 중요하다고 느꼈습니다.</p>
<h3 id="q7-팀원이-본인의-기술-제안에-동의하지-않으면-어떻게-하나요">Q7. 팀원이 본인의 기술 제안에 동의하지 않으면 어떻게 하나요?</h3>
<p>먼저 제 제안이 실제 문제를 해결하는지 다시 확인합니다. 익숙한 기술이나 제가 선호하는 구조라는 이유만으로 설득하려 하면 팀 입장에서는 부담이 될 수 있기 때문입니다.</p>
<p>UNIBUSK에서 Next.js 버전 선택을 두고 의견이 달랐던 경험이 있습니다. 팀원은 최신 버전인 Next.js 16을 사용하고 싶어 했지만, 프로젝트 진행 당시 우리가 고려하던 Cloudflare 배포 어댑터에서 해당 버전 지원이 충분하지 않았습니다.</p>
<p>저는 단순히 &quot;최신 버전이라 위험하다&quot;고 말하기보다, 우리 프로젝트가 organization 단위로 배포되어야 했고 비용 제약도 있었기 때문에 배포 방식까지 함께 고려해야 한다고 설명했습니다. 만약 최신 버전을 선택해 Cloudflare 배포 경로가 막히면 다른 배포 환경을 선택해야 하고, 그에 따른 비용이나 설정 부담이 커질 수 있다고 이야기했습니다.</p>
<p>그래서 버전 선택 기준을 &quot;최신인가&quot;가 아니라 &quot;현재 팀이 안정적으로 개발하고, 비용 안에서 배포까지 완료할 수 있는가&quot;로 바꿔 논의했습니다. 이 경험을 통해 기술 선택은 개인의 선호가 아니라 안정성, 호환성, 배포 비용, 팀의 운영 가능성을 함께 보고 결정해야 한다고 느꼈습니다.</p>
<h3 id="q8-협업에서-가장-중요하게-보는-기준은-무엇인가요">Q8. 협업에서 가장 중요하게 보는 기준은 무엇인가요?</h3>
<p>제가 협업에서 가장 중요하게 보는 기준은 데드라인 안에서 팀이 같은 우선순위를 보고 움직이게 만드는 것입니다.</p>
<p>기획자, 디자이너, 개발자는 각각 중요하게 생각하는 기준이 다릅니다. 기획자는 사용자 가치와 출시 일정을 보고, 디자이너는 사용성과 일관성을 보고, 개발자는 구현 가능성과 유지보수 리스크를 봅니다. 이 기준이 정리되지 않으면 각자 열심히 일해도 중요한 작업의 순서가 달라질 수 있다고 생각합니다.</p>
<p>그래서 저는 협업할 때 먼저 이번 배포에서 반드시 지켜야 할 핵심 흐름이 무엇인지 확인하려고 합니다. UNIBUSK처럼 6주 안에 MVP를 만들어야 하는 상황에서는 공연 탐색, 상세 확인, 공연 등록처럼 사용자가 목적을 달성하는 핵심 흐름을 우선했고, 세부 인터랙션이나 후속 개선 가능한 부분은 범위를 조정했습니다.</p>
<p>이 과정에서 중요한 것은 단순히 제 의견을 말하는 것이 아니라, 왜 이 작업이 먼저인지, 미루는 작업은 어떤 영향이 있는지, 후속으로 어떻게 처리할지를 팀이 이해할 수 있게 공유하는 것이라고 생각합니다.</p>
<h2 id="10-역질문-후보">10. 역질문 후보</h2>
<ol>
<li>ItNew에서 현재 가장 집중하고 있는 서비스가 ERLN, MemoryMint, MinTrigger 중 어디인지, 신규 입사자가 먼저 투입될 가능성이 높은 제품은 무엇인지 궁금합니다.</li>
<li>공고에 AI가 생성한 코드와 설계의 품질·보안·아키텍처 적합성 검토가 언급되어 있었는데, 실제 팀에서는 Claude Code나 Cursor를 어떤 워크플로우로 사용하고 계신지 궁금합니다.</li>
<li>프론트엔드 기준으로 현재 가장 개선하고 싶은 품질 이슈가 초기 로딩, 상태 관리, 테스트, 디자인 시스템, API 연동 중 어디에 가까운지 궁금합니다.</li>
<li>초·중급 개발자에게 입사 후 1~3개월 안에 가장 기대하는 역할은 기능 구현, 기존 코드 품질 개선, 문서화, AI 도구 활용 방식 정립 중 어디에 가까운지 궁금합니다.</li>
</ol>
<h2 id="11-답변-시-주의할-점">11. 답변 시 주의할 점</h2>
<ul>
<li>Spring Boot 실무 경험이 있는 것처럼 말하지 않습니다.</li>
<li>AI 도구를 “잘 사용합니다”보다 “무엇을 위임하고 어떻게 검증하는지”로 설명합니다.</li>
<li>성능 수치를 말할 때 측정 도구와 검증 범위를 함께 말합니다.</li>
<li>ItNew의 회사 설명은 확인한 공개 자료 범위에서만 말합니다.</li>
<li>프로젝트 수치를 과장하지 않고, 한계와 추가 검증 방법을 함께 말합니다.</li>
</ul>
<h2 id="참고한-공개-자료">참고한 공개 자료</h2>
<ul>
<li>ItNew 원티드 채용공고: <a href="https://www.wanted.co.kr/wd/358179">https://www.wanted.co.kr/wd/358179</a></li>
<li>ItNew 공식 홈페이지: <a href="https://itnewcorp.com/">https://itnewcorp.com/</a></li>
<li>ItNew 원티드 기업정보: <a href="https://www.wanted.co.kr/company/41725">https://www.wanted.co.kr/company/41725</a></li>
<li>THE VC ItNew 기업정보: <a href="https://thevc.kr/itnew">https://thevc.kr/itnew</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[OAuth2 쿠키 인증 트러블슈팅: Cross-Domain Cookie 접근 불가 이슈]]></title>
            <link>https://velog.io/@park-moen/OAuth2-%EC%BF%A0%ED%82%A4-%EC%9D%B8%EC%A6%9D-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-Cross-Domain-Cookie-%EC%A0%91%EA%B7%BC-%EB%B6%88%EA%B0%80-%EC%9D%B4%EC%8A%88%EC%B4%88%EC%95%88</link>
            <guid>https://velog.io/@park-moen/OAuth2-%EC%BF%A0%ED%82%A4-%EC%9D%B8%EC%A6%9D-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-Cross-Domain-Cookie-%EC%A0%91%EA%B7%BC-%EB%B6%88%EA%B0%80-%EC%9D%B4%EC%8A%88%EC%B4%88%EC%95%88</guid>
            <pubDate>Fri, 30 Jan 2026 16:37:58 GMT</pubDate>
            <description><![CDATA[<h2 id="1-문제-상황">1. 문제 상황</h2>
<h3 id="현재-아키텍처">현재 아키텍처</h3>
<ul>
<li><strong>Frontend</strong>: <code>localhost:3000</code> (Next.js)</li>
<li><strong>Backend</strong>: <code>unibusk.site</code> (Spring Boot)</li>
<li><strong>인증 방식</strong>: Kakao OAuth2 + HttpOnly Cookie</li>
</ul>
<h3 id="발생한-문제">발생한 문제</h3>
<p>OAuth2 로그인 성공 후 FE 콜백 페이지에서 인증 쿠키에 접근할 수 없어 인증 상태 확인 및 API 요청이 불가능한 상황</p>
<hr>
<h2 id="2-인증-플로우-be-oauth2-워크플로우-기반">2. 인증 플로우 (BE OAuth2 워크플로우 기반)</h2>
<pre><code>Step 1: 사용자 &quot;로그인&quot; 버튼 클릭

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  const data = await response.json();

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

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

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

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

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

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

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

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

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

# Windows
choco install mkcert
mkcert -install

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

const posts = getPosts(POSTS_URL);

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

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

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

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

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

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

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

</aside>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

var foo;


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

function foo() {
  const x = 2;

  bar()
}

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

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

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

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

  innerFunction();
}

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

</br>
</br>

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


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

function foo() {
  const exVar = 100;

  bar();
}

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

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

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

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

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

function outer() {
    const y = 2;

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

const result = outer();

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

function outer() {
    const y = 2;

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

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

function outer() {
    const y = 2;

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

const result = outer();

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

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

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

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

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

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

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

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

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

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

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

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

  return Company;
})();

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

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

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

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

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



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

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



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

```js
// webpack.config.js

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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



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

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

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

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

  $mainContainer.appendChild($clone);
&lt;/script&gt;
</code></pre>
<p><strong>Template Element와 Custom Elements를 컴포넌트로 구성하려면 HTML, JS 두 개의 파일이 필요하다는 단점으로 인해 현재 Template Element의 Web Component에서의 영향력이 떨어지고 있는 상태입니다.</strong></p>
<p><br><br></p>
<h2 id="🔑-글을-마치며">🔑 글을 마치며...</h2>
<hr>
<p>글을 마치기 전에 작성하지 않은 부분에 대해서 말씀드리겠습니다. W3C 표준 명세에서는 Web Component 스펙으로 3가지가 아닌 4가지가 공표하고 있습니다. HTML Import라는 하나의 표준이 더 존재하지만 제가 포스터에서 작성하지 않은 이유가 있습니다.</p>
<ol>
<li>Firefox에서 현재 HTML Import를 지원하지 않고 있으며 다른 브라우저들도 구현에 나서지 않고 있기 때문입니다.</li>
<li>위에서 말했던 Template Element의 단점인 HTML, JS 파일을 각각 필요한 단점을 해결하기 위해서 HTML Import가 탄생했지만 이미 ES Module 표준이 존재하고 있으며, 많은 브라우저에서 지원 및 개발 중인 단계입니다.</li>
<li>제 생각이지만 HTML Import 사용을 위한 <code>&lt;link&gt;</code> 가 CSS 적용을 위한 <code>&lt;link&gt;</code> 유사하며, 차라리 EM Module을 사용하는 방식이 깔끔하다고 생각합니다.</li>
<li>HTTP/2를 사용하면 여러 개 파일을 빠르게 받을 수 있다고 해도, 여전히 번들링한 단일 파일을 받는 것이 빠릅니다.</li>
</ol>
<p>이러한 단점으로 인해서 HTML Import는 MDN에서도 보이지 않고 있으며 다른 대책으로 Web Component를 만드는 현상이 발생하고 있습니다. (lit-html을 사용하는 경우가 많습니다).</p>
<p>현재 Web Component는 여러 충돌이 있었지만 빠르게 발전하고 있으며 Google에서는 polymer라는 라이브러리도 발표하며 많은 사람에게 영향을 주고 있습니다. 10년 후에는 React, Vue, Angular 등의 프레임워크(라이브러리)를 사용하지 않으며, 순수 JS로 코딩을 하는 날이 오지 않을까 하는 제 생각이 반영되어 블로그를 작성하게 되었습니다. (미리미리 준비하는 자세!!)</p>
<p>또한 현재 대부분의 개발은 React를 사용하고 있으며 React의 Virtual DOM 개념에 대해서 모호하게 알고 있다고 생각하고 있었습니다. 이번 Web Component 블로그를 작성하면서 React Component 및 Virtual DOM이 어떻게 만들어지고 있는지에 대해서 생각을 정리할 수 있는 시간이었습니다. </p>
<h2 id="참고-자료">참고 자료</h2>
<hr>
<p><a href="https://ui.toast.com/weekly-pick/ko_20170428">Web Components: Keep calm and UseThePlatform</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/Web_Components">Web Component in MDN</a></p>
<p><a href="https://haeminmoon.github.io/2020/02/08/web-component/">Web Component</a></p>
<p><a href="https://frontsom.tistory.com/5">웹 컴포넌트 (Web Component)</a></p>
]]></description>
        </item>
    </channel>
</rss>