SPA vs serverrouting – sådan undgår du 404 på Netlify, Vercel og GitHub Pages

Har du nogensinde refreshet en underside i din SPA og blevet mødt af en 404?

Du deployer din React-, Vue- eller Svelte-app. Alt spiller på forsiden. Men så går du til /dashboard, trykker refresh… og får en fin, kold 404 fra serveren.

Lokalt virkede alt. Selvfølgelig. For der kører en dev-server, som er bygget til SPA routing. Din host gør noget andet: den prøver at finde en fysisk fil på den sti, du har skrevet.

For at fikse det er du nødt til at skelne mellem to ting: serverrouting og clientrouting.

Hvorfor din SPA får 404 efter deploy

En klassisk SPA (Single Page Application) bruger typisk History API routing. Det betyder, at alle dine sider i virkeligheden er JavaScript, der kører i browseren, efter index.html er loadet.

Forestil dig, at du har en React Router-konfiguration som:

import { BrowserRouter, Routes, Route } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </BrowserRouter>
  );
}

Din app kender /about og /dashboard. Men din host gør ikke. Når du skriver /dashboard i adresselinjen, sker der to ting:

1) Browseren sender en HTTP-request til serveren på stien /dashboard.
2) Serveren leder efter en fil/mappe ved navn dashboard.

Hvis du ikke har en fysisk /dashboard/index.html eller lignende, svarer serveren med 404. Den ved ikke, at din app ville have håndteret den route, hvis bare den havde fået lov at loade index.html først.

Det, du vil, er egentlig: “Giv altid index.html tilbage, og lad mit JS håndtere resten”, med mindre der rent faktisk findes en fil på den sti.

Det mønster kaldes ofte “History API fallback”. Og det er præcis det, du skal konfigurere på Netlify, Vercel og GitHub Pages.

Er det faktisk en SPA-fejl? Hurtig diagnose

Inden du begynder at rode i configs, er det rart lige at tjekke, om problemet faktisk handler om SPA-routing og ikke bare en slå-fejl i et filnavn.

Mini-tjek: opfører din app sig som en SPA?

Et par hurtige indikatorer:

Hvis du klikker rundt mellem “siderne” i navigationen, og siden ikke reloades fuldt (ingen hvid skærm, ingen Favicon-blink), så kører du clientside routing.

Hvis dine links ser sådan ud i React:

<Link to="/about">About</Link>

eller i Vue Router:

<router-link to="/about">About</router-link>

og ikke som:

<a href="/about">About</a>

så er det også et tegn på SPA-routing.

Typisk symptom: kun 404 når du refresher eller går direkte til en underside

Det klassiske billede:

Du kan:

– Åbne forsiden
– Klikke til /about og /dashboard via links i appen

Men du får 404, hvis du:

– Refresher på /about
– Indsætter URL’en til /dashboard direkte i browseren

Hvis det er sådan, er du landet det rigtige sted. Nu handler det om at lære din host at sende alt tilbage til index.html.

Netlify vs SPA-routing – brug _redirects eller netlify.toml

Netlify er faktisk ret venlig over for SPA’er. Du skal bare fortælle den, at alle ukendte paths skal pege på din index.html.

Metode 1: _redirects-fil

Den mest lige-til løsning er en fil i din build-output-mappe (typisk dist eller build) med navnet _redirects.

Indholdet kan være så simpelt som:

/*    /index.html   200

Det betyder: For alle paths (/*), giv /index.html tilbage med statuskode 200.

Vigtige detaljer:

– Filen skal ende i din public/build-mappe, som Netlify faktisk deployer.
– Ingen ekstra quotes, ingen kolon, bare linjen som ovenfor.
– Pas på editoren ikke kalder den _redirects.txt.

En typisk React-opsætning:

my-app/
  public/
    index.html
    _redirects
  src/
  package.json

Med npm run build bliver _redirects kopieret til build/, og så læser Netlify den.

Metode 2: netlify.toml

Hvis du hellere vil holde ting i en config-fil, kan du bruge netlify.toml i roden af dit repo:

[build]
  command = "npm run build"
  publish = "build"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

Pointen er stadig den samme: alt udefineret falder tilbage til index.html.

Hvis du vil ned ad kaninhullet med flere varianter, har Netlify selv ret fine eksempler i deres docs: Netlify redirects.

Vercel vs SPA-routing – brug rewrites i vercel.json

På Vercel skal du tænke i “rewrites” i stedet for redirects. En rewrite omskriver stien internt, uden at ændre URL’en i browseren.

vercel.json med rewrite til index.html

Læg en vercel.json i roden af projektet:

{
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ]
}

Det betyder: Uanset hvad brugeren beder om, så serv /index.html. Din SPA-router tager over derfra.

Hvis du har API-routes eller statiske filer, du ikke vil overskrive, kan du være mere specifik, fx:

{
  "rewrites": [
    {
      "source": "/(about|dashboard|settings)",
      "destination": "/index.html"
    }
  ]
}

eller bruge en kombination med routes/redirects, alt efter hvordan din app er skruet sammen. Se Vercels egne eksempler på SPA’er i Vercel rewrites-dokumentationen.

Byg-output: sørg for at det er en static build

Hvis du bruger f.eks. Next.js i pure SPA-mode, er der lidt andre mønstre, men hvis du står med en klassisk create-react-app eller Vite SPA, handler det typisk om to ting:

– At npm run build laver en statisk mappe (ofte dist eller build).
– At Vercel peger på den som output.

Det klarer Vercel som regel selv via framework-detection, men det er værd at dobbelttjekke i dashboardet, hvis tingene opfører sig mystisk.

GitHub Pages vs SPA-routing – brug 404.html-tricket

GitHub Pages er lidt mere stædig. Du har ikke samme fleksible routing som på Netlify og Vercel. Men der er et velkendt hack: 404.html-tricket.

Idéen: lad 404.html indlæse din SPA og omdirigere

GitHub Pages viser automatisk en 404.html, hvis den ikke finder en given sti. Det kan du udnytte ved at lave en 404-side, der i praksis er din SPA eller et lille script, der loader den.

Den nemme version er at kopiere index.html til 404.html i din build-output-mappe. Når en bruger går til /about, finder GitHub Pages ikke en fysisk fil, viser 404.html, som så er din app, og din router matcher /about.

En forenklet proces kunne være:

npm run build
cp build/index.html build/404.html

Det kan du lægge ind i et lille deploy-script.

React Router + basename når du hoster i et subpath

Hvis din GitHub Pages-side ligger på https://brugernavn.github.io/repo-navn/, så ligger din SPA ikke på roden (/), men på /repo-navn/. Det skal din router kende.

I React Router kan du bruge basename:

import { BrowserRouter } from "react-router-dom";

function App() {
  return (
    <BrowserRouter basename="/repo-navn">
      {/* dine routes her */}
    </BrowserRouter>
  );
}

Og i din package.json kan du sætte:

{
  "homepage": "https://brugernavn.github.io/repo-navn/"
}

Så bygger create-react-app med de rigtige asset-paths. Det samme princip gælder, hvis du bruger andre bundlere: du skal sikre, at publicPath/base-URL peger det rigtige sted hen.

Hvis du gerne vil forstå forskellen på roddomæne og undermapper lidt mere, har vi også artikler om URL-struktur i forbindelse med f.eks. HTML-mappestruktur, som minder meget om samme problem.

Typiske faldgruber med SPA-routing efter deploy

Selv når dine redirects/rewrites er sat op, kan der være småting, der driller. Her er de hyppigste, jeg støder på.

Assets loader ikke, når du går direkte til en underside

Du kan f.eks. se, at CSS eller billeder forsvinder, hvis du åbner /about direkte. Ofte handler det om relative stier som:

<link rel="stylesheet" href="styles.css" />
<img src="images/logo.png" />

Når du står på /about, leder browseren efter /about/styles.css. Det findes ikke.

Brug i stedet stier med rod:

<link rel="stylesheet" href="/styles.css" />
<img src="/images/logo.png" />

eller (i bundleren) sørg for, at du importerer assets via JS/CSS, så den håndterer paths for dig.

Mix af serverroutes og SPA-routes

Hvis du både har rigtige serverroutes (f.eks. et API) og SPA-routes, skal dine regler være lidt mere præcise.

Eksempel på Netlify med API under /.netlify/functions og SPA for resten:

/.netlify/functions/*  /.netlify/functions/:splat  200
/*                     /index.html                  200

Samme idé i Vercel med rewrites og functions-mapper.

Hvis du går i gang med backend-delen også, giver det faktisk mening at forstå det hele som to lag: serverrouting til API og statiske filer, og clientrouting til “side-skift”. Det er det, jeg typisk bygger op fra bunden i mere full stack-orienterede projekter, som dem vi også beskriver i artikler om f.eks. intro til full stack.

Tjekliste – sådan tester du din SPA-routing efter deploy

Når du føler, du er færdig, er her en hurtig manuelt-test, som fanger 90 % af fejlene.

1. Start på forsiden

Åbn dit domæne uden ekstra path. Tjek:

– Loader appen uden fejl i konsollen?
– Er der 200-svar på HTML, JS og CSS i Network-tabben?

2. Klik rundt internt

Brug dine links til /about, /dashboard osv. Kig efter:

– Skifter URL’en i adresselinjen?
– Er der undgået full page reloads (SPA-beskyldningen)?

3. Refresh på en underside

Stå på f.eks. /about og tryk F5 eller Cmd+R. Du vil se en af to ting:

– 200 på HTML-filen (godt, History API fallback virker).
– 404 på HTML-filen (din redirect/rewrite er ikke ramt).

4. Åbn en underside i ny fane

Højreklik på linket til f.eks. /dashboard og vælg “Åbn link i ny fane”. Eller indsæt URL’en direkte. Appen skal stadig loade og vise den rigtige view.

5. Tjek for konsolfejl om assets

Hvis noget ikke ser rigtigt ud, kigger jeg altid i browserens console og Network-tab først. 404 på JS/CSS filer afslører som regel path-problemer, ikke routing-config.

Hvis du vil blive stærkere til at debugge i browseren generelt, er det faktisk en kæmpe hjælp at lære de basale værktøjer i f.eks. Chrome DevTools, som vi blandt andet bruger igen og igen i små øvelser i artikler som denne om JavaScript debugging.

Til sidst – det er ikke magi, det er bare to forskellige hjerner

Hele problemet her handler om, at din server tænker i filer og mapper, mens din SPA tænker i routes og komponenter. Så snart du får serveren til konsekvent at give index.html tilbage og lader JavaScript tage over, forsvinder 404’erne typisk af sig selv.

Og hvis du lige har siddet en aften og kæmpet med det, fordi alt virkede “jo fint lokalt” – velkommen i klubben. Jeg har personligt brugt længere tid på en manglende _redirects-fil, end jeg nogensinde vil indrømme over for andre end en logfil.

Sara Vestergaard er selvlært kode-nørd, der stille og roligt er gået fra at rode med en enkelt HTML-side til at bygge små værktøjer, scripts og hjemmesider til sig selv og vennerne. Hun startede med at lave en simpel band-hjemmeside som teenager og opdagede, hvor tilfredsstillende det er, når noget, du har skrevet, pludselig lever på skærmen.

For Sara handler kodning ikke om store ord eller imponerende titler, men om meget konkrete problemer: den kedelige opgave, der tager for lang tid, den ven der mangler en lille porteføljeside, eller den liste, der burde sortere sig selv. Hun elsker at pille ting fra hinanden – også kode – for at se, hvad der egentlig foregår, og hun har brugt utallige aftener på at google fejlbeskeder, teste små eksempler og langsomt bygge sin forståelse op.

På Coding Class deler hun den tilgang videre. Hun skriver til dig, der gerne vil lære at kode ved at gøre det i praksis: små projekter, korte kodebidder og forklaringer, der hænger sammen med det, du faktisk sidder med på skærmen. Hun skærer ind til benet, viser typiske fejl og deres løsninger og giver altid et forslag til, hvordan du kan bygge en tand videre, når grundideen først virker.

Når hun ikke skriver til Coding Class eller nørkler med nye små projekter, hænger Sara på klatrevæggen, vander sine altanplanter eller spiller gamle Nintendo-spil. Men hun ender næsten altid tilbage ved tasterne – for der er altid endnu en lille ting, der kunne være smartere, hurtigere eller bare lidt sjovere at bruge.

1 kommentar

comments user
Ida M.

SÅ genkendeligt jeg skal lave en simpel hjemmeside til min strikkebiks

Send kommentar

You May Have Missed