정적 사이트의 문의 폼4분 분량
Netlify Forms 없이 Netlify에 문의 폼 추가하기
Netlify Forms 없이 Netlify 사이트에서 문의 폼을 운영하세요. 빌드 시점 감지 없이 React에서 JSON을 POST하고, 배포 프리뷰용 폼을 따로 둡니다.
폼에서 data-netlify를 빼고 action을 Ikiform 엔드포인트로 지정하세요. 그러면 Netlify는 페이지만 제공하고 POST에는 관여하지 않습니다. React와 Vue 폼은 숨겨진 HTML 사본이 필요 없고 JSON을 POST할 수 있습니다. 프리뷰 호스트는 Allowed domains에서 사이트와 일치하지 않으므로, netlify.toml에서 배포 프리뷰에 다른 폼 ID를 설정하세요.
Netlify에서 다른 점
Netlify는 주요 정적 호스트 중 유일하게 폼 기능이 내장되어 있습니다. Netlify Forms는 빌드할 때마다 배포된 HTML을 읽어 data-netlify나 netlify가 표시된 폼을 찾고, 그 폼에 대해서는 여러분의 사이트로 보낸 POST를 받습니다. 그 밖의 정적 페이지 POST는 모두 실패하며, Netlify에서는 405가 아니라 404로 실패합니다.
curl -i -X POST -d "message=hello" https://your-site.netlify.app/contact/HTTP/2 404
server: Netlify
cache-status: "Netlify Edge"; fwd=method; fwd-status=404
content-length: 0이 404 때문에 사람들은 사라지지도 않은 페이지를 찾아 헤맵니다. 실제 의미는 감지된 폼 중 POST와 일치하는 것이 없다는 것입니다. Netlify Forms를 쓸지는 별개의 문제이며, Ikiform과 Netlify Forms 비교에서 솔직하게 답합니다. 사이트가 언제까지나 Netlify에 있고 폼이 일반 HTML이라면 Netlify Forms가 더 수고가 적습니다. 이 페이지는 그 밖의 경우를 위한 것입니다.
| 여러분의 상황 | Netlify Forms가 걸림돌이 되는 이유 |
|---|---|
| 폼을 React, Vue, Svelte로 렌더링함 | 빌드는 정적 HTML을 파싱하므로, 클라이언트에서 렌더링하는 폼은 Netlify가 찾을 수 있게 숨겨진 사본이 필요합니다 |
fetch로 JSON을 POST하고 싶음 | Netlify Forms는 urlencoded와 multipart 본문만 받습니다 |
| 사이트를 다른 호스트로 옮길 수도 있음 | 폼이 여러분의 도메인으로 POST하므로 Netlify가 그 도메인을 제공하는 동안에만 작동합니다 |
| 메시지가 직접 검증하는 웹훅으로 가야 함 | Ikiform은 모든 전달에 Standard Webhooks 헤더로 서명하고 재시도합니다 |
- Netlify Forms를 쓰면 Netlify가 배포마다 HTML을 스캔해 data-netlify가 표시된 폼을 찾습니다.
- 찾은 폼은 여러분의 사이트로 POST를 받고, Netlify가 이를 저장합니다.
- 숨겨진 사본 없이 JavaScript로 렌더링한 폼처럼 찾지 못한 폼은 모든 POST에서 404를 받습니다.
- Ikiform을 쓰면 빌드 시점에 아무것도 찾을 필요가 없습니다.
- 폼이 HTML이나 JSON으로 ikiform.com에 바로 POST하고, Ikiform이 저장하고 이메일로 알려 줍니다.
Netlify Forms에서 폼 옮기기
- 폼에서
data-netlify또는netlify,netlify-honeypot,data-netlify-recaptcha를 지우고, 숨겨진form-name입력도 지우세요. 남겨 두면form-name이 일반 필드로 저장됩니다. - 아래와 같은 JavaScript 폼의 숨겨진 정적 사본을 지우세요. Netlify가 폼을 감지하는 데만 필요했던 것입니다.
action또는fetchURL을 Ikiform 엔드포인트로 설정하세요. 기존의action="/thanks"는 폼의 Redirect after submit 설정으로 옮깁니다.- 먼저 Netlify의 Forms 페이지에서 지난 제출을 내려받으세요. 자동으로 옮겨지지 않습니다.
<form name="contact" netlify hidden>
<input name="name" />
<input name="email" />
<textarea name="message"></textarea>
</form>사이트의 다른 폼이 아직 Netlify Forms를 쓴다면 form detection을 켜 두세요. Netlify 문서에 따르면 감지를 끄는 것은 Netlify Forms를 쓰지 않는 사이트만을 위한 것입니다. 더 이상 쓰는 폼이 없다면 끄면 배포할 때마다 스캔을 하나 줄일 수 있습니다.
Netlify의 스팸 설정을 대신하는 것
Netlify는 제출에 Akismet을 실행하고 netlify-honeypot과 reCAPTCHA를 제공합니다. Ikiform에서는 폼의 Honeypot field를 bot-field처럼 이미 쓰는 이름으로 설정하면 허니팟이 계속 작동합니다. 스팸 점수는 설정 없이 모든 POST에 적용되고, Spam check를 Ikiform으로 설정하면 키 없이 reCAPTCHA 대신 Cloudflare Turnstile을 씁니다. 전환할 때 reCAPTCHA div를 지우세요.
JSON을 POST하는 React 폼
Netlify Forms가 끼어들지 않으면 클라이언트에서 렌더링하는 폼에 사본이 필요 없고, JSON을 보낼 수 있습니다. JSON은 타입을 유지하고 값을 먼저 다듬을 수 있게 해 줍니다. 이 예제는 어떤 Netlify 컨텍스트가 페이지를 빌드했는지도 기록합니다.
import { type FormEvent, useState } from "react";
const ENDPOINT = `https://ikiform.com/f/${import.meta.env.VITE_IKIFORM_FORM_ID}`;
interface Message {
name: string;
email: string;
message: string;
context: string;
}
export function ContactForm() {
const [sent, setSent] = useState(false);
const [error, setError] = useState("");
async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const data = new FormData(event.currentTarget);
const body: Message & Record<string, string> = {
name: String(data.get("name") ?? "").trim(),
email: String(data.get("email") ?? "").trim(),
message: String(data.get("message") ?? "").trim(),
context: import.meta.env.VITE_CONTEXT ?? "local",
_gotcha: String(data.get("_gotcha") ?? ""),
};
setError("");
const response = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(body),
}).catch(() => null);
const result = await response?.json().catch(() => null);
if (result?.ok) return setSent(true);
const fields: Record<string, string[]> = result?.error?.fields ?? {};
setError(Object.values(fields)[0]?.[0] ?? result?.error?.message ?? "Couldn't send. Try again.");
}
if (sent) return <p role="status">Got it. I'll answer from my own inbox, usually the same day.</p>;
return (
<form onSubmit={onSubmit}>
<label htmlFor="name">Name</label>
<input id="name" name="name" autoComplete="name" required />
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" autoComplete="email" required />
<label htmlFor="message">Message</label>
<textarea id="message" name="message" rows={6} minLength={10} required />
<input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
{error ? <p role="alert">{error}</p> : null}
<button>Send</button>
</form>
);
}Content-Type: application/json은 Ikiform에 JSON을 파싱하라고 알리고,Accept: application/json은 JSON 응답을 요청합니다.VITE_CONTEXT는 예시 이름입니다. Netlify는 빌드 중에CONTEXT를 설정합니다. 빌드 명령에서 Vite에 노출하거나, 이 필드를 빼세요.- 허니팟은 JSON 키로 보냅니다. 입력이 숨겨져 있어도 본문에 남겨 두어야 이를 채우는 봇이 스스로 드러납니다.
배포 프리뷰에는 별도의 폼
배포 프리뷰는 deploy-preview-12--your-site.netlify.app 같은 호스트에 있습니다. 이는 your-site.netlify.app의 하위 도메인이 아니므로 Allowed domains에서 일치하지 않고, 프리뷰 POST는 스팸으로 분류됩니다. netlify.toml에서 프리뷰와 브랜치 배포에 별도의 폼을 주고, 같은 파일에서 Content-Security-Policy 헤더를 설정하세요.
[build]
command = "npm run build"
publish = "dist"
[context.production.environment]
VITE_IKIFORM_FORM_ID = "frm_xxxxxxxxxxxxxxxx"
[context.deploy-preview.environment]
VITE_IKIFORM_FORM_ID = "frm_yyyyyyyyyyyyyyyy"
[context.branch-deploy.environment]
VITE_IKIFORM_FORM_ID = "frm_yyyyyyyyyyyyyyyy"
[[headers]]
for = "/*"
[headers.values]
Content-Security-Policy = "default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"프리뷰 폼의 Allowed domains는 비워 두거나, 테스트하는 동안 정확한 프리뷰 호스트를 추가하세요. 프로덕션 폼에는 여러분의 도메인과 your-site.netlify.app을 넣습니다. netlify.toml의 헤더는 모든 배포 컨텍스트에 적용됩니다. _headers 파일을 선호한다면 게시 디렉터리에 들어가야 합니다.
netlify.app만 단독으로 추가하지 마세요. 다른 사람의 사이트에 있는 폼 복사본을 포함해 모든 Netlify 사이트가 일치하게 됩니다.작동 확인하기
- 배포 프리뷰를 열어 제출하고, 메시지가 프리뷰 폼에 도착하는지 확인하세요.
- 병합한 뒤 프로덕션에서 제출하세요. 프로덕션 폼에 도착하고, Netlify의 Forms 페이지에는 새로운 것이 없는지 확인하세요.
- 알림 수신자마다 받은편지함을 확인하세요. 각자 필드가 담긴 이메일을 받고, 폼에
email필드가 있으면 Reply가 제출한 사람에게 갑니다. - 허니팟을 채워
_gotcha=bot으로 POST하세요. 여전히 성공 응답을 받지만, 제출은 스팸으로 저장되고 이메일이나 웹훅을 보내지 않습니다. - 아무것도 오지 않았나요? 대시보드의 상태 필터를 Spam으로 바꿔 보세요. 스팸은 실제 제출과 같은 성공 응답을 받습니다.
주의할 점
| 문제 | 해결 방법 |
|---|---|
제출 시 404 | POST가 여러분의 사이트로 갔습니다. 캐시된 빌드에 이전 action이 남아 있거나, fetch 호출이 아직 /로 POST합니다. 게시 디렉터리에서 form-name을 검색하세요. |
| 제출이 여전히 Netlify에 나타남 | 페이지의 어떤 폼이 아직 data-netlify나 netlify와 함께 여러분의 사이트로 POST하므로 Netlify가 계속 감지하고 저장합니다. 속성을 지우고, action을 Ikiform으로 설정하고, 다시 배포하세요. |
프리뷰에서만 Ikiform이 404 not_found | 배포 프리뷰 컨텍스트에 VITE_IKIFORM_FORM_ID가 없어서 URL이 undefined로 끝납니다. [context.deploy-preview.environment] 아래에 추가하세요. |
403 origin_not_allowed, 또는 모든 POST가 스팸으로 분류됨 | 페이지의 도메인이 폼의 Allowed domains에 없습니다. 도메인을 추가하고, 로컬 테스트용으로 localhost도 추가하세요. 등록한 도메인의 하위 도메인은 허용됩니다. |
422 files_not_supported | 폼에 파일이 선택된 <input type="file">이 있습니다. 파일 업로드는 아직 지원하지 않습니다. 입력을 지우거나 대신 링크를 요청하세요. |
질문
네. Netlify는 빌드 시점에 감지한 폼만 처리합니다. data-netlify가 없고 action이 다른 엔드포인트를 가리키는 폼은 Netlify가 다른 페이지처럼 제공하며, 그 엔드포인트로 바로 POST합니다.
Netlify는 배포된 HTML에서 Netlify Forms가 그 폼을 감지하지 않았다면 정적 페이지로 보낸 POST에 404로 응답합니다. 폼을 Netlify Forms용으로 표시하거나, action을 다른 백엔드로 지정하세요.
네. 감지는 속성에 따라 폼마다 이루어집니다. Netlify가 처리할 폼에는 data-netlify를 남기고 form detection을 켜 두세요.
Ikiform이 아니라 Netlify에 남습니다. form detection을 끄기 전에 사이트의 Forms 페이지에서 내려받아 기록과 함께 보관하세요.
아직은 안 됩니다. 파일이 포함된 POST는 422 files_not_supported로 거부됩니다. 대신 공유 드라이브 URL 같은 파일 링크를 요청하세요.
막히는 단계가 있거나 내 상황에 맞는 가이드가 필요하다면 이메일을 보내세요: hello@ikiform.com.