Seamless Contact Form experience with Netlify Form in Nuxt 3

Aug 06, 2024 ยท 6 min read
Seamless Contact Form experience with Netlify Form in Nuxt 3

Contact form is an essential part of any portfolio site, where people can reach you for further queries and questions. In this article, we will explore how to use Netlify Form service to create a contact form and handle its submission from end to end in a Nuxt static site.

Prerequisites

You need to have an active Netlify account, and create a Nuxt 3 application using the following command:

npx nuxi@latest init your-nuxt-app

Once done, we are ready to build our contact form, starting with enabling the Netlify Form service.

Enabling Netlify Form service

Netlify Serverless Form is an out-of-the-box service from Netlify that allows us to manage forms in our static applications at ease, without extra API calls or server setup.

In the Netlify dashboard, we navigate to our target site project, select the Form section on the left sidebar, and scroll down to the Form Detection section. Here, we can enable the form detection by clicking the Enable Form detection button.

Netlify Form Detection in disabled mode

Once enabled, Netlify is ready to detect and manage our target HTML form during the deployment process. But to fullly use this feature, we need to create the form with the necessary Netlify attributes, which we will do next.

Creating a contact form

In our Nuxt application, let's create a contact.vue page, located in pages/ directory. This page contains an HTML form with the following attributes:

  • netlify or data-netlify="true" - Netlify detects the target form using this attribute.
  • data-netlify-honeypot - Netlify uses this attribute to locate a hidden "honeypot" field for spam protection.
  • name - unique name of the form to appear in the Netlify Form panel.
  • method - HTTP method to use when submitting the form.
  • id - The unique identifier for the form.
<form 
  name="contact-form" 
  method="POST"
  netlify
  data-netlify="true" 
  data-netlify-honeypot="bot-field" 
  id="contact-form"
>
  <!-- form fields -->
</form>

For the spam protection, we add a hidden input as the "honeybot" field, which is visible to bots but not for real users. If the field is filled, Netlify will reject the submission. Its name should be identical with the value of data-netlify-honeypot, as shown below:

<div class="hidden">
  <label>
    Donโ€™t fill this out if youโ€™re human: <input name="bot-field" />
  </label>
</div>

And since we are working with static Nuxt site (SSR pre-rendering mode), we need to add an additional hidden input field with the name form-name and the value of the form's name, as follows:

<input type="hidden" name="form-name" value="contact-form" />

Next, we add the actual form fields for the user to fill in:

<div>
  <label for="name" class="label">
    Your Name
  </label>
  <input type="text" name="name" id="name" 
    placeholder="What is your name?" class="field" required />
</div>
<div>
  <label for="email" class="label">
    Your Email
  </label>
  <input type="email" name="email" id="email" 
    placeholder="What is your email?" class="field" required />
</div>
<div>
  <label for="message" class="label">
    Your Message
  </label>
  <textarea rows="4" name="message" id="message" 
    placeholder="What do you want to talk about?" class="field" />
</div>

And some button for submitting and reseting the form when needed:

<div>
  <button type="submit">Send message</button>
  <button type="reset">Clear</button>
</div>

At this point, we have created a simple contact form with the necessary attributes for Netlify Form to detect. Upon a successful form submission, Netlify will redirect the user to a default form's success page. We can override this behavior by giving a custom success page URL with the action attribute in the form element.

Or, we can stop the redirecting mechanism and handle the form submission programatically, which we will do next.

Handling form submission with JavaScript

In our page's script section, we define a handleSubmit method to handle the form submission. We use the fetch API to send the form data as a URL-encoded string using the FormData API, with the content type of "application/x-www-form-urlencoded":

const handleSubmit = async (e) => {
  const form = e.target;
  const formData = new FormData(form);
  try {
    const result = await fetch("/", {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: new URLSearchParams(formData).toString(),
    });
  } catch (error) {
    console.error(error);
  }
};

To manage the form submission's status, we define the FormState enum and formState variable as follows:

const FormState = {
  IDLE: "IDLE",
  PENDING: "PENDING",
  SUCCESS: "SUCCESS",
  ERROR: "ERROR",
} as const;

const contactFormState = ref<keyof typeof FormState>(FormState.IDLE);

And in the handleSubmit method, we update the state accordingly:

const handleSubmit = async (e: any) => {
  contactFormState.value = FormState.PENDING;
  /**... */
  try {
    /**... */
    if (result.ok) {
      contactFormState.value = FormState.SUCCESS;
    } else {
      contactFormState.value = FormState.ERROR;
    }
  } catch (error) {
    /**... */
    contactFormState.value = FormState.ERROR;
  }
};

We can also reset the form's data once the form is submitted successfully, as shown below:

try {
  /** ... */
  if (result.ok) {
    contactFormState.value = FormState.SUCCESS;
    form.reset();
  }
}

And we reset the form state back to IDLE after a few seconds for re-submission:

try {
  /**... */
} catch (error) {
  /**... */
} finally {
  setTimeout(() => {
    contactFormState.value = FormState.IDLE;
  }, 5000);
}

We then bind the form submission event submit to handleSubmit, with the modifier .prevent for preventDefault(), as follows:

<form
  name="contact-form"
  @submit.prevent="handleSubmit"
>

Finally, we display a custom message to the user based on the form's state as below:

<!--contact.vue-->
<p v-if="formState === FormState.SUCCESS" class="text-green-400 text-center">
  Form has been submitted successfully!
</p>
<p v-else-if="formState === FormState.ERROR" class="text-red-500 text-center">
  Form submission failed. Please try again.
</p>

At this point, our contact page will look similar to the following upon a successful submission:

Contact form after submission

With that, our app is ready for deployment. However, when we use Nuxt 3 in SSR pre-rendering mode (or static site generating mode), Netlify is sometimes unable to detect and parse the form properly during the build process. We need an additional workaround to make sure that Netlify detects the form.

Let's do it in the next section.

Making sure Netlify detects the form on deployment

To do so, in the public directory, we create an HTML file - contact-duplicate.html - with the form's main HTML code, including all the form fields, and the required attributes for Netlify to use:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Contact Form</title>
</head>
<body>
  <form 
    name="contact-form" 
    method="POST"
    netlify
    data-netlify="true" 
    data-netlify-honeypot="bot-field" 
    id="contact-form"
    class="hidden"
  >
    <!--form fields & buttons-->
  </form>
</body>
</html>

That's it! Nuxt will automatically include this file in the build process, allowing Netlify to detect the form and perform the necessary actions during deployment. Now, when we deploy our site to Netlify for the first time, it will detect the form and add it to the system automatically.

Netlify Active forms in the dashboard

Summary

In this post, we learned how to create and manage a contact form submission using Netlify Form in Nuxt SSR pre-rendering mode. We also learned how to add basic spam protection to the form, handle the form submission programmatically using JavaScript, and display a custom message to the user based on the form's state without redirecting the user.

What's next? How about adding more validations or integrate a third-party service like VeeValidate for better user experience?

๐Ÿ‘‰ Learn about Vue 3 and TypeScript with my new book Learning Vue!

๐Ÿ‘‰ If you'd like to catch up with me sometimes, follow me on X | LinkedIn.

Like this post or find it helpful? Share it ๐Ÿ‘‡๐Ÿผ ๐Ÿ˜‰