본문으로 건너뛰기
개발 머꼬
개발 노트Next.js
hohyeon.dev16

Server Action의 redirect가 catch에 잡혀 리다이렉트가 안 된 이유

  • #Common Pitfall
  • #Engineering Note
  • #Next.js

문제 발생

Server Action에서 저장에 성공하면 상세 페이지로 보내려고 했는데, 리다이렉트가 되지 않고 "알 수 없는 오류" 화면이 떴습니다.

"use server";

export async function createPost(formData: FormData) {
  try {
    const post = await savePost(formData);
    redirect(`/posts/${post.slug}`);   // ❌ try 안
  } catch (error) {
    console.error(error);
    return { message: "저장에 실패했습니다." };
  }
}

로그에는 NEXT_REDIRECT라는 낯선 오류가 찍혀 있었습니다.

원인 분석

redirect()는 값을 반환하는 함수가 아니라 오류를 던지는 함수입니다. Next.js 공식 문서가 그대로 적습니다 — redirect()를 호출하면 NEXT_REDIRECT 오류를 던지고 그것이 던져진 라우트 세그먼트의 렌더링을 종료합니다.

그래서 try 블록 안에서 호출하면 바로 아래 catch가 그 오류를 잡아버립니다. 프레임워크가 받아야 할 신호를 우리 코드가 가로챈 것입니다. 문서도 못박습니다 — redirect는 오류를 던지므로 try/catch를 쓸 때는 try 블록 바깥에서 호출해야 합니다.

해결 방안

  1. try 바깥으로 뺍니다. 성공 여부를 변수로 받고, 리다이렉트는 블록이 끝난 뒤에 합니다.
"use server";

export async function createPost(formData: FormData) {
  let slug: string;
  try {
    const post = await savePost(formData);
    slug = post.slug;
  } catch (error) {
    console.error(error);
    return { message: "저장에 실패했습니다." };
  }

  redirect(`/posts/${slug}`);   // ✅ try 바깥
}
  1. return을 붙일 필요는 없습니다. 문서의 안내대로 redirect는 TypeScript의 never 타입을 쓰기 때문에 return redirect()로 쓸 필요가 없습니다. 붙여도 무해하지만 필수는 아닙니다.

  2. 상태 코드가 왜 307/308인지 알아둡니다. 전통적으로 쓰던 302는 많은 브라우저가 원래 요청 메서드와 무관하게 POST를 GET으로 바꿉니다. /users로 POST한 것이 /people로 GET이 되면 의미가 달라집니다. 307은 메서드를 보존하고, 영구 이동인 308도 마찬가지입니다.

  3. Server Action의 폼 제출은 예외입니다. 문서에 따르면 Server Action 폼 제출은 303 응답을 써서 브라우저가 GET으로 따라가게 합니다. 그리고 JavaScript를 쓸 수 있으면 HTTP 리다이렉트 대신 클라이언트 사이드 내비게이션을 수행합니다.

  4. 영구 이동은 permanentRedirect입니다. 307 대신 308을 보냅니다. URL 구조를 바꿨을 때 검색엔진에 알리는 용도입니다.

  5. 히스토리 동작을 의도대로 고릅니다. 기본값이 상황마다 다릅니다 — Server Action에서는 push, 그 외에서는 replace입니다. Server Component에서는 type 인자가 아무 효과가 없습니다.

import { redirect, RedirectType } from "next/navigation";
redirect("/dashboard", RedirectType.replace);
  1. 이벤트 핸들러에서는 쓰지 않습니다. 문서가 명시합니다 — Client Component에서 렌더링 과정 중에는 호출할 수 있지만 이벤트 핸들러에서는 안 됩니다. 그 자리에는 useRouter를 씁니다.
"use client";
const router = useRouter();
<button onClick={() => router.push("/posts")}>목록</button>
  1. 렌더 이전에 보내야 하면 다른 도구입니다. 문서가 next.config.jsredirects나 Proxy를 가리킵니다. 인증 리다이렉트처럼 페이지에 들어가기도 전에 판단되는 것은 그쪽이 맞습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.