← All frameworks

Vue framework

Nuxt form handling without a backend

Nuxt gives you a Nitro server, but you rarely want to own server-side form handling — parsing bodies, sending mail, persisting data, and minding GDPR. Skip all of it by posting from the client straight to Formward, an EU-hosted form backend. Your Nuxt app can even be statically generated with nuxi generate and the form still works.

The component below is ordinary Vue inside Nuxt. It uses the native fetch() (no need for $fetch here, since the request leaves your app entirely) and a reactive status ref to drive the sending, success, and error UI.

EU-hosted · Whether you deploy Nuxt as a server or a static export, submissions are received and stored in Sweden — your form data never leaves the EU/EEA.

The Nuxt example

Copy this into your project and replace <FORM_ID> with the id of a form you create in the Formward dashboard.

vue

<script setup>
const status = ref("idle"); // idle | sending | ok | error

async function onSubmit(e) {
  status.value = "sending";
  const form = e.target;
  try {
    const res = await fetch("https://forms.formward.eu/f/<FORM_ID>", {
      method: "POST",
      headers: { Accept: "application/json" },
      body: new FormData(form),
    });
    const data = await res.json(); // { ok: true, id, files }
    if (!res.ok || !data.ok) throw new Error(data.error || res.status);
    form.reset();
    status.value = "ok";
  } catch {
    status.value = "error";
  }
}
</script>

<template>
  <p v-if="status === 'ok'">Thankswe will be in touch.</p>
  <form v-else @submit.prevent="onSubmit">
    <input type="email" name="email" required />
    <textarea name="message" required></textarea>
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off"
      style="display:none" aria-hidden="true" />
    <button :disabled="status === 'sending'">
      {{ status === "sending" ? "Sending…" : "Send" }}
    </button>
    <p v-if="status === 'error'" role="alert">Could not send. Try again.</p>
  </form>
</template>

Success and error handling

  • ref is auto-imported in Nuxt 3, so the component needs no explicit import — drop it into pages/ or components/ as-is.
  • Posting to Formward from the client means nuxi generate (full static export) keeps working; there is no Nitro route to render at request time.
  • The Accept: application/json header switches Formward from its 302-redirect default to the { ok, id, files } JSON response.

Spam protection

Every example above includes the _gotcha honeypot field. It is hidden from real users and must stay empty; Formward silently drops any submission where it is filled, which stops most bots with no CAPTCHA. For a stricter gate, add a Cloudflare Turnstile widget and send its token as the cf-turnstile-response field — Formward verifies it on receipt.

The JSON response

A standard POST gets a 302 redirect (or your thank-you page). Send the Accept: application/json header — as every fetch() example above does — and Formward returns JSON instead:

HTTP/1.1 200 OK
Content-Type: application/json

{ "ok": true, "id": "clxyz123...", "files": [] }

On failure the body is { ok: false, error } with a 4xx status (validation, plan limit, and so on). See the AJAX docs for the full status-code table and CORS notes.

Other frameworks

Collect your first Nuxt submission

Create a form, paste the snippet, and keep every submission in the EU. GDPR-clean from the first POST.

Nuxt form handling without a backend | Formward