Don't see any form submissions in Netlify UI despite success (NextJs)

Hello, I’ve got a single contact form that I’ve built into my NextJs site hosted on Netlify and I’m not seeing any successful form submissions in the Netlify Dashboard https://app.netlify.com/sites/YOURSITENAME/forms despite getting successful submissions several times that I’ve tested. When I visit my projects forms dashboard, it detected my contact form which is good, but there is no history of any submissions.

Site name: https://blackstonewebdesigns.com, blackstone-web-designs.netlify.app
Contact form: https://blackstonewebdesigns.com/contact

I’ve looked over the Quote from “[Support Guide] Form problems, form debugging, 404 when submitting - Support / Support Guides - Netlify Support Forums” (support guide) and didn’t see anything that would help with the exception that it’s possible my form submission is being eaten by a redirect. When I submit the form it sends the following payload containing all the appropriate fields to the www.blackstonewebdesigns.com/__forms.html which in the network tab the HTTP status code of the response for me is 303. In the support guide there is one small mention of a redirect that could cause an issue, but I’m not fully certain if that’s actually my issue because they talk about submitting to / and I believe that’s only applicable to static sites.

This is the payload I see in the network tab when I submit my form:

form-name      contact
name                Test User
email                 testuser@mailinator.com
phone               5555555555
message          This is a test message!

__forms.html is a simple HTML file I’ve put into my /public folder for NextJs and looks as follows:

<html>
  <body>
    <form
      name="contact"
      data-netlify="true"
      data-netlify-recaptcha="true"
      hidden
    >
      <input type="hidden" name="form-name" value="contact" />
      <input name="name" type="text" />
      <input name="email" type="email" />
      <input name="phone" type="tel" />
      <textarea name="message"></textarea>
      <div data-netlify-recaptcha="true"></div>
    </form>
  </body>
</html>

My contact page isn’t being SSR rendered (see the ‘use client’ at the top), but because I’m using React, I’ve followed some guides on the Netlify site it feels like I’m doing everything correct. Here is my contactPage.tsx code:

 'use client';

import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Mail, Phone } from 'lucide-react';
import { Button } from '@/ui-kit/components/button/button';
import { Card } from '@/ui-kit/components/card/card';
import { CONTACT_EMAIL, CONTACT_PHONE } from '@/constants/info';
import styles from './contactPage.module.css';

const contactFormSchema = z.object({
  name: z
    .string()
    .refine((val) => val.length > 0, { message: 'Please enter a name' })
    .min(2, { message: 'Name must be at least 2 characters' }),
  email: z.email({ message: 'Please enter a valid email address' }),
  phone: z
    .string()
    .min(10, { message: 'Please enter a valid phone number' })
    .regex(/^[\d\s\-\(\)]+$/, {
      message:
        'Phone number can only contain digits, spaces, dashes, and parentheses',
    }),
  message: z
    .string()
    .refine((val) => val.length > 0, { message: 'Please enter a message' })
    .min(10, { message: 'Message must be at least 10 characters' })
    .max(1000, { message: 'Message must be less than 1000 characters' }),
});

type ContactFormData = z.infer<typeof contactFormSchema>;

export const ContactPage = () => {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [submitStatus, setSubmitStatus] = useState<
    'idle' | 'success' | 'error'
  >('idle');

  const {
    register,
    handleSubmit,
    formState: { errors },
    reset,
  } = useForm<ContactFormData>({
    resolver: zodResolver(contactFormSchema),
  });

  const onSubmit = async (data: ContactFormData) => {
    setIsSubmitting(true);
    setSubmitStatus('idle');

    const formData = new URLSearchParams();
    formData.append('form-name', 'contact');
    formData.append('name', data.name);
    formData.append('email', data.email);
    formData.append('phone', data.phone);
    formData.append('message', data.message);

    try {
      const response = await fetch('/__forms.html', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: formData.toString(),
      });

      if (response.ok) {
        setSubmitStatus('success');
        reset();
      } else {
        setSubmitStatus('error');
      }
    } catch {
      setSubmitStatus('error');
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <div className={styles.contactPage}>
      <section className={styles.hero}>
        <div className={styles.container}>
          <h1 className={styles.title}>Get In Touch</h1>
          <p className={styles.subtitle}>
            Ready to get started? Contact us today to discuss your project.
          </p>
        </div>
      </section>

      <section className={styles.content}>
        <div className={styles.container}>
          <div className={styles.grid}>
            <div className={styles.formSection}>
              <Card className={styles.formCard}>
                <h2 className={styles.sectionTitle}>Send Us a Message</h2>
                <p className={styles.description}>
                  Share your vision for your new website and all the features
                  you&apos;d like to include. We respond within 24 hours. For
                  immediate assistance, call us directly—if we&apos;re
                  unavailable, we&apos;ll return your call the same day.
                </p>

                <form
                  name="contact"
                  method="POST"
                  data-netlify="true"
                  data-netlify-recaptcha="true"
                  onSubmit={(e) => {
                    void handleSubmit(onSubmit)(e);
                  }}
                  className={styles.form}
                >
                  <input type="hidden" name="form-name" value="contact" />

                  <div className={styles.formGroup}>
                    <label htmlFor="name" className={styles.label}>
                      Name *
                    </label>
                    <input
                      type="text"
                      id="name"
                      {...register('name')}
                      className={styles.input}
                      aria-invalid={errors.name ? 'true' : 'false'}
                    />
                    {errors.name && (
                      <span className={styles.errorMessage}>
                        {errors.name.message}
                      </span>
                    )}
                  </div>

                  <div className={styles.formGroup}>
                    <label htmlFor="email" className={styles.label}>
                      Email *
                    </label>
                    <input
                      type="email"
                      id="email"
                      {...register('email')}
                      className={styles.input}
                      aria-invalid={errors.email ? 'true' : 'false'}
                    />
                    {errors.email && (
                      <span className={styles.errorMessage}>
                        {errors.email.message}
                      </span>
                    )}
                  </div>

                  <div className={styles.formGroup}>
                    <label htmlFor="phone" className={styles.label}>
                      Phone *
                    </label>
                    <input
                      type="tel"
                      id="phone"
                      {...register('phone')}
                      className={styles.input}
                      aria-invalid={errors.phone ? 'true' : 'false'}
                    />
                    {errors.phone && (
                      <span className={styles.errorMessage}>
                        {errors.phone.message}
                      </span>
                    )}
                  </div>

                  <div className={styles.formGroup}>
                    <label htmlFor="message" className={styles.label}>
                      Message *
                    </label>
                    <textarea
                      id="message"
                      {...register('message')}
                      rows={6}
                      className={styles.textarea}
                      aria-invalid={errors.message ? 'true' : 'false'}
                    />
                    {errors.message && (
                      <span className={styles.errorMessage}>
                        {errors.message.message}
                      </span>
                    )}
                  </div>

                  <div
                    className={styles.recaptcha}
                    data-netlify-recaptcha="true"
                  />

                  {submitStatus === 'success' && (
                    <div className={styles.successMessage}>
                      Thank you! Your message has been sent successfully.
                      We&apos;ll get back to you soon.
                    </div>
                  )}

                  {submitStatus === 'error' && (
                    <div className={styles.errorMessage}>
                      Something went wrong. Please try again or contact us
                      directly.
                    </div>
                  )}

                  <Button
                    type="submit"
                    variant="primary"
                    size="large"
                    fullWidth
                    disabled={isSubmitting}
                  >
                    {isSubmitting ? 'Submitting...' : 'Submit'}
                  </Button>
                </form>
              </Card>
            </div>

            <div className={styles.ctaSection}>
              <Card className={styles.ctaCard}>
                <h2 className={styles.ctaTitle}>Ready to Get Started?</h2>
                <p className={styles.ctaDescription}>
                  Let&apos;s discuss your project and how we can help bring your
                  vision to life.
                </p>
                <div className={styles.ctaButtons}>
                  <a href={`tel:${CONTACT_PHONE}`}>
                    <Button variant="primary" size="large" fullWidth>
                      <Phone size={20} style={{ marginRight: '0.5rem' }} />
                      Call Us Now
                    </Button>
                  </a>
                  <a href={`mailto:${CONTACT_EMAIL}`}>
                    <Button variant="outline" size="large" fullWidth>
                      <Mail size={20} style={{ marginRight: '0.5rem' }} />
                      Send Email
                    </Button>
                  </a>
                </div>
              </Card>
            </div>
          </div>
        </div>
      </section>
    </div>
  );
};

Did you try to remove data-netlify-recaptcha=“true” to see if it works? It probably reject any submission that doesnt have a valid g-recaptcha-response token ,it might received a missing token and bounces the request.

and also try this one , it might work

Change this :

const response = await fetch(‘/__forms.html’, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/x-www-form-urlencoded’ },
body: formData.toString(),
});

to

const response = await fetch(‘/’, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/x-www-form-urlencoded’ },
body: formData.toString(),
});

@rrandr, after removing the data-netlify-recaptcha="true", I started seeing form submissions come in, but without any bot/spam prevention enabled.

Posting to /__forms.html is necessary for me since I’m using javascript to render the form: (relevent OpenNext docs)

I was reading over this section of the forms setup docs and it makes it sound like you can easily have Netlify drop in the proper recaptcha into the page, but thinking about it a bit more it makes sense that on my contact page the <div /> I added for the recaptcha wasn’t populated since it’s a client side rendered page.

From what I can tell, I’ll need to implement a custom recaptcha after getting setup on Google and adding the env keys into Netlify and then do as you mentioned and pass the g-recaptcha-response value along with the form when I’m submitting it.

EDIT: For anyone else running into this problem in the future, I was able to implement custom recaptcha v2 challenge by following the Custom Recaptcha steps oulined here in the Netlify docs.