Build a PWA with React and Vite
Published · by mister-guiiug
In short. To build a PWA with React and Vite, add vite-plugin-pwa, which generates the manifest and the service worker. Provide 192 and 512 pixel icons, including a maskable one, pick an update strategy, set the base path, then test on a production build, because the service worker is disabled in development.
A progressive web app (PWA) can be installed on the home screen, opens in its own window and can start without a network. With React and Vite, the foundation is quick to lay. What turns it into a PWA comes down to three building blocks, and a few decisions best made early.
The three building blocks of a PWA
- The manifest: a JSON file that gives the app's name, icons, colors, start address and display mode (
standalonefor a window without an address bar). - The service worker: a script that caches the app's files so it can start offline, and that handles the arrival of new versions.
- HTTPS: service workers only run in a secure context. During development,
localhostcounts as one.
Step by step
- Create the project.
npm create vite@latest my-app -- --template react-tssets up a React app in TypeScript. - Add the PWA plugin.
npm install -D vite-plugin-pwa, then declareVitePWA()invite.config.ts. The plugin generates the manifest and a Workbox-based service worker, which caches the files produced by the build. - Fill in the manifest:
name,short_name,start_url,display,theme_color,background_color,lang, and icons of 192 and 512 pixels. - Add a "maskable" icon. Android crops icons into a circle or a rounded square. An icon declared
maskablekeeps its design inside a safe zone: a centered circle whose diameter is 80% of the icon. On a 512-pixel icon, the design must fit in a circle of about 410 pixels. - Pick an update strategy. With
registerType: 'autoUpdate', the page reloads as soon as a new version is ready. With'prompt', a message offers the update and the user chooses when. If your app contains forms, prefer'prompt': a reload in the middle of typing loses data. - Set the base path and routing. On GitHub Pages, the app often lives under a subpath: set
base(for example/my-app/). And static hosting does not know your routes: reloading/my-app/settingsreturns a 404 error. Two workarounds: route with#(HashRouter), or publish a404.htmlthat reloads the app. - Test on a build. By default, the plugin does not enable the service worker during development. Run
npm run buildthennpm run preview, and open the Application tab of the browser's developer tools: manifest, service worker and cache are all visible there.
What takes time afterwards
A PWA that holds up in production needs more than these three blocks: a light and dark theme, several languages, data that survives an update, sync with a server that copes with network drops, tests, a size budget. Every project rewrites that framework. That is what a starter kit saves you.
How PWA Starter Kit helps
PWA Starter Kit is the skeleton of the mister-guiiug family of apps: a complete, tested and deployed app that has no business logic, only the framework.
- The stack: React 19, Vite 8, strict TypeScript, Tailwind 4, React Router, Zustand. The manifest is generated by a shared library (
@mister-guiiug/dev-pwa-config) throughvite-plugin-pwa, withmaskableicons; a404.htmlis produced at build time for deep links. - Updates in
promptmode, with a check every hour: the page never reloads on its own. - Two languages (French and English) and a light or dark theme.
- A backend with a local fallback: without configuration, data stays on the device. With two variables (
VITE_SUPABASE_URLandVITE_SUPABASE_ANON_KEY), notes go through Supabase, and writes made offline wait in a queue, then leave again when the network returns. SQL migrations, RLS policies and database tests are included. - A sample feature (notes) that shows data export and import, and deletion that can be undone for eight seconds.
- A quality gate: Vitest and Playwright tests, and a build that fails if the size exceeds the budget or if the compliance check finds a gap. Technical choices are explained in
docs/adr/.
Starting from the skeleton
The repository recommends not cloning it, but using its generator: npx github:mister-guiiug/create-lg-pwa-app my-app --from main. Without --from main, the generator starts from the skeleton's latest published tag, which may lag behind what is described here: at the time of this update, that tag lacks undoable deletion and the offline queue. The generator applies your id and display name, installs dependencies, builds the app and makes the first commit. With --publish, it also creates the public repository and turns on GitHub Pages.
Two caveats. The shared library is published on GitHub Packages, which requires a token even for a public package: export NODE_AUTH_TOKEN (a GitHub token with the read:packages scope) before npm install. And the skeleton is designed first for the apps of the family. Its code is under the MIT license: read it, take what helps you. The French original of this guide is Créer une PWA avec React et Vite.
Frequently asked questions
Does a React PWA work offline automatically?
Not entirely. The service worker caches the app's files, so the app can open without a network. Data coming from a server needs its own strategy: local storage, a queue of pending writes. The skeleton shows both.
HashRouter or path-based routing on GitHub Pages?
Both work. Routing with # avoids any 404 but gives less readable addresses. Path-based routing needs a fallback 404.html. The skeleton chose paths, and documents why.
Why prompt rather than autoUpdate?
Because an automatic reload can land in the middle of typing. With prompt, the new version offers itself and the user chooses when. The trade-off: someone can ignore the message and stay on an old version for a long time.
Can you use the skeleton without Supabase?
Yes. It starts with no configuration at all, in local mode, and says so in its settings. Supabase is just an adapter that switches on when its two variables are present.
References
- Development, vite-plugin-pwa: the service worker is disabled by default in development.
- Service Worker API, MDN: secure contexts,
localhostincluded. - Maskable icons, web.dev: a safe zone with a radius of 40% of the width.
- Deploying a static site, Vite: the
baseoption for GitHub Pages.