{"slug": "how-i-compile-react-shaped-tsx-without-react-or-hydration", "title": "How I compile React-shaped TSX without React or hydration", "summary": "A developer created Kudzu, an experimental HTML-first TSX framework that compiles React-shaped TSX into static HTML and capability-specific JavaScript, eliminating the need for React, a virtual DOM, or hydration on the client. The framework executes function components during the build and ships only the JavaScript required for each route's actual behavior.", "body_md": "I started building Kudzu while making static websites with AI.\n\nAI coding tools have become very good at producing React-shaped TSX, and I have become used to reviewing code in that form. Function components, props, JSX, and event handlers are often easier for me to understand and verify than scattered DOM queries and imperative JavaScript mutations.\n\nBut I was still building static pages.\n\nI wanted to keep TSX as the authoring and code-review format without automatically shipping React, a virtual DOM, hydration, or a browser-side component tree.\n\nKudzu grew from that idea:\n\nWrite familiar TSX, execute components during the build, and ship ordinary HTML with only the JavaScript each route actually needs.\n\nKudzu is an experimental, HTML-first TSX framework.\n\nWebsite: [kudzujs.cloud](https://kudzujs.cloud/)\n\nGitHub: [github.com/kudzujs/kudzu](https://github.com/kudzujs/kudzu)\n\nConsider a blog, documentation site, newsletter, or product landing page.\n\nMost of the page is already known during the build:\n\nTSX is a convenient way to author and review that structure.\n\n```\nfunction PostCard({ title, description, href }: {\n  title: string\n  description: string\n  href: string\n}) {\n  return (\n    <article>\n      <h2><a href={href}>{title}</a></h2>\n      <p>{description}</p>\n    </article>\n  )\n}\n```\n\nThe component model is useful for authoring, but that does not necessarily mean the browser needs a component runtime.\n\nFor a static page, I wanted the output to remain ordinary HTML.\n\n```\n<article>\n  <h2><a href=\"/posts/hello\">Hello</a></h2>\n  <p>My first article.</p>\n</article>\n```\n\nI also wanted interactive pages to receive only the JavaScript required for their actual behavior.\n\nKudzu treats components as build-time authoring units.\n\n```\nReact-shaped TSX\n        ↓\nKudzu compiler\n        ↓\nStatic HTML + CSS + capability-specific ESM\n```\n\nFunction components execute during the build.\n\nThe browser does not receive:\n\nA completely static route receives no client JavaScript.\n\nA Kudzu page lives in `src/pages`\n\n.\n\n```\n// src/pages/index.tsx\n\nexport default function HomePage() {\n  return (\n    <main>\n      <h1>Hello from Kudzu</h1>\n      <p>This page becomes static HTML.</p>\n    </main>\n  )\n}\n```\n\nBuild the project:\n\n```\nnpm run build\n```\n\nThe output is written to `dist`\n\n.\n\n```\ndist/\n├─ index.html\n└─ assets/\n   └─ style.css\n```\n\nBecause the page has no browser behavior, Kudzu does not add a JavaScript runtime.\n\nKudzu supports a React-shaped `useState`\n\nAPI.\n\n``` js\nimport { useState } from \"@kudzujs/core\"\n\nexport default function Counter() {\n  const [count, setCount] = useState(0)\n\n  return (\n    <button onClick={() => setCount(count + 1)}>\n      Count: {count}\n    </button>\n  )\n}\n```\n\nThe compiler identifies:\n\nThe component is executed during the build to produce the initial HTML.\n\nThe browser receives a small behavior plan that updates the relevant DOM node directly.\n\n```\nclick\n  → update logical state\n  → queue a DOM commit\n  → patch the bound text node\n```\n\nThe browser does not rerun the component.\n\nKudzu deliberately differs from React's state snapshot behavior.\n\n```\nfunction increaseTwice() {\n  setCount(count + 1)\n  setCount(count + 1)\n}\n```\n\nIn Kudzu:\n\nThe handler above increments the value by two and patches the DOM once.\n\nThis model gives event handlers normal sequential JavaScript behavior without requiring a browser component rerender.\n\nKudzu has two event compilation paths.\n\nSimple setter-only handlers compile to compact behavior commands.\n\n``` js\n<button onClick={() => setCount(count + 1)}>\n  Increase\n</button>\n```\n\nNormal synchronous or asynchronous handlers compile to external ESM.\n\n``` js\nasync function loadStatus() {\n  setStatus(\"loading\")\n\n  try {\n    const response = await fetch(\"/api/status\")\n    const result = await response.json()\n    setStatus(result.status)\n  } catch {\n    setStatus(\"failed\")\n  }\n}\n```\n\nKudzu does not use:\n\n`eval`\n\n;`new Function`\n\n;Relative TypeScript helpers can also be bundled into the generated handler module.\n\n``` js\nimport { normalizeStatus } from \"../lib/status\"\n\nasync function loadStatus() {\n  const response = await fetch(\"/api/status\")\n  const result = await response.json()\n\n  setStatus(normalizeStatus(result))\n}\n```\n\nState can be used in text, attributes, styles, and form properties.\n\n```\nconst [open, setOpen] = useState(false)\n\nreturn (\n  <button\n    className={open ? \"active\" : \"idle\"}\n    aria-expanded={open}\n    onClick={() => setOpen(!open)}\n  >\n    {open ? \"Close\" : \"Open\"}\n  </button>\n)\n```\n\nKudzu compiles the expressions into direct DOM updates.\n\n```\nopen changes\n  → patch class\n  → patch aria-expanded\n  → patch text\n```\n\nThe entire component is not rerendered.\n\nInline conditional children compile to bounded DOM ranges.\n\n```\nconst [open, setOpen] = useState(false)\n\nreturn (\n  <>\n    <button onClick={() => setOpen(!open)}>\n      Toggle menu\n    </button>\n\n    {open && (\n      <nav>\n        <a href=\"/docs\">Docs</a>\n        <a href=\"/example\">Examples</a>\n      </nav>\n    )}\n  </>\n)\n```\n\nBoth branches are prepared during the build. The browser mounts or removes only the relevant DOM range.\n\nThere is still no virtual DOM or component tree.\n\nKudzu also supports a constrained form of keyed list rendering.\n\n```\nconst [items, setItems] = useState([\n  { id: 1, name: \"Oak\" },\n  { id: 2, name: \"Pine\" }\n])\n\nreturn (\n  <ul>\n    {items.map(item => (\n      <li key={item.id}>\n        {item.name}\n        <button\n          onClick={() =>\n            setItems(items.filter(entry => entry.id !== item.id))\n          }\n        >\n          Remove\n        </button>\n      </li>\n    ))}\n  </ul>\n)\n```\n\nThe initial list is emitted as static HTML.\n\nWhen state changes, Kudzu moves, inserts, updates, or removes the keyed DOM nodes directly.\n\nExisting keyed nodes are preserved instead of remounted.\n\nAsync components can load data during the build.\n\n``` js\nexport default async function ProductsPage() {\n  const products = await fetch(\n    \"https://example.com/api/products\"\n  ).then(response => response.json())\n\n  return (\n    <main>\n      <h1>Products</h1>\n\n      <ul>\n        {products.map(product => (\n          <li>{product.name}</li>\n        ))}\n      </ul>\n    </main>\n  )\n}\n```\n\nWhen the route and data are available during the build, the result is complete static HTML.\n\nNo browser fetch or hydration is required.\n\nBuild-known dynamic routes use `getStaticPaths()`\n\n.\n\n```\n// src/pages/posts/[slug].tsx\n\nexport function getStaticPaths() {\n  return [\n    {\n      params: { slug: \"hello-kudzu\" },\n      props: { title: \"Hello Kudzu\" }\n    },\n    {\n      params: { slug: \"static-html\" },\n      props: { title: \"Static HTML\" }\n    }\n  ]\n}\n\nexport default function Post({\n  title\n}: {\n  title: string\n}) {\n  return <h1>{title}</h1>\n}\n```\n\nThe build produces:\n\n```\ndist/posts/hello-kudzu/index.html\ndist/posts/static-html/index.html\n```\n\nThe result can be deployed to an ordinary static host.\n\nSometimes data or browser APIs are only available after the document loads.\n\nKudzu supports a deliberately constrained `useEffect`\n\nform.\n\n``` js\nimport { useEffect, useState } from \"@kudzujs/core\"\n\nexport default function StatusPage() {\n  const [status, setStatus] = useState(\"loading\")\n\n  useEffect(() => {\n    fetch(\"/api/status\")\n      .then(response => response.json())\n      .then(result => setStatus(result.status))\n  }, [])\n\n  return <p>Status: {status}</p>\n}\n```\n\nThe effect is not executed during static rendering.\n\nKudzu emits a route-specific effect entry that updates the existing logical state and bound DOM nodes.\n\nThe component itself is not shipped or rerun.\n\nKudzu does not give every interactive route the same runtime.\n\nA route receives only the capabilities it uses.\n\n```\nStatic route          → HTML only\nState commands        → compact behavior runtime\nReactive bindings     → binding capability\nConditional DOM       → bounded DOM operations\nKeyed lists           → list capability\nNormal handlers       → external handler ESM\nEffects               → route-specific effect entry\nRuntime parameters    → route-specific pathname reader\n```\n\nA static route remains JavaScript-free even if another route in the same project is interactive.\n\nKudzu writes deployable artifacts to `dist`\n\n.\n\nA minimal Cloudflare configuration looks like this:\n\n```\n{\n  \"name\": \"my-kudzu-site\",\n  \"compatibility_date\": \"2026-07-20\",\n  \"assets\": {\n    \"directory\": \"dist\"\n  }\n}\n```\n\nBuild and deploy:\n\n```\nnpm run build\nnpx wrangler deploy\n```\n\nThe Kudzu website itself is deployed using Cloudflare static assets.\n\nThe project also uses build-time generation for:\n\nVanilla JavaScript is not the problem.\n\nFor small behavior, it is often the smallest and best browser output.\n\nThe problem I was trying to solve was authoring and review.\n\nWhen working with AI-generated code, I found this form easier to inspect:\n\n```\n<PostCard\n  title={post.title}\n  description={post.description}\n  href={`/posts/${post.slug}`}\n/>\n```\n\nthan a collection of unrelated operations:\n\n``` js\nconst article = document.createElement(\"article\")\nconst heading = document.createElement(\"h2\")\nconst link = document.createElement(\"a\")\n\nlink.href = `/posts/${post.slug}`\nlink.textContent = post.title\nheading.append(link)\narticle.append(heading)\n```\n\nThe browser may ultimately need ordinary DOM operations.\n\nI just did not want to author and review the entire site at that level.\n\nKudzu uses TSX as the input language and ordinary HTML and DOM operations as the output.\n\nKudzu is not a drop-in React replacement.\n\nIt supports a statically analyzable React-shaped subset.\n\nUnsupported patterns fail during the build instead of silently causing Kudzu to ship a general component runtime.\n\nKudzu currently does not provide:\n\nThe goal is not to run every React application unchanged.\n\nThe goal is to preserve familiar TSX authoring where it can be compiled safely into static HTML and direct browser behavior.\n\nKudzu is experimental, and the supported TSX surface may still change.\n\nThe current implementation includes:\n\nI am especially interested in feedback about:\n\nCreate a project:\n\n```\nnpm create kudzu@latest my-app\ncd my-app\nnpm run dev\n```\n\nBuild the static output:\n\n```\nnpm run build\n```\n\nThe generated files are available in:\n\n```\ndist/\n```\n\nThe build execution plan is available in:\n\n```\n.kudzu/kudzu-plan.json\n```\n\nWebsite: [https://kudzujs.cloud/](https://kudzujs.cloud/)\n\nExamples: [https://kudzujs.cloud/example](https://kudzujs.cloud/example)\n\nDocumentation: [https://kudzujs.cloud/docs](https://kudzujs.cloud/docs)\n\nGitHub: [https://github.com/kudzujs/kudzu](https://github.com/kudzujs/kudzu)", "url": "https://wpnews.pro/news/how-i-compile-react-shaped-tsx-without-react-or-hydration", "canonical_source": "https://dev.to/bunzzeok/how-i-compile-react-shaped-tsx-without-react-or-hydration-59k0", "published_at": "2026-07-25 12:23:04+00:00", "updated_at": "2026-07-25 12:33:11.371375+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence"], "entities": ["Kudzu", "React"], "alternates": {"html": "https://wpnews.pro/news/how-i-compile-react-shaped-tsx-without-react-or-hydration", "markdown": "https://wpnews.pro/news/how-i-compile-react-shaped-tsx-without-react-or-hydration.md", "text": "https://wpnews.pro/news/how-i-compile-react-shaped-tsx-without-react-or-hydration.txt", "jsonld": "https://wpnews.pro/news/how-i-compile-react-shaped-tsx-without-react-or-hydration.jsonld"}}