본문 바로가기
Frontend2026년 8월 22일7분 읽기

Next.js after() — 응답 후 백그라운드 작업 예약하기

YS
김영삼
조회 3
Next.js after() — 응답 후 백그라운드 작업 예약하기

after()는 Next.js가 응답을 사용자에게 다 보낸 뒤에 실행할 작업을 예약하는 API다. 로깅, 분석 이벤트 전송, 캐시 무효화처럼 "응답 속도에는 영향을 주면 안 되지만 꼭 해야 하는" 곁다리 작업을 응답 이후로 미뤄준다. import { after } from "next/server"로 가져와 서버 컴포넌트, 서버 액션, 라우트 핸들러, 미들웨어 안에서 쓸 수 있다.

서버리스 환경에서 특히 반갑다. 예전엔 응답을 보낸 뒤 백그라운드 작업을 돌리려 해도, 함수가 응답과 함께 얼어붙어서 로그가 안 나가는 일이 흔했다. after()는 그 구간을 프레임워크 차원에서 잡아준다.

왜 필요한가 — 분석 로깅의 딜레마

사용자가 페이지를 열면, 우리는 "누가 언제 뭘 봤나"를 기록하고 싶다. 그런데 이 로그 전송을 응답 경로에 그냥 넣으면, 로그 서버가 느릴 때 사용자 응답까지 같이 느려진다. 몇 번 데인 뒤에야 나는 이 원칙을 세웠다. 사용자에게 필요한 건 먼저 보내고, 우리 사정은 나중에 처리한다.

after()는 정확히 이 순서를 만들어 준다. 콜백은 응답 스트리밍이 끝난 다음 실행되므로, 로그가 아무리 느려도 사용자는 이미 화면을 받은 상태다.

import { after } from "next/server";
import { logView } from "@/lib/analytics";

export default async function Page({ params }) {
  const post = await getPost(params.slug);

  after(async () => {
    // 응답이 나간 뒤 실행 — 사용자 대기시간에 영향 없음
    await logView({ slug: params.slug, at: Date.now() });
  });

  return <Article post={post} />;
}

어디서 쓸 수 있나

위치사용 가능전형적 용도
서버 컴포넌트O조회 로깅
서버 액션O뮤테이션 후 알림 발송
라우트 핸들러O웹훅 응답 후 처리
미들웨어O지오/AB 로깅

참고로 Next.js 15.0에서는 unstable_after라는 이름의 실험 기능이었다가, 15.1부터 after로 정식 승격됐다. 예전 블로그 글에서 unstable_after를 봤다면 이제 접두어 없이 쓰면 된다.

서버 액션에서 — 응답은 즉시, 알림은 나중에

"use server";
import { after } from "next/server";

export async function createComment(formData) {
  const comment = await db.comment.create({
    data: { body: formData.get("body") },
  });

  after(async () => {
    // 사용자는 댓글이 달린 화면을 바로 받는다
    await sendEmailToAuthor(comment);
    await revalidateSearchIndex(comment.postId);
  });

  return { ok: true };
}

주의할 점

  • after 안에서는 요청 데이터를 새로 읽으려 하지 말자: 응답이 이미 끝난 시점이라 cookies()headers()를 새로 호출하면 안 된다. 필요한 값은 콜백 밖에서 미리 변수로 잡아 넘겨라.
  • 실패해도 응답은 안 바뀐다: 콜백에서 에러가 나도 이미 나간 응답에는 영향이 없다. 그래서 콜백 내부에서 try/catch로 직접 로깅해두지 않으면 조용히 사라질 수 있다.
  • 무거운 작업은 큐로: after()는 어디까지나 요청 수명 안에서 처리되는 짧은 후처리용이다. 수 분짜리 배치는 여전히 별도 잡 큐로 빼야 한다.

실전 패턴 — 조회수 집계와 캐시 갱신

가장 자주 쓰는 조합은 "사용자에게 콘텐츠를 먼저 보여주고, 뒤에서 조회수를 올린 다음 관련 캐시를 다시 검증"하는 흐름이다. 이걸 응답 경로에 넣으면 DB 쓰기와 재검증이 모두 사용자 대기 시간에 얹힌다. after() 안으로 옮기면 사용자는 즉시 페이지를 받고, 집계는 뒤에서 조용히 처리된다.

import { after } from "next/server";
import { revalidateTag } from "next/cache";

export default async function PostPage({ params }) {
  const post = await getPost(params.slug); // 사용자에게 필요한 것

  after(async () => {
    try {
      await db.post.update({
        where: { slug: params.slug },
        data: { views: { increment: 1 } },
      });
      revalidateTag(`post-${params.slug}`);
    } catch (e) {
      // 응답은 이미 나갔으니 여기서 직접 로깅
      console.error("after 집계 실패", e);
    }
  });

  return <Article post={post} />;
}

여기서 try/catch를 반드시 넣은 이유는 앞서 말했듯 콜백 실패가 조용히 삼켜지기 때문이다. 나는 이걸 안 넣었다가 며칠 동안 조회수가 안 오르는 걸 눈치 못 챈 적이 있다. 응답이 정상이라 에러 알림도 안 떴다. 그 뒤로는 after() 콜백엔 무조건 예외 처리와 로깅을 세트로 붙인다.

자주 묻는 질문

after는 setTimeout이나 그냥 await 없이 던지는 것과 뭐가 다른가요?

서버리스 환경에서 응답 후 코드는 함수가 동결되면서 실행이 끊길 수 있는데, after는 Next.js 런타임이 응답 종료 시점을 인지해 콜백을 확실히 돌려줍니다. 즉 프레임워크가 실행을 보장하는 예약 슬롯이라는 점이 그냥 프로미스를 방치하는 것과 다릅니다.

after 콜백은 사용자 응답을 지연시키나요?

아니요. 콜백은 응답 스트리밍이 끝난 뒤 실행되므로 사용자가 체감하는 응답 시간에는 영향을 주지 않습니다. 그래서 로깅이나 분석처럼 결과를 기다릴 필요 없는 작업에 적합합니다.

after 안에서 데이터베이스에 쓰기를 해도 되나요?

됩니다. 알림 저장, 통계 집계, 검색 인덱스 갱신 같은 쓰기 작업을 자주 넣습니다. 다만 실행 시간이 요청 수명 안으로 제한되므로, 오래 걸리는 대량 작업은 잡 큐로 분리하는 편이 안전합니다.

여러 번 호출하면 순서가 보장되나요?

같은 요청에서 after를 여러 번 호출하면 등록한 순서대로 실행됩니다. 다만 각 콜백이 비동기라면 내부 await 완료 순서까지 직렬화되는 것은 아니므로, 의존 관계가 있는 작업은 하나의 콜백 안에서 순차적으로 await 하는 편이 명확합니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록