Acerca de este problema HTML
El estándar HTML living define que los scripts con type="module" siempre se obtienen en paralelo y se evalúan después de que el documento haya sido parseado, que es el mismo comportamiento que el atributo defer proporciona para los scripts clásicos. Debido a que esta ejecución aplazada es una característica inherente de los scripts de módulo, la especificación prohíbe explícitamente combinar ambos. Incluir ambos no cambia cómo el navegador maneja el script, pero señala una falta de comprensión de cómo funcionan los módulos y produce HTML inválido.
Este error de validación surge comúnmente cuando los desarrolladores migran scripts clásicos a módulos ES. Un script clásico como <script defer src="app.js"></script> depende del atributo defer para evitar bloquear el parser. Al convertirlo a un módulo añadiendo type="module", es natural dejar defer en su lugar — pero ya no es necesario ni permitido.
Vale la pena señalar que el atributo async sí es válido en scripts de módulo y cambia su comportamiento. Mientras que defer es redundante porque los módulos ya están aplazados, async anula ese comportamiento por defecto y hace que el módulo se ejecute tan pronto como él y sus dependencias hayan terminado de cargarse, en lugar de esperar a que el parseo HTML se complete.
Cómo solucionarlo
Elimina el atributo defer de cualquier elemento <script> que tenga type="module". No se necesitan otros cambios — el comportamiento de carga y ejecución permanecerá idéntico.
Si intencionalmente quieres que el script se ejecute lo antes posible (antes de que se complete el parseo), usa async en lugar de defer. Pero si quieres el comportamiento aplazado estándar, simplemente omite ambos atributos y deja que el comportamiento por defecto del módulo tenga efecto.
Ejemplos
❌ Incorrecto: defer combinado con type="module"
<script type="module" defer src="app.js"></script>
El atributo defer es redundante aquí y causa un error de validación.
✅ Correcto: script de módulo sin defer
<script type="module" src="app.js"></script>
Los scripts de módulo se aplazan automáticamente, por lo que esto se comporta exactamente igual que el ejemplo incorrecto de arriba pero es HTML válido.
✅ Correcto: usando async con un módulo (cuando sea necesario)
<script type="module" async src="analytics.js"></script>
A diferencia de defer, el atributo async está permitido en scripts de módulo. Hace que el módulo se ejecute tan pronto como esté listo, sin esperar a que termine el parseo HTML.
✅ Correcto: script clásico con defer
<script defer src="app.js"></script>
Para scripts clásicos (no de módulo), el atributo defer es válido y necesario si quieres ejecución aplazada.
Encuentra problemas como este automáticamente
Rocket Validator escanea miles de páginas en segundos, detectando problemas de HTML en todo tu sitio web.