随记

从微信小程序迁移到 PWA

最近把一个原本运行在微信小程序里的项目迁移出了一套独立 PWA。原因很直接:小程序适合作为轻量入口,但平台审核、类目和能力限制比较明显,希望保留原有版本的同时,再提供一个不依赖微信运行环境的 Web 版本。

这次没有重写后端,而是把整个系统调整成了“双前端 + 单后端”的结构:

1
2
3
微信小程序 ─┐
├─→ FastAPI → 数据库 / AI / 邮件服务
PWA Web ────┘

业务数据仍然集中在服务器,PWA 只是增加了一个新的客户端。这样迁移成本比重新开发一套系统低很多。

小程序前端迁移到 Vue

新的 Web 端使用:

1
2
3
4
5
6
Vue 3
TypeScript
Vite
Vue Router
Pinia
PWA

第一版没有重新设计 UI,而是优先把原来的页面和功能搬过来,包括时间线、记录、回顾、问答、关注、个人中心,以及一个可拖动的小型桌宠。

小程序 API 需要替换成浏览器对应能力:

1
2
3
4
5
6
wx.request        → fetch
wx.setStorage → localStorage
wx.navigateTo → Vue Router
wx.showToast → Web Toast
Touch Events → Pointer Events
wx.login → 邮箱账号登录

其中业务数据仍然通过 API 获取,localStorage 只保存桌宠位置、界面状态和登录信息,不在浏览器里重新维护一套业务数据库。

移动端还增加了安全区域处理:

1
2
env(safe-area-inset-top)
env(safe-area-inset-bottom)

避免刘海屏和底部手势区域遮挡内容。

把网站变成 PWA

普通 Vue 网站基础上增加了 Web App Manifest 和 Service Worker:

1
2
manifest.webmanifest
sw.js

配置了应用名称、图标、主题色和:

1
display: standalone

这样浏览器可以把网页添加到手机桌面,打开后更接近普通 App。

Service Worker 当前只缓存静态资源,不缓存用户接口:

1
2
3
4
HTML
CSS
JS
图片

而登录、日志、AI 等 /api 请求全部保持在线访问,避免敏感数据被不必要地缓存。

Vue Router 使用 history 模式后还需要处理一个常见问题:直接刷新 /review、/login 之类的子页面时,静态托管平台可能返回 404。

因此增加 SPA fallback:

1
/* /index.html 200

让所有前端路由重新交给 Vue Router 处理。

GitHub + Cloudflare Pages

Web 版本单独建立 Git 仓库,不和原微信小程序、后端代码混在一起。

部署流程变成:

1
2
3
4
5
6
7
8
9
10
11
本地开发
↓
Git commit
↓
Push GitHub
↓
Cloudflare Pages 自动构建
↓
npm run build
↓
发布 dist

相比手动上传服务器,最大的好处是每次 push 都可以自动部署。

以后也可以使用简单的分支策略:

1
2
3
4
5
6
7
8
main
生产版本

feature/*
新功能

fix/*
Bug 修复

开发分支可以先生成 Preview 页面,确认没有问题再合并到 main。

部署时遇到的两个问题

第一个问题是 API 地址。

本地开发时习惯直接请求:

1
/api/...

部署到静态托管平台以后,这会变成:

1
PWA域名/api/...

自然找不到真正的 FastAPI。

后来通过 Vite 环境变量解决:

1
VITE_API_BASE_URL=https://<API域名>

然后 API Client 统一拼接 Base URL。

需要注意,VITE_* 是构建时变量,修改托管平台里的环境变量后必须重新 Build,单纯刷新网页不会生效。

第二个问题是 CORS。

PWA 和 FastAPI 已经属于不同 Origin:

1
2
3
4
5
PWA域名
↓
跨域请求
↓
API域名

因此后端需要把 PWA 来源加入 CORS 白名单。

浏览器发送 JSON POST 前还可能先发:

1
OPTIONS /api/...

也就是 CORS Preflight。遇到“页面能打开,但登录、验证码全部失败”的情况,Network 面板里的 OPTIONS 请求通常是很好的排查入口。

密钥继续只放服务器

迁移 Web 以后最需要注意的是:浏览器前端没有真正的 Secret。

例如:

1
2
3
4
邮件服务 SecretKey
JWT Secret
AI API Key
数据库凭据

都不能放进:

1
VITE_*

因为 Vite 环境变量最终会被打包到浏览器 JS 中。

正确结构仍然是:

1
2
3
4
5
PWA
↓
FastAPI
↓
邮件 / AI / 数据库

PWA 只能知道公开的 API 地址,所有真正的凭据继续保留在服务器环境变量中。

最后的结构

这次迁移完成后,项目从单一的小程序前端变成:

1
2
3
4
5
6
7
8
9
        ┌─ 微信小程序
│
用户 ───┤
│
└─ PWA
↓
FastAPI
↓
数据库 / AI / 邮件

整个过程真正改动最大的不是后端,而是运行环境的替换:把微信提供的导航、存储、网络请求、触摸事件逐步换成浏览器标准能力。

PWA 的价值也不在于完全取代原生 App,而是用一套普通 Web 技术获得了接近 App 的安装和使用体验,同时保留 Web 部署简单、跨平台和更新快的特点。

对于已经拥有独立后端的小程序项目,这种迁移方式比重新开发一个完整 App 要轻量得多。