Alepha isn't just a backend framework. It's a full-stack framework that treats your frontend as a first-class citizen of your application graph. No more "backend team vs frontend team" drama. Just code.
The @alepha/react package ships separately from the main alepha package. Why? Because not everyone needs React. Some people are still writing jQuery. We don't judge.
Recommended: Use the CLI to scaffold a React-ready project:
npx alepha init --react
This sets up everything:
@alepha/react and peer dependenciesindex.html entry pointmain.browser.ts for client hydrationManual installation:
npm install @alepha/react
Note: You'll need to manually create index.html and configure the browser entry point. But you knew that.
The core module (@alepha/react) provides essential React utilities that work anywhere. Next.js, Expo, your weird custom setup - doesn't matter. No router required.
1import { 2 // Hooks 3 useAlepha, // Access the Alepha instance 4 useInject, // Dependency injection in React 5 useClient, // Type-safe HTTP client 6 useStore, // Global state management 7 useAction, // Async action handler with loading/error/cancel 8 useEvents, // Subscribe to Alepha events 9 10 // Components11 ClientOnly, // Render only on client (skip SSR)12 ErrorBoundary, // Catch and display errors gracefully13 14 // Module15 AlephaReact, // Core module registration16} from "@alepha/react";
useAlepha - Access the FrameworkNeed the Alepha instance? Here you go:
1const MyComponent = () => {2 const alepha = useAlepha();3 4 // Access store, events, services...5 const user = alepha.store.get("current_user");6 7 return <div>Hello, {user?.name}</div>;8};
useInject - Dependency Injection in ReactYour services shouldn't live outside React. Inject them:
1const Dashboard = () => {2 const analytics = useInject(AnalyticsService);3 4 useEffect(() => {5 analytics.trackPageView("dashboard");6 }, []);7 8 return <div>Dashboard</div>;9};
Same DI container, same services, works in your components.
useClient - Type-Safe API CallsCall your backend with full type safety:
1import { useClient } from "@alepha/react"; 2import type { UserController } from "../api/UserController"; 3 4const UserProfile = () => { 5 const client = useClient<UserController>(); 6 const [user, setUser] = useState(null); 7 8 useEffect(() => { 9 client.getUser({ params: { id: "123" } }).then(setUser);10 }, []);11 12 return <div>{user?.name}</div>;13};
No more guessing endpoint URLs or request shapes. TypeScript knows.
useStore - Global StateRead and write global state without Redux boilerplate:
1import { useStore } from "@alepha/react"; 2import { themeAtom } from "./atoms/theme"; 3 4const ThemeToggle = () => { 5 const [theme, setTheme] = useStore(themeAtom); 6 7 return ( 8 <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}> 9 Current: {theme}10 </button>11 );12};
See the State Management guide for the full story.
useAction - Async Operations Done RightHandle async operations with loading states, error handling, and cancellation:
1import { useAction } from "@alepha/react"; 2 3const SaveButton = () => { 4 const [save, { loading, error }] = useAction(async () => { 5 await api.saveDocument(document); 6 }); 7 8 return ( 9 <button onClick={save} disabled={loading}>10 {loading ? "Saving..." : "Save"}11 </button>12 );13};
Features:
ClientOnly - Skip SSRSome code should only run in the browser. LocalStorage, window dimensions, that sort of thing:
1import { ClientOnly } from "@alepha/react"; 2 3const LiveClock = () => { 4 const [time, setTime] = useState(new Date()); 5 6 useEffect(() => { 7 const interval = setInterval(() => setTime(new Date()), 1000); 8 return () => clearInterval(interval); 9 }, []);10 11 return (12 <ClientOnly>13 <span>{time.toLocaleTimeString()}</span>14 </ClientOnly>15 );16};
No hydration mismatches. No cryptic warnings. Just works.
ErrorBoundary - Graceful FailuresCatch errors before they crash your app:
1import { ErrorBoundary } from "@alepha/react";2 3const App = () => (4 <ErrorBoundary fallback={<div>Something went wrong</div>}>5 <Dashboard />6 </ErrorBoundary>7);
If you're using Alepha's DI system without the router, register the core module:
1import { Alepha } from "alepha";2import { AlephaReact } from "@alepha/react";3 4const alepha = Alepha.create().with(AlephaReact);
The router module (@alepha/react/router) is where the SSR magic happens. It gives you file-system-style routing with the power of TypeScript.
Is it required? No. But if you want SSR, you need it. If you're building a SPA, you still probably want it. It's good.
1import { 2 // Primitives 3 $page, // Define routes as page primitives 4 5 // Hooks 6 useRouter, // Navigation and path generation 7 useRouterState, // Current route state 8 useActive, // Active link detection 9 useQueryParams, // Query string access10 11 // Components12 Link, // Client-side navigation link13 NotFound, // 404 handling14 NestedView, // Nested route rendering15 16 // Module17 AlephaReactRouter, // Router module (auto-loads with $page)18} from "@alepha/react/router";
$page PrimitiveIn frameworks like Next.js, you create files in a pages/ directory. In Alepha, you define pages as class properties. Why? Type-safe linking between your backend and frontend.
1import { $page } from "@alepha/react/router"; 2import { t } from "alepha"; 3 4export class AppRouter { 5 home = $page({ 6 path: "/", 7 component: () => <div>Welcome!</div> 8 }); 9 10 dashboard = $page({11 path: "/dashboard",12 schema: {13 query: t.object({14 filter: t.optional(t.text())15 })16 },17 // Server-Side Data Fetching18 loader: async ({ query }) => {19 const stats = await db.stats.get(query.filter);20 return { stats };21 },22 // Props typed automatically from resolve23 component: ({ stats }) => {24 return <div>Stats: {stats.count}</div>25 }26 });27 28 userProfile = $page({29 path: "/users/:id",30 schema: {31 params: t.object({ id: t.text() })32 },33 loader: async ({ params }) => {34 return { user: await db.users.findById(params.id) };35 },36 component: ({ user }) => <UserCard user={user} />37 });38}
If your path has :id, declare it in schema.params. Query params go in schema.query. This isn't optional - it's how you get type safety.
1postDetail = $page({ 2 path: "/posts/:id/:slug", 3 schema: { 4 params: t.object({ 5 id: t.uuid(), 6 slug: t.text(), 7 }), 8 query: t.object({ 9 tab: t.optional(t.enum(["comments", "related"])),10 }),11 },12 loader: async ({ params, query }) => {13 // params.id is string (validated as UUID)14 // params.slug is string15 // query.tab is "comments" | "related" | undefined16 },17 component: ({ /* ... */ }) => { /* ... */ }18});
Without the schema, params and query are unknown. Nobody wants that.
useRouter HookType-safe navigation:
1import { useRouter } from "@alepha/react/router"; 2 3const Navigation = () => { 4 const router = useRouter<AppRouter>(); 5 6 return ( 7 <div> 8 {/* Navigate by page name */} 9 <button onClick={() => router.go("home")}>Home</button>10 <button onClick={() => router.go("dashboard")}>Dashboard</button>11 12 {/* With params */}13 <button onClick={() => router.go("userProfile", { params: { id: "123" } })}>14 View User15 </button>16 17 {/* History */}18 <button onClick={() => router.back()}>Back</button>19 </div>20 );21};
Use router.path() for URLs:
1const UserNav = () => { 2 const router = useRouter<AppRouter>(); 3 4 return ( 5 <nav> 6 <a href={router.path("home")}>Home</a> 7 <a href={router.path("userProfile", { params: { id: "123" } })}> 8 View User 9 </a>10 <a href={router.path("dashboard", { query: { filter: "active" } })}>11 Active Users12 </a>13 </nav>14 );15};
router.anchor()Get both href and onClick for client-side navigation:
1const NavLink = ({ page, children }) => {2 const router = useRouter<AppRouter>();3 4 return (5 <a {...router.anchor(page)}>6 {children}7 </a>8 );9};
useActiveBuild navigation that knows where you are:
1import { useActive } from "@alepha/react/router"; 2 3const NavLink = ({ href, children }) => { 4 const { isActive, isPending, anchorProps } = useActive(href); 5 6 return ( 7 <a 8 {...anchorProps} 9 className={isActive ? "active" : isPending ? "loading" : ""}10 >11 {children}12 </a>13 );14};15 16// With startWith for nested routes17const SidebarLink = ({ href, children }) => {18 const { isActive, anchorProps } = useActive({ href, startWith: true });19 20 // isActive is true for /users, /users/123, /users/settings...21 return (22 <a {...anchorProps} className={isActive ? "active" : ""}>23 {children}24 </a>25 );26};
Access and modify query params:
1const Filters = () => { 2 const router = useRouter<AppRouter>(); 3 4 const { sort, filter } = router.query; 5 6 const setSort = (value: string) => { 7 router.setQueryParams({ ...router.query, sort: value }); 8 }; 9 10 return (11 <select value={sort} onChange={(e) => setSort(e.target.value)}>12 <option value="name">Name</option>13 <option value="date">Date</option>14 </select>15 );16};
Alepha handles Server-Side Rendering:
$pageloader function for dataNo Babel config. No Webpack wrestling. alepha dev and alepha build handle it.
When you use $page in your module's primitives, AlephaReactRouter loads automatically:
1import { $module } from "alepha";2import { $page } from "@alepha/react/router";3 4// No manual .with(AlephaReactRouter) needed5export const MyAppModule = $module({6 name: "my-app",7 primitives: [$page], // Router auto-loads8});
| You want... | Use... |
|---|---|
| Just React utilities (hooks, components) | @alepha/react |
| SSR, routing, pages | @alepha/react/router |
| Works with Next.js/Expo | @alepha/react (core only) |
| Full Alepha experience | @alepha/react/router |
1// Core - works everywhere 2import { useAlepha, useClient, useStore, useAction, ClientOnly } from "@alepha/react"; 3 4// Router - full SSR experience 5import { $page, useRouter, useActive, Link } from "@alepha/react/router"; 6 7// Form - type-safe forms 8import { useForm } from "@alepha/react/form"; 9 10// Head - document head management (requires router)11import { $head, useHead } from "@alepha/react/head";12 13// i18n - internationalization14import { $dictionary, useI18n } from "@alepha/react/i18n";15 16// Auth - authentication (requires router)17import { useAuth } from "@alepha/react/auth";
Next up: State Management | Head Management | Forms | i18n