Most contact forms are slow for boring reasons: a heavy script, an extra redirect, and an error message nobody designed. This guide fixes all three in about an afternoon.
Why speed matters#
People decide whether a form worked in the first second after they press submit. A clear, quick response keeps them on the page and makes the follow-up feel personal.
Set up the endpoint#
Point the form at your endpoint and send the fields with fetch. Keep the request small and always return JSON so the page can show the right message. The MDN guide to fetch covers the details.
type Result = { ok: true } | { ok: false; message: string }
export async function submitForm(form: HTMLFormElement): Promise<Result> {
const response = await fetch(form.action, {
method: 'POST',
headers: { Accept: 'application/json' },
body: new FormData(form),
})
if (response.ok) return { ok: true }
const body = await response.json().catch(() => ({}))
return { ok: false, message: body.message ?? 'Something went wrong. Please try again.' }
}Test it from the terminal#
Before touching the page, confirm the endpoint answers with a quick request:
curl -X POST https://example.com/f/contact \
-H "Accept: application/json" \
-F "email=hello@example.com" \
-F "message=Testing <angle brackets> & ampersands"Handle every state#
Disable the button while the request is in flight.
Show the server message when validation fails.
Keep what people typed if anything goes wrong.
What changed#
After the changes, the same form felt noticeably quicker on a mid-range phone:
Measure | Before | After |
|---|---|---|
Time to response | 1.8 s | 0.4 s |
Completed forms | 61% | 78% |
Script size | 84 KB | 3 KB |
The fastest form is the one that answers before people start to wonder.
Next steps#
Add a follow-up reminder for every new enquiry.
Write a short confirmation email in your own voice.
Review the slowest pages once a month.
That is the whole recipe. Small, quick, and kind to the people filling it in.



