Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Validate every lead submission on the server, even if the browser also checks required fields and email format. For the Next.js App Router, a Server Action can parse the submitted FormData, validate it with a schema, return field errors for display, and stop before sending email or calling a CRM when the input is invalid. Add a rate limit to the submission operation before it triggers costly or externally visible work.

Choose the form handler that fits your router

Architecture Handler What to keep in mind
App Router Server Action Next.js documents form submissions handled by Server Actions, with server-side schema validation and field errors returned for display through useActionState. See the Next.js Forms guide.
Pages Router API Route Next.js documents API Routes as a server-side form-handling option. Apply the same server-side validation and abuse controls; see the Next.js Forms guide.

The code below illustrates the App Router pattern. It assumes the project has Zod installed and that checkLeadLimit is backed by a rate limiter appropriate to the deployment. The example deliberately leaves the limiter and email or CRM integration as application-specific functions rather than implying a universal threshold or provider setup.

Validate on the server, then return field errors

Browser checks such as required and type="email" improve immediate feedback, but users can bypass them and send a request directly. Treat a Server Action as a public endpoint: Next.js says Server Functions are reachable through direct POST requests. Perform the checks this operation needs inside the action, before any mutation or downstream work. See Next.js Forms and Next.js Mutating Data.

A practical schema checks required values, type, length, and semantic constraints. Allow legitimate Unicode and punctuation in names and messages; impose a message length suited to the form. Use a maintained email validator appropriate to the application rather than treating a simplistic pattern as proof of validity. A syntactically valid address does not prove mailbox access. Validation also does not replace parameterized database operations or context-aware output encoding. See the OWASP Input Validation Cheat Sheet.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// app/actions.ts
'use server'

import { z } from 'zod'

const leadSchema = z.object({
  name: z.string().trim().min(1, 'Enter your name.').max(100, 'Name is too long.'),
  email: z.string().trim().email('Enter a valid email address.').max(254, 'Email address is too long.'),
  message: z.string().trim().min(1, 'Enter a message.').max(5000, 'Message is too long.'),
})

type LeadState = {
  fieldErrors?: Record<string, string[] | undefined>
  status?: 'success' | 'error'
  message?: string
}

export async function submitLead(
  _previousState: LeadState,
  formData: FormData,
): Promise<LeadState> {
  // Check the endpoint-level rate limit here, before expensive downstream work.
  const limited = await checkLeadLimit()
  if (limited) {
    return { status: 'error', message: 'Too many submissions. Please try again later.' }
  }

  const parsed = leadSchema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
    message: formData.get('message'),
  })

  if (!parsed.success) {
    return {
      status: 'error',
      fieldErrors: parsed.error.flatten().fieldErrors,
    }
  }

  // Only validated values reach email, CRM, webhook, or persistence code.
  await sendLeadToYourBackend(parsed.data)
  return { status: 'success', message: 'Thanks — your message has been received.' }
}

checkLeadLimit and sendLeadToYourBackend are placeholders for functions you implement. If the limit is exceeded, an API-style handler should respond with HTTP 429 Too Many Requests. A Server Action returns action state rather than giving this example a custom HTTP response path; choose an API Route if your application needs to define that response explicitly. OWASP documents 429 for rate-limited requests in its REST Security Cheat Sheet.

Display the action state in a Client Component

Use useActionState to connect the action to the form and render returned errors. Keep labels and error messages associated with their fields so visitors can identify and correct a problem.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option
// app/lead-form.tsx
'use client'

import { useActionState } from 'react'
import { submitLead } from './actions'

const initialState = {}

export function LeadForm() {
  const [state, formAction, pending] = useActionState(submitLead, initialState)

  return (
    <form action={formAction}>
      <label htmlFor="name">Name</label>
      <input id="name" name="name" required maxLength={100} />
      {state.fieldErrors?.name?.map((error) => <p key={error}>{error}</p>)}

      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required maxLength={254} />
      {state.fieldErrors?.email?.map((error) => <p key={error}>{error}</p>)}

      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" required maxLength={5000} />
      {state.fieldErrors?.message?.map((error) => <p key={error}>{error}</p>)}

      {state.message && <p role="status">{state.message}</p>}
      <button type="submit" disabled={pending}>
        {pending ? 'Sending…' : 'Send'}
      </button>
    </form>
  )
}

The HTML constraints are visitor-facing conveniences, not substitutes for the schema. Use an email confirmation or verification flow if the business requirement is to establish mailbox control; format validation alone cannot do that.

Limit request size before parsing, then limit fields

Next.js documents a default Server Action body limit of 1 MB, intended to limit resource consumption; the value can be configured. That is a framework ceiling, not a suitable target for an ordinary lead message. Configure an appropriately tight request-size bound for the application and enforce much smaller field-level limits in the schema. The Next.js serverActions configuration reference describes the default and configuration option; OWASP also recommends input-size bounds in its Input Validation Cheat Sheet.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set limits as early as your hosting and request-handling stack allows, before buffering or parsing an oversized body. The exact configuration point depends on the deployment path; do not assume a field-level check can prevent the cost of receiving a huge request.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Design rate limits around the operation and deployment

A lead submission may send email, call an external service, deliver a webhook, or start expensive work. OWASP identifies such operations as possible resource-exhaustion or spam vectors. Limit the lead-submission feature itself, and consider additional controls around especially costly downstream operations; a broad site-wide limit alone may not protect the specific work. See the OWASP Business Logic Security Cheat Sheet.

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Choose the counter scope

Counter approach Behavior Trade-off
In-process counter Counts requests seen by one running process. It may not represent traffic across multiple instances or serverless invocations, so it is not a reliable shared limit in a horizontally scaled deployment.
Shared backing service Coordinates limit state across instances. Assess latency, availability behavior, cost, and operational needs for the actual deployment.

Upstash documents an HTTP-based Ratelimit library with Next.js and serverless examples, including multiple-limit capabilities. It is one possible implementation, not a requirement: see the Upstash Rate Limit overview.

Set and tune a policy without guessing

There is no evidence-backed universal requests-per-IP, per-email, or time-window threshold for every lead form. Pick limits based on expected traffic, observed abuse, the cost of the downstream action, and whether your deployment shares counter state. Revisit the policy as traffic and abuse patterns change. Avoid relying on a single identifier as a complete defense: legitimate users may share networks, and identifiers can be manipulated.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Harden the endpoint and protect submitted data

  • Review Server Action origin handling. Next.js compares the request Origin with Host or X-Forwarded-Host for Server Actions as a CSRF risk reduction. If a proxy or multi-layer deployment changes the apparent host, set serverActions.allowedOrigins only to the safe origins actually required; do not widen the list casually. See the configuration reference.
  • Keep data access and rendering safe. Use parameterized database operations and encode untrusted text for its output context; validation is not an injection defense by itself.
  • Minimize logs and retention. Keep only useful failure metadata for diagnosing rejected requests. Avoid logging full request bodies, secrets, or verbatim rejected input, which can expose sensitive data or create log-injection risks. Set retention appropriate to the lead data you collect; see the OWASP Input Validation Cheat Sheet.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.