从零开始构建一个现代化前端项目:一个前产品经理的血泪实战

数据迁移苦工
2025-12-18 15:18
阅读 770

上周五晚上十点半,我还在工位上疯狂敲键盘。隔壁组的测试小哥已经回家了,运维大哥在群里@我说“别搞太晚,明天还要上线”,而我的 VSCode 里还飘着三个未提交的 commit——其中两个是修复自己写错的配置。

这不是第一次了。自从半年前从产品经理转岗做前端开发,我就过上了“白天开会改需求、晚上刷题看源码”的斜杠生活。最近跳槽压力山大,手头这个新项目成了我证明自己的机会。领导说:“你不是懂产品吗?那这次就从零搭个高性能、可维护、能扛住流量的新项目,顺便给团队立个标杆。”

行吧,谁让我当年提需求时总说“这个很简单啊,前端改一下就行”呢?现在报应来了。


起因:别再用三年前的脚手架了!

事情起源于一次线上事故。去年双11期间,我们老项目因为打包体积太大(首屏 JS 超过 3MB),加上没做代码分割,导致大量用户白屏超时。运维查日志的时候发现,光是 vendor.js 就占了 2.8MB,里面甚至塞进了整个 Lodash 和 Moment.js —— 而我们只用了 _.debouncemoment().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/Brotli
  • rollup-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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝