Actualizado el

Por qué un artefacto React de Claude se ve en blanco fuera de Claude

Por

En Claude, la ventana alrededor de un archivo React hace cuatro trabajos que el archivo no hace solo: convierte el JSX, pone los paquetes que nombran los imports, monta el componente y carga Tailwind. Guardado como archivo y abierto en un navegador, o subido a un alojamiento que sirve los archivos tal como son, nada de eso pasa, y lo que sueles ver es una página en blanco.

Pega aquí el archivo React que se ve en blanco

Primero, lee la consola

Abre la página, y luego la consola del navegador. En Chrome en una Mac, es el menú View, luego Developer, luego JavaScript Console, con esos nombres en Chrome en inglés, o Cmd+Option+J en cualquier idioma; en Windows, Ctrl+Shift+J. La primera línea roja dice cuál de estos casos es:

1. El navegador no puede leer JSX

Una línea como return <div className="p-8">…</div> es JSX, no JavaScript. Algo tiene que convertirla en llamadas a funciones antes de que un navegador la ejecute. En una etiqueta <script> normal, el navegador no puede leer el script más allá del primer < que no espera, así que no se ejecuta nada del script. Guardado como archivo .jsx y abierto directamente, ni siquiera se ejecuta: el navegador muestra el código como texto, o lo descarga.

La solución es un paso de conversión: un bundler como Vite o esbuild, antes de que la página vaya a ningún lado. Para una prueba rápida, Babel puede convertir el JSX en el navegador con <script type="text/babel">, lo que todavía deja por resolver los imports de la siguiente sección.

2. Los imports no apuntan a ningún lado

Los archivos React de Claude empiezan con líneas como estas:

import { useState } from "react";
import { LineChart, Line } from "recharts";
import { Card } from "@/components/ui/card";

En Claude, la ventana pone esos paquetes. Un navegador no tiene idea de qué es react. En un script de módulo, un nombre suelto como ese es un error hasta que algo lo relaciona con una dirección; en un script clásico, el propio import es un error de sintaxis. El @/ de la tercera línea es un alias de ruta de la configuración de un proyecto, y el Card que nombra es un componente de shadcn/ui, un archivo que no existe fuera de Claude hasta que lo agregas.

La solución es un bundler que instale los paquetes desde npm, o un import map que le diga al navegador dónde está cada uno:

<script type="importmap">
{
  "imports": {
    "react": "https://esm.sh/react@18.3.1",
    "react/jsx-runtime": "https://esm.sh/react@18.3.1/jsx-runtime",
    "react-dom/client": "https://esm.sh/react-dom@18.3.1/client?external=react"
  }
}
</script>

Todos los paquetes que usan React tienen que compartir la única copia que tiene la página, y eso es lo que ?external=react le pide a esm.sh. Con dos copias, la página falla con Invalid hook call.

3. Nada monta el componente

El archivo de Claude termina con export default function App() { … }. Eso define un componente, pero no lo pone en la página. En Claude, ese paso lo hace la ventana. Solo, el archivo define una función que nada llama, lo que no es un error, así que la consola se queda callada y la página se queda vacía.

Una página necesita un elemento donde dibujar el componente, y una llamada que lo dibuje:

<div id="root"></div>
<script type="module">
  import { createElement } from "react";
  import { createRoot } from "react-dom/client";
  import App from "./App.js";

  createRoot(document.getElementById("root"))
    .render(createElement(App));
</script>

Aquí App.js es el archivo ya convertido, no el JSX, y tiene que venir de una dirección web (sección 5). Los tutoriales más antiguos usan ReactDOM.render en su lugar: React 18 lo declaró obsoleto y React 19 lo quitó, así que en un React actual falla con un TypeError.

4. Las clases de Tailwind no hacen nada

Claude da estilo a sus archivos React con clases de Tailwind, como className="p-8 rounded-xl bg-slate-900 text-white". En Claude, la ventana incluye los estilos de Tailwind. Una página normal no, así que las clases son solo nombres.

Este caso casi nunca queda completamente en blanco: el contenido está ahí, sin estilos, apilado contra el borde izquierdo. Parece en blanco cuando el texto debía ir sobre un fondo oscuro y ahora es blanco sobre blanco, o cuando el diseño depende de clases como h-screen y flex para darle tamaño a algo.

La solución es Tailwind mismo. En un build, busca nombres de clases en tus archivos y escribe el CSS que usan. Para una prueba rápida existe el Play CDN de Tailwind, un script que hace lo mismo en el navegador, y que la documentación de Tailwind describe como pensado para desarrollo, no para producción.

5. La página se abrió desde tu disco

Haz doble clic en un archivo HTML y se abre en una dirección file:///. Una página ahí no tiene un origen web propio, y Chrome y Edge rechazan parte de lo que pide:

  • Un script de módulo de otro archivo, <script type="module" src="app.js">. Los scripts de módulo se descargan bajo las reglas de CORS, que una página file:/// no puede cumplir.
  • El <script type="text/babel" src="App.jsx"> de Babel, porque Babel descarga ese archivo por su cuenta y se le rechaza de la misma manera.
  • Un fetch() de un archivo JSON o CSV que está al lado de la página.

El código escrito dentro de la página sigue ejecutándose, y los imports desde direcciones https que lo permiten, como esm.sh, siguen cargando. La solución es servir la carpeta desde un servidor web local y abrirla en http://localhost, en el puerto que use el servidor: 3000 para el primero de estos, 8000 para el segundo. Ejecuta cualquiera de los dos en la carpeta:

npx serve
python3 -m http.server

O sáltate los cinco

Pega el archivo en Solta, en la caja de arriba. Cuando publicas, Solta convierte el JSX, apunta cada import a esm.sh, con una versión fija para los paquetes que estos archivos suelen importar, sirve los componentes de shadcn/ui desde su propia copia, genera el CSS de las clases de Tailwind que usa el archivo, y monta el export default en una página. La página se sirve en una dirección https, así que las reglas de file:/// nunca entran en juego.

Si el archivo no tiene export default y no se monta solo, Solta no lo publica, y te dice qué agregar. Si el componente da un error mientras se dibuja, la página muestra el error en vez de una página en blanco. Lo que no puede arreglar es un paquete que esm.sh no tiene, o cualquier cosa que necesite un servidor: lo que no funciona. Una página que le pregunta algo a Claude mientras la gente la usa también necesita cambiar esa llamada: cómo un sitio llama a un modelo.

Preguntas

¿Por qué mi archivo React de Claude muestra una página en blanco?

Porque en Claude la ventana a su alrededor convierte el JSX, pone los paquetes que nombran los imports, monta el componente y carga Tailwind. Abierto directamente en un navegador, o en un alojamiento que sirve los archivos tal como son, nada de eso pasa.

¿Qué significa Unexpected token '<' en la consola?

Que el navegador encontró JSX, que no es JavaScript. Hay que convertirlo antes, con un bundler como Vite o esbuild, o, para una prueba rápida, con Babel en el navegador.

¿Cómo arreglo Failed to resolve module specifier "react"?

Con un bundler que instale los paquetes desde npm, o con un import map que le diga al navegador dónde está cada paquete, por ejemplo en esm.sh. Todos los paquetes que usan React tienen que compartir la misma copia, o la página falla con Invalid hook call.

¿Por qué la página queda vacía y la consola no muestra ningún error?

Porque nada monta el componente: el archivo define una función que nada llama, lo que no es un error. La página necesita un elemento donde dibujar y una llamada a createRoot que dibuje el componente ahí.

¿Por qué la página no funciona cuando abro el archivo con doble clic?

Se abre en una dirección file:///, y Chrome y Edge rechazan un script de módulo de otro archivo y un fetch() de un archivo de al lado. Sirve la carpeta desde un servidor local, con npx serve o python3 -m http.server, y ábrela en http://localhost.

Publica directamente desde tu chat de IA

Conecta Solta con Claude o ChatGPT y pídelo: el sitio queda en línea con un enlace, sin copiar código.