RE:NODE

Hosting aplikací12 min čtení

Nasazení Next.js na Node serveru

Vlastní hosting Next.js: proč build potřebuje víc paměti než aplikace, standalone výstup a složky, které chybí, proměnné prostředí zapečené do buildu a cache pro ISR.

6 čtenářů

Next.js se dá hostovat vlastními silami dvěma příkazy: next build vytvoří adresář .next a next start ho obsluhuje HTTP serverem v Node na portu, který si zvolíš. Všechno ostatní, co se pokazí, vyplývá ze čtyř skutečností. Build potřebuje mnohem víc paměti a CPU než běžící aplikace. Build potřebuje devDependencies, které běžící aplikace nepotřebuje. Některé proměnné prostředí jsou zapečené do výstupu a jiné se čtou za běhu a nic ti neřekne, které jsou které. A cache, díky které je inkrementální statická regenerace rychlá, je adresář na disku, takže má své požadavky na restarty a na provoz více instancí.

Tady je, co z toho plyne pro jeden server za reverzní proxy, což je běžný způsob provozu Next.js, když nejsi na platformě, pro kterou vznikl.

Build a start: dva příkazy s velmi odlišným apetitem#

bash
$ npm ci                      # devDependencies included, on purpose$ npm run build               # next build$ npm run start -- -p 3000 -H 0.0.0.0

První překvapení je, že npm ci --omit=dev && npm run build nefunguje. TypeScript, Tailwind, PostCSS, ESLint a typové balíčky jsou devDependencies a next build je potřebuje. Buď nainstaluj všechno a sestav na serveru, nebo sestav jinde a nasaď jen výstup. Proč je npm ci správný instalátor v obou případech, vysvětluje npm ci vs npm install.

Druhé je spotřeba zdrojů. Kompilace a kontrola typů středně velké aplikace může zabrat dobře přes gigabajt haldy a využije každé jádro, které najde, zatímco stejná aplikace obsluhující provoz pak sedí v pár stech megabajtech. Build v kontejneru s 1 GB je klasické selhání: skončí hláškou JavaScript heap out of memory, nebo se kontejner na limitu zastaví a log prostě skončí, což se projeví jako exit code 137. Žádná z těch hlášek neříká „kup na devadesát vteřin víc paměti“, ale přesně to znamenají.

Tři cesty ven, seřazené podle toho, kolik tě stojí:

  1. Sestav v CI a nasaď výstup. Server jen nainstaluje produkční závislosti a spustí aplikaci. Nejrychlejší a nejméně překvapivá možnost, a pokud už CI máš, volba jasná.
  2. Sestav dočasně na vyšším tarifu. Přechod na vyšší tarif změní limit na serveru, který už máš, takže tarif štědrý pro build a postačující pro aplikaci je rozumná odpověď, když se buildy dělají zřídka.
  3. Omez haldu a doufej. NODE_OPTIONS=--max-old-space-size=1536 přiměje V8 agresivněji uvolňovat paměť, místo aby rostl ke stropu, který nemá. Některá selhání změní na pomalé buildy. Paměť nevytvoří. Dva stropy, o které jde, vysvětluje Proč tvoje Node aplikace umírá na 2 GB u tarifu se 4 GB.

Doba buildu na zlomku CPU je druhá strana té směny. Build, který na notebooku trvá 40 sekund, může na půl jádře trvat několik minut, a to je několik minut výpadku, pokud sestavuješ na stejném serveru, který web obsluhuje.

při nasazenínové build idnačteno při startupřidělený portISR zapisuje zpětRepozitářapp/ a public/Proxy slotHTTPS na 443next buildpotřebuje devDependenciesNode servernext startVýstup .nextserver, static, cache
Od pushe k vykreslené stránce

Standalone výstup a dvě složky, které nekopíruje#

Nastavení output: "standalone" v next.config.js způsobí, že build vytvoří samostatný adresář: malý server.js a jen ty soubory z node_modules, které trasování označilo za nutné. Pro nasazení to obvykle znamená o stovky megabajtů méně než posílat celý strom.

next.config.js
const nextConfig = {  output: "standalone",  compress: false,      // the proxy in front already does this  poweredByHeader: false,};export default nextConfig;

Na čem se zaseknou všichni: standalone build záměrně nekopíruje dva adresáře, protože se čeká, že je bude servírovat CDN: public a .next/static. Když složku standalone někam přesuneš a spustíš ji tak, jak je, web se načte bez CSS, bez JavaScriptu a bez obrázků a konzole se zaplní chybami 404 pro /_next/static/.... Zkopíruj je tam:

bash
$ cp -r public .next/standalone/public$ cp -r .next/static .next/standalone/.next/static$ node .next/standalone/server.js

Vygenerovaný server čte PORT a HOSTNAME z prostředí. Nastav obojí výslovně - zvlášť HOSTNAME=0.0.0.0, protože server navázaný na loopback uvnitř kontejneru je z proxy nedostupný a selhání vypadá jako rozbitá aplikace, ne jako síťové nastavení. Všimni si také, že next start se na standalone build nepoužívá; build ti sám říká, že máš spustit přímo server.js.

Standalone se vyplatí, když je málo místa na disku nebo času na instalaci. Pokud ne, je to jen další pohyblivá část, a v monorepu je potřeba nasměrovat kořen trasování souborů výstupu na kořen workspace, jinak ve vytrasovaném balíku chybí balíčky, které leží mimo adresář aplikace.

Proměnné prostředí: zapečené při buildu, čtené za běhu#

Tohle je nejvíc matoucí věc na vlastním hostingu Next.js a pod povrchem je jednoduché pravidlo.

ProměnnáKdy se čteZměna vyžaduje
NEXT_PUBLIC_*Vložena do klientského balíku při builduNový build
Serverové proměnné v dynamickém kóduPři každém požadavkuRestart
Serverové proměnné ve staticky předrenderovaných stránkáchPři buildu, do HTMLNový build

NEXT_PUBLIC_API_URL není v nasazené aplikaci proměnná. Je to řetězec, který byl při buildu dosazen do tvého JavaScriptu. Nastavit ji na záložce Startup na jinou hodnotu a restartovat nezmění vůbec nic, protože už není co měnit. Pokud se hodnota musí lišit mezi stagingem a produkcí a používá se v prohlížeči, buď sestav dvakrát, nebo ji za běhu načítej z API routy.

Opačná past je serverová proměnná použitá na stránce, která byla staticky předrenderována. Hodnota, která existovala při buildu, je v HTML. Pokud musí být aktuální, označ routu jako dynamickou nebo hodnotu čti v cestě vázané na požadavek.

Tajné hodnoty bez prefixu NEXT_PUBLIC_ se do prohlížeče nedostanou, což je smysl toho prefixu, a patří do proměnných prostředí na záložce Startup, ne do repozitáře. Obecný argument je v článku Kam dát tajné hodnoty na aplikačním serveru a tady platí o to víc, že uniklá hodnota NEXT_PUBLIC_ už je v prohlížeči každého návštěvníka.

Cache: ISR, revalidace a co restart ztratí#

Inkrementální statická regenerace vykreslí stránku jednou, servíruje uloženou kopii a na pozadí ji znovu vykreslí, když zastará. Při vlastním hostingu je tím úložištěm adresář na disku pod .next/cache. Plynou z toho tři důsledky.

Nový build je nová cache. Každý build dostane vlastní identifikátor, takže stránky vygenerované předchozím nasazením se znovu nepoužijí. První návštěvník každé routy po nasazení zaplatí za vykreslení. Na webu s velkým počtem ISR rout je to viditelné studené období, což je argument pro to, abys důležité stránky po nasazení zahřál několika požadavky, místo aby je jako první našel skutečný uživatel.

Restarty nevadí, efemérní disky ano. Cache přežije restart, protože adresář přežije. Nepřežije nový build a neexistuje vůbec, pokud se souborový systém mezi spuštěními zahodí. Na běžném serveru s trvalým úložištěm to není problém, což je jedna z tichých výhod provozu Next.js na obyčejném stroji.

Víc instancí znamená víc cache. Dva procesy si každý drží vlastní, takže stejná URL může být na jednom zastaralá a na druhém čerstvá. K tomu slouží volba cacheHandler v next.config.js: nasměruj ji na sdílené úložiště a všechny instance se shodnou. Na jednom serveru ji nepotřebuješ a název konfigurace se mezi verzemi změnil, takže pokud ji nasadíš, ověř si dokumentaci pro svou hlavní verzi.

Samotná revalidace má dvě podoby: časovou, nastavenou přes export const revalidate = 3600 na routě nebo u jednotlivého fetche, a na požádání, přes revalidatePath nebo revalidateTag volané z route handleru nebo server action po změně. Revalidace na požádání je téměř vždy lepší pro obsah, který se mění, když člověk stiskne uložit.

next/image a CPU, se kterým nikdo nepočítal#

next/image optimalizuje obrázky na požádání v běžícím serveru: změní velikost, překóduje je do moderního formátu a výsledek uloží do cache v .next/cache/images. To je skutečné CPU a skutečný disk, na stejném kontejneru, který vykresluje tvé stránky.

  • Nainstaluj sharp. Bez něj je optimalizátor dramaticky pomalejší a Next na to při startu upozorní.
  • Externí zdroje potřebují nastavené images.remotePatterns, jinak požadavky selžou s jasnou hláškou, že hostitel není nakonfigurovaný.
  • Cache je klíčovaná zdrojem, šířkou a kvalitou, takže komponenta, která si vyžádá osm velikostí úvodního obrázku, vygeneruje osm kódování. Kolik variant může existovat, řídí deviceSizes a imageSizes.
  • minimumCacheTTL určuje, jak dlouho se optimalizovaný soubor drží, než se vyrobí znovu.
  • unoptimized: true vypne celou věc, což je správná odpověď, když jsou tvoje obrázky už ve správné velikosti a servírují se odjinud.

Na tarifu s půl jádrem způsobí stránka s tuctem neoptimalizovaných zdrojových obrázků pomalý první požadavek a zaplní adresář cache překódováními. Je to nejčastější důvod, proč se vlastnoručně hostovaný web v Next lokálně zdá v pořádku a na malém serveru ztuhne. Co NVMe skutečně mění tu platí nečekaným způsobem: kódování je vázané na CPU, takže rychlejší disk situaci nezachrání.

Co musí dělat proxy před aplikací#

HTTP obsluhuje vlastní server Next; proxy před ním se stará o TLS a název hostitele. Na RE:NODE je to slot pro proxy v každém tarifu pro aplikace: nasměruj záznam A na zobrazenou adresu a certifikát se vystaví a obnovuje automaticky v okně 21 dní, přičemž adresa klienta se předává v X-Forwarded-For. Obě poloviny popisují Co reverzní proxy skutečně dělá a Nasměrování domény na server.

Čtyři věci, které na té hranici musí sedět:

  • Komprese jednou. Next ve výchozím stavu komprimuje odpovědi. Pokud komprimuje i proxy, nastav v next.config.js compress: false a nech to na proxy, jinak platíš dvakrát za stejné bajty.
  • Předávaný protokol. Kód, který sestavuje absolutní URL, musí vědět, že původní požadavek byl HTTPS. Čti předávané hlavičky, nepředpokládej, jinak tvé kanonické odkazy a přesměrování míří na http://.
  • Streamování. Odpovědi App Routeru se streamují a hranice Suspense přicházejí postupně. Proxy, která odpovědi úplně bufferuje, z toho udělá jedinou pozdní odpověď - stránka pořád funguje, ale pocit zrychlení zmizí.
  • Statické soubory. Soubory pod /_next/static mají v názvu hash obsahu a jsou neměnné, takže se dají cachovat natvrdo a na dlouho. Které hlavičky to zařídí, vysvětluje Hlavičky HTTP cache vysvětleně a TTFB, Core Web Vitals a hosting poctivě říká, které části skóre z Lighthouse může hosting skutečně posunout.

Nasazení a restart#

Příkaz pro start na hostingu, který sestavuje na serveru, vypadá takhle:

bash
npm ci && npm run build && npm run start -- -p $PORT -H 0.0.0.0

To je poctivé, ale pomalé, protože se build spouští při každém startu. Lepší uspořádání je sestavovat, když se změní kód, ne když se spouští proces: sestav v CI, výstup commitni nebo nahraj a nech startovací příkaz být npm ci --omit=dev && npm run start.

Připoj repozitář místo nahrávání souborů. Na RE:NODE je integrace s Gitem jen pro GitHub, přes GitHub App s krátkodobými tokeny, takže fungují i soukromé repozitáře, a má dva přepínače: stáhnout větev při každém startu a nasadit při pushi, což restartuje už běžící server. Jeden záznam na každé nasazení ti říká, který push je skutečně živý. Postup je v článku Nasazení Node.js aplikace z GitHubu.

Nasazení stojí mezeru: proces se zastaví, nový se sestaví nebo spustí a dokud neposlouchá, proxy nemá s kým mluvit. S jedním procesem ji nemůžeš udělat nulovou, jen krátkou. Sestavuj předem, nasazuj, když je provoz nízký, a přečti si Nasazení bez výpadku na serveru, který má od všeho jen jeden kus, co skutečně pomáhá. Pokud tvá aplikace v Next zároveň nabízí API routy, na kterých něco závisí, platí beze změny provozní rady z článku Nasazení Express API do produkce - timeouty, čisté ukončení, health endpointy.

Řešení potíží#

`Could not find a production build in the '.next' directory`. Spustil jsi bez buildu, nebo výstup buildu nebyl součástí nasazeného. .next bývá v .gitignore, takže stažení repozitáře ho nepřinese.

Build je zabit bez chyby, exit code 137. Kontejner během buildu narazil na limit paměti. Sestav jinde, nebo na vyšším tarifu.

Web se načte bez stylů. Standalone build bez zkopírovaných public a .next/static.

`EADDRINUSE` při startu. Předchozí proces pořád drží port. Zastav ho pořádně; v panelu použij Stop a nespouštěj druhou kopii.

Stránka se neaktualizuje. Zjisti, o kterou cache jde. Staticky generovaná stránka potřebuje revalidaci nebo nový build; výsledek fetch se může cachovat podle výchozích hodnot tvé verze Next; prohlížeč ji může držet. Kontroluj v tomto pořadí.

Proměnná prostředí nemá žádný účinek. Pokud začíná na NEXT_PUBLIC_, byla při buildu zapečena do balíku a vyžaduje nový build.

Obrázky vrací 404 nebo hlásí chybu hostitele. images.remotePatterns zdroj neuvádí.

FAQ#

Potřebuju ke spuštění Next.js konkrétní platformu?

Ne. next build a next start jsou podporovaný způsob vlastního hostingu a proces Node za reverzní proxy spouští framework tak, jak byl navržen. Přebíráš provozní stránku: buildy, restarty, adresář cache a certifikát, o čemž je zbytek tohoto článku.

Proč build dojde paměť, když aplikace používá jen 200 MB?

Protože kompilace, kontrola typů a bundling probíhají najednou a drží celý graf modulů. Běžící aplikace z toho nedrží skoro nic. Dimenzuj podle buildu, nebo build z serveru přesuň.

Mám použít standalone výstup?

Použij ho, když je málo místa na disku nebo času na instalaci, nebo když sestavuješ image k nasazení. Nezapomeň zkopírovat public a .next/static vedle vygenerovaného server.js a nastav HOSTNAME a PORT výslovně.

Můžu web v Next.js hostovat na statickém nebo PHP tarifu?

Jen když ho exportuješ. output: "export" vytvoří čisté HTML, CSS a JavaScript bez serveru, které obslouží jakýkoli statický hosting - a vzdáš se renderování na serveru, ISR, route handlerů a optimalizace obrázků. Pokud je tvůj web obsah, který se mění při buildu, je ta směna často dobrá; pokrývají ji hosting statických webů a webhosting.

Kolik paměti potřebuje server s Next.js za běhu?

Malý web se po sestavení pohodlně vejde do 512 MB až 1 GB a roste se souběžností, velikostí tabulky rout a cache obrázků. Špička je build, ne obsluha. Než se rozhodneš, sleduj graf paměti v konzoli den, a pokud čekáš nápor, přečti si Dimenzování webové aplikace na den spuštění.

Proč je moje stránka po změně obsahu zastaralá?

Protože ji něco záměrně cachovalo. Zjisti, jestli jde o staticky generovanou stránku čekající na okno revalidace, o cachovaný fetch, nebo o prohlížeč. Revalidace na požádání přes revalidatePath po změně obsahu hádání odstraní.


Komentáře

Zcela anonymně: žádný účet, žádný e-mail, žádné cookie. Ukládáme jen jméno, které napíšeš, text a čas - nic dalšího. Počet odkazů je omezený a značkování se nevykresluje.

0/2000