"AI한테 '앱 만들어줘' 했더니 뚝딱 만들어주긴 했어요. 근데 기능 하나 추가하려니까 어디를 고쳐야 할지 모르겠어요. 건드리면 에러 나고..."
맞아요. 시작은 쉬운데 마무리가 안 되죠. 많은 분들이 여기서 멈춰요.
AI한테 "Next.js로 만들어줘" 하면 만들어는 줘요. 근데 그렇게 만들어진 코드는 **'일회용'**인 경우가 많아요. 나중에 기능을 추가하거나 수정하려고 보면, 모든 로직이 한 파일에 뒤엉켜 있어서 손을 댈 수가 없거든요.
우리는 21편에서 설계를 먼저 했잖아요? 그 설계대로 폴더를 나누고 역할을 분리하면, 나중에도 쉽게 고칠 수 있는 '지속 가능한' 앱이 돼요.
오늘은 그 설계를 실제 코드로 옮기는 과정을 보여드릴게요. 코드를 직접 짤 필요는 없어요. 하지만 **"AI한테 어떤 구조로 만들어달라고 해야 하는지"**는 알아야 해요.
이 글을 읽고 나면
- AI에게 "내가 원하는 구조(설계)"대로 프로젝트를 생성시킬 수 있어요
- Container-Presenter 패턴을 실제로 적용해서, 수정하기 쉬운 컴포넌트를 만들 수 있어요
- zustand로 전역 상태를 관리하고, 컴포넌트끼리 데이터를 주고받게 할 수 있어요
- 백엔드 없이도 동작하는 프론트엔드 화면을 완성할 수 있어요
21편 복습: 우리가 설계한 청사진
지난 글에서 정한 내용을 다시 꺼내볼게요. 이 설계도가 있어야 집을 짓죠.

오늘은 이 중에서 프론트엔드(UI + 상태 관리) 부분만 집중해서 만들 거예요. 백엔드(DB) 연결은 다음 편에서 할 거니까, 오늘은 **"화면이 잘 동작하는지"**에만 집중해요.
단계별 구현 가이드
1단계: 프로젝트 생성 (옵션이 중요해요)
AI한테 그냥 "만들어줘" 하면 옛날 방식(Pages Router)이나 우리가 원하지 않는 구조로 만들어줄 수 있어요. 우리가 정한 스택을 정확히 알려줘야 해요.
🧑🏻💻 나 (사용자)
"투두 리스트 웹앱 만들 거야. Next.js 프로젝트 생성해줘.
다음 옵션으로 해줘:
- Next.js 14 App Router 사용
- TypeScript 사용
- Tailwind CSS 사용
src/디렉토리는 사용 안 함 (app 폴더가 최상위에 있게)프로젝트 이름은
smart-todo로 해줘."
🤖 AI
네, 요청하신 옵션대로 Next.js 프로젝트를 생성할게요.
npx create-next-app@latest smart-todo --typescript --tailwind --app --no-src설치가 완료되면 폴더 구조가 이렇게 잡힐 거예요:
app/: 페이지 파일들public/: 이미지 등 정적 파일package.json: 라이브러리 목록
[!TIP] 왜
src/를 안 쓰나요? 이건 취향 차이긴 한데, 저는 초보자분들께src/없이 폴더 구조를 단순하게 가져가는 걸 추천해요. 뎁스(Depth)가 하나 줄어들면 파일 찾기가 조금 더 편하거든요. 21편 설계에서도 그렇게 잡았고요.
2단계: 폴더 구조 잡기 (빈 폴더부터)
집 지을 때 기둥부터 세우듯이, 폴더 구조부터 잡아야 해요. AI한테 우리가 설계한 폴더들을 만들어달라고 하세요.
🧑🏻💻 나 (사용자)
"루트 경로에 우리가 설계한 폴더들을 만들어줘. 각 폴더 안에
README.md파일을 만들어서 이 폴더가 뭐 하는 곳인지 설명도 적어줘.필요한 폴더:
components/(UI 컴포넌트)services/(비즈니스 로직)lib/(Supabase 설정 등)store/(zustand 상태 관리)types/(TypeScript 타입 정의)"
🤖 AI
네, 폴더 구조를 생성하고 각 폴더의 역할을 적어둘게요.
(생성 완료) 이제
components/에는 UI 파일만,store/에는 상태 관리 파일만 넣으면 돼요. 자리가 정해졌으니 코드가 섞일 일이 줄어들 거예요.
3단계: 타입 정의 (약속 정하기)
데이터가 어떻게 생겼는지 미리 정해두면 에러를 확 줄일 수 있어요. 이걸 타입(Type) 정의라고 해요.
🧑🏻💻 나 (사용자)
"데이터 구조부터 잡자.
types/todo.ts파일을 만들어줘.Todo 데이터는 이렇게 생겼어:
- id: 문자열 (UUID)
- title: 문자열 (할 일 내용)
- is_completed: 불리언 (완료 여부)
- created_at: 문자열 (생성일)
TypeScript 인터페이스로 정의해줘."
🤖 AI
types/todo.ts파일을 작성했습니다. 이제 앱 전체에서 'Todo'라고 하면 이 형태를 의미하게 됩니다.
4단계: 상태 관리 (zustand)
이제 "할 일 목록"을 어디선가 기억하고 있어야겠죠? 아직 DB는 없으니까, 일단은 앱이 실행되는 동안 메모리에 저장할게요. 이걸 **전역 상태(Store)**라고 해요.
🧑🏻💻 나 (사용자)
"zustand 라이브러리 설치해줘. 그리고
store/useTodoStore.ts를 만들어줘.기능:
todos: 할 일 목록 (배열)addTodo: 할 일 추가toggleTodo: 완료 상태 토글deleteTodo: 삭제지금은 DB 연결 없이 배열(
todos)만 조작하게 해줘."
🤖 AI
네,
npm install zustand후 스토어를 만들게요. 지금은 새로고침하면 데이터가 날아가지만, 앱 안에서는 잘 동작할 거예요.
5단계: 컴포넌트 만들기 (여기가 핵심!)
가장 중요한 Container-Presenter 패턴을 적용할 차례예요. 이걸 안 하면 나중에 수정을 못 해요.
🧑🏻💻 나 (사용자)
"
components/TodoList폴더를 만들고, 그 안에 두 파일을 만들어줘.
- TodoListContainer.tsx (로직)
- zustand store에서
todos,toggleTodo,deleteTodo를 가져와.- 가져온 데이터를 Presenter한테 넘겨줘.
- TodoListPresenter.tsx (UI)
- props로 받은
todos를 화면에 보여줘.- 디자인은 Tailwind CSS로 깔끔하게 해줘.
- 로직은 전혀 몰라야 해. 그냥 보여주기만 해."
🤖 AI
알겠습니다. 로직과 UI를 완벽하게 분리해서 작성할게요.
- Container: 데이터 관리자 역할
- Presenter: 화면 디자이너 역할
이렇게 나누면 나중에 디자인만 바꾸고 싶을 때 Presenter만 고치면 돼서 편해요.
6단계: 완성 및 확인
마지막으로 메인 페이지(app/page.tsx)에 우리가 만든 TodoListContainer를 갖다 놓으면 끝이에요.
🧑🏻💻 나 (사용자)
"메인 페이지(
app/page.tsx)에TodoListContainer랑TodoFormContainer를 배치해줘. 제목은 '나만의 할 일 관리'로 하고."
🎯 휴먼터치: AI 믿지 말고, 직접 눌러보세요
자, 코드는 다 짜졌을 거예요. 에러 없이 실행도 될 거고요. 하지만 "AI가 다 했으니까 됐겠지" 하고 넘어가면 절대 안 돼요.
지금 바로 브라우저를 켜서 확인해보세요. (npm run dev)
체크리스트:
- 입력창에 글자를 쓰고 '추가'를 눌러보세요. 목록에 바로 뜨나요?
- 체크박스를 눌러보세요. 취소선이 그어지나요?
- 삭제 버튼을 눌러보세요. 목록에서 사라지나요?
- (중요) 새로고침을 해보세요. 다 사라지나요?
"어? 새로고침하니까 다 사라지는데요?"
정상이에요! 👏 우리가 아직 **DB(데이터베이스)**를 연결 안 했고, **메모리(Store)**에만 저장했으니까요. 메모리는 껐다 켜면(새로고침하면) 날아가요.
이걸 확인했다면, 여러분은 오늘 목표를 100% 달성하신 거예요. "아, 지금은 메모리에만 저장되는구나. 영구히 저장하려면 DB가 필요하구나"를 몸소 체험하셨으니까요.
AI한테 이렇게 요청하세요
오늘 배운 내용을 써먹을 수 있는 프롬프트 템플릿이에요.
❌ 이렇게 하지 마세요
"투두리스트 만들어줘." (결과: 모든 코드가 한 파일에 섞여서 수정 불가능한 스파게티 코드가 됨)
✅ 이렇게 하세요
"Next.js App Router로 프로젝트 만들어줘.
구조 요구사항:
- 폴더 구조:
components,store,types로 나눠줘.- 컴포넌트: Container-Presenter 패턴을 써서 로직과 UI를 분리해줘.
- 상태 관리: zustand를 사용해서 전역 상태로 관리해줘.
우선 UI랑 상태 관리만 먼저 만들고, DB 연결은 나중에 할게."
이렇게 구체적으로 시켜야 "수정 가능한" 코드가 나옵니다.
오늘의 핵심 정리
✅ 설계 → 구현: 21편에서 설계한 폴더 구조대로 만들어야 나중에 안 꼬여요.
✅ UI 분리: 로직(Container)과 화면(Presenter)을 나누면 유지보수가 훨씬 쉬워져요.
✅ 상태 관리: 화면끼리 데이터를 공유하려면 useState보다 zustand 같은 전역 상태가 편해요.
✅ 검증: AI가 짠 코드는 반드시 버튼 하나하나 눌러가며 동작을 확인해야 해요.
셀프체크
이 글을 완전히 이해했는지 스스로 점검해보세요.
□ 로직 담당 컴포넌트와 UI 담당 컴포넌트를 분리하는 패턴의 이름은? □ 지금 만든 앱에서 새로고침을 하면 데이터가 사라지는 이유는? □ AI한테 프로젝트 생성을 요청할 때, 구체적인 옵션(기술 스택 등)을 명시해야 하는 이유는?
정답 보기
- Container-Presenter 패턴 (Logic vs UI)
- 아직 DB에 저장하지 않고 **메모리(Store)**에만 저장하고 있기 때문이에요.
- 명시하지 않으면 AI가 임의로(주로 옛날 방식이나 가장 쉬운 방식으로) 만들기 때문에, 우리가 원하는 구조를 얻을 수 없어요.
다음 글 예고
오늘 만든 "일회용 투두리스트"를 "진짜 서비스"로 만들어볼까요? 새로고침해도 지워지지 않고, 내 핸드폰에서도 볼 수 있게요.
다음 편 **[23편: 백엔드 연결]**에서는:
- Supabase로 5분 만에 백엔드 서버를 만들고
- 우리가 만든 앱에 로그인 기능을 붙이고
- 작성한 할 일을 DB에 영구 저장하는 법을 다룹니다.
이제 진짜 개발자가 된 기분이 드실 거예요. 다음 편에서 만나요!
이 시리즈 로드맵
PART 5: 첫 프로젝트 - 웹 서비스
[ 21편 ] 기획과 아키텍처 ✅
↓
[ 22편 ] 프론트엔드 구조 (지금 여기!)
↓
[ 23편 ] 백엔드 연결
↓
[ 24편 ] 배포하기
궁금한 점이 있거나, 따라하다 막힌 부분이 있다면 댓글로 남겨주세요!