Skip to content

Alt om WordPress, webutvikling — og mer til

⚡ Hvordan laste ekstern JavaScript uten å blokkere siden

⚡ Hvordan laste ekstern JavaScript uten å blokkere siden

Når nettleseren møter en <script>-tagg uten attributter, slipper den alt. Sidevisningen stopper helt opp til skriptet er lastet og kjørt. På en treg 4G-tilkobling betyr det 2-3 sekunder med blank skjerm.

Brukeren har allerede gått til en konkurrent innen den tid. Core Web Vitals registrerer en mislykket LCP, Google skyver siden ned i søkeresultatene, og du mister trafikk og konverteringer. Samtidig kan problemet løses med tre linjer hvis du vet hvor du skal se.

Nedenfor finner du en fungerende metode for å laste ekstern JavaScript uten blokkering. Fra den klassiske tofil-tilnærmingen til moderne async/defer og dynamisk import(). Med utprøvd kode du kan kopiere og lime inn.

💡 Rask oversikt:

  • Forstå problemet: hvordan en vanlig <script> blokkerer HTML-tolking og dreper lastehastigheten
  • Mestre den klassiske tilnærmingen: en liten loader (≤300 byte) som dynamisk henter inn hoved-JS-filen
  • Lær de innebygde attributtene async og defer: når og hvilken du bør bruke
  • Utforsk dynamisk import() for å laste moduler ved behov
  • Velg en strategi for prosjektet ditt med en sammenligningstabell

Hvorfor JavaScript blokkerer rendering

Når HTML-tolkeren når <script src="app.js">, gjør den nøyaktig tre ting: stopper tolkningen av dokumentet, laster ned filen, kjører den. Først etter det fortsetter den å bygge DOM-en.

Årsaken er arkitektonisk. Et skript kan inneholde document.write(), som endrer HTML i farten. Nettleseren vet ikke på forhånd om det finnes et slikt kall, så den tar det sikre og venter på full lasting og kjøring. Resultatet: selv et lett 5 KB skript legger til hundrevis av millisekunder til First Contentful Paint bare fra én enkelt nettverksrunde.

Problemet er ikke nytt. Tilbake i 2009 beskrev Nicholas Zakas en teknikk for dynamisk, ikke-blokkerende JavaScript-lasting, og den fungerer fortsatt i dag, riktignok med justeringer for moderne API-er. Med ankomsten av async, defer og ES-moduler har utviklere nå en hel verktøykasse. La oss undersøke hver enkelt.

Klassisk tilnærming: to filer og dynamisk lasting

Ideen er enkel. I stedet for å legge all JS-en din i én fil og feste den til siden via <script src="...">, deler du koden i to deler:

  • En liten loader (200-300 byte etter komprimering)
  • Hovedfilen med applikasjonslogikk

Loaderen settes inn inline nederst på siden, rett før </body>. Den oppretter en <script> programmatisk og legger den til i DOM-en, en slik tagg blokkerer ikke lenger tolkning fordi den dukker opp utenfor hoveddokumentflyten. Så snart hovedfilen lastes, kjører initialiseringen.

Moderne versjon av funksjonen i ren JS uten bakoverkompatibilitet for IE:

1function loadScript(url) {
2 return new Promise((resolve, reject) => {
3 const script = document.createElement('script');
4 script.src = url;
5 script.onload = resolve;
6 script.onerror = reject;
7 document.head.appendChild(script);
8 });
9}

Ni linjer. Ingen readyState-sjekker, ingen forgreninger for gammel IE, ingen pyramide av callbacks. Bare en funksjon som returnerer et Promise, praktisk å kombinere med async/await.

Bruk på siden ser slik ut (kode nederst, før avsluttende </body>):

1<script>
2 function loadScript(url) {
3 return new Promise((resolve, reject) => {
4 const script = document.createElement('script');
5 script.src = url;
6 script.onload = resolve;
7 script.onerror = reject;
8 document.head.appendChild(script);
9 });
10 }
11
12 loadScript('/js/app.js').then(() => {
13 // Initialize after main file loads
14 App.init();
15 });
16</script>

Det første skriptet (inline) er loaderen. Det tolkes og kjøres umiddelbart fordi det er under 300 byte. Det andre skriptet (app.js) lastes asynkront og forstyrrer ikke renderingen.

Hva om du har mer enn to filer? Kombiner dem under bygget. Moderne bundlere som Vite og Webpack gjør dette automatisk: tree-shaking, kodedeling, minifisering i én gjennomkjøring. Å manuelt administrere innlastingsrekkefølgen for et dusin filer er en vei til race conditions og feil.

Async og defer: innebygd avblokkering

HTML5 ga oss to attributter som løser problemet uten en eneste linje JavaScript:

1<script async src="analytics.js"></script>
2<script defer src="app.js"></script>

Begge laster filen parallelt med HTML-tolking. Forskjellen ligger i kjøringstidspunktet:

Attributt

Lasting

Kjøring

Rekkefølge

async

Parallelt med tolkning

Umiddelbart etter lasting

Ikke garantert

defer

Parallelt med tolkning

Etter full HTML-tolking

Garantert (som i dokumentet)

Tommelfingerregel:

  • async for uavhengige skript: analyse, annonser, tellere. De trenger ikke DOM-en, de bryr seg ikke om rekkefølge.
  • defer for hovedapplikasjonen: DOM-manipulasjon, grensesnittinitialisering. Skriptet venter på at siden er klar og kjører i riktig sekvens.

I praksis er kombinasjonen enkel: legg defer på alle skript i <head>, så oppfører de seg som om de er nederst på siden, men lastes tidligere. Ingen magi, bare nettleserens planlegger.

Og ja, du kan kombinere det med dynamisk lasting. Last for eksempel applikasjonskjernen via <script defer>, og fest tunge widgets dynamisk gjennom loadScript() bare når de faktisk trengs.

Dynamisk import(): moduler ved behov

ES2020 brakte dynamisk import(), en innebygd måte å laste en modul asynkront på, uten en bundler og uten ekstra funksjoner:

1// Loads only when user clicked
2button.addEventListener('click', async () => {
3 const { heavyChart } = await import('./chart-component.js');
4 heavyChart.render();
5});

Kallet import() returnerer et Promise. Modulen lastes i bakgrunnen, tolkning blokkeres ikke, siden forblir responsiv. Kode inne i modulen kjører i strict mode og i sitt eget scope, navnekonflikter elimineres.

Dette er det ideelle verktøyet for kodedeling uten en bundler. Tunge komponenter (diagrammer, editorer, kart) flyttes til separate filer og lastes ved første interaksjon. En bruker som aldri åpner et diagram, betaler ikke for det med trafikk og lastetid.

Sammenligning av tilnærminger

Hver metode har sin nisje. For å unngå gjetting har vi samlet egenskapene i en tabell:

Tilnærming

Blokkerer rendering

Krever JS

Kjøringsrekkefølge

For hvilke skript

<script src>

Ja

Nei

Garantert

Brukes ikke med mindre nødvendig

Dynamisk loadScript

Nei

Ja

Via .then()-kjede

Betinget lasting, tunge avhengigheter

<script async>

Nei

Nei

Ikke garantert

Analyse, annonser, tellere

<script defer>

Nei

Nei

Garantert

Hovedapplikasjon, DOM-manipulasjon

import()

Nei

Ja (ES-modul)

Via await

Kodedeling, komponenter ved behov

Hovedpoeng: ikke fikser på én metode. Et typisk produksjonsoppsett bruker to eller tre samtidig: defer for kjernen, async for metrikker, dynamisk import() for tunge komponenter.

Kort demovideo om temaet, gjennomgang av async og defer med visualisering av lastetidslinje:

⁉️🤔 Ofte stilte spørsmål

Hvordan skiller async seg fra defer i praksis?

Begge blokkerer ikke tolkning under lasting. Men async kjører skriptet umiddelbart etter at filen er lastet, selv om HTML ikke er ferdig tolket ennå, og uten rekkefølgegaranti. defer venter alltid på full DOM-klarhet og bevarer skriptsekvensen som i HTML. For hovedapplikasjonskode, bruk defer, for isolerte tellere, bruk async.

Kan du kombinere dynamisk lasting med defer?

Ja, dette er et vanlig scenario. Applikasjonskjernen lastes med defer i <head>, den initialiserer grensesnittet. Tunge eller sjeldent brukte moduler hentes via dynamisk loadScript() eller import() ved brukerinteraksjon. På denne måten får du både rask oppstart og utsatt lasting av sekundær kode.

Hva bør jeg bruke for et WordPress-nettsted?

WordPress legger automatisk til defer eller async via wp_enqueue_script() hvis du sender riktig argument i den femte parameteren: wp_enqueue_script('my-script', $url, [], null, ['strategy' => 'defer']). For tredjepartsskript (Google Analytics, annonser) er den enkleste tilnærmingen async-attributtet. Komplekse interaktive blokker (kalkulatorer, filtre) bør flyttes til dynamisk import() inne i en egendefinert modul.

Fungerer dette med tredjepartsskript som Google Analytics?

Ja. GA4-taggen gtag.js lastes med async som standard, så den blokkerer ikke siden. For andre tredjepartstjenester, sjekk dokumentasjonen: hvis skriptet ikke krever en klar DOM og ikke er avhengig av innlastingsrekkefølge, kan du trygt bruke async. Hvis det trenger DOM-en, bruk defer eller dynamisk lasting med en callback.

Hvordan bekrefter jeg at et skript virkelig ikke blokkerer siden?

Åpne Chrome DevTools → Ytelse → Spill inn → last siden på nytt. På tidslinjen, se etter gule «Scripting»-blokker før den grønne «First Contentful Paint». Hvis et skript er lastet med defer eller async, vil kjøringen være etter FCP. Lighthouse i «Ytelse»-modus vil vise anbefalingen «Fjern render-blokkerende ressurser», det skal ikke være noen skript i den listen.

Er det verdt å endre lastetilnærmingen din akkurat nå

Hvis skriptene dine fortsatt henger i <head> uten attributter, taper du søkerangeringer og irriterer brukere. Dette er ikke en hypotese, Lighthouse og PageSpeed Insights viser problemet i rødt i de første linjene i rapporten.

Hurtigstart: gå gjennom <script>-taggene i malen din, legg til defer for hovedkode og async for metrikker. Det tar fem minutter, og LCP kan forbedres med 300-500 ms. Deretter, dynamisk import() for tunge komponenter når du får tid til refaktorering.

La én lastemetode, <script defer> i <head>, bli værende, så laster siden uten synlige forsinkelser. Test det på prosjektet ditt i dag.