Next.js App Router DELETE API 404 해결기
“GET은 되는데 DELETE만 404 나는 이유”
최근 Next.js + TypeScript + App Router 기반으로 관리자 페이지 CRUD를 만들다가 꽤 헷갈리는 이슈를 만났다.
상황은 단순했다.
- /api/ads/1 GET 요청은 정상
- 그런데 DELETE 요청만 404
- route.ts 안의 console.log도 실행되지 않음
처음에는:
- fetch 문제인가?
- API 경로 문제인가?
- use client 문제인가?
- Turbopack 문제인가?
엄청 헷갈렸는데, 원인은 의외로 params 구조였다.
문제 상황
route.ts
import { NextResponse } from "next/server";
import { ads } from "@/lib/dummy-data";
export async function DELETE(
req: Request,
{ params }: { params: { id: string } }
) {
const id = Number(params.id);
const index = ads.findIndex((a) => a.id === id);
if (index === -1) {
return NextResponse.json(
{ message: "not found" },
{ status: 404 }
);
}
ads.splice(index, 1);
return NextResponse.json({
message: "deleted",
});
}
DeleteModal.tsx
const response = await fetch(`/api/ads/${adId}`, {
method: "DELETE",
});
발생한 에러
DELETE http://localhost:3000/api/ads/1 404 (Not Found)
그리고 route.ts 안의:
console.log("DELETE 들어옴");
조차 실행되지 않았다.
이상했던 점
브라우저에서 직접:
http://localhost:3000/api/ads/1
접속하면:
405 Method Not Allowed
이 나왔다.
이 의미는:
라우트 자체는 존재함
하지만 해당 HTTP Method를 처리 못함
즉:
- API 경로 자체는 정상
- route.ts도 인식 중
- 그런데 DELETE handler만 정상 실행 안 되는 상태

진짜 원인
문제는 params 타입이었다.
현재 프로젝트 환경:
- Next.js 16
- App Router
- React 19
- Turbopack
이 조합에서는 route handler의 params가 Promise 형태로 전달될 수 있다.
잘못된 코드
{ params }: { params: { id: string } }
이건 동기 객체라고 가정한 코드였다.
하지만 실제 runtime에서는:
params = Promise
형태로 전달되고 있었다.
해결 방법
수정 후
import { NextResponse } from "next/server";
import { ads } from "@/lib/dummy-data";
export async function DELETE(
req: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const adId = Number(id);
const index = ads.findIndex((a) => a.id === adId);
if (index === -1) {
return NextResponse.json(
{ message: "not found" },
{ status: 404 }
);
}
ads.splice(index, 1);
return NextResponse.json({
message: "deleted",
});
}
왜 이게 중요할까?
App Router의 핵심 특징 중 하나는:
페이지와 API를 비동기 기반으로 통합 처리한다는 것
특히 최신 Next.js에서는:
- page.tsx
- layout.tsx
- route.ts
모두 async 기반 흐름으로 통합되는 방향이다.
그래서 route.ts에서도 params가 Promise 형태로 전달될 수 있다.
내가 헷갈렸던 포인트
page.tsx에서는 이미 이렇게 쓰고 있었다
const { id } = await params;
그런데 route.ts에서는 예전 방식처럼:
params.id
로 사용하고 있었다.
즉:
- page.tsx는 최신 async 방식
- route.ts는 예전 sync 방식
이 섞여 있었던 것.
이번에 이해한 핵심
App Router에서 params는 “항상 일반 객체”라고 생각하면 안 된다.
환경/버전에 따라:
params: Promise<{ id: string }>
형태일 수 있다.
정리
DELETE만 안 되었던 이유
route.ts에서 params를 동기 객체처럼 사용했기 때문
해결 방법
const { id } = await params;
느낀 점
이번 이슈로:
- Server Component
- Route Handler
- App Router의 async 구조
- params 처리 방식
이 어떻게 연결되는지 조금 더 이해하게 됐다.
특히 App Router는 “비동기 기반 프레임워크”라는 걸 체감한 디버깅이었다 😄
728x90
'ETC > 이슈노트' 카테고리의 다른 글
| [이슈노트] iOS 이슈 모음 #10 (video 자동 재생, line 이슈) (1) | 2025.04.11 |
|---|---|
| [이슈노트] iOS 이슈 모음 #9 (15 이하에서 mask 이슈) (2) | 2024.10.25 |
| [이슈노트] iOS 이슈 모음 #8 (14 이하에서 gap 이슈) (0) | 2024.04.19 |
| [이슈노트] iOS 이슈 모음 #7 (구버전에서 swiper 터치 슬라이드 이슈) (0) | 2023.04.10 |
| [이슈노트] iOS 이슈 모음 #6 (letter-spacing, position: sticky, label 텍스트 줄바꿈) (0) | 2022.11.17 |