ADR-0006 · Next.js 16 con React 19 estable, npm como único gestor y updateTag para invalidar caché¶
Este ADR se numeró primero como 0004 en la rama
mvaliente-desa. Al integrarse condevelop, que ya tenía un ADR-0004 y un ADR-0005 distintos, pasó a ser el 0006.
- Estado: aceptado
- Decisores: mvaliente1
- Fecha de la decisión: 2026-08-31
Contexto y problema¶
El storefront corría sobre Next 15 y una release candidate de React
(19.0.0-rc-66855b96-20241106, de noviembre de 2024), fijada a mano y sostenida
con resolutions (types-react@19.0.0-rc.1) y overrides. Un RC clavado a una
fecha no recibe parches: cualquier corrección de React quedaba fuera de alcance
sin tocar ese andamiaje.
Al mismo tiempo, el repositorio arrastraba dos lockfiles. El ADR-0003 lo aceptó
como deuda y ya anotaba la consecuencia: "una dependencia agregada con yarn add
no llega a la imagen hasta que se actualiza package-lock.json: lo que se prueba
en local no es necesariamente lo que se despliega". En los hechos la deuda ya se
había resuelto sola en una dirección: el node_modules del entorno de desarrollo
estaba instalado con npm, no con Yarn, y yarn ni siquiera estaba en el PATH.
Opciones consideradas¶
- Subir sólo React a estable y quedarse en Next 15.
- Subir Next a 16 manteniendo el RC de React.
- Migrar ambos y unificar el gestor de paquetes en npm (elegida).
La opción 2 no es viable: no tiene sentido adoptar un major nuevo sobre una dependencia sin soporte. La 1 posterga el problema sin resolverlo.
Decisión¶
Se migró a Next 16.3.3 + React 19.2.8 estable, en dos etapas verificadas por
separado (primero React sobre Next 15, después Next 16), y se unificó el
repositorio en npm: se eliminó yarn.lock y la clave packageManager.
Los cambios que la migración obligó, más allá de subir versiones:
revalidateTag→updateTag(35 llamadas ensrc/lib/data/cart.tsysrc/lib/data/customer.ts). En Next 16revalidateTag(tag)sin segundo argumento quedó deprecado y su semántica cambió: ahora sirve stale content mientras revalida en segundo plano. La purga inmediata con lectura de los propios cambios pasó a llamarseupdateTag, y sólo puede invocarse dentro de una Server Action. Se verificó que las 35 llamadas cumplen esa condición.- Turbopack como bundler de producción. Es el default de Next 16. El proyecto
no tenía configuración propia de webpack ni de Babel, así que se eliminaron las
dependencias
webpack,babel-loadery@babel/core, que no se usaban. Se fijóturbopack.rootennext.config.jspara que Turbopack no infiera la raíz del proyecto desde unpackage-lock.jsonde un directorio superior. - ESLint 9 con flat config. Next 16 eliminó el comando
next linty dejó de aceptar la claveeslintennext.config.js..eslintrc.jsse reemplazó poreslint.config.mjsy el scriptlintpasó a sereslint .. paramsasíncronos. Cuatro rutas todavía tipabanparamscomo objeto plano; en Next 16 es unaPromise.tsconfig.json: Next 16 exigemoduleResolution: "bundler"yjsx: "react-jsx", y los reescribe solo al construir.- Se eliminó
src/app/auth/google/callback/page.tsx, código muerto que importaba un símbolo inexistente (handleGoogleCallback). Webpack lo dejaba pasar; Turbopack falla el build. El flujo real de OAuth vive ensrc/app/[countryCode]/auth/google/callback/page.tsx.
El bloque overrides se mantiene: @medusajs/ui sigue declarando
peer react ^18.3.1 y sin el override npm degrada React a 18.
Consecuencias¶
Positivas¶
- React y Next en versiones con soporte, parcheables por rango semver.
- Un solo lockfile: se cierra la deuda principal que el ADR-0003 dejó anotada. Lo que se prueba en local es lo que se construye en la imagen.
- Builds notablemente más rápidos con Turbopack (~10 s de compilación).
- Menos andamiaje: sin
resolutions, sinwebpack/babel-loader/@babel/core. - El cambio a
updateTaghace explícita la semántica que el carrito necesitaba desde siempre: leer los propios cambios sin servir contenido viejo.
Negativas / deuda asumida¶
middleware.tsmigrado aproxy.ts(hecho el 2026-09-03). El codemod oficial no corre con el árbol de trabajo sucio, así que se hizo a mano:git mv,export async function middleware→proxy, y dos mensajes de error internos. Verificado:ƒ Proxy (Middleware)en la tabla de rutas, sin warning de deprecación.- ESLint 9 activa reglas nuevas (
react-hooksv6) que antes no existían: 26 errores y 20 warnings preexistentes quedaron al descubierto, la mayoríareact-hooks/exhaustive-depsy@next/next/no-img-element. Ninguno bloquea el build, peronpm run lintno está en verde. - Node 20.18.3 se queda corto para el toolchain de ESLint 9, que pide
^20.19.0 || ^22.13.0 || >=24(npm warn EBADENGINEporeslint-visitor-keys). Next 16 sí funciona (pide>=20.9.0). El destino es Node 24 "Krypton", no 22: Node 20 ya está EOL, 22 está en mantenimiento y muere en abril 2027, y 24 es el Active LTS hasta abril 2028. Verificado el 2026-09-03 en v24.20.0, sobre los dos repos:npm ciynpm run builden verde, y ambos servidores levantados y respondiendo. Con Node 24 elEBADENGINEdesaparece. Falta que el encargado de infra suba elnode:20-alpinede losDockerfile;package.jsonya declara"engines": { "node": ">=24" }y hay.nvmrcen ambos repos. Nota: Node 24 trae npm 11.19, que por defecto ya no ejecuta los scripts de instalación de las dependencias. No rompe el build. - El ADR-0003 sigue vigente en lo que respecta a cómo se construye la imagen; sólo queda sin efecto su premisa de que el repositorio versiona dos lockfiles.
Verificación¶
npm run build en verde (685 páginas estáticas generadas) y prueba manual del
dev server sobre /py, /py/home, /py/store, /py/cart, /py/account,
/py/categories/*, /py/collections/* y fichas de producto: todas 200.
tsc --noEmit pasó de 22 a 21 errores, todos preexistentes al cambio: la
migración no introdujo ninguno. Esos 21 se corrigieron el 2026-09-21 (tarea
272) y con eso se quitó typescript.ignoreBuildErrors del next.config.js:¶
el build vuelve a fallar ante un error de tipos.
Validado a mano sobre Node 24, por ser el punto de mayor riesgo del cambio a
updateTag:
| Server action | Estado |
|---|---|
addToCart |
verificada (2026-09-02 y 2026-09-03) |
setShippingMethod, initiatePaymentSession, placeOrder |
verificadas: orden #120 con transferencia bancaria, redirección a /confirmed y carrito vaciado |
deleteLineItem |
verificada (2026-09-03) |
login, signout |
verificadas (2026-09-03) |
updateLineItem (cambiar cantidad) |
verificada (2026-09-03, cantidad 1 → 4) |
applyPromotions (aplicar promoción) |
pendiente |
updateRegion (cambiar de región) |
pendiente |
Al margen, ESLint 9 destapó dos defectos preexistentes que se corrigieron en el
mismo ciclo: en CreditButton un useCallback quedaba después de un return
temprano, con lo que la cantidad de hooks dependía del precio y React rompía la
ficha de producto al cambiar de variante cruzando el mínimo de crédito; y en
cart-dropdown un itemRef.current en el array de dependencias, que no dispara
re-render y volvía inerte esa dependencia.