<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>CH's 개발 공부 일지</title>
    <link>https://develop-ch94.tistory.com/</link>
    <description>개발 공부 및 개발 일지</description>
    <language>ko</language>
    <pubDate>Sun, 1 Nov 2026 17:28:55 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>develop-ch94</managingEditor>
    <image>
      <title>CH's 개발 공부 일지</title>
      <url>https://tistory1.daumcdn.net/tistory/8758806/attach/26e415d47b1549f6ba3492d89305a0f0</url>
      <link>https://develop-ch94.tistory.com</link>
    </image>
    <item>
      <title>8. AWS&amp;middot;데이터베이스&amp;middot;문제 해결</title>
      <link>https://develop-ch94.tistory.com/243</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q43. AWS로 웹 서비스를 배포한다면 어떤 구조를 생각하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;예를 들어 애플리케이션은 EC2나 컨테이너 환경에서 실행하고, 데이터베이스는 RDS, 파일은 S3, 정적 파일 전달은 CloudFront를 고려할 수 있습니다. 로그와 지표는 CloudWatch에서 확인합니다. 처음부터 모든 서비스를 도입하기보다는 트래픽, 운영 부담, 비용에 맞는 구조를 선택하겠습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안에 관한 꼬리 질문에는 다음과 같이 답할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;애플리케이션의 AWS 접근은 가능한 경우 IAM Role과 임시 자격 증명을 사용하고 필요한 권한만 부여합니다. 비밀값을 소스코드에 넣지 않고, 데이터베이스 접근 범위도 제한합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q44. 데이터베이스 인덱스와 트랜잭션은 왜 필요한가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;인덱스는 조회할 데이터를 빠르게 찾는 데 도움을 주지만 저장 공간과 쓰기 비용이 증가합니다. 자주 사용하는 조회 조건과 정렬, 실행 계획을 보고 적용합니다. 트랜잭션은 주문 생성과 재고 차감처럼 여러 변경을 하나의 작업 단위로 처리하며, 동시 요청에서는 격리 수준이나 잠금, 조건부 갱신도 고려해야 합니다&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q45. &quot;게시글 수정 후 화면이 안 바뀐다&quot; 또는 &quot;서비스가 느리가&quot;면 어떻게 분석하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;먼저 현상을 재현하고, 어느 단계에서 문제가 생기는지 구분합니다. 수정 문제라면 요청 payload, 서버 응답, DB 반영 여부를 확인한 뒤 React Query의 키와 캐시 갱신, Next.js 캐시, 화면 상태를 점검합니다. 성능 문제라면 브라우저 네트워크, 서버 처리 시간, DB 쿼리와 자원 사용량을 측정해 병목을 찾고, 수정 전후 결과를 비교합니다.&lt;/blockquote&gt;</description>
      <category>기술면접 대비</category>
      <author>develop-ch94</author>
      <guid isPermaLink="true">https://develop-ch94.tistory.com/243</guid>
      <comments>https://develop-ch94.tistory.com/243#entry243comment</comments>
      <pubDate>Mon, 19 Oct 2026 17:42:06 +0900</pubDate>
    </item>
    <item>
      <title>7. HTML&amp;middot;CSS</title>
      <link>https://develop-ch94.tistory.com/242</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q41. 시맨틱 HTML과 접근성은 왜 중요한가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;요소의 의미가 구조에 드러나면 유지보수뿐 아니라 스크린리더와 키보드 사용에도 도움이 됩니다. 동작을 실행하는 요소에는 button, 이동에는 링크를 사용하고, 입력에는 적절한 label을 연결합니다. 화면을 마우스로 사용할 수 있는지뿐 아니라 키보드와 보조 기술로도 사용할 수 있는지 확인합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q42. CSS 박스 모델과 Flexbox&amp;middot;Grid를 설명해주세요.&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;박스 모델은 content, padding, border, margin으로 구성됩니다. box-sizing: border-box를 사용하면 지정한 너비와 높이에 padding과 border가 포함됩니다. Flexbox는 한 방향의 배치와 정렬에, Grid는 행과 열을 함께 구성하는 배치에 적합하며, 실제 레이아웃에 맞춰 선택합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼬리 질문으로 반응형 구현을 물으면 고정 너비만 사용하지 않고, 유연한 크기&amp;middot;미디어 쿼리&amp;middot;콘텐츠에 맞는 분기점을 활용한다고 설명하세요.&lt;/p&gt;</description>
      <category>기술면접 대비</category>
      <author>develop-ch94</author>
      <guid isPermaLink="true">https://develop-ch94.tistory.com/242</guid>
      <comments>https://develop-ch94.tistory.com/242#entry242comment</comments>
      <pubDate>Mon, 19 Oct 2026 17:39:00 +0900</pubDate>
    </item>
    <item>
      <title>6. Node.js&amp;middot;Express&amp;middot;HTTP&amp;middot;보안</title>
      <link>https://develop-ch94.tistory.com/241</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q34. Node.js는 싱글 스레드인데 어떻게 여러 요청을 처리하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;일반적인 Node.js 프로세스의 JavaScript 실행은 하나의 이벤트 루프 스레드에서 이루어지지만, 비동기 I/O는 운영체제나 작업 종류에 따라 libuv의 워커 풀 등을 활용합니다. I/O 완료를 기다리는 동안 다른 요청을 처리할 수 있습니다. 다만 긴 CPU 연산은 이벤트 루프를 막아 다른 요청의 응답도 늦출 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼬리 질문으로 CPU 작업 처리 방법을 물으면 worker_threads, 별도 작업 프로세스, 작업 큐 등을 설명할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q35. Express 미들웨어란 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;요청과 응답 처리 흐름 사이에서 실행되는 함수입니다. 인증, 요청 로깅, 본문 파싱 같은 공통 처리를 수행하며, 응답을 종료하거나 next() 로 다음 처리에 넘깁니다. 등록 순서가 실행 흐름에 영향을 주기 때문에 순서가 중요합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q36. Express에서 오류 처리는 어떻게 하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;오류 응답 형식을 일관되게 유지하기 위해 중앙 오류 처리 미들웨어를 둡니다. 예상 가능한 입력 오류와 내부 서버 오류를 구분하고, 내부 오류의 상세 내용은 서버 로그에 남깁니다. 오류 미들웨어는 (err, req, res, next) 형태입니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q37. REST API를 설계할 때 무엇을 고려하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;br /&gt;URL은 자원을 중심으로 구성하고, 동작은 HTTP 메서드로 표현합니다. 응답 상태 코드, 오류 형식, 입력 검증, 페이지네이션 정책을 일관되게 정합니다. 예를 들어 게시글 조회는 GET /posts/:id, 생성은 POST /posts로 표현할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;함께 준비할 개념은 &lt;/span&gt;&lt;b&gt;&lt;span&gt;멱등성&lt;/span&gt;&lt;/b&gt;&lt;span&gt;입니다. 동일한 요청을 여러 번 수행해도 서버의 의도된 최종 효과가 같다는 의미이며, 응답 내용까지 항상 같아야 하는 것은 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q38. 인증과 인가의 차이는 무엇인가요? 세션과 JWT는 어떻게 다른가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;인증은 사용자가 누구인지 확인하는 것이고, 인가는 해당 작업을 수행할 권한이 있는지 확인하는 것입니다. 서버 세션 방식은 일반적으로 서버에 로그인 상태를 저장하고 세션 ID로 연결합니다. JWT 방식은 토큰의 서명과 만료, 발급자 등을 검증하며, 로그아웃이나 즉시 폐기를 처리하려면 별도 전략이 필요합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;추가로 알아둘 점:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;일반적인 서명된 JWT는 payload가 암호화된 것이 아니므로 비밀 정보를 넣지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;JWT도 쿠키에 저장할 수 있으므로, &amp;ldquo;세션은 쿠키이고 JWT는 로컬 스토리지&amp;rdquo;라는 구분은 부정확합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;로그인 여부와 별개로 &lt;/span&gt;&lt;b&gt;&lt;span&gt;게시글 소유자 등 자원별 권한을 서버에서 확인&lt;/span&gt;&lt;/b&gt;&lt;span&gt;해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q39. CORS란 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;브라우저가 다른 출처의 응답을 JavaScript에서 읽을 수 있는지 제어하는 정책입니다. 서버는 허용할 출처와 메서드 등을 응답 헤더로 지정합니다. CORS는 인증이나 인가를 대신하지 않으며, 브라우저 밖의 클라이언트가 API를 호출하는 것까지 차단하는 장치는 아닙니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q40. XSS, CSRF, SQL Injection은 어떻게 방어하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;XSS는 신뢰할 수 없는 입력이 페이지에서 스크립트로 실행되는 문제이므로, 출력 문맥에 맞는 이스케이프와 필요한 HTML 정제를 적용합니다. CSRF는 브라우저가 인증 정보를 자동 전송하는 특성을 악용하므로 인증 방식에 맞춰 CSRF 토큰, SameSite 쿠키, 출처 검증 등을 적용합니다. SQL Injection은 문자열로 쿼리를 조합하지 않고 파라미터 바인딩 등을 사용해 방어합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;React를 사용해도 &lt;/span&gt;dangerouslySetInnerHTML&lt;span&gt;이나 외부 DOM 조작 등을 통해 XSS가 발생할 수 있다는 점&lt;/span&gt;&lt;/p&gt;</description>
      <category>기술면접 대비</category>
      <author>develop-ch94</author>
      <guid isPermaLink="true">https://develop-ch94.tistory.com/241</guid>
      <comments>https://develop-ch94.tistory.com/241#entry241comment</comments>
      <pubDate>Fri, 16 Oct 2026 17:01:05 +0900</pubDate>
    </item>
    <item>
      <title>5. Next.js</title>
      <link>https://develop-ch94.tistory.com/240</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q29. React와 Next.js의 차이는 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;React는 UI를 구성하는 라이브러리이고, Next.js는 React를 기반으로 라우팅, 서버 렌더링, 서버 측 기능 등 애플리케이션 구성을 제공하는 프레임워크입니다. 프로젝트에서 필요한 렌더링 방식과 서버 기능, 배포 환경을 기준으로 Next.js 사용 여부를 판단합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q30. CSR, SSR, SSG, ISR의 차이는 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;CSR은 브라우저에서 JavaScript를 실행해 UI를 구성하는 방식입니다. SSR은 요청 시 서버에서 HTML을 생성하고, SSG는 빌드 시 미리 생성합니다. ISR은 미리 생성한 결과를 재검증 정책에 따라 갱신합니다. 데이터 변경 빈도, 개인화 여부, 초기 로딩 요구에 맞춰 선택합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 공개 소개 페이지는 정적 생성, 사용자별 화면은 요청 시 처리, 실시간 상호작용은 클라이언트 데이터 조회를 조합할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q31. Server Component와 Client Component는 어떻게 다른가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Server Component는 서버에서 실행되며 데이터 접근이나 서버 전용 로직에 적합합니다. Client Component는 상태, 이벤트 핸들러, 브라우저 API 등 상호작용이 필요한 부분에 사용합니다. App Router의 페이지와 레이아웃은 기본적으로 Server Component이고, 필요한 경계에 'use client'를 선언합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은&lt;b&gt; Client Component도 초기 페이지 로드에서는 서버에서 HTML을 미리 렌더링할 수 있다는 것&lt;/b&gt;입니다. &quot;Client Component는 항상 브라우저에서만 렌더링된다&quot;는 답변은 부정확합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q32. 하이드레이션이란 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;서버에서 생성된 HTML에 클라이언트의 React가 연결되어 이벤트와 상태 기반 상호작용이 가능해지는 과정입니다. 서버와 브라우저의 초기 렌더 결과가 다르면 hydration mismatch가 발생할 수 있습니다. 시간, 난수, 브라우저 저장소처럼 환경에 따라 달라지는 값의 처리에 주의합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q33. Next.js의 캐싱과 React Query의 캐싱은 같은가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;서로 다른 계층의 캐시입니다. Next.js는 서버 데이터나 렌더링 결과 등의 캐시를 관리하고, React Query는 QueryClient의 서버 상태 캐시를 관리합니다. 데이터를 변경했을 때 어떤 계층이 화면의 데이터를 제공하는지 확인하고 해당 캐시를 갱신해야 합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js의 캐시 기본 동작은 버전과 설정에 따라 달라집니다. &lt;b&gt;&quot;fetch는 항상 기본 캐시된다&quot;처럼 단정하지 말고&lt;/b&gt;, 프로젝트에서 사용한 버전과 캐시 정책으로 설명하세요.&lt;/p&gt;</description>
      <category>기술면접 대비</category>
      <author>develop-ch94</author>
      <guid isPermaLink="true">https://develop-ch94.tistory.com/240</guid>
      <comments>https://develop-ch94.tistory.com/240#entry240comment</comments>
      <pubDate>Mon, 12 Oct 2026 17:51:34 +0900</pubDate>
    </item>
    <item>
      <title>4. React Query - TanStack Query</title>
      <link>https://develop-ch94.tistory.com/239</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q24. React Query는 왜 사용하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;서버 데이터는 로컬 UI 상태와 달리 요청 지연, 실패, 재조회, 최신성 관리가 필요합니다. React Query는 조회 결과를 캐시하고 요청 상태와 재조회 등을 관리하는 데 사용합니다. 모달 열림 여부처럼 클라이언트에서만 관리하는 상태까지 모두 React Query에 넣지는 않습니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q25. staleTime과 gcTime은 어떻게 다른가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;staleTime은 데이터를 신선하다고 판단하는 기간이고, gcTime은 사용하지 않는 쿼리가 캐시에서 제거되기까지의 시간입니다. 일반적인 클라이언트 기본값은 각각 0과 5분입니다. stale 상태가 되었다고 캐시가 즉시 삭제되거나, 그 순간 요청이 반드시 발생하는 것은 아닙니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q26. 쿼리 키는 어떻게 설계하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;조회 결과를 구분하는 값들을 쿼리 키에 포함합니다. 예를 들어 게시글 목록이 페이지와 검색어에 따라 달라지면 ['posts', { page, keyword }] 처럼 구성합니다. 데이터가 달라지는데 키가 같으면 서로 다른 조회 결과가 같은 캐시를 공유할 수 있습니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q27. 데이터를 수정한 후 목록은 어떻게 갱신하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;서버 변경이 성공하면 관련 쿼리를 무효화해 다시 조회하거나, 응답으로 받은 확실한 데이터를 캐시에 직접 반영합니다. 서버가 계산하는 필드나 여러 목록의 조건이 얽혀 있다면 재조회가 단순할 수 있고, 단일 항목 수정처럼 결과가 명확하면 캐시 업데이트를 고려합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q28. 낙관적 업데이트란 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;서버 응답을 기다리기 전에 성공을 예상해 UI를 먼저 업데이트하는 방식입니다. 좋아요처럼 즉각적인 반응이 중요한 기능에서 활용할 수 있습니다. 기존 캐시를 보관하고, 충돌할 조회를 취소하며, 실패하면 복구하고, 요청 종료 후 서버 상태와 다시 맞추는 과정이 필요합니다.&lt;/blockquote&gt;</description>
      <category>기술면접 대비</category>
      <author>develop-ch94</author>
      <guid isPermaLink="true">https://develop-ch94.tistory.com/239</guid>
      <comments>https://develop-ch94.tistory.com/239#entry239comment</comments>
      <pubDate>Mon, 12 Oct 2026 16:20:50 +0900</pubDate>
    </item>
    <item>
      <title>3. React</title>
      <link>https://develop-ch94.tistory.com/237</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q16. React 컴포넌트는 언제 다시 렌더링되나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;자신의 상태가 변경되거나, 부모가 다시 렌더링하거나, 구독한 Context 값이 변경되면 다시 렌더링될 수 있습니다. 렌더링은 다음 UI를 계산하는 과정이고, 실제 DOM 변경과는 구분됩니다. 렌더링되었다고 항상 실제 DOM이 바뀌는 것은 아닙니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q17. useState로 값을 바꿨는데 바로 출력하면 이전 값이 나오는 이유는 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;각 렌더링에서 상태는 하나의 스냅샷처럼 동작하기 때문입니다. 상태 변경 함수는 다음 렌더링을 요청하며, 현재 실행 중인 함수가 참조하는 상태 변수를 즉시 바꾸지는 않습니다. 이전 값을 기준으로 다음 값을 계산할 때는 함수형 업데이트를 사용합니다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1792133240987&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;setCount(prev =&amp;gt; prev + 1);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q18. useEffect는 언제 사용하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;컴포넌트를 외부 시스템과 동기화할 때 사용합니다. 이벤트 구독, 타이머, 외부 라이브러리 연결 등이 예입니다. props나 state로 계산할 수 있는 값은 렌더링 중 계산하고, 사용자의 클릭으로 수행할 작업은 이벤트 핸들러에서 처리하는 것이 일반적으로 적절합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q19. useEffect의 의존성 배열과 cleanup은 왜 필요한가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;의존성 배열은 Effect에서 사용하는 반응형 값이 바뀌었을 때 동기화를 다시 수행하도록 합니다. 필요한 값을 누락하면 오래된 값을 참조할 수 있습니다. cleanup은 구독이나 타이머를 해제하며, 의존성이 바뀌어 Effect가 다시 실행되기 전과 컴포넌트가 제거될 때 수행됩니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q20. 목록의 key에 배열 인덱스를 사용하면 왜 문제가 생길 수 있나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;key는 React가 항목의 정체성을 구분하는 기준입니다. 삽입&amp;middot;삭제&amp;middot;정렬이 발생하는 목록에서 인덱스를 사용하면 다른 항목을 기존 항목으로 인식해 입력값이나 내부 상태가 잘못 연결될 수 있습니다. 이런 경우 데이터의 안정적인 고유 ID를 사용합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q21. useMemo, useCallback, React.memo는 어떻게 다른가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;useMemo는 계산 결과를, useCallback은 함수 참조를 재사용하는 데 사용합니다. React.memo는 props가 같을 때 부모 렌더링에 따른 컴포넌트 렌더링을 건너뛸 수 있게 합니다. 최적화에는 비교와 관리 비용이 있으므로, 실제 병목을 확인하고 적용합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q22. 상태는 어디에 두는 것이 좋나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;해당 상태를 사용하는 범위에 맞춰 배치합니다. 한 컴포넌트만 사용하는 상태는 로컬에 두고, 여러 컴포넌트가 공유하면 가까운 공통 부모로 올립니다. URL로 공유해야 하는 검색 조건은 URL에, 서버 데이터는 React Query 같은 서버 상태 관리 도구에 두는 방식을 고려합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q23. 제어 컴포넌트와 비제어 컴포넌트의 차이는 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;br /&gt;제어 컴포넌트는 입력값을 React 상태로 관리하고 value와 onChange로 연결합니다. 비제어 컴포넌트는 DOM이 입력값을 관리하고 필요할 때 ref 등으로 읽습니다. 실시간 검증이나 다른 UI와의 동기화가 필요하면 제어 방식이 유용하며, 폼의 복잡도에 따라 선택합니다.&lt;/blockquote&gt;</description>
      <category>기술면접 대비</category>
      <author>develop-ch94</author>
      <guid isPermaLink="true">https://develop-ch94.tistory.com/237</guid>
      <comments>https://develop-ch94.tistory.com/237#entry237comment</comments>
      <pubDate>Thu, 8 Oct 2026 18:31:19 +0900</pubDate>
    </item>
    <item>
      <title>2. JavaScript&amp;middot;TypeScript</title>
      <link>https://develop-ch94.tistory.com/236</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q5. var, let, const의 차이는 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;var는 함수 스코프이고, let과 const는 블록 스코프입니다. let은 재할당이 가능하고 const는 재할당이 불가능합니다. 다만 const로 선언한 객체의 내부 속성까지 변경할 수 없는 것은 아닙니다. 기본적으로 const를 사용하고 재할당이 필요한 경우에 let을 사용합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q6. 클로저란 무엇이고 어디에 활용하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;함수가 선언된 환경의 변수를, 그 환경의 실행이 끝난 뒤에도 참조할 수 있는 특성입니다. 외부에 직접 노출하지 않을 상태를 관리하거나, 콜백에서 특정 값을 기억할 때 활용합니다. React에서도 함수가 이전 렌더링의 값을 기억하면서 발생하는 stale closure 문제를 이해하는 데 중요합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q7. 이벤트 루프를 설명해주세요.&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;JavaScript는 현재 콜 스택의 코드를 실행한 뒤, 실행 환경이 관리하는 비동기 작업의 콜백을 처리합니다. 브라우저에서는 Promise의 후속 처리 같은 마이크로태스크가 다음 일반 태스크보다 먼저 처리됩니다. 그래서 같은 흐름에서 등록한 Promise.then과 setTimeout의 실행 순서가 달라질 수 있습니다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1792132359608&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(&quot;A&quot;);

setTimeout(() =&amp;gt; console.log(&quot;B&quot;), 0);

Promise.resolve().then(() =&amp;gt; console.log(&quot;C&quot;));

console.log(&quot;D&quot;);

// 일반적인 브라우저 실행 결과: A &amp;rarr; D &amp;rarr; C &amp;rarr; B&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setTimeout(..., 0)은 &lt;b&gt;즉시 실행을 보장하는 의미가 아닙니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q8. async/await는 코드를 동기적으로 실행하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;async/await는 Promise 기반 비동기 코드를 읽기 쉽게 표현하는 문법입니다. await는 해당 함수의 후속 실행을 기다리게 하지만, 그동안 전체 JavaScript 실행을 멈추는 것은 아닙니다. 서로 독립적인 요청을 연속해서 await 하면 불필요하게 순차 실행될 수 있어 Promise.all을 고려합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q9. Promise.all과 Promise.allSettled는 어떻게 다른가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Promise.all은 모든 작업의 성공 결과가 필요할 때 사용하고, 하나라도 실패하면 반환된 Promise가 거부됩니다. Promise.allSettled는 각 작업의 성공과 실패 결과를 모두 확인해야 할 때 사용합니다. Promise.all이 실패해도 이미 시작된 다른 작업이 자동으로 취소되는 것은 아닙니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q10. 얕은 복사와 깊은 복사의 차이는 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;얕은 복사는 최상위 속성을 복사하지만, 중첩 객체는 기존 객체와 참조를 공유합니다. 따라서 전개 연산자로 객체를 복사해도 중첩 객체를 수정하면 원본에 영향을 줄 수 있습니다. React 상태를 수정할 때는 변경되는 중첩 경로까지 새 객체로 만들어야 합니다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1792132592406&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const original = { user: { name: &quot;Kim&quot; } };
const copied = { ...original };

copied.user.name = &quot;Lee&quot;;

// original.user.name도 &quot;Lee&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q11. 일반 함수와 화살표 함수의 this는 어떻게 다른가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;일반 함수의 this는 호출 방식에 따라 결정됩니다. 화살표 함수는 자신만의 this를 만들지 않고 선언된 외부 환경의 this를 사용합니다. 따라서 객체 메서드처럼 호출한 객체를 this로 사용해야 하는 상황에서는 화살표 함수 선택에 주의합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q12. TypeScript를 사용하는 이유는 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;개발 단계에서 타입 오류를 발견하고, 함수와 데이터 구조의 계약을 명확하게 표현하기 위해 사용합니다. 자동 완성과 리팩터링에도 도움이 됩니다. 다만 타입 검사만으로 외부 데이터의 실제 형식이나 모든 실행 오류를 보장할 수는 없습니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q13. any와 unknown의 차이는 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;any는 타입 검사를 우회하기 때문에 잘못된 접근도 허용될 수 있습니다. unknown은 값의 타입을 확인하기 전에는 임의로 사용할 수 없습니다. 외부에서 들어오는 데이터처럼 타입을 아직 신뢰할 수 없는 값은 unknown으로 받고 검증한 뒤 사용하는 편이 안전합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q14. 유니온 타입과 타입 좁히기는 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;br /&gt;유니온 타입은 값이 여러 타입 중 하나일 수 있음을 표현합니다. 타입 좁히기는 typeof, 속성 확인, 판별 필드 등을 사용해 해당 분기에서 가능한 타입을 구체화하는 것입니다. 예를 들어 요청 상태를 status 필드로 구분하면 성공 상태에서만 응답 데이터에 접근하도록 만들 수 있습니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q15. 제네릭은 언제 사용하나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;br /&gt;다양한 타입에 재사용하면서도 입력과 출력 사이의 타입 관계를 유지해야 할 때 사용합니다. 예를 들어 ApiResponse&amp;lt;T&amp;gt;로 공통 응답 구조를 정의하면 사용자 응답과 게시글 응답이 같은 구조를 공유하면서 각각의 데이터 타입을 유지할 수 있습니다.&lt;/blockquote&gt;</description>
      <category>기술면접 대비</category>
      <author>develop-ch94</author>
      <guid isPermaLink="true">https://develop-ch94.tistory.com/236</guid>
      <comments>https://develop-ch94.tistory.com/236#entry236comment</comments>
      <pubDate>Thu, 8 Oct 2026 18:05:38 +0900</pubDate>
    </item>
    <item>
      <title>1. C# 경력과 풀스택 전환</title>
      <link>https://develop-ch94.tistory.com/234</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q1. C# 개발을 하다가 JavaScript 기반 풀스택으로 기술을 확장한 이유는 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;기존 C# 개발 경험을 바탕으로, 사용자가 접하는 화면부터 서버와 배포까지 서비스 전체 흐름을 이해하고 구현하는 역량을 넓히고 싶었습니다. 약 10개월 동안 JavaScript와 TypeScript, React, Node.js 등을 학습했습니다. 기존 실무 경험에서 얻은 문제 해결 방식과 협업 경험을 새로운 기술 환경에서도 활용하려고 합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q2. 기존 C# 경험이 새로운 기술 스택에서 어떻게 도움이 되나요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;언어와 프레임워크가 달라도 책임 분리, 예외 처리, 데이터 모델링, 디버깅 같은 기본 원칙은 이어진다고 생각합니다. 기존에 경험한 설계나 문제 해결 방법을 활용하면서, JavaScript의 비동기 실행 방식과 React의 렌더링 모델처럼 다른 부분은 별도로 학습했습니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q3. C#과 TypeScript의 타입 시스템은 어떻게 다른가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;C#은 클래스와 인터페이스의 선언 관계가 중요한 명목적 타입 체계가 중심이고, TypeScript는 객체의 구조가 맞으면 호환되는 구조적 타입 체계가 중심입니다. 또 TypeScript의 타입 정보는 일반적으로 JavaScript로 변환될 때 제거되기 때문에, API 응답이나 사용자 입력은 런타임 검증이 따로 필요합니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q4. 10개월 학습 경험으로 실무를 수행할 수 있다고 보는 근거는 무엇인가요?&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;학습 기간만으로 실무 역량을 설명하기보다는, 직접 구현한 기능과 해결한 문제로 설명하겠습니다. 프로젝트에서 제가 담당한 범위, 기술을 선택한 이유, 발생한 오류를 분석하고 수정한 과정을 구체적으로 말씀드릴 수 있습니다. 기존 실무에서 경험한 일정 관리와 협업 방식도 함께 활용할 수 있습니다.&lt;/blockquote&gt;</description>
      <category>기술면접 대비</category>
      <author>develop-ch94</author>
      <guid isPermaLink="true">https://develop-ch94.tistory.com/234</guid>
      <comments>https://develop-ch94.tistory.com/234#entry234comment</comments>
      <pubDate>Thu, 8 Oct 2026 17:28:19 +0900</pubDate>
    </item>
    <item>
      <title>React로 데이터 다루기 - 1-2. 배열과 데이터 렌더링</title>
      <link>https://develop-ch94.tistory.com/233</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. key를 써야 하는 이유&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;(1) key가 없으면 어떻게 될까?&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1791445970332&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function FruitList() {
  const fruits = ['사과', '바나나', '오렌지'];
  
  return (
    &amp;lt;ul&amp;gt;
      {fruits.map(fruit =&amp;gt; (
        &amp;lt;li&amp;gt;{fruit}&amp;lt;/li&amp;gt;
        {/* Warning: Each child should have a unique &quot;key&quot; prop */}
      ))}
    &amp;lt;/ul&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 도구 콘솔에 빨간 경고가 뜸. 화면은 일단 그려지긴 하지만 &lt;b&gt;리스트가 변경될 때 문제가 터짐&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;(2) key가 필요한 진짜 이유&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;key가 없을 때 '딸기'를 맨 앞에 추가:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1791446083184&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;이전:                           이후:
&amp;lt;li&amp;gt;사과&amp;lt;/li&amp;gt;     &amp;rarr;    &amp;lt;li&amp;gt;딸기&amp;lt;/li&amp;gt;   (사과를 딸기로 변경)
&amp;lt;li&amp;gt;바나나&amp;lt;/li&amp;gt;   &amp;rarr;    &amp;lt;li&amp;gt;사과&amp;lt;/li&amp;gt;   (바나나를 사과로 변경)
&amp;lt;li&amp;gt;오렌지&amp;lt;/li&amp;gt;   &amp;rarr;    &amp;lt;li&amp;gt;바나나&amp;lt;/li&amp;gt; (오렌지를 바나나로 변경)
                       &amp;lt;li&amp;gt;오렌지&amp;lt;/li&amp;gt; (새로 추가)

&amp;rarr; 4번의 DOM 조작! (비효율)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;key가 있을 때:&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1791446097084&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;이전:                           이후:
                       &amp;lt;li key=&quot;strawberry&quot;&amp;gt;딸기&amp;lt;/li&amp;gt;   (새로 추가)
&amp;lt;li key=&quot;apple&quot;&amp;gt;사과&amp;lt;/li&amp;gt;   &amp;rarr;  &amp;lt;li key=&quot;apple&quot;&amp;gt;사과&amp;lt;/li&amp;gt;   (그대로)
&amp;lt;li key=&quot;banana&quot;&amp;gt;바나나&amp;lt;/li&amp;gt; &amp;rarr;  &amp;lt;li key=&quot;banana&quot;&amp;gt;바나나&amp;lt;/li&amp;gt; (그대로)
&amp;lt;li key=&quot;orange&quot;&amp;gt;오렌지&amp;lt;/li&amp;gt; &amp;rarr;  &amp;lt;li key=&quot;orange&quot;&amp;gt;오렌지&amp;lt;/li&amp;gt; (그대로)

&amp;rarr; 1번의 DOM 조작! (효율적)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;(3) key는 어떤 값을 써야 할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가장 좋은 것: 고유한 id&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1791446174918&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const todos = [
  { id: 1, text: '운동하기' },
  { id: 2, text: '책 읽기' },
  { id: 3, text: '코딩하기' },
];

{todos.map(todo =&amp;gt; (
  &amp;lt;li key={todo.id}&amp;gt;{todo.text}&amp;lt;/li&amp;gt;
))}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;차선책: index(최후의 수단)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1791446215384&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{fruits.map((fruit, index) =&amp;gt; (
  &amp;lt;li key={index}&amp;gt;{fruit}&amp;lt;/li&amp;gt;
))}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;리스트가 &lt;b&gt;정렬/필터/추가/삭제 없이 그냥 보여주기만 할 때&lt;/b&gt;는 써도 OK&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;But, &lt;b&gt;하나라도 바뀔 가능성이 있으면&lt;/b&gt; 버그의 씨앗이 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;절대 쓰면 안 되는 것&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1791446437588&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//   최악! 랜덤값은 매 렌더링마다 바뀜
&amp;lt;li key={Math.random()}&amp;gt;{item}&amp;lt;/li&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;Math.random()은 렌더링할 때마다 새 값을 만듦. 그러면 React는 &quot;매번 새로운 리스트!&quot;로 인식해서 전체를 다시 그림&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;성능 최악 + State도 다 날아감&lt;/p&gt;
&lt;pre id=&quot;code_1791446503112&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ❌ 중복 가능성 있는 값
&amp;lt;li key={item.category}&amp;gt;{item.name}&amp;lt;/li&amp;gt;
// 같은 카테고리 항목이 여러 개면 key 중복!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;(4&lt;/b&gt;&lt;b&gt;) id가 없을 땐 어떻게 할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황 1: 서버에서 이미 id를 주는 경우 &lt;span&gt;⭐(실무 90%)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1791446916168&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// API 응답에 id가 포함됨
const users = [
  { id: 'user_123', name: '홍길동' },
  { id: 'user_456', name: '김철수' },
];

{users.map(user =&amp;gt; (
  &amp;lt;div key={user.id}&amp;gt;{user.name}&amp;lt;/div&amp;gt;
))}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;상황 2: 클라이언트에서 새로 만드는 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span&gt;할 일 추가처럼 &lt;b&gt;사용자가 직접 생성하는 항목&lt;/b&gt;은 id를 직접 만들어줘야 함&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1791447035378&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function TodoList() {
  const [todos, setTodos] = useState([]);
  
  function addTodo(text) {
    const newTodo = {
      id: crypto.randomUUID(), // 고유 ID 생성
      text,
      done: false,
    };
    setTodos([...todos, newTodo]);
  }
  
  return (
    &amp;lt;ul&amp;gt;
      {todos.map(todo =&amp;gt; (
        &amp;lt;li key={todo.id}&amp;gt;{todo.text}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span&gt;id 생성 3가지 방법&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1791447158912&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 방법 1: uuid 라이브러리 ⭐ 실무 표준
import { v4 as uuidv4 } from 'uuid';
id: uuidv4()  // &quot;a3b1c2d4-...&quot;
// &amp;rarr; 환경 안 타고 가장 안정적

// 방법 2: crypto.randomUUID() (간단한 토이 프로젝트용)
id: crypto.randomUUID()
// ⚠️ HTTPS/localhost에서만 동작하니 주의

// 방법 3: Date.now() (연습용, 실무 X)
id: Date.now()
// 빠르게 쳐야 할 때는 괜찮지만, 동시에 여러 개 생성되면 중복 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;상황 3: 정말 어쩔 수 없으면 복합 키&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1791447304262&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const items = [
  { category: 'fruit', name: 'apple' },
  { category: 'fruit', name: 'banana' },
  { category: 'vegetable', name: 'carrot' },
];

{items.map(item =&amp;gt; (
  &amp;lt;div key={`${item.category}-${item.name}`}&amp;gt;
    {item.name}
  &amp;lt;/div&amp;gt;
))}
// key: &quot;fruit-apple&quot;, &quot;fruit-banana&quot;, &quot;vegetable-carrot&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 조건부 렌더링 패턴&lt;/b&gt;&lt;/h2&gt;</description>
      <category>DEV/React</category>
      <category>react</category>
      <author>develop-ch94</author>
      <guid isPermaLink="true">https://develop-ch94.tistory.com/233</guid>
      <comments>https://develop-ch94.tistory.com/233#entry233comment</comments>
      <pubDate>Thu, 8 Oct 2026 17:15:11 +0900</pubDate>
    </item>
    <item>
      <title>중급 팀 프로젝트_최애의 포토 회고</title>
      <link>https://develop-ch94.tistory.com/232</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 프로젝트 개요&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최애의 포토는 사용자가 직접 포토카드를 생성하고, 마켓플레이스에서 포토카드를 구매하거나 교환할 수 있는 포토카드 거래 플랫폼입니다.&lt;/li&gt;
&lt;li&gt;주요 기능은 랜딩 페이지, 회원가입/로그인, 마이갤러리, 포토카드 생성, 마켓플레이스, 판매 등록, 구매, 교환 제안, 알림, 랜덤 포인트 획득 등으로 구성되어 있습니다.&lt;/li&gt;
&lt;li&gt;이번 프로젝트에서는 6명의 팀원이 FE와 BE를 함께 담당하는 풀스택 방식으로 개발을 진행했으며, 저는 주로 마켓플레이스의 구매/판매/교환 흐름과 관련된 API 설계 및 연동, BE 공통 구조 정리, 배포 과정의 문제 해결을 담당했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 담당한 작업&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저는 프로젝트 초반 요구사항 분석 단계에서 유저 스토리, 소프트웨어 요구사항, ERD, Prisma Schema, API 명세서 작성을 함께 정리했습니다. 특히 포토카드 생성 수량 제한, 마이갤러리, 판매 등록, 구매, 교환 제안, 알림 정책처럼 여러 도메인이 연결되는 기능을 데이터 흐름 기준으로 구조화했습니다.&lt;/li&gt;
&lt;li&gt;BE에서는 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;Photocard&lt;/b&gt;&lt;/span&gt;, &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;Ownership&lt;/span&gt;&lt;/b&gt;, &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;ShopListing&lt;/span&gt;&lt;/b&gt;, &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;Exchange&lt;/span&gt;&lt;/b&gt; 도메인 중심으로 Router, Controller, Service, Repository 계층 구조를 잡고, 각 계층의 역할을 분리했습니다. 또한 공통 응답 처리, 에러 처리, validation middleware, asyncHandler 같은 공통 미들웨어 구조도 함께 정리했습니다.&lt;/li&gt;
&lt;li&gt;FE에서는 마켓플레이스 상세 페이지 기준으로 판매자/구매자 분기, 구매 API 연동, 판매 수정/내리기 API 연동, 교환 제안 및 승인/거절 API 연동 방식을 정리했습니다. 이후 TanStack Query 기반으로 &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;.api.js&lt;/span&gt;&lt;/b&gt;와 &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;.queries.js&lt;/span&gt;&lt;/b&gt; 역할을 분리하여 서버 상태 관리 구조를 잡았습니다.&lt;/li&gt;
&lt;li&gt;배포 단계에서는 FE는 Vercel, BE는 Render에 배포하면서 환경변수, CORS, 쿠키 인증, Prisma generate, migration, 빌드 에러 등을 해결했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 기술적 성과&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 큰 성과는 복잡한 거래 흐름을 도메인 단위로 분리해 관리할 수 있는 구조를 만든 점입니다. 포토카드 자체 정보는 &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;Photocard&lt;/span&gt;&lt;/b&gt;, 사용자의 보유 수량은 &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;Ownership&lt;/span&gt;&lt;/b&gt;, 판매 등록 정보는 &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;ShopListing&lt;/span&gt;&lt;/b&gt;, 교환 요청은 &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;Exchange&lt;/span&gt;&lt;/b&gt;로 분리하여 각 테이블과 API의 책임을 명확히 했습니다.&lt;/li&gt;
&lt;li&gt;FE에서는 TanStack Query를 도입하여 구매, 판매, 교환 같은 서버 상태 변경 작업을 &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;useMutation&lt;/span&gt;&lt;/b&gt;으로 관리하고, 성공 후 관련 query를 invalidate하는 방식으로 UI 동기화를 처리할 수 있게 했습니다. 덕분에 API 호출 코드와 화면 컴포넌트의 책임이 분리되어 유지보수성이 좋아졌습니다.&lt;/li&gt;
&lt;li&gt;또한 Next.js 환경에서 &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;NEXT_PUBLIC_API_URL&lt;/span&gt;&lt;/b&gt;, 이미지 도메인 설정, Suspense boundary, AuthProvider 기반 인증 상태 관리 등 배포 환경에서 필요한 설정을 직접 점검하고 수정했습니다.&lt;/li&gt;
&lt;li&gt;BE에서는 Render 배포 환경에 맞춰 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;npm start&lt;/b&gt;&lt;/span&gt;, &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;prisma generate&lt;/span&gt;&lt;/b&gt;, &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;prisma migrate deploy&lt;/span&gt;&lt;/b&gt;, &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;DATABASE_URL&lt;/span&gt;&lt;/b&gt;, &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;CLIENT_URL&lt;/span&gt;&lt;/b&gt;, &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;JWT_SECRET&lt;/b&gt;&lt;/span&gt; 등의 설정을 정리했고, 로컬과 배포 환경 모두에서 API가 동작할 수 있도록 CORS 설정을 개선했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 문제점 및 해결 과정&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[4-1: 배포 환경에서 API 요청이 실패하는 문제]&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포 후 프론트엔드에서 백엔드 API를 직접 호출할 때 CORS 오류가 발생했습니다. 로컬에서는 정상 동작하던 요청도 Vercel과 Render처럼 서로 다른 도메인에 배포되면 브라우저의 origin 정책에 의해 차단될 수 있었습니다.&lt;/li&gt;
&lt;li&gt;기존에는 &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;client.js&lt;/span&gt;&lt;/b&gt;에서 Render 백엔드 주소를 직접 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;BASE_URL&lt;/b&gt;&lt;/span&gt;로 사용했지만, 이후 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;BASE_URL&lt;/b&gt;&lt;/span&gt;을 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;/api&lt;/b&gt;&lt;/span&gt;로 변경하여 Next.js 내부 API 경로를 경유하도록 수정했습니다.&lt;/li&gt;
&lt;li&gt;이를 통해 프론트엔드는 동일 origin인 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;/api&lt;/b&gt;&lt;/span&gt;로 요청을 보내고, 내부 프록시를 통해 백엔드와 통신하게 되었습니다. 그 결과 CORS 문제를 줄이고 배포 환경에서도 API 요청이 정상적으로 동작하도록 개선했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[4-2: 로그인 후 인증 상태가 UI에 반영되지 않는 문제]&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인에 성공했음에도 GNB에는 계속 로그인/회원가입 버튼이 표시되는 문제가 있었습니다. 원인은 GNB가 실제 로그인 사용자 정보를 사용하지 않고 더미 유저 데이터를 기준으로 렌더링하고 있었기 때문입니다.&lt;/li&gt;
&lt;li&gt;이를 해결하기 위해 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;AuthProvider&lt;/b&gt;&lt;/span&gt;에서 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;/auth/me&lt;/b&gt;&lt;/span&gt; 요청을 통해 현재 로그인한 사용자 정보를 가져오고, GNB는 user, isLoggedIn, isPending 상태를 기준으로 UI를 렌더링하도록 수정했습니다.&lt;/li&gt;
&lt;li&gt;이후 로그인 상태에서는 닉네임, 포인트, 로그아웃 버튼이 표시되고, 비로그인 상태에서는 로그인/회원가입 버튼이 표시되도록 개선했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[4-3: Vercel 빌드 시 import 경로 오류 발생]&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬에서는 정상적으로 실행되던 코드가 Vercel 배포 과정에서 빌드 실패가 발생했습니다. 원인은 컴포넌트 import 경로의 대소문자가 실제 폴더명과 일치하지 않았기 때문입니다.&lt;/li&gt;
&lt;li&gt;macOS 환경에서는 파일 경로의 대소문자 차이가 크게 드러나지 않을 수 있지만, Vercel의 Linux 환경에서는 대소문자를 엄격하게 구분합니다. 따라서 실제 폴더명과 import 경로를 정확히 일치시키는 방식으로 문제를 해결했습니다.&lt;/li&gt;
&lt;li&gt;이 과정을 통해 배포 환경에서는 파일명, 폴더명, import 경로의 대소문자를 반드시 일치시켜야 한다는 점을 확인했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[4-4: Prisma Client가 초기화되지 않는 문제]&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Render 배포 후 서버 실행 중 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;@prisma/client did not initialize yet&lt;/b&gt;&lt;/span&gt; 오류가 발생했습니다. 원인은 배포 환경에서 Prisma Client 생성 과정이 실행되지 않았기 때문입니다.&lt;/li&gt;
&lt;li&gt;이를 해결하기 위해 Render의 Build Command에 다음 명령어를 포함했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;verilog&quot;&gt;&lt;code&gt;npm install &amp;amp;&amp;amp; npx prisma generate &amp;amp;&amp;amp; npx prisma migrate deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;prisma generate&lt;/b&gt;&lt;/span&gt;를 통해 Prisma Client를 생성하고, &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;prisma migrate deploy&lt;/b&gt;&lt;/span&gt;를 통해 배포 환경 DB에 migration을 반영하도록 수정했습니다. 이후 배포 환경에서도 Prisma Client가 정상적으로 동작했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[4-5: 환경변수 및 Secret 관리 문제]&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.env.local에 GitHub Token, Google OAuth Secret 같은 민감 정보가 포함된 상태로 커밋되어 GitHub Push Protection에 의해 push가 차단되는 문제가 있었습니다.&lt;/li&gt;
&lt;li&gt;단순히 파일을 삭제하는 것만으로는 이미 커밋 히스토리에 secret이 남아 있기 때문에 문제가 해결되지 않았습니다. 해당 커밋을 정리하고, .env.local은 git에 올라가지 않도록 관리했습니다.&lt;/li&gt;
&lt;li&gt;이후 팀원들과 공유가 필요한 환경변수는 실제 값이 없는 .env.example 형태로 정리하는 방향으로 개선했습니다. 이를 통해 민감 정보가 저장소에 노출되는 위험을 줄였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 협업 및 피드백&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트 초반에는 기능 범위가 넓고 요구사항이 복잡해서 담당 파트를 나누는 데 어려움이 있었습니다. 이를 해결하기 위해 페이지와 기능을 기준으로 팀원별 담당 범위를 다시 정리했습니다.&lt;/li&gt;
&lt;li&gt;저는 마켓플레이스의 구매/판매/교환 흐름을 맡은 팀원들과 API 명세를 맞추고, 프론트엔드에서 필요한 요청/응답 데이터 구조를 백엔드 구현과 계속 비교했습니다. 특히 교환 제안, 교환 승인/거절, 판매 내리기처럼 상태 변경이 많은 기능은 FE와 BE의 책임을 명확히 나누는 것이 중요했습니다.&lt;/li&gt;
&lt;li&gt;또한 팀원들이 작업한 컴포넌트를 최대한 건드리지 않고, 기존 컴포넌트 구조 위에 API 연동 로직을 얹는 방향으로 작업했습니다. 이를 통해 충돌을 줄이고, 각자 맡은 UI 작업을 유지하면서 기능을 연결할 수 있도록 했습니다.&lt;/li&gt;
&lt;li&gt;코드 구조에 대해서도 피드백을 주고받았습니다. 예를 들어 validation middleware에서 body, params, query를 각각 따로 검사하던 방식은 휴먼 에러 가능성이 있어 공통 schema 기반으로 통합하는 방향을 논의했습니다. 이런 피드백을 반영하면서 팀 전체가 사용할 수 있는 일관된 백엔드 구조를 만들어갔습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 향후 개선 사항 및 제안&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 구조는 기본적인 기능 구현과 배포까지는 가능하지만, 앞으로 안정성을 높이기 위해 몇 가지 개선이 필요합니다.&lt;/li&gt;
&lt;li&gt;먼저 인증 구조를 더 명확히 정리할 필요가 있습니다. 현재는 쿠키 기반 인증을 사용하고 있기 때문에 FE/BE 도메인이 다른 배포 환경에서 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;credentials: &quot;include&quot;&lt;/b&gt;&lt;/span&gt;, CORS, cookie option 설정이 매우 중요합니다. 추후에는 refresh token 정책, access token 만료 처리, 로그인 유지 전략까지 함께 정리하면 더 안정적인 서비스가 될 것 같습니다.&lt;/li&gt;
&lt;li&gt;두 번째로 에러 응답 형식을 더 일관되게 관리할 필요가 있습니다. AppError, ERROR_CODE, successResponse 구조는 이미 잡혀 있으므로, 모든 도메인에서 동일한 응답 형식을 사용하도록 통일하면 FE에서 에러 처리하기 훨씬 쉬워집니다.&lt;/li&gt;
&lt;li&gt;세 번째로 API 테스트 환경이 필요합니다. 구매, 판매, 교환은 데이터 상태가 계속 바뀌는 기능이기 때문에 seed 데이터만으로는 테스트가 어려운 경우가 있습니다. Postman 또는 Swagger 예시 요청을 정리하고, 테스트용 계정/포토카드/판매글/교환 제안을 미리 구성하면 QA 속도가 빨라질 것입니다.&lt;/li&gt;
&lt;li&gt;마지막으로 배포 환경 문서화가 필요합니다. Vercel과 Render에 어떤 환경변수를 넣어야 하는지, migration은 언제 실행해야 하는지, CORS에는 어떤 URL을 넣어야 하는지 등을 README에 정리하면 팀원들이 같은 문제를 반복해서 겪지 않을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>코드잇 스프린트 FS 13기/중급 프로젝트(최애의 포토)</category>
      <author>develop-ch94</author>
      <guid isPermaLink="true">https://develop-ch94.tistory.com/232</guid>
      <comments>https://develop-ch94.tistory.com/232#entry232comment</comments>
      <pubDate>Wed, 7 Oct 2026 21:07:04 +0900</pubDate>
    </item>
  </channel>
</rss>