Next.js App Router팀 프로젝트테크 딥다이브

쇼핑몰 팀 프로젝트 ① 전체 구조 설계 — 스펙, 할 일, 아키텍처를 열흘 안에 세우는 법

· 2026-10-19 발행· 25분 읽기

목차

팀 프로젝트 첫날, 저는 레퍼런스 쇼핑몰을 브라우저에 띄워 놓고 한 시간 넘게 아무것도 못 했어요. 화면은 눈앞에 있는데 어디서 손을 대야 할지, 어디까지가 제 몫인지, 열흘 안에 뭐가 되고 뭐가 안 되는지 하나도 정해져 있지 않았거든요.

이 글은 그 한 시간을 다시 쓰는 글이에요. 레퍼런스 사이트 하나(저희는 패션 편집몰 하나를 골랐어요)와 열흘짜리 팀 일정이 주어졌을 때, 세 가지를 세우는 방법을 다룹니다. 첫째 스펙 — 어떤 화면을 누구에게, 어디까지 만들 것인가. 둘째 일 목록 — 그 화면을 만들려면 무슨 일이 어떤 순서로 필요하고 누가 맡는가. 셋째 아키텍처 — 파일을 어디에 두고, 데이터는 어떤 길로 화면까지 오고, 네 사람이 한 코드를 어떻게 함께 고치는가. 개념은 수업에서 배운 것이고, 증거는 제 작업일지와 팀 저장소에서 가져왔어요. 다 읽고 나면 다음 팀 프로젝트에서 첫날 오후에 이 세 가지를 종이 한 장으로 적을 수 있을 거예요.

프레임워크는 Next.js(리액트 기반 웹 프레임워크)의 App Router(폴더 구조가 곧 URL 이 되는 라우팅 방식)예요. 이 글은 시리즈 네 편 중 첫 편이고, 데이터 구조(ERD) 는 2편에서 따로 다루니 여기서는 “화면에서 데이터 목록을 적는다”까지만 가요.

이 글의 지도. 위에는 스펙, 일 목록, 아키텍처 세 상자가 화살표로 이어지고, 가운데에 조립·적용·교정·함정 띠, 아래에 10-12 부터 10-23 까지의 내 프로젝트 타임라인이 있다 이 글의 지도. 개념은 위 세 상자에서, 증거는 아래 타임라인에서 가져온다

열흘짜리 팀 프로젝트는 무엇부터 정해야 할까?

열흘 프로젝트의 첫 이틀은 코드가 아니라 결정에 쓴다. 무엇을 만들지(스펙), 무슨 일이 어떤 순서로 필요한지(일 목록), 파일과 데이터와 브랜치가 어떻게 놓이는지(구조)를 먼저 적고, 공통 세팅을 한 사람이 끝내 올린 뒤에야 네 사람이 갈라진다.

왜 이 순서냐면요. 스펙이 없으면 일 목록을 쪼갤 기준이 없고, 일 목록이 없으면 폴더를 누가 쓸지 모르고, 폴더가 없으면 네 사람이 같은 파일을 동시에 고쳐요. 반대로 셋이 정해지면 나머지 여드레는 매일 같은 루프를 돌면 됩니다. 아침에 오늘 할 일을 고르고, 공용 브랜치를 최신으로 받고, 내 브랜치에서 만들고, PR(Pull Request, 내 변경을 공용 브랜치에 합쳐 달라는 요청)을 올리고, 저녁에 진척을 말하고 작업일지를 쓰는 루프요.

수업에서 배운 큰 흐름은 이래요. 레퍼런스를 셸·본문·반복 카드 세 층으로 읽고, 필요한 화면을 확정하고, 라우팅 틀을 먼저 세우고, 헤더·푸터 같은 공통 부품을 만들고, 모바일부터 넓혀 가며 반응형을 입히고, 폼 처리 같은 서버 쪽 일은 화면이 끝난 뒤로 미룬다. 그리고 하나 더, 공통 세팅은 다 함께 먼저 끝내서 공용 브랜치에 올린 뒤 각자 갈라진다는 것. 공통을 늦게 고치면 모두의 브랜치에서 충돌이 나거든요.

저희 팀은 10월 12일에 저장소를 만들고 13일에 시작해 23일 최종 발표를 앞두고 있어요. 이 글을 쓰는 19일 기준으로 공용 브랜치에는 머지 커밋을 뺀 커밋이 75개, 합쳐진 PR 이 31개 쌓였고요. 그중 제 커밋이 10개, 제 PR 이 6개예요. 제 담당은 상품 상세 페이지였습니다.

전체 절차. 위 줄에 레퍼런스 관찰, 스펙, 일 목록, 구조, 공통 세팅 다섯 상자가 화살표로 이어지고, 아래에 오전 회의부터 오후 회의까지 매일 루프 다섯 칸이 점선 화살표로 되돌아간다 첫 이틀에 1~5 를 정하고, 6 의 루프를 여드레 돈 뒤 7 로 끝난다

스펙에는 무엇을 적을까 — 화면, 역할, 범위선?

스펙은 화면 목록, 그 화면을 보는 역할, 그리고 이번에 만들지 않을 것의 목록이다. 세 번째가 없으면 앞의 둘은 열흘 안에 끝나지 않는다.

여행으로 치면 스펙은 “어디를 갈지”가 아니라 “어디를 안 갈지”까지 적은 일정표예요. 가고 싶은 곳만 적은 일정표는 아무 데도 못 가게 만들거든요.

표기는 표 하나면 충분해요. 행은 화면, 열은 “손님이 하는 일 · 화면에 보이는 데이터 · 누가 보나 · 안/밖”. 수업에서는 쇼핑몰의 최소 흐름을 발견(홈) → 탐색(목록) → 결정(상세)로 가르쳤고, 장바구니는 자료마다 필수와 선택이 엇갈렸어요. 결제·회원·DB 연동은 보류, 최종 과제는 배포 필수였고요. 화면마다 보이는 데이터를 적어 두는 이유는 그 명사들이 2편의 데이터 구조로 바로 이어지기 때문이에요.

아래는 저희 팀 저장소를 되짚어 복원한 스펙이에요. 첫날 종이에 적은 원본이 남아 있지 않아서, 10월 19일 저장소의 폴더에서 거꾸로 읽은 것이라는 점을 밝혀 둡니다.

화면 손님이 하는 일 보이는 데이터 누가 보나 안/밖
홈 / 배너와 추천 카드를 훑는다 배너 · 카드(이미지·제목·태그) 모두 안 — 팀원
상품 상세 /product/[id] 사진을 넘기고 사이즈를 고른다 이미지 · 이름 · 정가/판매가 · 사이즈 · 혜택 모두 안 — 나
장바구니 /cart 담은 것을 확인한다 상품 줄 · 수량 · 합계 (빈 상태 따로) 회원 안 — 팀원
로그인 /login 들어온다 아이디 · 비밀번호 · 소셜 버튼 손님 안 — 팀원
관리자 /admin 상품을 관리한다 (화면 틀) 관리자 안 — 팀원
상품 목록 카테고리로 고른다 카드 그리드 · 정렬 모두 없음 — 상세의 “전체상품” 링크가 비어 있다
결제 · 실제 담기 · DB — — — 밖 — ERD 에만 그린다

여기서 두 가지가 보여요. 하나는 수업이 필수로 둔 목록 페이지가 저희 저장소에 없다는 것. 상세 페이지의 빵 부스러기(현재 위치 경로) 가운데 “전체상품”은 지금 404 로 가요. 다른 하나는 반대로 수업이 보류하라고 한 로그인·관리자를 저희가 먼저 만들었다는 것. 레퍼런스가 손님·회원·관리자 세 역할을 다 갖고 있으니 자연스럽게 따라간 건데, 범위선을 종이에 먼저 그었다면 순서가 달랐을 거예요. 스펙 표의 마지막 열이 비어 있으면 이런 일이 생깁니다.

스펙의 세 요소. 위에 발견, 탐색, 결정, 담기 네 화면 흐름과 범위 밖의 주문·결제, 가운데에 손님·회원·관리자 세 역할 상자, 아래에 안과 밖을 나눈 두 상자 화면 흐름은 손님의 행동 순서, 역할은 같은 화면을 다르게 보는 사람, 범위선은 이번 열흘의 끝선

필요한 일은 어떻게 쪼개고 순서를 정할까?

일 하나는 “끝났다”를 판정할 명령이 하나 붙은 덩어리이고, 일 목록은 그 덩어리들을 의존 순서로 늘어놓은 것이다. 판정 명령이 없는 일은 아직 일이 아니다.

요리로 치면 재료 손질이 끝나야 볶을 수 있는 것처럼, 일에도 선후가 있어요. 수업에서 배운 덩어리는 여섯이었어요. 환경, 데이터, 공통 컴포넌트, 페이지와 라우팅, 스타일, 검사와 문서. 각각 “개발 서버에 빈 화면이 뜬다”, “이미지 주소가 전부 열린다”, “카드 하나를 값만 바꿔 두 번 그린다”, “없는 주소에서 안내 화면이 뜬다” 같은 판정이 붙어 있었고요.

의존은 화살표로 적어요. A → B 는 A 가 끝나야 B 를 시작할 수 있다는 뜻이에요. 튜터가 준 10일치 구현 가이드를 의존으로 다시 그리면 이렇게 돼요. 프로젝트 생성 · 코드 스타일 · 저장소 규칙은 저장소가 생기기 전이라 한 사람이 연달아 한다. 그 뒤 타입과 mock(가짜 데이터, 진짜 DB 대신 화면을 그리기 위해 손으로 적은 배열)이 공용 브랜치에 합쳐져야 화면 담당자들이 그걸 가져다 쓸 수 있다. 레이아웃과 도메인 화면은 그다음에 동시에, 라우트 조립은 그 둘이 합쳐진 뒤에.

저희 팀의 실제 일 목록을 같은 열로 적으면 이래요. “담당” 열은 저장소 폴더에서 읽은 것이라 제 몫 외에는 역할만 적었어요.

# 덩어리 선행 끝 판정 담당 실제 날짜
1 프로젝트 생성 · 포매터 · 저장소 · 브랜치 규칙 · PR 템플릿 — 전원이 clone 해서 개발 서버가 뜬다 관리자 1명 10-12
2 각자 작업 폴더 만들기 1 빈 폴더가 공용 브랜치에 있다 전원 10-12
3 메인 mock 데이터 · 타입 1 카드 10장이 그려진다 팀원 10-13~
4 헤더 · 푸터 · 셸 레이아웃 1 모든 페이지에 한 번씩 보인다 팀원 10-13~
5 상품 상세: 갤러리 → 구매 패널 → mock 연결 → 탭 3, 4 /product/3 이 뜨고 /product/999 는 404 나 10-14 → 10-19
6 장바구니 · 로그인 · 관리자 화면 4 각 주소가 뜬다 팀원 10-13~
7 매일 build · 발표 전 main 반영 · 배포 5, 6 next build 통과, 배포 URL 전원 / 관리자 매일 · 10-23

5번 줄을 보면 제 일이 네 조각으로 쪼개져 있죠. 처음엔 한 덩어리였는데, 판정 명령을 붙이려니 저절로 나뉘었어요. “갤러리가 뜬다”와 “없는 id 가 404 다”는 다른 날 확인할 수 있는 다른 일이니까요.

일의 의존 그래프. 왼쪽 노란 상자 공통 세팅에서 타입·mock 과 단위 테스트 환경으로, 다시 공통 레이아웃과 도메인 화면으로, 오른쪽 라우트 조립과 검사·배포로 화살표가 이어진다 같은 세로줄은 동시에 해도 되는 일. 타입·mock 이 먼저 합쳐지는 것이 가장 중요한 선후 관계다

폴더는 어떤 기준으로 나눌까 — app, domains, shared?

파일의 자리는 “이 파일이 바뀌는 이유가 어디에 있는가”로 정한다. URL 이 바뀌어서면 app, 한 업무가 바뀌어서면 domains, 모두가 함께 바뀌어서면 shared 다.

사무실로 치면 app 은 안내 데스크(어느 방으로 가는지만 안다), domains 는 부서 사무실(그 부서 일만 한다), shared 는 비품실(누구나 쓴다)이에요. 수업에서 app 과 domains 의 경계는 강사가 직접 설명했고, shared 는 팀 프로젝트 규약이 더한 갈래라고 위키에 적혀 있어요. 같은 강사의 두 자료가 근거라 독립 확증은 아니라는 점도요.

기호는 폴더 트리로 써요. 수업 기준의 모양은 이랬어요.

app/                 주소. page · layout · loading · error · not-found 만
  (shop)/            라우트 그룹 — 괄호 폴더는 URL 에 안 나온다
    page.tsx         /
    product/[id]/    /product/3   ([id] 는 변수)
domains/             업무. 한 사람이 한 도메인을 맡는다
  products/
    types/           Product 의 모양
    components/      갤러리 · 패널 · 탭
    actions/ services/ queries/   (서버 쪽 일, 이번엔 비어 있어도 된다)
shared/              공용. 버튼 · 입력 · cn() · mock

라우트 그룹은 URL 에 안 나오는 괄호 폴더예요. (shop) 과 (admin) 을 나누면 손님 화면과 관리자 화면이 다른 레이아웃(헤더·푸터를 감싸는 틀)을 가질 수 있어요. 또 하나, 이 규칙을 사람 기억에 맡기지 않는 장치가 있어요. 컴포넌트 설치 도구(shadcn)의 설정 파일에서 저장 경로를 @/shared/… 로 돌려 두면 이후 설치되는 부품이 전부 shared 로 떨어지거든요.

아래는 제가 이 글을 쓰면서 팀 저장소 복사본에서 직접 뽑은 실제 트리예요. 이미지 파일은 뺐고, 폴더 30개에 파일 50개였어요.

팀 저장소의 app, domains, shared 폴더 트리를 터미널 화면으로 찍은 것. app 아래 _component, (admin), (shop), domains 아래 auth, cart, main, products, shared 아래 components/ui, lib, utils 가 보인다 2026-10-19 팀 저장소. 위 기호와 다른 곳이 셋 보인다 — 헤더·푸터가 app 안의 _component 에, mock 이 domains/main/data 에, 그리고 shared/mocks 는 없다

트리를 기호와 대 보면 어긋난 곳이 바로 보여요. 헤더와 푸터는 모두가 쓰는 셸이니 수업 기준으로는 shared 자리인데 app 안에 있고, mock 은 메인 담당자의 도메인 폴더에 들어 있어요. 틀렸다기보다 “누가 바꾸나”를 묻지 않고 “누가 만들었나”로 자리를 정한 흔적이에요. 이런 어긋남은 트리를 한 번 뽑아 보지 않으면 눈에 안 띕니다.

폴더 3갈래. app은 주소, domains는 업무, shared는 공용이라는 세 상자에 각각 바뀌는 이유와 예시 경로, 아래에 판단 질문이 적혀 있다 판단 질문은 하나다 — 이 파일은 누가 바꾸나

데이터는 어떤 길로 화면까지 올까 — mock 에서 DB 로?

데이터 경로는 타입, 데이터 접근, 라우트 세 단으로 나누고, 출처가 바뀔 때 고치는 칸을 데이터 접근 한 곳으로 묶어 둔다. 지금 mock 을 쓰는 이유는 나중에 DB 로 바꿀 때 화면 코드를 건드리지 않기 위해서다.

식당으로 치면 타입은 메뉴판(무엇이 나오는지의 약속), 데이터 접근은 주방(재료가 어디서 오든 메뉴판대로 내놓는다), 라우트는 홀(손님이 앉은 자리에 맞춰 내간다)이에요. 재료 공급처가 바뀌면 주방만 바뀌고 메뉴판과 홀은 그대로죠.

코드로는 이렇게 생겨요. 아래는 설명용으로 새로 쓴 예시예요.

// ① 타입 — domains/products/types.ts
export type Product = { id: number; name: string; image: string; tags: string[] };

// ② 데이터 접근 — domains/products/api/products.ts
export async function getProduct(id: string): Promise<Product | undefined> {
  const row = mockRows.find((r) => r.id === Number(id)); // URL 값은 문자열, mock 은 숫자
  return row ? toProduct(row) : undefined;                // 없으면 사실만 돌려준다
}

// ③ 라우트 — app/(shop)/product/[id]/page.tsx
const { id } = await params;          // Next 15 부터 params 는 Promise
const product = await getProduct(id);
if (!product) notFound();             // 404 는 여기서 "결정"한다

세 가지 결정이 들어 있어요. URL 에서 온 값은 항상 문자열이라 숫자 id 와 비교하려면 한 곳에서 바꿔야 하고, 그 “한 곳”이 데이터 접근이에요. 접근 함수는 없으면 undefined 만 돌려주고 404 는 라우트가 결정해요. 목록 페이지가 같은 함수를 쓸 때 원하지 않는 404 가 나면 안 되니까요. 그리고 mock 은 지금 당장 꺼낼 수 있지만 접근 함수를 미리 async 로 만들어 두면, DB 로 바꿔도 라우트의 await 줄은 그대로예요.

아래가 이 경로로 데이터가 흘러 들어온 제 상세 페이지예요. 이 글을 쓰면서 팀 저장소 복사본을 띄워 /product/3 을 직접 찍었어요. 상품명 “요즘 니트”와 이미지가 mock 에서 왔고, 가격과 사이즈는 아직 패널 안의 상수예요.

상품 상세 페이지 캡처. 왼쪽 갤러리 자리는 회색 상자로 가렸고, 오른쪽에 브랜드명, 상품명, 취소선 정가 259,000 과 빨간 5% 와 판매가 246,050, 최대 혜택가 줄, 혜택 아코디언 세 줄, 사이즈 M L XL 버튼, 총 상품 금액, 장바구니에 담기와 바로 구매하기 버튼이 있다 2026-10-19 팀 저장소 복사본의 /product/3. 빵 부스러기의 “요즘 니트”가 ③ → ② → mock 을 거쳐 온 값이다. 갤러리 사진은 출처를 확인하지 못해 회색 상자로 가렸다

데이터 경로 3단. URL에서 라우트, 데이터 접근, 출처로 화살표가 이어지고, 아래에 타입 상자가 라우트와 접근을 묶으며, 출처 아래 교체되는 칸 상자가 점선으로 표시되어 있다 사실은 ②, 결정은 ③. 출처가 바뀌면 ② 안쪽만 바뀐다

팀은 같은 코드를 어떻게 함께 고칠까 — 브랜치, 매일 PR, 검사?

작업은 사람마다 하나씩 가진 feature 브랜치에서 하고, 합치는 요청은 항상 develop 으로 보내며, main 에는 발표 전에 관리자만 반영한다. 저장소 설정이 막는 것과 팀이 약속만 한 것을 구분해야 규칙이 지켜진다.

브랜치는 같은 문서를 각자 복사해 고친 뒤 한 사람이 합치는 것과 같아요. 복사본(feature)에서 고치고, 합치는 자리(develop)에 요청을 보내고, 완성본(main)은 발표 직전에만 갱신하는 거죠.

기호는 세 줄짜리 선로예요. 아래 그림에서 노란 점이 제 커밋(변경 묶음), 검은 점이 합쳐진 머지 커밋이에요. 저장소에는 Ruleset(GitHub 의 브랜치 보호 설정)이 걸려 있어서 develop 과 main 에 직접 올리면 거절당해요. 반면 “PR 은 develop 으로”, “커밋 메시지 앞에 feat/fix 같은 타입”, “하루 안에 리뷰”는 아무도 막지 않는 약속이에요.

브랜치 3단 선로. main, develop, feature 세 줄에 커밋 점이 찍히고, feature 에서 develop 으로 PR 과 리뷰 1명을 거쳐 머지 커밋이 생기며, 마지막에 develop 에서 main 으로 발표 전 반영 화살표가 올라간다. 아래에 저장소가 막는 것과 팀이 약속한 것 두 상자 같은 규칙이라도 왼쪽 상자는 저장소가 거절하고 오른쪽 상자는 사람이 지킨다

저희 팀의 실제 값은 이래요. 팀 저장소 복사본에서 공용 브랜치의 커밋 날짜를 직접 셌습니다.

날짜 10-12 10-13 10-14 10-15 10-16 10-17 10-18 10-19
커밋 (머지 제외) 8 5 13 28 12 2 6 1

합치면 75개, 합쳐진 PR 은 31개예요. 제 경우 PR 템플릿의 “고민한 점” 칸이 작업일지의 초안이 됐고, 이슈 템플릿의 “하루 안에 끝낼 크기로 쪼개기” 칸이 위 일 목록 5번 줄을 네 조각으로 나눈 계기였어요. 한편 수업은 npm run test 를 build(배포용으로 묶으면서 오류를 잡는 명령) 앞에 자동으로 돌리는 환경까지 요구했는데, 저희 package.json 의 스크립트는 dev · build · start · lint 넷뿐이에요. 테스트 덩어리는 일 목록에서 빠져 있었던 거죠.

스펙, 일 목록, 구조는 어떻게 맞물릴까?

세 부품은 따로 놀지 않아요. 스펙의 화면 한 줄이 일 목록의 덩어리 하나가 되고, 그 덩어리가 domains 의 폴더 하나가 되고, 그 폴더가 feature 브랜치 하나가 돼요. 거꾸로 말하면 한 사람이 한 화면, 한 도메인 폴더, 한 브랜치를 맡으면 PR 이 서로의 파일을 건드리지 않는다는 뜻이에요. 저희 팀이 여드레 동안 PR 31개를 합치는 동안 제 커밋 이력에 충돌 해결 커밋이 10월 17일 한 번뿐인 이유가 이거예요.

세 가지 계획안을 같은 항목으로 견줘 볼게요. 발제문의 4일 안, 튜터의 10일 안, 그리고 저희 팀이 실제로 간 길이에요.

항목 발제문 4일 안 튜터 10일 안 우리 팀 실제 (10-12 ~ 10-19)
필수 화면 홈 · 목록 · 상세 목록 · 상세 · 장바구니 홈 · 상세 · 장바구니 · 로그인 · 관리자 (목록 없음)
데이터 Product 하나 + 상품 6개 타입 4종 + mock 4종 메인 mock 10개, 상세는 그것을 변환해 씀
폴더 types · data · components app · domains · shared app · domains · shared (셸은 app 안)
검사 npm run build 매일 단위 테스트가 build 앞에 자동 build 는 발표 전, 테스트 없음
브랜치 — feature → develop → main 같음, PR 31개
끝 판정 덩어리마다 명령 하나 가이드마다 “완료 확인” 절 PR 템플릿 체크 3개

총정리하면 이래요.

부품 묻는 질문 산출물 틀리면 생기는 일
스펙 무엇을 만들고 무엇을 안 만드나 화면 × (데이터 · 역할 · 안/밖) 표 범위 밖 화면을 먼저 만든다
일 목록 무엇이 무엇을 기다리나 덩어리 · 선행 · 끝 판정 · 담당 표 import 가 깨진 채로 시작한다
폴더 이 파일은 누가 바꾸나 app · domains · shared 트리 공용 파일을 여러 사람이 고친다
데이터 경로 출처가 바뀌면 어디를 고치나 타입 · 접근 · 라우트 3단 화면 코드마다 mock 이 박힌다
워크플로우 저장소가 막나, 사람이 지키나 Ruleset + 약속 목록 승인 없이 합치거나 리뷰가 밀린다

상품 상세 한 페이지를 이 틀로 풀면?

판정 체크리스트를 먼저 정해요. 제 작업 조각마다 같은 다섯 질문을 던졌습니다.

  1. 범위선 — 스펙의 “안”에 있나? 밖이면 모양만 두고 동작은 만들지 않는다.
  2. 데이터 출처 — 값이 mock 에서 오나, 상수인가, 아직 자리만 있나?
  3. 파일 자리 — 바뀌는 이유가 URL 인가, 상품 업무인가, 모두인가?
  4. 선행 — 무엇이 먼저 합쳐져 있어야 하나?
  5. 끝 판정 — 무슨 명령이나 화면으로 “끝”을 확인하나?
조각 (날짜) 1 범위선 2 데이터 출처 3 파일 자리 4 선행 5 끝 판정
갤러리 (10-14) 안 자리만 (회색 상자 6칸) domains/products/components 셸 레이아웃 썸네일 2번을 누르면 큰 이미지가 바뀐다
구매 패널 (10-15) 안 — 담기는 이동만 상수 (정가 259,000) domains/products/components 갤러리 PR 판매가 246,050 과 총액이 같다
mock 연결 (10-16) 안 mock → 변환 함수 types · api · app/(shop)/product/[id] 메인 mock 머지 /product/1~10 이 뜨고 /product/999 는 404
탭 (10-19) 안 자리만 (임시 문구) domains/products/components mock 연결 탭 세 개가 그려지고 첫 탭만 활성
설명 표 · 추천 (미착수) 안 없음 같은 폴더에 0바이트 파일 탭 —

표의 2열이 핵심이에요. 네 조각 중 mock 에서 오는 값은 상품명과 이미지뿐이고, 가격·사이즈·혜택은 패널 안의 상수예요. 2편에서 데이터 구조를 세우면 이 열이 전부 “DB” 로 바뀌어야 하고, 그때 고치는 칸은 3단 중 데이터 접근 한 곳이어야 해요. 그래서 10월 16일에 변환 함수를 따로 뺐던 거고요.

아래는 같은 캡처의 아랫부분, 10월 19일에 붙인 탭이에요. 상품 설명 자리가 임시 문구인 것이 5열 “자리만”의 실제 모습이에요.

상품 상세 페이지 아래쪽 탭 영역 캡처. 상품 설명, 리뷰 [0], 문의 [0] 세 탭 중 첫 탭에 굵은 밑줄이 있고, 그 아래 회색으로 상품 설명이 들어갈 자리입니다 라는 임시 문구가 있다 같은 /product/3 캡처의 아랫부분. 탭은 열렸지만 설명 표 파일은 아직 0바이트라 임시 문구를 넘겼다

처음 설계는 무엇이 틀렸고 어떻게 고쳤나?

10월 14일 아침의 제 계획은 레퍼런스를 위에서부터 그대로 옮기는 거였어요. 파일 여섯 개를 한 번에 만들고, 빈 파일도 자리 잡기용으로 같이 올리고, 페이지 파일 안에서 mock 을 직접 찾고, 태블릿 폭부터 2단으로 벌리고, 개발 서버에서 뜨면 끝. 작업일지에 남은 결정은 네 군데에서 달라요.

항목 전 (10-14 아침) 무엇이 틀렸나 후 (작업일지의 결정)
커밋 단위 6파일 한 PR, 빈 파일 포함 빈 파일이 공용 브랜치에 가면 누가 작업 중인지 헷갈리고, 가져다 쓰면 import 가 깨진다 9층으로 해부해 6파일을 설계하되 갤러리 하나만 PR. 빈 파일 3개는 일부러 제외
파일 역할 페이지가 mock 을 직접 읽음 출처가 바뀌면 페이지마다 고쳐야 하고, 404 가 데이터 함수에 묶인다 타입 · 접근 · 라우트 3단. 변환은 접근 함수 한 곳, 404 는 라우트
반응형 768px 부터 2단 768px 에서 패널 폭이 약 360px 이 되어 라벨과 값이 겹친다. sticky 는 grid 칸이 늘어나 움직일 틈이 0 1024px 부터 2단, 갤러리 칸에 self-start 를 짝으로
빌드 dev 에서 뜨면 끝 저장소의 Next 16 은 cacheComponents 가 켜져 있어 params 를 요청 값으로 본다. dev 는 통과하고 build 만 깨진다 일단 export const instant = false 로 통과. 바른 구조는 바깥 Suspense + 안쪽 async 컴포넌트

네 번째 줄은 좀 더 적을게요. 10월 16일 기록에 따르면 next build 가 “prerender(빌드 때 미리 그리기) 중에 요청 값에 닿았다”는 오류를 냈고, 오류 메시지가 고칠 방법을 셋 제시했어요. 저는 셋 중 “이 페이지는 기다려도 된다”는 선언과 Suspense(기다리는 동안 자리표시를 보여 주는 리액트 장치) 감싸기를 함께 넣어 통과시켰는데, 같은 날 복사본에서 하나씩 빼 보니 통과시킨 건 선언 쪽이었어요. Suspense 는 await 가 끝난 뒤의 JSX(화면 조각을 적는 문법)를 감싸고 있어서 기다리는 동안엔 존재하지 않았거든요. dev 에서 뜬 것과 build 가 통과한 것은 다른 사실이고, 고친 뒤에는 “무엇이 실제로 고쳤는가”를 하나씩 빼 보며 확인해야 한다는 걸 여기서 배웠어요.

교정 전후. 왼쪽 점선 상자에 10-14 아침의 생각 네 가지, 오른쪽 상자에 작업일지에 남은 결정 네 가지가 커밋 단위, 파일 역할, 반응형, 빌드 네 화살표로 대응되어 있다 네 줄 모두 “레퍼런스를 그대로”에서 “역할과 판정으로”로 옮겨 간 것이다

직접 겪은 함정은 무엇이었나?

전부 제 작업일지와 이 글을 쓰며 돌린 명령에서 나온 것이에요.

1. 명령을 엉뚱한 폴더에서 실행했다. 10월 14일 기록에 따르면 그날 난 문제 8건 중 코드 버그는 2건이었고, 2건은 상위 폴더에서 git status 와 npm run dev 를 돌린 거였어요. 상위 폴더가 다른 저장소라 전혀 다른 파일 목록이 떴고, 서버는 안 떴어요. 질문은 늘 “지금 어느 폴더에서, 누구 권한으로”예요.

2. 0바이트 파일이 팀 전체 타입 검사를 깨뜨렸다. 같은 날 tsc --noEmit 이 라우트 파라미터 오류를 내서 제 [id] 페이지를 의심했는데, 첫 번째 오류를 읽어 보니 팀원 폴더의 page.tsx 가 “모듈이 아니다”였어요. 빈 파일이었던 거죠. 두 번째 오류는 그 2차 오류였고요. 10월 19일에는 반대로 제가 빈 파일을 import 하려다 TS2304 를 만났어요. 파일이 있다와 쓸 수 있다는 다른 말이라, 지금은 쓰기 전에 wc -c 로 크기를 봐요.

3. URL 의 id 는 문자열이다. /product/3 의 3 은 '3' 으로 오고 mock 의 id 는 숫자예요. 그대로 비교하면 영원히 못 찾아요. 변환은 한 곳에서.

4. 이미 있는 것을 또 추가할 뻔했다. 10월 16일 “위로 가기” 버튼을 상세 페이지에 넣으려다 먼저 어디서 오는지 찾아보니 (shop) 레이아웃이 이미 모든 하위 페이지에 넣고 있었어요. 넣었으면 버튼이 둘이 됐을 거예요. “어디에 추가할까”보다 “이미 어디서 오고 있나”가 먼저예요.

5. 없는 주소 화면이 기본값이다. 이 글을 쓰며 복사본에서 /product/999 를 열어 봤더니 404 화면은 떴지만 저희가 만든 화면이 아니라 프레임워크 기본 화면이었어요. 위 트리에 not-found.tsx 가 없는 것과 같은 말이에요. 수업의 끝 판정 “없는 주소에서 안내 화면”은 반만 통과한 셈이에요. 그리고 10월 16일 기록에 따르면 프로덕션 빌드에서는 이 화면의 HTTP 상태 코드가 404 가 아니라 200 이었어요. 화면과 상태 코드는 별개로 확인해야 해요.

프레임워크 기본 404 화면 캡처. 큰 글씨 404 와 세로선, 그 옆에 This page could not be found 문장 2026-10-19 복사본의 /product/999. 안내는 뜨지만 우리 화면은 아니다

6. 카드가 없는 페이지로 가고 있었다. 메인 카드의 링크가 원래 mock 에 적힌 가상의 주소를 가리켜 전부 404 였어요. 팀원 파일이라 링크 한 줄만 고치고 나머지는 기록으로 남겼어요. 다른 사람 파일은 내 일에 꼭 필요한 최소만 건드리고, 그것도 PR 과 일지에 적는 것이 팀의 경계예요.

요약

열흘 프로젝트는 첫 이틀에 스펙, 일 목록, 구조를 종이에 적고 공통 세팅을 한 사람이 올린 뒤에 갈라진다.

스펙의 핵심은 “안 만들 것”의 목록이고, 일의 핵심은 “끝났다”를 판정할 명령이며, 폴더의 핵심은 “누가 바꾸나”라는 질문이다.

데이터는 타입, 접근, 라우트 세 단으로 흐르고, 출처가 mock 에서 DB 로 바뀔 때 고치는 칸은 접근 한 곳이어야 한다.

한 사람이 한 화면, 한 도메인 폴더, 한 브랜치를 맡으면 PR 은 서로의 파일을 건드리지 않는다.

dev 에서 뜬 것과 build 가 통과한 것은 다른 사실이다. 매일 build 를 돌린다.

다음 글은 2편 백엔드 — 데이터 구조와 ERD, 도메인 구조, 고객 여정이에요. 이 글의 스펙 표 “보이는 데이터” 열에서 명사를 뽑아 표와 관계선으로 세우고, 위 적용 표의 2열을 전부 “DB” 로 바꾸는 준비를 합니다. 3편은 그 데이터 구조에 맞춘 프론트엔드, 4편은 프론트와 백엔드를 실제로 잇는 이야기예요.

출처 — 개념은 프론트엔드 수업의 프로젝트 안내와 팀 프로젝트 구현 가이드, 협업 규칙에서 배운 것을 제 말로 다시 썼다. 폴더 구조와 일정 관련 위키 페이지는 같은 수업의 두 자료가 근거라 독립 확증이 아니며(위키 신뢰도 표시 low), 4일 로드맵과 3페이지 흐름은 단일 교안 기준이다. 증거는 2026-10-14 · 15 · 16 · 19 작업일지와 10-16 회고, 그리고 2026-10-19 에 팀 저장소 복사본에서 직접 뽑은 폴더 트리 · 커밋 수 · 화면 캡처다. 저장소 이름과 팀원 정보는 적지 않았다.

관련 키워드

Next.js App Router팀 프로젝트쇼핑몰 클론프로젝트 구조 설계도메인 폴더 구조Git 브랜치 전략스펙 정의mock 데이터

앤디의 마케팅노트

마케팅을 시스템으로 만드는 과정을 매주 기록해요. 잘 된 것도, 안 된 것도 같이 봐요.

maily에서 구독하기