[Next.js] fetch, Server Action, Route Handler의 사용 목적과 차이점

조아영·2024년 10월 8일

📌

fetch, Route Handler, Server Action은 모두 네트워크 요청을 처리할 때 사용할 수 있지만, 각각의 목적과 사용 환경에 차이가 있습니다.

◼ fetch

fetch는 클라이언트와 서버 모두에서 사용할 수 있는 기본적인 네트워크 요청 함수입니다. 브라우저 환경과 Node.js 서버 환경에서 모두 작동하기 때문에 어느 환경에서나 네트워크 요청을 보낼 수 있는 범용적인 도구입니다.

  • 용도 : 데이터를 가져오거나 서버에 데이터를 보낼 때 사용
  • 특징 : 클라이언트와 서버 모두에서 사용 가능
  • 사용 예시 : React 컴포넌트에서 API 데이터를 가져올 때 사용하거나 서버 사이드에서 외부 API를 호출할 때 사용
import React, { useEffect, useState } from "react";

function FetchExample() {
    const [data, setData] = useState(null);

    useEffect(() => {
        fetch("/api/data") // API 엔드포인트 호출
            .then((response) => response.json())
            .then((data) => setData(data))
            .catch((error) => console.error("Error:", error));
    }, []);

    return <div>{data ? <p>데이터: {JSON.stringify(data)}</p> : <p>로딩 중...</p>}</div>;
}

export default FetchExample;

◼ Server Action

Server ActionNext.js에서 서버 환경에서만 동작하는 기능입니다. 리액트 컴포넌트 내부에서 서버 쪽에서 실행되는 로직을 구현할 때 사용합니다. 이를 통해 클라이언트가 아닌 서버에서 데이터를 처리하는 코드를 작성할 수 있습니다. 주로 서버에서만 실행되는 동작이 필요할 때 사용되며, 클라이언트에서는 이를 직접 호출할 수 없습니다.

  • 용도 : 서버에서 실행되어야 하는 로직을 리액트 컴포넌트와 통합할 때 사용
  • 특징 : 리액트 컴포넌트 내부에서 사용 가능하고, 서버 환경에서만 동작
  • 사용 예시 : 사용자 인증이나 데이터베이스와의 상호작용 등 클라이언트에서 실행되지 않아야 할 서버 측 로직을 작성할 때 유용
// app/api/serverApi.ts
export async function sendContactForm(formData: FormData) {
    const res = await fetch("/api/contact", {
        method: "POST",
        body: formData,
    });

    if (!res.ok) {
        throw new Error("서버와 통신 중 오류가 발생했습니다.");
    }

    return await res.json();
}

// app/contact/page.tsx
"use client";

import React, { useState } from "react";
import { sendContactForm } from "../api/serverApi"; // serverApi에서 import

export default function ContactPage() {
    const [name, setName] = useState<string>("");
    const [message, setMessage] = useState<string>("");
    const [responseMessage, setResponseMessage] = useState<string | null>(null);

    async function handleSubmit(e: React.FormEvent) {
        e.preventDefault();

        const formData = new FormData();
        formData.append("name", name);
        formData.append("message", message);

        try {
            const response = await sendContactForm(formData);
            setResponseMessage(response.message);
        } catch (error) {
            setResponseMessage("전송 중 오류가 발생했습니다.");
        }
    }

    return (
        <div>
            <h1>문의하기</h1>
            <form onSubmit={handleSubmit}>
                <label>
                    이름:
                    <input type="text" value={name} onChange={(e) => setName(e.target.value)} required />
                </label>
                <br />
                <label>
                    메시지:
                    <textarea value={message} onChange={(e) => setMessage(e.target.value)} required />
                </label>
                <br />
                <button type="submit">전송</button>
            </form>
            {responseMessage && <p>{responseMessage}</p>}
        </div>
    );
}

◼ Route Handler

Route HandlerNext.js에서 API 엔드포인트를 정의할 때 사용하는 도구입니다. 이것은 컴포넌트 외부에서 동작하며, 클라이언트와 서버 간의 네트워크 요청을 처리하는 전용 함수입니다. 클라이언트가 서버에 요청을 보낼 수 있도록 경로(route)를 생성해 주며, 요청을 받고 처리하는 로직을 작성할 수 있습니다.

  • 용도 : 클라이언트가 서버에 API 요청을 보낼 수 있는 경로를 만들고, 해당 요청을 처리할 때 사용
  • 특징 : 클라이언트나 다른 서버에서 접근할 수 있는 엔드포인트를 생성
  • 사용 예시 : API 서버로부터 데이터를 받아와야 하거나, 사용자 인증이나 데이터 저장을 위한 경로를 만들 때 사용
// app/api/welcome/route.ts
import { NextResponse } from "next/server";

export async function POST(request: Request) {
    const { name } = await request.json(); // 요청에서 JSON 데이터를 추출
    const message = `안녕하세요, ${name}님! 환영합니다.`; // 환영 메시지 생성

    return NextResponse.json({ message }); // 클라이언트에 응답
}

// 클라이언트에서 API 호출
async function sendWelcomeRequest(name: string) {
    // /api/welcome 엔드포인트에 POST 요청
    const response = await fetch("/api/welcome", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ name }),
    });

    const data = await response.json();
    console.log(data.message); // "안녕하세요, [이름]님! 환영합니다."
}

// 사용 예시
sendWelcomeRequest("철수");

◼ 요약

  • fetch : 클라이언트나 서버에서 데이터를 가져오는 기본적인 네트워크 요청을 보낼 때 사용.
  • Server Action : 서버에서만 실행되어야 할 리액트 컴포넌트 내부 로직을 구현할 때 사용. 서버 전용.
  • Route Handler : 클라이언트가 서버에 요청을 보낼 수 있는 API 경로를 정의할 때 사용. 엔드포인트 생성. 클라이언트와 서버가 모두 연관된 네트워크 요청을 처리하는 경우 사용.

◼ 차이점

아래 두 문장의 차이를 좀 더 구체적으로 알아보자.

  • Server Action : 사용자 인증이나 데이터베이스와의 상호작용 등 클라이언트에서 실행되지 않아야 할 서버 측 로직을 작성할 때 유용.
  • Route Handler : 사용자 인증이나 데이터 저장을 위한 경로를 만들 때 사용

Server Action

  • 초점 : 클라이언트에서 실행되지 않아야 하는 서버 측 로직에 대한 강조입니다. 서버 측에서만 처리해야 하는 로직의 성격에 중점을 두고 있으며, 보안과 관련된 이슈를 강조합니다.
  • 예시 :
    • 사용자 로그인 시 비밀번호를 데이터베이스와 비교하는 과정은 서버에서만 수행되어야 하며, 클라이언트에서 이러한 로직을 처리하면 보안상 위험이 발생할 수 있습니다.
    • 데이터베이스에 직접 접근하여 데이터를 조회, 수정, 삭제하는 작업도 서버 측에서만 이루어져야 합니다.

Route Handler

  • 초점 : API 엔드포인트를 만드는 작업에 대한 강조입니다. 로직을 클라이언트가 사용할 수 있도록 API 엔드포인트를 정의하는 작업에 중점을 두고 있습니다.
  • 예시 :
    • 특정 API 엔드포인트를 통해 클라이언트가 로그인 요청을 보낼 수 있도록 하는 경우, 이 엔드포인트는 서버에서 사용자 인증 로직을 처리합니다.
    • 사용자 정보를 서버의 데이터베이스에 저장하기 위한 POST 요청을 처리하는 엔드포인트도 이 범주에 포함됩니다.

0개의 댓글