从零开始构建一个现代化前端项目:一个前产品经理的血泪实战
上周五晚上十点半,我还在工位上疯狂敲键盘。隔壁组的测试小哥已经回家了,运维大哥在群里@我说“别搞太晚,明天还要上线”,而我的 VSCode 里还飘着三个未提交的 commit——其中两个是修复自己写错的配置。
这不是第一次了。自从半年前从产品经理转岗做前端开发,我就过上了“白天开会改需求、晚上刷题看源码”的斜杠生活。最近跳槽压力山大,手头这个新项目成了我证明自己的机会。领导说:“你不是懂产品吗?那这次就从零搭个高性能、可维护、能扛住流量的新项目,顺便给团队立个标杆。”
行吧,谁让我当年提需求时总说“这个很简单啊,前端改一下就行”呢?现在报应来了。
起因:别再用三年前的脚手架了!
事情起源于一次线上事故。去年双11期间,我们老项目因为打包体积太大(首屏 JS 超过 3MB),加上没做代码分割,导致大量用户白屏超时。运维查日志的时候发现,光是 vendor.js 就占了 2.8MB,里面甚至塞进了整个 Lodash 和 Moment.js —— 而我们只用了 _.debounce 和 moment().format()。
当时我真的想砸电脑。更惨的是,这项目还是我当 PM 时主导的,现在作为开发者接手,简直是在给自己挖的坑里填土。
所以这次,我下定决心:从零开始,不依赖任何老旧脚手架,用最新工具链,打造一个真正现代化的前端项目。
目标很明确:
- 首屏加载 < 1s(Lighthouse ≥ 90)
- 支持 TypeScript + React 18 + Vite
- 自动化 CI/CD + GitHub Actions
- 开箱即用的性能监控和错误上报
- 代码规范 + 提交校验,杜绝“我本地能跑”
第一步:选型不是炫技,而是权衡
很多人一上来就堆技术栈:Next.js?Remix?Qwik?SvelteKit?但现实是,我们团队只有 3 个前端,两个还在学 React,另一个主力刚休产假。所以,稳定、文档全、社区活跃比“酷”更重要。
最终我选了 Vite + React 18 + TypeScript。理由?
- Vite 的 HMR 快到飞起,再也不用等 Webpack 编译 30 秒
- React 18 的并发特性对后续优化留了空间
- TS 能减少低级 Bug(尤其像我这种半路出家的)
至于状态管理?先不用 Redux,用 React Context + useReducer 够了。等业务复杂了再上 Zustand 或 Jotai,别一上来就过度设计。
🗣️ 吐槽一句:有些同学觉得不用 Redux 就不够“专业”,但你见过几个 MVP 项目真需要全局状态树的?别被 KPI 绑架了技术选型。
初始化项目:告别 create-react-app
npm create vite@latest my-modern-app -- --template react-ts
cd my-modern-app
npm install
搞定。不到 10 秒,一个干净的项目骨架就有了。对比以前用 CRA 等半天,Vite 真香。
但别急着写业务代码!先装一堆插件(毕竟我是 VSCode 插件狂魔):
eslint+prettier+husky+lint-staged:代码风格统一@vitejs/plugin-react-swc:用 SWC 替代 Babel,快 20 倍vite-plugin-compression:自动 Gzip/Brotlirollup-plugin-visualizer:分析打包体积@sentry/vite-plugin:错误监控集成
对了,别忘了 .vscode/settings.json 里配上自动格式化:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
这样每次保存,代码自动变漂亮,PR 里再也不会被同事吐槽“缩进不对”。
性能优化:不是加个 loading 就叫优化
很多团队把“性能优化”理解成加个骨架屏,但真正的优化是从构建到部署的全链路。
1. 代码分割(Code Splitting)
React.lazy + Suspense 是基础操作:
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
);
}
但注意:不要懒加载所有页面!首页、登录页这些关键路径必须同步加载,否则 SEO 和首屏体验会崩。
2. 资源预加载
用 <link rel="preload"> 提前加载关键资源:
<!-- index.html -->
<link rel="preload" as="style" href="/src/assets/main.css">
<link rel="prefetch" as="script" href="/assets/dashboard.abc123.js">
Vite 支持通过 ?inline 或 ?url 控制资源处理方式,比如:
import logoUrl from './logo.png?url'; // 返回 public URL
import Worker from './worker?worker'; // 创建 Web Worker
3. 图片优化
别再直接扔 <img src="huge.jpg" /> 了!用 loading="lazy" + decoding="async":
<img
src={optimizedImage}
alt="产品图"
loading="lazy"
decoding="async"
width="300"
height="200"
/>
更狠一点?上 next/image 那套?但我们没用 Next.js。所以我写了个简单的 Image 组件,配合 Cloudinary 或 imgix 做动态裁剪。
4. 构建产物压缩
在 vite.config.ts 里加:
import viteCompression from 'vite-plugin-compression';
export default defineConfig({
plugins: [
reactSwc(),
viteCompression({ algorithm: 'brotliCompress' }),
viteCompression({ algorithm: 'gzip' })
]
});
上线后,JS 文件从 500KB → 120KB(Gzip 后),Brotli 更狠,只要 90KB。
自动化:让机器干活,人类摸鱼
作为前 PM,我深知“人工流程=事故温床”。所以这次,我把所有流程自动化。
Git 提交校验
用 Husky + lint-staged,确保每次 commit 都通过 lint:
// package.json
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"]
}
}
执行 npx husky-init 自动生成 .husky/pre-commit。
效果?再也不会出现“我本地 Prettier 没开,提交了乱码”的社死场面。
GitHub Actions CI
.github/workflows/ci.yml:
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci
- run: npm run build
- run: npm run test
- name: Upload bundle stats
uses: changesets/bundle-size-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
每次 PR 都会自动跑测试、构建,并报告打包体积变化。要是体积涨了 10%,直接 block 合并——产品经理想加个“小功能”?先问问 CI 同不同意!
💡 实战经验:有一次,实习生 PR 里不小心引入了整个 Ant Design,CI 直接报警,打包体积暴增 800KB。要不是自动化,这 bug 得上线才被发现。
错误监控:别等用户投诉才修 Bug
前端错误不像后端有日志系统,用户遇到白屏可能直接关掉页面。所以,主动上报是底线。
我集成了 Sentry:
// main.tsx
import * as Sentry from '@sentry/react';
if (import.meta.env.PROD) {
Sentry.init({
dsn: 'YOUR_DSN',
integrations: [new Sentry.BrowserTracing()],
tracesSampleRate: 1.0,
});
}
// 包裹根组件
const AppWithSentry = Sentry.withProfiler(App);
同时,在 Vite 构建时上传 source map:
// vite.config.ts
import { sentryVitePlugin } from '@sentry/vite-plugin';
export default defineConfig({
plugins: [
// ...其他插件
sentryVitePlugin({
org: 'your-org',
project: 'your-project',
authToken: process.env.SENTRY_AUTH_TOKEN,
})
],
build: {
sourcemap: true // 必须开启
}
});
现在,任何未捕获的异常都会带上调用栈、用户行为路径、设备信息。上周有个 Safari 兼容性问题,Sentry 直接告诉我“发生在 iOS 14.5,使用了 Array.at()”,5 分钟定位,10 分钟修复。
性能数据说话:别自嗨
项目上线后,我跑了 Lighthouse 对比:
| 指标 | 老项目 | 新项目 |
|---|---|---|
| Performance | 42 | 93 |
| First Contentful Paint | 3.2s | 0.8s |
| Largest Contentful Paint | 5.1s | 1.1s |
| Bundle Size (JS) | 2.8MB | 320KB |
最关键的是,用户跳出率下降了 37%。老板在周会上夸我:“看来转岗是对的。” 我心里想:那当然,我可是既懂用户痛点,又会写代码的人。
血泪踩坑记录
当然,过程没那么顺利。分享几个让我深夜抓狂的坑:
坑 1:Vite 的环境变量不是 process.env
Vite 用 import.meta.env,而且只有以 VITE_ 开头的变量才会暴露给客户端。
// .env
VITE_API_URL=https://api.example.com
// 代码中
const apiUrl = import.meta.env.VITE_API_URL;
我一开始写成 process.env.API_URL,本地能跑(因为 Webpack 兼容),但 build 后 undefined。上线后 API 全挂,差点背锅。
坑 2:SWC 不支持某些 Babel 插件
我们有个老库用了装饰器(@observable),SWC 默认不支持。折腾半天,最后加了个 .swcrc:
{
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true
},
"transform": {
"legacyDecorator": true
}
}
}
建议:迁移前先检查依赖是否兼容 SWC,别盲目追求速度。
坑 3:GitHub Pages 不支持 Brotli
我们最初部署到 GitHub Pages,结果发现它只服务 Gzip,Brotli 文件被当普通文件下载。解决方案:要么换 CDN(如 Netlify/Vercel),要么只生成 Gzip。
最后我们切到了 Vercel,免费 + 自动 HTTPS + Edge Network,真香。
最后:现代化 ≠ 复杂化
写这篇文章时,我已经把这个项目模板开源到 GitHub:github.com/yourname/modern-frontend-starter(名字虚构,但你可以真去搜类似项目)。
它没有花里胡哨的动画,没有 20 个状态管理库,但它快、稳、可维护。这才是现代化前端的核心。
作为从产品转技术的人,我越来越觉得:最好的技术方案,是那个能准时交付、不出事故、让团队睡得着觉的方案。
跳槽面试时,我会带着这个项目去讲:“这是我从零搭建的,性能 93 分,错误率低于 0.1%,而且——我一个人维护了三个月,没加过班。”
哦对了,上周五的加班,其实是因为我把 git push 写成了 git push --force,覆盖了同事的代码……
还好有 GitHub 的 reflog 救我狗命。
(完)
📌 友情提示:本文所有配置已在真实项目验证,但技术迭代快,建议结合当前版本调整。别 copy-paste 完事,理解原理才是王道。
如果你觉得有用,欢迎 star 我的 GitHub 项目,或者请我喝杯咖啡(简历已更新,求内推 😅)。

评论 0