Next.js에서 문의 양식 만들기 (백엔드 없이)
Source: Dev.to

옵션, 빠르게
“Next.js contact form”을 검색하면 보통 세 가지 접근 방식을 보게 됩니다:
- Route Handler + email API (Resend, SendGrid 등) – 완전한 제어가 가능하지만 모든 것을 직접 관리해야 합니다.
- Server Actions – 더 깔끔한 개발자 경험(DX)을 제공하지만 이메일 + 스팸 처리를 직접 해야 합니다.
- Form‑backend 서비스 – 데이터를 호스팅된 엔드포인트로 전송하면 저장 + 이메일을 서비스가 처리합니다.
대부분의 프로젝트—랜딩 페이지, SaaS 사이트, 정적 앱—에서는 옵션 3이 가장 빠른 경로입니다. 이 가이드는 해당 접근 방식을 단계별로 안내합니다.
가장 간단한 버전: HTML만 사용
로드 상태나 인터랙티브 기능이 필요하지 않다면, JavaScript 없이 순수 HTML 폼만 사용할 수 있습니다:
<form action="https://formtorch.com/f/YOUR_FORM_ID" method="POST">
<input type="text" name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required></textarea>
<button type="submit">Send message</button>
</form>
이미 동작하는 폼입니다. 브라우저가 제출을 처리하고, 이후에 리디렉션됩니다.
자체 페이지로 리디렉션
<form action="/thank-you" method="POST">
<!-- fields -->
</form>
많은 경우에 이것만으로 충분합니다. 하지만 React 앱은 보통 좀 더 인터랙티브한 기능을 원합니다.
적절한 React 연락 양식
다음은 로딩, 성공, 오류 상태를 포함한 프로덕션 수준의 컴포넌트입니다:
"use client";
import { useState } from "react";
type FormState = "idle" | "loading" | "success" | "error";
export function ContactForm() {
const [state, setState] = useState<FormState>("idle");
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setState("loading");
try {
const data = new FormData(e.currentTarget);
const res = await fetch("https://formtorch.com/f/YOUR_FORM_ID", {
method: "POST",
headers: { "X-Requested-With": "XMLHttpRequest" },
body: data,
});
if (!res.ok) throw new Error();
setState("success");
} catch {
setState("error");
}
}
if (state === "success") {
return (
<div>
<h3>Message sent.</h3>
<p>Thanks for reaching out. I’ll get back to you soon.</p>
</div>
);
}
return (
<form onSubmit={handleSubmit} noValidate>
<label>
Name
<input type="text" name="name" required />
</label>
<label>
Email
<input type="email" name="email" required />
</label>
<label>
Message
<textarea name="message" required></textarea>
</label>
{state === "error" && <p>Something went wrong. Try again.</p>}
<button type="submit" disabled={state === "loading"}>
{state === "loading" ? "Sending…" : "Send message"}
</button>
</form>
);
}
왜 이렇게 구현하면 좋은가
FormData대신 JSON 사용: 모든 필드(파일 포함)를 자동으로 캡처합니다.X-Requested-With헤더: 응답이 HTML 리다이렉트가 아닌 JSON이 되도록 보장합니다.- 로딩 상태 + 비활성화된 버튼: 중복 제출을 방지합니다.
유효성 검사 추가
구조를 변경하지 않고 클라이언트 측 유효성 검사를 추가할 수 있습니다:
function validate(data: FormData) {
const errors: Record<string, string> = {};
if (!data.get("name")) errors.name = "Name is required";
const email = String(data.get("email") ?? "");
if (!email) errors.email = "Email is required";
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
errors.email = "Invalid email";
if (!data.get("message")) errors.message = "Message is required";
return errors;
}
브라우저의 기본 UI를 비활성화하고 오류를 직접 표시하려면 <form> 요소에 noValidate를 추가하세요.
react-hook-form 사용하기
보다 복잡한 폼에서는 react-hook-form이 상태 관리를 간소화합니다:
import { useForm } from "react-hook-form";
export function ContactForm() {
const { register, handleSubmit, formState } = useForm();
async function onSubmit(data: any) {
const formData = new FormData();
Object.entries(data).forEach(([k, v]) => formData.append(k, String(v)));
await fetch("https://formtorch.com/f/YOUR_FORM_ID", {
method: "POST",
headers: { "X-Requested-With": "XMLHttpRequest" },
body: formData,
});
}
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<input {...register("name")} placeholder="Name" required />
<input {...register("email")} type="email" placeholder="Email" required />
<textarea {...register("message")} placeholder="Message" required></textarea>
<button type="submit" disabled={formState.isSubmitting}>
{formState.isSubmitting ? "Sending…" : "Send"}
</button>
</form>
);
}
Formtorch와 같은 호스팅된 폼 백엔드를 사용하면, 맞춤 API나 서버‑사이드 이메일 처리, 추가 인프라 관리 없이도 몇 분 안에 완전한 기능의 연락 폼을 구축하고 실행할 수 있습니다. 즐거운 코딩 되세요!
스팸은 어떻게 할까?
모든 공개 양식은 스팸을 받습니다.
일반적인 접근법
- CAPTCHA (사용자 경험 저하)
- 허니팟
- 속도 제한
- 스팸 감지
간단한 허니팟 필드:
<input type="text" name="website" style="display:none" tabindex="-1" autocomplete="off" />
Bots fill it. Humans don’t.
핵심 요약
백엔드를 구축하지 않아도 연락처 양식 이메일을 보낼 수 있습니다.
실용적인 설정
- 간단한 페이지 → HTML 폼
- React 앱 →
fetch + FormData - 복잡한 폼 →
react-hook-form
그 외의 모든 것은 단순히 배관일 뿐입니다.
선택 사항: 백엔드 완전 생략
이메일 API, 스팸 필터링, 제출물 저장 등을 다루고 싶지 않다면, 호스팅된 폼 백엔드를 사용할 수 있습니다.
예를 들어, Formtorch는 다음을 제공합니다:
- 바로 사용할 수 있는 엔드포인트
- 이메일 알림
- 내장 스팸 필터링
따라서 서버 코드를 전혀 사용하지 않고도 폼이 작동합니다.
사용해 보고 싶다면:
어쨌든 흐름을 이해하면, 연락 폼이 “설정 작업”이 아니라 해결된 문제로 바뀝니다.
