Next.js, Node.js, Git 조합? 당신의 코드는 이미 해킹당했을 수도 있습니다 (실무자가 본 90%의 실수)
2026. 6. 6.
Next.js, Node.js, Git 조합? 당신의 코드는 이미 해킹당했을 수도 있습니다 (실무자가 본 90%의 실수)
그리고 그 개발자 중 일부는 이미 프로덕션에 배포했죠. 이게 바로 정말 무서운 부분입니다.
머릿속에서 계속 맴도는 이야기가 하나 있습니다. 한 주니어 개발자가 있었죠. 똑똑하고, 포트폴리오도 꽤 괜찮은 친구였습니다. 프리랜서 클라이언트를 위해 풀스택 앱을 만들었어요. Next.js 프론트엔드에 Node.js 백엔드, 모든 것을 깔끔하게 연결했죠. 그리고 GitHub에 푸시했습니다. 그런데 3주 뒤, 클라이언트의 경쟁사가 그 리포지토리를 찾아냈습니다. 공개 리포지토리였고, .env 파일이 버젓이 그대로 들어있었거든요. 데이터베이스 비밀번호, Stripe 시크릿 키까지, 모든 것이요.
그 프로젝트는 단순한 기술적 실패가 아니었습니다. 신뢰의 실패였죠.
그리고 여기 더 잔인한 현실이 있습니다. 이건 무능한 사람이 저지른 멍청한 실수가 아니었어요. 세 가지 도구를 실제로 어떻게 연결해야 하는지 제대로 배우지 못한 개발자가 저지르는 아주 일반적인 실수였습니다. 코딩은 할 줄 알았지만, 이 도구들이 어떻게 서로 연동되는지는 몰랐던 거죠.
2026년 현재, 대다수의 개발자들이 똑같은 상황에 처해 있습니다. 그리고 아무도 이를 인정하려 들지 않죠.
이 스택이 지금 압도적으로 지배적인 이유 (그리고 문제가 되는 이유)
Next.js, Node.js, 그리고 Git. 이 조합은 사실상 웹 개발 스택의 표준으로 자리 잡았습니다. Next.js는 2024년 말 주간 npm 다운로드 600만을 돌파했고, 2025년을 거쳐 2026년 현재까지도 그 수치를 유지하고 있습니다. Node.js는 전체 웹사이트의 약 6.3%에 직접적으로 사용되며, 서버 렌더링 프레임워크를 통해 훨씬 더 많은 사이트에 간접적으로 영향을 미치고 있죠. Stack Overflow Developer Survey 2025에 따르면, Git은 전문 개발자의 97.8%가 사용합니다.
그러니까 개발자라면 누구나 당연히 '알고 있어야' 할 세 가지 강력한 도구들이 있는 셈입니다. 그런데 모두가 서로 이미 알고 있다고 생각하기 때문에, 실제 이 도구들이 어떻게 상호작용하는지 아무도 가르쳐주지 않습니다.
공식 문서는 각 도구를 개별적으로 다룹니다. 튜토리얼도 마찬가지로 각자의 영역에서 설명하죠. 그러다 개발자가 실제 프로젝트에서 이들을 연결해야 하는 순간, 갑자기 모든 것이 혼란스러워지는 겁니다.
이 글이 바로 그 이야기입니다. 생태계에 아무도 말하지 않는 빈틈이 있다는 것을 인정하는, 아무도 쓰려 하지 않았던 글 말이죠.
실제 발생하고 있는 실수들 (그리고 얼마나 심각한지)
솔직하게 말씀드리겠습니다. 자주 발생하는 순서가 아니라, 얼마나 치명적인 피해를 줄 수 있는지에 따라 정리했습니다.
1. 당신의 .env 파일은 아마 이미 GitHub에 올라가 있을 겁니다
직설적으로 말씀드리죠.
지난 3년간 Next.js나 Node.js 프로젝트를 시작했고, git init 또는 git add .를 처음 실행하기 전에 .gitignore를 설정하지 않았다면, 당신의 비밀 정보가 이미 저장소 히스토리에 남아있을 가능성이 매우 높습니다. 나중에 파일을 삭제했더라도 소용없습니다. Git은 일반 파일 시스템처럼 작동하지 않아요. 모든 것을 기억합니다.
GitHub의 보안팀은 2024년에만 1,280만 개 이상의 비밀 정보가 공개 리포지토리에서 노출되었다고 보고했습니다. 여기에는 API 키, 데이터베이스 자격 증명, 토큰, 비공개 키 등이 포함됩니다. 2025년에도 이 수치는 줄어들지 않았죠.
그런데 이상한 점은, create-next-app으로 프로젝트를 생성할 때 기본 .gitignore 파일에 .env가 분명히 포함되어 있다는 겁니다. 그런데 왜 이런 일이 계속 발생할까요?
개발자들이 스타터 리포지토리를 클론하거나, 커스텀 설정을 직접 만들고, 6개월 전 유튜브 튜토리얼에서 .gitignore 설정 부분이 제대로 보이지 않는 폴더 구조를 복사해 오기 때문입니다. 그리고는 습관적으로 git add .를 실행해버리죠. 제가 실무에서 이런 케이스를 수습해 봤을 때, 프로젝트 초기 Git 설정을 조금만 신경 썼다면 피할 수 있는 사고가 대부분이더군요.
Next.js + Node.js 프로젝트에 필요한 올바른 .gitignore는 다음과 같습니다:
# Node
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# Next.js
.next/
out/
build/
dist/
# Environment variables - 이게 핵심입니다!
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env*.local
# Vercel
.vercel
# TypeScript
*.tsbuildinfo
next-env.d.ts
# OS
.DS_Store
*.pem
Thumbs.db
# Debug
.npm
.eslintcache
만약 이미 비밀 정보를 푸시했다면, 파일을 삭제하고 다시 푸시하는 것으로는 히스토리에서 제거되지 않는다는 고통스러운 진실을 마주해야 합니다. 다음 단계를 따라야 합니다:
# git-filter-repo 설치 (2025년 이후 BFG보다 더 좋습니다)
pip install git-filter-repo
# 전체 히스토리에서 .env 파일 제거
git filter-repo --path .env --invert-paths
# 그 다음 강제 푸시 (팀원들과 조율이 필요합니다)
git push origin --force --all
하지만 솔직히, 비밀 정보가 몇 시간 이상 노출되었다면? 자격 증명을 재발급하세요. 이미 유출되었다고 가정하는 것이 진짜 답입니다.

2. 아무도 제대로 설명해주지 않는 NEXT_PUBLIC_ 함정
이것은 미묘해서 숙련된 개발자들마저도 놓치기 쉽습니다.
Next.js는 서버와 클라이언트라는 두 가지 환경을 가집니다. 환경 변수는 이 두 환경에서 다르게 작동합니다. NEXT_PUBLIC_으로 시작하는 변수는 클라이언트 측 JavaScript 번들에 포함됩니다. 나머지는 모두 서버에만 머무릅니다.
간단해 보이죠? 하지만 여기서 많은 사람이 실수를 저지릅니다.
// 이것은 서버에서 실행됩니다 (API 라우트, 서버 컴포넌트)
// 잘 작동합니다
const dbPassword = process.env.DB_PASSWORD;
// 이것은 클라이언트(브라우저)에서 실행됩니다
// process.env.DB_PASSWORD는 여기서는 undefined입니다
// 왜냐하면 NEXT_PUBLIC_ 접두사가 붙지 않았기 때문입니다
const something = process.env.DB_PASSWORD; // undefined, 오류 없이 조용히 깨짐
끔찍한 부분은 오류가 발생하지 않는다는 겁니다. 대부분의 경우 크래시가 나지 않습니다. 그냥 undefined가 나오고, 한참 뒤에 이해하기 어려운 오류가 발생하여 디버깅에 몇 시간을 낭비하게 만들죠.
그리고 반대의 실수는 훨씬 더 나쁩니다:
// 절대로 이렇게 하지 마세요
// 이것은 모든 사용자의 브라우저에 당신의 비밀 정보를 노출시킵니다
NEXT_PUBLIC_DB_PASSWORD=supersecret123
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_...
NEXT_PUBLIC_이 붙은 모든 것은 당신의 사이트를 여는 모든 사람에게 보입니다. JavaScript 번들에 내장되기 때문입니다. 개발자 도구, 소스 보기… 모두 거기에 있습니다.
| 변수 유형 | 접근 가능 위치 | 브라우저에 노출 여부? | 사용 용도 |
|---|---|---|---|
NEXT_PUBLIC_* | 클라이언트 + 서버 | YES | API 기본 URL, 공개 키, 분석 ID |
일반 ENV_VAR | 서버 전용 | NO | DB 비밀번호, 시크릿 키, 토큰 |
process.env.NODE_ENV | 양쪽 | NO (내장) | 환경 감지 |
규칙은 간단합니다. 비밀 정보라면 절대로 NEXT_PUBLIC_을 붙이지 마세요. 클라이언트에서 사용해야 한다면, 그것은 비밀 정보가 아니어야 합니다.

3. CORS는 버그가 아닙니다. 바로 당신의 문제입니다.
Next.js는 3000번 포트에서 실행되고, Node.js/Express 백엔드는 3001번 포트에서 실행됩니다. 프론트에서 fetch 요청을 보냈는데, CORS 오류가 발생합니다. 구글링을 하다가 2019년에 올라온 Stack Overflow 답변을 찾아서 이렇게 하라고 합니다:
// 모두가 따라 하는 게으른 "해결책"
app.use(cors());
로컬에서는 잘 작동합니다. 그래서 프로덕션에 푸시했죠.
그러자 프로덕션에서 또다시 문제가 발생합니다. 오리진이 다르기 때문이거나, 더 나쁜 경우: 작동은 하지만 이제 세상의 모든 오리진이 당신의 백엔드에 요청을 보낼 수 있게 허용한 꼴이 됩니다. 당신이 원치 않는 사람들까지 말이죠. 실제로 프론트와 백엔드 개발자 간의 불필요한 논쟁의 상당수는 잘못된 CORS 설정에서 시작되는 경우가 많습니다.
Next.js + Node.js 설정에서 실제로 올바른 CORS 구성은 다음과 같습니다:
// Express 백엔드 - cors.config.js
const allowedOrigins = [
'http://localhost:3000',
'https://your-actual-domain.com',
process.env.FRONTEND_URL // 동적으로 .env에 설정
].filter(Boolean);
app.use(cors({
origin: function(origin, callback) {
// 오리진이 없는 요청 허용 (모바일 앱, Postman, curl)
if (!origin) return callback(null, true);
if (allowedOrigins.indexOf(origin) === -1) {
const msg = 'CORS 정책이 이 오리진을 차단했습니다: ' + origin;
return callback(new Error(msg), false);
}
return callback(null, true);
},
credentials: true, // 쿠키/세션을 사용하는 경우
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
솔직히 말해서, 이미 Next.js를 사용하고 있다면 별도의 Express 서버가 정말 필요한지 스스로에게 물어봐야 합니다. Next.js API 라우트는 이런 CORS 문제 없이도 상당량의 백엔드 로직을 처리할 수 있습니다.
4. API 라우트와 Express를 언제 사용해야 하는지 제대로 모른다
이것은 팀에 몇 주간의 비용을 지불하게 만드는 아키텍처적 혼란입니다.
Next.js에는 내장된 API 라우트( pages/api/ 또는 app/api/route.ts )가 있습니다. 이들은 Node.js 환경에서 실행됩니다. Vercel에 배포할 때는 기본적으로 서버리스로 작동하며, 대부분의 경우에 충분합니다.
하지만 많은 개발자가 다음과 같은 실수를 저지릅니다:
a) Next.js API 라우트로 완벽하게 처리할 수 있는 기능을 위해 전체 Express.js 서버를 구축하여 불필요한 복잡성과 CORS 문제를 초래합니다.
b) 실제로 지속적인 연결, WebSockets, 무거운 백그라운드 작업, 또는 Vercel이 잘 지원하지 않는 다른 기능이 필요함에도 불구하고 모든 것을 Next.js API 라우트에 쑤셔 넣습니다.
다음은 솔직한 비교입니다:
| 사용 사례 | Next.js API 라우트 | 별도 Node.js/Express |
|---|---|---|
| 앱 자체 데이터 CRUD | 완벽함 | 과도한 기능 |
| 인증 (JWT, 세션) | 괜찮음 | 불필요한 복잡성 |
| 실시간 기능 (WebSockets) | 네이티브 지원 안 함 | 필요함 |
| 무거운 파일 처리 | 콜드 스타트 문제 발생 | 더 좋음 |
| 여러 프론트엔드 간 공유 API | 불편함 | 올바른 선택 |
| 마이크로서비스 아키텍처 | 잘못된 도구 | 올바른 도구 |
| 백그라운드 작업 / Cron | 제한적 (Vercel Cron 제한) | 더 좋음 |
| 장기 실행 프로세스 | 엄격한 타임아웃 제한 | 필요함 |
| 대규모 데이터베이스 커넥션 풀링 | 작동하지만 까다로움 | 더 많은 제어 |
표준 웹 앱에 데이터베이스, 인증, CRUD 기능만 있다면 그냥 Next.js API 라우트를 사용하세요. Express는 필요 없습니다. 불필요하게 복잡성만 추가하는 셈이죠.
지속적인 연결, 여러 클라이언트에 서비스 제공, 무거운 백그라운드 작업 실행, 또는 직접 제어하는 VM에서 실행되어야 하는 무언가를 구축하고 있다면 별도의 Node.js 서버가 합리적입니다.
제가 본 대부분의 개발자들은 Express를 먼저 배웠기 때문에 놓지 못하고 잘못된 선택을 하는 경우가 많았습니다.
5. 프로덕션에서 죽어버리는 하드코딩된 URL
이것은 너무나 흔해서 거의 통과의례 같은 실수입니다.
// 노트북에서는 작동하지만, 다른 모든 곳에서는 깨집니다
const response = await fetch('http://localhost:3001/api/users');
이것은 localhost로 하드코딩되어 있습니다. Vercel이나 다른 서버에 배포하면 localhost:3001은 아무 의미가 없습니다. 프로덕션 환경에는 localhost라는 머신이 없죠. 요청은 어디로도 가지 못합니다.
해결책은 복잡하지 않습니다. 하지만 실제로 적용해야 합니다.
// .env.local (개발 환경용)
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001
// .env.production (프로덕션 환경용)
NEXT_PUBLIC_API_BASE_URL=https://api.yourdomain.com
// 코드에서
const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL;
const response = await fetch(`${API_BASE}/api/users`);
만약 Next.js API 라우트를 백엔드로 사용한다면(동일한 앱), 절대 URL조차 필요 없습니다:
// 백엔드가 동일한 Next.js 앱일 경우 개발 및 프로덕션 모두에서 작동
const response = await fetch('/api/users');
상대 URL. 저평가되어 있습니다. 사람들은 이걸 너무 복잡하게 생각합니다.

6. 풀스택 프로젝트의 Git 워크플로우는 다르다는 것을 아무도 이야기하지 않는다
일반적인 단일 기술 프로젝트에서 Git 워크플로우는 간단합니다. 기능 브랜치, PR, 병합. 하지만 Next.js + Node.js 프로젝트에서 프론트엔드와 백엔드가 때로는 동일한 리포지토리에 있고 때로는 다른 리포지토리에 있을 때, 상황은 빠르게 복잡해집니다.
두 가지 주요 패턴은 다음과 같습니다:
모노레포 (동일 리포지토리, 두 앱 모두)
my-project/
├── apps/
│ ├── web/ (Next.js)
│ └── api/ (Express.js)
├── packages/
│ └── shared/ (공유 타입, 유틸리티)
└── package.json (워크스페이스가 포함된 루트)
폴리레포 (개별 리포지토리)
my-project-frontend/ (Next.js)
my-project-backend/ (Express.js)
대부분의 주니어 개발자들이 저지르는 실수는 위 패턴 중 어느 것도 따르지 않는다는 점입니다. 모든 것을 하나의 평평한 폴더에 넣고 모두 함께 커밋하여, 프론트엔드 변경과 백엔드 변경이 항상 동일한 커밋에 얽히게 만듭니다. 코드 리뷰를 복잡하게 만들고, 롤백을 악몽으로 만들죠.
그리고 브랜칭이 있습니다. 풀스택에 실제로 효과적인 방식은 다음과 같습니다:
# 절대로 main에 직접 커밋하지 마세요
git checkout -b feature/user-authentication
# 의미 있는 메시지로 자주 커밋하세요
# 나쁜 커밋 메시지:
git commit -m "fix stuff"
# 좋은 커밋 메시지:
git commit -m "feat(auth): /api/auth/verify에 JWT 유효성 검사 미들웨어 추가
- 토큰 만료 확인
- 유효하지 않은 토큰에 대해 401 반환
- 실패 시도 콘솔에 로깅 (임시)"
# 완료되면 푸시하고 PR을 엽니다
git push origin feature/user-authentication
컨벤셔널 커밋 표준 (feat:, fix:, chore:, docs:, refactor:)은 단순히 미적인 것이 아닙니다. semantic-release와 같은 도구나 자동 변경 로그 생성기가 여기에 의존합니다. 2026년 GitHub에 통합된 AI 코드 리뷰 도구와 함께, 구조화된 커밋은 AI에게 변경 사항에 대한 더 나은 컨텍스트를 제공하기도 합니다.
7. package-lock.json 내전
모든 팀은 결국 이 싸움을 겪게 됩니다.
어떤 개발자는 npm install을 사용합니다. 다른 개발자는 yarn add를 사용하죠. 세 번째 개발자는 유튜브 비디오를 보고 pnpm을 사용했습니다. 이제 같은 리포지토리에 package-lock.json, yarn.lock, pnpm-lock.yaml이 모두 존재합니다. 세 가지 다른 락파일, 세 가지 다른 의존성 해결 알고리즘, 그리고 프로덕션은 당신의 노트북에서 실행되는 패키지 버전과 완전히 다른 버전을 실행하고 있을 수도 있습니다.
이것은 사소한 문제가 아닙니다. Node.js의 패키지 생태계는 악성 코드가 인기 패키지에 포함되는 공급망 공격을 겪어왔습니다. 락파일은 당신의 방어 메커니즘입니다. 만약 팀의 모든 사람이 다른 락파일을 생성하고 있다면, 그 락파일은 무의미합니다.
하나의 패키지 관리자를 선택하고 강제하세요. 나머지는 삭제하십시오.
// package.json - 이것을 추가하세요
{
"engines": {
"node": ">=22.0.0",
"npm": ">=10.0.0"
},
"packageManager": "npm@10.8.0"
}
# .npmrc - 다른 패키지 관리자 사용을 막기 위해
engine-strict=true
그리고 package-lock.json을 커밋하세요. 크기가 크고, 병합 충돌을 일으킬 수 있다는 것을 압니다. 그래도 커밋하세요. .gitignore에 추가하는 순간, 재현 가능한 빌드를 포기하는 것과 다름없습니다.
8. .next 폴더는 소스 코드가 아닙니다
이것은 작은 실수지만, 이런 것들이 쌓여 문제를 일으킵니다.
.next/는 Next.js의 빌드 출력 폴더입니다. next build나 next dev를 실행할 때마다 새로 생성됩니다. 당신의 머신, 당신의 Node.js 버전, 그리고 당신의 빌드 설정에 특화되어 있죠.
일부 개발자들은 필요하다고 생각해서 이 폴더를 커밋합니다. 하지만 필요 없습니다. .gitignore에 포함되어야 합니다.
| 폴더 | Git에 커밋? | 이유 |
|---|---|---|
.next/ | 아니요 | 빌드 출력물, 머신별로 다름 |
node_modules/ | 절대 아니요 | package.json에서 설치 가능 |
out/ | 보통 아니요 | 정적 export 출력물 |
public/ | 예 | 앱에서 제공하는 정적 자산 |
src/ | 예 | 실제 소스 코드 |
.env.local | 절대 아니요 | 로컬 비밀 정보 |
next.config.js | 예 | 설정 파일, 비밀 정보 아님 |
node_modules를 커밋하는 것은 이 실수의 고전적인 버전입니다. 모두 결국 배우게 되죠. 하지만 .next/와 out/는 생각보다 많은 사람들을 곤경에 빠뜨립니다.
9. 아무도 실제 환경에 맞게 next.config.js를 구성하지 않는다
next.config.js 파일은 많은 중요한 프로덕션 동작이 정의되는 곳이지만, 대부분의 튜토리얼에서는 이 부분을 완전히 건너뜁니다.
다음은 설정되지 않아서 프로덕션에서 문제가 발생하는 실제 예시입니다:
// next.config.js - 실제 프로덕션 설정은 이렇게 생겼습니다
/** @type {import('next').NextConfig} */
const nextConfig = {
// Next.js에 외부 API의 위치를 알려줍니다 (리라이트/프록시용)
async rewrites() {
return [
{
source: '/api/backend/:path*',
destination: `${process.env.BACKEND_URL}/api/:path*`,
},
];
},
// 이미지 도메인 (외부 이미지에서 400 오류가 나지 않도록)
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'your-image-cdn.com',
port: '',
pathname: '/images/**',
},
],
},
// Next.js 설정을 통해 노출시키려는 환경 변수
// (빌드 타임 값에 대한 NEXT_PUBLIC_ 접두사 대체제)
env: {
APP_VERSION: process.env.npm_package_version,
},
// 엄격 모드 - 개발 환경에서 더 많은 React 문제 감지
reactStrictMode: true,
// 배포를 위한 출력 모드
// 'standalone'은 Docker/VPS 배포에 적합합니다
output: process.env.NEXT_OUTPUT_MODE || undefined,
};
module.exports = nextConfig;
많은 사람들을 힘들게 하는 것 중 하나: Next.js 15(App Router 포함)는 Pages Router와 일부 엣지 케이스에서 리라이트 처리가 다릅니다. Node.js 백엔드에 대한 요청을 Next.js 리라이트를 통해 프록시하는 경우, 프로덕션과 일치하는 스테이징 환경에서 테스트해야 합니다. Vercel의 서버리스 환경은 VPS에서 next start를 실행하는 것과 동일하지 않습니다.
10. 아무도 계획하지 않는 배포 분할
마지막이자 실제 프로젝트에서 가장 비싼 실수입니다.
로컬에서 앱을 개발합니다. Next.js와 Node.js가 모두 당신의 머신에서 실행되고, 통신도 잘 되고, 모든 것이 완벽하게 작동합니다. 그러다 배포를 시작하죠. 그리고 누군가(아마도 당신)는 생각 없이 결정을 내립니다:
"Next.js 앱은 Vercel에 배포해야지. 무료이고 쉬우니까."
그래서 Next.js 앱은 서버리스, 자동 확장, 엣지 배포되는 Vercel에 올라갑니다.
Node.js 백엔드는... 5달러짜리 DigitalOcean droplet에 올라가거나, Railway에, 아니면 15분 동안 활동이 없으면 종료되는 Render의 무료 티어에 올라갑니다.
이제 당신은 다음과 같은 상황에 처합니다:
- 서로 다른 배포 주기
- 서로 다른 환경 변수 관리 시스템
- 한쪽은 콜드 스타트 지연이 있지만 다른 쪽은 없음
- 도메인이 변경될 때마다 업데이트해야 하는 CORS 구성
- 유지보수해야 할 두 개의 별도 CI/CD 파이프라인
- 무료 티어에서 Node.js 백엔드가 콜드 스타트를 겪으며, Vercel 프론트엔드가 느려 보이게 만듦
아무도 이걸 계획하지 않았습니다. 우연히 일어난 일이죠. 그리고 프로젝트 시작 후 3개월 만에 리팩토링을 해야 하는 상황에 놓입니다. 제가 직접 겪어봤을 때, 이런 불필요한 배포 분리는 팀의 불필요한 시간 낭비와 스트레스로 직결되곤 했습니다.
코드를 한 줄도 작성하기 전에 답해야 할 계획 질문은 다음과 같습니다:
| 시나리오 | 프론트엔드 | 백엔드 | 참고 사항 |
|---|---|---|---|
| 취미 프로젝트, 낮은 트래픽 | Vercel | Vercel (API 라우트만) | 하나의 배포, CORS 없음 |
| 중간 규모 앱, 예산 고려 | Vercel | Railway / Render | 콜드 스타트 계획 |
| 진지한 프로덕션 앱 | Vercel | VPS (DigitalOcean, Hetzner) | 가장 많은 제어, 더 많은 작업 |
| 완전 제어 / 엔터프라이즈 | VPS (Docker) | VPS (동일 또는 다름) | 모든 것을 직접 관리 |
| 모노레포, 풀스택 | Vercel | Vercel (둘 다) | 서버리스가 괜찮다면 작동 |
실제로 잘하는 개발자들이 아무도 말하지 않는 것
저는 충분한 코드베이스를 작업하고 관찰하면서 그 차이를 알아차렸습니다. 다음은 이 문제를 올바르게 처리하는 개발자들을 구분하는 요소입니다:
그들은 첫 번째 컴포넌트를 작성하기 전에 프로젝트 구조를 설정합니다. .gitignore, 환경 변수 스키마, API 기본 URL 구성 – 이 모든 것을 git init 전에 마무리하죠.
그들은 환경 변수 로딩을 독립적으로 테스트합니다. API 호출을 연결하기 전에, 실제로 사용될 런타임 컨텍스트에서 console.log(process.env.WHATEVER)를 찍어 확인합니다. 서버 컴포넌트? 클라이언트 컴포넌트? API 라우트? 모두 확인합니다.
그들은 .env.example 파일을 리포지토리에 커밋하여 필요한 모든 변수를 값 없이 보여줍니다. 이렇게 하면 팀원들에게 무엇을 구성해야 하는지 알려주면서도 아무것도 유출하지 않을 수 있습니다.
# .env.example (이것을 커밋하세요)
DATABASE_URL=postgresql://user:password@host:port/database
NEXTAUTH_SECRET=generate-with-openssl-rand-base64-32
NEXTAUTH_URL=http://localhost:3000
BACKEND_URL=http://localhost:3001
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001
# 프로덕션 값은 배포 플랫폼의 환경 설정에 입력하세요
# 절대로 .env.local 또는 .env.production을 커밋하지 마세요
그들은 Git 히스토리를 문서로 취급합니다. 단지 코드 리뷰만을 위해서가 아닙니다. 6개월 후에 프로덕션에서 문제가 발생했을 때, 좋은 커밋 히스토리는 몇 시간 대신 몇 분 안에 문제를 찾아낼 수 있도록 돕습니다.
# 커밋을 이진 탐색하여 버그가 도입된 시점 찾기
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
# Git이 중간 커밋으로 체크아웃하면 테스트합니다
# 그리고 정확한 커밋을 찾을 때까지 good 또는 bad로 표시합니다
git bisect good # 또는 git bisect bad
그들은 2026년에 App Router와 Pages Router 중 무엇을 사용할지 논쟁하지 않습니다. App Router가 기본이고, 안정적이며, React Server Components는 이 생태계의 미래입니다. 오늘날 새로운 프로젝트를 시작하면서 Pages Router를 사용한다면, 사실상 더 이상 사용되지 않는 패턴을 의도적으로 선택하는 것입니다. 물론 선택은 자유지만, 그것이 어떤 선택인지 솔직해져야 합니다.
매운맛 주장: 진짜 문제는 튜토리얼이지 개발자가 아니다
자, 이 말을 하면 사람들이 저에게 동의하지 않을 겁니다.
개발자 90%가 이 문제들을 잘못 처리하는 이유는 어리석거나 게을러서가 아닙니다. 바로 튜토리얼 산업 때문이죠.
"Next.js와 Node.js로 풀스택 앱 만들기" 유튜브 튜토리얼은 43분 길이입니다. 처음 38분은 기능 구현에 할애하고, 마지막 5분은 "이제 Vercel에 배포하세요."로 끝납니다. .gitignore 설정은 30초밖에 걸리지 않는데, 카메라 밖에서 처리되거나 설명 없이 템플릿에서 복사됩니다. 환경 변수는 단순화를 위해 하드코딩되죠. CORS는 app.use(cors())로 해결되고 아무도 그 이유를 설명해주지 않습니다.
수백만 명의 개발자가 이런 튜토리얼을 통해 배웠습니다. 그리고 그 패턴으로 실제 무언가를 만들려다가 실제 문제에 직면하게 된 거죠.
"문서는 도구가 무엇을 하는지 알려줍니다. 하지만 도구를 잘못 사용했을 때 어떤 일이 벌어지는지는 거의 알려주지 않습니다."
"튜토리얼을 마쳤다"와 "실제 프로덕션 소프트웨어를 만들 수 있다" 사이의 간극, 바로 그곳에 대부분의 개발자들이 갇혀 있습니다. 그리고 Next.js, Node.js, Git의 조합에서 그 간극이 가장 극렬하게 드러납니다.
이 문제의 80%를 해결할 단 하나의 방법
놀랍도록 간단합니다.
모든 프로젝트를 다음처럼 시작하세요:
# 1. 다른 무엇보다 Git을 먼저 초기화합니다
git init my-project
cd my-project
# 2. 어떤 파일도 추가하기 전에 .gitignore를 생성합니다
# 위 Node + Next.js 섹션의 예시를 복사합니다
# 3. .env.example을 생성합니다
touch .env.example
# 앱에 필요한 모든 변수를 (비어있는 값으로) 작성합니다
# 4. 실제 로컬 값들을 위한 .env.local을 생성합니다
# (이것은 이미 .gitignore에 있으므로 커밋되지 않습니다)
# 5. 그 다음에 Next.js 앱을 스캐폴딩합니다
npx create-next-app@latest . --typescript
# 6. 첫 번째 커밋은: gitignore + env.example + 그 외 아무것도 아니어야 합니다
git add .gitignore .env.example README.md
git commit -m "chore: gitignore와 env 템플릿으로 프로젝트 초기화"
이 순서. 이대로만. 모든 새로운 개발자가 Next.js 프로젝트를 시작할 때 이 여섯 단계를 이 순서대로 따른다면, 우발적인 비밀 정보 노출 건수는 극적으로 줄어들 것입니다.
순서가 중요한 이유는 create-next-app을 실행하고 나면 즉시 빌드를 시작하고 싶어지기 때문입니다. 그리고 그 흥분 속에서 git add .를 실행할 때 실제로 무엇이 스테이징되는지 확인하는 것을 잊어버리게 되는 거죠.
데이터 스냅샷: 2026년 현재 이 스택의 위치
이것이 대규모로 중요한 이유를 설명하는 몇 가지 수치입니다:
- Next.js는 주간 npm 다운로드 기준으로 React 메타 프레임워크 중 여전히 지배적인 위치를 유지합니다 (2026년 1분기 내내 주당 580만 건 이상)
- Node.js 22 LTS는 2026년 초 현재 권장 버전이며; 2025년 4월에 출시된 Node.js 24는 2026년 10월에 LTS로 전환될 예정입니다
- Git은 Stack Overflow Developer Survey 2025에서 개발자의 97.8%가 사용한다고 응답했습니다 — 사실상 보편적입니다
- The State of JavaScript 2024 설문조사는 Next.js가 81%의 유지율(사용해본 개발자 중 다시 사용할 의사가 있는 비율)을 보였으며, 매년 채택률이 증가하고 있음을 보여주었습니다
- GitHub의 2024년 투명성 보고서 데이터에 따르면 비밀 정보 스캔 기능이 수백만 건의 노출을 공개되기 전에 차단했습니다 — 이는 푸시 보호 기능이 이 글에서 설명한 실수들을 정확하게 잡아내고 있음을 의미합니다
이러한 채택 규모는 이러한 실수들이 소수의 개발자에게만 일어나는 것이 아님을 의미합니다. 전 세계적으로, 모든 경험 수준의 사람들이 매일 수만 번씩 저지르고 있는 실수입니다.
온라인에 좋은 답이 없는 일반적인 질문들
"스타트업이라면 Next.js API 라우트와 별도 Express 서버 중 무엇을 사용해야 하나요?"
스타트업 MVP를 구축하고 있다면 Next.js API 라우트를 사용하세요. 더 빠르게 출시할 수 있고, 관리해야 할 배포가 하나 줄어듭니다. 실제 스케일링 문제에 직면하면 그때 마이그레이션하세요. 생각보다 일찍 그 문제에 부딪히지는 않을 겁니다.
"node_modules가 한 번이라도 커밋되면 문제가 되나요?"
네. node_modules는 수백 메가바이트에 달할 수 있습니다. 리포지토리를 영구적으로 비대하게 만들고 (기억하세요, Git은 히스토리를 보관합니다), 재현성 문제를 일으킵니다. 비밀 정보를 제거하는 것과 같은 방식으로 git filter-repo를 사용하여 히스토리에서 삭제하세요.
"Next.js 15와 함께 사용할 올바른 Node.js 버전은 무엇인가요?"
Node.js 18이 Next.js 15의 최소 요구 사항입니다. 2026년 초 현재 프로덕션에서는 Node.js 22 LTS를 사용해야 합니다. Next.js 문서의 호환성 표가 최신 정보를 제공하므로 이를 참고하는 것이 가장 정확합니다.
"제 Next.js 앱은 Vercel에서 작동하는데, 환경 변수가 undefined인 이유는 무엇인가요?"
당신은 로컬 머신의 .env.local에 설정했지만, Vercel 대시보드의 환경 변수 설정에 추가하는 것을 잊었기 때문입니다. Vercel은 당신의 .env.local을 읽지 않습니다. Vercel 프로젝트 설정에서 모든 변수를 수동으로 설정해야 합니다. 이 문제는 누구나 한 번쯤 겪는 실수입니다.
마지막 생각
Next.js, Node.js, Git의 조합은 진정으로 강력합니다. 2026년의 생태계는 이 스택으로 규모가 두 배인 팀과도 경쟁할 수 있는 진지한 프로덕션 애플리케이션을 구축할 수 있을 만큼 성숙했습니다.
하지만 '강력하다'와 '관대하다'는 다릅니다. 이 스택은 관대하지 않습니다. 당신의 .env를 GitHub에 푸시하도록 내버려 둘 겁니다. 비밀 키 대신 undefined를 조용히 반환할 겁니다. 새벽 2시에 환경 변수가 깨진 채 배포되고도 유용한 오류 메시지를 주지 않을 겁니다.
이 문제를 올바르게 처리하는 개발자들이 반드시 더 재능 있는 것은 아닙니다. 그저 한 번 고통스러운 경험을 통해 배웠거나, 운 좋게도 그런 경험을 한 사람에게서 배웠을 뿐입니다.
이제 당신은 직접 겪지 않고도 이 모든 것을 읽었습니다.
이 지식을 활용하세요.
참고 자료 및 출처
- GitHub Security: "Detected and Prevented Secrets in 2024" — GitHub 블로그, 2025년 1월. https://github.blog/security/application-security/
- Stack Overflow Developer Survey 2025 — 버전 관리 시스템 사용 데이터. https://survey.stackoverflow.co/2025
- State of JavaScript 2024 — Meta-Frameworks 섹션, Next.js 유지율 및 사용 수치. https://2024.stateofjs.com/
- Next.js 15 릴리스 노트 및 변경 로그 — Vercel/Next.js GitHub 리포지토리. https://github.com/vercel/next.js/releases/tag/v15.0.0
- Node.js 릴리스 스케줄 — Node.js 재단. https://nodejs.org/en/about/previous-releases
nextnpm 패키지 다운로드 통계 — https://www.npmjs.com/package/next (주간 다운로드 추세, 2024-2026)- Next.js 문서: 환경 변수 — https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- Next.js 문서: API 라우트 — https://nextjs.org/docs/pages/building-your-application/routing/api-routes
- Conventional Commits Specification v1.0.0 — https://www.conventionalcommits.org/
git-filter-repo문서 — https://github.com/newren/git-filter-repo- OWASP Secrets Management Cheat Sheet — https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html
- Vercel 배포 환경 변수 가이드 — https://vercel.com/docs/projects/environment-variables
이 글은 실제 코드베이스에서 이러한 문제들을 디버깅했던 저의 경험을 반영합니다. 설명된 실수들은 엣지 케이스가 아니라 제가 반복적으로 보아온 패턴입니다. 만약 여기에 있는 내용이 당신이 해왔던 것과 모순된다면, 그것은 우연이 아닙니다.
온라인에서 저를 찾아보세요:
- ✍️ Medium: @syedahmershah
- 💬 DEV.to: @syedahmershah
- 🧠 Hashnode: @syedahmershah
- 💻 GitHub: @ahmershahdev
- 🔗 LinkedIn: Syed Ahmer Shah
- 🌐 Portfolio: ahmershah.dev
원문: https://dev.to/syedahmershah/what-90-of-devs-screw-up-when-wiring-nextjs-nodejs-and-git-together-4ieh 수집일: 2026-06-06 01:55:12