Pareto 4.0
Pareto 4.0 is about two things: making client navigation as fast as the initial SSR render, and proving it with numbers.
The headline features are NDJSON streaming for navigations, a React component convention for <head>, and document-level customization. But the real story is that we now have automated benchmarks running in CI — and Pareto handles 9.3x more data-loading requests than Next.js and sustains 6.5x higher streaming throughput under load.
NDJSON streaming navigation
Section titled “NDJSON streaming navigation”In 3.0, client-side navigations fetched loader data as a single JSON response. The browser waited for the entire payload before rendering. In 4.0, navigations use NDJSON (newline-delimited JSON) streaming.
When you navigate to a page with defer(), the client receives the non-deferred data immediately and starts rendering. Deferred values stream in as they resolve — the same progressive delivery model as the initial SSR render, now applied to every client navigation.
This means Suspense boundaries work identically on first load and on navigation. No behavioral differences, no special handling.
head.tsx component convention
Section titled “head.tsx component convention”Head management is no longer a descriptor object. It’s a React component.
Each route directory can have a head.tsx that exports a default component receiving { loaderData, params }:
export default function Head({ loaderData }: { loaderData: { title: string } }) { return ( <> <title>{loaderData.title} — Dashboard</title> <meta name="description" content={`Dashboard for ${loaderData.title}`} /> </> )}Head components merge from root layout to page. Duplicate tags are deduplicated by name, property, or httpEquiv — the deepest route wins. During client navigation, head components re-render to keep <title> and <meta> in sync without a full page reload.
Document customization
Section titled “Document customization”A new document.tsx convention lets you customize the HTML shell:
import type { DocumentContext } from '@paretojs/core'
export function getDocumentProps(ctx: DocumentContext) { const lang = ctx.url.startsWith('/zh') ? 'zh-CN' : 'en' return { lang, dir: lang === 'ar' ? 'rtl' : 'ltr' }}The returned props are applied to the <html> element. This is useful for i18n, RTL support, or any per-request HTML attributes.
Performance: the numbers
Section titled “Performance: the numbers”We built a comprehensive benchmark suite that runs in CI on every PR. The results speak for themselves.
Throughput (100 connections, 30s duration):
- Data Loading: Pareto 2,733/s vs Next.js 293/s (9.3x)
- API / JSON: Pareto 3,675/s vs Next.js 2,212/s (1.7x)
- Streaming SSR: Pareto 247/s, roughly equal across frameworks
Max sustainable QPS (1→1,000 connections, p99 < 500ms):
- Streaming SSR: Pareto 2,022/s vs Next.js 310/s (6.5x)
- Data Loading: Pareto 2,735/s vs Next.js 331/s (8.3x)
In concrete terms: if your data-loading pages need to handle 2,000 req/s at peak, that’s 1 Pareto server vs 6 Next.js instances. For streaming SSR dashboards, it’s 1 vs 7. Less infrastructure, less cost, fewer things to break.
Client JS bundle: 62 KB gzipped — roughly 1/4 of Next.js (233 KB). On 4G mobile, that’s ~100ms to download vs ~370ms. Every millisecond counts for bounce rate.
Full details in our benchmark blog post.
Other changes in 4.0
Section titled “Other changes in 4.0”startProductionServer()— New export from@paretojs/core/nodefor simplified production server startup with built-in static file serving, compression, and security headers.- Default error fallback — A built-in error fallback component when no custom error boundary is defined.
- Route pattern caching — Client-side route matching uses a pre-compiled regex cache for faster navigation.
wkWebViewFlushHint— Config option to force iOS WKWebView to paint the initial shell before the stream completes.
Breaking changes
Section titled “Breaking changes”HeadDescriptor/HeadFunctionreplaced byhead.tsxcomponentshydrateApp()removed — usestartClient()RouterProviderno longer exported — managed internallycreateStoreApi()removed — usedefineStore()from@paretojs/core/storedehydrate()/getHydrationData()/hydrateStores()removed — hydration is automatic- Barrel files removed — import directly from source modules
See the full changelog for migration details.
Try it
Section titled “Try it”npx create-pareto@latest my-appcd my-app && npm install && npm run dev