Skip to content

React & Frameworks

Based Page supports React and other modern frameworks with no build step. Import them directly from esm.sh in a <script type="module"> tag and deploy a single HTML file.

React via esm.sh

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My React App</title>
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-950 text-white min-h-screen">
  <div id="root"></div>

  <script type="module">
    import React, { useState } from 'https://esm.sh/react@18'
    import { createRoot } from 'https://esm.sh/react-dom@18/client'

    function Counter() {
      const [count, setCount] = useState(0)
      return React.createElement('div', { className: 'p-8' },
        React.createElement('h1', { className: 'text-2xl font-bold mb-4' }, `Count: ${count}`),
        React.createElement('button', {
          className: 'px-4 py-2 bg-blue-600 rounded',
          onClick: () => setCount(c => c + 1)
        }, 'Increment')
      )
    }

    createRoot(document.getElementById('root')).render(React.createElement(Counter))
  </script>
</body>
</html>

Deploy it:

"Deploy this React counter app at the slug 'react-counter'."

Deployed successfully!

URL: https://react-counter.based.page

JSX Without a Build Step

esm.sh supports HTM — a tagged template literal JSX alternative that works without a transpiler:

html
<script type="module">
  import { h, render } from 'https://esm.sh/preact@10'
  import { useState } from 'https://esm.sh/preact@10/hooks'
  import htm from 'https://esm.sh/htm@3'

  const html = htm.bind(h)

  function App() {
    const [count, setCount] = useState(0)
    return html`
      <div class="p-8">
        <h1 class="text-2xl font-bold mb-4">Count: ${count}</h1>
        <button class="px-4 py-2 bg-blue-600 rounded" onClick=${() => setCount(c => c + 1)}>
          Increment
        </button>
      </div>
    `
  }

  render(html`<${App} />`, document.body)
</script>

Preact (Lighter Alternative)

Preact is a 3KB React-compatible alternative. It works identically but loads faster:

html
<script type="module">
  import { h, render } from 'https://esm.sh/preact@10'
  import { useState, useEffect } from 'https://esm.sh/preact@10/hooks'

  function App() {
    const [data, setData] = useState(null)

    useEffect(() => {
      fetch('https://api.example.com/data')
        .then(r => r.json())
        .then(setData)
    }, [])

    return h('div', { class: 'p-8' },
      data ? h('pre', null, JSON.stringify(data, null, 2)) : h('p', null, 'Loading...')
    )
  }

  render(h(App, null), document.getElementById('root'))
</script>

SPA Routing with React Router

For apps with client-side routing, enable is_spa: true when deploying. This makes all 404 requests fall back to index.html, allowing React Router to handle the path.

For a single-file React app with routing:

html
<script type="module">
  import React from 'https://esm.sh/react@18'
  import { createRoot } from 'https://esm.sh/react-dom@18/client'
  import { BrowserRouter, Routes, Route, Link } from 'https://esm.sh/react-router-dom@6'

  function Home() {
    return React.createElement('div', null, 'Home page')
  }
  function About() {
    return React.createElement('div', null, 'About page')
  }

  function App() {
    return React.createElement(BrowserRouter, null,
      React.createElement('nav', null,
        React.createElement(Link, { to: '/' }, 'Home'),
        ' | ',
        React.createElement(Link, { to: '/about' }, 'About')
      ),
      React.createElement(Routes, null,
        React.createElement(Route, { path: '/', element: React.createElement(Home) }),
        React.createElement(Route, { path: '/about', element: React.createElement(About) })
      )
    )
  }

  createRoot(document.getElementById('root')).render(React.createElement(App))
</script>

Tell Claude to deploy with is_spa: true:

"Deploy this React Router app at 'my-spa'. Enable SPA mode for client-side routing."

Multi-file React App

If you have a pre-built React app (from vite build or create-react-app), use the multi-file deploy format:

deploy called with:
  files: [
    { path: "index.html", contentType: "text/html", content: "..." },
    { path: "assets/index.js", contentType: "application/javascript", content: "..." },
    { path: "assets/index.css", contentType: "text/css", content: "..." }
  ]
  is_spa: true

Useful esm.sh Packages

PackageImport
React 18https://esm.sh/react@18
ReactDOMhttps://esm.sh/react-dom@18/client
Preacthttps://esm.sh/preact@10
Preact hookshttps://esm.sh/preact@10/hooks
HTMhttps://esm.sh/htm@3
React Routerhttps://esm.sh/react-router-dom@6
Zustandhttps://esm.sh/zustand@4
date-fnshttps://esm.sh/date-fns@3
Zodhttps://esm.sh/zod@3
framer-motionhttps://esm.sh/framer-motion@11

Notes

  • No Node.js, no npm install, no bundler — everything runs in the browser.
  • esm.sh automatically resolves dependencies, so react-dom importing react just works.
  • For local development before deploying, any static HTTP server works (e.g. npx serve).
  • is_spa: true is only needed for apps with client-side routing (React Router, etc.). Single-page apps without routing don't need it.

Deploy apps from conversation.