# Como criar um campo de CPF que funcione bem em modo offline

> Aprenda a criar um campo de CPF funcional em modo offline com Service Workers, validação local e sincronização posterior.

**Publicado:** 20/08/2025
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/como-criar-um-campo-de-cpf-que-funcione-bem-em-modo-offline

---


Um campo de CPF offline-first valida o formato localmente sem precisar de internet, armazena resultados anteriores via IndexedDB e enfileira CPFs coletados para validar com a API da CPFHub.io assim que a conexão retorna — garantindo que o fluxo de trabalho nunca seja interrompido por instabilidade de rede.

## Introdução

Nem todo usuário tem conexão estável o tempo todo. Representantes comerciais em campo, equipes de atendimento em áreas rurais, eventos presenciais com Wi-Fi instável -- são muitos os cenários onde um formulário com CPF precisa funcionar offline ou com conexão intermitente. Um campo de CPF que trava ou exibe erros quando não há internet frustra o usuário e interrompe o fluxo de trabalho.

---

## Arquitetura offline-first para CPF

A abordagem offline-first significa que a aplicação é projetada para funcionar sem conexão como estado padrão, com a conectividade sendo um recurso adicional. Para validação de CPF, isso implica:

1. **Validação local de formato**: sempre disponível, sem dependência de rede.
2. **Cache de consultas anteriores**: resultados de API armazenados localmente.
3. **Fila de sincronização**: CPFs coletados offline são validados quando a conexão retorna.
4. **Indicador de estado**: o usuário sabe se a validação foi local ou via API.

---

## Detector de conexão

Primeiro, crie um módulo que monitora o estado da conexão:

```javascript
var ConnectionMonitor = (function () {
 var isOnline = navigator.onLine;
 var listeners = [];

 window.addEventListener("online", function () {
 isOnline = true;
 notifyListeners();
 });

 window.addEventListener("offline", function () {
 isOnline = false;
 notifyListeners();
 });

 function notifyListeners() {
 listeners.forEach(function (fn) {
 fn(isOnline);
 });
 }

 function onChange(fn) {
 listeners.push(fn);
 }

 function getStatus() {
 return isOnline;
 }

 return { onChange: onChange, getStatus: getStatus };
})();
```

---

## Validação local robusta

A validação local é a base da funcionalidade offline. Ela valida o formato sem depender de rede:

```javascript
var CpfValidator = {
 validateFormat: function (cpf) {
 var digits = cpf.replace(/\D/g, "");

 if (digits.length !== 11) {
 return { valid: false, reason: "incomplete" };
 }

 if (/^(\d)\1{10}$/.test(digits)) {
 return { valid: false, reason: "repeated" };
 }

 // Primeiro dígito verificador
 var sum = 0;
 for (var i = 0; i < 9; i++) {
 sum += parseInt(digits[i]) * (10 - i);
 }
 var r1 = (sum * 10) % 11;
 if (r1 === 10) r1 = 0;
 if (r1 !== parseInt(digits[9])) {
 return { valid: false, reason: "check_digit_1" };
 }

 // Segundo dígito verificador
 sum = 0;
 for (var i = 0; i < 10; i++) {
 sum += parseInt(digits[i]) * (11 - i);
 }
 var r2 = (sum * 10) % 11;
 if (r2 === 10) r2 = 0;
 if (r2 !== parseInt(digits[10])) {
 return { valid: false, reason: "check_digit_2" };
 }

 return { valid: true, reason: null };
 },
};
```

---

## Cache local com IndexedDB

O IndexedDB oferece armazenamento persistente e estruturado, ideal para cache de consultas de CPF:

```javascript
var CpfCache = (function () {
 var DB_NAME = "cpfhub_cache";
 var STORE_NAME = "cpf_results";
 var DB_VERSION = 1;
 var CACHE_TTL = 24 * 60 * 60 * 1000; // 24 horas

 function openDB() {
 return new Promise(function (resolve, reject) {
 var request = indexedDB.open(DB_NAME, DB_VERSION);

 request.onupgradeneeded = function (event) {
 var db = event.target.result;
 if (!db.objectStoreNames.contains(STORE_NAME)) {
 var store = db.createObjectStore(STORE_NAME, { keyPath: "cpf" });
 store.createIndex("timestamp", "timestamp");
 }
 };

 request.onsuccess = function () {
 resolve(request.result);
 };

 request.onerror = function () {
 reject(request.error);
 };
 });
 }

 async function get(cpf) {
 var db = await openDB();
 return new Promise(function (resolve) {
 var tx = db.transaction(STORE_NAME, "readonly");
 var store = tx.objectStore(STORE_NAME);
 var req = store.get(cpf);

 req.onsuccess = function () {
 var result = req.result;
 if (result && Date.now() - result.timestamp < CACHE_TTL) {
 resolve(result.data);
 } else {
 resolve(null);
 }
 };

 req.onerror = function () {
 resolve(null);
 };
 });
 }

 async function set(cpf, data) {
 var db = await openDB();
 return new Promise(function (resolve) {
 var tx = db.transaction(STORE_NAME, "readwrite");
 var store = tx.objectStore(STORE_NAME);
 store.put({
 cpf: cpf,
 data: data,
 timestamp: Date.now(),
 });
 tx.oncomplete = function () {
 resolve();
 };
 });
 }

 async function cleanup() {
 var db = await openDB();
 var tx = db.transaction(STORE_NAME, "readwrite");
 var store = tx.objectStore(STORE_NAME);
 var index = store.index("timestamp");
 var cutoff = Date.now() - CACHE_TTL;

 var req = index.openCursor(IDBKeyRange.upperBound(cutoff));
 req.onsuccess = function (event) {
 var cursor = event.target.result;
 if (cursor) {
 cursor.delete();
 cursor.continue();
 }
 };
 }

 return { get: get, set: set, cleanup: cleanup };
})();
```

---

## Fila de sincronização offline

CPFs coletados offline ficam em uma fila para validação quando a conexão retornar:

```javascript
var SyncQueue = (function () {
 var QUEUE_KEY = "cpfhub_sync_queue";

 function getQueue() {
 try {
 return JSON.parse(localStorage.getItem(QUEUE_KEY) || "[]");
 } catch (e) {
 return [];
 }
 }

 function saveQueue(queue) {
 localStorage.setItem(QUEUE_KEY, JSON.stringify(queue));
 }

 function add(cpf, formData) {
 var queue = getQueue();
 queue.push({
 cpf: cpf,
 formData: formData,
 timestamp: Date.now(),
 status: "pending",
 });
 saveQueue(queue);
 }

 async function processQueue() {
 var queue = getQueue();
 var pending = queue.filter(function (item) {
 return item.status === "pending";
 });

 for (var i = 0; i < pending.length; i++) {
 var item = pending[i];

 var controller = new AbortController();
 var timeoutId = setTimeout(function () {
 controller.abort();
 }, 10000);

 try {
 var res = await fetch(
 "https://api.cpfhub.io/cpf/" + item.cpf,
 {
 headers: {
 "x-api-key": "SUA_CHAVE_DE_API",
 Accept: "application/json",
 },
 signal: controller.signal,
 }
 );
 clearTimeout(timeoutId);
 var data = await res.json();

 item.status = data.success ? "validated" : "invalid";
 item.apiData = data;

 // Cachear resultado
 if (data.success) {
 await CpfCache.set(item.cpf, data);
 }
 } catch (err) {
 clearTimeout(timeoutId);
 // Manter como pendente para nova tentativa
 break;
 }
 }

 saveQueue(queue);
 return queue;
 }

 function getPendingCount() {
 return getQueue().filter(function (i) {
 return i.status === "pending";
 }).length;
 }

 return { add: add, processQueue: processQueue, getPendingCount: getPendingCount };
})();
```

---

## Componente do campo de CPF offline-first

```html
<div class="cpf-offline" id="cpf-field">
 <div class="cpf-offline__status" id="connection-status">
 <span class="status-dot"></span>
 <span class="status-text"></span>
 </div>

 <div class="form-group">
 <label for="cpf">CPF</label>
 <input
 type="text"
 id="cpf"
 class="cpf-input"
 inputmode="numeric"
 maxlength="14"
 placeholder="000.000.000-00"
 />
 <div class="cpf-feedback" id="cpf-feedback" role="status" aria-live="polite"></div>
 </div>

 <div class="sync-info" id="sync-info"></div>
</div>
```

```css
.cpf-offline__status {
 display: flex;
 align-items: center;
 gap: 6px;
 font-size: 12px;
 margin-bottom: 12px;
}

.status-dot {
 width: 8px;
 height: 8px;
 border-radius: 50%;
 transition: background-color 0.3s ease;
}

.status-dot--online {
 background: #059669;
}

.status-dot--offline {
 background: #dc2626;
}

.status-text--online {
 color: #059669;
}

.status-text--offline {
 color: #dc2626;
}

.cpf-feedback {
 font-size: 13px;
 margin-top: 6px;
 min-height: 20px;
}

.cpf-feedback--local {
 color: #f59e0b;
}

.cpf-feedback--api {
 color: #059669;
}

.cpf-feedback--cached {
 color: #3b82f6;
}

.cpf-feedback--error {
 color: #dc2626;
}

.sync-info {
 font-size: 12px;
 color: #64748b;
 margin-top: 8px;
 padding: 8px 12px;
 background: #f8fafc;
 border-radius: 6px;
 display: none;
}

.sync-info--visible {
 display: block;
}
```

---

## JavaScript principal

```javascript
var cpfInput = document.getElementById("cpf");
var feedback = document.getElementById("cpf-feedback");
var statusDot = document.querySelector(".status-dot");
var statusText = document.querySelector(".status-text");
var syncInfo = document.getElementById("sync-info");
var debounceTimer = null;

// Atualizar indicador de conexão
function updateConnectionUI(online) {
 statusDot.className = "status-dot status-dot--" + (online ? "online" : "offline");
 statusText.className = "status-text status-text--" + (online ? "online" : "offline");
 statusText.textContent = online ? "Online" : "Offline";
}

updateConnectionUI(ConnectionMonitor.getStatus());
ConnectionMonitor.onChange(function (online) {
 updateConnectionUI(online);
 if (online) {
 syncPending();
 }
});

// Input handler
cpfInput.addEventListener("input", function () {
 var v = this.value.replace(/\D/g, "").slice(0, 11);
 if (v.length > 9)
 v = v.replace(/(\d{3})(\d{3})(\d{3})(\d{1,2})/, "$1.$2.$3-$4");
 else if (v.length > 6)
 v = v.replace(/(\d{3})(\d{3})(\d{1,3})/, "$1.$2.$3");
 else if (v.length > 3) v = v.replace(/(\d{3})(\d{1,3})/, "$1.$2");
 this.value = v;

 var digits = v.replace(/\D/g, "");

 if (digits.length < 11) {
 feedback.textContent = "";
 return;
 }

 if (debounceTimer) clearTimeout(debounceTimer);
 debounceTimer = setTimeout(function () {
 validateCpf(digits);
 }, 400);
});

async function validateCpf(cpf) {
 // 1. Validação local
 var localResult = CpfValidator.validateFormat(cpf);
 if (!localResult.valid) {
 feedback.textContent = "CPF com formato inválido.";
 feedback.className = "cpf-feedback cpf-feedback--error";
 return;
 }

 // 2. Verificar cache
 var cached = await CpfCache.get(cpf);
 if (cached && cached.success) {
 feedback.textContent = "CPF válido (cache) - " + cached.data.name;
 feedback.className = "cpf-feedback cpf-feedback--cached";
 return;
 }

 // 3. Se online, consultar API
 if (ConnectionMonitor.getStatus()) {
 feedback.textContent = "Validando online...";
 feedback.className = "cpf-feedback";

 var controller = new AbortController();
 var timeoutId = setTimeout(function () {
 controller.abort();
 }, 10000);

 try {
 var res = await fetch("https://api.cpfhub.io/cpf/" + cpf, {
 headers: {
 "x-api-key": "SUA_CHAVE_DE_API",
 Accept: "application/json",
 },
 signal: controller.signal,
 });
 clearTimeout(timeoutId);
 var data = await res.json();

 if (data.success) {
 feedback.textContent = "CPF válido - " + data.data.name;
 feedback.className = "cpf-feedback cpf-feedback--api";
 await CpfCache.set(cpf, data);
 } else {
 feedback.textContent = "CPF não encontrado.";
 feedback.className = "cpf-feedback cpf-feedback--error";
 }
 return;
 } catch (err) {
 clearTimeout(timeoutId);
 // Falha de rede -- tratar como offline
 }
 }

 // 4. Modo offline: validação local + fila
 feedback.textContent =
 "CPF com formato válido (verificação offline). Será validado quando a conexão retornar.";
 feedback.className = "cpf-feedback cpf-feedback--local";

 SyncQueue.add(cpf, {});
 updateSyncInfo();
}

function updateSyncInfo() {
 var count = SyncQueue.getPendingCount();
 if (count > 0) {
 syncInfo.textContent =
 count +
 (count === 1
 ? " CPF aguardando validação online."
 : " CPFs aguardando validação online.");
 syncInfo.className = "sync-info sync-info--visible";
 } else {
 syncInfo.className = "sync-info";
 }
}

async function syncPending() {
 var pending = SyncQueue.getPendingCount();
 if (pending === 0) return;

 syncInfo.textContent = "Sincronizando " + pending + " CPF(s)...";
 await SyncQueue.processQueue();
 updateSyncInfo();
}

// Limpar cache antigo periodicamente
CpfCache.cleanup();
```

---

## Service Worker para cache de assets

Para que a página inteira funcione offline, registre um Service Worker:

```javascript
// sw.js
var CACHE_NAME = "cpf-form-v1";
var ASSETS = [
 "/",
 "/index.html",
 "/css/style.css",
 "/js/app.js",
];

self.addEventListener("install", function (event) {
 event.waitUntil(
 caches.open(CACHE_NAME).then(function (cache) {
 return cache.addAll(ASSETS);
 })
 );
});

self.addEventListener("fetch", function (event) {
 // Não cachear chamadas de API
 if (event.request.url.includes("api.cpfhub.io")) {
 return;
 }

 event.respondWith(
 caches.match(event.request).then(function (response) {
 return response || fetch(event.request);
 })
 );
});
```

```javascript
// Registro no HTML principal
if ("serviceWorker" in navigator) {
 navigator.serviceWorker.register("/sw.js");
}
```

---

## Perguntas frequentes

### O que é necessário para implementar validação de CPF neste contexto?
A validação de CPF exige uma chamada à API com o número do documento e a chave de autenticação. A CPFHub.io retorna o status do CPF, nome do titular e data de nascimento em menos de 200ms, permitindo a verificação em tempo real durante o cadastro ou transação.

### A API CPFHub.io funciona para todos os volumes de consulta?
Sim. O plano gratuito oferece 50 consultas por mês sem cartão de crédito — ideal para testes e projetos pequenos. Para volumes maiores, o plano Pro inclui 1.000 consultas mensais por R$149. Se o limite for ultrapassado, a API não bloqueia: cobra R$0,15 por consulta adicional.

### Como garantir conformidade com a LGPD ao usar uma API de CPF?
Use o CPF apenas para a finalidade declarada ao titular, armazene apenas o necessário (não guarde o CPF cru se um token bastar), implemente controle de acesso aos logs de consulta e documente a base legal para o tratamento. A [ANPD](https://www.gov.br/anpd) orienta que dados de identificação devem ser tratados com o princípio da necessidade.

### Quanto tempo leva para integrar a API CPFHub.io?
A integração básica leva menos de 30 minutos: crie uma conta em cpfhub.io, gere a API key no painel e faça uma chamada GET para `https://api.cpfhub.io/cpf/{CPF}` com o header `x-api-key`. A documentação inclui exemplos em Python, Node.js, PHP, Java e outras linguagens.

### Leia também

- [Como validar CPF no frontend com React e API REST](https://cpfhub.io/blog/como-validar-cpf-no-frontend-com-react-e-api-rest)
- [SLA de API de CPF: níveis de disponibilidade e o que exigir do seu provedor](https://cpfhub.io/blog/sla-api-cpf-niveis-disponibilidade)
- [API de CPF grátis para desenvolvedores: como começar em 5 minutos](https://cpfhub.io/blog/api-cpf-gratis-desenvolvedores-comecar-5-minutos)
- [10 erros mais comuns ao integrar uma API de CPF e como evitá-los](https://cpfhub.io/blog/10-erros-mais-comuns-ao-integrar-uma-api-de-cpf)

---

## Conclusão

Um campo de CPF offline-first garante que a coleta de dados nunca seja interrompida por problemas de conexão. Com validação local de formato, cache via IndexedDB, fila de sincronização e indicadores claros de estado, o usuário sabe exatamente o que esperar em cada situação. Quando a conexão está disponível, a API do [**CPFHub.io**](https://www.cpfhub.io/) confirma os dados cadastrais completos — nome, gênero e data de nascimento — em cerca de 900ms.

A API da CPFHub.io oferece respostas em ~900ms, uptime de 99,9% e conformidade com a LGPD. O plano gratuito com 50 consultas mensais é ideal para equipes que trabalham em campo e precisam validar CPFs de forma intermitente.

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito — e comece hoje mesmo.

