AI 코딩이 빨라질수록, 작업 기준이 더 필요해졌다
AI 코딩 도구를 쓰면서 처음에는 이런 생각을 했습니다.
“이제 개발이 엄청 빨라지겠는데?”
틀린 말은 아니었습니다. 실제로 Cursor 같은 도구를 쓰면 예전보다 훨씬 빠르게 코드를 만들 수 있습니다. 랜딩페이지 하나 만들고, 컴포넌트 나누고, 기본 API 붙이는 정도는 확실히 빨라졌습니다.
그런데 조금 써보니 다른 문제가 보였습니다.
코드는 빨리 만들어지는데, 프로젝트도 빨리 어질러질 수 있었습니다.
AI에게 “이거 만들어줘”, “저거 수정해줘”, “여기 디자인 좀 바꿔줘”, “API 하나 붙여줘”를 반복하다 보면 어느 순간 내가 만든 프로젝트인데도 구조가 잘 안 보이기 시작합니다.
“이 파일은 왜 생겼지?”
“이 결정은 왜 했지?”
“이건 어디까지 구현된 거지?”
“지금 이 작업은 끝난 건가, 아니면 중간 상태인가?”
여기서 살짝 현타가 왔습니다.
분명 개발은 빨라졌는데, 머릿속은 더 복잡해졌습니다. 그래서 이번에 goodtek-web을 만들면서는 단순히 AI에게 코드를 맡기는 방식이 아니라, 작업 흐름 자체를 먼저 잡아보기로 했습니다.
문제는 코드 생성 속도가 아니라, 그 코드를 어떤 기준으로 쌓아갈 것인가였습니다.
goodtek-web은 그냥 랜딩페이지가 아니었다
goodtek-web은 앞으로 ● goodtek의 중심 웹사이트가 될 프로젝트입니다.
이미 goodtek에는 블로그, 커뮤니티, 노트가 있습니다.
| 공간 | 역할 |
|---|---|
| 블로그 | 정리된 글, 검색 유입 |
| 커뮤니티 | 질문, 대화, 피드백 |
| 노트 | 개발 기록, 빌드 로그, 짧은 메모 |
각각의 역할은 분명합니다.
그런데 세 가지를 만들고 나니 또 다른 문제가 생겼습니다. 각각은 있는데, 전체를 설명하는 입구가 없었습니다.
처음 들어온 사람에게 블로그 주소를 줄 수도 있고, 커뮤니티 주소를 줄 수도 있고, 노트 주소를 줄 수도 있습니다. 하지만 그것들은 각각의 공간이지, goodtek이 무엇을 만들고 있고 어디로 가고 있는지 보여주는 중심 화면은 아니었습니다.
그래서 goodtek-web을 만들기 시작했습니다.
| 역할 | 설명 |
|---|---|
| 중심 입구 | 블로그, 커뮤니티, 노트를 연결 |
| SaaS 앱 허브 | 앞으로 개발될 앱들을 보여주는 공간 |
| Build in Public 기준점 | 진행 중인 작업과 결과물을 연결 |
| 비즈니스 웹사이트 | goodtek이 무엇을 하는지 설명 |
다만 처음부터 완성형 사이트를 만들 생각은 없었습니다.
솔직히 아직 어디까지 만들어야 할지도 정확히 모르겠습니다. 랜딩페이지는 필요할 것 같고, 블로그와 커뮤니티와 노트로 가는 링크도 필요할 것 같습니다. 앞으로 만들 SaaS 앱들을 보여주는 영역도 있어야 할 것 같습니다.
그런데 첫 화면에 어떤 문장을 넣어야 하는지, 메뉴는 어디까지 있어야 하는지, 앱 소개를 어느 정도까지 해야 하는지는 아직 흐릿했습니다.
예전 같았으면 여기서 기획을 오래 했을 겁니다. 화면 구조를 그리고, 메뉴를 정하고, 문구를 다듬고, 앞으로 들어갈 기능을 다 적어본 다음 개발을 시작했을 겁니다.
그런데 1인 개발자로 계속 해보니, 그 방식은 생각보다 오래 버티기 어렵습니다. 계획을 완벽하게 세우려다 보면 시작이 늦어지고, 막상 만들기 시작하면 계획과 다른 문제가 계속 나옵니다.
그래서 이번에는 반대로 가기로 했습니다.
완벽한 계획을 먼저 세우는 대신, 지금 보이는 작업을 하나의 타스크로 만들고, 그 타스크 안에서 필요한 결정을 하면서 앞으로 가기.
이번 첫 번째 타스크는 이렇게 잡았습니다.
프로젝트 구조, 기술스택, 디자인, 언어 등을 정하고 랜딩페이지를 만듭니다.
랜딩페이지를 만든다고 했지만, 실제로는 goodtek-web의 첫 개발 기준을 세우는 작업에 가까웠습니다.
AI에게 바로 맡기기 전에 만든 작업 흐름
[본문 이미지 01 삽입: vibeops와 Cursor로 TASK-001 작업 흐름 만들기]
프로젝트 이름은 goodtek-web으로 정했습니다. 초기화는 vibeops로 진행했습니다.
vibeops init
사용할 코딩 에이전트는 Cursor로 선택했습니다.
Cursor
.cursor/rules
.cursor/skills
브랜치 전략은 GitFlow lite로 잡았습니다.
| 브랜치 | 역할 |
|---|---|
main | production |
develop | integration |
task/* | 작업 단위 브랜치 |
혼자 개발하는 프로젝트라면 그냥 main에 바로 커밋해도 됩니다. 사실 그게 가장 빠릅니다.
하지만 이번에는 그렇게 하지 않기로 했습니다.
AI 코딩 도구를 쓰면 코드가 빨리 나옵니다. 그런데 코드가 빨리 나오는 만큼, 잘못된 방향으로도 빨리 갈 수 있습니다.
특히 피곤한 상태에서 밤에 작업하다 보면 “일단 되니까 넘어가자”가 반복되기 쉽습니다. 저도 그런 식으로 진행하다가 나중에 구조를 다시 잡느라 시간을 더 쓴 적이 많습니다.
혼자 개발한다고 해서 규칙이 필요 없는 게 아니라, 혼자 개발하기 때문에 더더욱 최소한의 규칙이 필요했습니다.
그래서 처음부터 아래 흐름을 잡았습니다.
main
└── 운영 기준
develop
└── 통합 기준
task/*
└── 실제 작업 단위
이 구조가 대단히 복잡한 건 아닙니다. 하지만 적어도 어떤 작업이 어디서 진행되고 있는지, 완료되면 어디로 합쳐야 하는지 기준은 생깁니다.
vibeops init 이후에는 이런 문서와 규칙이 생성됐습니다.
goodtek-web/
├── .cursor/
│ ├── rules/
│ └── skills/
├── docs/
│ ├── logs/
│ ├── project/
│ └── tasks/
├── AGENTS.md
├── .vibeops.json
├── .vibeops.env.example
└── .gitignore
여기까지는 아직 애플리케이션 코드가 없습니다.
하지만 제 입장에서는 이 단계가 꽤 중요했습니다. 보통은 바로 Next.js부터 설치하고 싶어집니다. 저도 그랬습니다.
그런데 이번에는 화면을 만들기 전에, AI와 어떤 방식으로 협업할지 먼저 잡았습니다.

AI에게 코드를 만들게 하는 것보다 먼저, AI가 따라야 할 작업 구조를 만드는 것.
이걸 먼저 해두면 이후에 “그냥 돌아가는 코드”가 아니라 기록되고 검증되는 작업 단위로 개발을 이어갈 수 있습니다.
TASK-001을 만들고 바로 구현하지 않은 이유
다음으로 첫 번째 타스크를 만들었습니다.
vibeops task add
그러면 지금 무엇을 하고 있는지 짧게 묻습니다.
What are you doing now?
여기에 제가 하려는 일을 적었습니다.
프로젝트 구조, 기술스택, 디자인, 언어 등을 정하고 랜딩페이지를 만듭니다.
그러자 TASK-001이 생성되었습니다.
docs/tasks/
└── TASK-001-define-project-direction-and-build-landing-page.md
동시에 작업 브랜치도 생성되었습니다.
task/001-define-project-direction-and-build-landing-page
여기서 바로 구현으로 들어가지 않았습니다.
Cursor의 Ask / Plan 모드로 먼저 계획을 잡았습니다. 이 부분이 중요했습니다. AI에게 바로 “랜딩페이지 만들어줘”라고 하면 결과물은 나옵니다. 그런데 그 결과물이 앞으로의 프로젝트 방향과 맞는지는 별개의 문제입니다.
이번 작업은 단순한 랜딩페이지 하나가 아니라, goodtek-web의 첫 구조를 정하는 작업이었습니다.
그래서 먼저 이런 것들을 정리했습니다.
| 정리한 항목 | 이유 |
|---|---|
| 프로젝트 구조 | 앞으로 앱이 커져도 유지 가능한 구조 필요 |
| 기술 스택 | 프론트, 백엔드, DB 기준 확정 |
| 디자인 기준 | AI가 매번 다른 UI를 만들지 않게 하기 위해 |
| UI 언어 | 한국어/영어 구조를 초기에 잡기 위해 |
| API 범위 | 첫 타스크가 커지는 것을 방지 |
| Docker 실행 방식 | 개발용과 통합 확인용을 분리 |
| 완료 기준 | “대충 됨”이 아니라 완료 판정 기준 필요 |
| 테스트 방법 | 실제로 동작하는지 확인하기 위해 |
처음에는 “랜딩페이지 하나 만들자” 정도였습니다.
그런데 계획을 잡다 보니 생각보다 결정해야 할 것이 많았습니다.
여기서 느낀 점은 분명했습니다.
AI에게 일을 잘 맡기려면, 사람도 먼저 생각을 조금 정리해야 합니다.
“알아서 잘 만들어줘”가 아니라, 이번 작업의 범위는 여기까지인지, 어떤 결정은 지금 하고 어떤 건 다음 타스크로 넘길지, 완료 기준은 무엇인지 정해줘야 결과가 덜 흔들립니다.
스택보다 먼저 정해야 했던 범위
첫 번째 타스크에서 정한 스택은 아래와 같습니다.
| 영역 | 선택 |
|---|---|
| Frontend | Next.js App Router + TypeScript |
| Backend | NestJS |
| Database | PostgreSQL |
| Package manager | pnpm workspaces |
| Local dev | podman-compose / docker compose |
| Design | DESIGN.md 기반 UI 규칙 |
| i18n | ko / en |
처음에는 랜딩페이지 하나인데 이렇게까지 해야 하나 싶기도 했습니다.
Next.js만 있어도 됩니다. 정적 사이트라면 Astro 같은 선택지도 있습니다. 그런데 goodtek-web은 단순한 소개 페이지로 끝낼 생각이 아니었습니다.
앞으로 블로그, 커뮤니티, 노트와 연결되고, 나중에는 goodtek에서 만드는 SaaS 앱들이 올라가는 중심 웹사이트가 될 예정입니다.
그러면 처음에는 랜딩페이지 하나로 시작하더라도, 구조는 나중에 확장할 수 있어야 합니다.
그래서 프론트엔드는 Next.js로 정했습니다. SEO도 필요하고, 나중에 앱 UI나 대시보드로 확장하기도 자연스럽다고 판단했습니다.
백엔드는 NestJS로 잡았습니다. 물론 이번 타스크에서 복잡한 API를 만들 계획은 없었습니다. 하지만 API 서버의 기본 구조가 있으면 이후 기능을 붙일 때 흐름이 훨씬 명확해집니다.
DB는 PostgreSQL로 정했습니다. 이것도 이번 타스크에서 본격적인 테이블 설계까지 하지는 않았습니다. DB 연결이 되는지 확인하는 수준으로 제한했습니다.
즉, 이번 타스크에서 백엔드는 비즈니스 기능 구현이 아니라 앞으로 붙일 수 있는 구조 확인이 목적이었습니다.
그래서 범위를 이렇게 나눴습니다.

| 이번 TASK에서 하는 것 | 이번 TASK에서 하지 않는 것 |
|---|---|
| Next.js 랜딩페이지 | 인증 |
| NestJS API 기본 구조 | 결제 |
| PostgreSQL 연결 | 관리자 페이지 |
/health 체크 | 대시보드 |
| Docker compose 실행 구조 | 본격적인 DB 스키마 설계 |
| 문서화 | 프로덕션 배포 |
이 선을 긋는 게 중요했습니다.
처음부터 인증도 넣고, 관리자도 만들고, 배포까지 하려고 하면 첫 타스크가 끝나지 않습니다.
지금 필요한 건 완성된 플랫폼이 아니라, 앞으로 쌓아갈 수 있는 첫 구조였습니다.
이번 TASK의 목표는 완성도가 아니라 확장 가능한 시작점이었습니다.
UI가 흔들리지 않게 DESIGN.md를 만들기
AI 코딩에서 UI는 생각보다 흔들리기 쉽습니다.
같은 “모던하게 만들어줘”라는 요청을 해도 결과가 매번 달라집니다. 어떤 때는 너무 SaaS 템플릿 같고, 어떤 때는 너무 장난스럽고, 어떤 때는 갑자기 엔터프라이즈 컨설팅 회사 같은 화면이 나옵니다.
저는 goodtek을 만들면서 이런 흔들림을 줄이고 싶었습니다.
그래서 UI 기준을 파일로 두기로 했습니다. PostHog 스타일을 참고한 DESIGN.md를 루트에 생성했습니다.
npx getdesign@latest add posthog
그리고 Cursor가 UI 파일을 수정할 때 이 문서를 참고하도록 .cursor/rules/design.mdc도 추가했습니다.
| 파일 | 역할 |
|---|---|
DESIGN.md | 색상, 타이포, spacing, 컴포넌트 톤 기준 |
.cursor/rules/design.mdc | Cursor가 UI 작업 전에 DESIGN.md를 읽도록 하는 규칙 |
AGENTS.md | 전체 에이전트 작업 기준 문서 |
이건 완성된 디자인 시스템은 아닙니다.
하지만 적어도 “이 프로젝트의 UI는 어떤 방향으로 가야 하는가”를 매번 다시 설명하지 않아도 됩니다.
이번에 해보면서 느낀 건, AI 코딩에서 중요한 문서는 README만이 아니라는 점입니다. 코드 설명 문서도 필요하지만, AI가 반복해서 참고할 기준 문서가 필요합니다.
특히 UI는 말로 설명하면 계속 흔들립니다. 그래서 DESIGN.md처럼 기준을 파일로 두는 방식이 꽤 유용했습니다.
다국어는 과하게 말고, 방향만 먼저
goodtek-web의 기본 언어는 한국어입니다.
제가 글을 쓰고 과정을 공유하는 것도 한국어가 중심입니다. 하지만 goodtek은 앞으로 SaaS 앱들이 올라갈 웹사이트가 될 가능성이 있습니다. 그러면 언젠가는 영어 페이지가 필요해질 수 있습니다.
그래서 첫 타스크부터 ko / en 구조를 넣었습니다.
다만 여기서도 욕심을 내지는 않았습니다. 번역 관리 시스템을 만들거나 CMS를 붙인 것은 아닙니다. 이번에는 랜딩페이지의 문구만 messages JSON으로 분리했습니다.
apps/web/messages/
├── ko.json
└── en.json
URL은 locale 기반으로 잡았습니다.
/ko
/en
/로 접속하면 기본 언어인 /ko로 이동합니다.
이렇게 해두면 나중에 문구를 바꿀 때 컴포넌트를 수정하지 않아도 됩니다. 카피는 messages 파일만 수정하면 됩니다.
여기서도 기준은 같았습니다.
지금 당장 과하게 만들지는 않는다. 하지만 나중에 고치기 어려운 부분은 초기에 방향만 잡아둔다.
기술 스택은 정했는데, 버전까지는 놓쳤다
첫 번째 타스크를 진행하면서 기술 스택은 정했습니다.
Next.js, NestJS, PostgreSQL, pnpm workspaces, Docker compose.
여기까지 정하면 어느 정도 방향이 잡혔다고 생각했습니다. 그런데 막상 구현을 진행하다 보니 하나를 놓쳤습니다.
“무엇을 쓸 것인가”만 정하면 부족했습니다.
“어떤 버전을 쓸 것인가”도 같이 정해야 했습니다.
처음에는 프레임워크와 도구만 정하고 넘어갔습니다. 그런데 이후 관련 패키지 버전을 확인해보니, 생각보다 버전 기준이 중요했습니다.
특히 장기적으로 운영할 프로젝트라면 더 그렇습니다. 너무 오래된 버전으로 시작하면 얼마 지나지 않아 업데이트 부담이 생깁니다. 반대로 무조건 latest만 따라가면 아직 생태계 호환이 덜 맞는 경우도 있습니다.
그래서 기준을 다시 잡았습니다.
가능하면 안정적인 최신 버전, 그리고 LTS 흐름 안에서 오래 가져갈 수 있는 버전을 선택하기로 했습니다.
| 구성 요소 | 버전 |
|---|---|
| Node.js | 24 |
| pnpm | 11.5.0 |
| Next.js | 16.2.6 |
| React | 19.2.6 |
| NestJS | ^11.1.24 |
| PostgreSQL | 18-alpine |
| TypeScript | ^5.9.3 |
| @types/node | ^25.9.1 |
TypeScript는 더 높은 최신 버전도 있었지만, Next.js와 NestJS 생태계 호환을 생각해서 안정적인 5.9.3으로 맞췄습니다.
여기서 배운 점은 분명했습니다.
기술 스택을 정할 때는 단순히 “Next.js를 쓴다”, “PostgreSQL을 쓴다”에서 끝내면 안 됩니다. 초기 문서에 버전 정책까지 같이 남겨야 합니다.
- 가능한 안정적인 최신 버전을 사용한다.
- LTS 또는 장기 지원 흐름을 우선한다.
- 생태계 호환성이 불확실한 최신 버전은 바로 올리지 않는다.
- 메이저 버전 업그레이드는 별도 타스크로 분리한다.
이번에는 첫 타스크 중간에 버전을 다시 올렸습니다. 결과적으로 빌드는 통과했습니다.
pnpm install
api build
web build
web lint
하지만 이 과정에서 PostgreSQL 메이저 버전 업그레이드 이슈도 같이 만났습니다.
PostgreSQL을 18로 올리면서 Docker 이미지의 데이터 마운트 경로가 기존과 달라졌고, 예전 볼륨 데이터가 남아 있어서 컨테이너가 정상 기동되지 않았습니다.
해결은 compose의 볼륨 마운트 경로를 맞추고, 개발용 DB라 기존 볼륨을 삭제한 뒤 다시 올리는 방식으로 진행했습니다.
docker compose down -v
docker compose up -d
여기서도 하나 더 정리했습니다.
| 명령어 | 의미 |
|---|---|
docker compose down | 컨테이너와 네트워크만 제거, DB 데이터는 유지 |
docker compose down -v | 컨테이너, 네트워크, 볼륨까지 제거, DB 데이터 삭제 |
즉, 평소 개발 중지와 재시작에는 down만 쓰면 됩니다.
down -v는 DB를 완전히 비우거나, PostgreSQL 메이저 버전을 올렸는데 기존 볼륨과 호환되지 않을 때처럼 특별한 경우에만 사용해야 합니다.
개발용 DB라고 해도 볼륨 삭제 명령은 가볍게 쓰면 안 됩니다.
이것도 오늘 다시 배운 부분입니다.
스택을 정하는 것만큼, 버전과 업그레이드 기준을 정하는 것도 중요합니다.
허접하지만 풀스택 구조는 생겼다

첫 번째 타스크 결과물은 아직 화려하지 않습니다.
솔직히 말하면 랜딩페이지는 아직 허접합니다. 하지만 이번 타스크의 목적은 “완성도 높은 랜딩페이지”가 아니었습니다.
목적은 goodtek-web을 앞으로 계속 수정하고 확장할 수 있는 구조로 만드는 것이었습니다.
이번 작업으로 대략 이런 구조가 만들어졌습니다.
goodtek-web/
├── DESIGN.md
├── compose.yaml
├── pnpm-workspace.yaml
├── package.json
├── .env.example
├── apps/
│ ├── web/
│ └── api/
├── docs/
└── .cursor/
구성은 아래와 같습니다.
| 경로 | 역할 |
|---|---|
apps/web | Next.js 웹 앱 |
apps/api | NestJS API 서버 |
compose.yaml | web / api / postgres 실행 구조 |
.env.example | 로컬 실행 환경 변수 예시 |
DESIGN.md | UI 기준 문서 |
docs/project | 아키텍처와 결정 기록 |
docs/tasks | 작업 단위 문서 |
apps/web에는 Next.js 앱이 있습니다.
Next.js 16, App Router, Tailwind, next-intl 기반으로 구성했습니다.
apps/api에는 NestJS API가 있습니다. API에는 /health 엔드포인트를 만들었습니다.
| 상태 | 응답 |
|---|---|
| DB 연결 정상 | 200 |
| DB 연결 실패 | 503 |
이건 단순하지만 중요합니다.
프론트만 떠 있는 게 아니라, 백엔드가 있고, DB가 있고, API가 DB 연결 상태를 확인할 수 있습니다.
즉, 아직 기능은 없지만 구조는 풀스택입니다.
이번 타스크에서 얻은 결과는 “예쁜 페이지”가 아니라 “프론트, 백엔드, DB가 함께 올라가는 첫 구조”였습니다.
pnpm dev와 dev:docker를 나눈 이유

구현하면서 Docker 실행 방식도 정리했습니다.
처음에는 전체를 Docker로 띄우면 깔끔하겠다고 생각했습니다. 그런데 프론트 UI를 수정하다 보면 화면을 계속 바꾸게 됩니다. 이때마다 Docker 이미지를 다시 빌드해야 하면 너무 느립니다.
그래서 개발 방식은 두 가지로 나눴습니다.
| 명령어 | 용도 |
|---|---|
pnpm dev | 매일 개발용 |
pnpm dev:docker | 통합 확인용 |
평소 개발은 pnpm dev로 진행합니다.
이 방식에서는 PostgreSQL과 API는 Docker에서 실행하고, Next.js 웹은 호스트에서 실행합니다.
| 구성 요소 | 실행 위치 |
|---|---|
| PostgreSQL | Docker |
| NestJS API | Docker |
| Next.js Web | Host |
이렇게 하면 Next.js Hot Reload가 바로 동작합니다. 로고나 스타일, 카피를 수정할 때는 이 방식이 훨씬 편합니다.
반대로 전체 구성을 확인할 때는 pnpm dev:docker를 사용합니다.
이 방식은 web, api, postgres를 모두 컨테이너로 띄웁니다.
| 구성 요소 | 실행 위치 |
|---|---|
| PostgreSQL | Docker |
| NestJS API | Docker |
| Next.js Web | Docker |
배포 전처럼 compose 전체 구성이 정상적으로 도는지 확인할 때 사용합니다.
이 구분은 실제로 유용했습니다. 프론트 화면을 만질 때는 빠르게 반복하고, 전체 구조가 컨테이너로도 도는지는 따로 확인할 수 있습니다.
처음부터 모든 걸 Docker 안에서만 하려고 하면 개발 속도가 떨어지고, 반대로 Docker를 아예 안 쓰면 배포에 가까운 환경 검증이 약해집니다.
그래서 둘을 나눴습니다.
알고 있어도 다시 헷갈리는 Docker 네트워크
이번 작업 중간에 Docker 네트워크도 다시 정리하게 됐습니다.
알고 있는 내용인데도 실제로 설정하다 보면 헷갈립니다.
핵심은 이겁니다.
| 접근 방향 | 주소 |
|---|---|
| 컨테이너 → 컨테이너 | 서비스 이름 |
| 호스트 → 컨테이너 | localhost 포트 매핑 |
예를 들어 API 컨테이너가 PostgreSQL에 접근할 때는 아래처럼 봅니다.
api
└── postgres:5432
하지만 브라우저나 호스트에서 접근할 때는 이렇게 봅니다.
browser
├── localhost:3000
└── localhost:3001
즉, 같은 서비스라도 어디에서 접근하느냐에 따라 주소가 다릅니다.
API 컨테이너 안에서는 DB 주소가 postgres:5432입니다. 하지만 호스트에서 DB에 직접 붙을 때는 localhost:5432가 됩니다.
이걸 머리로는 알고 있었는데, Next.js를 호스트에서 띄우고 API와 DB를 Docker에 두는 구조로 가다 보니 다시 한번 정리가 필요했습니다.
그래서 이 내용도 문서에 남겼습니다.
개발하면서 이런 내용을 남기는 이유는 단순합니다.
지금 헷갈린 건 나중에도 다시 헷갈릴 가능성이 높기 때문입니다.
작은 경고들을 그냥 넘기지 않기

이번 작업에서는 사소하지만 시간을 잡아먹는 문제들도 있었습니다.
첫 번째는 3000 포트 충돌이었습니다.
처음에는 Docker web 컨테이너가 잡고 있는 줄 알았습니다. 하지만 확인해보니 이전에 켜둔 Next.js dev 서버였습니다.
확인은 이렇게 했습니다.
lsof -i :3000
프로세스를 확인한 뒤 종료했습니다.
kill <PID>
그리고 이후에는 이런 상황을 쉽게 정리하려고 pnpm dev:kill 스크립트도 추가했습니다.
사소한 문제입니다. 하지만 실제 개발에서는 이런 사소한 문제가 시간을 잡아먹습니다. 그래서 이번 과정에서는 성공한 결과만 남기지 않고, 이런 시행착오도 같이 남기려고 합니다.
두 번째는 Next.js 16 관련 경고였습니다.
기존에는 middleware.ts를 사용하고 있었는데, Next.js 16에서는 proxy.ts 사용을 권장하는 경고가 나왔습니다. 기능이 바로 깨진 것은 아니었지만, 첫 구조를 잡는 단계에서 경고를 그대로 두고 싶지는 않았습니다.
그래서 middleware.ts를 제거하고, next-intl 문서 흐름에 맞춰 proxy.ts로 이전했습니다.
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
export default createMiddleware(routing);
export const config = {
matcher: ["/((?!api|trpc|_next|_vercel|.*\\..*).*)"],
};
이후 빌드는 통과했고, deprecated 경고도 사라졌습니다.
이런 부분은 작아 보이지만, 버전을 올렸을 때 반드시 확인해야 하는 지점입니다. 단순히 패키지 버전만 올리는 게 아니라, 그 버전에 맞게 권장 구조도 같이 맞춰야 합니다.
세 번째는 Next.js Image 경고였습니다.
처음에는 SVG 로고를 next/image로 넣고, Tailwind로 높이를 조정했습니다. 그런데 Next.js가 width / height와 CSS 크기 조정이 겹칠 수 있다는 경고를 냈습니다.
결국 로고 SVG의 viewBox 기준에 맞춰 width와 height를 명시하고, CSS 조정은 최소화하는 방식으로 정리했습니다.
원본 비율과 표시 크기가 같으면
width / height props만 쓰는 게 가장 단순하다.
CSS로 한쪽 크기를 바꿀 때만
반대쪽 auto를 style로 명시한다.
작은 경고지만 이런 것도 그냥 넘기면 나중에 쌓입니다.
처음 구조를 잡는 단계에서는 가능한 한 경고를 줄이는 편이 좋다고 봤습니다.
/ko만 열었는데 /en도 찍힌 순간
개발 로그를 보다 보니 /ko만 열었는데 /en 요청도 같이 찍히는 것처럼 보였습니다.
처음에는 다국어 라우팅이 잘못된 건가 싶었습니다.
확인해보니 버그가 아니라 Next.js App Router의 Link prefetch 동작이었습니다.
Header에는 ko / en 전환 링크가 있고, Next.js의 <Link>는 기본적으로 prefetch가 켜져 있습니다. 그래서 /ko 페이지를 열면 화면에 보이는 /en 링크를 Next.js가 미리 요청할 수 있습니다. 반대로 /en을 열면 /ko가 미리 요청될 수도 있습니다.
즉, 두 로케일을 한 번에 렌더링하는 문제가 아니라, 링크 prefetch 때문에 로그에 요청이 같이 보인 것이었습니다.
이것도 알고 나면 별일 아니지만, 모르면 라우팅 문제로 착각하기 쉽습니다. 필요하면 locale switcher 링크에 prefetch={false}를 줄 수도 있습니다. 이번에는 정상 동작으로 보고 넘어갔습니다.
● goodtek 로고도 다시 맞추기
랜딩페이지를 띄운 뒤에는 로고도 수정했습니다.
처음에는 Goodtek처럼 대문자 G가 들어간 형태였습니다. 하지만 goodtek은 항상 소문자 goodtek으로 쓰기로 했습니다. 그리고 로고도 추상적인 아이콘이 아니라, 단순한 원형점과 goodtek 워드마크를 기준으로 가져가기로 했습니다.
기준은 이 형태입니다.
● goodtek
처음 생성된 SVG는 원이 너무 크거나 텍스트와 너무 붙어 있었습니다. 그래서 원 크기와 간격을 다시 조정했습니다.
| 항목 | 기준 |
|---|---|
| 원 반지름 | 약 5.5px |
| 원과 텍스트 간격 | 약 5px |
| 텍스트 | goodtek 소문자 |
이것도 기능 개발은 아닙니다.
하지만 브랜드 기준이 흐트러지면 나중에 계속 수정하게 됩니다. 처음부터 완벽할 필요는 없지만, 최소한 “goodtek은 소문자이고, 로고는 ● goodtek이다” 정도는 고정해두는 게 좋다고 봤습니다.
TASK-001을 PR로 닫기 전에
구현과 검증이 끝난 뒤에는 vibeops로 작업을 마무리했습니다.
vibeops task done TASK-001
결과는 이렇게 정리됐습니다.
Sections
├── Result
└── Test Result
Committed
Pushed
Pull request created
Status → Done
여기서 좋았던 점은 단순히 커밋만 만든 게 아니라는 점입니다.
TASK 문서 안의 Result, Test Result가 채워지고, 커밋이 만들어지고, 작업 브랜치가 원격에 push되고, PR까지 생성되었습니다.
이제 이 작업은 그냥 로컬에서 “대충 된 상태”가 아니라, 리뷰하고 머지할 수 있는 작업 단위가 됐습니다.
PR을 GitHub에서 머지하면, 이후에는 develop 기준으로 다음 타스크를 만들면 됩니다.
이 흐름이 제가 원했던 방향입니다.
작업을 하나 만들고, 계획하고, 구현하고, 테스트 결과를 남기고, PR로 닫는 구조.
AI 코딩 도구를 쓰더라도 이 흐름이 있어야 프로젝트가 계속 앞으로 갈 수 있다고 생각합니다.
이번 TASK-001에서 남은 것
이번 TASK-001에서 만든 내용은 아래와 같습니다.
| 구분 | 내용 |
|---|---|
| 프로젝트 초기화 | goodtek-web 생성, vibeops init |
| 작업 흐름 | main / develop / task 브랜치 구성 |
| AI 협업 기준 | Cursor rules / skills, AGENTS.md |
| TASK 관리 | TASK-001 생성, plan 후 implement |
| Frontend | Next.js 16, App Router, Tailwind, next-intl |
| Backend | NestJS, /health |
| Database | PostgreSQL 18 |
| Package | pnpm workspaces |
| Design | DESIGN.md 기반 UI 기준 |
| i18n | ko / en |
| Local dev | pnpm dev, pnpm dev:docker |
| Troubleshooting | PG 볼륨, 3000 포트, Image 경고, proxy 전환 |
| Brand | ● goodtek 로고 방향 정리 |
| Completion | Result / Test Result 작성, commit, push, PR 생성 |
결과물만 보면 아직 랜딩페이지 하나입니다.
그리고 솔직히 아직 예쁘지도 않습니다.
하지만 프론트엔드, 백엔드, DB, Docker 실행 구조, 다국어, 디자인 기준, 작업 브랜치 흐름까지 갖춘 첫 번째 풀스택 구조는 만들어졌습니다.
이제부터는 이 구조 위에서 기능을 하나씩 수정하고 추가할 예정입니다. 작업은 계속 PR 단위로 쪼개서 진행하려고 합니다.
| 앞으로의 작업 | 진행 방식 |
|---|---|
| 랜딩 문구 다듬기 | PR |
| 블로그 / 커뮤니티 / 노트 링크 연결 | PR |
| 앱 목록 추가 | PR |
| 로그인 또는 대시보드 | 별도 PR |
| 배포 구조 정리 | 별도 PR |
이번에 남은 가장 큰 교훈은 단순했습니다.
AI 코딩은 코드를 빨리 만드는 도구이지만, 프로젝트를 덜 어지럽히려면 사람 쪽의 기준이 더 선명해야 합니다.
저도 하면서 생각이 바뀌었습니다.
처음에는 “랜딩페이지 하나 만들자”였는데, 끝나고 보니 사실상 goodtek-web의 작업 방식, 기술 기준, 브랜드 기준, 개발 환경 기준을 함께 잡은 셈이었습니다.
다음 작업부터는 이 허접한 첫 랜딩페이지를 조금씩 실제 ● goodtek의 입구처럼 바꿔보겠습니다.
아마 또 작은 경고를 만나고, 포트가 꼬이고, “이거 왜 이러지?”를 몇 번 더 하게 될 겁니다.
그래도 이번에는 적어도 그 시행착오가 그냥 흩어지지는 않을 것 같습니다.