Het verwijderen van Next.js zorgde voor een score van 100 in elke Lighthouse-categorie voor mijn blog

Waarom besloot ik mijn eigen blog te bouwen?

Wanneer je aan blogs denkt, komen er een paar namen in op. Medium, Blogger, Ghost... maar geen van hen was bevredigend. Medium en Blogger zijn overladen met functies en de feitelijke prestaties waren ook niet bevredigend.

Andere alternatieven zijn WordPress, Ghost en Hugo. Ik hield niet van de "plugin-hel" van WordPress. Ghost moet self-hosted worden (er is ook ghost.org, maar dat is veel te duur), en bij elke versie-upgrade ging er iets kapot in de editor, waardoor het up-to-date houden een karwei was. Hugo is de beste van dat rijtje, maar uiteindelijk is het instellen nog steeds een gedoe. Terugkijkend denk ik dat ik eigenlijk op zoek was naar SaaS.

De dienst die ik het meeste mocht was Bear Blog. De prestaties zijn geweldig en ik hou van de productfilosofie. Echter was het extreme minimalisme voor mij een nadeel; ook de UX van de editor werkte niet voor mij. Als het me gratis had toegestaan om AdSense toe te voegen en afbeeldingen te koppelen, had ik waarschijnlijk gewoon Bear Blog gebruikt.

Ik wilde een blog bouwen die net zo snel is als Bear Blog, maar met iets meer functionaliteit en een betere bruikbaarheid. (Je kunt dit resultaat bekijken op bloffy.co).

Een score van 100 behalen op Lighthouse Performance

Caching

Het spreekt voor zich dat caching essentieel is. Als je caching goed regelt, verbeteren de prestaties dramatisch. Next.js maakt caching erg handig via ISR (Incremental Static Regeneration) en breidt fetch uit met een next-optie voor caching. Alleen al het goed gebruiken hiervan brengt je al ver.

Helaas kan een site overal ter wereld worden bereikt. Binnen dezelfde regio maakt caching de zaken snel, maar bezoekers uit andere regio's hebben nog steeds last van netwerklatentie.

De rol van het CDN

Dat is waar een CDN (Content Delivery Network) voor dient. Wanneer je een Next.js-app implementeert op Vercel, krijg je standaard een CDN. Vercel is ongelooflijk handig, maar het Pro-plan is direct prijzig, er is het probleem van de 'serverless cold start' en de afhankelijkheid van het Vercel-ecosysteem is niet te negeren.

Ik overwoog een andere CDN-service, zoals Cloudflare, omdat je daar veel grootschalige infrastructuur gratis kunt draaien. Hierbij realiseerde ik me dat ik de caching van Next.js eigenlijk niet meer nodig had. Het ongeldig maken van twee verschillende caches elke keer dat ik een bericht publiceer, maakte het beheer lastig. Voor een statische website is een CDN alleen voldoende. Dit was reden nummer één waarom ik Next.js niet meer nodig had.

Beeldoptimalisatie

Afbeeldingen zijn tegenwoordig vaak groter dan 10MB. Als je die onbewerkt op het web serveert, gaan je prestaties volledig achteruit. Beeldoptimalisatie is een flinke uitdaging: je moet schalen per apparaat, comprimeren en vervolgens het CDN beheren.

In een vorig project gebruikte ik een serverless functie (AWS Lambda) om afbeeldingen te comprimeren en te schalen zodra ze in storage (S3) landden, met CloudFront als CDN. Dat was een redelijke setup, maar vereiste veel onderhoud. Ditmaal heb ik gekozen voor de Image Transformations van Cloudflare. Zelfs in het gratis pakket krijg je 5.000 afbeeldingen, dus ik gebruik dit zonder twijfel.

De analyse van Next.js

Na alle caching en beeldoptimalisatie waren de scores voor de Next.js-blog (versie 16.1.6) erg hoog, maar nog niet 100. Ik wilde een schone 100, zonder asterisk.

Bij nader onderzoek bleek dat er een aanzienlijke hoeveelheid "legacy JavaScript" werd gevonden. Dit was geen code die ik zelf had geschreven, maar code die voortkwam uit de framework-runtime die Next.js in zijn bundle verzendt.

Aangezien dit een statische pagina is met nul interactie, en elke pagina is vooraf gerenderd met ISR, dacht ik de richtlijnen van Next.js goed te hebben gevolgd. Als experiment verwijderde ik elke client component en liet ik alleen server components over. De score veranderde niet.

Het probleem was niet alleen hydratatie, maar de client runtime die Next.js altijd meelevert, zelfs voor statische pagina's. Zelfs bij server components blijven de runtime chunks, hydratatie en de RSC-payload voor de router aanwezig. Dat komt omdat next/link en next/image zelf client components zijn ('use client'). Het gebruik van slechts deze twee is genoeg om de runtime binnen te halen en hydratatie te triggeren.

Als ik ook next/link en next/image verwijder, blijven de runtime van de App Router, de router en de error boundaries nog steeds bestaan. Er is geen officiële manier om dit naar nul te brengen; de enige optie was een hack: de scripts uit de gerenderde HTML strippen. Toen besefte ik: "Als ik dat ga doen, waarom gebruik ik Next.js dan überhaupt?"

Ongebruikte code

Het grootste deel van het JavaScript was code die niet nodig was. Alleen al de ongebruikte code bedroeg 230.460 bytes. De meeste statische websites hebben helemaal geen JavaScript nodig, en ongebruikt JavaScript is helemaal overbodig.

Het verwijderen van Next.js

Uiteindelijk besloot ik Next.js volledig te verwijderen voor de kwaliteit van de blog, zelfs als het maar om 5 punten ging. Mijn oordeel was dat ik de overbodige functies van Next.js op de lange termijn ook niet nodig zou hebben. Ik overwoog om volledig 'vanilla' te gaan, maar ik wilde de productiviteit en de infrastructurele voordelen van een framework niet opgeven.

Ik koos voor Hono. De technische filosofie "snel en simpel" was precies de oplossing die ik zocht, en de JSX-ondersteuning was een bonus. De migratiekosten waren bovendien niet te groot.

Het resultaat was een score van 100 over de hele linie. Er is geen JavaScript meer aanwezig en de ongebruikte code is scherp gedaald naar 407 bytes. Als bonus is de build-tijd met ongeveer 40% afgenomen. (De SEO-score van 69 in de test was opzettelijk, omdat die specifieke site zoekindexering blokkeerde voor testdoeleinden).

Prestaties: Hono versus Next.js

Een vergelijking van de "long task" (de taak die wordt uitgevoerd voordat er iets op het scherm wordt gerenderd) laat het volgende zien:

  • Next.js (alleen server components): 309,3 ms
  • Hono: 68,01 ms

Dit is een verbetering van ongeveer 78%.

Heeft jouw statische site echt Next.js nodig?

Draagt een JS-payload, inclusief hydratatie, echt iets bij aan een statische site? Ik denk het niet. Het laat onnodig JavaScript achter, waardoor het in de meeste gevallen simpelweg niet nodig is.

Dat gezegd hebbende: voor een applicatie met veel interactie is Next.js een geweldig framework. Sterker nog, de management-app voor deze blog is gebouwd met Next.js!