Next.jsNext.js 기초 · 4기초

Server Actions와 API Routes — 서버 로직 처리

Next.jsServerActionsAPIRoutesroute.ts폼처리

Server Actions

서버에서 실행되는 함수를 클라이언트에서 직접 호출합니다.

// app/actions/user.ts
"use server";  // 서버에서만 실행

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

export async function createUser(formData: FormData) {
    const name = formData.get("name") as string;
    const email = formData.get("email") as string;

    // 서버에서 직접 DB 접근
    await db.users.create({ name, email });

    // 캐시 무효화 후 페이지 재검증
    revalidatePath("/users");
}

export async function deleteUser(id: string) {
    await db.users.delete(id);
    revalidatePath("/users");
}

폼에서 Server Action 사용

// app/users/new/page.tsx
import { createUser } from "@/actions/user";

export default function NewUserPage() {
    return (
        <form action={createUser}>
            <input name="name" required placeholder="이름" />
            <input name="email" type="email" required placeholder="이메일" />
            <button type="submit">생성</button>
        </form>
    );
}

useFormState로 응답 처리

"use client";
import { useFormState, useFormStatus } from "react-dom";
import { createUser } from "@/actions/user";

interface State {
    message: string | null;
    errors: Record<string, string[]>;
}

const initialState: State = { message: null, errors: {} };

function SubmitButton() {
    const { pending } = useFormStatus();
    return <button disabled={pending}>{pending ? "저장 중..." : "저장"}</button>;
}

function UserForm() {
    const [state, formAction] = useFormState(createUser, initialState);

    return (
        <form action={formAction}>
            <input name="name" />
            {state.errors.name && <p>{state.errors.name}</p>}

            <input name="email" type="email" />
            {state.errors.email && <p>{state.errors.email}</p>}

            <SubmitButton />
            {state.message && <p>{state.message}</p>}
        </form>
    );
}

API Routes

외부에서 호출 가능한 REST API를 만듭니다.

// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";

// GET /api/users
export async function GET(request: NextRequest) {
    const { searchParams } = new URL(request.url);
    const page = Number(searchParams.get("page") ?? "1");

    const users = await db.users.findMany({
        skip: (page - 1) * 20,
        take: 20,
    });

    return NextResponse.json({ users, page });
}

// POST /api/users
export async function POST(request: NextRequest) {
    const body = await request.json();

    // 유효성 검사
    if (!body.name || !body.email) {
        return NextResponse.json(
            { error: "name과 email은 필수입니다." },
            { status: 400 }
        );
    }

    const user = await db.users.create(body);
    return NextResponse.json(user, { status: 201 });
}
// app/api/users/[id]/route.ts
interface Params {
    params: { id: string };
}

export async function GET(request: NextRequest, { params }: Params) {
    const user = await db.users.findById(params.id);
    if (!user) return NextResponse.json({ error: "없음" }, { status: 404 });
    return NextResponse.json(user);
}

export async function DELETE(request: NextRequest, { params }: Params) {
    await db.users.delete(params.id);
    return new NextResponse(null, { status: 204 });
}

미들웨어

모든 요청에 앞서 실행됩니다.

// middleware.ts (루트에 위치)
import { NextRequest, NextResponse } from "next/server";

export function middleware(request: NextRequest) {
    const token = request.cookies.get("token")?.value;
    const pathname = request.nextUrl.pathname;

    // 인증 보호 경로
    if (pathname.startsWith("/dashboard") && !token) {
        return NextResponse.redirect(new URL("/login", request.url));
    }

    // 요청 헤더 추가
    const headers = new Headers(request.headers);
    headers.set("x-pathname", pathname);

    return NextResponse.next({ request: { headers } });
}

export const config = {
    matcher: ["/dashboard/:path*", "/api/:path*"],
};

Server Actions vs API Routes

항목Server ActionsAPI Routes
호출 방식폼/클라이언트 직접HTTP 요청
외부 접근불가가능
타입 안전성TypeScript 자동수동
적합한 경우폼 제출, 뮤테이션공개 API, 외부 연동

정리

  • Server Actions: 폼 처리, 데이터 변경 → Next.js 전용, 타입 안전
  • API Routes: REST API → 외부 서비스, 모바일 앱 연동에 적합
  • 미들웨어: 인증, 로깅, 헤더 조작 등 전처리

다음 편에서는 이미지와 폰트 최적화 — next/image와 next/font로 성능을 높이는 방법을 배웁니다.

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.