提供 PWA 组合层骨架:入口、路由、Provider 栈、设置页与后端选择,而非业务功能
pwa-starter-kit — Vite 启动器模板
作者 mister-guiiug
一个基于 Vite 与 React 的 PWA 启动骨架,把家族应用反复重写的组合层(入口、路由、Provider 栈、设置页、后端选择)固定下来,并附带 Supabase 可选后端与离线写入队列。
看看你的网站会是什么样
浏览真实页面截图,比较内容排版与手机效果。点击下方缩略图切换,放大查看整页细节。
全部页面与视图 2
截图用于了解视觉与布局,交互效果请以实际项目为准。
值得关注的模板亮点
从已有项目资料中,快速了解它的特点。
默认零配置启动,开发端口为 5240,并内置 manifest 生成与 SPA 深层链接回退
构建流程包含类型检查、体积预算与 pwa-doctor 严格检查,不通过即失败
Supabase 作为可选适配器启用,缺失环境变量时应用仍以本地模式完整运行
包含离线写入队列、同步状态徽章、删除撤销与账号删除等机制,并配有 ADR 文档
模板详细介绍
这个仓库是作者家族 PWA 应用的“骨架”,用于替代每个新应用从零重写组合层的做法。它本身不含业务逻辑,只提供框架性决策:main.tsx、路由、Provider 栈、设置界面、后端选择等。骨架建立在 @mister-guiiug/dev-pwa-config 之上,安装时需从 GitHub Packages 读取该依赖。
项目默认无需任何配置即可启动,开发服务器监听 5240 端口。它内置了若干经过取舍的机制:用 pwaBaseOptions 生成 manifest、spaFallbackPlugin 处理深层链接的 404、版本化存储、后端选择回退到本地并在设置页明示、删除笔记可撤销、支持删除账号、离线写入队列与同步状态徽章。构建命令会执行类型检查、Vite 构建、体积预算和 pwa-doctor 严格检查,任一不通过即失败。
Supabase 不是独立分支,而是当 VITE_SUPABASE_URL 与 VITE_SUPABASE_ANON_KEY 存在时启用的适配器;缺失时应用仍完整运行在本地模式。启用后包含迁移、pgTAP 测试、RLS 策略、账号删除函数、离线队列包装以及相关 GitHub Actions 工作流。README 还说明了两项无法由迁移或工作流自动完成的项目端设置:启用 Custom Access Token 钩子,以及把应用地址加入允许的回跳 URL 列表。
使用方式上,作者建议不要直接克隆仓库,而是通过 create-lg-pwa-app 生成器创建新项目;不带 --from 参数时默认基于旧标签 v1.2.0,而非 main 分支的最新状态。
适合你的项目吗?
适用场景
- 需要为新的 PWA 项目快速搭建统一的应用外壳与工程约定
- 希望在同一套骨架中按需接入 Supabase 后端,同时保留本地运行能力
- 需要参考离线写入队列、版本化存储、账号删除等具体实现方式
适合人群
- 使用 Vite 与 React 构建 PWA 的开发者
- 需要统一多个应用组合层与工程配置的团队
- 希望以生成器方式创建新项目而非直接克隆仓库的使用者
上手与定制指南
结合仓库当前版本的说明,按下面的资料检查内容、配置与运行要求。
- 1
npm install 前需导出 NODE_AUTH_TOKEN(具备 read:packages 权限),因为依赖从 GitHub Packages 读取
- 2
开发服务器默认监听 5240 端口,由 vite.config.ts 中的 devPortOf(APP_ID, 5240) 指定
- 3
启用 Supabase 需在仓库 vars 中设置 VITE_SUPABASE_URL 与 VITE_SUPABASE_ANON_KEY,并传入构建环境
- 4
还需在 Supabase 项目端启用 Custom Access Token 钩子,并将应用地址加入允许的回跳 URL 列表
- 5
作者建议通过 npx github:mister-guiiug/create-lg-pwa-app 生成新项目,而非直接克隆本仓库
授权与来源资料
使用条件与限制
免费使用 · 保留许可声明本次收录的是公开仓库中采用 MIT 许可的免费版本;免费范围为该版本代码,不代表外部服务或第三方素材免费。
使用时需要遵守
- 允许免费使用、修改、商用和分发该版本代码。
- 分发代码副本或实质性部分时,保留原作者版权声明和完整 MIT 许可声明。
- 代码按现状提供;担保与责任限制以原许可证及适用法律为准。
额外费用与待核对事项
- 安装依赖需从 GitHub Packages 读取私有包,必须先导出具有 read:packages 权限的 NODE_AUTH_TOKEN,否则 npm install 无法完成。
- Supabase 远程后端仅在设置 VITE_SUPABASE_URL 与 VITE_SUPABASE_ANON_KEY 时启用;未设置时应用仅以本地模式运行,无远程同步与账号功能。
- 启用 Supabase 需额外配置项目 ID 变量、SUPABASE_ACCESS_TOKEN 与 SUPABASE_DB_PASSWORD 两个密钥,并手动应用 keep-alive.sql,否则相关工作流会失败。
- 必须在 Supabase 项目端手动启用 Custom Access Token 钩子,否则令牌不携带角色,useRole() 永远无法识别管理员。
- 需在 Supabase 项目端把应用地址加入 site_url 与允许的回跳 URL 列表,否则登录链接点击后无法返回应用。
- 作者建议不要直接克隆仓库,而应通过 create-lg-pwa-app 生成器创建项目;直接克隆需自行完成大量标识符替换。
- 生成器不带 --from 参数时默认基于旧标签 v1.2.0,落后 main 分支 66 个提交,缺少撤销删除、账号删除、离线队列等功能。
- 演示图片、字体、图标及其他第三方素材需按各自许可核对,不能仅凭代码的 MIT 许可判断。
- 域名、托管、数据库、支付或 AI 等外部服务可能另外收费。
查看原文依据
Permission is hereby granted, free of charge
许可证原文
L'installation lit le socle sur GitHub Packages : exporter `NODE_AUTH_TOKEN` (un jeton avec `read:packages`) avant `npm install`.
README 原文
C'est un adaptateur qui s'active quand `VITE_SUPABASE_URL` et `VITE_SUPABASE_ANON_KEY` sont présentes
README 原文
Puis la référence du projet en variable (`SUPABASE_PROJECT_ID` : elle est dans l'URL), deux secrets pour les migrations (`SUPABASE_ACCESS_TOKEN`, `SUPABASE_DB_PASSWORD`, par le propriétaire), et appliquer `supabase/keep-alive.sql`.
README 原文
**activer le hook « Custom Access Token »** (Authentication → Hooks, ou l'API de gestion : `hook_custom_access_token_uri` = `pg-functions://postgres/public/custom_access_token_hook`). Sans lui, le jeton ne porte aucun rôle et `useRole()` ne voit jamais un administrateur
README 原文
**autoriser l'adresse de l'application comme retour de lien** (`site_url` et la liste d'URL autorisées). À la création, un projet n'autorise que `http://localhost:3000` : le lien de connexion part, l'utilisateur clique, et n'arrive nulle part.
README 原文
**Ne clonez pas ce dépôt** : appelez le générateur, qui en tire une archive et la met à votre nom
README 原文
https://github.com/mister-guiiug/pwa-starter-kit/blob/main/LICENSEhttps://github.com/mister-guiiug/pwa-starter-kit/blob/main/README.md**Sans `--from`, il ne part pas de ce que décrit ce README**, mais de la dernière étiquette du squelette : `v1.2.0`, posée le 06/09/2026, soixante-six commits derrière `main` au 28/09.
README 原文
DeepSeek 根据仓库许可证和 README 整理 ·2026-10-02。结果仅覆盖已读取资料,依赖、图片、字体及后续条款变更以各自原文为准。
项目核对记录与介绍依据
模板资料
- 来源平台
- GitHub
- 资料与维护参考分
- 资料较完整 · 85 / 100
- 技术栈
- Vite · React
- 网站类型
- 启动器
这个分数如何计算
本站按许可资料(最高30分)、演示链接(12分)、预览图(10分)、Star数(15分)、记录的维护时间(15分)、描述完整度(8分)、功能记录(5分)和分类资料(5分)计算。它不是代码、安全或性能测试,也不是 Google 评分;有链接不代表已实测可用。
这个项目需要核对什么
- 记录的仓库更新时间
- 2026-10-01
- 截图检查日期
- 2026-10-02
已收录演示链接。请用你的内容与屏幕尺寸检查;有演示不代表表单、认证或支付已经可用。
目录记录的主要技术栈是 Vite。请通过依赖清单和锁文件核对实际版本与托管要求。这些说明基于收录资料,不代表代码审计或部署实测。
查看具体选型方法 →介绍的制作与核对状态
介绍由 AI 辅助整理目录与仓库资料。仓库作者不等于这篇介绍的审核者。
已完成服务器许可规则与 AI 来源核对;未经过人工编辑审核。
查看当时使用的来源资料
SHA-256: 463fb2fd0fdd26cd491b4b297909c51b7b2d43e460d72e7759fc551574f2317e
2026-10-02T11:37:06.937Z
来源文件 / 仓库# pwa-starter-kit
Le **squelette** des applications PWA de la famille `miss-*` / `mister-*` : la
composition que chaque application réécrivait, assemblée une fois sur
[`@mister-guiiug/dev-pwa-config`](https://github.com/mister-guiiug/dev-pwa-config),
et dont le générateur tire chaque nouvelle application.
Il n'a pas de métier. Il a le **cadre** — et les décisions qui vont avec.
## Pourquoi il existe
Le socle est une bibliothèque : il donne des composants, des configurations et
des workflows. Ce qu'il ne peut pas donner, c'est la **composition** — `main.tsx`,
le routeur, la pile de fournisseurs, l'écran de réglages, le choix du backend.
Ce code-là ne se factorise pas dans un paquet agnostique, alors il est réécrit
à chaque naissance.
Mesuré sur les dix-sept applications du parc : **cette coquille pèse 22 % des
lignes, soit environ 52 000**. Et elle dérive — dix-sept `vitest.config.ts`
différents pour dix-sept applications, dix-sept `index.html`. Les composants
promus pour l'absorber ne sont pas adoptés après coup : la couche
d'authentification livrée le 02/09/2026 comptait dix copies dans cinq
applications, et **zéro migration**.
Une composition se donne à la naissance, ou jamais. C'est ce que fait ce dépôt.
## Démarrer
```bash
npm install
```
```bash
npm run dev
```
L'installation lit le socle sur GitHub Packages : exporter `NODE_AUTH_TOKEN`
(un jeton avec `read:packages`) avant `npm install`.
Le serveur de développement écoute sur le port **5240**, celui que le catalogue
du socle réserve au squelette : `devPortOf(APP_ID, 5240)` dans `vite.config.ts`,
repris par `.claude/launch.json` pour l'aperçu du poste. Une application
engendrée reçoit à sa naissance un port libre du même catalogue.
**L'application démarre sans aucune configuration.** C'est une propriété à
conserver : elle rend possibles le hors-ligne, les tests sans secrets, et la
page publique qu'on ouvre sans compte.
## Vérifier
| Commande | Ce qu'elle vérifie |
| --------------------- | ------------------------------------------------------------------------------------------ |
| `npm run lint` | ESLint du socle (react-hooks, jsx-a11y, react-refresh) |
| `npm run type-check` | TypeScript strict, `tsc -b` |
| `npm test` | Vitest : le magasin des notes, la file hors ligne, `useRole()` et la suppression de compte |
| `npm run test:e2e` | Playwright : le cadre, sur un **build de production** |
| `npm run build` | `tsc -b`, Vite, budget de poids, puis `pwa-doctor --strict` |
| `npm run doctor` | La conformité au parc : échoue sur un défaut, pas sur une dette |
| `npm run screenshots` | Régénère les captures du manifeste par `pwa-screenshots` du socle, sur un build |
`npm run build` échoue si le poids dépasse le budget **ou** si `pwa-doctor`
trouve la moindre dette. C'est délibéré : ce dépôt est la définition exécutable
de « conforme au parc », il doit passer sa propre porte.
## Ce qu'il démontre
Chaque pièce est là parce que son absence a coûté quelque chose de mesuré :
- **`pwaBaseOptions({ id })`** engendre le manifeste au lieu de le recopier.
Ce module du socle n'avait **aucun importateur**, et les manifestes écrits à
la main portaient `lang: en` sur trois applications françaises, pas d'`id`
six fois, pas de `maskable` trois fois ;
- **`spaFallbackPlugin()`** évite la page 404 de GitHub sur un lien profond —
quatre applications en souffraient ;
- **le magasin versionné** met la donnée de côté avant toute perte possible,
ce qu'aucun des sept `storage.ts` maison ne faisait ;
- **le sélecteur de backend** retombe sur le local et le **dit** dans l'écran
de réglages ;
- **`components.css`** est importé : sans lui, les composants du socle sont
nus, ça compile, les tests passent, et l'écran est cassé ;
- **la barre basse est collée, et le contenu réservé dessous, par `AppShell`**
du socle, dont ce sont les défauts (`navPlacement: 'fixed'`, d'où
`reserve: 'bottom-nav'`) : huit dépôts recopiaient la même règle CSS, et
chaque copie pouvait diverger sur la zone sûre iOS ;
- **les captures du manifeste sont lues dans `public/screenshots`** et les
couleurs dans `src/index.css` : `vite.config.ts` ne les recopie plus, et
`npm run screenshots` les régénère depuis un build.
- **le port des notes parle en mutations** (`add`, `remove`, `import`) : le
premier adaptateur Supabase effaçait toutes les lignes et réinsérait la
liste à chaque note ajoutée — son propre commentaire annonçait la limite ;
- **les réglages importent** ce qu'ils exportent, par
`versioned-store.import()` : le fichier passe par le schéma, un fichier
d'une autre app est refusé sans rien effacer, et des notes existantes
demandent confirmation avant d'être remplacées. C'est le seul moyen de
changer d'appareil sans compte, et presque aucune app du parc ne l'offrait ;
- **« Signaler un problème »** dans le pied de page (`AppFooter issues`) :
le gabarit `bug.yml` du compte, prérempli avec la version, le commit,
l'écran et le navigateur — ce qu'un rapport n'a jamais après coup. Le pied
de page est rendu **sur l'accueil et sur « À propos », nulle part ailleurs**
— la règle famille du 06/09/2026, que `pwa-doctor` vérifie ; dans la
coquille, il suivait chaque écran ;
- **la fiche d'installation** (`PwaInstallPrompt`) apparaît dans « À propos »
quand le navigateur le permet, et seulement alors ;
- **supprimer une note s'annule** au lieu de se confirmer : la note quitte
l'écran tout de suite, la base ne l'apprend qu'après huit secondes, et
« Annuler » la remet **à sa place**. `ConfirmDialog` du socle est adopté par
quatorze applications, `useUndoableState` par zéro, et **aucune** n'offrait
d'annulation après suppression d'un enregistrement — cf.
[ADR 0008](./docs/adr/0008-annuler-plutot-que-confirmer.md) ;
- **on peut supprimer son compte**, et la base le prouve. Dix applications du
parc ont des comptes, **deux** offraient une voie d'effacement. La carte
« Zone dangereuse » demande de **retaper son adresse** — pas un « OK », qui
est le même clic que celui qu'on regrette — et n'existe pas en mode local.
Surtout : qu'une fonction `security definer` puisse effacer dans `auth.users`
était **documenté et jamais prouvé** sur ce parc ; dix-huit assertions pgTAP
le figent, mécanisme compris — cf.
[ADR 0009](./docs/adr/0009-supprimer-son-compte.md) ;
- **on écrit hors ligne**, et ce qui attend le **dit**. La file du socle
(`sync-queue`) enveloppe le port distant : une note écrite sans réseau est
retenue puis rejouée, une écriture refusée par la RLS part en lettre morte au
premier essai au lieu de boucler, et `SyncStatusBadge` porte l'état. Le
branchement d'un port sur cette file avait été écrit trois fois dans le parc
et jamais dans le squelette — cf.
[ADR 0010](./docs/adr/0010-ecrire-hors-ligne.md).
## Supabase : une variante, pas un fork
Le backend distant n'est **pas** une branche séparée. C'est un adaptateur qui
s'active quand `VITE_SUPABASE_URL` et `VITE_SUPABASE_ANON_KEY` sont présentes,
et l'application reste entière sans elles. Un fork aurait deux CI, deux
historiques, et divergerait en quelques semaines.
Ce que la variante apporte, tout est déjà là :
| Pièce | Ce qu'elle règle |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `supabase/migrations/0001…0005` | `profiles`, rôles, RLS **deny-by-default** avec double verrou, le hook qui recopie le rôle dans le jeton, et `delete_my_account()` |
| `supabase/tests/*.test.sql` | trente et une assertions pgTAP : deux comptes ne se voient pas, le rôle arrive dans le jeton, un compte s'efface vraiment |
| `supabase/config.toml` | la pile locale, pour jouer migrations et tests depuis zéro (`pwa-supabase-test.yml`) |
| `src/backend/supabase.ts` | l'adaptateur du seul port `notes` — les autres restent locaux ; `flowType: 'pkce'` |
| `src/backend/queued-notes.ts` | la file d'écritures hors ligne (`sync-queue` du socle) enveloppant ce port ; rien en mode local |
| `src/auth/`, `src/features/account/` | le fournisseur, le formulaire — lien d'abord, mot de passe en option — et `useRole()` |
| `.github/workflows/supabase-*.yml` | tests pgTAP sur une pile jetable, migrations, et le keep-alive anti-pause du plan Free |
Pour l'activer : poser les deux variables dans **`vars`** du dépôt (jamais dans
`secrets` : Vite les copie dans le bundle), **et les passer au build**.
`pwa-deploy.yml` n'injecte que ce que l'appelant lui donne, et le `deploy.yml`
de ce dépôt ne lui passe que `VITE_POSTHOG_KEY` : sans les deux lignes
ci-dessous, le site publié reste en local, pendant que migrations et
keep-alive, qui lisent `vars` directement, visent bien le projet.
```yaml
build-env: |
VITE_POSTHOG_KEY=${{ vars.VITE_POSTHOG_KEY }}
VITE_SUPABASE_URL=${{ vars.VITE_SUPABASE_URL }}
VITE_SUPABASE_ANON_KEY=${{ vars.VITE_SUPABASE_ANON_KEY }}
```
Les nommer aussi en `required-env` arrête le déploiement quand l'une manque.
Puis la référence du projet en variable (`SUPABASE_PROJECT_ID` : elle est dans
l'URL), deux secrets pour les migrations (`SUPABASE_ACCESS_TOKEN`,
`SUPABASE_DB_PASSWORD`, par le propriétaire), et appliquer
`supabase/keep-alive.sql`. Sans la table `keep_alive`, le ping du keep-alive
répond 404 et le workflow rougit ; le projet s'endort quand même. La procédure
complète, geste par geste et avec ses variantes, est dans
[PARAMETRAGE.md](https://github.com/mister-guiiug/dev-pwa-config/blob/main/PARAMETRAGE.md)
du socle.
Deux réglages de plus, côté projet Supabase, que ni une migration ni un
workflow ne peuvent poser — et dont l'absence ne fait aucun bruit :
- **activer le hook « Custom Access Token »** (Authentication → Hooks, ou
l'API de gestion : `hook_custom_access_token_uri` =
`pg-functions://postgres/public/custom_access_token_hook`). Sans lui, le
jeton ne porte aucun rôle et `useRole()` ne voit jamais un administrateur ;
- **autoriser l'adresse de l'application comme retour de lien** (`site_url`
et la liste d'URL autorisées). À la création, un projet n'autorise que
`http://localhost:3000` : le lien de connexion part, l'utilisateur clique,
et n'arrive nulle part.
## Les décisions
Elles sont écrites, avec leur contexte mesuré et ce qu'elles écartent :
[`docs/adr/`](./docs/adr/README.md).
Un gabarit donne des fichiers ; un squelette donne des décisions déjà prises.
C'est la seule différence qui compte.
## Partir de là
**Ne clonez pas ce dépôt** : appelez le générateur, qui en tire une archive et
la met à votre nom :
```bash
npx github:mister-guiiug/create-lg-pwa-app miss-exemple --publish
```
**Sans `--from`, il ne part pas de ce que décrit ce README**, mais de la
dernière étiquette du squelette : `v1.2.0`, posée le 06/09/2026, soixante-six
commits derrière `main` au 28/09. Elle est sur le socle 4 et ses workflows
`@v4`, n'a ni l'annulation des suppressions, ni la suppression de compte, ni la
file hors ligne, ni la mesure d'audience, et rend encore le pied de page dans
la coquille. `--from main` part de la pointe.
[`create-lg-pwa-app`](https://github.com/mister-guiiug/create-lg-pwa-app)
substitue l'identifiant et le nom affiché, installe les dépendances avec
**npm 10.9.8**, construit l'application, fait le premier commit, crée le dépôt
public et active Pages **par un PUT**, seule forme qui empêche Jekyll de
republier le README à la place de l'application. Deux réserves, qu'il ne traite
pas encore :
- la CI de la famille rejoue le lockfile en npm 11 (Node 26.10.0), et npm 10
retire les champs `libc` qu'écrit npm 11 et que porte le lockfile de ce
dépôt ;
- le nom court « Starter Kit » (`apple-mobile-web-app-title` d'`index.html`)
n'est pas remplacé, pas plus que la page de `content/pages/` et l'image
`public/og-image.jpg`, qui présentent le squelette.
Restent cinq gestes, que le générateur imprime et ne fait pas :
1. `node scripts/apply-rulesets.mjs <id>` depuis le socle, pour protéger la
branche ;
2. inscrire l'application au catalogue du socle par une PR, sans quoi elle
n'apparaît pas chez ses sœurs ;
3. régénérer les icônes (`npm run icons`) depuis un nouveau `favicon.svg`, en
accordant l'option `--bg` du script à la couleur de sa tuile — c'est elle
qui comble le pourtour du maskable, et un désaccord met un cadre autour de
l'icône installée ;
4. remplacer la fonctionnalité d'exemple de `src/features/home/`, en gardant
l'écran : son accroche (`app.tagline`) et le pied de page de la famille, que
`pwa-doctor --strict` exige sur l'accueil. Supprimer le dossier retirerait
aussi la route `/`. Le générateur le dit quand l'accueil engendré les porte ;
5. relire la description (la meta d'`index.html`, `app.tagline` et
`about.what` de `src/i18n/messages.ts`) et traduire l'anglais marqué
`TODO traduire`.
Pour une application Supabase, il imprime aussi ce qui manque en silence
(variables, secrets, table `keep_alive`, adresse de retour du lien, hook de
rôle), mais pas encore les lignes `build-env` décrites plus haut.
À la main, la substitution n'est pas courte : `pwa-starter-kit` figure dans une
dizaine de fichiers, dont la clé du stockage local (`src/backend/local.ts`) et
le préfixe de la file (`src/backend/queued-notes.ts`). Une copie qui les
garderait partagerait ses données locales avec le squelette, sur l'origine
`mister-guiiug.github.io` commune à toute la famille. Le nom affiché, lui, est
dans `index.html` et `src/i18n/messages.ts`.
## Licence
MIT — voir [LICENSE](./LICENSE).
看看同类模板
对比同技术栈与用途的模板,找到更适合的视觉与内容结构。

react-starter-kit
Modern React starter kit with Bun, TypeScript, Tailwind CSS, tRPC, Stripe, and Cloudflare Workers. Production-…
Vite · SaaS

cove
The Cove Stack: 🏝️ minimal TanStack Start template with Vite+, Better Auth, Drizzle ORM, shadcn/ui
Vite · SaaS

vite-react-boilerplate
A production ready, scalable starter template for Vite + React
Vite · 启动器

react-vite-admin
This Starter utilizes React, Recoil, React Query, React Hooks, Typescript, Axios And Vite. 全新技术栈的后台管理系统
Vite · 后台

typescript-react_best_practices-vite_template
<⚡⚛️> Start your React project with Vite following code best practices (TypeScript, Linting, testing, CI/CD…)
Vite · 启动器

cloudflare-starter-kit
Template (boilerplate) repository for scaffolding Cloudflare Workers projects
Vite · 启动器
