NNicheStackTutorials
← All tutorials
SvelteKit

Fix SvelteKit Static Broken Links on Vercel (Trailing Slash Issue)

SvelteKit adapter-static deploys but internal links 404 on Vercel. Fix trailingSlash, prerender, and vercel.json rewrites.

·9 min read·SvelteKit
Fix SvelteKit Static Broken Links on Vercel (Trailing Slash Issue) cover

Tested on: SvelteKit 2.16, adapter-static 3.0, Vercel Symptom: npm run preview works. Production 404 on half the links.

TL;DR

  • Match trailingSlash in svelte.config.js with Vercel URL behavior.
  • Prerender all routes or add fallback for dynamic paths.
  • Use vercel.json rewrites if mixing trailing slash styles.

The failure pattern

  • /blog works, /blog/ 404
  • Or the reverse
  • <a href="/about"> 404 but typing URL manually works
  • Assets load, navigation breaks

Root cause: three configs disagreeing

  1. svelte.config.jskit.trailingSlash
  2. Vercel project → Trailing Slash setting
  3. Your <a href> paths

All three must match. Pick one style and enforce it everywhere.

My fix — always trailing slash

svelte.config.js:

kit: {
  trailingSlash: 'always',
  paths: {
    relative: false
  },
  adapter: adapter({ fallback: '404.html' })
}

Vercel dashboard → Project Settings → Trailing Slash: Always.

Every internal link:

<a href="/about/">About</a>
<a href="/blog/{post.slug}/">{post.title}</a>

Error in build log (subtle)

[WARN] Files prefixed with +layout will be ignored in version 3

Unrelated warning — don't chase it. Focus on link audit.

Audit script

scripts/check-links.mjs:

import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';

const buildDir = 'build';
const htmlFiles = [];

function walk(dir) {
  for (const f of readdirSync(dir, { withFileTypes: true })) {
    const p = join(dir, f.name);
    if (f.isDirectory()) walk(p);
    else if (f.name.endsWith('.html')) htmlFiles.push(p);
  }
}
walk(buildDir);

const hrefRe = /href="(\/[^"#]+)"/g;
const missing = [];

for (const file of htmlFiles) {
  const html = readFileSync(file, 'utf8');
  let m;
  while ((m = hrefRe.exec(html))) {
    const href = m[1];
    const target = href.endsWith('/') ? `build${href}index.html` : `build${href}.html`;
    try { readFileSync(target); }
    catch { missing.push({ from: file, href }); }
  }
}

console.log(missing.length ? missing : 'All links OK');

Run after every build:

npm run build && node scripts/check-links.mjs

vercel.json (optional safety net)

{
  "trailingSlash": true,
  "cleanUrls": false
}

vercel.json mistake

{ "cleanUrls": true }

With cleanUrls: true + trailingSlash: 'always' in SvelteKit = guaranteed 404s. Pick one URL style.

Result

Broken internal links: 23 → 0
Link check script runs in CI (12s)
No more "works on my machine" deploys

FAQ

Why do SvelteKit static links 404 on Vercel?

Trailing slash mismatch — SvelteKit generates /about/ but Vercel serves /about, or vice versa. Align trailingSlash config with host defaults.

What trailingSlash setting works for Vercel?

Often trailingSlash: 'always' with Vercel, or 'ignore' with explicit vercel.json redirects. Test both /path and /path/.

Do I need adapter-static for Vercel static deploy?

Yes, for fully static sites. Set adapter-static with fallback: undefined and prerender all routes in +layout.ts.

Tags: SvelteKitVercelBroken LinksTrailing Slash