router icon indicating copy to clipboard operation
router copied to clipboard

vite-plugin-pwa incompatible with tanstack start production builds

Open f1shy-dev opened this issue 4 months ago • 12 comments

Which project does this relate to?

Start

Describe the bug

vite-plugin-pwa's build steps (generate assets, generate serviceworker bundle) are seemingly not executed when running vite build with VitePWA() and tanstackStart() both present.

This is likely because to vite-plugin-pwa not having proper support for the Vite 6 environment API. There is an open PR to solve this: https://github.com/vite-pwa/vite-plugin-pwa/pull/786, however the current state of this PR has the same issue as the stable version of the package.

No PWA/SW steps are ran in build:

Image

but it works in the vite dev server:

Image

Your Example Website or App

https://stackblitz.com/edit/github-djpcrmc9?file=src%2Frouter.tsx

Steps to Reproduce the Bug or Issue

  1. npm install
  2. npm run build
  3. No PWA/SW steps are ran in build:

Expected behavior

  • Build steps actually run during vite build
  • Service worker gets built into tanstack start's client-dist, so it can also get picked up by nitro publicAssets[]
  • Any script injection steps such as into
  • also work(?)

Screenshots or Videos

No response

Platform

  • Start Version: ^1.131.7
  • OS: macOS, Stackblitz WebContainer
  • Browser: Chrome
  • Browser Version: 139.0.7258.66
  • Bundler: Vite
  • Bundler Version: ^6.3.5

Additional context

No response

f1shy-dev avatar Aug 17 '25 22:08 f1shy-dev

+1

FatahChan avatar Aug 24 '25 18:08 FatahChan

@f1shy-dev did you find any workaround?

FatahChan avatar Aug 24 '25 18:08 FatahChan

@FatahChan For now you can try a few other more manual/involved things:

Try other plugins: https://serwist.pages.dev/docs/vite/getting-started (haven't tried myself, but somebody said it might work).

Or you can manually make a vite plugin-type thing which runs pwa-assets-generator and or creates a service worker:

//vite.config.ts, plugins section
{
  name: "workbox",
  applyToEnvironment(e) {
    return e.name === "ssr"; // runs after client is done generating, but before the nitro server copies publicAssets[] so that injectManifest can glob on static assets properly
  },
  // in my case, I made a script to generate just the SW, but you can also add assets etc
  buildStart: () => workboxGenerate(),
},
//workbox-generate.ts
import { injectManifest } from 'workbox-build'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { build } from 'esbuild'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

export async function workboxGenerate() {
  const clientDist = resolve(__dirname, '.tanstack/start/build/client-dist')
  const swDest = resolve(clientDist, 'sw.js')

  await build({
    entryPoints: [resolve(__dirname, 'src/sw.ts')],
    bundle: true,
    outfile: swDest,
    format: 'esm',
    target: 'es2020',
    minify: true,
    define: {
      'import.meta.env.DEV': 'false',
      'self.__WB_BUILD_ID': `"${buildId}"`
    }
  })

  // could have used generateSW and skipped the esbuild stages, I just needed more customisability
  const { count, warnings, size } = await injectManifest({
    swSrc: swDest,
    swDest: swDest,
    globDirectory: clientDist,
    globPatterns: [
      '**/*.{js,css,html,svg,png,ico,webmanifest,json}',
    ],
  });

  if (warnings.length) {
    console.warn('[workbox] warnings:', warnings)
  }
  console.log(`[workbox] generated sw.js with ${count} precached files (${(size/1024).toFixed(1)} KiB)`)
}

Then just register it with new Workbox or normal service workers on the client.

f1shy-dev avatar Aug 24 '25 18:08 f1shy-dev

I tried https://serwist.pages.dev/docs/vite doesn't work, kinda make sense as it's a fork of VITE pwa

FatahChan avatar Aug 24 '25 20:08 FatahChan

@FatahChan For now you can try a few other more manual/involved things:

Try other plugins: https://serwist.pages.dev/docs/vite/getting-started (haven't tried myself, but somebody said it might work).

Or you can manually make a vite plugin-type thing which runs pwa-assets-generator and or creates a service worker:

//vite.config.ts, plugins section { name: "workbox", applyToEnvironment(e) { return e.name === "ssr"; // runs after client is done generating, but before the nitro server copies publicAssets[] so that injectManifest can glob on static assets properly }, // in my case, I made a script to generate just the SW, but you can also add assets etc buildStart: () => workboxGenerate(), }, //workbox-generate.ts import { injectManifest } from 'workbox-build' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { build } from 'esbuild'

const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename)

export async function workboxGenerate() { const clientDist = resolve(__dirname, '.tanstack/start/build/client-dist') const swDest = resolve(clientDist, 'sw.js')

await build({ entryPoints: [resolve(__dirname, 'src/sw.ts')], bundle: true, outfile: swDest, format: 'esm', target: 'es2020', minify: true, define: { 'import.meta.env.DEV': 'false', 'self.__WB_BUILD_ID': "${buildId}" } })

// could have used generateSW and skipped the esbuild stages, I just needed more customisability const { count, warnings, size } = await injectManifest({ swSrc: swDest, swDest: swDest, globDirectory: clientDist, globPatterns: [ '**/*.{js,css,html,svg,png,ico,webmanifest,json}', ], });

if (warnings.length) { console.warn('[workbox] warnings:', warnings) } console.log([workbox] generated sw.js with ${count} precached files (${(size/1024).toFixed(1)} KiB)) } Then just register it with new Workbox or normal service workers on the client.

where is buildId defined? thanks

FatahChan avatar Aug 24 '25 21:08 FatahChan

I tried https://serwist.pages.dev/docs/vite doesn't work, kinda make sense as it's a fork of VITE pwa

I use Tanstack Router with serwist successful - I am not sure about what exactly would need to be changed to make it start with Start - so I am curious about it as I soon want to migrate from Router to Start

dohomi avatar Aug 25 '25 00:08 dohomi

@FatahChan buildId was just an optional thing I added in but i defined it via viteConfig.define

const config = defineConfig({
  define: {
    'window.BUILD_ID': `"${buildId}"`,
  },
//...

f1shy-dev avatar Sep 01 '25 14:09 f1shy-dev

Any news no this?My team needs to start a new project but don't want to use Nextjs 😅

kno-raziel avatar Sep 25 '25 01:09 kno-raziel

Any news no this?My team needs to start a new project but don't want to use Nextjs 😅

If your website is SPA, then TanStack Router + Hono with hRPC) could do the job and pwa plugin works great with it.

ahrorbeksoft avatar Sep 27 '25 15:09 ahrorbeksoft

Would love a timeline on this, if any.

joodaloop avatar Oct 28 '25 22:10 joodaloop

FYI, I created a similar bug report in the Serwist repo b/c I'm experiencing the same issue there as well and can imagine that they have the same common ground b/c Serwist is forked from vite-plugin-pwa:

[Bug]: sw.js is not built in vite with Tanstack Start production build

CombeeMike avatar Nov 05 '25 15:11 CombeeMike

+1

CongVan avatar Dec 09 '25 11:12 CongVan