- 발행일
배포 첫날 커밋 열 개 — www 누락, 리라이트 삭제, 그리고 읽히지도 않던 middleware
배포 첫날 커밋 열 개
이 글은 2026년 9월에 당시 커밋 이력을 다시 보며 정리한 것입니다. 날짜는 작업한 날 기준입니다.
2024년 11월 28일 오후 4시 48분, "사이트 배포 시작"이라는 커밋을 올렸습니다. 그날 밤 11시 반까지 커밋이 열 개 더 붙었어요. 제목만 늘어놓으면 이렇습니다.
16:48 사이트 배포 시작
16:58 사이트 배포 시작 : www 누락
17:12 메타 태그의 URL 수정
17:31 리라이트 삭제
17:36 리라이트 삭제 2차
18:33 재시도
18:48 재시도 2차
18:55 재시도 3차
18:57 재시도 4차
23:32 아이콘 신규 추가
로컬에서는 잘 돌던 앱이 배포하니까 API 요청이 안 갔고, 저는 뭐가 문제인지 모른 채 설정 파일을 하나씩 바꿔 가며 다시 올렸습니다. 프런트 4년차였지만 Next.js App Router로 배포까지 혼자 맡은 건 이때가 처음이었어요. 같은 날 웹소켓도 안 붙었는데, 그 이야기는 따로 썼습니다. 이 글은 HTTP 요청과 리라이트, middleware 쪽만 다룹니다.
로컬 주소를 그대로 들고 올라갔다
첫 커밋(decfbf4)의 diff는 한 줄입니다.
{
source: '/api/:path*',
- destination: 'http://13.209.177.247:8080/api/:path*',
+ destination: 'https://triptune.site/api/:path*',
},
개발 중에는 백엔드 EC2의 IP를 next.config.js의 리라이트에 박아 두고 썼습니다. 브라우저가 /api/...로 요청하면 Next 서버가 백엔드로 넘겨 주는 구조예요. 배포하면서 IP를 도메인으로 바꿨는데, 10분 뒤 두 번째 커밋(b775d42)이 www.를 붙였습니다. 커밋 제목이 "www 누락"이에요. 백엔드가 www가 붙은 쪽에서만 받았던 것으로 읽히는데, 올려 보고 나서야 안 겁니다.
리라이트를 지우고, middleware도 지우고
그래도 안 됐습니다. 17시 31분 커밋(aa2ab2b)에서 리라이트를 지우고, 요청 함수가 절대 주소를 직접 만들도록 바꿨어요.
- const url = `${endpoint}`;
+ const url = `https://www.triptune.site${endpoint}`;
5분 뒤 "리라이트 삭제 2차"(0ec4bc6)에서는 middleware.ts를 파일째 지우고 rewrites() 블록도 통째로 걷어냈습니다. 그리고 한 시간 뒤부터 "재시도"가 네 번 이어져요.
| 커밋 | 무엇을 했나 |
|---|---|
cc9bb12 재시도 | _redirects 파일 추가(/api/*를 백엔드로, 나머지를 /.netlify/functions/next_router로), @netlify/plugin-nextjs 설치 |
736b680 재시도 2차 | netlify.toml에 같은 리다이렉트를 한 번 더 |
e79cfd1 재시도 3차 | to = "https:/www.triptune.site/api/:splat" — www를 붙이면서 슬래시 하나를 빠뜨림 |
f38c01a 재시도 4차 | 지웠던 middleware.ts와 rewrites()를 그대로 되살림 |
3차의 https:/www는 지금 봐야 보이는 오타입니다. 그날은 못 봤어요. 4차에서 지운 걸 되살렸다는 건, 지운 게 원인이 아니었다는 뜻이기도 합니다. 그러니까 리라이트 삭제 두 번은 아무것도 고치지 못했고, 그 사실을 확인하는 데 한 시간 반이 걸렸습니다.
다음날, matcher를 뒤집었다
11월 29일 13시 51분 커밋 제목은 "도메인을 사용하고 싶어요.."입니다(ddaef20). middleware를 이렇게 바꿨어요.
export function middleware(req: NextRequest) {
const { pathname } = req.nextUrl;
-
- if (pathname.startsWith('/api') && !/\/api\//.test(pathname)) {
- return NextResponse.rewrite('/404');
+ if (pathname.startsWith('/api')) {
+ return NextResponse.next();
}
-
- return NextResponse.next();
+ return NextResponse.rewrite('/404');
}
export const config = {
- matcher: ['/api/:path*'],
+ matcher: ['/((?!api).*)'],
};
전에는 /api로 시작하는 요청만 보다가, 이제는 /api가 아닌 모든 요청을 봅니다. 그리고 그 요청을 전부 /404로 보내요. 글자 그대로 읽으면 홈 화면을 포함한 모든 페이지가 404가 되는 코드입니다. 그런데 사이트는 떴고, 이 코드는 /api가 /apis로 바뀐 것 말고는 지금 브랜치에도 그대로 있습니다.
지금 다시 보니 이유가 있습니다. 이 프로젝트는 src/ 디렉터리를 씁니다. Next.js 문서는 src를 쓰는 프로젝트에서는 middleware.ts도 src/ 안에 두라고 합니다. 저장소의 middleware.ts는 프로젝트 루트에 있어요. 그러니까 이 파일은 실행된 적이 없었을 가능성이 큽니다. 지우고, 되살리고, matcher를 뒤집은 것 전부가 아무 효과도 없었던 거예요. 당시 배포 로그가 남아 있지 않아 100% 단정은 못 하지만, 저 코드가 실제로 돌았다면 사이트가 떴을 리 없습니다.
그 뒤 한 달
같은 날 15시 23분(c6b7609)에는 리라이트를 주석 처리하고 요청 함수에 https://www.triptune.site/${endpoint}를 넣었는데, endpoint가 /api로 시작해서 슬래시가 두 개 붙었습니다. 다음날(74bf560) 슬래시를 뺐고, 12월 5일(554bdc8)에는 다시 상대 경로로 돌아왔어요. 그 커밋에 남긴 주석이 // TODO : 예전 URL 과 달라요!입니다.
12월 23일(65b7268)에 /api를 /apis로 바꾸고 _redirects와 netlify.toml을 지웠습니다. 왜 /apis인지는 커밋에 안 남아 있어요. 그리고 12월 29일(63faf44)에 rewrites()가 /apis/:path* → https://www.triptune.site/api/:path*로 다시 들어옵니다. 한 달 동안 같은 설정을 넣고 빼고 이름을 바꾼 겁니다.
검증
- 그날 배포가 결국 됐는지는 커밋으로 알 수 있습니다. 다음날부터 기능 커밋이 이어지고, 웹소켓 글에 적힌 대로 사이트가 열려 있었으니까요. 다만 어느 커밋이 API 요청을 살렸는지는 모릅니다. 여러 개를 한꺼번에 바꿔서 원인을 고립시킬 수 없었어요.
- middleware가 실행되지 않았다는 판단은 파일 위치(루트
middleware.ts+src/디렉터리)와 코드 내용(비/api요청 전부/404)을 놓고 2026년에 내린 것입니다. 당시 로그로 확인한 게 아닙니다. https:/www오타는git show e79cfd1로 확인했습니다.
남은 것 · 한계
- 한 번에 하나씩 바꾸지 않았습니다. 리라이트, 요청 함수의 base URL, Netlify 설정, middleware를 같은 시간대에 같이 만졌어요. 그래서 열 개 커밋 중 어느 것이 효과가 있었는지 지금도 모릅니다.
- middleware.ts는 지금도 루트에 있습니다. 실행되지 않는 파일이
/apis버전으로 살아남아 있어요. 지우거나src/로 옮겨서 실제로 돌게 하거나 둘 중 하나를 해야 하는데, 이 글을 쓰는 시점에도 안 했습니다. @netlify/plugin-nextjs는_redirects와netlify.toml을 지운 뒤에도package.json에 남아 있습니다.- 반년 뒤 SDMS에 입사해 첫 배포에서 정적 파일 경로가 Nginx에서 깨지는 일을 겪었는데, 로컬과 배포 환경의 경로 해석 차이라는 점에서 같은 종류의 문제였습니다. 그때도 커밋 세 개로 고치긴 했는데, 왜 고쳐졌는지는 역시 남기지 못했습니다. 같은 종류의 문제를 두 번 겪고도 원인은 두 번 다 못 좁힌 셈이에요.
관련 글: WebSocket connection to wss failed — 후보 열 개를 놓고 하루 종일 좁힌 기록 · fetch가 Next.js에서 선호되는 이유, 그리고 axios와의 차이점 · axios를 걷어냈다가 이틀 만에 되돌렸다가 다시 걷어냈다 · ./img가 Nginx에서 깨졌다