Lär dig bygga snabba dashboards med 100k rader med paginering, virtualisering, smart filtrering och bättre frågor så interna verktyg känns kvicka.

En listvy känns oftast bra tills den inte gör det längre. Användare börjar märka små stopp som lägger sig ovanpå varandra: scrollningen hakar, sidan fryser en stund efter varje uppdatering, filter tar sekunder att svara, och du får en spinner efter varje klick. Ibland ser fliken ut att vara fryst eftersom UI-tråden är upptagen.
100k rader är en vanlig vändpunkt eftersom det belastar varenda del av systemet samtidigt. Datamängden är fortfarande normal för en databas, men tillräckligt stor för att små ineffektiviteteter blir synliga i webbläsaren och över nätverket. Om du försöker visa allt på en gång blir en enkel skärm en tung pipeline.
Målet är inte att rendera alla rader. Målet är att hjälpa någon hitta vad de behöver snabbt: de rätta 50 raderna, nästa sida eller ett snävt urval via ett filter.
Det hjälper att dela upp arbetet i fyra delar:
Om någon av delarna är dyr blir hela skärmen långsam. En enkel sökbox kan trigga en förfrågan som sorterar 100k rader, returnerar tusentals poster och sedan tvingar webbläsaren att rendera dem alla. Så blir skrivning laggig.
När team bygger interna verktyg snabbt (inklusive med vibe-coding-plattformar som Koder.ai) är listvyer ofta den första platsen där verklig datatillväxt visar gapet mellan "fungerar på demo" och "känns omedelbar varje dag."
Innan du optimerar, bestäm vad "snabbt" betyder för den här skärmen. Många team jagar throughput (ladda allt) när användare mest behöver låg latens (se något uppdateras snabbt). En lista kan kännas omedelbar även om den aldrig laddar alla 100k rader, så länge den svarar snabbt på scroll, sortering och filter.
Ett praktiskt mål är tid till första rad, inte tid till fullständig laddning. Användare litar på sidan när de ser de första 20–50 raderna snabbt och interaktionerna förblir mjuka.
Välj ett litet set siffror du kan följa varje gång du ändrar något:
COUNT(*) och breda SELECTs)Dessa kopplar till vanliga symptom. Om browser-CPU:n spikar när du scrollar gör frontend för mycket per rad. Om spinnern väntar men scrollningen är okej är det ofta backend eller nätverk. Om förfrågan är snabb men sidan ändå fryser är det nästan alltid rendering eller tung klient-sidig bearbetning.
Gör ett enkelt experiment: behåll UI samma, men begränsa backend temporärt så den returnerar bara 20 rader med samma filter. Blir det snabbt är flaskhalsen sannolikt laststorlek eller frågetid. Om det fortfarande är långsamt, titta på rendering, formatering och per-rad-komponenter.
Exempel: en intern Orders-skärm känns seg när du skriver i sökfältet. Om API:t returnerar 5 000 rader och browsern filtrerar dem på varje knapptryck kommer skrivningen att lagga. Om API:t tar 2 sekunder på grund av en COUNT-fråga på ett oindexerat filter, ser du väntetiden innan någon rad ändras. Olika åtgärder, samma användarklagomål.
Browsern är ofta den första flaskhalsen. En lista kan kännas långsam även när API:t är snabbt, helt enkelt för att sidan försöker måla för mycket. Den första regeln är enkel: rendera inte tusentals rader i DOM samtidigt.
Redan innan du lägger på full virtualisering, håll varje rad lättviktig. En rad med nästlade wrappers, ikoner, tooltips och komplexa villkorliga stilar i varje cell kostar dig på varje scroll och varje uppdatering. Föredra klartext, några små badges och bara en eller två interaktiva element per rad.
Stabil radhöjd hjälper mer än det låter. När varje rad har samma höjd kan webbläsaren förutsäga layout och scrollen förblir mjuk. Variabla radhöjder (brytande text, expanderande anteckningar, stora avatarer) triggar extra mätningar och omlayout. Om du behöver extra detaljer, överväg en sidopanel eller en enda expanderbar vy, inte en hel flerradig rad.
Formatering är en annan tyst kostnad. Datum, valuta och tung strängbearbetning slår till när det upprepas över många celler.
Om ett värde inte är synligt, beräkna det inte än. Cachea dyra formateringsresultat och beräkna dem på begäran, till exempel när en rad blir synlig eller när användaren öppnar en rad.
En snabb genomgång som ofta ger märkbar förbättring:
Exempel: en intern fakturatabell som formaterar 12 kolumner med valuta och datum hakar vid scroll. Att cacha formaterade värden per faktura och skjuta upp arbete för off-screen-rader kan få det att kännas omedelbart, även innan djupare backend-arbete.
Virtualisering innebär att tabellen bara ritar de rader du faktiskt kan se (plus en liten buffert ovan- och nedanför). När du scrollar återanvänder den samma DOM-element och byter innehåll i dem. Det hindrar browsern från att försöka måla tiotusentals radkomponenter samtidigt.
Virtualisering passar bra när du har långa listor, breda tabeller eller tunga rader (avatarer, statuschips, action-menyer, tooltips). Det är också användbart när användare scrollar mycket och förväntar sig en jämn, kontinuerlig vy istället för att hoppa sida för sida.
Det är inte magi. Några saker orsakar ofta överraskningar:
Det enklaste tillvägagångssättet är tråkigt: fast radhöjd, förutsägbara kolumner och inte för många interaktiva widgets i varje rad.
Du kan kombinera båda: använd paginering (eller cursor-baserad load more) för att begränsa vad du hämtar från servern, och virtualisering för att hålla rendering billig inom den hämtade skivan.
Ett praktiskt mönster är att hämta en normal sidstorlek (ofta 100–500 rader), virtualisera inom den sidan och erbjuda tydliga kontroller för att gå mellan sidor. Om du använder infinite scroll, lägg till en synlig indikator som visar "Inläst X av Y" så användare förstår att de inte ser allt än.
Om du behöver en listvy som förblir användbar när datan växer, är paginering vanligtvis det säkraste standardvalet. Det är förutsägbart, fungerar bra för admin-flöden (granska, redigera, godkänn) och stöder vanliga behov som export av "sida 3 med dessa filter" utan överraskningar. Många team återvänder till paginering efter att ha testat finare scrollning.
Infinite scroll kan kännas trevligt för casual bläddring, men det har dolda kostnader. Folk tappar ofta känslan för var de är, back-knappen återställer inte alltid samma plats och långa sessioner kan bygga upp minne när fler rader laddas. En mellangrund är en "Ladda mer"-knapp som fortfarande använder sidor, så användare håller orienteringen.
Offset-paginering är den klassiska page=10&size=50-metoden. Den är enkel men kan bli långsammare på stora tabeller eftersom databasen kan behöva hoppa över många rader för att nå senare sidor. Den kan också kännas konstig när nya rader anländer och objekt flyttas mellan sidor.
Keyset-paginering (ofta kallad cursor-paginering) ber om "nästa 50 rader efter senaste sedda posten", vanligtvis med ett id eller created_at-värde. Den brukar förbli snabb eftersom den inte behöver räkna och hoppa över lika mycket arbete.
En praktisk regel:
Användare gillar totals, men en fullständig "räkna alla matchande rader" kan vara dyr med tunga filter. Alternativ inkluderar att cacha counts för populära filter, uppdatera count i bakgrunden efter att sidan laddat, eller visa ett ungefärligt tal (t.ex. "10 000+").
Exempel: en intern Orders-skärm kan visa resultat omedelbart med keyset-paginering och sedan fylla i det exakta totalet först när användaren slutat ändra filter under en sekund.
Om du bygger detta i Koder.ai, behandla paginerings- och count-beteende som en del av skärmspecen tidigt, så genererade backend-frågor och UI-state inte krockar senare.
De flesta listvyer känns långsamma för att de börjar helt öppna: ladda allt och be användaren smalna ner. Vänd på det. Börja med vettiga standarder som returnerar en liten, användbar mängd (t.ex. senaste 7 dagarna, Mina objekt, Status: Öppen) och gör "All time" till ett explicit val.
Textsök är en annan vanlig fälla. Om du kör en fråga på varje knapptryck skapar du en kö av förfrågningar och ett UI som flimrar. Debounca sökinput så du bara frågar när användaren pausat kort, och avbryt äldre förfrågningar när en ny startar. En enkel regel: om användaren fortfarande skriver, slå inte servern än.
Filtrering känns snabb när den också är tydlig. Visa filterchips ovanför tabellen så användare ser vad som är aktivt och tar bort det med ett klick. Håll chip-etiketterna mänskliga, inte råa fältnamn (t.ex. Owner: Sam istället för owner_id=42). När någon säger "mina resultat försvann" är det oftast ett osynligt filter.
Mönster som håller stora listor responsiva utan att göra UI komplicerat:
Sparade vyer är den tysta hjälten. I stället för att lära användare bygga perfekta engångsfilter, ge dem ett par presets som matchar verkliga arbetsscenarion. Ett ops-team kan växla mellan "Misslyckade betalningar idag" och "Högvärdiga kunder" — ett klick, omedelbart begripligt och lättare att hålla snabbt i backend.
Om du bygger ett internt verktyg i en chattstyrd builder som Koder.ai, behandla filter som en del av produktflödet, inte som ett på-läsen-tillägg. Börja från de vanligaste frågorna och designa standardvyn och sparade vyer runt dem.
En listvy behöver sällan samma data som en detaljsida. Om ditt API returnerar allt om allt betalar du dubbelt: databasen gör mer arbete och browsern får och renderar mer än den kan använda. Frågeformning är vanan att fråga bara efter det listan behöver just nu.
Börja med att returnera endast de kolumner som behövs för att rendera varje rad. För de flesta dashboards är det ett id, ett par etiketter, en status, en ägare och tidsstämplar. Stor text, JSON-blobs och beräknade fält kan vänta tills användaren öppnar en rad.
Undvik tunga joins för första rendering. Joins är okej när de träffar index och ger små resultat, men blir dyra när du joinar flera tabeller och sedan sorterar eller filtrerar på den joinade datan. Ett enkelt mönster: hämta listan från en tabell snabbt, ladda sedan relaterade detaljer on demand (eller batch-ladda för synliga rader endast).
Håll sorteringsalternativen begränsade och sortera på indexerade kolumner. "Sortera efter vad som helst" låter hjälpsamt, men tvingar ofta fram långsamma sorteringar på stora dataset. Föredra några förutsägbara val som created_at, updated_at eller status, och säkerställ att de är indexerade.
Var försiktig med server-sida-aggregation. COUNT(*) på ett stort filtrerat set, DISTINCT på en bred kolumn eller totalpages-beräkningar kan dominera din responstid.
En praktisk approach:
COUNT och DISTINCT som valfria; cacha eller approximera när möjligtOm du bygger interna verktyg på Koder.ai, definiera en lättvikts listfråga separat från detaljfrågan i planeringsläge så UI:t förblir snabbrörligt när datan växer.
Om du vill att en listvy ska hålla sig snabb vid 100k rader måste databasen göra mindre arbete per förfrågan. De flesta långsamma listor beror inte på "för mycket data" utan på fel åtkomstmönster.
Börja med index som matchar vad användarna faktiskt gör. Om din lista vanligtvis filtreras på status och sorteras på created_at vill du ha ett index som stödjer båda, i rätt ordning. Annars kan databasen skanna fler rader än du väntar och sedan sortera dem, vilket snabbt blir dyrt.
Fixar som ofta ger störst förbättring:
tenant_id, status, created_at).OFFSET-sidor. OFFSET får databasen att gå förbi många rader bara för att hoppa över dem.Ett enkelt exempel: en intern Orders-tabell som visar kundnamn, status, belopp och datum. Join inte varje relaterad tabell och hämta fulla orderanteckningar för listvyn. Returnera bara kolumnerna som tabellen använder, och ladda resten i en separat förfrågan när användaren klickar en order.
Om du bygger med en plattform som Koder.ai, behåll detta mindset även om UI:t genereras från chat. Se till att genererade API-endpoints stödjer cursor-paginering och selektiva fält så databasarbetet förblir förutsägbart när tabellen växer.
Om en list-sida känns långsam i dag, börja inte med att skriva om allt. Lås först vad normal användning ser ut som, och optimera den vägen.
Definiera standardvyn. Välj standardfilter, sortering och synliga kolumner. Listor blir långsamma när de försöker visa allt som standard.
Välj en pagineringsstil som matchar användningen. Om användare mest skannar de första sidorna räcker klassisk paginering. Om folk hoppar djupt (sida 200+) eller du behöver stabil prestanda oavsett hur långt de går, använd keyset-paginering (baserad på stabil sort som created_at plus id).
Lägg till virtualisering för tabellkroppen. Även om backend är snabb kan browsern få problem när den renderar för många rader samtidigt.
Få sök och filter att kännas omedelbara. Debounca skrivning så du inte skickar förfrågningar på varje knapptryck. Håll filterstate i URL:en eller i en delad state-store så refresh, back-knapp och delning av vy fungerar pålitligt. Cacha senaste lyckade resultat så tabellen inte blinkar tom.
Mät, sedan tunna frågor och index. Logga servertid, databas-tid, payload-storlek och render-tid. Tunna sedan frågan: välj bara kolumner du visar, applicera filter tidigt och lägg till index som matchar din standardfilter + sort.
Exempel: en intern support-dashboard med 100k ärenden. Standardisera på Öppna, tilldelade till mitt team, sortera på nyast, visa sex kolumner och hämta bara ticket id, subject, assignee, status och tidsstämplar. Med keyset-paginering och virtualisering håller du både databas och UI förutsägbart.
Om du bygger interna verktyg i Koder.ai passar den här planen bra i ett iterative-and-check-flöde: justera vyn, testa scroll och sök, och tunna frågan tills sidan förblir kvick.
Det snabbaste sättet att få en listvy att kännas trasig är att behandla 100k rader som en normal datamängd. De flesta långsamma dashboards har några förutsägbara fällor.
En stor är att rendera allt och dölja det med CSS. Även om det ser ut som bara 50 rader är synliga betalar browsern fortfarande kostnaden för att skapa 100k DOM-noder, mäta dem och ommåla vid scroll. Om du behöver långa listor, rendera bara vad användaren kan se (virtualisering) och håll radkomponenterna enkla.
Sök kan också tyst förstöra prestanda när varje knapptryck triggar en full tabellskanning. Det händer när filter inte backas av index, när du söker över för många kolumner eller när du kör contains-frågor på stora textfält utan plan. En bra regel: det första filter en användare når efter ska vara billigt i databasen, inte bara bekvämt i UI:t.
En annan vanlig fråga är att hämta fulla poster när listan bara behöver sammanfattningar. En rad behöver oftast 5–12 fält, inte hela objektet, inte långa beskrivningar och inte relaterad data. Att dra extra data ökar databasarbete, nätverkstid och klientparsing.
Export och totals kan frysa UI om du beräknar dem på main-thread eller väntar på en tung förfrågan innan du svarar. Håll UI interaktivt: starta export i bakgrunden, visa progress och undvik att räkna om totals vid varje filterändring.
Slutligen kan för många sortval slå tillbaka. Om användare kan sortera på vilken kolumn som helst kommer du att sortera stora resultatuppsättningar i minnet eller tvinga databasen in i långsamma planer. Håll sorterna till ett litet set indexerade kolumner och gör standard-sorten kompatibel med ett verkligt index.
Snabb kontroll:
Behandla listprestanda som en produktfunktion, inte ett engångsfix. En listvy är snabb bara när den känns snabb medan riktiga personer scrollar, filtrerar och sorterar på riktig data.
Använd denna checklista för att bekräfta att du fixat rätt saker:
En enkel verklighetskontroll: öppna listan, scrolla i 10 sekunder, applicera sedan ett vanligt filter (t.ex. Status: Öppen). Om UI fryser är problemet oftast rendering (för många DOM-rader) eller tung klient-sidig transform (sortering, gruppering, formatering) som sker vid varje uppdatering.
Nästa steg, i ordning, så du inte hoppar mellan fixar:
Om du bygger detta med Koder.ai (koder.ai), börja i Planning Mode: definiera exakta listkolumner, filterfält och API-svarsform först. Iterera sedan med snapshots och rollback om ett experiment gör sidan långsammare.
Börja med att ändra målet från “ladda allt” till “visa de första användbara raderna snabbt.” Optimera för tid till första rad och smidiga interaktioner när man filtrerar, sorterar och rullar, även om hela datasetet aldrig laddas samtidigt.
Mät tid till första rad efter laddning eller filterändring, tid för filter/sort att uppdatera, storlek på svarspayload, långsamma databasfrågor (speciellt breda SELECTs och COUNT(*)) samt toppar i browserns main-thread. De här siffrorna kopplar direkt till vad användare uppfattar som “lagg”.
Begränsa API:t temporärt till att returnera endast 20 rader med samma filter och sortering. Om det blir snabbt betalar du främst för frågekostnad eller payload-storlek; om det fortfarande är långsamt är flaskhalsen ofta renderingen, formatering eller klientarbete per rad.
Rendera inte tusentals rader i DOM samtidigt, håll radkomponenter enkla och föredra fast radhöjd. Undvik dyr formatering för off-screen-rader; beräkna och cachea formatering först när en rad blir synlig eller öppnas.
Virtualisering monterar bara de rader du faktiskt ser (plus en liten buffert) och återanvänder DOM-element när du scrollar. Det är värt det när användare scrollar mycket eller rader är “tunga”, men fungerar bäst när radhöjden är konsekvent och tabellayouten förutsägbar.
Paginering är oftast säkrare för admin- och interna arbetsflöden eftersom det håller användaren orienterad och begränsar serverarbete. Oändlig scroll kan fungera för avslappnad bläddring, men den försvårar navigering och kan öka minnesanvändningen om du inte sätter tydliga gränser.
Offset-paginering är enklare (page=10&size=50) men kan bli långsammare djupare in eftersom databasen måste hoppa över många rader. Keyset- eller cursor-paginering fortsätter från sista sedda posten (created_at + id) och håller ofta prestandan stabil, men lämpar sig sämre om du måste hoppa till ett exakt sidnummer.
Kör inte en förfrågan på varje knapptryckning. Debounca sökfältet, avbryt pågående förfrågningar när en ny startar, och standardisera på smalare filter (t.ex. senaste 7 dagarna eller Mina objekt) så den första frågan är liten och användbar.
Returnera bara de fält som listan faktiskt renderar — ofta ett id, etiketter, status, ägare och tidsstämplar. Flytta stora texter, JSON-blobs och relaterad data till en detaljförfrågan så första rendering blir lätt och förutsägbar.
Lägg index som matchar verkliga filter- och sorteringsmönster, gärna kompositindex (t.ex. tenant_id, status, created_at). Behandla exakta totals som valfria — cachea, förkalkylera eller approximera dem så de inte blockerar huvudsvaret.