after()로 응답 이후 작업을 처리하는 법
응답을 막지 않고 뒷정리를 하고 싶을 때
로그를 남기거나, 분석 이벤트를 보내거나, 캐시를 미리 데워두는 작업들이 있습니다. 이런 작업의 공통점은 사용자가 결과를 기다릴 필요가 없다는 겁니다. 그런데 서버 액션이나 라우트 핸들러 안에서 await로 이런 작업을 처리하면, 사용자는 로깅이 끝날 때까지 응답을 기다리게 됩니다. 느린 로깅 서비스 하나가 전체 요청을 늦추는 셈이죠.
next/server의 after()는 이 문제를 위해 있습니다. 응답이 사용자에게 전달된 뒤에 실행할 작업을 등록합니다.
import { after } from "next/server";
import { logAnalyticsEvent } from "@/lib/analytics";
export async function POST(request: Request) {
const body = await request.json();
const result = await saveOrder(body);
after(() => {
// 응답이 나간 뒤 실행된다 — 사용자는 이 작업을 기다리지 않는다
logAnalyticsEvent("order_created", { orderId: result.id });
});
return Response.json(result);
}after()가 아닌 것
가장 흔한 오해는 "백그라운드에서 영원히 실행되는 작업"으로 이해하는 겁니다. 그렇지 않습니다. after()는 현재 요청의 생명주기 안에서 응답 이후로 실행 시점을 미루는 것이지, 요청과 무관한 별도의 워커를 만드는 게 아닙니다. 서버리스 환경(Vercel 등)에서는 함수 인스턴스가 응답 이후 얼마 지나지 않아 종료될 수 있으므로, after() 콜백은 짧게 끝나야 합니다. 몇 분씩 걸리는 배치 작업을 여기 넣으면 인스턴스가 먼저 종료되어 작업이 중간에 끊길 수 있습니다.
또한 after() 콜백 안에서 던진 에러는 사용자에게 전달되지 않습니다. 응답은 이미 나갔으니까요. 그래서 콜백 내부에서 자체적으로 에러를 잡고 로깅하지 않으면, 실패가 조용히 사라집니다. 실무에서는 after() 콜백 자체를 try/catch로 감싸고 실패를 별도로 기록하는 걸 권합니다.
무거운 비동기 작업(이메일 발송 대기열, 이미지 리사이징 배치)이 필요하다면 after()보다 별도의 큐나 백그라운드 작업 서비스를 쓰는 게 맞습니다. after()는 "응답을 늦추고 싶지 않은 가벼운 뒷정리" 용도로 좁게 쓰는 게 안전합니다.