Langsom webapp? 10 typiske årsager og hvordan du løser dem
Langsom webapp? Her er de 10 typiske årsager, fra N+1-forespørgsler og manglende indeks til tunge billeder, og sådan finder og løser du dem.

Freelance full-stack udvikler
- Udgivet
- Læsetid
- 14 min.
Indhold i indlægget9
En langsom webapp skyldes sjældent at serveren er for lille. Oftest er det et par konkrete flaskehalse: for mange databaseforespørgsler, manglende indeks og cache, opgaver der kører mens brugeren venter, eller tunge billeder og scripts i browseren. De fleste kan findes med de rigtige værktøjer og løses uden at bygge appen om.
Jeg laver selv vedligehold og videreudvikling af webapps, så læs med det forbehold. Jeg har forsøgt at være tydelig om hvad du kan tjekke selv, og hvornår en udvikler som mig ikke er svaret.
Den korte version
| # | Årsag | Typisk tegn | Typisk løsning |
|---|---|---|---|
| 1 | N+1-forespørgsler | Lister bliver langsommere jo flere rækker de viser | Hent relaterede data samlet (eager loading) |
| 2 | Manglende indeks | Søgning og filtrering går i stå på store tabeller | Indeks på de kolonner der søges i |
| 3 | For meget data pr. side | Sider uden sideinddeling, eksporter der aldrig bliver færdige | Sideinddeling og kun de kolonner der bruges |
| 4 | Ingen cache | Dashboards regner det samme ud igen og igen | Gem resultatet og genbrug det |
| 5 | Tunge opgaver i forgrunden | Knapper der "tænker" i mange sekunder | Flyt opgaven til en kø i baggrunden |
| 6 | Langsomme eksterne API'er | Siden er langsom når et andet system er det | Timeouts, kø og lokal kopi af data |
| 7 | Tunge billeder | Siden tegnes langsomt, især på mobil | Rigtig størrelse, moderne formater, senere indlæsning |
| 8 | For meget JavaScript | Siden ser færdig ud, men reagerer ikke på klik | Mindre kode pr. side og færre tredjepartsscripts |
| 9 | Statiske filer uden cache | Hvert besøg henter de samme filer forfra | Komprimering, browsercache og evt. CDN |
| 10 | Opsætning og hosting | Alt er lidt langsomt, hele tiden | Nyere PHP, produktionsindstillinger, passende hosting |
Min tommelfingerregel: er det første svar fra serveren langsomt, ligger årsagen typisk i nummer 1-6 eller 10. Kommer det første svar hurtigt, men siden føles tung, så kig på 7-9.
Hastighed er nemmere at tænke ind fra start end at rette bagefter. Vil du se hvor det hører hjemme i et samlet forløb, så læs min gennemgang af et udviklingsprojekt fra idé til drift.
Sådan finder du ud af hvor tiden går
Før nogen begynder at rette, skal du vide om tiden går på serveren eller i browseren. Ellers risikerer du at betale for at optimere billeder når problemet er en databaseforespørgsel der tager fire sekunder.
Browseren. Kør dine vigtigste offentlige sider gennem Googles PageSpeed Insights. Google anbefaler at sidens største element er tegnet inden for 2,5 sekunder, at siden reagerer på klik inden for 200 millisekunder, og at layoutet ikke hopper rundt under indlæsningen. Det er de tre Core Web Vitals, og de skal være opfyldt for mindst 75 % af besøgene ifølge Googles egen gennemgang af målene. PageSpeed Insights kan ikke logge ind, så sider bag login måler du i browserens udviklerværktøjer under fanen Netværk.
Serveren. Tiden til første byte (TTFB) viser hvor længe browseren venter før serveren overhovedet begynder at svare. Google kalder 0,8 sekunder eller mindre for godt. Ligger din app langt over, er det serverens arbejde der skal undersøges. Her bruger en udvikler værktøjer der viser hver databaseforespørgsel og hvor lang tid den tager. Jeg bruger selv Ray til det i Laravel-projekter, og Laravel Debugbar og Telescope er udbredte alternativer.
Det du selv kan gøre, er at skrive ned hvilke sider der er langsomme, for hvem og hvornår. "Ordreoversigten er langsom for administratorer mandag morgen" er langt mere værd for en udvikler end "appen er langsom".
Databasen: det første sted jeg kigger
I webapps med brugere, ordrer og rapporter er databasen den mest almindelige flaskehals. Problemerne er svære at se i starten fordi der kun er lidt testdata. De dukker først op når appen har været i drift et stykke tid, og datamængden er vokset.
1. N+1-forespørgsler: hundredvis af små opslag i stedet for ét
Det klassiske eksempel er en liste over ordrer hvor hver række også viser kundens navn. Koden henter først alle ordrer i én forespørgsel og derefter kunden til hver ordre, én ad gangen. Laravels dokumentation bruger selv et eksempel med 25 bøger og deres forfattere, hvor koden kører 26 forespørgsler i stedet for 2. Med 500 rækker bliver det til 501.
Hver enkelt forespørgsel er hurtig, så problemet ses ikke i et testmiljø med ti ordrer. Løsningen er at hente de relaterede data på forhånd i én samlet forespørgsel, det der kaldes eager loading. Det er typisk en lille ændring pr. side. Laravel kan også sættes op til at stoppe med en fejl under udvikling når koden henter data på den langsomme måde. Så bliver nye tilfælde fanget før de når produktion.
2. Manglende indeks i databasen
Et indeks fungerer som stikordsregistret bag i en bog. Uden det skal databasen læse hele tabellen igennem for at finde de rækker du beder om, og MySQLs dokumentation understreger at det bliver dyrere jo større tabellen er.
Tegnet er søgning, filtrering og sortering der bliver langsommere måned for måned efterhånden som tabellen vokser. Løsningen er indeks på de kolonner der søges og sorteres i, fx kunde, status og dato. Det kræver en vurdering, for hvert indeks gør til gengæld skrivning lidt langsommere og fylder på disken. I en typisk webapp bliver de fleste tabeller dog læst langt oftere end der skrives til dem, og et manglende indeks på en central kolonne er en af de billigste rettelser på listen.
3. Appen henter mere data end den viser
En side der viser 25 kunder, men henter alle 20.000 fra databasen og sorterer dem i koden, bliver langsommere for hver ny kunde. Det samme gælder sider der henter alle kolonner, også store tekstfelter, selvom kun navn og e-mail bliver vist, og rapporter der tæller op i koden i stedet for at lade databasen gøre det.
Løsningen er sideinddeling, at hente de kolonner der faktisk bruges, og at lade databasen tælle, summere og sortere. Store eksporter og importer skal køre i bidder så de ikke løber tør for hukommelse undervejs. Det er sjældent svært at rette, men det kræver at nogen gennemgår de tungeste sider én for én.
Serveren: arbejde der kunne være gjort på forhånd
4. Ingen cache af tunge beregninger
Cache betyder at et resultat bliver gemt og genbrugt i stedet for at blive regnet ud forfra. Et dashboard der viser omsætning pr. måned, behøver ikke at gennemgå alle ordrer hver gang en medarbejder åbner det. Resultatet kan gemmes i fem minutter, en time eller indtil der kommer en ny ordre.
Tegnet er sider med tal, grafer og statistik der er langsomme for alle brugere selvom tallene sjældent ændrer sig. Cache virker, men det har en pris: bliver cachen ikke ryddet korrekt, ser brugerne gamle tal. Derfor skal det besluttes pr. side hvor friske data skal være. Jeg starter typisk med de få sider der bliver åbnet oftest og er tungest at beregne, frem for at cache alt.
5. Tunge opgaver kører mens brugeren venter
Når en bruger trykker "Send faktura", og siden tænker i otte sekunder, er det ofte fordi appen laver PDF'en, sender mailen og opdaterer regnskabssystemet før den svarer. Alt det kan ske i baggrunden. Med en kø får brugeren svar med det samme mens opgaven bliver udført et øjeblik efter.
Det samme gælder masseudsendelse af mails, import af filer, billedbehandling og synkronisering med andre systemer. Laravel har køer indbygget, og det er en af grundene til at Laravel er mit standardvalg til webapps. Køer kræver dog at nogen holder øje med dem. En kø der er gået i stå, giver en anden og mere lumsk fejl: mailen kommer bare aldrig.
6. Langsomme kald til eksterne API'er
Mange webapps henter data fra andre systemer: regnskab, betaling, fragt eller et CRM. Venter din side på svar fra e-conomic eller en fragtleverandør hver gang den åbnes, bliver din app aldrig hurtigere end deres. Er deres API nede, kan din side hænge i mange sekunder før den giver op.
Løsningen er en kombination af tre ting: korte timeouts så appen giver op i tide, en lokal kopi af data der ikke ændrer sig hvert sekund, og at kald der ikke behøver ske med det samme, bliver flyttet til køen. Kig særligt efter denne årsag hvis appen kun er langsom på bestemte tidspunkter eller i bestemte forløb.
Browseren: det brugeren faktisk venter på
Selv med en hurtig server kan siden føles langsom hvis browseren skal hente og køre for meget. Det gælder især på mobil og på svage forbindelser.
7. Tunge og forkert skalerede billeder
Et billede fra et kamera eller en designfil kan sagtens fylde flere megabyte. Bliver det vist som et lille kort på 300 pixel i bredden, henter browseren stadig hele filen. Det er en af de hyppigste årsager til at en side tegnes langsomt.
Løsningen er at skalere billeder til den størrelse de vises i, og at bruge moderne formater som WebP og AVIF. Tests nævnt i Googles gennemgang af AVIF viser markant mindre filer end JPEG. Billeder længere nede på siden kan vente med at blive hentet til brugeren scroller ned (lazy loading). Uploader brugerne selv billeder, skal appen gøre det automatisk, for ingen husker at gøre det i hånden.
8. For meget JavaScript og for mange tredjepartsscripts
En side kan se færdig ud og alligevel ikke reagere når du klikker. Det skyldes typisk at browseren har travlt med at køre JavaScript. Kilden er ofte appens egen kode, der bliver sendt samlet til alle sider, eller scripts fra andre: chatwidgets, analyseværktøjer, annoncepixels, cookiebannere og videoafspillere.
Løsningen er at dele koden op så hver side kun henter det den bruger, at fjerne biblioteker der ikke længere bliver brugt, og at tage en ærlig snak om hvert tredjepartsscript. Spørgsmålet er ikke om scriptet er nyttigt, men om det er mere værd end den hastighed det koster. På offentlige sider er tredjepartsscripts ofte en større post end appens egen kode.
9. Statiske filer uden komprimering og cache
Billeder, CSS-filer, skrifttyper og JavaScript ændrer sig sjældent. Alligevel er mange servere sat op så browseren henter dem forfra ved hvert besøg eller får dem sendt ukomprimerede. Det rammer især de brugere der vender tilbage hver dag, og det er netop dem en webapp har flest af.
Løsningen ligger i serverens opsætning: komprimering med gzip eller Brotli, lange cachetider på filer der får et nyt navn når de ændres, og eventuelt et CDN, altså et netværk af servere der leverer filerne fra et sted tæt på brugeren. Det er ofte en af de hurtigste rettelser på listen fordi det handler om indstillinger og ikke om kode.
Driften: opsætning og hosting
10. Forældet opsætning og hosting der ikke passer til appen
Den sidste årsag gør alt lidt langsomt, hele tiden. Typiske eksempler er en gammel PHP-version, en app der kører i fejlsøgningstilstand (debug mode) i produktion, manglende OPcache, som holder færdigoversat PHP-kode i hukommelsen, og at Laravels egne cachekommandoer til konfiguration og ruter aldrig er sat op. Nyere PHP-versioner er generelt hurtigere og får samtidig stadig sikkerhedsrettelser.
Hertil kommer hostingen. Typiske problemer er et billigt webhotel hvor din app deler server med mange andre, en server der står langt fra brugerne, eller én lille maskine hvor database, køer og webserver kæmper om de samme ressourcer. Her kan et skift af hosting være det rigtige svar. Gør det bare efter du har tjekket nummer 1-9, ellers flytter du problemet med over på en dyrere server.
Hvornår en større server, en ombygning eller en udvikler ikke er svaret
Det er fristende at købe sig ud af problemet. Nogle gange er det fornuftigt, men ofte ikke.
En større server hjælper når appen reelt mangler ressourcer, fx fordi trafikken er vokset kraftigt. Den hjælper ikke på en side der laver 500 forespørgsler eller mangler et indeks. Så kører den dårlige kode bare lidt hurtigere indtil datamængden har indhentet den nye server.
En ombygning eller et skift til microservices er sjældent svaret på hastighed. Flaskehalsene på listen findes i alle frameworks og arkitekturer, og en opdeling i mange tjenester tilføjer netværkskald imellem dem. Min anbefaling er at de fleste projekter starter som en monolit. Er koden derimod så rodet at hver rettelse skaber to nye fejl, er hastigheden et symptom på teknisk gæld, og så skal den behandles som det. Er du i tvivl om hvor din kode står, så kig efter tegnene på en sund kodebase.
En udvikler som mig er heller ikke altid det rigtige valg:
- Er din side bygget i Shopify, Wix eller et WordPress-tema med mange plugins, så find en specialist i netop den platform. Meget af hastigheden afhænger af platformens egne begrænsninger.
- Er det et standardsystem du lejer, fx et bookingsystem eller et CRM, er det leverandøren der skal løse det. Send dem konkrete eksempler med side og tidspunkt.
- Er appen kun langsom for én bruger eller på ét netværk, så tjek forbindelsen og browseren før du betaler for en udvikler.
- Ligger dine vigtigste sider allerede inden for Googles anbefalinger, og klager ingen brugere, er pengene ofte bedre brugt på nye funktioner.
Næste skridt: sådan gør du din webapp hurtigere
- Vælg de tre til fem sider der betyder mest for dine brugere eller din omsætning. Det er dem der skal måles først, ikke hele appen.
- Mål dem: PageSpeed Insights for de offentlige sider og tiden til første svar for siderne bag login. Skriv tallene ned så du kan se om rettelserne virker.
- Ret det der giver mest for mindst. Et indeks eller eager loading på en central side er ofte en lille opgave med stor effekt.
- Sæt overvågning op så du opdager det når en side bliver langsom igen. Hastighed er en del af den løbende drift og vedligehold af en webapp, ikke et engangsprojekt.
Vil du have hjælp til at finde og rette flaskehalsene, kan du se hvordan jeg arbejder på siden om vedligehold og videreudvikling af webapps. Du får direkte kontakt med den der skriver koden, og svar inden for én arbejdsdag.
Ofte stillede spørgsmål
Hvad koster det at gøre en langsom webapp hurtigere?
Det afhænger mest af hvor mange årsager der er, og hvordan koden er bygget. Et manglende indeks eller en N+1-forespørgsel kan ofte rettes på få timer når først den er fundet. Selve undersøgelsen tager typisk længere tid end rettelsen. Er hastigheden et symptom på dyb teknisk gæld, bliver det et større projekt. Bed derfor om en afgrænset gennemgang af de vigtigste sider først så du ved hvad du køber.
Kan en langsom webapp skade min placering på Google?
For offentlige sider kan hastigheden have en vis betydning. Google bruger Core Web Vitals som ét af mange signaler, men relevant indhold vejer tungere. Sider bag login kan Google slet ikke se, så her handler hastighed om dine brugere og ikke om søgemaskiner. For de fleste webapps er den største pris ved langsomhed tabt tid hos medarbejdere og kunder, ikke dårligere placeringer.
Hvorfor er min webapp kun langsom nogle gange?
Når hastigheden svinger, skyldes det ofte noget der sker på bestemte tidspunkter. Det kan være planlagte job der kører tunge rapporter, en kø der hober sig op, en cache der lige er udløbet, eller et eksternt API der er langsomt. På et delt webhotel kan andre kunders trafik også påvirke din app. Notér tidspunkterne så en udvikler kan sammenholde dem med serverens logfiler.
Er PHP for langsomt til en moderne webapp?
Nej, i en typisk webapp til en virksomhed er PHP sjældent flaskehalsen. Nyere PHP-versioner med OPcache er hurtige nok til langt de fleste opgaver, og årsagerne på listen, som for mange forespørgsler, manglende cache og tunge sider, findes lige så vel i apps bygget med Node, Python eller Ruby. Skifter du sprog uden at rette dem, får du de samme problemer igen, bare til en langt højere pris.