React & Next.js

JSX — what it actually compiles to

JSX looks like HTML sitting inside JavaScript, which makes it easy to assume it's some kind of template syntax React parses at runtime. It isn't — it's compiled away entirely before your code ever runs, into plain function calls.

Beginner

3 min read

JSX is not valid JavaScript on its own — it needs a compiler

const element = <h1 className="title">Hello, world!</h1>;

Browsers don't understand this syntax at all — <h1> isn't valid inside a .js file as far as JavaScript's own grammar is concerned. A build tool (Babel, or the TypeScript compiler with JSX support, or the compiler built into Next.js/Vite) transforms every JSX expression into plain JavaScript before the code ever reaches a browser or Node — this is the exact same "compile-time transformation, nothing survives at runtime" relationship the TypeScript domain covers for type annotations, just transforming syntax instead of erasing types.

What it actually compiles to: nested function calls

const element = <h1 className="title">Hello, world!</h1>;
// What the above actually becomes after compilation
const element = React.createElement("h1", { className: "title" }, "Hello, world!");

React.createElement(type, props, ...children) is a plain JavaScript function. <h1 className="title">Hello, world!</h1> is nothing more than syntax sugar for calling it with "h1" as the element type, { className: "title" } as its props, and "Hello, world!" as its child content. This is the single fact that demystifies most of what looks like JSX "magic" — nested JSX just becomes nested createElement calls, each one describing one node in the tree React will eventually reconcile against the real DOM.

{}: the one escape hatch back into real JavaScript

const name = "Ada";
const items = ["a", "b", "c"];
 
const element = (
  <div>
    <p>Hello, {name}!</p>
    <p>You have {items.length} items</p>
    <p>{1 + 1}</p>
  </div>
);

Curly braces {} inside JSX drop back into plain JavaScript expression evaluation — whatever's inside is evaluated as a real expression, and the resulting value is inserted into the tree. This works for a variable ({name}), a computed value ({items.length}), or any expression at all ({1 + 1}) — the one hard rule is that it must be an expression (something that produces a value), not a statement (like an if block or a for loop, neither of which "returns" anything) — this is exactly why conditional rendering in JSX reaches for the ternary operator or && instead of a plain if, covered in this domain's props lesson.

Every JSX element must have exactly one root

// Error: JSX expressions must have one parent element
function Broken() {
  return (
    <h1>Title</h1>
    <p>Paragraph</p>
  );
}
 
// Fixed: wrapped in a single parent
function Fixed() {
  return (
    <>
      <h1>Title</h1>
      <p>Paragraph</p>
    </>
  );
}

Because JSX compiles to a single createElement(...) call (or, for a component, a single function return value), a component can only return one root element — two sibling elements with no shared parent has no single call to compile down into. <>...</> (a Fragment, shorthand for React.Fragment) exists specifically to satisfy this "exactly one root" requirement without adding a real, unwanted <div> wrapper to the actual rendered DOM — Fragments themselves produce no DOM node at all.

className, not class — and why the difference exists at all

// HTML: <div class="card">
// JSX:
<div className="card">

class is a reserved word in JavaScript (used for actual class declarations), so JSX can't reuse it as a prop name the way HTML uses it as an attribute name — className is the JSX equivalent, which then compiles to setting the real DOM's className property. This is the most common individual "gotcha" for anyone coming from writing plain HTML — a handful of other attributes differ the same way (for becomes htmlFor, for the identical reason: for is also a reserved word), but className is by far the one encountered first and most often.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. What does `<h1 className="title">Hello</h1>` actually compile to?

2. Why must the content inside `{}` in JSX be an expression, not a statement (like an if block)?

3. Why does JSX use `className` instead of `class`?