TL;DR: Most React libraries are incremental improvements on existing solutions. Some, however, break the mold by being laser focused and opinionated, with a focus on developer experience. Need self-hosted auth without vendor lock-in? Complex forms built with JSON schema that non-developers can maintain? Charts that render server-side without bundle bloat? There are production-ready libraries for all of them.
This guide covers 5 essential React libraries for 2026, with real-world implementation examples for each.
What You'll Learn
- How to add enterprise-grade auth without monthly SaaS fees
- Building complex forms that product teams can own and maintain
- Rendering charts server-side to eliminate bundle bloat
- Making URL search params type-safe and server-compatible
- Building LLM agents directly in your Next.js codebase
Prerequisites
- React 18+ or Next.js 14+ project
- Node.js v18+ for all installations here
- 15-30 minutes per library setup
What Makes These Libraries Different?
The React ecosystem in 2026 certainly isn't lacking in tools. In fact, it's actually drowning in them. I picked these five because each of these solves a specific, painful problem that generic solutions can’t handle elegantly, even with the glut of AI agent-assisted coding we have right now.
I had five broad criteria in mind:
- It must solve a specific pain point that developers hit repeatedly -- vendor lock-in, form complexity, bundle size, state management, AI integration.
- It must work with the standard modern React stack seamlessly -- Next.js with RSC, TypeScript, Tailwind, Vercel, PostgreSQL, et al.
- It must provide excellent DX with strong typing, clear documentation, and minimal boilerplate.
- It must scale from prototype to production without major refactoring.
- It must be AI-friendly – a clear internal structure, stable and well-typed APIs, and documentation precise enough that AI coding assistants can extend + integrate it correctly without guessing or hallucinating.
The idea is that you pick the tool that matches your exact problem, integrate it in minutes, and move on to building your actual product.
💡 I'm focusing entirely on production-ready React libraries here, which means some super interesting but still-bleeding-edge libraries (like Signalium for signals and reactive functions) didn't make the cut. The five I’ve included have proven themselves in real-world applications.
Ready to upgrade your React stack for the new year? Let's dive in.
1. SurveyJS: Complex Forms Without Complex Code ⭐
Repository: https://github.com/surveyjs/survey-library Documentation: https://surveyjs.io/form-library/documentation/overview License: MIT (Commercial license available) Free Tier: Completely free for MIT-licensed use
What SurveyJS does:
SurveyJS is the React forms library you reach for when the typical React Hook Form + Zod stack starts becoming an ad-hoc rules engine.
SurveyJS is a schema-driven form engine that treats form definitions as data rather than component logic. Instead of having form structure, validation, conditional logic, and calculations within your JSX and hooks, SurveyJS offloads + centralizes all of it in a JSON schema that acts as the single source of truth.
You can learn more about dynamically creating forms from JSON schemas here.
This approach handles enterprise-grade form complexity - multi-page workflows, branching logic, expression-based calculations, matrix questions, file uploads, e-signatures -- while keeping form logic editable outside of React. React simply becomes the renderer; the form engine owns the rules.
Why SurveyJS is essential:
There's a ceiling to how far component-based forms scale.
For simple CRUD forms, React Hook Form plus a validation schema (via Zod etc.) is fine. But once a form grows into a long-lived workflow -- I will define this as when it needs conditional paths, derived values, regulatory requirements, and frequent non-UI changes -- that component model slows development down to a crawl. Business rules end up scattered across render branches, effects, and validation callbacks. Even tiny logic changes require code edits, redeploys, and careful regression testing.
SurveyJS solves this by making form structure and behavior explicit. By modeling forms as JSON:
- Logic becomes declarative instead of implicit
- Dependencies and calculations are visible and reviewable
- Form definitions can be versioned and audited without ever touching JSX
- Non-developers can safely add or edit workflows through any CMS or admin UI
In practice, this shifts forms in React from being UI implementations to rule systems that just so happen to render as UI - which is exactly what complex, compliance-heavy, or business-critical forms actually are.
Key Features of SurveyJS:
- Schema-first form architecture. Forms are defined as durable JSON schemas -- not component trees -- so they can live in databases, version control, or a CMS. React is reduced to a renderer; the schema is the source of truth.
- Declarative logic and calculations. Conditional visibility, branching workflows, validation, and derived values are expressed directly in the schema via a deterministic rules and expression engine, meaning you'll never have to write ad-hoc conditional rendering logic or keep effect-driven state in React.
- Enterprise-grade inputs and workflows. Supports complex, production-ready input types - multi-page flows, matrices, file uploads, signatures, scoring -- without needing custom components or bespoke validation layers.
- Infrastructure and framework-agnostic. Schemas render consistently across React and other frameworks thanks to SurveyJS's modular architecture, while all form data and submissions remain self-hosted, avoiding vendor lock-in and usage-based pricing.
- AI-agent–ready by design. SurveyJS provides first-party, machine-readable documentation on Context7, allowing any MCP-enabled coding agent (Cursor, Claude Code, GPT Codex, etc.) to integrate and extend forms correctly without relying on guesswork or hallucinated code.
SurveyJS Quick Start:
npm install survey-react-ui
Basic Form Setup:
"use client"
import { Model } from "survey-core"
import { Survey } from "survey-react-ui"
import "survey-core/survey-core.css"
const surveyJson = {
// model your form in data, using a JSON schema
pages: [{ elements: [ { type: "text",
name: "email",
title: "Email address",
isRequired: true,
validators: [{
type: "email"
}]
},
{ type: "dropdown",
name: "company_size",
title: "Company size",
choices: ["1-10", "11-50", "51-200", "201-500", "500+"]
},
{ type: "boolean",
name: "needs_demo",
title: "Would you like a demo?",
visibleIf: "{company_size} = '201-500' or {company_size} = '500+'"
} ]
}]
}
export
function OnboardingForm() {
const survey = new Model(surveyJson) survey.onComplete.add((sender) = >{ console.log(JSON.stringify(sender.data, null, 3))
// Send to your API
}) return <Survey model={survey} />}
Advanced Form that can perform calculations:
const mortgageCalculator = { elements: [ { type: "text",
name: "loan_amount",
title: "Loan Amount ($)",
inputType: "number",
isRequired: true
},
{ type: "text",
name: "interest_rate",
title: "Interest Rate (%)",
inputType: "number",
isRequired: true
},
{ type: "text",
name: "loan_term",
title: "Loan Term (years)",
inputType: "number",
isRequired: true
},
{ type: "expression",
name: "monthly_payment",
title: "Estimated Monthly Payment",
expression: "{loan_amount} * ({interest_rate}/100/12) * pow(1 + {interest_rate}/100/12, {loan_term}*12) / (pow(1 + {interest_rate}/100/12, {loan_term}*12) - 1)",
displayStyle: "currency",
currency: "USD"
} ]
}
Advanced Form with branching logic + business decisions:
// Typical risk assessment form with state, branching, and non-trivial business rules
const riskAssessmentForm = { pages: [ { name: "profile",
elements: [ { type: "dropdown",
name: "industry",
title: "Industry",
choices: ["SaaS", "Finance", "Crypto", "Retail", "Healthcare"],
isRequired: true
},
{ type: "text",
name: "employees",
title: "Number of employees",
inputType: "number",
isRequired: true,
validators: [{
type: "numeric",
minValue: 1
}]
},
{ type: "text",
name: "years_in_business",
title: "Years in business",
inputType: "number",
isRequired: true
} ]
},
{ name: "financials",
visibleIf: "{industry} = 'Finance' or {industry} = 'Crypto'",
elements: [ { type: "text",
name: "annual_volume",
title: "Annual transaction volume ($)",
inputType: "number",
isRequired: true,
validators: [ { type: "expression",
expression: "{annual_volume} >= 0",
text: "Volume must be positive"
} ]
},
{ type: "dropdown",
name: "compliance_certified",
title: "Compliance certifications",
choices: ["SOC 2", "PCI DSS", "ISO 27001", "None"],
visibleIf: "{industry} = 'Finance'"
} ]
},
{ name: "decision",
elements: [ { type: "expression",
name: "risk_score",
expression: "({industry} = 'Crypto' ? 50 : ({industry} = 'Finance' ? 30 : 10)) + ({employees} < 10 ? 20 : 0) + ({years_in_business} < 2 ? 15 : 0) + (notempty({annual_volume}) and {annual_volume} > 1000000 ? 25 : 0)"
},
{ type: "expression",
name: "approval_status",
expression: "{risk_score} < 30 ? 'approved' : ({risk_score} < 60 ? 'manual_review' : 'rejected')"
},
{ type: "html",
visibleIf: "{approval_status} = 'approved'",
html: "<div style='padding: 20px; background: #d4edda; border-radius: 8px;'><h3>✓ Approved automatically</h3><p>Your application meets our risk criteria. Processing will begin immediately.</p></div>"
},
{ type: "html",
visibleIf: "{approval_status} = 'manual_review'",
html: "<div style='padding: 20px; background: #fff3cd; border-radius: 8px;'><h3>⚠ Requires manual review</h3><p>Your application will be reviewed by our team within 2-3 business days.</p></div>"
},
{ type: "html",
visibleIf: "{approval_status} = 'rejected'",
html: "<div style='padding: 20px; background: #f8d7da; border-radius: 8px;'><h3>✗ Application declined</h3><p>Based on our risk assessment, we cannot proceed at this time.</p></div>"
} ]
} ]
}
Usage:
const survey = new Model(riskAssessmentForm) survey.onComplete.add((sender) = >{ const {
risk_score,
approval_status,
...formData
} = sender.data // send to your API with decision outcome console.log({ approval_status, risk_score, formData })})
How to Use SurveyJS
- Replace complex form logic with JSON-driven forms. Use SurveyJS when your forms require conditional branching, calculated fields, multi-page flows, or need to be maintained by non-developers. Store form definitions as JSON in your database or CMS, so your product and marketing teams can iterate without code deployments.
- For enterprise applications with compliance requirements, use SurveyJS to build audit-ready forms with built-in validation, conditional logic, and data export capabilities. All form submissions stay on your infrastructure, meeting data residency and security requirements.
- For research and academic surveys, put SurveyJS's matrix questions, randomization, scoring, and multi-language support to work. Their expression language handles complex calculations without custom JavaScript.
- For calculator forms and estimators, use SurveyJS's expression language to build mortgage calculators, insurance premium estimators, ROI calculators, and other dynamic calculation forms without writing custom math logic. (See calculator form examples and the CalculatedValue API for details.)
Best for: Complex form logic, enterprise applications, research platforms, insurance/finance calculators, compliance-heavy forms, and any scenario where non-technical teams need to own form logic.
| Feature | React Hook Form + Zod | SurveyJS |
|---|---|---|
| Form definition model | Component-driven (JSX + hooks). The source of truth is split across components, hooks, and schemas. | Driven by a single declarative JSON schema that is the source of truth. |
| Conditional / Derived / Calculated fields | Needs custom JS + useEffect | Built-in expression engine |
| Multi-page workflows | Needs manual state & routing | First-class pages and flow control |
| Business rule visibility | Implicit and scattered | Explicit and reviewable |
| Non-developer editable | ❌ No | ✅ Yes (CMS / admin UI friendly) |
| Audit & versioning | ❌ Code diffs only | ✅ Schema diffs, DB / CMS versionable |
| Runtime behavior | Tightly coupled to React | Framework-agnostic form engine |
2. BetterAuth: Auth Without Vendor Lock-in
Repository: https://github.com/better-auth/better-auth Documentation: https://www.better-auth.com/docs/introduction License: MIT Free Tier: Completely free, self-hosted
What it does:
BetterAuth is the way to go for auth in 2026, now that NextAuth/Auth.js isn’t being maintained actively right now. It’s a framework-agnostic library for TypeScript that gives you enterprise-grade features (OAuth2, passkeys, 2FA, multi-tenant) without the vendor lock-in of auth-as-a-service platforms. It proudly promotes the “anti-SaaS” approach to auth – you own the code, you own the data, and you never get surprise pricing changes.
Why it's essential:
If you've ever built a startup on Clerk or Auth0, you know the pain: everything works great until you hit their usage limits. Suddenly you're paying $500/month for auth when your entire AWS bill is $200. You can't scale a SaaS business on someone else's pricing model.
BetterAuth flips the script. You get the same features (social auth, 2FA, passkeys, session management) and the same fantastic DX, but self-hosted. No per-user fees, no vendor dependency, and you can customize anything since you own the code 100%.
Key Features:
- Framework-Agnostic: Works with Next.js, Remix, SvelteKit, Express, or vanilla React
- Complete Auth Features: OAuth2, email/password, magic links, passkeys, 2FA, multi-tenant
- Type-Safe: Full TypeScript support with generated types for your schema
- Database Flexibility: Works with any database (PostgreSQL, MySQL, SQLite, MongoDB) or ORM via adapters (official + community)
- Plugin System: Extend with official plugins or build your own
- No Vendor Lock-in: Self-hosted, open-source, MIT licensed
Quick Start:
npm install better-auth
Step 1: Set Environment Variables:
Create a .env file with:
BETTER_AUTH_SECRET=your-secret-key-at-least-32-characters
BETTER_AUTH_URL=http://localhost:3000
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
Step 2: Basic Setup (Next.js):
import { betterAuth} from "better-auth"
import { prismaAdapter } from "better-auth/adapters/prisma"
// they have many other ORM adapters + you can directly use your SQLite, PostgreSQL, MySQL libraries
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "postgresql"
}),
emailAndPassword: { enabled: true
},
socialProviders: { github: { clientId: process.env.GITHUB_CLIENT_ID ! ,
clientSecret: process.env.GITHUB_CLIENT_SECRET !
},
google: { clientId: process.env.GOOGLE_CLIENT_ID ! ,
clientSecret: process.env.GOOGLE_CLIENT_SECRET !
}
}
})
Step 3: Route Handler (Next.js App Router):
// app/api/auth/[...all]/route.ts
import {
auth
}
from "@/lib/auth"import {
toNextJsHandler
}
from "better-auth/next-js"export const {
POST,
GET
} = toNextJsHandler(auth)
Step 4: Create Database Tables:
Run the CLI to generate and migrate your database schema.
npx @better-auth/cli generate
Step 5: Client Setup:
// lib/auth-client.ts
import { createAuthClient } from "better-auth/react"
export const authClient = createAuthClient(
{ baseURL: process.env.NEXT_PUBLIC_APP_URL // optional if same domain })
export const {
signIn,
signUp,
signOut,
useSession
} = authClient
Step 6: Use it in your components:
"use client"
import { signIn, useSession } from "@/lib/auth-client"export
function LoginButton() {
const {
data: session
} = useSession()
if (session) {
return < div > Welcome,
{
session.user.name
} < /div> }
return ( <button onClick={() => signIn.social({ provider: "github" })}> Sign in with GitHub </button > )
}
How To Use BetterAuth:
- Replace auth-as-a-service with in-house auth. Use BetterAuth when your product already runs on Postgres and you want authentication to live alongside the rest of your data, eliminating per-user or per-MAU pricing.
- For compliance and data-residency requirements, deploy BetterAuth in the same region and infrastructure as your app to satisfy SOC2, GDPR, or EU data-residency constraints without relying on third-party vendors.
- For custom authentication and SSO flows, use BetterAuth when you need SAML, SSO, or identity workflows that don't fit neatly into a hosted provider's opinionated model.
- If you need multi-tenant B2B SaaS, use BetterAuth's organization plugin to manage multiple tenants with enterprise SSO, isolated auth policies, and customer-specific identity requirements. Each organization can have its own SSO provider linked via the SSO plugin.
Best for: B2B SaaS teams, compliance-sensitive products, and devs who want authentication to behave like first-class infrastructure rather than a managed black box.
3. Rosen Charts: Beautiful Copy-Paste D3 Charts with Zero Bundle Bloat
Repository: https://github.com/Filsommer/rosenCharts Website: https://rosencharts.com License: MIT Free Tier: Completely free, open-source
What it does:
Rosen Charts is the first charting library designed explicitly for React Server Components. Instead of shipping 100KB+ JavaScript bundles to your users, it renders charts server-side using D3 and native SVG/HTML. You get the full power of D3 with the copy-paste simplicity of shadcn components.
Why it's essential:
Traditional charting libraries have a dirty secret: they're massive. Recharts adds 100KB+ to your client bundle (gzipped). Chart.js is 180KB+ (gzipped). ApexCharts can hit 300KB+ (gzipped). For a dashboard with 5 charts, you've just added half a megabyte of JavaScript that has to download, parse, and execute before users see anything.
Rosen Charts renders 100% server-side. Your users get pure HTML and SVG -- no JavaScript payload, no hydration delay, no layout shift. The charts are there instantly, accessible, and tiny. Then you progressively enhance with interactivity only where needed.
Key Features:
- RSC-Native: First-class React Server Component support -- render on the server, ship minimal JS
- Copy-Paste Components: Import charts like you import shadcn components -- no complex configuration
- Built on D3 + HTML/SVG: Full customization access to the underlying elements
- Automatic Dark Mode: Respects dark: classes in Tailwind automatically
- Tiny Bundle Size: Server-rendered charts = zero client-side JavaScript by default
- Progressive Enhancement: Add interactivity (use client) only where needed
Quick Start:
Rosen Charts is copy-paste components, but you still need d3
npm install d3
For TypeScript, also install:
npm install --save-dev @types/d3
Then visit https://rosencharts.com and start copy-pasting whatever chart code you need (Example: Get bar charts here)
Basic Line Chart (copy-paste from rosencharts.com):
// components/charts/line-chart.tsx (Server Component)
import {
scaleTime,
scaleLinear,
max,
line as d3_line
}
from "d3"import {
CSSProperties
}
from "react"const data = [ {
date: new Date("2024-01-01"),
revenue: 4000
},
{
date: new Date("2024-02-01"),
revenue: 3000
},
{
date: new Date("2024-03-01"),
revenue: 5000
},
{
date: new Date("2024-04-01"),
revenue: 4500
},
] export
function LineChart() { const xScale = scaleTime() .domain([data[0].date, data[data.length - 1].date]) .range([0, 100]) const yScale = scaleLinear() .domain([0, max(data.map((d) = >d.revenue)) ? ?0]) .range([100, 0]) const line = d3_line < typeof data[number] > () .x((d) = >xScale(d.date)) .y((d) = >yScale(d.revenue))
return ( < div className = "relative h-64 w-full" style = {
{ "--marginTop": "0px",
"--marginRight": "8px",
"--marginBottom": "25px",
"--marginLeft": "25px",
}
as CSSProperties
} > < div className = "absolute inset-0 h-[calc(100%-var(--marginTop)-var(--marginBottom))] w-[calc(100%-var(--marginLeft)-var(--marginRight))] translate-x-[var(--marginLeft)] translate-y-[var(--marginTop)] overflow-visible" > < svg viewBox = "0 0 100 100"className = "overflow-visible w-full h-full"preserveAspectRatio = "none" > < path d = {
line(data) !
}
fill = "none"className = "stroke-blue-500"strokeWidth = "2"vectorEffect = "non-scaling-stroke" / > < /svg> </div > < /div> )}/
Interactive Chart (with Client Component):
"use client"import {
useState
}
from "react"import {
scaleBand,
scaleLinear,
max
}
from "d3"import {
CSSProperties
}
from "react"
const data = [ {
name: "Jan",
value: 4000
},
{
name: "Feb",
value: 3000
},
{
name: "Mar",
value: 5000
},
] export
function InteractiveBarChart() { const[selectedBar, setSelectedBar] = useState < string | null > (null) const xScale = scaleBand() .domain(data.map((d) = >d.name)) .range([0, 100]) .padding(0.2) const yScale = scaleLinear() .domain([0, max(data.map((d) = >d.value)) ? ?0]) .range([100, 0])
return ( < div className = "relative w-full h-64"style = {
{ "--marginTop": "0px",
"--marginRight": "0px",
"--marginBottom": "16px",
"--marginLeft": "25px",
}
as CSSProperties
} > < div className = "absolute inset-0 h-[calc(100%-var(--marginTop)-var(--marginBottom))] w-[calc(100%-var(--marginLeft)-var(--marginRight))] translate-x-[var(--marginLeft)] translate-y-[var(--marginTop)] overflow-visible" > {
data.map((d, index) = >( < div key = {
index
} onClick = { () = >setSelectedBar(d.name)
} style = {
{ left: `$ {
xScale(d.name)
} % `,
bottom: "0",
width: `$ {
xScale.bandwidth()
} % `,
height: `$ {
100 - yScale(d.value)
} % `,
}
} className = {`absolute bg - blue - 500 cursor - pointer transition - opacity $ { selectedBar === d.name ? "opacity-100": "opacity-70"
}`
} / > ))
} < /div> </div > )
}
How To Use Rosen Charts:
- Replace heavy client-side charting libraries with server-rendered charts. Use Rosen Charts when you need fast-loading dashboards, SEO-friendly visualizations, or want to minimize JavaScript bundle sizes. Server-side rendering eliminates client-side JavaScript for static charts, improving Core Web Vitals and initial load times.
- For analytics dashboards and admin panels, leverage Rosen Charts' zero-bundle approach to render multiple charts without impacting page load. Add interactivity selectively with use client only where tooltips, hover effects, or dynamic filtering are needed.
- For marketing sites and landing pages, use Rosen Charts to display testimonials, metrics, or comparison charts with zero JavaScript payload. Server-rendered SVG charts are instantly visible, accessible, and SEO-friendly.
- For mobile-first applications, Rosen Charts' server-side rendering ensures charts appear immediately even on slow 3G connections.
Best for: Dashboards, analytics platforms, marketing sites, mobile-first applications, and anyone who cares about Core Web Vitals and bundle size.
Bundle Size Comparison:
| Library | Client Bundle (Gzipped) | Initial Load | Hydration Cost |
|---|---|---|---|
| Recharts | ~100KB+ | Full parse + execute | High |
| Chart.js | ~180KB+ | Full parse + execute | High |
| ApexCharts | ~300KB+ | Full parse + execute | Very High |
| Rosen Charts (RSC) | 0KB* | Instant (HTML/SVG) | None |
*Interactivity adds minimal KB only for interactive charts
4. nuqs: URL Search Params as Type-Safe React State
Repository: https://github.com/47ng/nuqs Documentation: https://nuqs.47ng.com License: MIT Free Tier: Completely free, open-source
What it does:
nuqs (pronounced "nukes") turns URL search params into type-safe React state with built-in parsing, validation, and server-side compatibility. It's useState for URLs -- but actually good. Works seamlessly with Next.js App Router, Remix, React Router, and TanStack Router.
Why it's essential:
URL state is the most underutilized state management pattern in React. Everyone knows you should put filters, pagination, and search queries in the URL (for shareability, SEO, back-button support), but the implementation is always a mess of useEffect, URLSearchParams, manual parsing, and bugs.
nuqs makes URL state as simple as useState while giving you type-safety, built-in parsers (integers, dates, arrays, JSON), server-side rendering support, and automatic shallow routing. No more useEffect spaghetti.
Key Features:
- Type-Safe: Full TypeScript support with runtime validation
- Built-in Parsers: Integers, floats, booleans, dates, arrays, JSON, enums, and custom parsers
- Server-Compatible: Works with Next.js RSC and other SSR frameworks
- Shallow Routing: Updates URL without full page reload (or opt into navigation)
- Throttling: Built-in throttling for high-frequency updates (default 50ms)
- Framework Support: Next.js (App + Pages Router), Remix, React Router, TanStack Router
- Tiny Bundle: Only 6 KB gzipped
Quick Start:
npm install nuqs
Setup Adapter (Next.js App Router):
// app/layout.tsx
import {
NuqsAdapter
}
from 'nuqs/adapters/next/app'export
default
function RootLayout({
children
}) {
return ( < html > < body > < NuqsAdapter > {
children
} < /NuqsAdapter> </body > < /html> )}/
Basic Usage:
"use client"import {
useQueryState,
parseAsInteger
}
from "nuqs"
export
function ProductFilters() { const[minPrice, setMinPrice] = useQueryState( "min", parseAsInteger.withDefault(0) ) const[maxPrice, setMaxPrice] = useQueryState( "max", parseAsInteger.withDefault(1000) )
return ( < div > < input type = "number" value = {
minPrice
} onChange = { (e) = >setMinPrice(parseInt(e.target.value))
} / > < input type = "number" value = {
maxPrice
} onChange = { (e) = >setMaxPrice(parseInt(e.target.value))
} / > < /div> )}/
Advanced Example with Multiple Filters:
"use client"import {
useQueryStates,
parseAsString,
parseAsArrayOf,
parseAsStringEnum,
parseAsInteger
}
from "nuqs"const SortOrder = ["asc", "desc"] as constexport
function ProductList() { const[filters, setFilters] = useQueryStates({ search: parseAsString.withDefault(""),
categories: parseAsArrayOf(parseAsString).withDefault([]),
sort: parseAsStringEnum(SortOrder).withDefault("asc"),
page: parseAsInteger.withDefault(1)
})
return ( < div > < input value = {
filters.search
} onChange = { (e) = >setFilters({
search: e.target.value
})
} placeholder = "Search products..." / > < select value = {
filters.sort
} onChange = { (e) = >setFilters({
sort: e.target.value as "asc" | "desc"
})
} > < option value = "asc" > Price: Low to High < /option> <option value="desc">Price: High to Low</option > < /select> {/ * URL updates automatically: ?search = laptop & sort = desc & page = 2 * /} </div > )
}
Server-Side Usage (RSC):
// app/products/page.tsx
import {
createSearchParamsCache,
parseAsInteger
}
from "nuqs/server"
const searchParamsCache = createSearchParamsCache({ page: parseAsInteger.withDefault(1)
})
export default async
function ProductsPage({ searchParams
}:
{ searchParams:
Promise < {
page ? :string
} >
}) { const {
page
} = await searchParamsCache.parse(searchParams) const products = await fetchProducts({
page
})
return < ProductsList products = {
products
}
/>}/
How To Use nuqs:
- Replace manual URL state management with type-safe hooks. Use nuqs when you need shareable URLs for filters, search queries, pagination, or any state that should persist across page reloads. The URL becomes your single source of truth, eliminating the need for complex useEffect chains and manual parsing.
- For e-commerce filtering, use nuqs to encode all filter state in the URL. Users can share filtered product URLs, bookmark specific views, and use the back button to navigate filter changes. All filter state persists on page refresh without additional state management.
- For search interfaces, leverage nuqs's throttling to debounce search input updates to the URL. This provides SEO benefits, shareable search URLs, and eliminates the need for separate search state management.
- For dashboards and data tables, use nuqs to encode pagination, sorting, and filter state in the URL. Users can bookmark specific views and share them with teammates. Server components can read the URL state directly without prop drilling.
Best for: E-commerce filters, search interfaces, dashboards, data tables, any app where state should be shareable and SEO-friendly.
Why Not Just useState?
| Feature | useState | nuqs |
|---|---|---|
| Shareable links | ❌ Lost on reload | ✅ Encoded in URL |
| Back button support | ❌ Breaks navigation | ✅ Works perfectly |
| SEO-friendly | ❌ Invisible to crawlers | ✅ Indexed by search engines |
| Type-safety | ⚠️ Manual parsing | ✅ Built-in validators |
| SSR compatible | ⚠️ Complex setup | ✅ Works out of the box |
| Bundle size | ✅ 0KB | ✅ Only 6 KB gzipped |
5. Mastra - LLM Agents That Live in Your Next.js Codebase
Repository: https://github.com/mastra-ai/mastra Website: https://mastra.ai License: Apache 2.0 Free Tier: Completely free, self-hosted
What it does:
Mastra is a TypeScript-native agent framework built specifically for the JavaScript ecosystem. It gives you graph-based workflows, type-safe tools with Zod, and a visual Studio playground -- all without leaving your Next.js repo or spinning up Python microservices. Built by the team behind Gatsby.
Why it's essential:
Most AI agent frameworks assume you're okay with splitting your stack. Want LangChain? Spin up a Python service. LangGraph? Same deal. Now you're managing two codebases, two deployment pipelines, and dealing with API boundaries between your app and your agents.
Mastra says no. Agents live in the same codebase as your Next.js app. Use the same TypeScript types, the same database connections, the same auth middleware. npm create mastra scaffolds agents, tools, and RAG pipelines right into your existing project.
Key Features:
- TypeScript-Native: No Python required -- agents live in your existing Next.js/Node.js codebase
- Graph-Based Workflows: State machines, branching, loops, human-in-the-loop, suspend/resume
- Type-Safe Tools: Define tools with Zod schemas -- catch errors at compile time
- Visual Studio: Interactive playground for building and testing workflows (run mastra dev)
- Tool Compatibility: Compatible with Vercel's AI SDK for familiar abstractions
- OpenTelemetry Tracing: Debug agent workflows in Honeycomb, Grafana, or any OTel-compatible platform
- 600+ Models: Unified interface for OpenAI, Anthropic, Gemini, and 40+ providers
Quick Start:
npm create mastra@latest
Basic Agent Setup:
// src/mastra/agents/support-agent.ts
import {
Agent
}
from "@mastra/core/agent"import {
createTool
}
from "@mastra/core/tools"import {
z
}
from "zod"
const searchKnowledgeBaseTool = createTool({ id: "search-knowledge-base",
description: "Search the product knowledge base",
inputSchema: z.object({ query: z.string()
}),
outputSchema: z.object({ results: z.array(z.string())
}),
execute: async(inputData) = >{ // Your vector search logic here
return await searchVectorDB(inputData.query)
}
}) const escalateToHumanTool = createTool({ id: "escalate-to-human",
description: "Escalate complex issues to human support",
inputSchema: z.object({ reason: z.string(),
userEmail: z.string()
}),
outputSchema: z.object({ escalated: z.boolean()
}),
execute: async(inputData) = >{ await createSupportTicket({
reason: inputData.reason,
userEmail: inputData.userEmail
})
return {
escalated: true
}
}
})
export const supportAgent = new Agent({ id: "support-agent",
name: "Support Agent",
instructions: "You help users with product questions and route complex issues to humans.",
model: "anthropic/claude-sonnet-4-20250514",
tools: { searchKnowledgeBase: searchKnowledgeBaseTool,
escalateToHuman: escalateToHumanTool
}
})
Register Agent:
// src/mastra/index.ts
import {
Mastra
}
from "@mastra/core"import {
supportAgent
}
from "./agents/support-agent"
export const mastra = new Mastra({ agents: {
supportAgent
}
})
Workflow with Human-in-the-Loop:
// src/mastra/workflows/approval-workflow.ts
import {
createWorkflow,
createStep
}
from "@mastra/core/workflows"import {
z
}
from "zod"
const approvalStep = createStep({ id: "request-approval",
inputSchema: z.object({ draft: z.string()
}),
outputSchema: z.object({ approved: z.boolean()
}),
resumeSchema: z.object({ approved: z.boolean()
}),
execute: async({
inputData,
resumeData,
suspend
}) = >{ // First time: suspend for approval
if (!resumeData ? .approved) {
return await suspend({})
} // After resume: continue with approval status
return { approved: resumeData.approved
}
}
})
export const approvalWorkflow = createWorkflow({ id: "content-approval",
inputSchema: z.object({ topic: z.string()
}),
outputSchema: z.object({ published: z.boolean()
})
}).then(createStep(contentAgent, { id: "generate-draft"
})).then(approvalStep).then(createStep(publishAgent, { id: "publish"
})).commit()
Resume Workflow (API Route):
// app/api/workflows/approve/route.ts
import {
mastra
}
from "@/mastra"export async
function POST(req: Request) { const {
runId,
approved
} = await req.json() const workflow = mastra.getWorkflow("content-approval")
const run = await workflow.createRun({
runId
}) const result = await run.resume({ step: "request-approval",
resumeData: {
approved
}
})
return Response.json({
result
})
}
Usage in Next.js App:
// app/api/chat/route.ts
import {
mastra
}
from "@/mastra"export async
function POST(req: Request) {
const {
message
} = await req.json() const agent = mastra.getAgent("supportAgent") const response = await agent.generate([ {
role: "user",
content: message
}])
return Response.json({
text: response.text
})
}
How To Use Mastra:
- Replace Python microservices with TypeScript-native agents. Use Mastra when you want agents to live in the same codebase as your Next.js app, sharing types, database connections, and deployment infra.
- For SaaS AI copilots, build "chat with your docs"-like features directly in your Next.js app. Agents can access your database, call your APIs, and use your existing authentication -- all without separate Python services or complex API integrations.
- For workflow automation, use Mastra's suspend/resume capabilities to build approval flows, email processing pipelines, and multi-step onboarding. Workflows pause for human input, then resume automatically when conditions are met -- with all state persisted across deployments.
- For internal tools and admin panels, leverage Mastra's graph-based workflows to build complex automation logic. Agents analyze data, request approvals via Slack or email, then execute actions -- all with clear visual debugging in Studio.
Best for: SaaS platforms, AI copilots, automation tools, customer support systems, and any team that wants agents without the Python microservices overhead.
Mastra vs. LangGraph:
| Feature | Mastra | LangGraph |
|---|---|---|
| Language | TypeScript only | Python/TypeScript |
| Visual Editor | ✅ Studio (local dev) | ✅ LangGraph Studio |
| Type-Safe Tools | ✅ Zod schemas | ⚠️ Optional (pydantic) |
| Suspend/Resume | ✅ First-class | ✅ Storage adapter |
| Same Codebase | ✅ Lives in Next.js | ❌ Separate service |
| Deployment | ✅ Bundle with app itself | ⚠️ LangGraph Cloud |
| Best For | JS SaaS, UI-centric | Multi-agent, data pipelines |
Quick Comparison: Which Library Fits Your Needs?
The React ecosystem in 2026, so far, seems to reward focus over flexibility. These libraries prove that being opinionated about one problem is better than being mediocre at many.
| Library | What it solves | Setup difficulty | Cost | Best suited for |
|---|---|---|---|---|
| BetterAuth | Self-hosted auth without vendor lock-in | Medium | Free (self-hosted) | SaaS platforms, compliance-heavy apps |
| SurveyJS | Complex forms that even non-devs can maintain easily | Very Easy | Free (MIT) | Enterprise apps, research, calculators |
| Rosen Charts | Server-rendered charts that can be easily copy-pasted, with zero bundle bloat | Very Easy | Free | Dashboards, analytics, mobile-first |
| nuqs | Type-safe URL state management | Easy | Free | E-commerce, search, filters, pagination |
| Mastra | LLM agents in your Next.js codebase | Medium | Free (framework), API costs apply | AI copilots, automation, support tools |
Frequently Asked Questions
Q: Can I use all of these at once?
A: You can absolutely stack them together! In one Next.js app, you can use BetterAuth for user auth, SurveyJS for onboarding forms, Rosen Charts for analytics dashboards, nuqs for filters, and Mastra for AI support. All without overlap or conflict.
Q: Are these safe to self-host for production SaaS apps?
A: Yes. All five are open-source and designed to run on your own infrastructure. BetterAuth and Mastra are explicitly built for self-hosting, SurveyJS keeps all form data on your backend, Rosen Charts ships no client JavaScript by default, and nuqs has no server dependency at all.
Q: How steep is the learning curve for these libraries compared to generic solutions? A: Lower than it looks. Each library has a super narrow, well-defined scope and avoids configuration sprawl. You trade flexibility for clarity: fewer decisions, clearer mental models, and faster implementation.
Q: What makes these React libraries good for AI-assisted coding?
A: They’re all explicit, opinionated, and structurally predictable – which is exactly what AI coding assistants need. All five libraries have excellent, well-structured APIs, docs, and TypeScript support – while some, like SurveyJS, go one step further and have Context7 MCP support.
Q: Which AI coding assistants do these work best with?
A: I could get them all working in one app using just Claude Code with a local model (gpt-oss:20b via Ollama). Pretty much anything will work.
Have you tried any of these libraries? What has your experience been like? Found others worth recommending? Share in the comments below 👇
Comments
Loading comments…