Uppy 6.0 y las subidas de archivos reanudables
Configura cargas reanudables con Uppy y Tus, conoce los cambios de Uppy 6.0 en la firma S3 y los paquetes core, y revisa los pasos clave para migrar desde la versión 5.
Uppy es un componente de subida de archivos en JavaScript de código abierto. Combinado con su plugin @uppy/tus, reanuda las subidas de archivos grandes tras una caída de la conexión o el cierre de una pestaña, de modo que el usuario no tenga que volver a enviar el archivo desde el principio.
La mayoría de los equipos recurren a una librería tras recibir el mismo informe de error: alguien conectado al Wi-Fi de un hotel llegó al 90 % de un vídeo de 2 GB, la conexión se cayó y la subida volvió a cero. Si creaste tu propio componente de subida siguiendo nuestras guías sobre cómo crear una zona de arrastre para subir archivos o subida de archivos personalizada en React, este es el punto en el que ese enfoque deja de ser suficiente.
Este artículo explica por qué importan las subidas reanudables, cómo hacer una configuración mínima de Uppy, qué ha cambiado en Uppy 6.0 y cuánto trabajo supone actualizar desde la versión 5.x.
Puntos clave
- Tus es un protocolo abierto, basado en HTTP, que hace que las subidas sean reanudables. Una conexión caída o una pestaña cerrada no hacen perder el progreso: la subida continúa desde el último byte que recibió el servidor.
- Uppy 6.0 reescribe
@uppy/aws-s3en torno a tres modos de firma (getCredentials,signRequestycompanionEndpoint), y cada configuración utiliza exactamente uno de ellos. - En Uppy 6.0,
@uppy/utils,@uppy/store-default,@uppy/companion-clienty@uppy/provider-viewsse distribuyen ahora dentro de@uppy/corecomo exportaciones de subruta (subpath exports). - Los proyectos que usan el metapaquete
uppyo el bundle del CDN necesitan pocos cambios de código, o ninguno, para migrar a la versión 6.0. - Si alojas Companion en tu propia infraestructura, debes actualizarlo a Companion 7 antes de que el cliente pase a
@uppy/core6, porque los tokens OAuth viajan ahora a través del WebSocket de Companion. Además, Companion 7 funciona sobre Express 5.
¿Por qué usar Uppy en lugar de un componente de subida hecho a mano?
Un componente de subida hecho a mano, basado en un único POST con fetch o XMLHttpRequest, no puede retomar la subida donde la dejó. Cuando la conexión se cae, se pierden todos los bytes ya enviados. Tus resuelve esto a nivel de protocolo: el cliente pregunta al servidor cuánto ha recibido ya y después envía solo el resto. La reproducción de sesiones (session replay) deja muy a la vista el coste de no contar con esto: tras una subida fallida, puedes ver cómo el usuario vuelve al selector de archivos y elige de nuevo el mismo archivo grande.
Las subidas reanudables con Tus sí requieren un componente equivalente en el servidor. La documentación de @uppy/tus recomienda tusd, y los servidores Tus pueden almacenar los archivos en local o en un almacenamiento externo como S3.
Configuración mínima de Uppy con Dashboard y Tus
Tres paquetes bastan para tener un componente de subida reanudable completo: @uppy/core gestiona el estado, @uppy/dashboard proporciona la interfaz y @uppy/tus se encarga de la transferencia.
import Uppy from '@uppy/core';
import Dashboard from '@uppy/dashboard';
import Tus from '@uppy/tus';
import '@uppy/core/css/style.min.css';
import '@uppy/dashboard/css/style.min.css';
new Uppy()
.use(Dashboard, { inline: true, target: 'body' })
// Point this at your own tus server, e.g. a tusd instance
.use(Tus, { endpoint: 'https://your-tus-server/files/' });
inline: true renderiza el Dashboard directamente dentro del elemento de destino en lugar de abrirlo como un modal. Si prefieres crear tu propia interfaz de progreso, nuestro artículo sobre cómo crear una barra de progreso de subida explica los eventos subyacentes.
Las fuentes remotas, como Google Drive, pasan por Companion, el componente de servidor de Uppy. Companion obtiene el archivo en el servidor, de modo que los bytes nunca pasan por el navegador del usuario. Aquí hay un detalle importante: la opción chunkSize de @uppy/tus solo se aplica a los archivos enviados desde el dispositivo del usuario. Las subidas que pasan por Companion siguen la propia opción chunkSize de Companion, así que configura ambas si necesitas limitar el tamaño de cada petición.
Por defecto, @uppy/tus reintenta un fragmento (chunk) fallido tras una serie de esperas y limita cuántas subidas se ejecutan al mismo tiempo. Cuando el servidor devuelve un HTTP 429, su lógica onShouldRetry predeterminada pausa todas las peticiones en cola y espera antes de volver a intentarlo, recorriendo los intervalos definidos en retryDelays.
Novedades de Uppy 6.0
Uppy 6.0 trae tres cambios principales: @uppy/aws-s3 se ha reescrito desde cero, cuatro paquetes de soporte se han fusionado en @uppy/core y se ha eliminado @uppy/instagram. Según el artículo de lanzamiento de Uppy 6.0, seis paquetes han recibido un salto de versión mayor: @uppy/core, @uppy/companion, @uppy/aws-s3, @uppy/tus, @uppy/components y uppy.
En Uppy 5, el antiguo plugin @uppy/aws-s3 tenía hasta ocho callbacks, y los que necesitabas variaban según tu configuración: subida multiparte o simple, con Companion o sin él, firmando en el navegador o en un servidor. La reescritura sustituye todo eso por tres modos de firma, de los que eliges exactamente uno:
| Modo | Dónde se realiza la firma | ¿Requiere Companion? |
|---|---|---|
getCredentials | En el navegador (SigV4), con credenciales temporales obtenidas de tu endpoint | No |
signRequest | Tu propio firmante | No |
companionEndpoint | Companion | Sí |
import Uppy from '@uppy/core';
import AwsS3 from '@uppy/aws-s3';
new Uppy().use(AwsS3, {
// Swap this for an R2, MinIO or Spaces endpoint
s3Endpoint: 'https://my-bucket.s3.eu-west-1.amazonaws.com',
region: 'eu-west-1',
getCredentials: async () => fetch('/s3-credentials').then((r) => r.json()),
});
Companion ya no es necesario para subir archivos a S3. Con getCredentials, el propio navegador firma cada petición, y el plugin funciona con cualquier servicio compatible con S3, incluidos Cloudflare R2, MinIO y DigitalOcean Spaces. La referencia de @uppy/aws-s3 enumera los campos que debe devolver getCredentials, así que, en el ejemplo anterior, tu endpoint /s3-credentials tiene que devolver exactamente esa estructura. El artículo de lanzamiento también indica que la reescritura ha corregido errores antiguos relacionados con el backoff de reintentos, la caducidad de credenciales, la detección del modo sin conexión y la pausa o reanudación de subidas multiparte. Las pruebas de CI del plugin se ejecutan ahora contra un servidor MinIO real en lugar de mocks.
¿Cómo actualizar de Uppy 5 a Uppy 6?
Si usas el metapaquete uppy o el bundle del CDN, la actualización a la versión 6.0 requiere pocos cambios de código, o ninguno. En cualquier otro caso, revisa esta lista; la guía oficial de migración contiene todos los detalles:
- Paquetes individuales: cambia las importaciones de
@uppy/utils,@uppy/store-default,@uppy/companion-clienty@uppy/provider-viewsa sus subrutas en@uppy/core. Los paquetes antiguos están marcados como obsoletos (deprecated) en npm. - Companion autoalojado: actualiza primero a Companion 7 y después pasa el cliente a
@uppy/core6. Companion 7 sigue siendo compatible con clientes de Uppy más antiguos, pero un cliente de Uppy 6 no funcionará con Companion 6 o versiones anteriores. Companion 7 está construido sobre Express 5 y no funciona como middleware en una aplicación con Express 4, así que migra tu aplicación a Express 5 antes de hacer el cambio.companion.socket()necesita ahoracompanionOptionscomo segundo argumento. Los usuarios de Companion alojado no tienen que hacer nada. - Manejadores de errores de Tus: revisa de nuevo tu código para
upload-error, porque ahoraresponseviene rellenado.
- import { fetcher } from '@uppy/utils'
+ import { fetcher } from '@uppy/core/utils'
- import { RequestClient } from '@uppy/companion-client'
+ import { RequestClient } from '@uppy/core/companion-client'
CompanionClientProvider y CompanionClientSearchProvider también han desaparecido. En su lugar, importa Provider desde @uppy/core/companion-client. La fusión existe para evitar un fallo concreto: un lockfile que mantenía una copia antigua de @uppy/utils junto a una versión más reciente de @uppy/core, lo que dejaba dos copias del mismo paquete en un mismo árbol de dependencias.
Desde Uppy 6.0, @uppy/tus pasa el código de estado y el cuerpo de la respuesta del servidor al evento upload-error y a file.response. Las versiones anteriores emitían el error sin la respuesta del servidor.
uppy.on('upload-error', (file, error, response) => {
if (error.isNetworkError) return showOfflineNotice(); // placeholder helper
console.log(response?.status);
console.log(response?.body.xhr.responseText);
});
Seguirás obteniendo undefined en response en dos casos: cuando el servidor no llegó a responder (por ejemplo, si no se puede acceder a él) o cuando el archivo era remoto y se subió a través de Companion.
Uso de Uppy con React y otros frameworks
Los wrappers de Uppy para frameworks permiten que los componentes se suscriban al estado de Uppy sin tener que conectar los eventos a mano. En React, el hook useUppyState de @uppy/react recibe una instancia de Uppy y un selector, y vuelve a renderizar el componente cuando cambia la porción de estado seleccionada. La documentación de la integración con React describe la API completa.
import { useState } from 'react';
import Uppy from '@uppy/core';
import Tus from '@uppy/tus';
import { useUppyState } from '@uppy/react';
function UploadStatus() {
const [uppy] = useState(() =>
new Uppy().use(Tus, { endpoint: 'https://your-tus-server/files/' }),
);
const files = useUppyState(uppy, (state) => state.files);
const progress = useUppyState(uppy, (state) => state.totalProgress);
return <p>{Object.keys(files).length} files, {progress}% uploaded</p>;
}
La inicialización diferida (lazy) de useState crea la instancia de Uppy una sola vez por componente en lugar de en cada renderizado. Uppy también dispone de wrappers para Vue, Svelte y Angular.
Cuándo no usar Uppy
Un único archivo pequeño, como el avatar de un perfil, no necesita Uppy ni Tus. Basta con un POST de formulario normal a tu propio endpoint, y Tus añadiría una integración de servidor que luego tendrías que mantener. Si más adelante quieres la interfaz de Uppy pero conservando un endpoint HTTP convencional, @uppy/xhr-upload cubre ese caso: puede enviar una subida estándar mediante formulario multipart o una petición PUT. Nuestra guía sobre la subida de archivos con Multer en Node muestra la parte del servidor que recibe los archivos.
Conclusión
Las subidas reanudables son la razón por la que merece la pena añadir Uppy. Uppy 6.0 simplifica su uso, con un único plugin de S3 que tiene un solo modo de firma por configuración y un único paquete core en lugar de cinco que había que mantener sincronizados. Para empezar, instala @uppy/core, @uppy/dashboard y @uppy/tus, apunta el plugin de Tus a una instancia de tusd y sube un archivo grande con la red limitada (throttling). Si ya usas la versión 5.x, empieza por los cambios en Companion y en upload-error, ya que son los que pueden romper el código existente.
Preguntas frecuentes
¿Qué debe devolver la función getCredentials en @uppy/aws-s3?
Devuelve un objeto con una propiedad credentials que contiene las credenciales temporales en camelCase (accessKeyId, secretAccessKey y sessionToken), además de un campo expiration opcional, y una propiedad region para la que firmar. AWS STS devuelve estas claves en PascalCase, así que primero debes mapearlas. Uppy almacena el resultado en caché durante todo el ciclo de vida del plugin. Ante un error ExpiredToken o InvalidAccessKeyId, obtiene nuevas credenciales y reintenta una vez. Restringe al máximo la política asociada, porque es el navegador quien guarda estas credenciales.
¿Qué opciones de @uppy/aws-s3 se eliminaron en Uppy 6.0? ¿Sigue funcionando shouldUseMultipart?
shouldUseMultipart sigue funcionando en @uppy/aws-s3 6.0, al igual que getChunkSize, allowedMetaFields y limit. Las opciones eliminadas son endpoint, headers, cookiesRule, getTemporarySecurityCredentials, getUploadParameters, signPart, createMultipartUpload, listParts, abortMultipartUpload, completeMultipartUpload, uploadPartBytes y retryDelays. Las configuraciones existentes con Companion se migran renombrando endpoint a companionEndpoint. headers y cookiesRule no tienen un sustituto directo: las cookies del mismo origen siguen llegando a Companion, pero los bearer tokens o las cookies entre sitios requieren otra configuración, como un proxy de autenticación delante de Companion.
¿Cuál es la diferencia entre las subidas reanudables con Tus y @uppy/golden-retriever?
Tus permite que el servidor conserve los bytes que ya ha recibido para que la transferencia continúe, mientras que Golden Retriever restaura en el navegador la selección de archivos y el estado de Uppy tras un fallo o el cierre de una pestaña. En Uppy 6.0, Golden Retriever almacena por defecto en IndexedDB los metadatos, el estado y los archivos locales de hasta 10 MiB, y recurre a localStorage para los metadatos como alternativa. Los archivos más grandes necesitan el almacén opcional basado en Service Worker, que sobrevive al cierre de pestañas pero no al reinicio del navegador.
¿Puede un cliente de Uppy 6 conectarse a un servidor Companion más antiguo?
No. Un cliente de Uppy 6 necesita Companion 7, porque los tokens OAuth viajan ahora a través de la conexión WebSocket de Companion en lugar de window.opener. Companion 7 mantiene postMessage como alternativa para los clientes de Uppy anteriores a la 6.0, así que despliega primero Companion 7 y después actualiza el cliente. En el servidor, pasa a companion.socket() el mismo objeto de opciones que pasas a companion.app(). Los usuarios de Companion alojado no necesitan hacer cambios.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k