Skip to content
On this page

SvelteKit

TIP

From version ^0.1.0, SvelteKitPWA has SvelteKit ^1.0.0 as peer dependency.

INFO

For Type declarations, Prompt for update and Periodic SW Updates go to Svelte entry.

TIP

You should remove all references to SvelteKit service worker module to disable it on your application.

SvelteKit PWA Plugin

vite-plugin-pwa provides the new SvelteKitPWA plugin that will allow you to use vite-plugin-pwa in your SvelteKit applications.

You will need to install SvelteKitPWA using:

shell
pnpm add -D @vite-pwa/sveltekit
pnpm add -D @vite-pwa/sveltekit

To update your project to use the new vite-plugin-pwa for SvelteKit, you only need to change the Vite config file (you don't need oldest pwa and pwa-configuration modules):

ts
// vite.config.js / vite-config.ts
import { SvelteKitPWA } from '@vite-pwa/sveltekit'

/** @type {import('vite').UserConfig} */
const config = {
  plugins: [
    sveltekit(),
    SvelteKitPWA({/* pwa options */})
  ],
}

export default config
// vite.config.js / vite-config.ts
import { SvelteKitPWA } from '@vite-pwa/sveltekit'

/** @type {import('vite').UserConfig} */
const config = {
  plugins: [
    sveltekit(),
    SvelteKitPWA({/* pwa options */})
  ],
}

export default config

SvelteKit PWA Plugin Options

SvelteKit PWA Plugin options
ts
import type { VitePWAOptions } from 'vite-plugin-pwa'

export interface KitOptions {
  /**
   * The base path for your application: by default will use the Vite base.
   *
   * @deprecated since ^0.1.0 version, the plugin has SvelteKit ^1.0.0 as peer dependency, Vite's base is now properly configured.
   * @default '/'
   * @see https://kit.svelte.dev/docs/configuration#paths
   */
  base?: string

  /**
   * @default '.svelte-kit'
   * @see https://kit.svelte.dev/docs/configuration#outdir
   */
  outDir?: string

  /**
   * @see https://github.com/sveltejs/kit/tree/master/packages/adapter-static#fallback
   */
  adapterFallback?: string

  /**
   * @default 'never'
   * @see https://kit.svelte.dev/docs/configuration#trailingslash
   */
  trailingSlash?: 'never' | 'always' | 'ignore'
}

export interface SvelteKitPWAOptions extends Partial<VitePWAOptions> {
  kit?: KitOptions
}
import type { VitePWAOptions } from 'vite-plugin-pwa'

export interface KitOptions {
  /**
   * The base path for your application: by default will use the Vite base.
   *
   * @deprecated since ^0.1.0 version, the plugin has SvelteKit ^1.0.0 as peer dependency, Vite's base is now properly configured.
   * @default '/'
   * @see https://kit.svelte.dev/docs/configuration#paths
   */
  base?: string

  /**
   * @default '.svelte-kit'
   * @see https://kit.svelte.dev/docs/configuration#outdir
   */
  outDir?: string

  /**
   * @see https://github.com/sveltejs/kit/tree/master/packages/adapter-static#fallback
   */
  adapterFallback?: string

  /**
   * @default 'never'
   * @see https://kit.svelte.dev/docs/configuration#trailingslash
   */
  trailingSlash?: 'never' | 'always' | 'ignore'
}

export interface SvelteKitPWAOptions extends Partial<VitePWAOptions> {
  kit?: KitOptions
}

SvelteKit Pages

If you want your application to work offline, you should ensure you have not set csr: false on any of your pages since it will prevent injecting JavaScript into the layout for offline support.

Auto Update

Since SvelteKit uses SSR/SSG, we need to call the vite-plugin-pwa virtual module using a dynamic import.

The best place to include the virtual call will be in main layout of the application (you should register it in any layout):

src/routes/+layout.svelte
html
<script>
  import { onMount } from 'svelte'
  import { pwaInfo } from 'virtual:pwa-info'
  
  onMount(async () => {
    if (pwaInfo) {
      const { registerSW } = await import('virtual:pwa-register')
      registerSW({
        immediate: true,
        onRegistered(r) {
          // uncomment following code if you want check for updates
          // r && setInterval(() => {
          //    console.log('Checking for sw update')
          //    r.update()
          // }, 20000 /* 20s for testing purposes */)
          console.log(`SW Registered: ${r}`)
        },
        onRegisterError(error) {
          console.log('SW registration error', error)
        }
      })
    }
  })
  
  $: webManifest = pwaInfo ? pwaInfo.webManifest.linkTag : ''
</script>

<svelte:head>
    {@html webManifest}
</svelte:head>

<main>
  <slot />
</main>
<script>
  import { onMount } from 'svelte'
  import { pwaInfo } from 'virtual:pwa-info'
  
  onMount(async () => {
    if (pwaInfo) {
      const { registerSW } = await import('virtual:pwa-register')
      registerSW({
        immediate: true,
        onRegistered(r) {
          // uncomment following code if you want check for updates
          // r && setInterval(() => {
          //    console.log('Checking for sw update')
          //    r.update()
          // }, 20000 /* 20s for testing purposes */)
          console.log(`SW Registered: ${r}`)
        },
        onRegisterError(error) {
          console.log('SW registration error', error)
        }
      })
    }
  })
  
  $: webManifest = pwaInfo ? pwaInfo.webManifest.linkTag : ''
</script>

<svelte:head>
    {@html webManifest}
</svelte:head>

<main>
  <slot />
</main>

Prompt for update

Since SvelteKit uses SSR/SSG, we need to add the ReloadPrompt component using a dynamic import.

The best place to include the ReloadPrompt component will be in main layout of the application (you should register it in any layout):

src/routes/+layout.svelte
html
<script>
  import { onMount } from 'svelte'
  import { pwaInfo } from 'virtual:pwa-info'

  let ReloadPrompt
  onMount(async () => {
    pwaInfo && (ReloadPrompt = (await import('$lib/ReloadPrompt.svelte')).default)
  })

  $: webManifest = pwaInfo ? pwaInfo.webManifest.linkTag : ''  
</script>

<svelte:head>
    {@html webManifest}
</svelte:head>

<main>
  <slot />
</main>

{#if ReloadPrompt}
  <svelte:component this={ReloadPrompt} />
{/if}
<script>
  import { onMount } from 'svelte'
  import { pwaInfo } from 'virtual:pwa-info'

  let ReloadPrompt
  onMount(async () => {
    pwaInfo && (ReloadPrompt = (await import('$lib/ReloadPrompt.svelte')).default)
  })

  $: webManifest = pwaInfo ? pwaInfo.webManifest.linkTag : ''  
</script>

<svelte:head>
    {@html webManifest}
</svelte:head>

<main>
  <slot />
</main>

{#if ReloadPrompt}
  <svelte:component this={ReloadPrompt} />
{/if}
$lib/ReloadPrompt.svelte
html
<script lang="ts">
	import { useRegisterSW } from 'virtual:pwa-register/svelte'
	const {
		needRefresh,
		updateServiceWorker,
		offlineReady
	} = useRegisterSW({
		onRegistered(r) {
		// uncomment following code if you want check for updates
		// r && setInterval(() => {
		//    console.log('Checking for sw update')
		//    r.update()
		// }, 20000 /* 20s for testing purposes */)
			console.log(`SW Registered: ${r}`)
		},
		onRegisterError(error) {
			console.log('SW registration error', error)
		},
	})
	const close = () => {
		offlineReady.set(false)
		needRefresh.set(false)
	}
	$: toast = $offlineReady || $needRefresh
</script>

{#if toast}
	<div class="pwa-toast" role="alert">
		<div class="message">
			{#if $offlineReady}
				<span>
					App ready to work offline
				</span>
			{:else}
				<span>
					New content available, click on reload button to update.
				</span>
			{/if}
		</div>
		{#if $needRefresh}
			<button on:click={() => updateServiceWorker(true)}>
				Reload
			</button>
		{/if}
		<button on:click={close}>
			Close
		</button>
	</div>
{/if}

<style>
	.pwa-toast {
		position: fixed;
		right: 0;
		bottom: 0;
		margin: 16px;
		padding: 12px;
		border: 1px solid #8885;
		border-radius: 4px;
		z-index: 2;
		text-align: left;
		box-shadow: 3px 4px 5px 0 #8885;
		background-color: white;
	}
	.pwa-toast .message {
		margin-bottom: 8px;
	}
	.pwa-toast button {
		border: 1px solid #8885;
		outline: none;
		margin-right: 5px;
		border-radius: 2px;
		padding: 3px 10px;
	}
</style>
<script lang="ts">
	import { useRegisterSW } from 'virtual:pwa-register/svelte'
	const {
		needRefresh,
		updateServiceWorker,
		offlineReady
	} = useRegisterSW({
		onRegistered(r) {
		// uncomment following code if you want check for updates
		// r && setInterval(() => {
		//    console.log('Checking for sw update')
		//    r.update()
		// }, 20000 /* 20s for testing purposes */)
			console.log(`SW Registered: ${r}`)
		},
		onRegisterError(error) {
			console.log('SW registration error', error)
		},
	})
	const close = () => {
		offlineReady.set(false)
		needRefresh.set(false)
	}
	$: toast = $offlineReady || $needRefresh
</script>

{#if toast}
	<div class="pwa-toast" role="alert">
		<div class="message">
			{#if $offlineReady}
				<span>
					App ready to work offline
				</span>
			{:else}
				<span>
					New content available, click on reload button to update.
				</span>
			{/if}
		</div>
		{#if $needRefresh}
			<button on:click={() => updateServiceWorker(true)}>
				Reload
			</button>
		{/if}
		<button on:click={close}>
			Close
		</button>
	</div>
{/if}

<style>
	.pwa-toast {
		position: fixed;
		right: 0;
		bottom: 0;
		margin: 16px;
		padding: 12px;
		border: 1px solid #8885;
		border-radius: 4px;
		z-index: 2;
		text-align: left;
		box-shadow: 3px 4px 5px 0 #8885;
		background-color: white;
	}
	.pwa-toast .message {
		margin-bottom: 8px;
	}
	.pwa-toast button {
		border: 1px solid #8885;
		outline: none;
		margin-right: 5px;
		border-radius: 2px;
		padding: 3px 10px;
	}
</style>

SvelteKit and Adapters

If you set certain SvelteKit options, you should also configure the PWA plugin properly using the kit option:

Released under the MIT License.