최종 프로젝트
자율 프로젝트의 한 사용자 흐름을 정하고, 정상·실패 상태와 README·배포·회고 기록으로 완료 여부를 판단합니다.
이 글에서는 만들고 싶은 앱의 사용자 흐름 하나를 골라, 어디까지 만들면 첫 버전을 마쳤다고 할지 정합니다. 화면을 구현한 뒤에는 정상과 실패 상태, 시작 방법을 남겨 다른 사람도 결과를 확인할 수 있게 합니다.
최종 프로젝트의 목표
이 최종 프로젝트는 특정 기능을 강제로 요구하지 않습니다. 주제와 기능은 스스로 정하되, 첫 버전에는 고른 흐름에 필요한 도구만 씁니다. 여러 페이지나 외부 API, 별도의 상태 관리 라이브러리는 앱이 필요로 할 때 더해도 됩니다.
먼저 만들고 싶은 앱의 주제와 주요 기능을 스스로 정의하고, Vite(권장)나 선호하는 다른 방식으로 프로젝트를 시작합니다. 앱의 구조를 컴포넌트 단위로 나누어 설계하고 구현하되, 지금 화면에 필요한 state부터 정하고 외부 연결이나 상태 공유가 필요할 때 그에 맞는 훅을 고르며, 배운 훅을 모두 쓰는 것이 목표는 아닙니다. 상태 관리는 앱의 복잡도에 따라 useState, useReducer, Context API 가운데 알맞은 방법을 고르고, 필요하면 Redux나 Zustand 같은 외부 라이브러리를 써도 됩니다.
여러 화면의 주소가 필요하다면 6장에서 쓴 react-router로 페이지 이동을 구현하며, 한 화면 안에서 끝나는 앱에는 필수 조건이 아닙니다. 외부 데이터가 필요한 앱이라면 fetch나 Axios로 요청하고 로딩과 빈 결과, 오류를 함께 보여 주며, 로컬 데이터만 쓰는 앱은 그 데이터의 변경과 저장 흐름을 확인합니다. 느린 동작이 있다면 React Developer Tools의 Profiler로 비용을 먼저 확인하고, React.memo, useCallback, useMemo는 확인한 원인에 맞을 때 적용해 변경 전후를 비교합니다.
앱을 공개하려면 빌드 결과와 실제 호스트의 동작을 확인합니다. 이 글의 dist 정적 배포 안내는 Vite SPA를 기준으로 하며, 서버 기능을 포함한 다른 프레임워크는 그에 맞는 배포 방식을 씁니다. 호스팅 서비스에 맞는 SPA 재작성도 설정하는데, _redirects는 Netlify의 방식일 뿐 다른 호스트에 그대로 적용되는 공통 규격은 아닙니다. 원한다면 GitHub나 GitLab 저장소를 만들고 Netlify나 Vercel의 자동 배포로 CI/CD 워크플로도 경험해 봅니다.
프로젝트 아이디어 제안
거창할 필요는 없으며, 작지만 흥미를 느끼는 주제를 고르는 것이 중요합니다. 다음은 몇 가지 아이디어입니다.
고급 버전의 To-Do List라면 Context API나 useReducer로 상태 관리를 깊이 다루고, useMemo를 활용한 필터링과 정렬, 항목의 편집과 삭제, todos/active와 todos/completed 같은 라우팅, 로컬 스토리지에 저장하고 불러오는 기능을 넣을 수 있습니다. 간단한 날씨 앱이라면 OpenWeatherMap 같은 외부 날씨 API를 연동해 도시를 검색하고 고르며 현재 날씨와 예보를 보여 주고, TanStack Query(예전 이름 React Query)로 데이터 요청을 다듬어 볼 수 있습니다. 영화나 도서 검색 앱이라면 TMDB나 네이버 책 API 같은 정보 API를 연동해 검색과 결과 목록, 라우팅으로 연결한 상세 페이지, 로딩 스피너와 오류 메시지를 다룹니다.
간단한 포트폴리오 웹사이트라면 자기소개와 기술, 프로젝트 목록 페이지에 미디어 쿼리나 styled-components로 반응형 디자인을 입히고, Netlify Forms로 연락처 폼을 만들 수 있습니다. 미니 쇼핑몰의 상품 목록이라면 가상의 상품 데이터를 카테고리별로 거르고 가격순으로 정렬하며, 상품 상세 페이지와 상태 관리가 중요한 장바구니를 만듭니다. 간단한 그림판이나 메모장이라면 canvas 요소를 이용한 그리기, 텍스트 입력과 저장·불러오기처럼 상태 관리가 중요한 기능을 다룹니다.
너무 복잡한 아이디어보다는 핵심 기능을 먼저 구현하고 조금씩 넓혀 가기를 권합니다. 아이디어를 고른 뒤에는 첫 버전에 남길 기능과 다음 버전으로 미룰 기능을 나누어야 완성할 가능성이 높아집니다.
프로젝트 진행 가이드
진행 가이드는 체크리스트처럼 훑기보다, 실행할 수 있는 마일스톤 단위로 끊어 관리하면 좋습니다.
먼저 주제를 정하고 기능을 정의합니다. 무엇을 만들고 싶은지, 첫 버전에서 사용자가 끝낼 흐름 하나는 무엇인지 정하고, 그 흐름의 정상 결과와 실패했을 때의 안내를 먼저 적습니다. 정적 데이터인지 외부 API인지처럼 어떤 데이터가 필요한지, 그 흐름에 어떤 화면과 주소가 필요한지도 정하며, 여러 주소가 필요할 때 라우팅 경로를 정합니다. 그다음 프로젝트를 시작합니다.
npm create vite@latest my-final-project -- --template react --no-interactive
cd my-final-project
npm installNext.js 같은 다른 프레임워크로 시작해도 좋지만, 이 글의 명령과 dist 정적 배포 설명은 Vite SPA 기준이며 다른 프레임워크의 서버 기능에는 별도의 배포 방식이 필요합니다. 라우팅이 필요하면 npm install react-router로 라우터를 설치하고, 외부 API를 쓴다면 Axios나 fetch를 준비합니다. 공통 스타일은 src/index.css에, 컴포넌트에 연결한 스타일은 그 CSS 파일에 두며, 파일 연결이 낯설다면 첫 번째 React 앱 만들기의 App.css 편집을 복습합니다.
컴포넌트 구조를 설계할 때는 src/components/, src/pages/, src/hooks/, src/utils/처럼 폴더를 나누어 관련 코드를 모으고, 컴포넌트마다 역할과 props, 상태를 간단히 스케치합니다. 핵심 기능은 가장 중요한 것부터 차례로 구현하며, 기능마다 useState나 useEffect 같은 알맞은 훅을 쓰고 UI와 로직을 나누는 연습을 하고, React Developer Tools로 컴포넌트 트리와 상태의 변화를 확인합니다.
여러 주소가 필요하다면 src/App.jsx나 src/main.jsx에서 BrowserRouter가 앱을 감싸도록 하고, Routes와 Route로 페이지 라우팅을, Link나 useNavigate로 페이지 이동을 구현합니다. 데이터는 useEffect로 컴포넌트가 마운트될 때 가져오고 로딩과 오류 상태로 사용자에게 피드백을 주며, 서버 데이터의 캐시와 갱신 규칙이 복잡해지면 TanStack Query나 SWR의 도입을 따로 검토합니다. 첫 흐름을 끝내려고 새 라이브러리를 꼭 더할 필요는 없습니다.
성능 최적화는 성능 문제가 있는 동작에서 Profiler로 렌더링 비용을 확인한 뒤, 원인에 맞는 기법만 고릅니다. 렌더링 비용이 높은 컴포넌트는 React.memo로 감싸고, 자식에게 넘기는 콜백은 useCallback으로, 비싼 계산 결과나 참조가 중요한 객체와 배열은 useMemo로 메모이제이션하며, 적용한 뒤 다시 프로파일링해 효과를 확인하고 지나친 최적화는 피합니다.
배포를 준비할 때는 프로젝트 코드를 GitHub나 GitLab 같은 원격 저장소에 푸시합니다. Netlify에서는 public/_redirects의 /* /index.html 200으로 기본 SPA 재작성을 구성할 수 있지만, 이 넓은 규칙은 누락된 자산도 HTML 200으로 가릴 수 있으므로 운영 경로의 정책에 맞춰 범위를 정하고, 다른 호스트는 그 서비스의 설정을 씁니다. 배포할 때는 npm run build로 빌드하고, Netlify나 Vercel 같은 정적 호스팅 서비스에서 Git 저장소를 연동해 dist를 배포합니다. 배포된 URL에서 첫 버전의 핵심 흐름과 실패 상태를 확인하고, 여러 경로를 쓴다면 그 주소를 직접 열었을 때도 동작하는지 봅니다. 배포하지 않았다면 로컬에서 확인한 결과와 배포를 확인하지 못한 사실을 나누어 기록합니다.
목록 조회 하나를 끝내는 예
첫 버전의 범위를 게시물 목록 읽기와 조회가 실패했을 때의 재시도로 정했다고 해 봅시다. 아래는 완료 기록을 남기는 예일 뿐, 자신의 프로젝트를 꼭 게시판으로 바꿀 필요는 없습니다. 이미 Axios 게시판 조회·추가·삭제를 만들었다면 게시물 조회·추가·삭제 컴포넌트와 데이터 연동 페이지와 검증을 이어서 보고, 다른 앱이라면 그 앱의 한 흐름에 같은 질문을 적용합니다.
| 작은 단계 | 독자가 확인할 끝점 | 남길 기록 |
|---|---|---|
| 첫 범위 정하기 | 목록 읽기와 조회 재시도까지 만들고, 새 글 작성과 삭제는 이번 범위에 넣지 않음 | README의 목표, 필요한 데이터, 다음 버전으로 미룬 기능 |
| 화면과 상태 잇기 | /posts에서 로딩 뒤 목록을 보여 주고, 빈 배열은 빈 목록 안내, 조회 실패는 오류와 다시 시도 버튼으로 구분 | 라우트·컴포넌트·state·API 주소, 사용한 데이터와 기대 결과 |
| 실패 후 복구하기 | API를 쓸 수 없을 때 오류 안내가 나오고, API를 복구한 뒤 다시 시도하면 GET 조회 결과가 목록으로 바뀜 | 기대한 화면과 실제 화면, 실패를 확인한 위치, 다시 시도한 결과 |
| 다른 사람이 시작할 수 있게 하기 | 프로젝트 폴더와 명령, 데이터 준비, 접속 주소를 추측하지 않고 같은 흐름을 시작할 수 있음 | README의 사용한 버전·설치와 실행 명령·API와 seed 준비·확인한 것과 확인하지 못한 것 |
| 배포와 회고 남기기 | 배포했다면 실제 URL에서 같은 흐름을 확인하고, 아직 배포하지 않았다면 미확인으로 남김 | 실제 URL이나 미확인 상태, 확인한 결과, 다음 개선 하나 |
조회가 실패하면 브라우저 Network 탭에서 GET /posts의 주소와 응답을 먼저 봅니다. 이어서 mockApi.js의 baseURL, useAxios의 오류 처리, PostList의 error와 onRetry 연결을 확인합니다. 조회의 재시도는 GET을 다시 보내는 행동이며, 이미 성공한 글 추가나 삭제를 다시 실행하는 것과 구분합니다.
Vite 클라이언트를 호스팅했다고 4000번 포트에서 실행한 로컬 json-server까지 배포되는 것은 아닙니다. 외부 API가 필요한 앱이라면 실제로 접속할 수 있는 API 주소와 호스트의 환경 설정, CORS 조건을 따로 준비하고, README에는 로컬 목록을 확인한 사실과 배포 환경의 API 연결을 확인한 사실을 나누어 적습니다. 다음 기능은 이 기록을 바탕으로 고르며, 처음부터 기능의 수를 늘리기보다 한 흐름의 정상 결과와 실패 복구, 시작 방법을 설명할 수 있는지를 먼저 확인합니다.
PROJECT SCOPE / DELIVERY LOOP
한 사용자 흐름을 구현·검증·회고로 이어간다
작은 범위의 사용자 흐름을 route·UI·상태·데이터까지 연결한다. 기능 검증과 빌드·배포 뒤 실제 결과를 기록하고, 회고에서 확인한 사실을 다음 범위에 반영한다.
01 / SCOPE
사용자 결과와 첫 버전 범위를 정한다
핵심 기능·데이터·페이지 경로와 이번에 다루지 않을 기능을 기록한다. 기능 수를 늘리기보다 한 흐름의 성공·실패 조건을 먼저 정한다.
02 / IMPLEMENT
한 사용자 흐름을 끝까지 연결한다
route, UI, 상태 소유 위치와 데이터 경계를 함께 구현한다. 작은 컴포넌트와 필요한 Hook을 사용하고, 새 도구는 앱의 요구에 맞춰 선택한다.
03 / VERIFY
예외 상태와 빌드까지 확인한다
loading·empty·error·success, 키보드·접근성, 핵심 흐름 테스트를 확인한다. 성능은 측정 후 변경한다. production build와 실제 자산·깊은 URL 동작을 검증한다.
04 / RELEASE
호스트에 맞는 릴리스 절차를 구성한다
Vite SPA의 dist를 전달하고 호스트의 rewrite·캐시 정책을 설정한다. 검증한 동일 파일의 digest 승격과 rollback 대상 기록은 별도로 구성할 절차이며 Git 연동만으로 완료되지 않는다.
05 / OBSERVE
실제 URL에서 기대와 결과를 비교한다
핵심 행동·오류·배포 결과를 기록한다. README, 재현 방법, 검증 기록, release ID와 복구 대상을 공유 기록에 남긴다.
06 / RETROSPECT
사실을 다음 범위의 근거로 삼는다
틀린 가정·부족한 증거·다음 개선점을 정하고 첫 단계로 돌아간다. 모든 단계에서 수용 기준과 완료 증거를 갱신한다.
다음 개선은 기록한 결과를 기준으로 고른다. 이 장의 dist 정적 배포는 Vite SPA 기준이며 다른 프레임워크의 서버 기능에는 별도 배포 방식이 필요하다.
위 그림은 한 사용자 흐름을 구현하고 검증해 회고까지 이어 가는 순환을 보여 줍니다. 그림의 요약처럼 작은 범위의 사용자 흐름을 라우트와 UI, 상태, 데이터까지 연결하고, 기능 검증과 빌드, 배포 뒤의 실제 결과를 기록해 회고에서 확인한 사실을 다음 범위에 반영합니다. 순서는 사용자 결과와 비목표를 정하는 범위 정의, 라우트·UI·상태·데이터를 잇는 한 흐름의 구현, 상태와 접근성, 테스트를 확인하는 검증과 빌드, 검증한 산출물을 전달하는 릴리스, 핵심 행동과 오류를 보는 실제 결과 관찰, 다음 작은 범위를 고르는 회고의 여섯 단계이며, 회고에서 다시 범위 정의로 돌아갑니다. 그림의 캡션이 밝히듯 단계마다 받아들일 기준과 완료의 기록을 갱신하며, 기능의 수나 특정 도구를 강제하지 않습니다.
그림 아래의 여섯 칸은 단계마다 할 일을 정리합니다. 범위 단계에서는 핵심 기능과 데이터, 페이지 경로와 함께 이번에 다루지 않을 기능을 기록하고 한 흐름의 성공과 실패 조건을 먼저 정하며, 구현 단계에서는 라우트와 UI, 상태를 가질 위치와 데이터의 연결을 함께 구현하고 새 도구는 앱의 요구에 맞춰 고릅니다. 검증 단계에서는 loading·empty·error·success, 키보드와 접근성, 핵심 흐름의 테스트를 확인하고, 성능은 측정한 뒤에 바꾸며, 프로덕션 빌드와 실제 자산, 깊은 URL의 동작을 검증합니다. 릴리스 단계에서는 Vite SPA의 dist를 전달하고 호스트의 재작성과 캐시 정책을 설정하되, 검증한 같은 파일의 digest 승격과 롤백 대상의 기록은 따로 구성할 절차이며 Git 연동만으로 끝나지 않습니다. 관찰 단계에서는 실제 URL에서 핵심 행동과 오류, 배포 결과를 기록하고 README와 재현 방법, 검증 기록, release ID와 복구 대상을 공유 기록에 남기며, 회고 단계에서는 틀린 가정과 부족한 확인, 다음 개선점을 정하고 첫 단계로 돌아갑니다. 그림의 마지막 설명처럼 다음 개선은 기록한 결과를 기준으로 고르며, 이 장의 dist 정적 배포는 Vite SPA 기준입니다.
다른 사람이 시작할 수 있는지 확인하기
README에 명령을 적은 뒤에는, 자신의 기억을 빼고 그 문장만 따라갈 수 있는지 확인합니다. 작업 폴더와 사용한 버전, 실행할 명령, 필요한 데이터와 API, 시작 주소를 나누어 적으며, API가 없는 로컬 프로젝트에 API 설치 단계를 억지로 더하지 않습니다. 로컬의 값이 새로고침한 뒤에도 남는지도 고른 저장 방식에서 확인한 결과로 남깁니다.
예를 들어 로컬 데이터로 할 일의 추가와 완료, 삭제 흐름을 정했다면 “빈 목록 → Mina의 할 일 추가 → 완료 표시 → 삭제 → 빈 목록 안내”를 첫 범위로 둘 수 있습니다. 저장하지 않는 state 예제라면 새로고침한 뒤 처음 목록으로 돌아오는 것이 조건이고, 저장 기능을 더했다면 저장의 성공과 실패, 복원 동작을 별도의 범위로 확인합니다. 외부 API나 라우팅을 쓰지 않았다는 사실 자체는 미완성의 이유가 아닙니다.
셀프 피드백과 개선
프로젝트를 완성한 뒤에는 다음 질문을 스스로 던지며 개선할 점을 찾아보세요. 이 앱에서 가장 중요한 기능은 무엇이었고 잘 구현되었는지, 코드는 이해하고 유지보수하기 쉬운지, 컴포넌트들이 각자의 역할에 따라 잘 나뉘어 있는지 돌아봅니다. 상태 관리는 효율적이었는지, 성능 문제는 없었고 최적화는 알맞았는지, 다음 버전에서는 어떤 기능을 더하고 싶은지도 생각해 봅니다. 이 프로젝트로 새로 배운 점은 무엇이고, 어려웠던 점은 무엇이었으며 어떻게 해결했는지도 기록해 두면 다음 프로젝트의 출발점이 됩니다.
정리
- 최종 프로젝트는 기능의 수가 아니라 한 사용자 흐름의 정상 결과와 실패 복구, 시작 방법을 설명할 수 있는지로 첫 버전의 완료를 판단합니다.
- 라우팅과 외부 API, 상태 관리 라이브러리, 최적화는 앱이 필요로 할 때만 더합니다.
- README에는 사용한 버전과 명령, 데이터와 API의 준비, 확인한 것과 확인하지 못한 것을 나누어 적습니다.
- 로컬에서 확인한 결과와 배포 환경에서 확인한 결과를 구분하며, 클라이언트를 배포했다고 로컬 목 API까지 배포되는 것은 아닙니다.
- 범위 정의, 구현, 검증과 빌드, 릴리스, 관찰, 회고의 순환을 되풀이하며 다음 범위를 기록한 결과로 고릅니다.
연습 문제
1. 완료 범위와 다음 개선
게시물의 GET 목록 조회와 실패 안내, 재시도는 로컬에서 확인했습니다. README에는 프런트엔드 실행 명령만 있고 필요한 API의 시작과 seed 안내가 없습니다. 배포 URL에서는 API 연결이 실패했고, 새 글 POST가 실패한 뒤의 GET 재조회는 성공했습니다. 첫 버전 전체를 “배포 완료, 글 작성까지 성공”이라고 기록할 수 있을까요? 원래 범위인 목록 조회를 유지하면서 다음 개선 하나를 고른다면 무엇을 먼저 재현해야 할까요?
해설 보기
그렇게 기록할 수 없습니다. 로컬의 목록 조회와 복구는 확인한 범위이지만, README만으로는 다른 사람이 API를 시작할 수 없으므로 데이터 준비와 실행 안내를 보완하고 그대로 따라갈 수 있는지 확인해야 합니다. 배포 환경의 API 실패는 해결하지 못한 문제로 남기며, 배포 URL을 만들었다는 사실과 그 URL에서 목록 흐름을 마쳤다는 사실을 나눕니다.
GET 재조회의 성공은 목록을 읽었다는 것일 뿐 앞선 POST가 저장되었다는 것을 알려 주지 않습니다. 고른 첫 범위가 목록 조회라면 작성 기능을 이번 완료 조건에 몰래 더하거나 그 성공을 주장하지 않습니다. 다음 개선은 배포 환경에서 실패한 GET의 주소와 응답, 환경 값, API의 접근 조건을 재현해 원인을 찾는 것으로 잡을 수 있습니다.
2. 로컬 데이터만 쓰는 앱의 완료 조건
API 없이 컴포넌트의 state만으로 할 일 목록을 만들었습니다. 할 일을 추가하고 완료 표시를 한 뒤 새로고침했더니 처음의 빈 목록으로 돌아왔습니다. 이것은 버그일까요, 완료일까요?
해설 보기
정한 범위에 따라 다릅니다. 저장하지 않는 state 예제로 범위를 정했다면, 새로고침하면 처음 목록으로 돌아오는 것이 기대한 결과이므로 완료 조건을 만족합니다. README에도 저장하지 않는다는 사실을 적어 둡니다.
새로고침한 뒤에도 남아야 한다는 요구가 범위에 있었다면, 로컬 스토리지 같은 저장 방식을 더하고 저장의 성공과 실패, 복원 동작을 따로 확인해야 하므로 아직 완료가 아닙니다. 어느 쪽이든 외부 API를 쓰지 않았다는 사실 자체는 미완성의 이유가 아닙니다.
이것으로 11장과 React 트랙을 마칩니다. 11장에서는 Vite로 앱을 빌드해 dist 산출물을 로컬에서 확인하는 방법에서 시작해, 정적 호스팅 서비스에 배포하고 SPA 경로의 새로고침 문제를 해결하는 방법, CI/CD의 기초 개념과 다음 학습 경로를 살펴보고, 최종 프로젝트의 한 흐름을 정해 완료 여부를 판단하는 방법을 익혔습니다. 처음부터 다시 훑어보고 싶다면 React 교재의 목차로 돌아가세요.