발행일

배포 첫날 커밋 열 개 — www 누락, 리라이트 삭제, 그리고 읽히지도 않던 middleware

배포 첫날 커밋 열 개

이 글은 2026년 9월에 당시 커밋 이력을 다시 보며 정리한 것입니다. 날짜는 작업한 날 기준입니다.

2024년 11월 28일 오후 4시 48분, "사이트 배포 시작"이라는 커밋을 올렸습니다. 그날 밤 11시 반까지 커밋이 열 개 더 붙었어요. 제목만 늘어놓으면 이렇습니다.

16:48  사이트 배포 시작
16:58  사이트 배포 시작 : www 누락
17:12  메타 태그의 URL 수정
17:31  리라이트 삭제
17:36  리라이트 삭제 218:33  재시도
18:48  재시도 218:55  재시도 318:57  재시도 423: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.tsrewrites()를 그대로 되살림

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.tssrc/ 안에 두라고 합니다. 저장소의 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로 바꾸고 _redirectsnetlify.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_redirectsnetlify.toml을 지운 뒤에도 package.json에 남아 있습니다.
  • 반년 뒤 SDMS에 입사해 첫 배포에서 정적 파일 경로가 Nginx에서 깨지는 일을 겪었는데, 로컬과 배포 환경의 경로 해석 차이라는 점에서 같은 종류의 문제였습니다. 그때도 커밋 세 개로 고치긴 했는데, 왜 고쳐졌는지는 역시 남기지 못했습니다. 같은 종류의 문제를 두 번 겪고도 원인은 두 번 다 못 좁힌 셈이에요.

관련 글: WebSocket connection to wss failed — 후보 열 개를 놓고 하루 종일 좁힌 기록 · fetch가 Next.js에서 선호되는 이유, 그리고 axios와의 차이점 · axios를 걷어냈다가 이틀 만에 되돌렸다가 다시 걷어냈다 · ./img가 Nginx에서 깨졌다