NNicheStackTutorials
← All tutorials
SvelteKit

SvelteKit Static Export Guide: adapter-static to Vercel Deploy

Complete SvelteKit static export — adapter-static config, prerender routes, trailing slashes, and Vercel deployment that works.

·14 min read·SvelteKit
SvelteKit Static Export Guide: adapter-static to Vercel Deploy cover

Tested on: SvelteKit 2.16, adapter-static 3.0, Node 22 Goal: Ship a blog with zero server runtime. Time lost: 6 hours on one typo.

TL;DR

  • Install @sveltejs/adapter-static; set fallback only if needed.
  • Export prerender = true in root +layout.ts.
  • Match trailingSlash with Vercel; output to build/.

Starting point

npm create svelte@latest my-blog
cd my-blog
npm install -D @sveltejs/adapter-static

svelte.config.js (working version)

import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter({
      pages: 'build',
      assets: 'build',
      fallback: '404.html',
      precompress: false,
      strict: true
    })
  }
};

export default config;

Enable prerender globally

src/routes/+layout.ts:

export const prerender = true;

Without this, npm run build dies on the first dynamic route.

Error #1 — the one that ate my afternoon

@/sveltejs/adapter-static: all routes must be fully prerenderable,
but found the following routes that are dynamic:
  - src/routes/blog/[slug]

Fix: add +page.ts inside [slug]/:

import type { PageLoad } from './$types';
import { error } from '@sveltejs/kit';

export const prerender = true;

export const load: PageLoad = async ({ params }) => {
  const posts = import.meta.glob('/src/content/*.md', { eager: true });
  const match = Object.entries(posts).find(([path]) =>
    path.includes(params.slug)
  );
  if (!match) throw error(404, 'Not found');
  return { post: (match[1] as { metadata: Record<string, string> }).metadata };
};

And enumerate slugs for the adapter:

export function entries() {
  return [
    { slug: 'hello-world' },
    { slug: 'static-export-notes' }
  ];
}

Error #2 — trailing slash mismatch on Vercel

404 on /about/ but /about works

svelte.config.js kit section:

kit: {
  trailingSlash: 'always',
  adapter: adapter({ /* ... */ })
}

Match Vercel project setting: Trailing Slash → Always.

Build & verify locally

npm run build
npx serve build
# curl -I http://localhost:3000/about/

Vercel deploy

Framework preset: Other. Output directory: build. No install command changes needed.

Result

Build time: 42s
Output size: 1.2 MB (gzipped assets ~280 KB)
Lighthouse Performance: 97
Zero serverless invocations on free tier

Static export is not "easier than SSR" — it's stricter. Every route must declare how it prerenders. Once that's done, hosting is trivial.

FAQ

How do I export SvelteKit as a static site?

Use @sveltejs/adapter-static, set prerender = true in +layout.ts, and configure kit.adapter in svelte.config.js.

Can SvelteKit static sites deploy to Vercel?

Yes — set outputDirectory to build and framework: null. Align trailingSlash with Vercel URL behavior.

What is adapter-static fallback option?

fallback: '200.html' enables SPA mode for dynamic routes. Omit fallback for fully prerendered static sites.

Tags: SvelteKitStatic Exportadapter-staticVercel