정적 사이트의 문의 폼4분 분량
Vercel에 호스팅한 문의 폼의 405 오류 해결하기
Vercel의 정적 페이지는 폼 POST에 405 Method Not Allowed로 응답합니다. 그 이유와 폼을 백엔드로 보내는 방법을 알아보세요.
Vercel은 정적 빌드를 파일로 제공하며, 해당 경로를 처리하는 함수가 없으므로 파일로 보낸 POST는 빈 본문의 405를 받습니다. 대신 폼의 action을 Ikiform 엔드포인트로 지정하거나, POST를 받는 Vercel Function을 작성하세요. Ikiform을 쓴다면 Vercel 환경마다 폼 ID를 따로 설정해 프리뷰는 테스트 폼으로, 프로덕션은 실제 폼으로 POST하게 하세요.
Vercel이 405로 응답하는 이유
Vercel은 각 요청을 정적 파일이나 함수로 라우팅합니다. 정적 사이트, Vite 빌드, output: "export"를 쓴 Next.js 앱에는 파일만 있습니다. /contact에 대한 GET은 파일을 돌려주지만, 같은 경로로 보낸 POST는 갈 곳이 없으므로 여러분의 코드가 실행되기도 전에 Vercel이 거부합니다.
curl -i -X POST -d "message=hello" https://your-project.vercel.app/contactHTTP/2 405
server: Vercel
content-disposition: inline
x-vercel-cache: HIT
content-length: 0응답에 본문이 없으므로 브라우저는 빈 페이지나 자체 오류 화면을 보여 줍니다. 다음 세 가지 구성이 이 상황으로 이어집니다.
| 여러분의 코드 | Vercel에서 405가 나는 이유 |
|---|---|
action이 없는 <form method="post"> | 폼이 자신이 있는 페이지, 즉 정적 파일로 POST합니다 |
Vite나 일반 HTML 프로젝트의 action="/api/contact" | 프로젝트 루트의 api/ 폴더에 파일이 없으므로 그 경로를 처리하는 함수가 없습니다 |
| 정적 내보내기의 Next.js Server Action 또는 Route Handler | output: "export"는 Server Actions나 요청을 읽는 Route Handlers를 지원하지 않습니다. next dev가 오류를 알려 주며, 빌드에는 이를 실행할 서버가 없습니다 |
- 폼이 Vercel 배포의 경로로 POST합니다.
- Vercel이 그 경로를 처리하는 함수를 찾습니다.
- 직접 작성한 함수가 있으면 실행됩니다. 정적 빌드에는 없으므로 Vercel은 빈 본문의 405로 응답합니다.
- action이 Ikiform을 가리키면 POST는 Vercel에 닿지 않습니다.
- Ikiform이 메시지를 저장하고, 이메일로 알리고, 브라우저를 Vercel 사이트의 정적 페이지로 돌려보냅니다.
그러니 함수를 추가하거나 POST를 다른 곳으로 보내야 합니다. api/contact.ts에 Vercel Function을 두면 되지만, 그러면 이메일 API, 스팸 필터링, 레이트 리밋이 필요하고 이를 직접 유지해야 합니다. POST를 Ikiform으로 보내면 배포는 정적으로 유지됩니다. HTML에서 고칠 것은 속성 하나입니다.
<form method="post">
<input name="email" type="email" required />
<textarea name="message" required></textarea>
<button>Send</button>
</form><form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">함수를 직접 작성하고 싶다면
메시지가 여러분의 코드만 닿을 수 있는 곳으로 가야 한다면 함수도 괜찮은 선택입니다. 시작하기 전에 필요한 것을 따져 보세요.
- 이메일 제공업체 계정, 인증된 발신 도메인, Vercel 환경 변수에 넣을 API 키.
- 서버 측 검증. 브라우저의
required속성은 편의 기능일 뿐 검사가 아닙니다. - 스팸 필터링과 레이트 리밋. 없으면 인터넷의 모든 봇이 문의 폼을 통해 메일을 보냅니다.
- 이메일 제공업체가 다운되었을 때 메시지를 보관할 곳과 다시 보낼 방법.
폼 백엔드가 이 목록을 대신 처리합니다. 그래도 여러분의 코드를 거치게 하고 싶다면, 폼은 Ikiform으로 POST하게 두고 함수에서 서명된 웹훅을 받으세요. 핸들러는 직접 운영하는 서버에 있습니다.
Vercel 환경별 폼 ID
Vercel에서는 풀 리퀘스트마다 프리뷰 배포가 만들어집니다. 프리뷰가 프로덕션 폼으로 POST하면 모든 테스트 메시지가 실제 메시지와 함께 받은편지함에 쌓입니다. 프리뷰용 Ikiform 폼을 하나 더 만들고, Vercel 환경 변수가 고르게 하세요. Settings › Environment Variables에서 같은 이름을 두 번 추가하세요.
VITE_IKIFORM_FORM_ID Production frm_xxxxxxxxxxxxxxxx
VITE_IKIFORM_FORM_ID Preview frm_yyyyyyyyyyyyyyyyVite는 빌드 시점에 index.html의 %NAME% 플레이스홀더를 VITE_ 변수로 바꾸므로, 일반 HTML 폼도 JavaScript 없이 이를 쓸 수 있습니다. VITE_VERCEL_ENV는 Vercel이 Vite 프로젝트에 설정하며, 메시지가 어느 배포에서 왔는지 기록합니다.
<form id="contact" action="https://ikiform.com/f/%VITE_IKIFORM_FORM_ID%" method="post">
<label for="name">Name</label>
<input id="name" name="name" autocomplete="name" required />
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" required />
<label for="message">Message</label>
<textarea id="message" name="message" rows="6" minlength="10" required></textarea>
<input type="hidden" name="build" value="%VITE_VERCEL_ENV%" />
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
<button type="submit">Send message</button>
</form>- 정적 사이트에서 Vercel은 빌드 시점에 환경 변수를 읽습니다. 값을 바꾼 뒤에는 다시 배포하세요. 그렇지 않으면 HTML에 이전 값이 남습니다.
- Vite는 변수가 없으면 플레이스홀더를 그대로 두므로, ID가 빠지면
%VITE_IKIFORM_FORM_ID%가 그대로 배포되고 모든 POST가 폼에 닿기도 전에400 Bad Request로 실패합니다. 아래의 가드가 이를 잡아 줍니다. - 폼 ID는 어느 쪽이든 공개됩니다. 브라우저에 전달되는 것은
VITE_변수뿐이며, 그래서 여기에는 비밀을 넣으면 안 됩니다.
const form = document.querySelector("#contact");
if (form && form.action.includes("%VITE_")) {
console.error("VITE_IKIFORM_FORM_ID is not set for this Vercel environment");
form.querySelector("button").disabled = true;
}output: "export"로 Next.js를 쓰나요? 대신 클라이언트 컴포넌트에서 process.env.NEXT_PUBLIC_IKIFORM_FORM_ID를 읽으세요. 필라 가이드에 정적 내보내기용 폼이 있습니다. Next.js 앱이 Vercel 서버에서 실제로 실행된다면 Server Action이 시크릿 키로 POST할 수 있습니다. Next.js 가이드를 참고하세요.
헤더와 깔끔한 URL
사이트가 Content-Security-Policy를 설정한다면 vercel.json에서 설정하고 Ikiform을 포함하세요. cleanUrls는 thanks.html을 /thanks로 제공하므로 리디렉션 대상이 더 깔끔해집니다.
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"cleanUrls": true,
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"
}
]
}
]
}프리뷰와 허용 도메인
your-project-git-branch-team.vercel.app 같은 프리뷰 URL은 your-project.vercel.app의 하위 도메인이 아니므로 Allowed domains에서 일치하지 않습니다. 여기서 보낸 POST는 origin_mismatch와 함께 스팸으로 분류됩니다. 프리뷰용 폼을 따로 두어야 하는 또 다른 이유입니다. 그 폼의 허용 도메인은 비워 두거나, 테스트 중인 프리뷰 호스트를 추가하세요.
localhost 테스트가 아니므로 다른 실제 제출처럼 플랜 사용량에 포함됩니다.작동 확인하기
- 수정하기 전에 프로덕션 URL로 위의
curl을 실행해405를 확인하세요. 수정한 뒤에는 배포된 HTML에서 폼의action이https://ikiform.com/f/로 시작해야 합니다. - 프리뷰 배포를 열어 제출하고, 메시지가 프로덕션 폼이 아니라 프리뷰 폼에 도착하는지 확인하세요.
- 프로덕션 도메인에서 제출하고
build필드가production인지 확인하세요. - 알림 수신자마다 받은편지함을 확인하세요. 각자 필드가 담긴 이메일을 받고, 폼에
email필드가 있으면 Reply가 제출한 사람에게 갑니다. - 아무것도 오지 않았나요? 대시보드의 상태 필터를 Spam으로 바꿔 보세요. 스팸은 실제 제출과 같은 성공 응답을 받습니다.
주의할 점
| 문제 | 해결 방법 |
|---|---|
폼을 바꾼 뒤에도 여전히 405 | 테스트 중인 배포가 변경 전에 빌드되었거나, JavaScript 핸들러가 fetch로 여러분의 경로에 POST합니다. 빌드된 파일에서 /api/contact를 검색하세요. |
모든 POST에서 400 Bad Request, 또는 Ikiform에서 404 not_found | 폼 ID가 빌드에 들어가지 않았거나, 그 환경에 맞지 않는 ID입니다. 해당 환경에 변수가 있는지 확인하고 다시 배포하세요. |
| 프리뷰는 되는데 프로덕션 POST가 스팸으로 분류됨 | 프로덕션 폼의 Allowed domains에 프로덕션 도메인이 빠져 있습니다. your-project.vercel.app과 함께 커스텀 도메인도 추가하세요. |
| 브라우저가 여러분의 페이지 대신 Ikiform의 감사 페이지로 이동함 | _redirect는 도메인이 Allowed domains에 있고 https: URL일 때만 따릅니다. 도메인을 추가하거나, 대신 폼 설정에서 Redirect after submit을 설정하세요. |
질문
폼이 정적 파일만 있는 경로로 POST하기 때문입니다. Vercel은 GET 요청에 파일을 제공하고, 그 경로를 처리하는 함수가 없으면 POST를 405로 거부합니다. 폼을 폼 백엔드로 POST하거나 Vercel Function을 추가하세요.
아니요. output: export에서는 실행할 서버가 없으므로 Server Actions를 지원하지 않습니다. 클라이언트 컴포넌트에서 폼 백엔드로 POST하거나, 정적 내보내기 없이 배포해 Vercel이 액션을 실행하게 하세요.
권하지 않습니다. Preview 환경 변수로 프리뷰에 별도의 폼 ID를 주어 테스트 메시지가 받은편지함이나 웹훅에 닿지 않게 하세요.
아니요. 폼은 vercel.json 없이도 작동합니다. ikiform.com을 허용하는 Content-Security-Policy를 설정하거나, 감사 페이지 URL을 깔끔하게 하려고 cleanUrls를 켤 때만 추가하세요.
아니요, 무료 플랜은 없습니다. 테스트는 무료입니다. 결제하기 전에 localhost에서 한 달에 50건의 테스트 제출을 보낼 수 있습니다. 실제로 운영하려면 Pro가 제출 500건에 월 $6부터이며, 한 번만 결제해 제출 10,000건을 $49에, 50,000건을 $149에 살 수도 있습니다. 크레딧은 만료되지 않습니다.
막히는 단계가 있거나 내 상황에 맞는 가이드가 필요하다면 이메일을 보내세요: hello@ikiform.com.