YAPP 공식 홈페이지(www.yapp.co.kr) 레포에서 작업하는 에이전트와 기여자를 위한 안내서입니다.
코드에서 바로 읽을 수 있는 내용은 줄이고, 코드만 봐서는 알 수 없는 규칙과 함정을 적었습니다.
스타일 규칙은 DESIGN.md에, 29기 개편 요구사항은 docs/29th-renewal.md에 따로 있습니다.
- Next.js 13.5 Pages Router, React 18, TypeScript, styled-components 6
- 서버·DB·API 없음. 모든 콘텐츠는
database/의 TS/JSON 파일이고, 전 페이지가 정적 생성(SSG)됩니다. - 패키지 매니저는 pnpm (
packageManager: pnpm@10.34.6). yarn, npm은 쓰지 않습니다. - 다크모드는 지원하지 않습니다.
color-scheme: only light로 라이트 화면을 강제합니다.
pnpm install # 의존성 설치
pnpm dev # 개발 서버 (localhost:3000)
pnpm build # sitemap 생성 후 next build
pnpm start # 빌드 결과 실행
pnpm lint # next lint
pnpm exec tsc --noEmit # 타입 검사- 테스트 코드는 없습니다. 검증은
tsc,lint,build, 그리고 화면 확인으로 합니다. build는scripts/generate-sitemap.mjs를 먼저 실행해public/sitemap.xml을 만듭니다. 이 파일은 커밋하지 않습니다(.gitignore).- 커밋할 때
simple-git-hooks가pnpm lint-staged(eslint --fix, prettier)를 실행합니다.
| 경로 | 내용 |
|---|---|
pages/ |
index(홈), recruit/index, recruit/[job](직군별 인재상 & JD), project/index, project/[...slug], story(메뉴에서 숨김), 404 |
components/common/ |
Header, Footer, Banner, ProjectCard, Image, JoinSection, SEO 등 공용 |
components/home/ |
홈 섹션: IntroSection(기수별 배너), AnimatedTextSection, GridSection, ProjectSection, SponsorSection, RecuitBtn(FAB) |
components/recruit/, components/project/ |
각 페이지 전용 컴포넌트 |
database/ |
home.ts, recruit.ts, metaData.ts(SEO), contact.ts, medium.json, projects/<기수>/<슬러그>.json |
constants/ |
yapp.ts(기수·링크), status.ts(모집 일정·상태), breakpoints.ts, path.ts, headerMenus.ts |
styles/ |
theme.ts(palette, textStyleV2), media.ts, global-styles.ts |
public/assets/ |
images/<기수>/, project/(프로젝트 썸네일·본문), icons/, sponsors/, lottie/, video/ |
utils/ |
getAllProjects.ts, getImageSize.ts, gtag.ts |
import는 baseUrl: ./ 기준 절대 경로를 씁니다(components/common, styles/media 등).
색, 글자, 반응형, 이미지, 컴포넌트 스타일 작성 규칙은 DESIGN.md에 있습니다. 화면을 고치기 전에 먼저 읽습니다.
꼭 지킬 것만 추리면:
- 색은
theme.palette키로, 글자는theme.textStyleV2로 씁니다. hex 값을 컴포넌트에 직접 쓰지 않습니다. - 최소 지원 폭은 320px입니다.
- 요청받지 않은 기존 레이아웃·디자인은 건드리지 않습니다.
next/image에는sizes를 지정합니다.- Prettier: 작은따옴표, 세미콜론, 2칸 들여쓰기, trailing comma
all, 80자.
database/projects/<기수>/<슬러그>.json을 만듭니다. 타입은types/project.ts의Project입니다.- 썸네일은
public/assets/project/<기수>_thumbnail_<슬러그>.webp, 본문은<기수>_content_<슬러그>_<n>.webp로 넣습니다. 본문 이미지는 변환 없이 그대로 내려가므로 폭 2080px 이하의 WebP로 줄여서 넣습니다. - 주소는 파일 경로에서 나옵니다:
/project/<기수>/<슬러그>. 상세 페이지는 파일명(슬러그)만으로 프로젝트를 찾으므로, 기수가 달라도 슬러그가 겹치면 안 됩니다. - 회고(
retrospects[].content)는 HTML 문자열입니다. 문단 사이는<br /><br />, 문단 안 줄바꿈은<br />입니다. - 홈 캐러셀에 올리려면
database/home.ts의CAROUSEL_DATA에 추가합니다.
sitemap은 database/projects/*/*.json을 읽어 자동으로 갱신됩니다.
- 페이지별 메타 정보는
database/metaData.ts의PAGE_SEO에 있고,pages/_app.tsx가components/common/SEO.tsx에 넘깁니다. - 동적 페이지는
getStaticProps에서seoprop을 반환합니다(pages/project/[...slug].tsx참고). - 대표 주소는
https://www.yapp.co.kr입니다(metaData.ts의siteUrl).robots.txt와 sitemap도 이 주소를 씁니다. - 새 페이지를 만들면
PAGE_SEO와scripts/generate-sitemap.mjs의 정적 경로 목록에 추가합니다.
constants/status.ts의 날짜 세 개가 모집 상태를 정합니다.
| 상태 | 조건 | 지원 버튼 링크 |
|---|---|---|
PRE |
RECRUITING_START 이전 |
사전 알림 신청 폼 |
ACTIVE |
시작 ~ RECRUITING_DEADLINE |
지원 페이지(YAPP_RECRUIT_ALL) |
EXTRA |
마감 ~ RECRUITING_EXTRA_DEADLINE |
지원 페이지 |
POST |
그 이후 | 다음 기수 알림 신청 폼 |
- 상태는 방문자 브라우저의 시계로 1초마다 다시 계산합니다. 빌드 시점에 고정되지 않으므로, 마감 뒤에 다시 배포할 필요가 없습니다.
- 홈은 상태와 무관한 섹션을 정적 HTML에 넣고, 상태에 따라 달라지는 지원 카드와 하단 지원 버튼만 브라우저에서 상태를 확인한 뒤에 그립니다(
pages/index.tsx). 상태에 따라 달라지는 내용을 서버 렌더링에 넣으면 빌드 시점과 방문 시점의 상태가 달라 화면이 어긋납니다. - 상태별 문구는
database/home.ts의HOME_BANNER_BY_STATUS,database/recruit.ts의RECRUIT_BANNER_BY_STATUS에 있습니다.
PR → YAPP-admin/yapp-web main 머지
→ GitHub Action (git-push-action.yml): build.sh 실행
→ YAPPgit/yapp-web main 에 복사본 푸시
→ Vercel (팀 yappgits-projects, 프로젝트 yapp-web) 빌드·배포
→ www.yapp.co.kr
- GitHub의 기본 브랜치는
develop이지만, 배포는main에 푸시될 때만 됩니다. PR은main으로 엽니다. build.sh는cp -R ./yapp-web/*로 복사합니다. 최상위 점(.) 파일은 배포 레포로 넘어가지 않습니다..eslintrc.json,.babelrc,.prettierrc,.gitignore가 Vercel 빌드에는 없습니다.- 그래서 Vercel에 전달해야 하는 설정은 점으로 시작하지 않는 파일(
vercel.json,package.json,next.config.js)에 둡니다. - Vercel 설치 명령은
vercel.json의pnpm install --frozen-lockfile입니다.package.json을 고치면pnpm-lock.yaml도 같이 커밋해야 빌드가 통과합니다. - PR 단계의 빌드 검사가 없습니다. 빌드가 깨져도 머지한 뒤에야 드러나므로, 머지 전에 로컬에서 확인합니다.
- Vercel 환경 변수는 Vercel에서 관리합니다.
.env*파일은 커밋하지 않습니다.
기본:
pnpm exec tsc --noEmit && pnpm lint && pnpm build의존성, 빌드 설정, next.config.js를 바꿨다면 Vercel과 같은 조건으로 한 번 더 확인합니다.
- 레포 밖 빈 폴더에 트리를 내보냅니다:
git checkout-index -a --prefix=<폴더>/ - 그 폴더에서 최상위 점 파일을 지웁니다(배포 복사본과 같게).
vercel pull --yes --environment=production후vercel build --prod를 실행합니다.
배포 뒤에는 Vercel 배포 상태가 Ready인지, 운영 사이트에서 바꾼 화면과 /robots.txt, /sitemap.xml이 열리는지 봅니다. 빌드 로그는 vercel inspect <배포 URL> --logs로 봅니다.
- 브랜치는
main에서 따고, PR은YAPP-admin/yapp-web의main으로 엽니다. - 커밋 메시지는 한 줄입니다:
feat:/fix:/style:/chore:+ 한국어 제목. 본문과Co-Authored-By는 붙이지 않습니다.- 예:
fix: 320px 화면에서 프로젝트 카드가 잘리는 문제 수정
- 예:
.env*,.vercel/,public/sitemap.xml,pnpm-workspace.yaml은 커밋하지 않습니다.- PR을 열면 작성자가 자동으로 담당자로 지정됩니다(
auto_assign.yml).
- Next 13에 묶여 있습니다. 14 이상에서만 고쳐진 보안 권고가 남아 있습니다. 16으로 올리려면
next build --webpack(커스텀 webpack 설정 때문)과images.qualities: [75, 90]설정이 필요합니다. - 레포 안에 중첩된 git worktree에서
pnpm install을 그냥 실행하면 상위 체크아웃의pnpm-workspace.yaml을 따라 올라가 상위node_modules와 lockfile을 덮어쓸 수 있습니다. worktree에서는pnpm install --ignore-workspace를 씁니다. - 중첩 worktree에서는 모듈 해석이 상위
node_modules까지 올라갑니다. worktree에서tsc와 빌드가 통과해도 배포에서는 실패할 수 있으니, 의존성 변경은 레포 밖 복사본에서 확인합니다. pnpm dev화면은 headless 브라우저에서body{display:none}상태로 남는 경우가 있습니다. 자동 화면 확인은pnpm build && pnpm start로 합니다.- 테스트 서버는 포트로 찾아서 종료합니다(
lsof -iTCP:<포트>). 프로세스 이름으로 죽이면 자식 프로세스가 옛 빌드를 계속 서비스합니다. next.config.js의compiler.styledComponents를 지우면 안 됩니다. 이 설정이 styled-components의 클래스 이름을 파일과 컴포넌트 기준으로 고정합니다. 없으면 이름이 만들어진 순서로 정해져 빌드와 접속 경로에 따라 달라지고, 서버가 그린 HTML과 브라우저가 붙이는 이름이 어긋나면 다른 컴포넌트의 스타일이 엉뚱한 곳에 붙습니다. 같은 코드인데 Preview는 멀쩡하고 운영 배포만 깨질 수 있습니다(29기 첫 운영 배포에서 모집 일정 카드가 깨졌습니다)..babelrc의babel-plugin-styled-components는forceSwcTransforms때문에 쓰이지 않습니다._middleware.ts는 최상위에 있어서 Next 13에서 동작하지 않습니다.story페이지는 헤더 메뉴에서 빠져 있지만 주소로는 열리고 sitemap에도 들어 있습니다.
기수가 바뀌면 코드에서 함께 바꿔야 하는 값입니다. 디자인 요구사항에는 보통 빠져 있으니 직접 챙깁니다.
constants/yapp.ts:YAPP_GENERATION, 직군별 공고 링크,YAPP_FAQ_NOTION, 사전·다음 기수 알림 폼 링크. 알림 폼은NEXT_GENERATION_RECRUIT_LINK값을PREVIOUS_GENERATION_RECRUIT_LINK로 옮기고,NEXT에는 다음 기수 폼을 넣습니다(기수만 올리고 이걸 빼먹으면 모집 전 버튼이 지난 기수 폼으로 연결됩니다).constants/status.ts:RECRUITING_START,RECRUITING_DEADLINE,RECRUITING_EXTRA_DEADLINE, 화면에 보이는 기간 문구RECRUITING_PERIOD_TEXT. 추가 모집이 없으면RECRUITING_EXTRA_DEADLINE을 마감일과 같게 둡니다.- 날짜 문구 하드코딩:
database/home.ts의HOME_BANNER_EXTRA,database/recruit.ts의RECRUIT_SCHEDULE - 문구 하드코딩:
database/recruit.ts의'28기 iOS 추가 모집',database/home.ts의 배너 제목(기수 컨셉 문구) - 홈 통계:
database/home.ts의CURRENT_INFO_DATA(운영 기수, 현재 활동 회원 수 등) - 기수 이미지:
public/assets/images/<기수>/를 만들고Banner,JoinSection,RecruitBanner,pages/index.tsx의 배너 preload,database/metaData.ts의 OG 이미지(현재images/28th/preview.png) 경로를 바꿉니다. - 기수 색:
styles/theme.ts에 새 기수 키를 추가하고 사용처를 바꿉니다. 방법은 DESIGN.md의 "기수 브랜딩 색"에 있습니다. - 홈 배너 컴포넌트: 기수마다
BannerNNth.tsx를 새로 만들고pages/index.tsx에서 교체하는 방식입니다.
- 29기 홈페이지 개편: 요구사항과 코드 대응은 docs/29th-renewal.md에 있습니다. 작업 브랜치는
codex/29th-website입니다.