Next.js: Build a Simple Interactive Contact Form

Written by

in

In today’s digital landscape, having a functional contact form on your website is crucial for engaging with your audience. It provides a direct channel for visitors to reach out, ask questions, provide feedback, or even express interest in your products or services. But building a contact form can sometimes feel like a chore, especially when you need to handle form submissions, validation, and user experience. That’s where Next.js comes in handy. Next.js, with its server-side rendering, API routes, and easy-to-use form handling, simplifies the process and makes it a breeze to create dynamic and interactive web applications, including a clean and efficient contact form. In this tutorial, we will walk you through, step by step, how to build a simple, yet fully functional, contact form using Next.js. We’ll cover everything from setting up your project to handling form submissions and providing feedback to the user. Whether you’re a beginner or an intermediate developer, this tutorial will equip you with the knowledge to create a professional-looking contact form that seamlessly integrates into your Next.js application.

Prerequisites

Before we dive into the code, let’s make sure you have everything you need:

  • Node.js and npm (or yarn): You’ll need Node.js and npm (Node Package Manager) or yarn installed on your machine. These are essential for managing project dependencies and running the development server.
  • A Code Editor: A code editor like VS Code, Sublime Text, or Atom will be your best friend throughout this journey.
  • Basic Understanding of JavaScript and React: Familiarity with JavaScript and React concepts will be helpful. While we’ll explain things as we go, a basic understanding will make it easier to follow along.

Setting Up Your Next.js Project

Let’s start by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app my-contact-form
cd my-contact-form

This command creates a new Next.js project named “my-contact-form” and navigates you into the project directory. Next.js will automatically set up all the necessary files and configurations for you. Next, start the development server by running:

npm run dev

Or, if you’re using yarn:

yarn dev

Your application should now be running on http://localhost:3000. Open your web browser and navigate to this address to see the default Next.js welcome page. Now that our project is set up, let’s start building our contact form!

Creating the Contact Form Component

In this section, we’ll create a React component that will render our contact form. This component will handle the form’s user interface, including the input fields and the submit button. Inside your “my-contact-form” directory, create a new file named “components/ContactForm.js” (you may need to create the “components” directory first). Add the following code to ContactForm.js:

import { useState } from 'react';

function ContactForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [message, setMessage] = useState('');
  const [submitted, setSubmitted] = useState(false);
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();

    const formData = {
      name,
      email,
      message,
    };

    try {
      const response = await fetch('/api/contact', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(formData),
      });

      const data = await response.json();

      if (response.ok) {
        setSubmitted(true);
        // Clear the form fields after successful submission
        setName('');
        setEmail('');
        setMessage('');
        setError(null);
      } else {
        setError(data.message || 'An error occurred.');
      }
    } catch (error) {
      setError('An unexpected error occurred.');
      console.error('Submission error:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="contact-form">
      <h2>Contact Us</h2>
      {submitted && (
        <div className="success-message">
          Thank you for your message! We'll get back to you soon.
        </div>
      )}
      {error && (
        <div className="error-message">
          {error}
        </div>
      )}
      <div className="form-group">
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          value={name}
          onChange={(e) => setName(e.target.value)}
          required
        />
      </div>
      <div className="form-group">
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          name="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
      </div>
      <div className="form-group">
        <label htmlFor="message">Message:</label>
        <textarea
          id="message"
          name="message"
          value={message}
          onChange={(e) => setMessage(e.target.value)}
          required
        />
      </div>
      <button type="submit" disabled={submitted}>
        {submitted ? 'Submitting...' : 'Submit'}
      </button>
    </form>
  );
}

export default ContactForm;

Let’s break down the code:

  • Import useState: We import the useState hook from React to manage the form’s state.
  • State Variables: We declare state variables to hold the input values (name, email, message), a flag to indicate successful submission (submitted), and a variable to hold any errors (error).
  • handleSubmit Function: This asynchronous function is triggered when the form is submitted. It prevents the default form submission behavior, prepares the form data, and sends a POST request to the /api/contact route (we’ll create this route later).
  • Fetch API: The fetch API is used to send the form data to the server. The server-side code (in /api/contact) will handle the form data processing, such as sending emails or saving to a database.
  • Conditional Rendering: The component conditionally renders success and error messages based on the submitted and error states.
  • Form Elements: The form includes input fields for name and email, a textarea for the message, and a submit button. The onChange event handlers update the state variables as the user types. The required attribute ensures that the user fills in the required fields.
  • Disabled Submit Button: The submit button is disabled while the form is submitting or after it has been successfully submitted.

Creating the API Route

Now, let’s create an API route to handle the form submission on the server-side. In Next.js, API routes live in the pages/api directory. Create a file named pages/api/contact.js and add the following code:


// pages/api/contact.js
export default async function handler(req, res) {
  if (req.method === 'POST') {
    const { name, email, message } = req.body;

    // Basic validation
    if (!name || !email || !message) {
      return res.status(400).json({ message: 'All fields are required' });
    }

    // Simple email validation (you might want a more robust solution)
    if (!/^[w-.]+@([w-]+.)+[w-]{2,4}$/.test(email)) {
      return res.status(400).json({ message: 'Invalid email address' });
    }

    try {
      // Replace with your actual email sending logic (e.g., using Nodemailer)
      // For this example, we'll just log the data to the console
      console.log('Form data received:', { name, email, message });

      // Simulate a successful operation
      await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate processing time

      res.status(200).json({ message: 'Form submitted successfully!' });
    } catch (error) {
      console.error('Error processing form submission:', error);
      res.status(500).json({ message: 'Failed to submit form' });
    }
  } else {
    res.setHeader('Allow', ['POST']);
    res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

Here’s what the API route does:

  • Handles POST requests: It checks if the request method is POST.
  • Extracts form data: It extracts the name, email, and message from the request body.
  • Basic validation: It performs basic validation to ensure that all required fields are filled and that the email address is in a valid format.
  • Simulates sending an email: In a real-world scenario, you would integrate an email sending service like Nodemailer or use a service like SendGrid, Mailgun, or AWS SES. For this example, we log the form data to the console and simulate a short processing time using setTimeout.
  • Returns a response: If the submission is successful, it returns a 200 OK status with a success message. If there’s an error (e.g., validation fails, email sending fails), it returns an appropriate error status and message.

Integrating the Contact Form into Your Page

Now that we’ve created the contact form component and the API route, let’s integrate the form into your main page. Open pages/index.js and modify it as follows:


import ContactForm from '../components/ContactForm';

function HomePage() {
  return (
    <div className="container">
      <main className="main">
        <ContactForm />
      </main>
    </div>
  );
}

export default HomePage;

This code imports the ContactForm component and renders it within a container. You can customize the styling and layout of your page as needed.

Styling the Contact Form

To make the contact form visually appealing, let’s add some basic CSS. You can add the following CSS to your styles/globals.css file, or create a separate CSS file for the contact form component:


.container {
  min-height: 100vh;
  padding: 0 0.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: #f0f0f0;
}

.main {
  padding: 5rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.contact-form {
  background-color: #fff;
  padding: 2rem;
  border-radius: 8px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  width: 100%;
  max-width: 500px;
}

.form-group {
  margin-bottom: 1rem;
}

label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: bold;
}

input[type="text"], input[type="email"], textarea {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 1rem;
  margin-bottom: 0.5rem;
}

textarea {
  height: 120px;
}

button {
  background-color: #0070f3;
  color: white;
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1rem;
  transition: background-color 0.2s ease;
}

button:hover {
  background-color: #0056b3;
}

button:disabled {
  background-color: #cccccc;
  cursor: not-allowed;
}

.success-message, .error-message {
  padding: 1rem;
  margin-bottom: 1rem;
  border-radius: 4px;
}

.success-message {
  background-color: #d4edda;
  color: #155724;
  border: 1px solid #c3e6cb;
}

.error-message {
  background-color: #f8d7da;
  color: #721c24;
  border: 1px solid #f5c6cb;
}

This CSS provides basic styling for the form, including input fields, labels, buttons, and success/error messages. Feel free to customize the styling to match your website’s design.

Testing Your Contact Form

Now, it’s time to test your contact form! Open your Next.js application in your browser and navigate to the page where you’ve integrated the form (usually the home page at http://localhost:3000). Fill out the form fields and click the submit button. You should see a success message if the form submission is successful. Check your browser’s console (usually opened by right-clicking on the page and selecting “Inspect” or “Inspect Element”) to see the form data logged by the API route. If you encounter any errors, carefully review the error messages in the console and the code, and double-check that you’ve followed all the steps correctly.

Common Mistakes and Troubleshooting

Here are some common mistakes and how to fix them:

  • Incorrect API Route Path: Double-check that the fetch call in your ContactForm.js component is pointing to the correct API route (/api/contact).
  • CORS Errors: If you encounter CORS (Cross-Origin Resource Sharing) errors, it means your frontend is trying to access a resource on a different domain than the one it’s hosted on. This is less likely with Next.js, as the API routes run on the same server. However, if you are calling an external API, you might need to configure CORS on the external API server or use a proxy.
  • Form Data Not Being Sent: Make sure your form fields have the correct name attributes, as these are used to identify the form data in the API route. Also, ensure you are using the e.preventDefault() method in your handleSubmit function to prevent the default form submission behavior, which can cause the page to reload and the data not to be sent.
  • Missing Dependencies: Ensure that you have installed all necessary dependencies. In this case, there are no external dependencies, but if you were using an email sending library (e.g., Nodemailer), you would need to install it with npm install nodemailer or yarn add nodemailer.
  • Incorrect Email Sending Configuration: If you’re using an email sending service (like Nodemailer, SendGrid, Mailgun, or AWS SES), make sure you’ve configured the service correctly with your API keys, email addresses, and other settings. Also, check your spam folder, as emails can sometimes end up there.
  • Server Errors: Check your server-side code (pages/api/contact.js) for any errors. Use console.log() statements to debug your code and see what’s happening. Also, check the server logs for any error messages.

Enhancements and Next Steps

Here are some ways to enhance your contact form:

  • Implement Email Sending: Integrate an email sending service (e.g., Nodemailer, SendGrid, Mailgun, AWS SES) to send the form data to your email address.
  • Add More Validation: Implement more robust form validation on both the client-side and server-side to ensure data integrity.
  • Implement reCAPTCHA: Add reCAPTCHA to prevent spam submissions.
  • Store Submissions: Save the form submissions to a database.
  • Improve Styling: Customize the styling of the form to match your website’s design.
  • Add File Uploads: Allow users to upload files via the contact form.
  • Use a Form Library: Consider using a form library like Formik or React Hook Form to simplify form management and validation.

Key Takeaways

  • You’ve learned how to create a basic contact form in Next.js.
  • You’ve learned how to handle form submissions using API routes.
  • You’ve learned how to implement basic form validation.
  • You’ve learned how to display success and error messages.

FAQ

  1. Can I use this contact form on a production website? Yes, you can. However, you’ll need to implement email sending or another method of processing the form data on the server-side. You should also add more robust validation and consider adding reCAPTCHA to prevent spam.
  2. How do I send emails using Nodemailer? You can install Nodemailer using npm install nodemailer. Then, in your API route, you would import Nodemailer, configure your email settings (e.g., SMTP server, email address), and use Nodemailer’s sendMail function to send the email.
  3. How do I add reCAPTCHA to my form? You can use a reCAPTCHA component from a library like react-google-recaptcha. You’ll need to obtain reCAPTCHA keys from Google and configure the component in your form. You’ll also need to verify the reCAPTCHA response on the server-side before processing the form data.
  4. What if I want to store the form submissions in a database? You can integrate a database (e.g., MongoDB, PostgreSQL) with your Next.js application. In your API route, you would use a database client library to connect to the database and save the form data.
  5. Can I use this contact form without Next.js? Yes, you can adapt the code to use it in a regular React application. You would need to set up a server-side endpoint to handle the form submissions, which would require configuring a backend framework or using a serverless function.

Building a contact form with Next.js is a streamlined process, providing a robust solution for your website’s communication needs. By following the steps outlined in this tutorial, you’ve created a functional and adaptable contact form. Remember to prioritize user experience, implement comprehensive validation, and secure your form against spam. By incorporating these enhancements, you can create a contact form that not only facilitates communication but also enhances the overall professionalism of your website, making it a valuable asset for engaging with your audience and driving meaningful interactions. The ability to easily integrate such a form is a testament to the power and flexibility of Next.js, empowering developers to create dynamic and user-friendly web applications with ease.