Nuxt 2.15.3+Tailwind+PWA模块下iOS Safari安装PWA后字体首次加载失败
iOS 主屏幕PWA自定义字体首次加载异常问题
问题现象
我开发了一款Nuxt PWA应用,访问地址为www.shirime.one,整体运行状态正常,但在移动端Safari环境下存在自定义字体加载异常的问题。
将应用通过iOS版Safari添加到主屏幕作为PWA安装后,使用MacBook连接设备调试时,在资源目录中找不到字体文件夹;如果通过Safari检查器对PWA页面执行一次刷新操作,字体即可正常加载。
该现象表明,iOS Safari环境下安装的独立运行PWA,在首次打开时Nuxt PWA模块无法正常加载字体类资源,目前暂不明确具体成因。
现有项目配置
Nuxt PWA 配置
pwa: { icon: { fileName: 'app-icon.png', }, meta: { name: 'Shirime Challenge', mobileApp: true, mobileAppIOS: true, description: false, lang: false, theme_color: '#000000', ogHost: 'https://shirime.one', twitterCard: 'summary', twitterSite: '@jbty_dev', twitterCreator: '@jbty_dev', }, },
@font-face 配置
@font-face { font-family: 'Nouveau'; src: url('~/assets/fonts/NouveauRegular.eot'); font-weight: 100; font-style: normal; } @font-face { font-family: 'Nouveau'; src: local('Nouveau Regular'), local('NouveauRegular'), url('~/assets/fonts/NouveauRegular.woff2') format('woff2'), url('~/assets/fonts/NouveauRegular.woff') format('woff'), url('~/assets/fonts/NouveauRegular.ttf') format('truetype'), url('~/assets/fonts/NouveauRegular.svg#NouveauRegular') format('svg'); font-weight: 100; font-style: normal; font-display: swap; }
Tailwind 配置
import plugin from 'tailwindcss/plugin' module.exports = { purge: [ './components/**/*.{vue,js}', './layouts/**/*.vue', './pages/**/*.vue', './plugins/**/*.{js,ts}', './nuxt.config.{js,ts}', ], darkMode: false, // or 'media' or 'class' theme: { extend: { fontFamily: { heading: ['Nouveau', 'sans-serif'], body: ['Futura CondensedLight', 'sans-serif'], incantation: ['Stranger back in the Night', 'sans-serif'], }, colors: { red: '#FF0000', redAccent: '#ff1414', red40: 'rgba(255,0,0,0.4)', black: '#000000', black92: 'rgba(0,0,0,0.92)', white: '#ffffff', white40: 'rgba(255,255,255,0.4)', }, fontSize: { xs: '.82rem', sm: '.95rem', tiny: '.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '2.2rem', '4xl': '2.9rem', '5xl': '3.3rem', '6xl': '3.8rem', '7xl': '6rem', '8xl': '11rem', }, }, }, variants: { extend: {}, }, plugins: [ plugin(function ({ addUtilities }) { const newUtilities = { '.no-text-shadow': { textShadow: 'none', }, '.red-text-shadow': { textShadow: '0px 0px 10px rgba(255,0,0,0.2)', }, '.red-text-shadow-big': { textShadow: '0px 0px 15px rgba(255,0,0,0.6)', }, '.white-text-shadow': { textShadow: '0px 0px 10px rgba(255,255,255,0.4)', }, '.red-box-b-shadow': { boxShadow: '0 18px 10px -10px rgba(255,0,0,0.2)', }, '.white-box-b-shadow': { boxShadow: '0 18px 10px -10px rgba(255,255,255,0.2)', }, '.backdrop-blur': { backdropFilter: 'blur(2px)', }, } addUtilities(newUtilities) }), ], }
package.json 配置
{ "name": "shirime", "author": { "name": "Thery Jean-Baptiste", "email": "jb.thery@flameapp.fr", "url": "https://flameapp.fr" }, "description": "Shirime Challenge - www.shirime.one digital ouija, make your friends believe that you speak with an all-knowing mind! Progressive Web App available for installation on Ios, Android, Windows, Mac Os and Linux.", "version": "1.1.0", "private": true, "scripts": { "dev": "nuxt", "build": "nuxt generate", "start": "nuxt start", "lintfix": "eslint --fix --ext .js,.vue --ignore-path .gitignore .", "lint:js": "eslint --ext .js,.vue --ignore-path .gitignore .", "lint:style": "stylelint **/*.{vue,css} --ignore-path .gitignore", "lint": "npm run lint:js && npm run lint:style", "test": "jest" }, "lint-staged": { "*.{js,vue}": "eslint", "*.{css,vue}": "stylelint" }, "husky": { "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", "pre-commit": "lint-staged" } }, "dependencies": { "@nuxtjs/amp": "^0.5.4", "@nuxtjs/google-adsense": "^1.3.0", "@nuxtjs/pwa": "^3.3.5", "@nuxtjs/sitemap": "^2.4.0", "cookie-universal-nuxt": "^2.1.4", "core-js": "^3.9.1", "nuxt": "^2.15.3", "nuxt-i18n": "^6.21.1", "nuxt-lazy-load": "^1.2.5", "vue-social-sharing": "^3.0.5" }, "devDependencies": { "@commitlint/cli": "^12.0.1", "@commitlint/config-conventional": "^12.0.1", "@nuxt/types": "~2.15.3", "@nuxtjs/eslint-config": "^6.0.0", "@nuxtjs/eslint-module": "^3.0.2", "@nuxtjs/stylelint-module": "^4.0.0", "@nuxtjs/tailwindcss": "^4.0.1", "@tailwindcss/postcss7-compat": "^2.0.3", "autoprefixer": "^10.2.5", "babel-core": "7.0.0-bridge.0", "babel-eslint": "^10.1.0", "babel-jest": "^26.6.3", "eslint": "^7.22.0", "eslint-config-prettier": "^7.2.0", "eslint-plugin-nuxt": "^2.0.0", "eslint-plugin-prettier": "^3.3.1", "fibers": "^5.0.0", "husky": "4.3.8", "lint-staged": "^10.5.4", "postcss": "^8.2.8", "prettier": "^2.2.1", "stylelint": "^13.12.0", "stylelint-config-prettier": "^8.0.2", "stylelint-config-standard": "^21.0.0", "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.3" } }
问题成因与解决方法
核心根源是iOS独立运行的PWA环境下,Service Worker 首次启动时未将字体资源纳入预缓存列表,首次打开时无法从离线缓存读取字体,手动刷新后字体请求被SW缓存,因此后续加载正常。
- 基础修复方案:调整字体存放路径
- 将所有字体文件从
~/assets/fonts/目录移动到Nuxt项目的~/static/fonts/目录 - 同步修改
@font-face中的资源路径,将原有的~/assets/fonts/前缀替换为/fonts/,例如把url('~/assets/fonts/NouveauRegular.woff2')修改为url('/fonts/NouveauRegular.woff2') - 优势:
static目录下的资源不会被Webpack打包添加内容哈希,会被Nuxt PWA模块默认识别为静态资源,自动纳入Service Worker的预缓存清单,PWA首次启动时就会完成字体资源的缓存加载。
- 将所有字体文件从
- 进阶修复方案:显式配置Service Worker预缓存规则
- 如果希望保留assets目录下的字体资源,可在Nuxt的pwa配置中补充workbox规则,显式指定需要预加载的字体资源:
pwa: { // 保留原有pwa配置... workbox: { preCaching: [ '/fonts/*.woff2', '/fonts/*.woff', '/fonts/*.ttf' ], runtimeCaching: [ { urlPattern: /^https:\/\/www\.shirime\.one\/fonts\/.*\.(?:woff2|woff|ttf|eot|svg)$/, handler: 'CacheFirst', options: { cacheName: 'font-cache', expiration: { maxEntries: 10, maxAgeSeconds: 60 * 60 * 24 * 365 } } } ] } } - 作用:避免打包过程中资源路径变更导致的缓存漏匹配,强制Service Worker在PWA启动时加载指定的字体资源。
- 如果希望保留assets目录下的字体资源,可在Nuxt的pwa配置中补充workbox规则,显式指定需要预加载的字体资源:
- 额外适配优化
- 现有@font-face配置中单独拆分的eot格式字体规则缺少
font-display: swap属性,可合并到同一字体的规则中,避免重复资源请求。 - 确保所有字体资源和应用为同域资源,跨域资源会被iOS Safari的PWA环境拦截,导致Service Worker无法正常缓存。
- 现有@font-face配置中单独拆分的eot格式字体规则缺少
内容的提问来源于stack exchange,提问作者Jean-Baptiste THERY
相关产品推荐
相关产品推荐

