Cómo servir archivos de audio desde Cloudflare R2 con tu propio dominio y sin errores CORS

Cuando desarrollas una aplicación web que utiliza archivos de audio almacenados en Cloudflare R2, es común encontrarse con problemas relacionados con CORS (Cross-Origin Resource Sharing). En este artículo explicamos cómo configurar correctamente un subdominio personalizado para servir archivos públicos desde R2 a través de un Worker de Cloudflare, evitando bloqueos del navegador y asegurando una integración limpia en tu frontend.

El problema: error CORS al cargar archivos de audio desde R2

Supongamos que tienes una aplicación llamada MyVoiceApp alojada en https://myvoiceapp.com. Los archivos de audio generados por la app se almacenan en un bucket público de R2. Al intentar cargarlos desde el frontend, por ejemplo usando fetch o un reproductor como wavesurfer.js, el navegador lanza un error como este:

Access to fetch at 'https://pub-XXXX.r2.dev/archivo.mp3' from origin 'https://myvoiceapp.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.

Esto ocurre porque los enlaces públicos directos de R2 (r2.dev) no incluyen encabezados CORS, por lo que el navegador bloquea el acceso desde otros orígenes.

La solución: servir los archivos desde tu propio subdominio con un Worker

La forma recomendada de resolver este problema es la siguiente:

  1. Crear un Cloudflare Worker que actúe como proxy y devuelva los archivos desde R2 con los encabezados CORS adecuados.

  2. Crear un subdominio personalizado (por ejemplo media.myvoiceapp.com) que apunte al Worker.

  3. Configurar tu aplicación para que utilice las nuevas URLs personalizadas.

Paso 1: Crear el Worker

Desde el panel de Cloudflare:

  1. Ve a la sección Workers & Pages.

  2. Crea un nuevo Worker llamado, por ejemplo, audio-proxy.

  3. En el editor, añade el siguiente código:

export default {
async fetch(request, env, ctx) {
const url = new URL(request.url)
const key = url.pathname.slice(1)
const object = await env.AUDIO_BUCKET.get(key)

if (!object) {
return new Response(«Archivo no encontrado», { status: 404 })
}

return new Response(object.body, {
headers: {
«Content-Type»: object.httpMetadata?.contentType || «audio/mpeg»,
«Access-Control-Allow-Origin»: «*»,
«Access-Control-Allow-Methods»: «GET»,
«Access-Control-Allow-Headers»: «*»
}
})
}
}

  1. Enlaza el Worker al bucket R2 en la sección de Bindings:

    • Tipo: R2 bucket

    • Nombre de variable: AUDIO_BUCKET

    • Bucket: selecciona tu bucket (por ejemplo, audio-files)

Guarda y despliega el Worker.

Paso 2: Configurar el subdominio

  1. En la zona DNS de myvoiceapp.com, añade un registro:

    • Tipo: CNAME

    • Nombre: media

    • Valor: <tu-worker>.workers.dev

    • Proxy activado (nube naranja)

  2. Vuelve al panel del Worker → Domains & RoutesCustom domain.

  3. Añade media.myvoiceapp.com.

  4. Cloudflare detectará el CNAME y lo enlazará automáticamente al Worker.

Paso 3: Usar las nuevas URLs

A partir de ahora, en lugar de usar:

https://pub-XXXX.r2.dev/archivo.mp3

debes usar:

https://media.myvoiceapp.com/archivo.mp3

Estas nuevas URLs sí incluyen los encabezados CORS, por lo que puedes usarlas sin problema en fetch, <audio>, o reproductores como wavesurfer.js.

Conclusión

Usar Cloudflare Workers para servir archivos desde R2 es una solución potente, segura y personalizable. Evita errores CORS, mejora la imagen de tu marca (al usar un subdominio propio) y te permite tener control total sobre cómo se entregan tus archivos estáticos.

Con esta arquitectura, puedes seguir usando R2 como almacenamiento económico y escalable, mientras ofreces una experiencia profesional y sin bloqueos desde tu frontend.