Técnico 22 de abr. de 2026 · 8 min de lectura

Moderación en el edge: Cloudflare Workers + ToxicFilter

Pon un proxy de moderación ligero en el edge. Menos latencia, caídas que afectan a menos cosas y una integración más sencilla en el cliente.

Eduardo Lázaro
Eduardo Lázaro
Fundador de ToxicFilter
Moderación en el edge: Cloudflare Workers + ToxicFilter

Moderar en el edge es un patrón más elegante de lo que parece: la llamada de moderación ocurre físicamente cerca del usuario, el cliente nunca habla directamente con el proveedor de moderación y tu origen solo ve contenido limpio. También es una forma estupenda de bajar la latencia, reducir la superficie de ataque y simplificar el SDK del cliente.

En este artículo vemos paso a paso un Cloudflare Worker colocado delante de ToxicFilter. El mismo patrón funciona con Vercel Edge Functions, Deno Deploy y AWS Lambda@Edge con pequeños ajustes.

¿Por qué moderar en el edge?

  • Latencia. El worker se ejecuta en cientos de ciudades. Un usuario en São Paulo se conecta a un worker en São Paulo, y las llamadas que hace después a la API de moderación y a tu origen viajan por la red de Cloudflare en lugar de por la conexión del usuario.
  • Seguridad. Tu clave de la API de ToxicFilter se guarda en los Worker Secrets, nunca en el cliente. Si un atacante descompila tu aplicación móvil, no encuentra claves.
  • Límites de peticiones y caché. El edge es el sitio natural para ambas cosas. Usa Cloudflare KV o Durable Objects para cuotas por usuario, y la Cache API integrada para cachear la moderación por hash del contenido.
  • Clientes más simples. Tus clientes de iOS, Android y web hablan con una sola URL y un solo esquema de autenticación: el tuyo.

Un Worker mínimo

// wrangler.toml
name = "moderation-edge"
main = "src/index.ts"
compatibility_date = "2026-01-01"

[vars]
ORIGIN_URL = "https://api.yourapp.com"

# Secrets (set with `wrangler secret put`):
# TOXICFILTER_API_KEY

// src/index.ts

export default {
    async fetch(request: Request, env: Env): Promise<Response> {
        const url = new URL(request.url);

        if (url.pathname !== '/comments' || request.method !== 'POST') {
            return fetch(request);
        }

        const body = await request.clone().json<{ text: string }>();

        // Per-user rate limit
        const userId = request.headers.get('X-User-Id') ?? 'anon';
        const rateOk = await checkRate(env, userId);
        if (!rateOk) return new Response('Too Many Requests', { status: 429 });

        // Content-hash cache
        const hash = await sha256(body.text);
        const cached = await env.CACHE.get(`mod:${hash}`);
        if (cached) {
            const { blocked } = JSON.parse(cached);
            if (blocked) return new Response('Blocked', { status: 422 });
            return fetch(request);
        }

        // Call moderation
        const modRes = await fetch('https://toxicfilter.com/api/v1/text', {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${env.TOXICFILTER_API_KEY}`,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ content: body.text, surface: 'comment' }),
        });
        const mod = await modRes.json<ModResult>();
        const blocked = mod.decision === 'block';

        await env.CACHE.put(
            `mod:${hash}`,
            JSON.stringify({ blocked }),
            { expirationTtl: 3600 }
        );

        if (blocked) {
            return new Response(
                JSON.stringify({ error: 'blocked', reason: mod.signals[0]?.reason }),
                { status: 422, headers: { 'Content-Type': 'application/json' } }
            );
        }

        // Forward the original request to your origin
        return fetch(request);
    }
};

Qué hace el Worker, paso a paso

  1. Enruta solo lo que necesita moderación. El tráfico que no son comentarios pasa tal cual.
  2. Aplica un límite de peticiones por usuario en el KV local del edge. Sin ida y vuelta a la base de datos.
  3. Consulta una caché por hash del contenido. Los mensajes duplicados (que son frecuentes) se saltan por completo la llamada al modelo.
  4. Llama a ToxicFilter con un secreto que el cliente nunca ve.
  5. Cachea la decisión durante una hora. Vigila este TTL, porque no te interesa servir decisiones antiguas sobre contenido que se ha editado.
  6. Devuelve un error claro si se bloquea, o reenvía al origen si se aprueba.

Lo que aprendimos a base de golpes

1. Los arranques en frío no son un mito

Incluso los runtimes de edge "basados en isolates" tienen arranques en frío en regiones que estaban inactivas. Cuenta con 5 a 15 ms para la primera petición tras la inactividad. Si te mueves por debajo de los 50 ms, esto importa.

2. Peticiones en streaming

Para subidas de imágenes, no puedes hacer clone() de un cuerpo de petición grande en memoria. Usa un enfoque en streaming y calcula el hash sobre la marcha, o recibe las subidas en el edge solo cuando sean pequeñas.

3. La observabilidad es distinta

Lo que tu APM habitual te daba de serie, aquí no lo tienes. Usa el logging de la plataforma de edge (Cloudflare Logpush, Vercel Observability) o envía los eventos a un SaaS de forma explícita. "En staging funcionaba" dice poco cuando cada petición se ejecuta en un sitio distinto.

4. Fail-open frente a fail-closed en el edge

¿Qué pasa cuando ToxicFilter deja de responder un momento desde esa región del edge? Te toca la misma decisión que en el artículo sobre límites de peticiones, solo que ahora está en código JavaScript que corre en la infraestructura de Cloudflare y no en tu propio stack. Decídelo de forma explícita; no te quedes con lo que venga por defecto.

Cuándo moderar en el edge es la opción equivocada

  • Contenido multimedia pesado. Moderar vídeo, audio largo o imágenes de alta resolución suele requerir el propio pipeline de ingesta del proveedor de moderación, no un proxy en el edge.
  • Residencia de datos regulada. Si el contenido de tus usuarios no puede salir nunca de una región concreta, asegúrate de que tanto tu proveedor de edge como tu proveedor de moderación permiten fijar la región.
  • Aplicaciones solo internas. Si de todos modos todo está detrás de una VPN, pierdes la mayoría de las ventajas del edge.

Para todo lo demás (chat público, comentarios, reseñas, perfiles) el patrón del edge sale ganando sin hacer ruido: más rápido, más seguro, más sencillo. Las mismas cincuenta líneas de código del Worker suelen sobrevivir a tres versiones de tu monolito.

Pruébalo con tu tráfico real

Permitir, revisar o bloquear, y el motivo explicado. En el plan gratuito: 2.000 créditos al mes, sin tarjeta. Una comprobación cuesta 1 crédito, unos 8 si la lee el modelo y unos 10 por imagen.