Alternativas al registro npm: JSR, privados y espejos
El registro público no es el único destino: JSR y su modelo ESM-first con tipos nativos de TypeScript, los registros privados como Verdaccio o Artifactory para código interno, los espejos de caché que blindan tus builds contra caídas y retiradas del upstream, y cómo elegir el destino de cada paquete con criterio.
Durante años, “publicar un paquete” significó exactamente una cosa: subirlo al registro público de npm. Pero el registro es una capa sustituible, no una ley física. En 2026 conviven JSR, con su apuesta ESM-first y tipos de TypeScript sin build previo; los registros privados que guardan el código que nunca debe salir de la empresa; y los espejos que cachean el upstream para que una caída ajena no pare tu despliegue. Conocer el abanico es dejar de estar atado a un solo destino.
- Entender JSR como registro ESM-first con tipos nativos y su interoperabilidad con clientes npm.
- Situar los registros privados para código interno y cómo se enrutan por scope en
.npmrc. - Comprender el papel de un espejo o proxy de caché frente a caídas y retiradas del upstream.
- Elegir el destino adecuado —público, JSR, privado o espejo— según la naturaleza del paquete.
JSR: el registro ESM-first con tipos nativos
JSR es el registro impulsado por el equipo de Deno, pensado desde cero para el JavaScript moderno. Sus decisiones de diseño son deliberadas y rompedoras:
- Solo ESM: no admite el formato CommonJS heredado, lo que simplifica todo el modelo de módulos.
- TypeScript de origen: sirve el código con tipos nativos y transpila bajo demanda para cada runtime; los tipos nunca se desincronizan de la fuente porque no hay un paso de declaración que mantener.
- Solo con scope: todo paquete es
@scope/nombre, sin el espacio plano y disputado del registro clásico. - Procedencia integrada: firma vía OIDC de serie, más una puntuación de calidad que premia documentación,
exportscorrectos y firma.
Lo decisivo es que JSR no te encierra: interopera con el ecosistema existente.
Añades un paquete de JSR a un proyecto Node con npx jsr add, que escribe en tu package.json una dependencia enrutada al scope reservado @jsr, y el registro sirve un tarball compatible —ya transpilado— para que npm, pnpm o yarn lo instalen sin enterarse de que el origen era TypeScript puro.
Un paquete no necesita package.json: le basta un jsr.json con su name, su version y sus exports. Y el mismo paquete se consume desde cualquier gestor, porque JSR habla el protocolo de todos:
# anade un paquete de JSR desde distintos gestores
npx jsr add @std/encoding # npm
pnpm dlx jsr add @std/encoding # pnpm
bunx jsr add @std/encoding # bun
deno add jsr:@std/encoding # deno, de forma nativa
# publicar a JSR desde el directorio del paquete
npx jsr publish
{
"name": "@acme/parser",
"version": "1.0.0",
"exports": "./mod.ts"
}
Al publicar en JSR no subes solo código: el registro genera la documentación de la API directamente desde tus tipos y tus comentarios, y calcula una puntuación que mide la salud del paquete —si trae procedencia, si documenta sus símbolos, si sus exports son correctos, si funciona en varios runtimes—. Esa presión hacia la calidad es parte del diseño: publicar bien deja de ser un gesto opcional para volverse visible y comparable de un vistazo.
Registros privados
No todo el código debe ser público. Para el software interno de una empresa —librerías compartidas entre equipos, clientes de servicios propios— existen los registros privados.
El abanico va del autohospedado y abierto a lo gestionado de nivel corporativo:
- Verdaccio: ligero, abierto y autohospedado; el punto de entrada más común.
- GitHub Packages y GitLab Package Registry: integrados en tu plataforma de código.
- Artifactory, Nexus, CodeArtifact y Artifact Registry: gestión de artefactos a escala empresarial.
Todas hablan el protocolo de npm, así que el cliente que ya conoces publica e instala contra ellas sin cambios.
El pegamento vuelve a ser el enrutado por scope del nivel anterior: en el .npmrc mapeas el scope interno a la URL del registro privado y su token, mientras el resto de scopes siguen yendo al público. Así, un mismo npm install resuelve @acme/core contra tu servidor interno y react contra el registro público, sin ambigüedad ni configuración por dependencia.
# el scope interno va al registro privado; JSR, a su scope reservado
@acme:registry=https://npm.acme.internal/
@jsr:registry=https://npm.jsr.io/
//npm.acme.internal/:_authToken=${ACME_TOKEN}
En un .npmrc de proyecto, el token de un registro privado no va literal: se interpola desde una variable de entorno y el fichero se versiona sin el secreto. Así el enrutado por scope viaja en el repositorio —cualquiera que clone resuelve los paquetes internos— pero la credencial vive solo en el entorno de CI o del desarrollador, nunca en el historial de git.
Espejos y proxies de caché
La tercera categoría no reemplaza al registro público: lo protege.
Un espejo o proxy de caché —Verdaccio en modo proxy, los repositorios virtuales de Artifactory, Nexus— se sitúa delante del registro público y guarda copia de cada paquete que tu organización descarga. La primera instalación viaja al upstream; las siguientes se sirven desde la caché local.
Más allá de la velocidad, aporta garantías que ninguna caché casual ofrece:
- Resiliencia: si el upstream cae o retiran una versión de la que dependías —el fantasma del incidente que dejó a medio mundo sin compilar—, tu build sigue porque el artefacto ya vive en tu espejo.
- Gobernanza: listas de paquetes permitidos o vetados, y cribado de versiones con vulnerabilidades conocidas antes de que lleguen a tus máquinas.
- Aislamiento: builds reproducibles en entornos sin salida a internet (air-gapped).
flowchart TD DEV[proyecto] --> RC[.npmrc enruta por scope] RC --> PUB[registro publico npm] RC --> JSR[JSR paquetes TypeScript] RC --> PRIV[registro privado interno] PRIV --> PROXY[espejo cachea el upstream] PROXY --> PUB style DEV fill:#89b4fa,color:#11111b style PROXY fill:#fab387,color:#11111b style PUB fill:#a6e3a1,color:#11111b
El espejo deja de ser una simple caché para convertirse en un punto único donde tu organización decide qué del mundo exterior tiene permiso de entrar.
Elegir el destino
Con el abanico completo, la decisión deja de ser un acto reflejo y se vuelve una elección con criterio. La naturaleza del paquete dicta su destino:
JSR
ESM-first y TypeScript de origen con tipos nativos. Interopera con clientes npm vía el scope @jsr. Para lo que nace en TypeScript.
Registro privado
Verdaccio, Artifactory, GitHub Packages. Habla el protocolo npm. Para el código interno que no debe salir de la empresa.
Espejo o proxy
Cachea el registro público delante de tus builds. Para resiliencia, velocidad y gobernanza sobre lo que entra.
Cada perfil de paquete tiene un destino natural:
- Librería pública de propósito general: el registro público, por alcance y familiaridad.
- Paquete que nace en TypeScript: JSR, para servir tipos nativos sin un paso de build.
- Código interno de la empresa: un registro privado, enrutado por scope, que nunca sale al exterior.
- Fiabilidad crítica: un espejo delante del público, para que ninguna caída ajena detenga tus despliegues.
Y no son opciones excluyentes: un mismo proyecto puede consumir del público a través de un espejo, publicar su código interno en un privado y tirar de un paquete de JSR, todo enrutado por scope en el mismo .npmrc.
El destino, en el fondo, se elige por dependencia y no por proyecto: es una propiedad de cada paquete que consumes o publicas, no una decisión monolítica que tomas una vez y arrastras para todo.
La gran lección de cierre de este nivel es aprender a ver la arquitectura por debajo del hábito. Escribiste npm install mil veces creyendo que hablabas con “npm”, pero en realidad hablabas con una interfaz —nombres con scope, enrutado por .npmrc, tarballs servidos por HTTP— que resulta estar implementada, por defecto, por el registro público. Esa interfaz es lo estable; el registro detrás es intercambiable. Una vez que lo ves, el mapa se abre: JSR sustituye el backend por uno ESM-first que sirve TypeScript con tipos nativos, sin renunciar a la compatibilidad con tus clientes de siempre; un registro privado sustituye el destino de un scope concreto por un servidor que tú controlas, para que el código interno nunca salga; y un espejo interpone una caché que blinda tus builds contra caídas y retiradas ajenas, y de paso te da un cortafuegos de gobernanza sobre lo que entra. Ninguna de las tres opciones te obliga a abandonar lo aprendido: todas hablan el mismo protocolo y se enrutan con las mismas herramientas. Esa es la marca de una abstracción bien diseñada —que puedas cambiar la implementación sin reescribir tu forma de trabajar— y saber reconocerla es lo que te permite elegir el destino de cada paquete con criterio: público para lo que compartes, JSR para lo que nace en TypeScript, privado para lo que proteges, y con un espejo delante cuando la fiabilidad no es opcional. Has cerrado el mapa del registro; publicar, versionar, distribuir y firmar ya no son magia. El resto del camino a producción te espera.
- Añade un paquete de JSR a un proyecto Node con
npx jsr adde inspecciona cómo queda enrutado el scope@jsren tu configuración. - Levanta un Verdaccio local, publícale un paquete de práctica y ajusta tu
.npmrcpara instalarlo desde ahí. - Configura el mismo Verdaccio como proxy del registro público y observa cómo cachea una dependencia tras la primera descarga.
- Escribe un
.npmrcque enrute un scope interno a un registro privado y deje el resto en el público. - Para cuatro paquetes imaginarios —uno público, uno TypeScript-first, uno interno y uno crítico— decide su destino y justifícalo.