<ForrigeUke uke="43" år="2025" />
Next.js 16, varige funksjoner og kritikk mot direktiver - i denne ukens ForrigeUke.
Dette var uken for sjøhester 🐠, økonomiske snarveier 💵, anstrengte forhold 💔 - og 2944 ting som skjedde i frontend-verdenen.
«<ForrigeUke /> er en artikkelserie som oppsummerer hva som skjedde i frontend-verden i uken som var.»

The next Next(.js) 😻
Next.js 16 er her! 🥳 Og her er to veldig gode grunner til at du 🫵 bør oppgradere:
Få bedre kontroll over cachen din 💾
Caching har fått en skikkelig overhaling i Next.js 16. Med Cache Components blir det nå både enklere og mer eksplisitt å styre hva som skal caches.
For å forstå caching i Next.js er det lurt å være kjent med hvordan rendering foregår. Vi skiller mellom statisk og dynamisk rendering av sider. Med statisk rendering blir sider rendret ved byggetid. Resultatet blir cachet og kan gjenbrukes mellom forespørsler. Men hvis innholdet på en side er dynamisk og baserer seg på cookies eller eksterne data, så kan ikke siden nødvendigvis bli rendret på forhånd. Da blir siden dynamisk, og blir rendret idet brukeren går inn på den. Dynamiske sider kan bruke Next.js sin data cache for å lagre resultatet av den dynamiske data-innhentingen. Etter hvert vil cachen bli utdatert, og må bli revalidert. Hvis brukeren går inn på en side etter revalidering, hentes de dynamiske dataene på nytt, istedenfor å bruke cachen.
En side i en web-applikasjon er sjeldent kun statisk eller kun dynamisk. Den består ofte av forskjellige komponenter, hvorav noen kan være statiske, mens andre må være dynamiske. Partial Pre-Rendering (PPR) er hvordan Next.js kombinerer statisk og dynamisk rendering. Med en gang brukeren går inn på en side, vil serveren sende et statisk skall av cachet innhold for å få en rask initiell innlasting. Dynamiske seksjoner wrappet i Suspense vil samtidig begynne å rendre på serveren, og blir levert til brukeren når de er klare.

Før cache components prøvde Next.js å automagisk optimalisere sider som statiske, som kunne gi uventet oppførsel på dynamiske sider. Man kunne opte ut av cachingen med fetch(url, { cache: 'no-store' }) og export const dynamic = 'force-dynamic'. Med cache components tar Next.js utgangspunkt i at alle sider er dynamiske. Dermed må du selv markere hvilke funksjoner, pages eller layouts som kan caches:
export async function hentProdukter() {
'use cache'
const data = await db.query('SELECT * FROM produkter')
return data
}Enkelte typer datainnhenting kan være dynamiske, men likevel caches; for eksempel forespørsler mot databaser eller API-er. Andre typer data, som headere, cookies eller søkeparametere, skal derimot aldri caches på serveren. Slike verdier er ofte knyttet til en spesifikk bruker, og caching kan føre til at data utilsiktet blir delt mellom brukere. Derfor er Next.js-APIer som returnerer denne typen informasjon (såkalte 'Runtime APIs') ikke tillatt å bruke i komponenter som benytter use cache-direktivet. Bruker du use cache, vil du bli bedt om å pakke runtime-API-kall inn i en Suspense. Hvis du likevel ønsker å cache cookies, headere eller søkeparametere, kan du gjøre det med use cache: private-direktivet. Mens use cache lagrer ting på serveren, vil use cache: private alltid cache på klienten, og er dermed isolert til enkelte brukere.
Så sett i gang, oppgrader prosjektet ditt til Next.js 16, og få kontroll på cachen din!
Hjelp dine robot-venner 🤖
Vercel har også kommet ut med en "Model Context Protocol" (MCP)-server lansert i next-devtools-mcp-pakken, som åpner for KI-assistert feilsøking av Next.js-applikasjoner!
MCP-serveren kobler seg automatisk til Next.js-prosjektet ditt, og gir KI-en tilgang til applikasjonens tilstand og relevant kjøretidsinformasjon. For eksempel kan den hente stack traces for diverse Next.js-feil. KI-en får også tilgang til Next.js sin kunnskapsdatabase og dokumentasjon. Det betyr at KI nå kan hjelpe deg med å migrere til Next.js 16! I tillegg inneholder pakken hjelpeverktøy for å konfigurere caching med det nye use cache-direktivet, og teste applikasjoner i nettleseren gjennom en integrasjon med PlayWright MCP.
Resultatet er altså langt mer treffsikre forslag, og bedre feilsøkingshjelp.
Les oppsett-guiden og sett KIen i gang med en Next.js-oppgave mens du leser resten av artikkelen 🤓
"use workflow" ⚙️
På torsdag kom Vercel ut med Workflow Development Kit (WDK); et open-source rammeverk som lar deg gjøre funksjoner til langlevde workflows. I en workflow kan du kjøre ett steg, pause, og vente en hel uke eller måned før du fortsetter til neste steg. Imellom kjøringene kan web-applikasjonen din oppdateres eller til og med skrus av og på. Best av alt brukes nesten ingen prosesseringskraft imellom stegene.
Det er en elegant løsning på et klassisk problem: hvordan kjøre langvarige prosesser uten å blokkere serveren. Og når stadig flere web-applikasjoner skal integrere AI-modeller som bruker sekunder, minutter eller til og med timer på å svare, blir slike mekanismer uvurderlige.
Eksempelet nedenfor viser hvordan workflows kan brukes i praksis til å sende en e-post til en bruker 1 måned etter registrering. Vi markerer workflow-funksjonen med "use workflow"-direktivet. Workflowen betsår av ett eller flere steg. Hvert steg defineres i en egen funksjon, markert med et "use step"-direktiv.
import { sleep } from 'workflow';
export async function registrerOgSendPromo(epost: string) {
"use workflow";
const bruker = await opprettBruker(epost);
await sleep("1 month");
await sendPromoEpost(epost);
}
async function opprettBruker(epost: string) {
"use step";
// ...
}
async function sendPromoEpost(epost: string) {
"use step";
// ...
}Workflowen skal være deterministisk; den skal alltid kalle stegene i samme rekkefølge, gitt den samme innmaten. Den er kun ansvarlig for orkestrering og kjøring av steg. Den skal altså verken gjøre nettverkskall, kobles til databaser, eller bruke I/O. Stegene kan være ikke-deterministiske. De kjøres i et vanlig Node.js kjøretidsmiljø, og kan hente data fra eksterne kilder. Hvis et steg feiler, kan workflowen bli konfigurert til å kjøre det på nytt.
I Next.js kan workflows og steg defineres i all server-kode, inkludert routes og server actions.
Hvis du er litt som meg, får du ganske mye glede ut av å forstå hvordan ting virker under panseret. Så hvordan kan disse tilsynelatende enkle direktivene gjøre funksjoner varige ("durable")? Heldigvis har Vercel skrevet strålende god dokumentasjon som vi kan dykke ned i sammen 🤓
Gitt workflowen i eksempelet over, vil WDK transformere koden 3 ganger, og lagre hver transformasjon hver for seg. Den transformerer
1. Stegene i workflowen ("step mode")
2. Workflow-funksjonen ("workflow mode")
3. Klient / applikasjonskoden som kjører i web-applikasjonen vår ("client mode")
1. Step mode 👣
I "step mode" lages en oversikt over hvilke steg vi har tilgjengelig. Når en funksjon blir markert med “use step”, lar WDK funksjonsinnholdet forbli uendret. Dette er fordi funksjonen skal kjøres i et helt ordinært kjøretidsmiljø, med tilgang til Node-APIer, filsystemet, databaser osv.
Hver step-funksjon blir registrert med registerStepFunction(), som tar inn to argumenter: selve funksjonen som skal kjøre, og en ID-streng på formatet "step//{filsti}//{funksjonsnavn}". Denne IDen blir senere brukt av workflowen til å identifisere hvilken step-funksjon som skal kalles.
Kompilatoren putter altså følgende kode i en step.js-fil.
import { registerStepFunction } from "workflow/internal/private";
async function opprettBruker(epost: string) {
// ...
}
async function sendPromoEpost(epost: string) {
// ...
}
registerStepFunction("step//workflows/min-workflow.js//opprettBruker", opprettBruker);
registerStepFunction("step//workflows/min-workflow.js//sendPromoEpost", sendPromoEpost);2. Workflow mode 🤹
Poenget med workflow mode er å identifisere alle workflows, og knytte de til registrerte steg. WDK lar selve workflow-funksjonen forbli uendret. Kall til step-funksjoner blir derimot erstattet med funksjonskall til globalThis[Symbol.for("WORKFLOW_USE_STEP")]("<steg-ID>"). Denne hooken sjekker om et steg har blitt kjørt før. Hvis ja, returnerer den resultatet av kjøringen. Hvis ikke, putter den steget på en kø for at det skal bli kjørt i en bakgrunnsprosess.
Koden nedenfor blir puttet i en flow.js-fil av kompilatoren.
import { sleep } from 'workflow';
export async function registrerOgSendPromo(epost: string) {
const bruker = await opprettBruker(epost);
await sleep("1 month");
await sendPromoEpost(epost);
}
// registrer workflow-ID
handleUserSignup.workflowId = "workflow//workflows/min-workflow.js//registrerOgSendPromo";
async function opprettBruker(epost: string) {
globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//workflows/min-workflow.js//opprettBruker")
}
async function sendPromoEpost(epost: string) {
globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//workflows/min-workflow.js//sendPromoEpost")
}Dette er nøkkelen i hvordan workflows kan pause i lang tid imellom kjøringer. En workflow kjører nemlig alltid fra starten av, men kjører kun stegene som ikke er gjennomført enda. Fullførte steg returnerer cachede verdier uten å faktisk kjøre på nytt. Siden både workflowen og alle stegene er persistert i henholdsvis flow.js og step.js, kan serveren restarte, og applikasjonen kan bli oppdatert uten at det påvirker kjøringen.
3. Client mode 🛑 ✋
Til slutt vil WDK sørge for at vi starter workflowen på rett måte. Den må nemlig alltid startes med WDKs egne start()-funksjon. Gjør vi et direkte funksjonskall til workflow-funksjonen, bør vi få en feil. Derfor erstattes den med en throw Error("You attempted to execute..."). I tillegg får den en referanse til workflow-IDen, som kan brukes av start-funksjonen til å forstå hvilken workflow den skal starte.
export async function registrerOgSendPromo(epost: string) {
throw Error("You attempted to execute...")
}
registrerOgSendPromo.workflowId = "workflow//workflows/min-workflow.js//registrerOgSendPromo"Til sammen gjør disse tre transformasjonene at det blir mulig for use workflow å huske hvor den slapp. Når workflowen pauses, for eksempel i én måned, ligger ikke en prosess og spinner i bakgrunnen. I stedet blir tilstanden lagret, og når tiden er inne, gjenopptas kjøringen fra neste steg.
Men det reiser også et interessant spørsmål: hvor langt skal vi egentlig la disse magiske direktivene gå?
Direktiver? Æsj 🤢 Eller...?
Selv om "use workflow" er imponerende, er det også et eksempel på hvordan rammeverk nå legger stadig mer semantikk inn i tilsynelatende enkle strenger. Med introduksjonen av use cache og use workflow har Vercel nemlig begynt å lene seg ganske tungt på direktiver, noe ikke alle er like begeistret for. Tanner Linsley, skaperen av TanStack, mener det er på tide å trekke i bremsen, og har nylig delt sine tanker i blogginnlegget Directives and the Platform Boundary.
Linsley peker på at noen direktiver, som use client og use server, gir mening fordi de kommuniserer noe helt konkret, nemlig hvor koden kan kjøre, og fungerer som et felles koordineringspunkt for flere rammeverk. De har allerede blitt adoptert av flere rammeverk, deriblant Waku. Problemet oppstår altså når direktiver blir knyttet til enkelte rammeverk, eller bare kan brukes i spesifikke kontekster, slik som use cache og use workflow.
Med stadig flere direktiver beveger vi oss derimot inn i et mer uklart landskap. Det er ikke lenger åpenbart hvor funksjonaliteten kommer fra, eller hvem som "eier" den. For mange kan en direktiv-streng se ut som en del av selve språket JavaScript, mens andre vil tro at det er noe rammeverkspesifikt. Hvis vi i stedet hadde brukt eksplisitte imports, hadde man enkelt sett hvor funksjonen kommer fra. Et direktiv som bare består av en magisk streng, gjør koden vanskeligere å lese, forstå og feilsøke.
Linsley mener at vi er på vei inn i en ond sirkel. Når én leverandør introduserer et nytt direktiv, venner utviklere seg til det, og begynner å forvente å kunne bruke det i andre rammeverk. Da føler andre leverandører seg presset til å støtte det samme direktivet, ofte med egne implementasjonsdetaljer. Resultatet er at to rammeverk kan tolke de samme direktivene på litt forskjellige måter.
I tillegg reduserer direktiver kompleks oppførsel til en enkel boolean: enten er funksjonen en "workflow", eller så er den ikke det. Dette blir fort en begrensning når man trenger mer konfigurasjon. Ville det ikke vært bedre om vi kunne ha importert en step-funksjon-wrapper med alle konfigurerbare parametere i et tydelig og mer utviklervennlig grensesnitt?
Hva synes du 🫵 om direktiver? Har Vercel gått for langt? Bør de kanskje ... "use common sense"? 🥁
Det var alt for denne gang. Vi sees neste uke! 👋
