Cache e CDN são fundamentais para performance:
Cache local no dispositivo do usuário.
# Cache por 1 ano (recursos estáticos)
Cache-Control: public, max-age=31536000, immutable
# Cache por 1 hora (conteúdo dinâmico)
Cache-Control: public, max-age=3600
# Sem cache (conteúdo sempre atualizado)
Cache-Control: no-cache, no-store, must-revalidate
# ETag - hash do conteúdo
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
# Last-Modified - data de modificação
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
Cache programático no navegador.
// Service Worker - Cache First
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// Retorna do cache se disponível
if (response) {
return response;
}
// Senão, busca da rede
return fetch(event.request).then((response) => {
// Adiciona ao cache
const responseClone = response.clone();
caches.open('v1').then((cache) => {
cache.put(event.request, responseClone);
});
return response;
});
})
);
});
// Cache First (para recursos estáticos)
async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) return cached;
const response = await fetch(request);
const cache = await caches.open('v1');
cache.put(request, response.clone());
return response;
}
// Network First (para conteúdo dinâmico)
async function networkFirst(request) {
try {
const response = await fetch(request);
const cache = await caches.open('v1');
cache.put(request, response.clone());
return response;
} catch (error) {
return await caches.match(request);
}
}
// Stale While Revalidate
async function staleWhileRevalidate(request) {
const cache = await caches.open('v1');
const cached = await cache.match(request);
const fetchPromise = fetch(request).then((response) => {
cache.put(request, response.clone());
return response;
});
return cached || fetchPromise;
}
Cache intermediário (proxies, CDN).
# Cache público compartilhado
Cache-Control: public, max-age=3600
# Cache privado (apenas navegador)
Cache-Control: private, max-age=3600
# Revalidação obrigatória
Cache-Control: must-revalidate
Rede de servidores distribuídos geograficamente que servem conteúdo estático.
<!-- Recursos estáticos via CDN -->
<link rel="stylesheet" href="https://cdn.example.com/css/app.css">
<script src="https://cdn.example.com/js/app.js"></script>
<img src="https://cdn.example.com/images/hero.jpg" alt="Hero">
# HTML - revalidação frequente
Cache-Control: no-cache, must-revalidate
# CSS/JS - cache longo com versionamento
Cache-Control: public, max-age=31536000, immutable
# Com hash no nome do arquivo
app.a1b2c3d4.js
# Imagens - cache longo
Cache-Control: public, max-age=31536000
# Com versionamento ou hash
image-v2.jpg
image.abc123.jpg
# APIs - cache curto com revalidação
Cache-Control: private, max-age=300, must-revalidate
// Build gera arquivos com hash
app.a1b2c3d4.js
styles.b5c6d7e8.css
<!-- HTML referencia arquivos versionados -->
<script src="/js/app.a1b2c3d4.js"></script>
<link rel="stylesheet" href="/css/styles.b5c6d7e8.css">
<!-- Menos ideal, mas funcional -->
<script src="/js/app.js?v=1.2.3"></script>
<link rel="stylesheet" href="/css/styles.css?v=1.2.3">
// Detectar atualização do Service Worker
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v2').then((cache) => {
return cache.addAll([
'/',
'/css/app.css',
'/js/app.js'
]);
})
);
self.skipWaiting(); // Ativa imediatamente
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== 'v2') {
return caches.delete(cacheName);
}
})
);
})
);
});
HTTP/1.1 200 OK
Content-Type: text/css
Cache-Control: public, max-age=31536000, immutable
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: no-cache, must-revalidate
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
// Cloudflare Workers - edge computing
addEventListener('fetch', (event) => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
// Lógica no edge
const response = await fetch(request);
return response;
}
// Service Worker completo
const CACHE_NAME = 'app-v1';
const urlsToCache = [
'/',
'/css/app.css',
'/js/app.js',
'/images/logo.png'
];
// Install - cache inicial
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache);
})
);
});
// Activate - limpar caches antigos
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});
// Fetch - estratégia de cache
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// Cache hit
if (response) {
return response;
}
// Cache miss - buscar da rede
return fetch(event.request).then((response) => {
// Verificar se resposta é válida
if (!response || response.status !== 200) {
return response;
}
// Clonar resposta para cache
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
});
})
);
});