Jeg fortrød min første SPA – sådan vælger jeg mellem SPA og MPA i dag
Mit første rigtige hobbyprojekt blev en SPA i React. Det skulle bare have været en simpel side med et par undersider. I stedet fik jeg build-fejl, mærkelig routing og dårlig SEO. Siden da er jeg blevet ret nærig med, hvornår noget faktisk skal være en SPA.
I den her artikel gennemgår jeg, hvornår en SPA giver mening, hvornår en klassisk MPA er smartere, og hvordan du kan træffe valget uden at drukne i buzzwords.
Hvad er en SPA og en MPA, uden fluff
Hvad er en SPA?
SPA står for “Single Page Application”. Ideen er:
- Brugeren loader én HTML-side én gang.
- JavaScript henter data og skifter indholdet ud i browseren, uden fulde side-reloads.
- Routing (når URL’en skifter) styres i JavaScript.
Typisk: React, Vue, Svelte, Angular og vennerne. Ofte bundet sammen af en bundler som Vite, Webpack eller lignende.
Oplevelsen for brugeren kan føles super hurtig, fordi der ikke er fulde reloads hver gang. Men du betaler med mere kompleksitet i JavaScript-land.
Hvad er en MPA?
MPA står for “Multi Page Application”. Det er det klassiske web:
- Hver URL svarer til en rigtig HTML-side.
- Når du klikker på et link, laver browseren en ny request og får en ny HTML-side.
- Routing styres af serveren (eller bare af filsystemet, hvis det er statiske filer).
Typisk: klassiske PHP-sider, WordPress, Django, Rails, statiske sites bygget med f.eks. Eleventy eller et simpelt HTML/CSS setup.
Her er oplevelsen lidt mere “klik, load, ny side”. Men til gengæld er SEO og deploy ofte langt nemmere.
6 scenarier og hvad der typisk giver mest mening
Jeg plejer at tænke i scenarier i stedet for teknologier. Her er seks typiske situationer jeg selv (og mange andre) rammer.
1. Portfolio, personlig side eller simpel landing page
Du har måske:
- Forside
- Om mig
- Projekter
- Kontakt
Der er næsten ingen dynamik. Måske en kontaktformular og lidt små animationer.
Mit valg: MPA eller ren statisk HTML. 100 %.
Hvorfor:
- Ingen grund til at bygge routing, bundling og state-håndtering for 4 sider.
- Du kan smide det på et simpelt webhotel eller GitHub Pages uden ekstra konfiguration.
- Google kan læse indholdet direkte, uden at vente på JavaScript.
Hvis du vil lege med JavaScript, så brug det til små ting: en lightbox, en fiks animation, eller en kontaktformular med AJAX. Ikke hele arkitekturen.
2. Blog eller indholdssite
Forestil dig et site med 10-500 artikler. Måske kategorier, tags, søgning og lidt filtrering.
Mit valg: Stadig MPA som udgangspunkt, gerne som statisk site.
Eksempler:
- Statisk site generator (SSG) som Astro, Eleventy eller Hugo.
- Et simpelt CMS med server-rendering (WordPress, Ghost, osv.).
Du kan sagtens få det til at føles hurtigt, selv om det er MPA. Brug f.eks. prefetch af links eller lidt JavaScript ovenpå. Men alle artiklerne ligger som rigtige HTML-sider, som søgemaskiner elsker.
Hvis du vil nørde mere i statiske sider, så har vi en intro til HTML og struktur, der er et godt sted at starte.
3. Dashboard eller webapp med login
Nu taler vi ting som:
- Admin-paneler
- Analytics-dashboards
- Projektstyringsværktøjer
- Apps hvor brugeren ændrer data hele tiden
Mit valg: Ofte SPA, eventuelt med server-rendering ovenpå (f.eks. Next.js).
Hvorfor:
- Brugeren interagerer konstant: filtrerer, sorterer, opdaterer, åbner modaler.
- Der er masser af delt state (data, filters, brugerens valg), som en SPA er god til at håndtere.
- Du kan holde det hele i én “shell” og bare skifte indholdet ud.
SEO er ofte mindre vigtig her, fordi meget ligger bag login. Så du kan godt acceptere mere JavaScript-kompleksitet for en bedre interaktiv oplevelse.
4. Simpelt værktøj uden login
Eksempler:
- En lille “beregn din skat”-widget
- Et password-generator værktøj
- En farve-palette generator
Mit valg: MPA med lidt JavaScript, eller en meget lille SPA uden tungt framework.
Hvis appen bare er én side, kan du ofte slippe afsted med en simpel <script>-fil, hvor du håndterer al logikken.
<input id="length" type="number" />
<button id="generate">Generer</button>
<pre id="result"></pre>
<script>
const lengthInput = document.getElementById("length");
const generateBtn = document.getElementById("generate");
const result = document.getElementById("result");
generateBtn.addEventListener("click", () => {
const length = Number(lengthInput.value) || 12;
result.textContent = generatePassword(length);
});
function generatePassword(len) {
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
let out = "";
for (let i = 0; i < len; i++) {
out += chars[Math.floor(Math.random() * chars.length)];
}
return out;
}
</script>
Her giver et helt SPA-setup sjældent mening. Du får bare mere build-opsætning at vedligeholde.
5. E-commerce med mange produktsider
Shop med:
- Produktsider
- Kategorier
- Søgning og filtrering
- Kurv og checkout
Mit valg: Ofte en hybrid: MPA/SSR til produktsiderne + SPA-agtig oplevelse i kurv/checkout.
Produktsiderne skal rangere godt i søgning. De har typisk meget tekst, billeder og metadata. Så server-renderede HTML-sider er oplagte.
Kurv og checkout må gerne være meget interaktive og hurtige. Her kan en SPA-agtig del give mening, f.eks. en React-komponent på en ellers klassisk side.
6. Læringsprojekter og portfolios til at vise kode frem
Hvis din primære grund til at vælge SPA er “jeg vil lære React” eller “jeg vil have noget at vise på GitHub”, så er det faktisk et helt legitimt argument.
Mit valg: Gør det klart for dig selv, om det er et læringsprojekt eller et produktionsprojekt.
- Til læring: byg gerne en SPA, men anerkend at du vælger ekstra kompleksitet for at lære.
- Til noget der skal være færdigt hurtigt: vælg det simpleste der kan virke (ofte MPA).
Det kan også være en ret god øvelse at bygge samme lille app både som MPA og som SPA for at mærke forskellen.
Hvad bliver sværere når du vælger SPA?
SPA’er kan føles “moderne”, men de kommer med et kompleksitetsbudget. Her er de ting, jeg selv tit glemmer i begejstringen.
1. Routing og 404’er
I en SPA styrer du routing i JavaScript, f.eks. med React Router. Men serveren skal stadig vide, hvad den skal gøre, når nogen går direkte ind på /dashboard.
Hvis du ikke konfigurerer det, så sker der typisk det her:
- Brugeren skriver
/dashboardi adresselinjen. - Serveren kender ikke den sti og returnerer en 404.
- JavaScript når aldrig at starte din SPA-router.
Typisk fix: Konfigurer serveren til at sende index.html tilbage for alle ruter, og lad så JavaScript finde den rigtige “side”.
Det kan være alt fra en lille indstilling i Netlify til konfiguration i Nginx. Ikke rocket science, men det skal gøres.
2. SEO og initial render
Som udgangspunkt loader en ren SPA én tom HTML-shell og henter indholdet med JavaScript.
Teknisk set kan moderne søgemaskiner eksekvere JavaScript, men det er mere skrøbeligt, og du har mindre kontrol over, hvornår den ser hvad.
Så du skal ofte have:
- Server-side rendering (SSR)
- Static site generation (SSG)
- Hydration (server-renderet HTML der gøres interaktiv i browseren)
Når du når dertil, er du ikke længere i “simpel SPA”-land. Så er du i Next.js/Nuxt/Astro-territorium. Fint, men det er ekstra ting at forstå.
3. Caching og deploy
En ren SPA bliver ofte bygget til et bundtede filer:
index.htmlmain.[hash].jsvendor.[hash].js
Her skal du tænke over:
- Cache headers (må browseren cache
.js-filerne længe?) - Hvad sker der, hvis du deployer en ny version, mens brugeren har en gammel cache?
Det er ikke svært at gøre nogenlunde fornuftigt, men det er flere beslutninger. Især hvis du sammenligner med et simpelt MPA-setup, hvor hver HTML-side bare er en fil.
4. Initial load og bundle-størrelse
SPA’er trækker ofte et helt framework + din egen kode ned i ét bundt (eller flere). Det betyder:
- Lidt langsommere første load, især på mobilen.
- Du skal tænke over code-splitting og lazy loading.
Et simpelt statisk site med ren HTML/CSS og en lille smule JS kan føles markant hurtigere, simpelthen fordi der er mindre der skal hentes og parses.
Hvad bliver sværere når du vælger MPA?
MPA’en er ikke bare ren idyl. Den har sine egne udfordringer, især hvis du vil have en meget app-agtig oplevelse.
1. Delt state på tværs af sider
Tænk på ting som:
- Filters der skal huskes mellem sider
- En kurv der skal følge brugeren rundt
- En “wizard”-flow over flere sider
I en SPA kan du holde det hele i én central state (Redux, context, signals, you name it). I en MPA skal du typisk:
- Gemmes state i URL-parametre
- Bruges sessioner eller cookies
- Sendes ting frem og tilbage i forms
Det er ikke umuligt, men det er mere manuelt arbejde.
2. Følelsen af “snappy” UI
MPA giver fulde side-reloads. Det betyder:
- Hele DOM’en ryger og bygges igen.
- Nogle ting føles tungere: tab-views, små konfigurationer, live-validering.
Du kan kompensere med:
- AJAX requests (fetch/XHR) til dele af siden
- Progressive enhancement: du har en basis-HTML-løsning, og så lægger du JS ovenpå til bedre UX
Hvis du opdager, at du manuelt er ved at bygge et mini-SPA oven på en MPA, så kan det være et tegn på, at du var tættere på en rigtig SPA fra starten.
3. Komplekse interaktioner
Ting som drag and drop, live charts, real-time samarbejde og lignende er bare markant rare at bygge i et setup, hvor du har en langtidsholdbar JavaScript-app kørende.
Det kan godt bygges oven på en MPA, men det ligner hurtigt en SPA inde i en MPA. Det er lidt ligesom at have en hel lejlighed inde i din stue.
Et simpelt beslutningsframework – 7 spørgsmål
Når jeg står med et nyt projekt, tænker jeg ikke “SPA vs MPA” først. Jeg tænker: “Hvor meget kompleksitet har jeg råd til nu og i fremtiden?”.
Her er 7 spørgsmål jeg bruger som mental tjekliste.
1. Hvor vigtig er SEO på indholdet?
- Meget vigtigt (blog, marketing-site, dokumentation): start med MPA eller SSR/SSG.
- Mindre vigtigt (dashboard, intern app): SPA er mere åbenlyst.
2. Hvor meget interaktivitet forventer du?
- Mest læse, lidt klikke: MPA med lidt JS.
- Konstant interaktion, mange små UI-ændringer: SPA eller hybrid.
3. Har du brug for delt state på tværs af mange “sider”?
- Hvis ja: hælder mod SPA.
- Hvis nej: MPA er ofte lettere.
4. Hvem skal vedligeholde det om 1-2 år?
- Hvis det kun er dig, og du kan lide at nørde frameworks: SPA kan være fint.
- Hvis det skal kunne løftes af flere med blandet niveau: simpelt MPA-setup er ofte nemmere at overtage.
5. Hvor meget build-opsætning vil du slås med?
- Hvis du bliver træt bare af tanken om bundlers, env-filer og deployment pipelines: MPA.
- Hvis du allerede er hjemmevant i Vite, bundling og deployment til f.eks. Vercel: SPA/SSR kan give mening.
6. Skal appen fungere nogenlunde uden JavaScript?
- Hvis ja (offentlige tjenester, kritiske flows): MPA/SSR med progressive enhancement.
- Hvis nej (interne værktøjer, spil, dashboards): SPA er helt fint.
7. Bygger du for at lære, eller for at levere hurtigt?
- For at lære: tillad dig selv mere kompleksitet (SPA, frameworks, SSR).
- For at levere: vælg den løsning, der kræver færrest bevægelige dele.
Hvis du vil øve dig i netop den slags afvejninger, kan du også kigge på vores andre artikler om webudvikling og arkitektur. De går mere ned i, hvordan du tænker i lag og ansvar.
Bonus: Hybrid-løsninger forklaret uden store ord
Mange moderne setups prøver at kombinere det bedste fra SPA og MPA. Du har måske stødt på ord som SSR, SSG og ISR. Lad os tage lynversionen.
Server-side rendering (SSR)
SSR betyder, at serveren genererer HTML til hver request, men stadig med et frontend-framework i spil.
Flowet:
- Bruger besøger
/blog. - Serveren kører din React/Vue-kode på serveren, laver HTML.
- Browseren får færdig HTML, viser den hurtigt.
- JavaScript “hydrater” siden, så den bliver interaktiv.
Det føles som en SPA, efter initial load, men søgemaskiner får rigtig HTML fra starten.
Static site generation (SSG)
SSG betyder, at du bygger HTML-siderne på forhånd, f.eks. når du deployer.
Flowet:
- Du kører et build.
- Build-processen henter data (f.eks. blogposts) og genererer rigtige HTML-filer.
- Du uploader filerne til et statisk hosting-miljø.
Det er glimrende til blogs, dokumentation, marketing-sites. Frameworks som Astro, Next.js og andre kan lave SSG og bagefter lægge SPA-adfærd ovenpå dele af siden.
Hydration i menneskesprog
Hydration er bare navnet for det øjeblik, hvor JavaScript tager over en allerede renderet HTML-side og gør den interaktiv.
Hvis du f.eks. server-render en liste med todo’s, vil HTML’en allerede vise dem, inden JS loader. Når JS så loader, binder den events til knapper, formularer osv., så du kan interagere med dem.
Det er derfor mange moderne frameworks kan give dig det bedste fra begge verdener: hurtig initial HTML + interaktiv SPA-oplevelse.
SPA vs MPA i min egen værktøjskasse
Hvis jeg koger min egen praksis ned, ender jeg typisk sådan her:
- Små sites, portfolier, simple landing pages: klassisk MPA eller statisk site.
- Indholdstunge sites (blogs, docs): MPA/SSG, evt. lidt JS ovenpå.
- Dashboards, interne værktøjer, komplekse interaktioner: SPA eller SSR-baseret framework.
- Læringsprojekter: hvad jeg har lyst til at blive bedre til, ikke hvad der er “optimalt”.
Hvis du vil styrke fundamentet, før du kaster dig ud i SPA-land, er det ofte en god ide at have styr på basis som CSS layout og basal JavaScript. Det gør frameworks langt mindre skræmmende.
Afslutning – og den SPA jeg stadig ikke gider røre ved
Den første SPA jeg byggede, ligger stadig gemt i en eller anden repo med et alt for ambitiøst navn. Jeg har ikke åbnet den i årevis, mest fordi jeg ved, at jeg skal kæmpe med gammel tooling, mærkelig routing og halvfærdig state-håndtering.
Hvis jeg skulle bygge det samme projekt i dag, ville jeg nok lave det som et lille statisk site med lidt JavaScript drysset ovenpå. Og gemme SPA’en til det projekt, der rent faktisk har brug for det.
Det er i virkeligheden det vigtigste her: du behøver ikke altid vælge det tungeste værktøj for at være “rigtig” udvikler. Vælg det, der gør det lettere for dig at blive færdig, og gem de vilde SPA-setup til de projekter, der faktisk fortjener dem.









2 comments