Přeskočit na obsah
BHPS, úvodní stránka

Zápisky

Next.js vs TanStack Start: benchmark, ve kterém se liší jen framework

Jan Szotkowski

V tomhle článku

Next.js 16.4.2 proti TanStack Start 1.169.3: stejná aplikace, stejné UI, stejná data, liší se jen framework

Nejdřív férově: nejsem neutrální. Na tomhle webu jsem nedávno psal, proč jsem opustil Next.js a stavíme na TanStack Startu. Přesně proto jsem chtěl čísla, která neobstojí jen u mě, ale i u někoho, kdo Next.js používá a má ho rád. Celý benchmark je proto veřejný na GitHubu: obě aplikace, všechny měřicí skripty, surová data i popis metodiky. Kdo najde chybu, ať mi pošle pull request. Opravím ji a změřím znovu.

A výsledek rovnou prozradím: jednoho vítěze nemá. TanStack Start je výrazně rychlejší a úspornější na serveru, při buildu i ve vývoji. Next.js vyhrává tam, kde mu pomáhá přednačítání a server actions. A v tom, co vidí uživatel při načtení stránky, jsou oba prakticky stejní.

Jak jsem měřil

Postavil jsem monorepo se dvěma aplikacemi. Mají stejné stránky, stejné React komponenty a stejná data. Liší se jen framework, který je obsluhuje.

StránkaJak se renderuje (v obou)Co testuje
/předpřipravená při builduzákladní cena frameworku
/blog/[slug]předpřipravená, 200 článkůbuild s mnoha stránkami, statické servírování
/products?category&sort&pagena serveru při každém požadavkuserverový render s parametry v adrese
/products/[id]na serveru při každém požadavku, s formulářemdynamická route, načtení dat, mutace
/dashboardpředpřipravená, 5 000 řádků oživených v prohlížečihydratace a náročná práce na klientu
/api/searchAPI endpointčistá propustnost serveru

Verze jsou zafixované přesně a každá je aspoň týden stará:

Verze
Next.js (App Router, Turbopack)16.4.2
TanStack Start (Vite, Nitro)1.169.3, Router 1.170.45, Vite 8.3.4
React19.3.1
Node24.22.1
StrojApple M5 Pro, 15 jader, 48 GB RAM

Aby to bylo fér, hlídal jsem tyhle věci:

  • Stejná data bez databáze a sítě. Data se generují v paměti z pevného seedu, takže se neměří rychlost databáze ani šum sítě. Obě aplikace vracejí stejný obsah.
  • Data jen na serveru v obou. V Next.js přes server komponenty, ve Startu přes server funkce (createServerFn). Při navigaci v prohlížeči si je obě aplikace stahují ze serveru.
  • Stejné cache hlavičky. Startu jsem nastavil přesně ty, které Next.js posílá ve výchozím stavu.
  • Stejná komprese. Ani jedna aplikace nekomprimuje sama a před obě je při měření v prohlížeči postavený stejný kompresní proxy server, jak by to v produkci udělal nginx.
  • Produkční buildy bez telemetrie, analytiky a webových fontů.
  • Střídání pořadí. Každé měření střídá pořadí aplikací (A/B, pak B/A), aby jedné z nich nepomohlo ani neuškodilo zahřátí počítače.
  • Opakování. Každé číslo je medián z opakovaných běhů: build 10×, dev server 5× s pěti úpravami souboru, Docker 30×, zátěž 3 kola po 60 s, prohlížeč 30× na desktopu i na mobilu.
  • Idiomatický kód. Obě aplikace používají výchozí doporučená nastavení svého frameworku. Kde se výchozí chování liší (třeba přednačítání odkazů), nechal jsem ho tak a v článku to popisuju.

Výsledky v kostce

Next.jsTanStack Start
Build bez cache1,91 s1,07 s
Build s cache1,18 s1,07 s
JavaScript při prvním načtení (brotli)118 kB99 kB
Start dev serveru978 ms664 ms
HMR (úprava sdílené komponenty)77 ms103 ms
Paměť dev serveru893 MB589 MB
Velikost Docker image407 MB341 MB
Paměť kontejneru v klidu81 MB47 MB
Serverový render detailu produktu1 952 req/s7 751 req/s
Statická stránka13 026 req/s40 830 req/s
LCP, FCP, TTFB, CLS v prohlížečistejnéstejné
Navigace na přednačtenou statickou stránku (mobil)51 ms532 ms
Odeslání formuláře (mobil)638 ms1 201 ms

Podrobně k jednotlivým částem.

Build a vývoj

Graf build a vývoj, kratší je lepší. Build bez cache: Next.js 1,91 s, TanStack Start 1,07 s. Build s cache: 1,18 s proti 1,07 s. Start dev serveru: 978 ms proti 664 ms. HMR: Next.js 77 ms, Start 103 ms.

Start se sestaví zhruba za polovinu času: 1,07 s proti 1,91 s včetně předpřipravení všech 203 stránek. Zajímavé je, že Next.js díky cache Turbopacku při opakovaném buildu zrychlí na 1,18 s, kdežto Startu cache nepomáhá a staví pořád stejně rychle. Vite pro produkční build trvalou cache nemá.

Ve vývoji je rozdíl podobný. Dev server Startu naservíruje první stránku za 664 ms, Next.js za 978 ms. První kompilace dashboardu trvá 251 ms proti 398 ms a celý dev server zabere 589 MB paměti proti 893 MB.

Jednu věc ale Next.js vyhrává: HMR, tedy jak rychle se úprava souboru projeví v otevřeném prohlížeči. Next.js to zvládne za 77 ms, Start za 103 ms. V praxi jsou obě čísla pod hranicí, kterou člověk vnímá, ale patří sem.

Jen pro úplnost: obě aplikace jsou malé. U velkého projektu se absolutní časy natáhnou a poměr může vypadat jinak.

Co stáhne prohlížeč

Při prvním načtení stáhne prohlížeč u Next.js 118 kB JavaScriptu (po kompresi brotli) v šesti souborech, u Startu 99 kB ve třech. U Next.js se do toho nepočítá polyfill pro staré prohlížeče, který moderní prohlížeč vůbec nestahuje.

Velikost se přitom mezi stránkami skoro nemění. Většinu tvoří samotný React a framework, ne kód stránek.

Server pod zátěží

Graf propustnosti serveru při 50 spojeních, víc je lepší. Statická stránka: Next.js 13 026 req/s, TanStack Start 40 830 req/s (3,1×). Statický článek: 9 294 proti 39 451 (4,2×). Detail produktu: 1 952 proti 7 751 (4,0×). Výpis s filtry: 1 032 proti 1 568 (1,5×). API: 10 758 proti 24 812 (2,3×).

Tady jsou rozdíly největší. Každou stránku jsem 60 sekund zatěžoval nástrojem autocannon při 10, 50 a 100 souběžných spojeních. V tabulce je 50 spojení, ostatní úrovně vycházejí podobně.

StránkaNext.jsTanStack StartRozdíl
Statická /13 026 req/s40 830 req/s3,1×
Statický článek blogu9 294 req/s39 451 req/s4,2×
Výpis produktů s filtry (SSR)1 032 req/s1 568 req/s1,5×
Detail produktu (SSR)1 952 req/s7 751 req/s4,0×
API endpoint10 758 req/s24 812 req/s2,3×
Dashboard, 2 MB HTML136 req/s2 582 req/s19×

U statických stránek jsem si u Startu nejistý, jestli se už nedostal na strop samotného měřicího nástroje, který při 40 tisících požadavků za sekundu běží na stejném počítači. Skutečné číslo Startu tedy může být ještě vyšší, nikoli nižší.

Výpis produktů je výjimka, kde se rozdíl zmenšuje na 1,5×. Tam většinu času zabere filtrování a řazení dat, tedy kód aplikace, který je v obou stejný.

Největší rozdíl je u dashboardu s 2 MB HTML. Next.js při něm vytíží procesor na 100 % a odešle 0,26 GB/s, Start 6,7 GB/s. Ověřil jsem, že Start opravdu posílá celou stránku. Pravděpodobná příčina: Next.js ve výchozím nastavení počítá ETag (otisk obsahu pro cache) z celého těla každé odpovědi, kdežto Nitro, na kterém Start běží, ho má spočítaný už při buildu. Profilováním jsem to neověřoval. Při 100 souběžných spojeních pak u Next.js některé požadavky na dashboard čekaly déle než 10 sekund. Start měl v celém zátěžovém testu nula chyb.

Obě aplikace přitom v klidu zaberou málo paměti: Next.js 104 MB, Start 63 MB.

Docker

Graf paměti a Dockeru, méně je lepší. Paměť dev serveru: Next.js 893 MB, TanStack Start 589 MB. Paměť kontejneru v klidu: 81 MB proti 47 MB. Velikost Docker image: 407 MB proti 341 MB.

Obě aplikace běží ve stejném základním obrazu node:24.22.1-bookworm-slim a se stejnými limity: 2 CPU a 1 GB paměti.

Next.jsTanStack Start
Velikost image407 MB341 MB
Od docker run po první odpověď242 ms126 ms
První serverový render po startu61 ms56 ms
Paměť v klidu81 MB47 MB

Start kontejneru zahrnuje i režii samotného Dockeru, která je u obou stejná. Pokud škálujete na nulu nebo spouštíte hodně instancí, je poloviční start a o 40 % menší paměť znát.

V prohlížeči

Tady přichází výsledek, který možná překvapí: v tom, co vidí uživatel při načtení stránky, se frameworky prakticky neliší.

Měřil jsem Lighthousem 30× na desktopu a 30× na mobilním profilu, kde je procesor 4× zpomalený a síť je jako pomalé 4G (562 ms latence, 1,6 Mbps). LCP, FCP, TTFB i CLS vycházejí u obou stejně v rámci šumu. Na desktopu se obě aplikace vykreslí za 30–40 ms, dashboard za 130–137 ms. Posun layoutu (CLS) je nulový u obou.

Drobné rozdíly:

  • Blokování hlavního vlákna (TBT) na mobilu: Start má na serverově renderovaných stránkách kolem 30 ms, Next.js kolem 1 ms. Obojí je hluboko pod hranicí 200 ms, kterou Google považuje za dobrou, takže to uživatel nepozná.
  • Spuštění JavaScriptu na mobilu: na úvodní stránce, článku blogu a detailu produktu je Start pomalejší (kolem 110 proti 75 ms), na výpisu produktů vycházejí stejně a na dashboardu s 5 000 řádky je naopak rychlejší (430 proti 472 ms).
  • Seřazení 5 000 řádků na dashboardu trvá u obou stejně: na desktopu kolem 195 ms, na mobilu kolem 800 ms. To dává smysl, práci dělá React a stejná komponenta, ne framework.

Tady se frameworky liší nejvíc, a to kvůli dvěma rozdílným výchozím strategiím.

Přednačítání odkazů. Next.js v produkci přednačítá odkazy, které jsou vidět na obrazovce. TanStack Start ve výchozím doporučeném nastavení přednačítá, až když na odkaz najedete myší. Na mobilu s pomalou sítí to vypadá takhle (simuloval jsem člověka, který si stránku chvíli prohlíží a před kliknutím drží prst nebo myš nad odkazem 200 ms):

Graf navigace na mobilu, kratší je lepší. Z úvodní stránky na blog: Next.js 51 ms, TanStack Start 532 ms. Z blogu na produkty: 617 ms proti 494 ms. Výpis na druhou stránku: 611 ms proti 500 ms.

Navigace (mobil)Next.jsTanStack Start
/ → /blog (statická stránka)51 ms532 ms
/blog → /products (dynamická stránka)617 ms494 ms
Výpis → druhá stránka výpisu611 ms500 ms

Next.js má statickou stránku přednačtenou celou, takže přechod je okamžitý. U dynamické stránky celou přednačíst nemůže a tam je o něco rychlejší Start, kterému pomůže i těch 200 ms nad odkazem. Je to tedy rozdíl ve strategii, ne v rychlosti frameworku. Start umí přednačítat i odkazy na obrazovce, jen to není jeho výchozí nastavení. Next.js naopak za své přednačítání platí víc požadavky: na stránce s výpisem produktů jich při načtení udělá na desktopu 43 a na mobilu 25, Start v obou případech 5.

Odeslání formuláře. Přidání recenze k produktu trvá na mobilu v Next.js 638 ms, ve Startu 1 201 ms. Spočítal jsem požadavky a důvod je jasný. Next.js server action pošle formulář a novou podobu stránky dostane zpátky v jedné odpovědi. Start pošle formulář server funkcí a pak si stránku načte znovu druhým požadavkem (router.invalidate()). Na pomalé síti to je jeden round-trip navíc. Oba postupy jsou idiomatické. Start by to zvládl taky jedním požadavkem, kdyby server funkce vrátila nová data a aplikace je sama dosadila, ale to už je ruční práce navíc.

Na desktopu bez zpomalené sítě jsou obě navigace i odeslání formuláře pod 50 ms.

Co jsem cestou zjistil

Při stavbě benchmarku jsem narazil na tři věci, které stojí za zmínku samy o sobě:

  1. Vestavěný prerender ve Startu se na Node serveru nepoužije. Když stránky předpřipravíte přes tanstackStart({ prerender }), uloží se HTML až poté, co si Nitro sestaví seznam statických souborů. Node server je pak vůbec neservíruje a renderuje stránky při každém požadavku znovu. Dashboard tak trval kolem 110 ms místo 4 ms. Řešení je předpřipravit stránky přes Nitro (nitro({ prerender: { routes } })), pak se servírují jako statické soubory. Ověřeno na Startu 1.169.3 a Nitru 3.0.260903-beta.
  2. Next.js dev server blokuje HMR, když web otevřete přes 127.0.0.1. Na localhost funguje normálně. Jde o bezpečnostní výchozí nastavení (allowedDevOrigins), ale chvíli mi trvalo, než jsem pochopil, proč se změny neprojevují.
  3. Typované odkazy a sdílené komponenty se úplně nesnesou. Když komponenta ze sdíleného balíčku dostane adresu jako obyčejný řetězec, musíte typ odkazu v obou frameworcích přetypovat. Typová bezpečnost routeru je skvělá, ale končí na hranici balíčku.

Chyby v měření, které jsem našel a opravil

Tahle část je možná nejdůležitější. Benchmarky na internetu často vypadají přesně, a přitom měří něco jiného, než tvrdí. U sebe jsem našel několik takových chyb a radši je popíšu, než abych je zametl pod koberec:

  • Lighthouse zkresloval interakce. Když měří interakci uvnitř svého záznamu (timespan), nahrává detailní trace a stránku tím zpomalí. Seřazení 5 000 řádků v něm trvalo 1,3 s, ve skutečnosti 0,2 s. Interakce teď měřím zvlášť, bez Lighthouse.
  • Skript klikal dřív, než framework stihl přednačíst. Next.js přednačítá, když má prohlížeč volnou chvíli, Start při najetí myší. Skript, který klikne hned po načtení, výsledky ovlivňoval náhodou. Teď 3 s počká a 200 ms drží myš nad odkazem, stejně u obou.
  • 200 souběžných spojení měřilo macOS, ne frameworky. macOS ve výchozím stavu drží ve frontě jen 128 čekajících spojení a zbytek zahodí. Obě aplikace pak hlásily tisíce chyb, které s nimi neměly nic společného. Proto test končí na 100 spojeních.
  • Do JavaScriptu Next.js jsem omylem počítal polyfill, který moderní prohlížeč nestahuje. Next.js tak vypadal o 34 kB těžší, než je.
  • Zapomenutý proces z ladění běžel celou dobu prvního kompletního měření a vytěžoval jedno jádro. Celé měření jsem proto zahodil a pustil znovu na čistém počítači.

A jednu metriku jsem nakonec nepublikoval vůbec: INP (latenci interakce) u navigace a odeslání formuláře. Headless Chrome, ve kterém měření běží, potvrdí vykreslení snímku až po doběhnutí síťového požadavku. Latence pak vychází stejná jako celá doba požadavku, a to i u úhozů do klávesnice, které proběhly ještě před kliknutím. U interakcí bez sítě (řazení dashboardu) to problém není, ale u navigace a formulářů by to číslo bylo zavádějící u obou frameworků.

Co tenhle benchmark neříká

Aby z něj nikdo nevyvozoval víc, než unese:

  • Jeden stroj, jedno prostředí. Měřeno na Apple M5 Pro, aplikace běží jako Node servery a v Dockeru. Vercel ani Cloudflare jsem neměřil, a Next.js je na Vercel optimalizovaný. Tam by výsledky mohly vypadat jinak.
  • Syntetická aplikace. Žádná databáze, žádné volání externích API. U skutečné aplikace bude velkou část času zabírat právě databáze a rozdíly mezi frameworky se zmenší.
  • Mobil je simulovaný. Je to Chrome se zpomaleným procesorem a sítí, ne skutečný telefon.
  • Pohodlí vývoje se neměří. Typová bezpečnost, ekosystém, dokumentace ani to, jak rychle najdete vývojáře, v tabulkách nejsou. Pro výběr frameworku jsou ale často důležitější než milisekundy.
  • Nitro v3, na kterém Start na serveru běží, je pořád v betě. Mezi verzemi se jeho chování i výkon ještě může měnit.

Kdy co vybrat

Next.js dává smysl, když:

  • nasazujete na Vercel a chcete využít jeho ekosystém,
  • máte hodně statického obsahu a záleží vám na okamžitých přechodech mezi stránkami (přednačítání funguje bez práce navíc),
  • stavíte na formulářích a mutacích a líbí se vám server actions, které vrátí novou stránku v jedné odpovědi,
  • potřebujete React Server Components jako základ architektury,
  • chcete největší komunitu a nejsnazší hledání lidí.

TanStack Start dává smysl, když:

  • hostujete sami na Node nebo v Dockeru a záleží vám na propustnosti serveru, paměti a ceně infrastruktury,
  • chcete rychlejší build a rychlejší start dev serveru,
  • chcete méně JavaScriptu v prohlížeči a víc kontroly nad tím, co se děje na klientu,
  • oceníte typově bezpečný routing a server funkce, které hlídá TypeScript,
  • nechcete být svázaní s jedním poskytovatelem hostingu.

Repo a jak si to zopakovat

Všechno je v repu janszotkowski/tanstack-start-nextjs-benchmark: obě aplikace, měřicí skripty, surová data a podrobná metodika. Celý benchmark spustíte jedním příkazem:

pnpm install && pnpm bench:all

Na M5 Pro běží zhruba 4,5 hodiny a na konci vygeneruje přehled výsledků. Potřebujete Node 24.22.1, nainstalovaný Chrome a běžící Docker. Pokud najdete něco, co je podle vás nefér, ať už v kódu jedné z aplikací, nebo v měření, otevřete issue nebo pull request. Obzvlášť uvítám, když se na kód podívá někdo, kdo Next.js zná líp než já.

Závěr

Čekal jsem, že TanStack Start bude rychlejší. Nečekal jsem, že na serveru typicky dvakrát až čtyřikrát (u velké stránky dokonce devatenáctkrát) a že v prohlížeči bude výsledek prakticky nerozhodný. Pro uživatele, který si stránku otevře, je framework skoro neviditelný. Rozdíl pozná hlavně ten, kdo platí servery, a vývojář, který čeká na build.

A Next.js má svoje silné stránky, které čísla potvrdila: přednačítání statických stránek, server actions a rychlejší HMR. Pokud ho používáte a jste spokojení, tenhle benchmark vám nedává důvod utíkat. Pokud stavíte nový projekt, hostujete sami a záleží vám na výkonu serveru, je TanStack Start podle těchhle čísel velmi silná volba.

Pokud řešíte, jaký framework zvolit na nový projekt, nebo zvažujete migraci, ozvěte se nám. Rádi se podíváme i na váš konkrétní případ.

Nové články chcete dostávat do čtečky? Odebírejte zápisky přes RSS.