Next.js 미드레벨 코딩 면접

Next.js 미드레벨 (3~7년) 코딩 · 알고리즘 5문항 조회수 89 · 2026-06-29 (월) 11:11:08
1 렌더링 최적화
Medium

Q. Next.js에서 대량의 데이터를 렌더링하는 리스트 컴포넌트가 있을 때, 각 아이템이 복잡한 계산을 포함하고 있어 리렌더링 시 성능 문제가 발생합니다. key 속성의 올바른 사용법과 안티패턴을 설명하고, index를 key로 사용했을 때 발생할 수 있는 문제점을 구체적으로 설명해주세요. 또한 아이템 순서가 변경되거나 삽입/삭제가 빈번한 리스트에서 권장되는 key 전략을 제시해주세요.

A. 모범답안

key는 React가 Virtual DOM 비교 시 어떤 아이템이 변경/추가/삭제되었는지 식별하는 데 사용됩니다. index를 key로 사용하면 아이템 순서가 변경될 때 React가 잘못된 DOM 노드를 재사용하여 상태가 꼬이거나 불필요한 리렌더링이 발생합니다. 특히 입력 필드가 있는 리스트에서는 잘못된 값이 표시될 수 있습니다. 안정적인 고유 ID(데이터베이스 ID, UUID 등)를 key로 사용해야 하며, 없는 경우 데이터 생성 시점에 고유 ID를 생성하여 할당하는 것이 권장됩니다. key가 변경되면 컴포넌트가 완전히 언마운트/마운트되므로 의도적인 리셋이 필요한 경우에만 key를 변경해야 합니다.

핵심 포인트
  • • index를 key로 사용 시 순서 변경 시 상태 불일치와 성능 저하 발생
  • • 안정적이고 고유한 ID를 key로 사용해야 React가 정확히 아이템 추적 가능
  • • key 변경 시 컴포넌트 완전 재마운트되므로 신중하게 사용
  • • 입력 필드나 상태를 가진 리스트에서 특히 중요
Follow-up 질문

만약 서버에서 받은 데이터에 고유 ID가 없고, 클라이언트에서 UUID를 생성하여 key로 사용한다면 SSR과 CSR 간의 hydration mismatch 문제가 발생할 수 있습니다. 이를 어떻게 해결하시겠습니까?

2 메모리 관리
Hard

Q. Next.js 클라이언트 컴포넌트에서 setInterval, setTimeout, 이벤트 리스너, WebSocket 연결 등을 사용할 때 발생할 수 있는 메모리 누수 패턴을 설명하고, useEffect의 cleanup 함수를 활용한 올바른 정리 방법을 설명해주세요. 또한 React 18의 Strict Mode에서 useEffect가 두 번 실행되는 이유와 이것이 메모리 누수 디버깅에 어떻게 도움이 되는지 설명해주세요.

A. 모범답안

컴포넌트가 언마운트되었는데도 타이머, 이벤트 리스너, 구독 등이 계속 실행되면 메모리 누수가 발생합니다. useEffect의 cleanup 함수에서 clearInterval, clearTimeout, removeEventListener, WebSocket close 등을 호출하여 리소스를 정리해야 합니다. React 18 Strict Mode는 개발 환경에서 컴포넌트를 마운트-언마운트-재마운트하여 cleanup 로직이 제대로 구현되었는지 검증합니다. cleanup이 없으면 재마운트 시 리스너가 중복 등록되어 문제가 즉시 드러나므로 개발 단계에서 메모리 누수를 조기에 발견할 수 있습니다. 또한 클로저로 인해 오래된 state를 참조하는 문제도 방지해야 하므로 ref나 함수형 업데이트를 활용해야 합니다.

핵심 포인트
  • • useEffect cleanup 함수에서 모든 비동기 작업, 구독, 리스너 정리 필수
  • • Strict Mode의 이중 실행은 cleanup 로직 검증을 위한 의도된 동작
  • • 클로저로 인한 stale state 참조 문제 주의
  • • 타이머 ID, 구독 객체 등은 ref에 저장하여 cleanup에서 접근
Follow-up 질문

컴포넌트가 빠르게 마운트/언마운트를 반복하는 상황에서 API 요청을 취소하지 않으면 어떤 문제가 발생할 수 있으며, AbortController를 사용한 요청 취소 패턴을 어떻게 구현하시겠습니까?

3 상태 관리 패턴
Medium

Q. Next.js 애플리케이션에서 여러 컴포넌트가 공유하는 복잡한 상태를 useReducer로 관리할 때의 장점을 설명하고, useState와 비교했을 때 어떤 상황에서 useReducer가 더 적합한지 설명해주세요. 또한 reducer 함수를 작성할 때 불변성을 유지해야 하는 이유와 Immer 라이브러리 같은 도구를 사용하는 이점을 설명해주세요.

A. 모범답안

useReducer는 다음 상태가 이전 상태에 의존하는 복잡한 로직, 여러 하위 값을 포함하는 상태 객체, 상태 업데이트 로직을 컴포넌트에서 분리하고 싶을 때 적합합니다. 여러 setState를 연속 호출하는 대신 단일 dispatch로 상태 전환을 명확히 표현할 수 있고, 액션 타입으로 의도를 명시적으로 드러낼 수 있습니다. reducer는 순수 함수여야 하므로 불변성을 유지해야 하는데, 이는 React가 참조 비교로 변경을 감지하고 이전 상태 추적을 가능하게 하기 때문입니다. Immer를 사용하면 draft 객체를 직접 수정하는 방식으로 코드를 작성해도 내부적으로 불변 업데이트를 수행하여 코드 가독성과 유지보수성이 향상됩니다.

핵심 포인트
  • • 복잡한 상태 로직, 다중 하위 값, 상태 간 의존성이 있을 때 useReducer 적합
  • • 액션 타입으로 상태 변경 의도를 명시적으로 표현 가능
  • • 불변성 유지는 React의 변경 감지와 시간 여행 디버깅에 필수
  • • Immer는 mutable 스타일 코드로 immutable 업데이트 구현
Follow-up 질문

useReducer와 Context API를 함께 사용하여 전역 상태 관리를 구현할 때, 성능 최적화를 위해 Context를 여러 개로 분리하는 전략과 그 이유를 설명해주세요.

4 비동기 처리
Hard

Q. Next.js 컴포넌트에서 여러 개의 독립적인 API 호출을 병렬로 처리하되, 하나의 요청이 실패해도 나머지 성공한 데이터는 표시하고 싶습니다. Promise.all, Promise.allSettled, Promise.race의 차이점과 각각의 적합한 사용 사례를 설명하고, 부분 실패 시나리오를 우아하게 처리하는 에러 핸들링 패턴을 제시해주세요.

A. 모범답안

Promise.all은 모든 Promise가 성공해야 resolve되며 하나라도 실패하면 즉시 reject되므로 all-or-nothing 시나리오에 적합합니다. Promise.allSettled는 모든 Promise의 성공/실패 여부와 관계없이 모든 결과를 반환하므로 부분 실패를 허용하는 경우에 적합합니다. Promise.race는 가장 먼저 완료되는 Promise의 결과를 반환하므로 타임아웃 구현이나 여러 소스 중 가장 빠른 응답 사용 시 유용합니다. 부분 실패 처리는 Promise.allSettled로 모든 결과를 받은 후 status로 성공/실패를 구분하고, 성공한 데이터는 표시하면서 실패한 항목은 fallback UI나 재시도 버튼을 제공하는 방식이 권장됩니다.

핵심 포인트
  • • Promise.all은 하나 실패 시 전체 실패, all-or-nothing 상황에 사용
  • • Promise.allSettled는 모든 결과 반환, 부분 실패 허용 시나리오에 적합
  • • Promise.race는 가장 빠른 결과 사용, 타임아웃 패턴에 유용
  • • 부분 실패 시 성공 데이터 표시 + 실패 항목 fallback 제공
Follow-up 질문

API 요청에 타임아웃을 구현하기 위해 Promise.race와 AbortController를 함께 사용하는 패턴을 설명하고, 타임아웃 후 실제 요청을 취소하는 것이 왜 중요한지 설명해주세요.

5 폼 처리 최적화
Medium

Q. Next.js에서 대규모 폼(수십 개의 입력 필드)을 다룰 때, 각 입력마다 개별 useState를 사용하는 방식과 단일 객체 상태로 관리하는 방식의 장단점을 비교하고, 제어 컴포넌트와 비제어 컴포넌트의 차이점을 설명해주세요. 또한 입력 필드가 많을 때 성능 최적화를 위해 어떤 접근 방식을 선택해야 하는지 설명해주세요.

A. 모범답안

개별 useState는 각 필드가 독립적으로 리렌더링되지만 코드가 장황해지고, 단일 객체 상태는 코드가 간결하지만 한 필드 변경 시 전체 폼이 리렌더링될 수 있습니다. 제어 컴포넌트는 React 상태로 입력값을 관리하여 실시간 유효성 검사와 조건부 렌더링이 쉽지만 매 입력마다 리렌더링이 발생합니다. 비제어 컴포넌트는 ref로 DOM에 직접 접근하여 성능이 좋지만 실시간 피드백이 어렵습니다. 대규모 폼에서는 react-hook-form 같은 라이브러리를 사용하여 비제어 컴포넌트 기반으로 성능을 최적화하면서도 유효성 검사와 에러 처리를 효율적으로 할 수 있습니다. 필드별로 격리된 리렌더링과 필요한 경우에만 watch로 값을 구독하는 방식이 권장됩니다.

핵심 포인트
  • • 제어 컴포넌트는 실시간 검증 쉽지만 리렌더링 많음, 비제어는 성능 좋지만 제어 어려움
  • • 대규모 폼에서 개별 useState는 관리 복잡, 단일 객체는 불필요한 리렌더링 유발
  • • react-hook-form 등 라이브러리로 비제어 기반 최적화 + 유효성 검사 가능
  • • 필드 격리와 선택적 구독으로 성능 최적화
Follow-up 질문

폼 제출 중 사용자가 뒤로 가기 버튼을 누르거나 페이지를 새로고침하려 할 때, 변경 사항이 손실될 수 있음을 경고하는 기능을 어떻게 구현하시겠습니까? beforeunload 이벤트와 Next.js 라우터 이벤트를 활용한 방법을 설명해주세요.

댓글 0

로그인 후 댓글을 작성할 수 있습니다.

아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!