Para criar um formulário de cadastro com validação de CPF em Next.js, combine React Hook Form para gerenciamento de estado, Zod para validação de schema e um Route Handler que consulta a API da CPFHub.io no servidor. Essa arquitetura garante que a API key nunca fica exposta no cliente e que o nome e a data de nascimento do titular são preenchidos automaticamente após a validação. O resultado é um formulário que reduz erros de digitação e melhora a experiência do usuário.
Introdução
Formulários de cadastro são um dos pontos mais críticos de qualquer aplicação. Validar o CPF durante o preenchimento, com feedback visual e preenchimento automático de dados, melhora drasticamente a experiência do usuário e a qualidade dos dados coletados. Este guia mostra como construir esse formulário em Next.js com React Hook Form, validação de CPF no cliente e no servidor, e integração com a API do CPFHub.io.
Configurando as dependências
Instale as bibliotecas necessárias para o formulário:
// Terminal
// npm install react-hook-form @hookform/resolvers zod
// lib/schemas.js
import { z } from 'zod';
function validarDigitosCpf(cpf) {
const digitos = cpf.split('').map(Number);
if (new Set(digitos).size === 1) return false;
let soma = 0;
for (let i = 0; i < 9; i++) soma += digitos[i] * (10 - i);
const d1 = soma % 11 < 2 ? 0 : 11 - (soma % 11);
soma = 0;
for (let i = 0; i < 10; i++) soma += digitos[i] * (11 - i);
const d2 = soma % 11 < 2 ? 0 : 11 - (soma % 11);
return digitos[9] === d1 && digitos[10] === d2;
}
export const cadastroSchema = z.object({
cpf: z.string()
.transform((val) => val.replace(/\D/g, ''))
.pipe(
z.string()
.length(11, 'CPF deve conter 11 dígitos')
.refine(validarDigitosCpf, 'Dígitos verificadores inválidos')
),
nome: z.string().min(3, 'Nome deve ter pelo menos 3 caracteres'),
email: z.string().email('Email inválido'),
dataNascimento: z.string().min(1, 'Data de nascimento é obrigatória'),
});
| Biblioteca | Papel |
|---|---|
react-hook-form | Gerenciamento de estado do formulário |
zod | Validação de schema com tipagem |
@hookform/resolvers | Integração entre React Hook Form e Zod |
Componente de input de CPF com máscara
Crie um componente de input que aplica máscara e validação visual ao CPF:
// components/CpfInput.jsx
'use client';
import { forwardRef, useCallback } from 'react';
function aplicarMascara(valor) {
const digitos = valor.replace(/\D/g, '').slice(0, 11);
if (digitos.length <= 3) return digitos;
if (digitos.length <= 6) return `${digitos.slice(0, 3)}.${digitos.slice(3)}`;
if (digitos.length <= 9)
return `${digitos.slice(0, 3)}.${digitos.slice(3, 6)}.${digitos.slice(6)}`;
return `${digitos.slice(0, 3)}.${digitos.slice(3, 6)}.${digitos.slice(6, 9)}-${digitos.slice(9)}`;
}
const CpfInput = forwardRef(function CpfInput(
{ onChange, onBlur, name, error, validado, ...props },
ref
) {
const handleChange = useCallback(
(e) => {
const mascarado = aplicarMascara(e.target.value);
e.target.value = mascarado;
onChange(e);
},
[onChange]
);
let borderClass = 'border-gray-300';
if (error) borderClass = 'border-red-500';
else if (validado) borderClass = 'border-green-500';
return (
<div>
<label htmlFor={name} className="block text-sm font-medium mb-1">
CPF
</label>
<input
ref={ref}
id={name}
name={name}
type="text"
inputMode="numeric"
maxLength={14}
placeholder="000.000.000-00"
onChange={handleChange}
onBlur={onBlur}
className={`w-full p-3 border rounded ${borderClass}`}
{...props}
/>
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
</div>
);
});
export default CpfInput;
Route handler para validação
Crie o endpoint que válida o CPF e retorna dados para preenchimento automático:
// app/api/validar-cpf/route.js
export async function POST(request) {
const { cpf } = await request.json();
const cpfLimpo = cpf.replace(/\D/g, '');
if (cpfLimpo.length !== 11) {
return Response.json(
{ valido: false, message: 'CPF incompleto' },
{ status: 400 }
);
}
try {
const response = await fetch(
`${process.env.CPFHUB_BASE_URL}/cpf/${cpfLimpo}`,
{
headers: { 'x-api-key': process.env.CPFHUB_API_KEY },
}
);
if (!response.ok) {
return Response.json({
valido: true,
encontrado: false,
message: 'CPF válido, mas não encontrado na base',
});
}
const dados = await response.json();
if (dados.success) {
return Response.json({
valido: true,
encontrado: true,
nome: dados.data.name,
dataNascimento: dados.data.birthDate,
genero: dados.data.gender,
});
}
return Response.json({ valido: true, encontrado: false });
} catch {
return Response.json(
{ valido: false, message: 'Erro na validação' },
{ status: 502 }
);
}
}
Formulário completo de cadastro
Monte o formulário com validação integrada e preenchimento automático:
// components/FormularioCadastro.jsx
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useState } from 'react';
import { cadastroSchema } from '@/lib/schemas';
import CpfInput from './CpfInput';
export default function FormularioCadastro() {
const [cpfValidado, setCpfValidado] = useState(false);
const [submitting, setSubmitting] = useState(false);
const {
register,
handleSubmit,
setValue,
formState: { errors },
} = useForm({
resolver: zodResolver(cadastroSchema),
});
const handleCpfBlur = async (e) => {
const cpf = e.target.value;
if (cpf.replace(/\D/g, '').length !== 11) return;
try {
const response = await fetch('/api/validar-cpf', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cpf }),
});
const dados = await response.json();
if (dados.encontrado) {
setValue('nome', dados.nome);
setValue('dataNascimento', dados.dataNascimento);
setCpfValidado(true);
}
} catch {
console.error('Erro ao validar CPF');
}
};
const onSubmit = async (data) => {
setSubmitting(true);
try {
console.log('Dados do cadastro:', data);
} finally {
setSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4 max-w-lg mx-auto">
<CpfInput
{...register('cpf')}
error={errors.cpf?.message}
validado={cpfValidado}
onBlur={handleCpfBlur}
/>
<div>
<label htmlFor="nome" className="block text-sm font-medium mb-1">Nome</label>
<input id="nome" {...register('nome')} className="w-full p-3 border rounded" />
{errors.nome && <p className="text-red-500 text-sm mt-1">{errors.nome.message}</p>}
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium mb-1">Email</label>
<input id="email" type="email" {...register('email')} className="w-full p-3 border rounded" />
{errors.email && <p className="text-red-500 text-sm mt-1">{errors.email.message}</p>}
</div>
<div>
<label htmlFor="dataNascimento" className="block text-sm font-medium mb-1">
Data de Nascimento
</label>
<input id="dataNascimento" type="date" {...register('dataNascimento')}
className="w-full p-3 border rounded" />
{errors.dataNascimento && (
<p className="text-red-500 text-sm mt-1">{errors.dataNascimento.message}</p>
)}
</div>
<button type="submit" disabled={submitting}
className="w-full p-3 bg-blue-600 text-white rounded">
{submitting ? 'Cadastrando...' : 'Cadastrar'}
</button>
</form>
);
}
Perguntas frequentes
Por que usar um Route Handler do Next.js em vez de chamar a API de CPF diretamente do cliente?
Chamar a API da CPFHub.io diretamente do navegador exporia sua API key para qualquer usuário que inspecionar as requisições de rede. O Route Handler (app/api/validar-cpf/route.js) roda no servidor, mantém a chave segura em variáveis de ambiente e ainda permite adicionar validação, rate limiting e logging antes de repassar a consulta. A documentação do Next.js detalha como criar e proteger esses endpoints.
Como funciona o preenchimento automático de nome e data de nascimento?
Quando o usuário termina de digitar o CPF (evento onBlur), o componente chama o Route Handler, que consulta a CPFHub.io. Se o CPF for encontrado, a API retorna name e birthDate, e o formulário usa setValue do React Hook Form para preencher os campos automaticamente. O usuário vê o feedback imediato sem precisar digitar as informações manualmente.
A API da CPFHub.io pode retornar erro 429 e travar o formulário?
Não. A CPFHub.io nunca bloqueia requisições nem retorna HTTP 429. Ao superar as 50 consultas mensais do plano gratuito, cada consulta adicional é cobrada automaticamente a R$0,15. Para formulários de cadastro com alto volume, isso significa que o fluxo nunca é interrompido por limite de requisições.
Como validar o CPF sintaticamente no cliente antes de chamar a API?
O schema Zod deste artigo já inclui a função validarDigitosCpf, que verifica os dígitos verificadores localmente. Essa validação roda no navegador antes de qualquer chamada de rede, evitando consultas desnecessárias à API para CPFs obviamente inválidos (como 111.111.111-11) e melhorando a performance percebida pelo usuário.
Conclusão
Um formulário de cadastro com validação de CPF integrada eleva a qualidade dos dados e a experiência do usuário. A combinação de React Hook Form para gerenciamento de estado, Zod para validação de schema e a API do CPFHub para enriquecimento de dados cria uma solução completa e profissional.
Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e adicione validação e preenchimento automático de CPF ao seu formulário Next.js ainda hoje.
CPFHub.io
Pronto para integrar a API?
50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.
Sobre a redação
Redação CPFHub.io
Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.



