Руководство
Контактная форма Next.js (без бэкенда)
Next.js не нужен свой приёмник формы. Можно вставить тот же простой HTML-form action в любой Server Component, или - если нужны клиентские состояния успеха и ошибки - использовать небольшой Client Component с fetch. Оба POST'ят напрямую в Formhook.
Используйте обычную HTML-форму
API-маршрут не нужен. Форма POST'ит напрямую в Formhook, и вашему фреймворку никогда не приходится касаться отправки.
<form action="https://formhook.app/f/YOUR_API_KEY" method="POST">
<label>
Email
<input type="email" name="email" required />
</label>
<label>
Message
<textarea name="message" required></textarea>
</label>
<!-- honeypot: bots fill this; humans don't see it -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" hidden />
<!-- optional: send users to a thank-you page after a native POST -->
<input type="hidden" name="_redirect" value="https://example.com/thanks" />
<button type="submit">Send</button>
</form>Или отправить через fetch в Next.js
Используйте этот вариант, если хотите состояния успех/ошибка на клиенте без полной перезагрузки страницы.
"use client";
import { useState } from "react";
export function ContactForm() {
const [status, setStatus] = useState<"idle" | "sending" | "ok" | "error">("idle");
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("sending");
const form = e.currentTarget;
const data = Object.fromEntries(new FormData(form));
const res = await fetch("https://formhook.app/f/YOUR_API_KEY", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
setStatus(res.ok ? "ok" : "error");
if (res.ok) form.reset();
}
if (status === "ok") return <p>Thanks - we'll be in touch.</p>;
return (
<form onSubmit={onSubmit}>
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<input type="text" name="_gotcha" tabIndex={-1} hidden />
<button type="submit" disabled={status === "sending"}>
{status === "sending" ? "Sending…" : "Send"}
</button>
{status === "error" && <p>Something went wrong. Please try again.</p>}
</form>
);
}Как это работает
Обычный form action работает в любой странице или layout'е Next.js, включая Server Components. Никакого app/api/contact/route.ts, никакого Server Action, никакого middleware. Если хотите клиентский UX с состояниями успеха и ошибки, второй сниппет показывает канонический паттерн: "use client", useState, onSubmit, fetch к formhook.app/f/YOUR_API_KEY. Honeypot (_gotcha) и опциональный _redirect - те же специальные поля из HTML-руководства.
Обычная форма или fetch: что выбрать?
Берите обычную форму с action, если нужен ноль JavaScript: она работает в Server Component и продолжает работать, даже если гидратация не удалась. Цена: при отправке браузер уходит с вашей страницы, поэтому добавьте скрытое поле _redirect с абсолютным URL на одном из ваших разрешённых источников, например https://example.com/thanks. Без него посетитель увидит «сырой» JSON-ответ.
Берите версию с fetch, если сообщение об успехе или ошибке должно появляться на месте, а кнопка - блокироваться при отправке. Обычные отправки формы могут перенаправлять; JSON-запросы через fetch получают JSON-ответ, поэтому _redirect там игнорируется. Подробнее - в статье AJAX-отправка формы.
Как исправить 403 origin_not_allowed при локальной разработке
Formhook принимает отправки из браузера только с источников, перечисленных в Разрешённых источниках вашей формы, и совпадение точное: схема, хост и порт. http://localhost:3000, https://example.com и https://www.example.com - три разных источника. Добавьте URL для разработки, продакшен-домен и домен превью-деплоя, на котором вы тестируете, без пути и без завершающего слэша. Пустой список отклоняет все запросы из браузера.
Если отправка всё равно не проходит, тело ответа называет причину: origin_not_allowed (403) значит, что источника нет в списке, form_not_found (404) - что API-ключ неверный, а rate_limited (429) - что нужно подождать число секунд из Retry-After. Подробнее - в статье Контактная форма не работает?
Где хранить API-ключ
Ключ в любом случае оказывается в вашем HTML или JavaScript-бандле, поэтому считайте его публичным идентификатором, а не секретом. Переменная окружения NEXT_PUBLIC_FORMHOOK_KEY всё равно удобна, чтобы в разработке использовать одну форму, а в продакшене - другую.
Форму защищает то, что находится на стороне Formhook: список разрешённых источников, honeypot _gotcha, лимиты запросов по IP и по форме и, по желанию, Cloudflare Turnstile. Список источников не даёт другим сайтам отправлять данные из браузера, но это не аутентификация, ведь скрипт может подставить любой заголовок Origin. От ботов защищают слои против спама; защита контактных форм от спама сравнивает их.
Отправка из Server Action вместо браузера
Если нужно добавить данные на сервере или проверить их перед отправкой, вызывайте Formhook из Server Action или route handler. Серверные запросы не отправляют заголовок Origin, поэтому они должны нести заголовок X-Auth-Token, который создаётся в настройках формы. Храните этот токен в серверной переменной окружения, но никогда не в Client Component. Большинству контактных форм это не нужно, поэтому в сниппетах выше этого нет. Полный формат запроса - в документации.
Перед запуском
- Добавьте продакшен-источник с
https://в разрешённые источники. - Оставьте поле-ловушку
_gotchaи решите, включать ли Turnstile. - Отправьте тестовую заявку и убедитесь, что она появилась в дашборде и пришла как push-уведомление.
- Добавьте под формой короткое уведомление о конфиденциальности. В руководстве по форме, соответствующей GDPR есть формулировки, которые можно адаптировать.
Что происходит дальше
- Отправка появляется в панели Formhook в течение секунды.
- Push-уведомление приходит на каждое включённое устройство.
- Отвечайте прямо из панели (Pro и выше) - удобно для клиентской работы, где у каждого проекта свой маршрут контактов.
Запустите вашу контактную форму на Next.js сегодня.
Зарегистрируйтесь бесплатно, создайте форму, вставьте API-ключ. Пять минут.
Начать бесплатноБесплатный тариф: 5 форм · 250 отправок/мес · Без банковской карты.
Контактная форма Next.js - FAQ
Нужно ли добавлять API-маршрут или Server Action?
Можно ли использовать это в Server Component?
Как бороться со спамом без написания middleware с rate-limit'ом?
Руководства для других фреймворков
Migrating from Formspree? See how Formhook compares.