Skip to content
dihlabs.lol

Speed

optz vs Next.js

The same site on optz and on Next.js 16, measured on one core, and a feature-by-feature comparison.

On this page

The same site built twice: portfolio/ on optz and bench/nextjs-portfolio/ on Next.js 16.4.3 (App Router, Turbopack, React 19.3.1). Pages, components, Tailwind CSS, security headers, the blog API and the proxy to the Rust engine are equivalent; the components are the same files with "use client" and next/link in place of islands and optz's Link.

Every Next.js feature optz didn't have is now built in (see Features), and apps/features/ uses all of them in one app, checked by a 22-step browser suite (e2e.cjs) that passes on the Bun server, the single executable and the edge worker in workerd.

The numbers below were measured on the site as it was before it became dihlabs.lol (commit 9a312d6, when it still had the social feed); bench/nextjs-portfolio is a port of that version. compare.sh now compares the URLs both versions still share.

Reproduce the numbers with bash bench/compare.sh (after npm ci in bench/nextjs-portfolio). Raw numbers: bench/results.json.

Speed

A 4-vCPU Linux VM. Each server is pinned to one core with taskset -c 0, the same budget as the VPS the site runs on. wrk runs on the other three cores: 3 threads, 64 connections, 10 s per URL, Accept-Encoding: br, gzip like a browser. Both are in production mode (optz start, next start), with no nginx in front of either. Measured 2026-10-26, after adding all the features below.

URLwhat it isoptz req/sNext req/soptz p99Next p99
/static page, 2 islands / 2 client components56,6363893.7 ms1.24 s
/blog/<slug>static page from Markdown54,5675013.3 ms994 ms
/api/blogJSON, prerendered at build time55,03557717 ms1.01 s
/social/post/1page whose data loads in the browser56,1851583.4 ms1.46 s
/nope404 page50,33461520 ms553 ms

On one core, optz serves about 100× more requests per second (350× on /social/post/[id]), with tail latency in milliseconds rather than seconds. Two checks that Next wasn't handicapped by the setup:

  • Every Next page above is a cache HIT with x-nextjs-prerender: 1: prerendered pages, not renders.
  • Next's best case, with proxy.ts removed and no compression, is 1,033 req/s for /. Most of its cost is the request pipeline itself, not gzip or the proxy.

/social/post/[id] is where the designs differ most. optz serves one prebuilt shell for every id (render: "client"), so it costs the same as a static page. Next renders the route on every request (ƒ in its build output); generateStaticParams = () => [] would make it cache each id after its first request instead, at about its static-page rate.

optzNext
Build1.8 s9.5 sclean build; optz's includes downloading fonts and rendering 2 OG images
Cold start to first 20074 ms692 msprocess start until / answers
First hit of the 5 URLs (sum)3 ms185 msNext loads route code lazily
Memory, idle35 MB140 MBRSS, process tree
Memory, peak under load52 MB333 MBRSS during the runs above
Response bytes, /1,4953,431brotli/gzip as sent
Framework on disk~220 KB source307 MBframework/src vs next + @next

JavaScript sent to the browser

Cold load in Chromium, all scripts, compressed as transferred:

pageoptz (islands + Preact)Next
/ (live status + feed)14.5 KB144.6 KB
/blog (no interactivity)2.9 KB144.6 KB
/blog/<slug> (no interactivity)2.9 KB144.6 KB
/social (token, compose, feed)14.7 KB144.6 KB
/social/post/113.1 KB147.2 KB

Next ships React, its router and the RSC runtime on every page, including pages with no client components. optz pages are HTML; only islands hydrate, with Preact (client.runtime: "preact"). The 2.9 KB on blog pages is the HTML-swap navigation with hover prefetch.

What this means on the VPS

At ~400 req/s per core, Next needs a CDN or nginx cache in front of it to survive a spike. optz handles ~55k req/s on the same core, and 52 MB peak memory leaves the rest of the 2 GB for the Rust engine and its connections. Whether native code would be faster still is measured in NATIVE.md (short answer: no, not for this site).

Features

✅ built in · ◐ partly · ❌ not available

Routing and rendering

featureoptzNext.jsnotes
File-based routes, nested layouts✅ app/routes, _layout.tsx✅ app/**/page.tsx, layout.tsx
Dynamic, catch-all, optional catch-all, groups✅ [id], [...rest], [[...rest]], (group)✅ same
Error boundary, 404 page✅ _error.tsx (also during SSR: status 500), _404.tsx✅ error.tsx, not-found.tsx
Loading UI✅ _loading.tsx✅ loading.tsxSuspense fallback while the page streams
Parallel routes✅ @slot folders, default.tsx✅ sameoptz: islands mode
Intercepting routes✅ (.)x, (..)x, (...)x✅ samee.g. a photo opens as a modal from the feed
Static generation✅ default, paths()✅ default, generateStaticParams
Server rendering per request✅ render: "dynamic"✅
Streaming SSR, Suspense, async server components✅✅optz streams dynamic pages; static pages wait for every boundary at build
One static shell for any param✅ render: "client"❌Next renders per request or per id
Incremental static regeneration✅ export const revalidate✅stale copy served while one render rebuilds
On-demand revalidation✅ revalidatePath, revalidateTag✅ same
Unlisted params rendered on demand✅ dynamicParams = true✅ same
Draft mode✅ draftMode(), signed cookie✅
redirect(), notFound()✅✅
Static export✅ optz export✅ output: "export"
RSC wire format, client router cache, Partial Prerendering, "use cache"❌✅optz navigates by fetching HTML and keeps unchanged islands mounted instead

Data and mutations

featureoptzNext.jsnotes
Server-only data loading✅ loader or async components✅ async server components
Server Actions✅ "use server" modules✅
Forms that work without JavaScript✅ <form action={fn}>, fn.bind(null, id)✅
Refresh after a mutation✅ action calls revalidatePath → the page updates in place✅islands keep their state
cookies(), headers(), after()✅ optz/server✅ next/headers, next/server
Typed API client from route files✅ api("/api/posts/[id]")❌wrong route, method, param or body is a compile error
API routes✅ app/api/*.ts✅ route.ts
Prerendered API routes✅ export const prerender = true✅ static route handlers
In-memory response cache (LRU, SWR, merged misses)✅ cache = { maxAge, staleWhileRevalidate, tags }◐ data cache + ISR

Browser JavaScript and assets

featureoptzNext.jsnotes
Pages without JS✅ default (islands mode)❌2.9 KB vs 145 KB above
Partial hydration, on load / idle / visible✅ *.island.tsx, export const hydrate◐ client components; the runtime always loads
Preact runtime option✅ client.runtime: "preact"❌
Client navigation with prefetch✅✅
Image optimization✅ <Image> + /_optz/image (AVIF/WebP, resize, cache)✅ next/imageneeds sharp; edge workers serve originals
Font optimization✅ fonts in config: self-hosted Google Fonts, preload, size-matched fallbacks✅ next/fontNext also has next/font/local
Script loading strategies✅ <Script strategy>✅ next/scriptno inline scripts (strict CSP), no web-worker strategy
Strict CSP with no 'unsafe-inline'✅ static pages need nothing; streamed pages get a nonce automatically◐ needs nonces, which make every page dynamic
Pre-compressed brotli + gzip✅ at build time❌ gzip per request

Metadata and SEO

featureoptzNext.js
Title, description, canonical, robots, Open Graph, Twitter cards✅ head()✅ metadata
Open Graph images from components✅ page.og.tsx (PNG at build time)✅ opengraph-image.tsx
sitemap.xml, robots.txt, web manifest from code✅ app/sitemap.ts, robots.ts, manifest.ts✅ same
hreflang alternates✅ automatic with i18n✅ alternates

Deployment and runtime

featureoptzNext.jsnotes
i18n routing✅ i18n in config: prefixes, Accept-Language detection, localized links◐ App Router: by hand in proxy.ts
Middleware✅ chain, typed ctx.locals✅ proxy.ts
Edge runtime✅ optz build --target edge: the whole app as one worker✅ per routetested in Cloudflare's workerd
Single-file deploy✅ optz build --compile: one executable◐ output: "standalone" (a folder + Node)
nginx config generator, micro-cache for proxied APIs✅ optz nginx❌
Turn features off for less memory per request✅ features.*❌

Tooling

featureoptzNext.jsnotes
Dev server with state-preserving updates✅ Fast Refresh for islands, CSS swap, page HTML updated in place✅ Fast Refreshoptz restarts the server process on each change (~0.3 s)
Tailwind CSS✅ built in✅ via PostCSS
Type-check command, typed routes✅ optz check✅ typedRoutes + next build
Project starter✅ optz create✅ create-next-app
RuntimeBunNode.js
Ecosystem, docs, hosting integrationssmall, this repolarge

When to pick which

optz: content sites, blogs, dashboards and apps where most pages are static or load their data in the browser; small servers; anywhere requests per core, memory and JavaScript per page matter. It now covers the Next.js feature set an app like this needs.

Next.js: apps that lean on the RSC wire format (client router cache, Partial Prerendering, "use cache"), very large codebases that benefit from Turbopack, and teams that want the ecosystem, documentation and managed hosting around it.