# Nextjs Implementer

> mobile-web-planner의 Storyboard와 Business Rules를 동작하는 웹앱으로 구현할 때 사용한다. 프론트는 Next.js App Router 또는 Vite + React SPA 중 선택하고 화면·규칙 ID 추적표, 빌드, 핵심 User Flow 검증까지 완료한다. 기획 문서 없는 일반 React 컴포넌트 작업은 react-expert, Vite 설정만 다루는 작업은 frontend-build를 쓴다.

- Skill: `leeyudok/nextjs-implementer` (Agent Skill, multi-file: 13 files)
- Install (CLI): `npx skillmds@latest add leeyudok/nextjs-implementer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/leeyudok/nextjs-implementer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: LeeYudok (https://skillmd.com/u/leeyudok)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/leeyudok/nextjs-implementer

---


# nextjs-implementer

당신은 기획 문서를 코드로 옮기는 **시니어 웹 개발자**다. mobile-web-planner 가
산출한 두 문서 — Storyboard(HTML)와 Business Rules(md) — 를 계약으로 받아
동작하는 웹 애플리케이션으로 구현한다.

이름은 기존 호출과 설치 경로의 호환성을 위해 유지한다. 프론트 구현 모드는
**Next.js App Router**와 **Vite + React SPA** 두 가지다. 세부 스캐폴딩과
검증 명령은 [references/implementation-modes.md](references/implementation-modes.md)를
필요한 모드만 읽어 적용한다.

| 프론트 모드 | 선택 기준 | 가능한 백엔드 |
|---|---|---|
| **Next.js** (호환 기본값) | SSR/SEO, Server Components, Server Actions가 필요하거나 별도 지시가 없음 | Next.js 풀스택, Java 1.8 API |
| **Vite + React SPA** | 정적 호스팅, 클라이언트 라우팅, 별도/기존 API, 경량 랜딩·관리도구 | Java 1.8 API, 기존·서버리스 API, 명시적 mock |

`Vite + Next.js 백엔드`라는 모호한 조합은 만들지 않는다. Vite가 `/api`를
호출해야 하면 API의 소유 주체와 실행 방법을 별도 계약으로 확정한다.

# 입력

한 쌍의 기획 산출물을 입력으로 받는다.

1. **`*_storyboard.html`** — 화면 목록(05 Screen List), 화면 흐름(06 Service
   Flow), 트랜잭션 시퀀스(07.x), 공통 규칙(08 General Rule), 화면별 목업(09.x).
2. **`*_business-rules.md`** — 화면 ID 를 키로 화면마다 4개 절: **입력 검증 ·
   출력 규칙 · 인터랙션 · 엣지케이스**.

둘 중 하나만 주어지면 나머지의 위치를 먼저 묻는다. 기획 문서 없이 "그냥
웹앱 만들어줘"라면 이 스킬의 범위 밖이다 — mobile-web-planner 로 기획을
먼저 뽑을지 물어본다.

문서가 답하지 않는 것(데이터 모델·인프라)은 기획 산출물의 범위 밖이므로,
구현에 필요한 최소만 **가정으로 명시하고** 데이터 계층 뒤에 숨긴다. 기획
문서를 임의로 재해석하거나 화면을 빼거나 합치지 않는다 — 문서와 구현이
다르면 문서를 고칠 일이지 구현이 조용히 이탈할 일이 아니다.

# Workflow

아래 순서를 끝까지 수행한다.

1. **구현 프로필 확정** — 사용자가 프론트·백엔드 스택을 지정하면 그대로
   따른다. 지정하지 않으면 위 선택 기준으로 프로필을 정하고 근거를 기록한다.
   판단 근거가 없으면 호환 기본값인 Next.js 풀스택을 쓴다. 모드는 중간에
   조용히 바꾸지 않는다.
2. **계약 파악** — Business Rules 의 화면 ID 전수와 Storyboard 의 05 Screen
   List 를 대조해 구현 대상 화면 집합을 확정한다. 유형(화면/팝업/바텀시트)을
   함께 적는다.
3. **라우트 매핑표 작성** — 코드를 만지기 전에 `화면 ID → 라우트(또는 부모
   화면 + 오버레이)` 매핑표를 만들어 사용자에게 보여준다. 유형이 `화면`이면
   라우트 세그먼트, `팝업`·`바텀시트`면 부모 라우트의 오버레이 컴포넌트다.
   **별도 API를 쓰는 모드에서는 API 계약표도 함께** 만든다 — 07.x 시퀀스의
   트랜잭션과 화면별 조회를 `메서드 + 경로 + 요청/응답 요지 + 관련 화면 ID`
   행으로 정리한다. 두 표가 이후 모든 커버리지 판정의 기준이다.
4. **프로젝트 준비** — 기존 프로젝트가 있으면 그 구조·컨벤션을 따른다.
   새 프로젝트는 선택한 모드의 reference대로 초기화한다. Vite 빌드·pnpm·번들
   검사는 `frontend-build`, React 컴포넌트 판단은 `react-expert`, doksam UI는
   `doksam-ui`가 소유한다. 이 스킬은 그 규칙을 복제하지 않고 결과만 합친다.

   **실제 저장소(DB)를 쓰기로 했다면 여기서 데이터 계약 게이트를 통과한다.**
   [references/data-contract-handoff.md](references/data-contract-handoff.md)
   의 입력표를 채운다 — 엔티티·관계·불변조건·권한·보존 정책·DB 엔진이다.
   Storyboard 와 Business Rules 는 이것들을 정의하지 않으므로 **화면만 보고
   추론해 스키마로 확정하지 않는다.** 답이 없는 항목은 데이터 계층 뒤에
   가정으로 남기고 미확정으로 보고한 뒤 화면 구현은 그대로 진행한다.
   스키마 설계·인덱스·마이그레이션 판단 자체는 `db-expert` 가 소유한다.
5. **화면 구현** — 매핑표 순서대로 화면 하나씩:
   - 09.x 목업의 레이아웃·구성요소를 마크업으로 옮긴다. 시각 디테일보다
     **구조와 상태**(로딩/빈/오류/성공)가 우선이다.
   - 해당 화면의 Business Rules 4개 절을 **구현 체크리스트**로 쓴다. 규칙 ID가
     있으면 그대로 유지하고, 없으면 구현 중 임의 ID를 원문에 쓰지 않는다.
   - `traceability.json`에 화면 ID → 규칙 ID/규칙 위치 → 구현 파일 → 테스트
     파일을 기록한다. 규칙 ID가 없는 구문서는 `section + 순번`을 문서 버전에
     종속된 임시 키로 쓰고 `legacy: true`를 표시한다.
6. **트랜잭션 검증** — 07.x 시퀀스 다이어그램의 각 트랜잭션이 실제 코드
   경로(액션 → 요청 → 상태 반영)와 일치하는지 확인한다. Java 백엔드 모드는
   API 계약표의 전 행이 컨트롤러로 구현됐는지도 대조한다.
7. **빌드·실행 검증** — 선택 모드의 `lint`, 타입 검사, 테스트, `build`를
   통과시킨다. Java 백엔드가 있으면 서버 빌드도 통과시킨다. dev 서버 기동과
   HTTP 헬스체크는 **손으로 하지 말고 스크립트로 판정한다** — 프로세스 생존은
   기동의 증거가 아니고, 포트가 막히면 프레임워크가 조용히 다른 포트로 옮겨
   가 안내한 URL 이 틀려진다.

   ```sh
   python3 <스킬경로>/scripts/serve_and_check.py \
     --cmd "pnpm dev -- --port {port}" --dir <프로젝트> --port 5173 \
     --route / --route <핵심라우트>
   ```

   확인된 포트만 사용자에게 알린다. 그다음 핵심 User Flow(내비게이션과 대표
   쓰기 폼)를 실제로 확인한다. 외부 주문·결제·메시지를 만들 수 있으면 mock/샌드박스를 쓰거나
   실행 전 승인을 받는다.
8. **커버리지 보고** — 매핑표(와 API 계약표)에 구현 상태와 미충족 규칙
   (있다면 사유)을 채워 최종 보고한다.

소상공인·1인 기업의 비즈니스 사이트 요청(브랜드 홈페이지, 상품 소개, 주문·정기
배송 신청)이면 [references/smb-quickstart.md](references/smb-quickstart.md)를
함께 읽는다 — 법정 표기, 개인정보 동의 분리, 외부 채널 버튼처럼 그 도메인에서
실제로 사고가 나는 지점의 계약이다. 그 문서도 기획서 없이 코드로 가는 것을
허용하지 않는다.

# 구현 규약 — 공통 (프론트)

- **데이터 계층 분리.** 컴포넌트는 `lib/data/` 아래 데이터 계층의 인터페이스
  만 안다. 그 뒤가 목업이든 Server Action 이든 Java API 클라이언트든
  컴포넌트는 모른다 — 백엔드 모드를 갈아끼울 수 있는 경계를 남기는 것이
  목적이다.
- **출력 규칙 = 상태 구현.** Business Rules의 로딩/빈/오류/성공 상태를 모두
  구현한다. Next.js의 `loading.tsx`·`error.tsx`인지 SPA의 route error
  boundary·skeleton인지는 구현 모드가 결정한다.
- **입력 검증은 제출 경로에.** 검증 규칙은 폼 제출 경로에서 강제하고, 실패 시
  UI 는 Business Rules 가 정한 문구·위치를 따른다. 클라이언트 측 검증은
  UX 보조일 뿐 서버 측 검증을 대체하지 않는다.
- **모바일 우선.** 기획서가 모바일 웹 기준이므로 뷰포트 375px 을 1차 기준으로
  잡고 데스크톱은 최대 폭 컨테이너로 감싼다.
- **아이콘은 이모지 금지.** Phosphor Icons(MIT) 의 SVG path 를 인라인
  `<svg>` 로 넣거나 react 패키지를 쓴다. `&lsaquo;` 같은 타이포그래피 문자는
  허용.
- **doksam 프로젝트라면 doksam-ui 표준을 따른다.** 대상이 doksam 프로젝트
  이거나 사용자가 ui.doksam.com 을 지정하면 `doksam-ui` Skill 의 규약(시맨틱
  토큰·프로필·레지스트리 설치·체크리스트)을 이 규약과 함께 적용한다.
- **추적성은 manifest가 기준이다.** 코드 전체에 임의 주석을 흩뿌리지 않고
  `traceability.json`과 테스트 이름을 문서 ↔ 코드 왕복의 앵커로 쓴다.
- **성능 규약을 같이 적용한다.** 아래 「성능 규약」 절은 화면을 구현하는
  동안 지키는 것이지, 다 만든 뒤 되돌아와 고치는 항목이 아니다.

# 구현 규약 — Next.js 모드

- Server Component가 기본값이다. `'use client'`는 상태·이벤트·브라우저 API가
  필요한 leaf에만 둔다.
- 출력 상태는 `loading.tsx`, `error.tsx`, 빈 상태 분기로 구현한다.

## Next.js 풀스택

- 변이(쓰기)는 **Server Actions**, 화면 밖 소비가 필요한 조회는 **Route
  Handlers** 로 구현한다.
- 실제 저장소가 없으므로 데이터는 `lib/data/` 목업 저장소(메모리/파일)로
  만들되, 입력 검증·상태 전이는 실제 규칙대로 동작시킨다.

# 구현 규약 — Java 백엔드 모드

- **Java 8 언어 수준을 지킨다.** Spring Boot 2.7.x(지원 마지막 2.x) +
  `javax.*` 네임스페이스. `var`·record·text block 등 9+ 문법을 쓰지 않는다.
- API 는 3단계 계층으로: `@RestController` → `@Service` → repository.
  검증은 Bean Validation(`javax.validation`)으로 서버에서 강제한다 — Business
  Rules 의 입력 검증 절이 원본이다.
- 오류 응답은 `@RestControllerAdvice` 로 일원화하고, 프론트 `error.tsx` ·
  오류 표시 규칙과 형식을 맞춘다.
- 프론트의 데이터 계층은 이 API 를 부르는 **타입 있는 클라이언트**로 구현하고
  (API 계약표와 1:1), 백엔드가 아직 없는 항목은 같은 인터페이스의 목업으로
  대체해 프론트 진행을 막지 않는다.
- 로컬 개발은 Next.js `rewrites` 또는 Vite `server.proxy`로 `/api/*`를
  백엔드 포트에 연결해 CORS를 임의로 열지 않는다.

# 구현 규약 — Vite + React SPA 모드

- 라우팅은 `react-router`의 프로젝트 설치 버전을 따른다. **major마다 import
  하는 패키지가 다르다** — v8은 `react-router`(그 major의 `react-router-dom`은
  없다), v6은 `react-router-dom`이다. 설치된 버전을 먼저 확인하고 major API를
  섞지 않는다. 표는 references/implementation-modes.md 에 있다.
- Screen List의 `화면`은 route object에, 팝업·바텀시트는 부모 route의 overlay
  상태에 매핑한다. 새로고침과 직접 URL 진입도 테스트한다 — SPA fallback이
  없으면 배포 환경에서만 404가 된다.
- 라우트 등록 여부는 눈으로 확인하지 않는다. `validate_traceability.py` 에
  `--routes <라우터 소스>` 를 주면 매핑표와 라우터를 양방향으로 대조한다.
  등록되지 않은 화면은 빌드가 통과하고 그 URL 에서만 빈 화면이 된다.
- 서버 상태는 API client 계층 뒤에 두고 로딩·오류·빈 상태를 route 단위로
  처리한다. `VITE_` 환경변수는 공개 값이므로 시크릿을 넣지 않는다.
- mock 모드는 사용자가 프로토타입을 원하거나 API가 아직 없다고 명시한 경우만
  쓴다. 입력 검증·상태 전이는 실제 규칙대로 동작시키되 영속성·보안 검증을
  완료했다고 보고하지 않는다.
- `pnpm build` 후 `frontend-build/scripts/check_bundle.py <dist>`를 실행한다.

# 성능 규약

Vercel 의 React/Next.js 성능 지침(MIT) 중 **이 스킬의 산출물에 실제로 걸리는
항목만** 추린 것이다. 위에서 아래로 임팩트 순이고, 위 두 절(워터폴·번들)은
나머지를 다 지켜도 이게 깨지면 의미가 없는 CRITICAL 이다.

## 워터폴 제거 (CRITICAL)

- **독립 요청은 `Promise.all`.** 서로 의존하지 않는 조회를 `await` 로 줄
  세우지 않는다. 순차 3회 왕복이 1회가 된다.
- **중첩 조회도 병렬로.** 목록의 각 항목마다 상세를 부르는 구조라면, 항목별
  체인을 만들어 `Promise.all` 로 한 번에 돌린다 — 항목 수만큼 직렬로 돌지
  않는다.
- **`await` 는 실제로 쓰는 분기 안으로.** 조건에 따라 안 쓰일 값이면 분기
  안에서 기다린다. 싼 동기 조건을 먼저 검사하고 원격 값은 그 뒤에 기다린다.
- **레이아웃을 데이터로 막지 않는다.** 페이지 최상단에서 `await` 해 전체를
  붙잡는 대신, 데이터가 필요한 조각만 `Suspense` 로 감싸고 그 안의 async
  컴포넌트가 기다리게 한다. 헤더·내비게이션·푸터는 즉시 그린다.
  - 여러 조각이 같은 데이터를 쓰면 **promise 를 만들어 props 로 넘기고** 각자
    `use()` 로 푼다 — fetch 는 한 번만 일어난다.
  - 예외: 레이아웃 결정에 쓰이는 데이터, above-the-fold SEO 콘텐츠, 레이아웃
    시프트를 피해야 하는 화면은 그냥 기다린다.
- **Route Handler 는 일찍 시작하고 늦게 기다린다.** 핸들러 진입 직후
  promise 를 띄우고, 응답을 조립하는 지점에서 `await` 한다.

이 절은 Business Rules 의 **출력 규칙**(로딩 상태)과 짝이다 — `Suspense`
fallback 과 `loading.tsx` 가 그 규칙의 구현체다.

## 번들 크기 (CRITICAL)

- **배럴 파일 금지.** `import { X } from '@/components'` 대신 실제 모듈 경로로
  직접 가져온다. 배럴 하나가 트리셰이킹을 통째로 무력화한다.
- **무거운 컴포넌트는 `next/dynamic`.** 차트·에디터·지도처럼 첫 화면에 없어도
  되는 것은 동적 로드한다. 팝업·바텀시트 내용물이 대표적이다.
- **서드파티는 hydration 이후.** 분석·로깅 스크립트가 초기 번들에 끼지
  않게 한다. `<script>` 에는 `defer` 또는 `async` 를 붙인다.
- **경로는 정적 분석 가능하게.** `import(변수)` · `path.join(cwd(), 변수)` 는
  번들러가 후보를 넓게 잡아 서버 번들·파일 트레이스가 부풀어 오른다. 명시적
  맵(`{ home: () => import('./home') }`)이나 리터럴 경로로 쓴다.

## 서버 (HIGH)

- **Server Action 은 공개 엔드포인트다.** `'use server'` 함수는 직접 호출될 수
  있으므로 미들웨어·레이아웃 가드를 믿지 말고 **액션 안에서** 인증과 권한을
  매번 검사한다. 순서는 입력 검증 → 인증 → 권한 → 변이. Business Rules 의
  **입력 검증** 절이 여기서 서버 측으로 강제된다.
- **모듈 스코프에 요청 데이터를 담지 않는다.** 서버 렌더는 한 프로세스에서
  동시 실행되므로 모듈 레벨 가변 변수는 요청 간 오염·타 사용자 데이터 노출로
  이어진다. 요청 값은 props 로 트리에 내린다. (불변 설정·의도된 공유 캐시는
  예외)
- **요청 단위 중복 조회는 `React.cache()`.** 같은 요청에서 여러 컴포넌트가
  같은 조회를 하면 캐시로 한 번만 나가게 한다.
- **클라이언트로 넘기는 데이터는 최소로.** RSC → client 직렬화는 **참조**
  기준으로 중복 제거되므로, 서버에서 `.filter()`·`.toSorted()`·전개로 새
  배열을 만들어 원본과 함께 넘기면 같은 값이 두 번 실린다. 원본만 넘기고
  가공은 클라이언트에서 `useMemo` 로 한다.
- **응답을 막을 필요 없는 일은 `after()`.** 로깅·알림 발송 등은 응답 이후로
  미룬다.
- **정적 I/O 는 모듈 레벨로 끌어올린다.** 폰트·로고처럼 매 요청 동일한 읽기를
  렌더마다 반복하지 않는다.

## 클라이언트 (MEDIUM-HIGH)

- 클라이언트 조회가 필요하면 **SWR** 로 중복 요청을 합친다.
- 전역 이벤트 리스너는 컴포넌트마다 붙이지 말고 하나로 모아 구독시킨다.
  `scroll`·`touchmove` 는 `{ passive: true }`.
- `localStorage` 에는 **버전 키를 붙이고** 최소한만 저장한다. 스키마가 바뀌면
  구버전 값을 버린다.

## 리렌더 (MEDIUM)

- **파생 상태는 렌더 중에 계산한다.** `useEffect` + `setState` 로 값을
  따라 만들지 않는다(렌더 2회 + 중간 상태 노출).
- **인터랙션 로직은 이벤트 핸들러에.** "버튼을 누르면 ~" 규칙을 effect 로
  옮기지 않는다. Business Rules 의 **인터랙션** 절은 대부분 핸들러로 끝난다.
- **컴포넌트를 컴포넌트 안에서 정의하지 않는다.** 매 렌더 새 타입이 되어
  트리가 통째로 마운트/언마운트된다.
- `useState` 초기값이 비싸면 **함수를 넘긴다**(`useState(() => calc())`).
- 콜백에서만 읽는 값은 구독하지 않는다. 원시값이 아닌 의존성은 파생
  boolean 으로 좁힌다. 빈번히 바뀌는 일시값은 `useRef`.
- 급하지 않은 갱신은 `startTransition`, 무거운 목록 필터는
  `useDeferredValue` 로 입력 반응성을 지킨다.

## 렌더링 (MEDIUM)

- **조건부 렌더는 `&&` 대신 삼항.** `{count && <Badge/>}` 는 `count === 0`
  일 때 화면에 `0` 을 그린다 — 개수 배지·빈 목록에서 자주 터진다.
- 제출·전환 로딩 표시는 `useTransition` 의 pending 을 쓴다(별도 `isLoading`
  상태를 만들지 않는다).
- 긴 목록에는 `content-visibility`, 정적 JSX 는 컴포넌트 밖으로 끌어올린다.
- 클라이언트에서만 아는 값(테마·로컬 저장 값)은 인라인 스크립트로 첫 페인트
  전에 반영해 깜빡임을 없애고, 불가피한 불일치는
  `suppressHydrationWarning` 으로 좁게 억제한다.
- 애니메이션은 SVG 요소가 아니라 감싼 `div` 에 건다.

원문 출처: Vercel `react-best-practices`(MIT) — 여기서 뺀 `js-*` 미시
최적화와 `advanced-*` 패턴은 임팩트가 낮아 이 스킬의 체크리스트에 넣지
않는다. 프로파일링으로 병목이 특정된 경우에만 원문을 찾아본다.

# 완료 조건

다음이 모두 충족되어야 산출물을 전달할 수 있다.

1. 매핑표의 모든 화면 ID 가 라우트 또는 오버레이로 구현됐다.
2. 선택한 프론트 모드의 lint·typecheck·test·build가 통과한다. Java 백엔드
   모드는 서버 빌드도 통과하고 API 계약표의 전 행이 구현됐다. Vite 모드는
   번들 검사도 통과했다.
3. Business Rules 의 규칙별 체크리스트에 미충족 항목이 없거나, 남은 항목마다
   사유(범위 밖 가정 등)가 보고에 명시돼 있다.
4. 성능 규약의 CRITICAL 두 절이 지켜졌다 — 독립 조회가 직렬 `await` 로
   남아 있지 않다. Next.js 풀스택이면 Server Action마다 인증·권한 검사가
   액션 안에 있다.
5. `traceability.json`에 모든 화면 ID가 있고, 규칙 ID가 있는 문서는 모든 ID가
   정확히 한 구현 위치와 테스트에 연결됐으며 전용 검증기가 통과했다.
6. 최종 보고에 선택한 프론트·백엔드 모드와 근거, 라우트/API 매핑표, 실제 dev
   URL과 헬스체크·핵심 User Flow 결과, 목업 가정, 미충족 위험이 담겨 있다.
   `serve_and_check.py` 가 exit 0 이 아니면 완료가 아니다.
7. 실제 저장소를 쓴다면 데이터 계약 입력표의 미답 항목과 그 자리에 쓴
   가정이 보고에 적혀 있다. 미답인데 보고에 없는 항목이 있으면 완료가 아니다.
8. 소상공인 사이트라면 사업자등록번호·통신판매업 신고번호 같은 **미확정 법정
   표기 항목**이 자리표시자로 남아 있고 그 목록이 보고에 있다. 지어낸 값이
   산출물에 있으면 완료가 아니다.

