我是怎么把个人论坛首页性能从80分优化到100分的(附踩坑全记录)

本文记录了一个大二学生在开发校园论坛时遇到的真实线上问题,以及从 Lighthouse 80 分一步步优化到 100 分的完整过程。如果你也在独立做全栈项目,希望这些坑和经验对你有帮助。

项目背景

我是一名计算机专业大二学生,独立开发了一个面向全校的校园论坛。

技术栈 :Vue3 + Vite + Pinia(前端) + Express + MongoDB(后端) 部署架构:Vercel(前端) + Railway(后端) + Cloudflare(DNS 与 CDN)

项目刚上线时,首页加载速度很不理想------用 Lighthouse 一跑,Performance 只有 80 分,First Contentful Paint 高达 3.6 秒。对一个面向全校学生的论坛来说,这个体验显然不行。

下面是我从发现问题到逐一解决的完整过程。


一、移动端深色模式异常:CSS 变量控制权之争

问题现象

论坛支持手动切换深色/浅色主题,用 Pinia Store 管理状态并持久化到 localStorage。桌面端测试一切正常,但用手机浏览器打开时,深色模式下的颜色完全错乱------该亮的地方暗了,该暗的地方亮了。

排查过程

刚开始怀疑是 CSS 变量写错了,在桌面端反复切换都没问题。后来无意中在手机 Chrome 里关掉了系统深色模式,论坛颜色居然变正常了。

这才意识到:不是我的代码逻辑错了,而是浏览器系统设置和我的手动切换在打架。

根因分析

我的 CSS 里同时存在两套深色判断机制:

  • CSS 媒体查询@media (prefers-color-scheme: dark) 监听系统偏好
  • JS 手动切换 :通过 document.documentElement.setAttribute('data-theme', 'dark') 控制

当手机系统处于深色模式时,媒体查询自动生效,覆盖 了我手动设置的 data-theme="light"。两套规则同时作用,颜色就乱了。

解决方案

既然 JS 已经完全接管了主题切换,CSS 媒体查询就成了干扰项。我把所有 @media (prefers-color-scheme: dark) 块里的变量迁移到 [data-theme="dark"] 选择器下,然后删掉媒体查询块。

同时改进了初始化逻辑,让系统偏好只在用户首次访问时作为默认值,之后完全由用户手动控制:

javascript 复制代码
const saved = localStorage.getItem(STORAGE_KEY)
const getSystemPreference = () => {
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
const theme = ref(saved || getSystemPreference())

额外发现

修完后手机端仍有异常,排查发现是部分安卓自带浏览器 (如华为、小米浏览器)有"强制深色模式",会无视网站的 data-theme 属性直接反转颜色。解决办法是在 index.html 里加 meta 标签明确告诉浏览器"我的网站已支持深色模式,请不要自作主张":

html 复制代码
<meta name="color-scheme" content="light dark" />

教训:前端状态管理要保证"单一数据源"原则。主题切换要么全交给 CSS 媒体查询,要么全交给 JS,混用必然出问题。


二、首页性能优化:从 Lighthouse 80 到 100

第一次跑 Lighthouse

优化前跑了一次,报告显示:

  • Performance:80
  • FCP(首次内容绘制):3.6s
  • LCP(最大内容绘制):3.6s
  • Speed Index:4.5s

关键瓶颈:Render-blocking requests,一个 2.3KB 的 CSS 文件阻塞了首屏渲染,预估可节省 890ms。

第一步:内联关键 CSS

既然是渲染阻塞,最简单的办法就是把阻塞的 CSS 直接内联到 HTML 里,让浏览器拿到 HTML 就能渲染,不用再等额外的网络请求。

我打开 Vite 构建后的 dist/assets/index-xxx.css,把全部内容复制进了 index.html<style> 标签。由于文件只有 8KB 左右,内联不会明显增加 HTML 体积。

但这里踩了一个坑:Vite 构建后 CSS 文件依然存在,我以为内联失败。后来才明白,Vite 仍输出完整 CSS 给非首屏路由使用,但浏览器已经不会被它阻塞首屏渲染了。

优化后 FCP 明显下降,Performance 从 80 飙到 97。

第二步:Preconnect 预连接后端

97 分已经不错了,但请求链分析显示,页面必须等 JS 下载并执行后,才发起 /api/posts 请求获取帖子列表。这三个步骤是串行的。

解决方案:在 HTML 头部加一行 <link rel="preconnect">,让浏览器在解析 HTML 时就提前和后端服务器握手,等 JS 发起请求时 TCP 连接已经就绪:

html 复制代码
<link rel="preconnect" href="https://forum-project-production.up.railway.app" />

这一步让关键路径延迟从 3055ms 降到 2355ms。

第三步:Cloudflare 缓存 API 响应

此时 /api/posts 有约 2 秒的响应延迟,这是 Railway 后端本身的响应时间 + 物理距离,前端已经无能为力。

我用 Cloudflare 的 Cache Rules 为 /api/posts 配置了 2 小时的边缘缓存。这样同一个接口在缓存有效期内直接从 Cloudflare 全球节点返回,响应时间降到 100ms 以内。

注意:缓存规则只对走你域名的请求生效。如果你的 API 请求直接指向 Railway 后端(跨域绝对路径),Cloudflare 缓存完全用不上。这一点在下一步踩坑中暴露了出来。

第四步:统一 API 路径 ------ 踩了最大的坑

之前的 API 请求全部用的 Railway 绝对地址:

javascript 复制代码
fetch('https://forum-project-production.up.railway.app/api/posts')

这导致所有请求绕过 Cloudflare,缓存规则形同虚设。于是我改成了相对路径:

javascript 复制代码
fetch('/api/posts')

但改完之后本地开发环境直接报错Unexpected end of JSON input

原因:相对路径在本地会请求 localhost:5173/api/posts,而 Vite 开发服务器上根本没有这个接口。

解决:在 vite.config.js 里配置代理,让开发环境也自动转发 /api 请求到 Railway:

javascript 复制代码
server: {
  proxy: {
    '/api': {
      target: 'https://forum-project-production.up.railway.app',
      changeOrigin: true
    }
  }
}

同时创建 vercel.json 为生产环境配置 Rewrite 代理:

json 复制代码
{
  "rewrites": [
    {
      "source": "/api/:path*",
      "destination": "https://forum-project-production.up.railway.app/api/:path*"
    }
  ]
}

又一个坑 :最开始把 vercel.json 放在了 src/ 目录里,部署后接口一直 404。折腾半天才发现这个文件必须放在项目根目录。

修完之后,Performance 终于冲到了 100


三、发帖体验优化:从"发出去了吗"到"发布成功"

原来的流程

发帖 → 调用 API → 清空表单。没了。没有反馈,不知道成功了没,也不知道帖子在哪。

优化后的流程

发帖 → 等待后端返回 → 弹出成功卡片 → 2 秒倒计时 → 自动跳转首页 → 新帖出现在列表顶部。

具体改动

usePostsStoreaddPost 函数需要 return newPost,让组件能拿到新帖的 _id

发帖组件增加三个状态:

  • submitting:控制按钮禁用和文字切换
  • showSuccessCard:控制成功弹窗
  • countdown:倒计时秒数

核心逻辑:

javascript 复制代码
async function submit() {
  if (!content.value.trim() || !title.value.trim()) return

  submitting.value = true
  try {
    await postsStore.addPost(content.value, title.value)
    // 清空表单、弹成功卡片、倒计时跳转
  } catch (err) {
    alert('发布失败:' + err.message)
  } finally {
    submitting.value = false
  }
}

另外两个小细节

  1. 新帖排在最前面push 改成 unshift,让新帖插入列表头部而非尾部。
  2. 跳转时滚到顶部 :在 Vue Router 里加 scrollBehavior() { return { top: 0 } },避免跳转后停在旧滚动位置。

四、总结与感受

这次优化踩了太多坑:CSS 变量控制权冲突、手机浏览器强制深色、Cloudflare 缓存不生效、vercel.json 位置放错、API 路径改动导致本地崩溃......每一个坑查出来都要花很久,但查出来那一刻的成就感也是真实的。

分享几点体会:

  1. Lighthouse 跑分不是玄学,报告里清清楚楚写着瓶颈。 先跑分再优化,不要盲目猜。
  2. 单一数据源原则不只是口号。 主题切换上的混乱就是典型反面案例。
  3. 部署和构建相关的配置文件要搞清楚放哪。 vercel.json 放错目录这种事情,文档不会特意强调,但错了排查起来很痛苦。
  4. 本地和生产环境要尽量对齐。 这次 vite.config.jsvercel.json 分工协作,才让开发和生产都正常工作。

如果你也在做独立项目,欢迎评论区交流你的踩坑经历。

相关推荐
likerhood1 小时前
ConcurrentHashMap详细讲解(java)
java·开发语言·性能优化
Amy_yang1 小时前
uni-app 安卓端纯前端预览 DOCX 的实现思路
前端·vue.js
xiangxiongfly9152 小时前
Vue3 动态加载静态资源
前端·javascript·vue.js
克里斯蒂亚诺更新2 小时前
ruoyi切换新版本初始化需要修改的地方
前端·javascript·vue.js
前端那点事2 小时前
Vite+Vue3环境判断终极解法!区分开发/生产环境,告别环境报错
前端·vue.js
ZHIS3 小时前
移动端 Vue3 高清 PDF 预览组件开发:支持手势缩放 + 按钮缩放 + 加载进度
vue.js
Amy_yang3 小时前
uni-app 中 web-view 的使用与 App 端全屏问题处理
前端·javascript·vue.js
蜡台5 小时前
Vue3 Hook 与 Store 状态管理:深度解析与选型指南
前端·javascript·vue.js
存在的五月雨5 小时前
项目中 Vitest 配置详解:vitest.config.ts
开发语言·javascript·vue.js