Your First Project
Build a complete React application with Lunx from scratch. This tutorial covers project creation, routing, API integration, and deploying a production build.
What you'll build
By the end of this tutorial you'll have a fully working React + TypeScript app with:
- Client-side routing via React Router v6
- A data-fetching hook that calls a public REST API
- Tailwind CSS styling with dark mode support
- A production build with SRI hashes and SBOM
Use the Lunx CLI to scaffold a React + TypeScript project with Tailwind CSS.
npm create lunx-dev@latest lunx-demo -- --framework react --ts --tailwind cd lunx-demo && npm install
Add client-side routing. Lunx handles the SPA fallback automatically — no extra config needed.
npm install react-router-domWrap your app in BrowserRouter and define your top-level routes.
// src/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> )
Define page routes and a shared navigation header.
// src/App.tsx import { Routes, Route, Link } from 'react-router-dom' import { HomePage } from '@/pages/HomePage' import { UsersPage } from '@/pages/UsersPage' export default function App() { return ( <div className="min-h-screen bg-gray-950 text-gray-100"> <nav className="border-b border-gray-800 px-6 py-4 flex gap-6"> <Link to="/" className="font-semibold text-white">Home</Link> <Link to="/users" className="text-gray-400 hover:text-white transition">Users</Link> </nav> <main className="p-6"> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/users" element={<UsersPage />} /> </Routes> </main> </div> ) }
Fetch data from the public JSONPlaceholder API and display it with loading and error states.
// src/pages/UsersPage.tsx import { useEffect, useState } from 'react' type User = { id: number; name: string; email: string; company: { name: string } } export function UsersPage() { const [users, setUsers] = useState<User[]>([]) const [loading, setLoading] = useState(true) const [error, setError] = useState<string | null>(null) useEffect(() => { fetch('https://jsonplaceholder.typicode.com/users') .then(r => r.json()) .then(data => { setUsers(data); setLoading(false) }) .catch(e => { setError(e.message); setLoading(false) }) }, []) if (loading) return <p className="text-gray-400">Loading users...</p> if (error) return <p className="text-red-400">Error: {error}</p> return ( <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> {users.map(u => ( <div key={u.id} className="p-4 rounded-xl border border-gray-800 bg-gray-900 hover:border-blue-500/40 transition-all"> <div className="font-semibold text-white">{u.name}</div> <div className="text-sm text-gray-400 mt-1">{u.email}</div> <div className="text-xs text-gray-600 mt-2">{u.company.name}</div> </div> ))} </div> ) }
Start the dev server. Edit any component — changes appear in the browser in ~18ms without losing application state.
npm run dev # ⚡ lunx v1.0.0 adapter: react port: 5173 # ✓ ready in 36ms
Run the production build. Lunx automatically scans for secrets, checks CVEs, generates an SBOM, and injects SRI hashes before writing output.
npm run build 🔍 Security scan — 0 issues 📦 dist/ assets/main-4a2c1.js 112 kB │ gzip: 35 kB assets/style-8d3f2.css 14 kB │ gzip: 4 kB index.html 1.1 kB bom.json 8.4 kB ✓ built in 612ms
Project structure after tutorial
lunx-demo/ ├── src/ │ ├── pages/ │ │ ├── HomePage.tsx │ │ └── UsersPage.tsx │ ├── components/ │ │ └── UserCard.tsx │ ├── hooks/ │ │ └── useUsers.ts │ ├── App.tsx │ ├── main.tsx │ └── index.css ├── dist/ ← production output │ ├── assets/ │ ├── bom.json ← CycloneDX SBOM │ └── index.html ← SRI hashes injected ├── lunx.config.ts └── package.json
dist/ folder to any static host — Vercel, Netlify, Cloudflare Pages, AWS S3, or GitHub Pages. No server required for a standard SPA.