Solta
Guides

Updated

Why a Claude React artifact shows a blank page outside Claude

By

In Claude, the window around a React file does four jobs the file doesn't do itself: it compiles the JSX, supplies the packages the imports name, mounts the component and loads Tailwind. Saved as a file and opened in a browser, or put on a host that serves files as they are, none of that happens, and what you usually get is a white page.

Read the console first

Open the page, then the browser's console. In Chrome on a Mac that is View, then Developer, then JavaScript Console, or Cmd+Option+J; on Windows, Ctrl+Shift+J. The first red line says which of these it is:

1. The browser can't read JSX

A line like return <div className="p-8">…</div> is JSX, not JavaScript. Something has to compile it into function calls before a browser runs it. In a plain <script> tag, the browser can't parse the script past the first < it doesn't expect, so none of the script runs. Saved as a .jsx file and opened directly, it doesn't run at all: the browser shows the code as text, or downloads it.

The fix is a compile step: a bundler such as Vite or esbuild, before the page goes anywhere. For a quick test, Babel can compile it in the browser with <script type="text/babel">, which still leaves the imports in the next section to solve.

2. The imports point nowhere

Claude's React files start with lines like these:

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

In Claude, the window provides those packages. A browser has no idea what react is. In a module script, a bare name like that is an error until something maps it to an address; in a classic script, import itself is a syntax error. The @/ in the third line is a path alias from a project's configuration, and the Card it names is a shadcn/ui component, a file that doesn't exist outside Claude until you add it.

The fix is a bundler that installs the packages from npm, or an import map that tells the browser where each one is:

<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>

Every package that uses React has to share the page's one copy of it, which is what ?external=react asks esm.sh for. With two copies, the page fails with Invalid hook call.

3. Nothing mounts the component

Claude's file ends with export default function App() { … }. That defines a component; it doesn't put it on the page. In Claude, the window does that step. On its own, the file defines a function that nothing calls, which isn't an error, so the console stays quiet and the page stays empty.

A page needs an element to render into, and one call that renders it:

<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>

Here App.js is the compiled file, not the JSX, and it has to come from a web address (section 5). Older tutorials use ReactDOM.render instead: React 18 deprecated it and React 19 removed it, so on a current React it fails with a TypeError.

4. Tailwind's classes do nothing

Claude styles its React files with Tailwind classes, such as className="p-8 rounded-xl bg-slate-900 text-white". In Claude, the window includes Tailwind's styles. A plain page doesn't, so the classes are only names.

This one is rarely completely blank: the content is there, unstyled, stacked down the left edge. It looks blank when the text was meant to sit on a dark background and is now white on white, or when the layout relies on classes like h-screen and flex to give anything a size.

The fix is Tailwind itself. In a build, it scans your files for class names and writes the CSS they use. For a quick test there is Tailwind's Play CDN, a script that does the same in the browser, which Tailwind's documentation describes as meant for development, not production.

5. The page was opened from your disk

Double-click an HTML file and it opens at a file:/// address. A page there has no web origin of its own, and Chrome and Edge refuse some of what it asks for:

  • A module script from another file, <script type="module" src="app.js">. Module scripts are fetched under CORS rules, which a file:/// page can't satisfy.
  • Babel's <script type="text/babel" src="App.jsx">, because Babel fetches that file itself and is refused the same way.
  • fetch() of a JSON or CSV file sitting next to the page.

Code written inside the page still runs, and imports from https addresses that allow it, such as esm.sh, still load. The fix is to serve the folder from a local web server and open it at http://localhost on the port the server uses: 3000 for the first of these, 8000 for the second. Run either one in the folder:

npx serve
python3 -m http.server

Or skip all five

Paste the file into Solta, in the box below. When you publish, Solta compiles the JSX, points each import at esm.sh, with a pinned version for the packages these files usually import, serves the shadcn/ui components from its own copy, compiles the Tailwind classes the file uses, and mounts the default export into a page. The page is served at an https address, so the file:/// rules never come into it.

If the file has no export default and doesn't mount itself, Solta won't publish it, and says what to add. If the component throws while it renders, the page shows the error instead of a white page. What it can't fix is a package esm.sh doesn't have, or anything that needs a server: what does not work. A page that asks Claude something while people use it needs that call changed too: how a site calls a model.

Try it with your own file

No account needed. Paste it or drop it, and the link is yours to keep once you claim it.

Or publish it from the chat

Add Solta to Claude or ChatGPT once, then ask in any conversation to publish what it built. The link comes back in the chat.