lunx.docs
DocsFrameworksReact

React Adapter

First-class React 18 support with Oxc-powered JSX compilation, React Fast Refresh HMR, automatic code splitting, and zero-config TypeScript.

React 18 · TypeScript · Fast Refresh
Oxc JSX compiler

TypeScript and JSX compiled by Oxc, including React Refresh. No Babel config needed.

React Fast Refresh

Component-level HMR with state preserved. Powered by the official react-refresh runtime.

Auto code splitting

React.lazy() calls automatically create separate chunks loaded on demand.

Zero config TS

TypeScript works out of the box. No tsconfig plugins or extra dependencies.

RSC ready

Experimental React Server Components support via the 'react-rsc' preset.

Strict mode

React.StrictMode is enabled in development by default for early detection of side effects.

Setup

bash
npx lunx-dev@latest create my-react-app --template react --ts
cd my-react-app && npm install && npm run dev
typescriptlunx.config.ts
import { defineConfig } from 'lunx-dev'

export default defineConfig({
  framework: 'react',

  // React-specific options
  react: {
    // JSX runtime: 'automatic' (default) | 'classic'
    jsxRuntime: 'automatic',

    // Enable React Fast Refresh in dev (default: true)
    fastRefresh: true,

    // TypeScript-aware transforms
    typescript: {
      strictNullChecks: true,
    },
  },
})

Project structure

text
my-react-app/
├── src/
│   ├── main.tsx           # ReactDOM.createRoot entry
│   ├── App.tsx            # Root component
│   ├── components/        # Shared UI components
│   ├── pages/             # Route-level components
│   ├── hooks/             # Custom hooks
│   └── index.css          # Global styles
├── index.html
├── lunx.config.ts
└── tsconfig.json

Entry point

typescriptsrc/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
)

TypeScript paths

jsontsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "strict": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Using React Router v6

typescriptsrc/App.tsx
import { Routes, Route, Navigate } from 'react-router-dom'
import { Suspense, lazy } from 'react'
import { Layout } from '@/components/Layout'

// Code-split routes — each becomes a separate chunk
const HomePage    = lazy(() => import('@/pages/HomePage'))
const DashBoard   = lazy(() => import('@/pages/Dashboard'))
const ProfilePage = lazy(() => import('@/pages/Profile'))
const NotFound    = lazy(() => import('@/pages/NotFound'))

export default function App() {
  return (
    <Suspense fallback={<div className="p-8 text-center">Loading...</div>}>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index       element={<HomePage />} />
          <Route path="dash" element={<DashBoard />} />
          <Route path="me"   element={<ProfilePage />} />
          <Route path="*"    element={<NotFound />} />
        </Route>
      </Routes>
    </Suspense>
  )
}

State management with Zustand

bash
npm install zustand
typescriptsrc/stores/useCounter.ts
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface CounterState {
  count: number
  increment: () => void
  decrement: () => void
  reset:     () => void
}

export const useCounter = create<CounterState>()(
  persist(
    (set) => ({
      count:     0,
      increment: () => set(s => ({ count: s.count + 1 })),
      decrement: () => set(s => ({ count: s.count - 1 })),
      reset:     () => set({ count: 0 }),
    }),
    { name: 'counter-storage' }
  )
)

Data fetching with TanStack Query

bash
npm install @tanstack/react-query
typescriptsrc/main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime:  1000 * 60,      // 1 minute
      gcTime:     1000 * 60 * 5,  // 5 minutes
      retry:      2,
    },
  },
})

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </QueryClientProvider>
  </React.StrictMode>
)

React Compiler

Install babel-plugin-react-compiler (with @babel/core) and lunx runs it on your components and hooks in dev and builds: automatic memoization, no config. react: { compiler: false }turns it off; an object passes the compiler's options.

bash
npm install -D babel-plugin-react-compiler @babel/core

React Server Components

An app on @vitejs/plugin-rsc (its rsc, ssr and client environments, as npm create vite -- --template rsc sets up) runs lunx dev, lunx build and lunx preview on lunx's engine: server components render on the server, "use client" components hydrate. Frameworks built on RSC (Waku, React Router RSC) work too.

Storybook and Vitest

bash
lunx exec storybook dev -p 6006
lunx exec vitest run
React DevTools
Install the React DevTools browser extension to inspect component trees, profile renders, and track state changes. With Fast Refresh enabled, component state is preserved between edits — you'll see updates instantly without losing your dev state.