apps/web에서 화면을 작성하는 디자이너·개발자·AI를 위한 가이드입니다. 제품 목적과 대상은
제품 맥락, 설치·실행과 기술 구성은 README를 참고합니다.
회원 앱의 페이지 경로·화면 상태 분류와 쿼리스트링 정책은 라우팅과 URL 상태 규칙에서 확인합니다. 해당 문서는 구현 완료 범위와 후속 기능 정책, 검증 결과를 구분합니다.
13개 회원 페이지의 라우트 진입점과 화면 슬라이스를 구성했습니다. 루트 레이아웃은
apps/web/app, 화면 코드는 apps/web/src/pages, Provider·전역 스타일·공통 페이지 레이아웃은
apps/web/src/app에 있습니다. 페이지에는 제목과 공통 준비 중 안내를 제공합니다.
홈·일정·공지·마이페이지는 하단 메뉴를 공유하며 홈과 마이페이지에는 바로가기 링크를 제공합니다.
상세 화면에는 내부 이력 또는 고정 상위 경로로 돌아가는 뒤로가기를 제공합니다. 실제 본문 기능은 후속 단계입니다.
로컬 개발에서는 홈의 개발용 링크와 /dev/routes에서 전체 화면을 확인할 수 있습니다.
프로덕션 빌드에서는 개발용 목록에 404를 반환합니다. 미정의 주소에는 공통 404 안내와 홈 링크를 제공합니다.
적용 대상은 사용자 웹 앱입니다. 관리자·모바일 앱의 폴더 구조를 이 문서에 맞춰 변경하지 않습니다.
FSD의 app, pages, features, shared를 사용합니다. 화면 단위로 작업을 시작하고 실제
재사용이 확인되면 분리합니다. widgets, entities는 도입하지 않습니다.
| 위치 | 역할 |
|---|---|
apps/web/app |
Next.js 라우트, 문서 레이아웃, 메타데이터 등 프레임워크 진입점 |
apps/web/src/app |
Provider, 전역 초기화, 공통 레이아웃과 스타일 연결 |
apps/web/src/pages/<화면> |
화면과 해당 화면에서만 사용하는 코드 |
apps/web/src/features/<기능> |
여러 화면에서 재사용하는 사용자 행동 |
apps/web/src/shared |
웹 내부에서 공유하는 도메인 비의존 코드 |
packages/ui |
SEED 기반 공용 UI와 스타일·폰트·테마 계약 |
packages/api |
공용 API 계약과 클라이언트 |
packages/app-bridge |
WebView와 네이티브 사이의 브리지 계약 |
pages/home처럼 하나의 화면이나 기능을 묶는 폴더를 슬라이스라고 합니다.
슬라이스 안에서는 ui, model처럼 코드의 역할에 따라 폴더를 나눕니다.
app과 shared는 화면별 슬라이스를 만들지 않고 역할별로 구성합니다.
apps/web/
├── app/
│ ├── layout.tsx
│ ├── not-found.tsx
│ ├── dev/routes/page.tsx # 로컬 개발 전용 목록
│ ├── (tabs)/ # 홈·일정·공지 목록·마이페이지
│ │ ├── layout.tsx
│ │ └── page.tsx # 홈의 / 경로
│ ├── notices/ # 공지 상세
│ │ └── [noticeId]/page.tsx
│ ├── my/ # 출석 내역·출석 규칙
│ │ └── attendance/
│ │ ├── page.tsx
│ │ └── rules/page.tsx
│ ├── settings/
│ │ └── page.tsx
│ └── (auth)/ # 로그인·가입
│ └── layout.tsx
├── pages/
│ └── README.md
└── src/
├── app/
│ ├── providers.tsx
│ ├── global.css.ts
│ ├── navigation/ # 내부 방문 이력과 뒤로가기
│ └── layouts/
│ ├── page-shell.tsx
│ ├── page-shell.css.ts
│ ├── tabs-layout.tsx
│ ├── bottom-navigation.tsx
│ └── detail-layout.tsx
├── pages/
│ ├── home/
│ │ ├── index.ts
│ │ └── ui/
│ │ ├── home-page.tsx
│ │ └── home-page.test.tsx
│ └── … # 나머지 페이지도 개별 슬라이스로 구성
└── shared/
└── ui/
└── page.css.ts
루트의 app은 Next.js가 URL과 연결하는 위치이고, src/app은 앱의 전역 구성을 담당합니다.
라우트의 page.tsx는 src/pages의 화면을 연결하고 화면 구현은 해당 슬라이스에 둡니다.
라우트 그룹 (tabs), (auth)는 URL에 포함되지 않습니다. 위 트리는 두 그룹의 화면별 하위
경로를 생략했습니다. (tabs)/layout.tsx는 탭 이동 사이에 유지되는 공통 레이아웃이며,
TabsLayout에서 PageShell과 하단 메뉴를 조합합니다. 페이지 전환 애니메이션 자체는 아직
구현하지 않았습니다.
포괄적인 (detail) 그룹은 사용하지 않습니다. 공지 상세·출석·설정은 각각 notices, my,
settings 아래에 배치합니다. 각 상세 라우트의 page.tsx에서 DetailLayout과 화면을 조합하고
직접 접근 시 복귀 경로를 전달합니다. 출석 내역과 규칙에 서로 다른 복귀 경로를 지정하고 중첩
헤더를 피하기 위한 구조입니다. DetailLayout은 PageShell의 뒤로가기 슬롯을 사용합니다.
(tabs)는 PageShell의 내비게이션 슬롯에 하단 메뉴를 넣고, (auth)는 기본 셸을 사용합니다. 헤더와 safe-area는 셸에서 한 번만
적용하고 페이지 본문에 중복 적용하지 않습니다. 여러 준비 중 페이지의 도메인 비의존 스타일은
shared/ui/page.css.ts를 사용합니다.
apps/web/pages/README.md는 Next.js가 src/pages를 Pages Router로 인식하지 않도록 루트
pages 폴더를 유지하는 이유를 설명합니다. 이 폴더에는 Pages Router 라우트를 추가하지 않습니다.
새 features 슬라이스와 shared 하위 모듈은 실제 사용처가 생길 때 생성합니다. 빈 레이어·역할별 폴더·슬라이스 그룹을
미리 만들지 않습니다.
새 화면은 pages/<화면>/ui에서 시작합니다. 화면 전용 카드·섹션·폼도 같은 ui에 둡니다.
화면이 길어져 컴포넌트 파일을 나누는 것만으로 새 feature가 필요해지지는 않습니다.
| 작성할 코드 | 배치 기준 |
|---|---|
| 화면과 화면 전용 컴포넌트 | 해당 페이지의 ui |
| 컴포넌트 스타일 | 컴포넌트 옆의 .css.ts |
| 컴포넌트 테스트 | 대상 파일 옆의 .test.tsx |
| 화면·기능 전용 상태, 타입, 샘플 데이터 | 해당 슬라이스의 model |
| 순수 계산·변환 함수 | 해당 슬라이스의 lib |
| 화면·기능 전용 API 연결 | 해당 슬라이스의 api, 기존 공용 클라이언트 사용 |
| 슬라이스 전용 설정 | 필요한 경우 해당 슬라이스의 config |
다른 화면에서도 같은 사용자 행동을 재사용하면 features/<기능>으로 분리합니다. 예를 들어
사전 출결보고를 여러 화면에서 같은 흐름으로 사용하게 되면 feature로 분리할 수 있습니다.
이는 배치 판단을 위한 예시이며 해당 기능을 지금 추가한다는 뜻은 아닙니다.
도메인에 의존하지 않는 코드가 웹 내부에서 반복되면 shared/ui, shared/lib 등 해당 역할에
배치합니다. 특정 화면이나 사용자 행동을 알아야 동작하는 코드를 단지 여러 곳에서 쓴다는 이유로
shared에 넣지 않습니다.
앱을 넘어 공유하는 코드는 루트 작업 지침의 공용화 기준을 따릅니다.
기존 SEED 컴포넌트와 공용 API·브리지 코드는 각 패키지의 공개 진입점으로 사용하고 웹의
shared에 복제하지 않습니다.
의존성은 app → pages → features → shared 방향으로 흐릅니다. 상위 레이어는 필요한 하위
레이어를 직접 사용할 수 있습니다. 페이지에서 shared를 쓰기 위해 빈 feature를 만들 필요는
없습니다. 루트 Next.js 라우트는 src/app의 전역 구성과 src/pages의 화면을 연결합니다.
pages·features에서 같은 레이어의 다른 슬라이스를 직접 import하지 않습니다. 서로 다른 슬라이스를 조합해야 하면 상위 레이어에서 조합합니다.- 슬라이스 외부에서는 해당 슬라이스의
index.ts를 사용합니다.ui·model내부 파일을 직접 import하지 않습니다. - 같은 슬라이스 안에서는 상대 경로를 사용합니다. 자신의
index.ts를 거쳐 다시 가져오지 않습니다. index.ts는 외부에서 필요한 컴포넌트·타입만 명시적으로 export합니다.export *로 내부를 모두 공개하지 않습니다.- 클라이언트에서 사용하는 진입점에 서버 전용 모듈을 섞지 않습니다. 둘을 함께 제공해야 할 때만 서버 전용 진입점을 분리합니다.
app·shared의 역할별 모듈은 순환 참조 없이 조합할 수 있습니다.shared전체를 묶는 배럴은 만들지 않고 필요한 모듈 경로로 가져옵니다.- 다른 앱의 코드를 직접 import하지 않습니다. 패키지는
@yapp-plus/ui처럼 공개 진입점을 사용합니다.
다음은 현재 홈의 연결 방식입니다. @/* alias는 apps/web/src/*를 가리킵니다.
// apps/web/app/(tabs)/page.tsx
export { HomePage as default } from '@/pages/home';// apps/web/src/pages/home/index.ts
export { HomePage } from './ui/home-page';홈과 마이페이지의 기본 본문·바로가기 스타일은 shared/ui에서 가져옵니다.
화면 전용 스타일이 필요해지면 해당 슬라이스의 ui에 작성합니다.
- 파일·폴더는
home-page.tsx,report-attendance처럼 kebab-case로 작성합니다. - 컴포넌트는
HomePage처럼 PascalCase, 훅은use접두사로 작성합니다. - 일반 컴포넌트는 named export를 사용합니다. Next.js가 요구하는 라우트 진입점에서는 프레임워크 규칙에 따라 default export를 사용합니다.
- 스타일은 기존 vanilla-extract와 SEED 토큰을 사용합니다. 새 스타일 도구나 자체 디자인 토큰 체계를 추가하지 않습니다.
화면을 시작하기 전에 제품 맥락에서 대상 사용자와 기능 범위를 확인하고, 연결된 기획 원문에서 화면 정책과 필요한 상태를 확인합니다. 그 다음 아래 순서로 작업합니다.
apps/web/src/pages/<화면>/ui에 화면 진입 컴포넌트를 만듭니다.- 이미
packages/ui에 있는 공용 컴포넌트와 SEED 토큰을 먼저 조합합니다. - 화면 전용 스타일과 테스트를 컴포넌트 옆에 둡니다.
- 다른 화면에서도 같은 사용자 행동이 필요해졌을 때만
features/<기능>으로 분리합니다. - 작업 중인 화면을 웹 브라우저와 WebView에서 확인하고
node --run check를 실행합니다.
현재 웹 앱을 단독으로 실행하려면 저장소 루트에서 다음 명령을 사용합니다.
mise exec -- pnpm --filter @yapp-plus/web dev페이지를 추가할 때는 apps/web/app/<경로>/page.tsx를 Next.js 라우트 진입점으로 두고,
하단 메뉴나 인증 화면의 공통 레이아웃이 필요한 경우 (tabs) 또는 (auth) 아래에 배치합니다.
실제 화면은 apps/web/src/pages/<화면>에 작성합니다. 라우트 파일에서 화면 슬라이스의 공개
index.ts만 가져옵니다.
웹 화면에서는 @yapp-plus/ui의 공개 진입점을 먼저 확인합니다. 같은 컴포넌트를 화면마다
직접 감싸거나 @seed-design/react 구현을 복사하지 않습니다. 공용 컴포넌트가 필요하지만
아직 패키지에 없다면 화면 안에서 임시로 복제하지 말고, 실제 소비자가 둘 이상인지 확인한 뒤
packages/ui 작업으로 분리합니다.
현재 공개된 버튼은 다음처럼 사용합니다. 폼을 제출하지 않는 버튼에는 type="button"을
명시하고, 로딩 중 클릭을 막아야 하면 loading과 disabled를 함께 전달합니다.
import { ActionButton } from '@yapp-plus/ui';
<ActionButton type="button" variant="brandSolid" loading={isLoading} disabled={isLoading}>
계속
</ActionButton>;SEED 컴포넌트가 필요할 때는 공식 React 컴포넌트 문서와
현재 packages/ui/seed-design.json을 확인합니다. CLI 스니펫은 packages/ui에서만 추가하고,
생성된 코드의 공개 export와 기존 React 19 계약을 검토한 뒤 화면에서 사용합니다. 토큰은
@seed-design/css/vars와 기존 vanilla-extract 스타일을 사용하며 임의의 색상·간격 상수를
새로 만들지 않습니다.
apps/web은 일반 브라우저와 apps/mobile의 WebView에서 같은 화면을 렌더링합니다.
브라우저 전용 전역 객체나 모바일 앱 내부 파일을 화면에서 직접 import하지 않습니다. 네이티브
기능이 필요하면 @yapp-plus/app-bridge의 공개 web 진입점과 기존 Provider를 확인합니다.
모바일 화면에서 다음을 확인합니다.
- safe-area 안쪽에 콘텐츠가 배치되는지
- 작은 화면에서 가로 스크롤이나 텍스트 잘림이 없는지
- 키보드·터치 입력과
:focus-visible상태가 동작하는지 - 시스템 라이트·다크 모드에서 SEED 색상 토큰의 대비가 유지되는지
화면 정책에 없는 로딩·오류·빈 상태를 임의로 확정하지 않습니다. 필요한 상태가 불분명하면 기획 원문에 질문을 남기고, 구현에서는 확인된 상태와 기존 컴포넌트 계약만 반영합니다.
AI에게 화면 구현을 맡길 때는 제품 맥락, 이 가이드, 관련 기획 링크와 참고할 기존 화면 경로를 함께 제공합니다. 요청에는 화면 경로, 사용 가능한 공용 컴포넌트, 확인된 상태, 실행할 검증 명령을 적습니다.
AI가 만든 변경은 다음 순서로 확인합니다.
- 새 파일이 올바른 페이지 슬라이스에 있고 import 방향을 지키는지 확인합니다.
@yapp-plus/ui와 SEED 토큰을 사용하며 임의의 UI·토큰 복제가 없는지 확인합니다.- 화면 테스트와 브라우저 확인으로 핵심 문구·접근성 이름·레이아웃을 확인합니다.
mise exec -- node --run check를 실행하고 필요한 경우mise exec -- node --run build를 실행합니다.
AI에게 제품 정책이나 출시 범위를 추측하게 하지 않습니다. 확인되지 않은 상태·API·공용 패키지를 만들지 말고, 불확실한 부분은 구현 전에 표시하도록 요청합니다.
위 자료를 바탕으로 이 저장소의 모노레포와 SEED 구성에 맞춰 규칙을 정했습니다. 참고 저장소의 공용 UI·API 배치를 그대로 복사하지 않습니다.