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.
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
ordata-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:
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.
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 ๐๐ผ ๐