App Router를 쓰면, 프론트 페이지(page.tsx)뿐 아니라 서버에서 실행되는 API 엔드포인트도 app 폴더 아래에서 함께 관리할 수 있습니다.
왜 app/api/.../route.ts로 API를 만들까?
App Router의 핵심은 “라우트는 폴더 구조로 결정된다” 입니다.
- app/(something)/page.tsx → 페이지 라우트
- app/api/(something)/route.ts → API 라우트
즉, route.ts는 “이 폴더 경로에 해당하는 요청을 처리하는 서버 핸들러”라는 의미입니다.
이렇게 쓰는 이유
- 프론트 + API를 한 프로젝트에서 통합 관리
- 별도의 Express/Nest 서버 없이도 가벼운 백엔드 역할을 수행
- 서버 코드가 자동으로 서버에서만 실행
- 이메일 발송 같은 민감한 로직(비밀키 사용, SMTP/API 호출 등)을 브라우저에 노출하지 않음
- 서버 컴포넌트/액션과 자연스럽게 연결
- 폼 제출, 웹훅, 백오피스 작업 등 “서버에서 해야 하는 일”을 라우트로 분리
폴더 구조가 URL이 되는 규칙
예를 들어 이런 구조
app/
api/
send-appointment-email/
route.ts
- 이 파일은 URL로는 이렇게 매핑됩니다
- /api/send-appointment-email
즉, 폴더 이름(send-appointment-email)이 곧 API 경로가 되는 셈이에요.
route.ts에는 뭘 써야 하나요?
route.ts에서는 HTTP 메서드 별로 함수 export를 합니다.
- export async function GET(req: Request) { ... }
- export async function POST(req: Request) { ... }
- export async function PUT(req: Request) { ... }
- export async function DELETE(req: Request) { ... }
그리고 응답은 보통 NextResponse.json(...)로 반환합니다.
예시: 예약 이메일 발송 API (POST)
// app/api/send-appointment-email/route.ts
import { NextResponse } from "next/server";
type Body = {
to: string;
customerName: string;
appointmentAt: string; // ISO string 추천
};
export async function POST(req: Request) {
try {
const body = (await req.json()) as Body;
// 1) 기본 검증
if (!body.to || !body.customerName || !body.appointmentAt) {
return NextResponse.json(
{ ok: false, message: "필수 값이 누락되었습니다." },
{ status: 400 }
);
}
// 2) 이메일 발송 로직 (예: Resend, SendGrid, Nodemailer 등)
// await sendEmail({ to: body.to, ... });
return NextResponse.json({ ok: true });
} catch (err) {
return NextResponse.json(
{ ok: false, message: "서버 에러가 발생했습니다." },
{ status: 500 }
);
}
}
- route.ts는 브라우저가 아니라 서버에서 실행되기 때문에, 이메일 API 키 같은 값을 환경변수로 안전하게 쓸 수 있어요.
프론트에서 이 API를 어떻게 호출하나요?
(1) Client Component에서 fetch로 호출
async function sendAppointmentEmail() {
const res = await fetch("/api/send-appointment-email", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
to: "test@example.com",
customerName: "홍길동",
appointmentAt: new Date().toISOString(),
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.message ?? "요청 실패");
}
(2) Server Action/서버 컴포넌트에서 호출
서버 쪽에서 이미 작업한다면, 굳이 API를 한 번 더 거치지 않고 직접 이메일 발송 함수를 호출하는 패턴도 많습니다.
- 사용자 입력(브라우저) → 서버로 보내야 하는 경우: API Route가 편함
- 이미 서버 내부 흐름(크론, 웹훅, 서버 컴포넌트)이라면: 함수 호출이 더 간단
'개발 공부 > React' 카테고리의 다른 글
| React useMutation + invalidateQueries (0) | 2026.01.13 |
|---|---|
| Clerk PricingTable (0) | 2025.12.25 |
| 탭 간 상태 동기화 - React (0) | 2025.12.21 |
| Clerk 로그인/회원가입 (0) | 2025.11.29 |
| UploadThing을 통한 이미지 업로드 구현하기 (0) | 2025.11.27 |