Skip to content
FormSubmit

Nuxt

How to add a contact form to Nuxt (no server routes)

Add a contact form to Nuxt 3 or 4 that works with nuxt generate and static hosting by posting directly to a form backend.

1 min readUpdated Sep 28, 2026

In Nuxt, create a ContactForm.vue component that submits FormData to your FormSubmit endpoint with fetch. You don't need a Nitro server route or an email module, and the form keeps working when you deploy a fully static site with nuxt generate.

1. Component

components/ContactForm.vue
<!-- components/ContactForm.vue -->
<script setup lang="ts">
import { ref } from "vue";

const ENDPOINT = "https://formsubmit.app/f/YOUR_FORM_ID";
const status = ref("idle");
const error = ref("");
const renderedAt = Date.now();

async function onSubmit(e) {
  status.value = "sending";
  const data = new FormData(e.target);
  data.set("_ts", String(renderedAt));
  try {
    const res = await fetch(ENDPOINT, { method: "POST", body: data, headers: { Accept: "application/json" } });
    const json = await res.json();
    if (!res.ok || !json.ok) throw new Error(json.error?.message ?? "Something went wrong");
    status.value = "success";
    e.target.reset();
  } catch (err) {
    error.value = err.message;
    status.value = "error";
  }
}
</script>

<template>
  <p v-if="status === 'success'" class="text-green-700">Thanks! We'll be in touch soon.</p>
  <form v-else class="mx-auto max-w-lg space-y-5" @submit.prevent="onSubmit">
    <div class="space-y-1.5">
      <label for="fs-name" class="block text-sm font-medium text-gray-900">Name</label>
      <input id="fs-name" type="text" name="name" placeholder="Jane Doe" required class="block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20">
    </div>
    <div class="space-y-1.5">
      <label for="fs-email" class="block text-sm font-medium text-gray-900">Email</label>
      <input id="fs-email" type="email" name="email" placeholder="jane@example.com" required class="block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20">
    </div>
    <div class="space-y-1.5">
      <label for="fs-message" class="block text-sm font-medium text-gray-900">Message</label>
      <textarea id="fs-message" name="message" rows="5" placeholder="How can we help?" required class="block w-full rounded-lg border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20"></textarea>
    </div>
    <!-- Honeypot: leave this hidden field empty to catch bots -->
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none">
    <p v-if="status === 'error'" class="text-sm text-red-600">{{ error }}</p>
    <button type="submit" :disabled="status === 'sending'" class="inline-flex w-full items-center justify-center rounded-lg bg-gray-900 px-4 py-2.5 text-sm font-medium text-white hover:bg-gray-800 disabled:opacity-50">
      {{ status === "sending" ? "Sending…" : "Send" }}
    </button>
  </form>
</template>

Nuxt auto-imports components, so you can use <ContactForm /> anywhere.

2. Page

pages/contact.vue
<script setup lang="ts">
useSeoMeta({ title: "Contact", description: "Get in touch with our team." });
</script>

<template>
  <main class="mx-auto max-w-xl px-4 py-16">
    <h1 class="mb-8 text-3xl font-semibold">Contact</h1>
    <ContactForm />
  </main>
</template>

3. Optional: runtime config

nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: { public: { formsubmitUrl: "https://formsubmit.app/f/YOUR_FORM_ID" } },
});

Then use useRuntimeConfig().public.formsubmitUrl in the component.

Why not a server route?

A Nitro route that sends email means managing an email provider, secrets, spam protection and storage — and it stops working on purely static hosting. FormSubmit handles all of that, including retries for integrations and a searchable inbox.

Before launch

Add your domain to the allowlist and consider an autoresponder.

Frequently asked questions

Does this work with nuxt generate?

Yes. The form submits from the browser directly to FormSubmit, so no Nitro server route is required.

Should I put the endpoint in runtimeConfig?

You can use runtimeConfig.public for convenience, but the endpoint isn't a secret.

Keep reading

Your form backend is 60 seconds away

Sign in with Google, create a form, paste the endpoint. Free forever for small sites — no credit card.