Introduction
This feature integrated @vite-pwa/sveltekit
Pass pwa option to theme default to use pwa. The options are exactly the same as SvelteKit PWA Plugin Options except for darkManifest, which is the manifest path that would used for dark theme
And the svelte.config.js need to config files.serviceWorker, use the SERVICE_WORKER_PATH exported from @sveltepress/theme-default
import function adapter(options?: AdapterOptions): Adapteradapter from '@sveltejs/adapter-static'
import { function vitePreprocess(opts?: VitePreprocessOptions): PreprocessorGroupvitePreprocess } from '@sveltejs/vite-plugin-svelte'
import { const SERVICE_WORKER_PATH: stringSERVICE_WORKER_PATH } from '@sveltepress/theme-default'
/** @type {import('@sveltejs/kit').Config} */
const
const config: {
extensions: string[];
preprocess: PreprocessorGroup[];
kit: {
adapter: Adapter;
files: {
serviceWorker: string;
};
};
}
config = {
extensions: string[]extensions: ['.svelte', '.md'],
preprocess: PreprocessorGroup[]preprocess: [function vitePreprocess(opts?: VitePreprocessOptions): PreprocessorGroupvitePreprocess()],
kit: {
adapter: Adapter;
files: {
serviceWorker: string;
};
}
kit: {
adapter: Adapteradapter: function adapter(options?: AdapterOptions): Adapteradapter(),
files: {
serviceWorker: string;
}
files: {
serviceWorker: stringserviceWorker: const SERVICE_WORKER_PATH: stringSERVICE_WORKER_PATH,
},
},
}
export default
const config: {
extensions: string[];
preprocess: PreprocessorGroup[];
kit: {
adapter: Adapter;
files: {
serviceWorker: string;
};
};
}
config If you want to enable pwa.
You will need to add workbox-window as a dev dependency to your Vite project.
Precache (versions & i18n)
By default Sveltepress only precaches the app shell and the homepage HTML. The shell is SvelteKit’s entry modules, hashed CSS / fonts, and root icons — not per-route _app/immutable/nodes or shared chunks. Other documentation pages and those hashed modules are cached at runtime when the user visits them (pages: NetworkFirst, capped at 50 entries; hashed client files: CacheFirst, capped at 400 entries / 30 days). Images and SvelteKit __data.json responses are also runtime-cached.
This keeps service worker install and update fast when the site has many pages, versions, and locales, so the refresh prompt can appear soon after a deploy. Precaching every prerendered HTML file or every client module makes Workbox hash, compare and download versions × locales × pages on every update.
On first install, homepage hydration may need the network until those hashed modules have been runtime-cached. After one online visit, visited pages (including home) stay available offline through the runtime cache.
pwa.precachePages
| Value | Precached HTML |
|---|---|
false (default) | Homepage only |
true | All prerendered HTML (historical versions are still ignored) |
string[] | Homepage + matching URL prefixes |
Precache only the current locale and a version snapshot:
import { const defaultTheme: ThemeDefaultdefaultTheme } from '@sveltepress/theme-default'
function defaultTheme(themeOptions?: DefaultThemeOptions | undefined): ResolvedThemedefaultTheme({
DefaultThemeOptions.pwa?: (SvelteKitPWAOptions & {
darkManifest?: string;
precachePages?: boolean | string[];
precacheClient?: boolean;
}) | undefined
pwa: {
precachePages?: boolean | string[] | undefinedWhich prerendered HTML pages to put in the Workbox precache.
Default false only precaches the homepage so service-worker
install/update stays fast on sites with many versions and locales.
false: homepage only
true: all prerendered HTML (historical versions are still ignored)
string[]: URL prefixes, e.g. ['/zh/', '/v/2026-08-27/']
precachePages: ['/zh/', '/v/2026-08-27/'],
},
}) Restore the previous “cache every page” behavior:
import { const defaultTheme: ThemeDefaultdefaultTheme } from '@sveltepress/theme-default'
function defaultTheme(themeOptions?: DefaultThemeOptions | undefined): ResolvedThemedefaultTheme({
DefaultThemeOptions.pwa?: (SvelteKitPWAOptions & {
darkManifest?: string;
precachePages?: boolean | string[];
precacheClient?: boolean;
}) | undefined
pwa: {
precachePages?: boolean | string[] | undefinedWhich prerendered HTML pages to put in the Workbox precache.
Default false only precaches the homepage so service-worker
install/update stays fast on sites with many versions and locales.
false: homepage only
true: all prerendered HTML (historical versions are still ignored)
string[]: URL prefixes, e.g. ['/zh/', '/v/2026-08-27/']
precachePages: true,
},
}) A glob starting with prerendered/ is always included. Otherwise @vite-pwa/sveltekit would append prerendered/**/*.{html,json} and pull every version/locale page back into the precache.
Visited pages still work offline through the runtime cache, even when they are not precached.
pwa.precacheClient
| Value | Precached client files |
|---|---|
false (default) | App shell only (entry + CSS / fonts + root icons) |
true | Every matching client file |
Restore the previous “precache every client JS/CSS module” behavior:
import { const defaultTheme: ThemeDefaultdefaultTheme } from '@sveltepress/theme-default'
function defaultTheme(themeOptions?: DefaultThemeOptions | undefined): ResolvedThemedefaultTheme({
DefaultThemeOptions.pwa?: (SvelteKitPWAOptions & {
darkManifest?: string;
precachePages?: boolean | string[];
precacheClient?: boolean;
}) | undefined
pwa: {
precacheClient?: boolean | undefinedWhich client files to put in the Workbox precache.
Default false only precaches the app shell (SvelteKit entry,
hashed CSS/fonts, and root icons) so service-worker install/update
stays fast on sites with many pages. Per-route nodes and chunks
are fetched on demand.
false: app shell only
true: every matching client file (the previous catch-all glob)
precacheClient: true,
},
}) precachePages and precacheClient are independent: HTML policy does not change the client glob, and the other way around.
Example config
Take the config this site use for example:
export default {
scope: stringscope: '/',
base: stringbase: '/',
strategies: stringstrategies: 'generateSW',
kit: {
trailingSlash: string;
}
kit: {
trailingSlash: stringtrailingSlash: 'always',
},
darkManifest: stringdarkManifest: '/manifest-dark.webmanifest',
manifest: {
start_url: string;
scope: string;
name: string;
short_name: string;
icons: {
src: string;
sizes: string;
type: string;
}[];
theme_color: string;
background_color: string;
display: string;
}
manifest: {
start_url: stringstart_url: '/',
scope: stringscope: '/',
name: stringname: 'Sveltepress',
short_name: stringshort_name: 'Sveltepress',
icons: {
src: string;
sizes: string;
type: string;
}[]
icons: [
{
src: stringsrc: '/android-chrome-192x192.png',
sizes: stringsizes: '192x192',
type: stringtype: 'image/png',
},
{
src: stringsrc: '/android-chrome-512x512.png',
sizes: stringsizes: '512x512',
type: stringtype: 'image/png',
},
],
theme_color: stringtheme_color: '#f2f2f2',
background_color: stringbackground_color: '#f2f2f2',
display: stringdisplay: 'standalone',
},
} as any