Hvorfor vi byggede om fra bunden
Build444's tidligere side fungerede. Den virkede. Men den havde opsamlet den slags teknisk gæld der gør at hver ny funktion tager tre gange så lang tid som den burde.
Vi havde brug for tosproget support (engelsk og dansk). Vi ville sælge ydelser direkte gennem siden med Stripe. Og ærligt talt ville vi bruge vores egen stak. Hvis vi fortæller kunder at de skal investere i hurtige, moderne websites, bør vores egen side være et.
Så i starten af 2026 rev vi den ned og byggede op på Next.js 16, React 19, Tailwind CSS v4 og Turbopack. Det her er bygge-loggen. Gevinsterne, fejlene og de ting vi ville gøre anderledes.
Stakken
Next.js 16.1.6 med App Router. Vi gik all-in på server components. De fleste af vores sider har nul client-side JavaScript medmindre de har brug for interaktivitet. Shop-, checkout- og formularsider er client components. Alt andet er server-renderet.
React 19. Den nye use hook og server actions simplificerede meget af vores data fetching. Vi bruger stadig useEffect et par steder, men langt mindre end før.
Tailwind CSS v4.1.18. Den nye CSS-first konfiguration er renere end den gamle JavaScript-config. Men migrationen var ikke gnidningsfri. Mere om det nedenfor.
Turbopack. Next.js 16 bruger Turbopack som standard til dev-serveren. Hot module replacement er hurtig. Byggetider faldt fra 12 sekunder til under 2. Men Turbopack har edge cases der kostede os reel debugging-tid.
Stripe SDK 20.3.1. Vi håndterer checkout server-side med Stripes seneste SDK. Moms-ID indsamling og automatisk fakturaoprettelse er bygget ind. Webhook-handleren videresender events til vores n8n automatiserings-workflows.
Turbopack: hurtig, men pas på kanterne
Turbopack erstattede Webpack som standard dev-bundler i Next.js 16. For det meste virker det bare. Sider genindlæses på millisekunder. Udvikleroplevelsen er mærkbart bedre.
Så ramte vi CSS-fejlen.
CSS-strippings-problemet
Da vi tilføjede nye CSS-klasser til vores globals.css, forsvandt multi-line formaterede regler lydløst. Ingen fejl. Ingen advarsel. Stilene var bare ikke der.
Vi brugte to dage på det her. Tjekkede browser-inspektøren. Tjekkede build-outputtet. Tilføjede !important i desperation. Ingenting.
Løsningen viste sig at være absurd: skriv nye CSS-regler som enkeltlinje-deklarationer.
/* Dette bliver strippet af Turbopack */
.dropdown-overlay {
display: flex;
gap: 8px;
background: #faf9f6;
}
/* Dette virker fint */
.dropdown-overlay { display: flex; gap: 8px; background: #faf9f6; }
Eksisterende multi-line regler var ikke påvirket. Kun nye tilføjede under hot module replacement. @keyframes blokke og font-family: var(...) i nye regler forårsagede også stripping. Vi skiftede til hardkodede værdier i stedet for CSS-variabler i nye overlay- og dropdown-regler.
Genstart af dev-serveren løste det ikke. Kun enkeltlinje-formatet virkede. Vi indberettede en issue. Per februar 2026 er den stadig åben.
Modulopløsnings-problemet
Tailwind v4 bruger @import "tailwindcss" som entry point. Det virker med Webpack. Med Turbopack fejler det. Turbopacks enhanced-resolve understøtter ikke style-betingelsen i package exports.
Løsningen: brug @import "tailwindcss/index.css" i stedet. Simpelt, når man ved det. Frustrerende når man ikke gør.
Tosproget arkitektur uden i18n-bibliotek
Vi havde brug for engelsk og dansk. De fleste guider anbefaler next-intl eller next-i18next. Vi prøvede begge. De tilføjede kompleksitet, bundle-størrelse og konfigurationsoverhead der ikke matchede vores use case.
Vi har to sprog. Ikke tyve. Så vi byggede det selv.
Middleware rewrites
En lille middleware-fil fanger indkommende requests og omskriver stier baseret på en sprogcookie eller Accept-Language headeren:
/aboutserverer engelsk/da/aboutserverer dansk- Førstegangsbesøgende detekteres via browsersprog
Middlewaren er 47 linjer. Den håndterer alt. Intet bibliotek nødvendigt.
Data-lang CSS-tilgangen
For komponenter der har brug for forskellig styling per sprog (danske ord er længere end engelske), bruger vi en data-lang attribut på HTML-elementet og targeter det i CSS:
[data-lang="da"] .hero-title { font-size: 2.2rem; }
[data-lang="en"] .hero-title { font-size: 2.5rem; }
Ingen JavaScript. Ingen re-renders. Bare CSS der gør hvad CSS gør.
Indholdsfiler per sprog
Blogindlæg, servicebeskrivelser og produkttekster ligger i separate filer per sprog: en.mdx og da.mdx. Blogsystemet læser den rigtige fil baseret på URL-sproget. Ingen oversættelsesnøgler. Ingen JSON-ordbøger. Bare skriv indholdet på hvert sprog.
Denne tilgang ville ikke skalere til 10 sprog. For to er den simplere end noget bibliotek vi prøvede.
Performance-resultater
Vi kørte Lighthouse-audits før og efter genopbygningen. Tallene taler for sig selv.
| Metrik | Gammel side | Ny side | |--------|-------------|---------| | Performance | 67 | 98 | | Accessibility | 82 | 100 | | Best Practices | 78 | 100 | | SEO | 89 | 100 | | LCP | 3,8s | 1,1s | | CLS | 0,18 | 0,01 | | INP | 380ms | 45ms |
Largest Contentful Paint faldt fra 3,8 sekunder til 1,1. Det er et direkte resultat af server components. Den gamle side indlæste en 240KB JavaScript-bundle før den kunne rendere hero-sektionen. Den nye side sender HTML.
Cumulative Layout Shift gik fra 0,18 (fejlende) til 0,01 (godkendt med god margin). Vi opnåede det ved at sætte eksplicitte dimensioner på hvert billede og bruge CSS aspect-ratio til responsive containere.
Interaction to Next Paint blev bedre fordi de fleste sider slet ikke har client-side JavaScript. Der er ingenting der blokerer main thread.
Shoppen: 30+ produkter, ét checkout-flow
Vi byggede en shop med 30+ produkter fordelt på 7 kategorier. Hver kategori har et farvesystem defineret i en enkelt TypeScript-fil. Produkter spænder fra engangs SEO-analyser til månedlige aftaler.
Checkout-flowet bruger Stripes server-side SDK. Ingen Stripe.js på klienten. Kunden klikker "Køb," vi opretter en Checkout Session server-side og redirecter til Stripes hosted side. Det holder vores bundle lille og vores PCI-compliance simpel.
Efter betaling sender Stripe en webhook til vores API-route, som videresender eventet til n8n for automatiseret levering. SEO-rapport produktet udløser et 48-node workflow der kører automatiseret analyse og leverer en PDF-rapport via email.
Hvad vi ville gøre anderledes
Start med Turbopack-test fra dag ét. Vi byggede det meste af CSS'en med Webpack og skiftede til Turbopack sent i projektet. Det var der CSS-fejlene dukkede op. Hvis vi havde startet med Turbopack, ville vi have opdaget enkeltlinje-workarounden tidligere og sparet to dage.
Brug CSS modules i stedet for globale klasser til nye komponenter. Vores globals.css er 800+ linjer. CSS modules ville have givet os bedre scoping og helt undgået Turbopack-strippings-problemet.
Sæt visuel regressionstest op. Vi fangede de fleste layout-fejl manuelt. Et værktøj som Playwrights screenshot-sammenligning ville have fanget CLS-problemerne hurtigere, især på tværs af de to sprogvarianter hvor tekstlængden er forskellig.
Planlæg den tosprogede indholdspipeline tidligere. At skrive indhold på to sprog tager dobbelt så lang tid som ét sprog. Vi undervurderede tiden til de danske versioner. Næste gang ville vi budgettere for oversættelse fra start, ikke bolte det på efter det engelske indhold er færdigt.
Var det det værd?
Next.js 16 med React 19 og Turbopack er en reel performance-opgradering. Vores Lighthouse-scores gik fra middelmådige til næsten perfekte. Server components eliminerede det meste af vores client-side JavaScript. Udvikleroplevelsen er hurtigere.
Men toolingen er stadig ung. Turbopack har reelle fejl. Tailwind v4's import-syntaks virker ikke ud af boksen. Man rammer edge cases der endnu ikke er dokumenteret.
Hvis du starter et nyt projekt i 2026, er denne stak det rigtige valg. Hvis du migrerer et eksisterende, skal du budgettere ekstra tid til Turbopack-særhederne. De kan løses, men de er ikke indlysende.
Vores side er nu hurtig, tosproget og sælger produkter direkte gennem Stripe. Genopbygningen tog seks uger. For hvad vi fik ud af det, var det tid godt brugt.
