从零开始构建一个现代化前端项目:我的踩坑实录
上周五晚上九点半,我刚把最后一行 Rust 代码跑通,正准备关机回家,钉钉突然弹出一条消息:“老张,新项目下周启动,前端你来搭架子。” 我心里一咯噔——又来了。这已经是今年第三次让我从零搭项目了。在这家公司待了三年多,眼看35岁生日快到了,每天早上八点雷打不动坐在工位上敲代码的日子,也不知道还能持续多久。最近其实已经在偷偷更新简历,打算换个环境,但手头的活儿总得干完不是?
说起来,这次的需求还真不简单:要搞一个支持实时协作的文档编辑平台,UI 要现代、响应快、兼容性好,还得能快速迭代。产品经理画了一堆高保真原型,运维说“最好能一键部署”,测试同事则幽幽地补了一句:“上次那个内存泄漏,线上跑了三天才被发现……”
行吧,既然躲不过,那就干。今天这篇就记录一下我从零搭建这个项目的全过程——不是教科书式的完美流程,而是真实世界里一个老程序员边踩坑边摸索出来的路子。
别再用 create-react-app 闭着眼睛开工了
很多人一听说“新建前端项目”,第一反应就是:
npx create-react-app my-awesome-app
说实话,三年前我也这么干。但现在?除非是临时 demo,否则真不敢这么糙。为啥?因为 CRA 虽然开箱即用,但它把配置全藏起来了——你不知道 Webpack 干了啥,Babel 怎么转的,连个自定义 ESLint 规则都得 eject 出来改,一旦 eject 基本就回不去了。
这次我决定自己搭脚手架。目标很明确:
- 支持 TypeScript(类型安全太重要了)
- 模块化 CSS(用 CSS Modules 或 Tailwind)
- 自动代码格式化 + Lint
- 快速本地开发(HMR 必须丝滑)
- 生产构建优化(Tree-shaking、Code Splitting)
我选了 Vite。不是因为它新,而是因为它真的快。冷启动 200ms,热更新基本无感。而且配置文件清晰,不像 Webpack 那种层层嵌套的噩梦。
初始化项目:
npm create vite@latest doc-collab -- --template react-ts
cd doc-collab
npm install
然后立刻装上我常用的工具链:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
配置 tailwind.config.js:
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
再在 src/index.css 里加三行:
@tailwind base;
@tailwind components;
@tailwind utilities;
搞定。现在我可以写 <div className="p-4 bg-blue-100 rounded-lg">Hello</div> 这种清爽的类名了,不用再和 CSS 文件里的命名空间打架。
Git 和 GitHub:别等代码写完了才想起来初始化
很多新人(包括当年的我)有个坏习惯:先疯狂写代码,等项目差不多了再 git init。结果就是第一次 commit 就几千行,后续根本没法追溯。
这次我从第一天就建好了 GitHub 仓库。名字叫 doc-collab-frontend,描述写清楚用途,加了 .gitignore(用 gitignore.io 生成的 React + Node 模板),还写了初步的 README.md。
最关键的是,我立刻配置了 分支策略:
main分支保护,禁止直接 push- 所有功能走
feature/xxx分支 - PR 合并需至少一人 review
别小看这点,上周另一个组就因为有人直接推到 main,把测试环境搞崩了,运维在群里咆哮了半小时。
我还顺手加了两个 GitHub Actions workflow:
- CI 流水线:每次 push 自动跑 lint、test、build
- 预发布部署:PR 合并后自动部署到 preview 环境(用 Vercel)
.github/workflows/ci.yml 片段:
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm ci
- run: npm run lint
- run: npm run build
这样,只要有人提 PR,我就知道代码能不能跑起来。省得等到上线前一天才发现“哦,原来你的代码在 Windows 上跑不了”。
组件设计:别让 UI 变成意大利面条
这次项目要求高复用性。比如“用户头像”组件,要在文档列表、评论区、协作面板里反复出现。如果每个地方都 copy-paste 一段 <img src={...} className="w-8 h-8 rounded-full" />,那以后产品经理说“头像要加 hover 效果”,你就得改十处。
我的做法是:原子化设计 + Storybook 预览。
先装 Storybook:
npx storybook init
然后建一个 src/components/ui/Avatar.tsx:
// Avatar.tsx
import { FC } from 'react';
interface AvatarProps {
src?: string;
alt?: string;
size?: 'sm' | 'md' | 'lg';
className?: string;
}
const SIZE_MAP = {
sm: 'w-6 h-6',
md: 'w-8 h-8',
lg: 'w-12 h-12',
};
export const Avatar: FC<AvatarProps> = ({
src,
alt = 'User',
size = 'md',
className = '',
}) => {
return (
<img
src={src || '/default-avatar.png'}
alt={alt}
className={`${SIZE_MAP[size]} rounded-full object-cover ${className}`}
/>
);
};
再写对应的 Avatar.stories.tsx:
import { Avatar } from './Avatar';
export default {
title: 'UI/Avatar',
component: Avatar,
};
export const Small = () => <Avatar size="sm" />;
export const Medium = () => <Avatar size="md" />;
export const Large = () => <Avatar size="lg" />;
跑 npm run storybook,就能在浏览器里看到所有状态。测试同学甚至可以直接拿这个链接确认 UI 是否符合设计稿——再也不用等我部署到测试环境。
性能不是玄学:监控和优化要趁早
去年双11期间,我们一个页面首屏加载花了 4.8 秒,用户跳出率飙升。事后复盘发现,光一个图表库就占了 1.2MB,而且还是同步加载的。
这次我从第一天就加了性能兜底措施:
路由级代码分割
用 React.lazy + Suspense 拆分路由:const EditorPage = lazy(() => import('./pages/EditorPage')); const Dashboard = lazy(() => import('./pages/Dashboard')); // 在 Router 里 <Routes> <Route path="/" element={ <Suspense fallback={<div>Loading...</div>}> <Dashboard /> </Suspense> } /> </Routes>图片懒加载 + WebP 格式
所有静态资源走 CDN,且自动转 WebP。本地开发时用vite-plugin-imagemin压缩。Performance Budget
在vite.config.ts里加了个插件,限制 bundle 大小:import { visualizer } from 'rollup-plugin-visualizer'; export default defineConfig({ plugins: [ // ...其他插件 visualizer({ open: false }), // 生成 stats.html ], build: { rollupOptions: { // 如果 vendor chunk > 500kb 就报警 } } });
上线前,我会跑 npm run build -- --report,看看有没有哪个依赖偷偷膨胀了。
调试技巧:Console.log 是最后的选择
老程序员都知道,靠 console.log 调异步逻辑,迟早会疯。这次我全程用 React DevTools + Vite 的 HMR 状态保留。
比如处理 WebSocket 实时协作时,状态更新很频繁。我在组件里加了:
useDebugValue(collaborators.length); // 在 DevTools 里直接显示人数
配合 Redux DevTools(如果用了状态管理),可以回放每一步操作。上周五就靠这个定位了一个“光标位置错乱”的 bug——原来是某个 action 的 payload 缺了 timestamp 字段,导致合并冲突时排序错误。
另外,Vite 的 HMR 默认会保留 React 组件状态。这意味着我改个样式,输入框里的文字不会丢;改个函数逻辑,当前打开的文档也不会重置。这对开发体验提升巨大。
最终效果:从混乱到可控
项目上线两周了,目前运行平稳。关键指标如下:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 首屏加载时间 | 3.2s | 1.1s |
| Bundle 大小 (gzip) | 2.1MB | 890KB |
| CI 构建时间 | 2m 15s | 42s |
最让我欣慰的是,新来的实习生也能快速上手——因为项目结构清晰、文档齐全、自动化到位。他昨天还跟我说:“哥,你这架子搭得真舒服,比上家公司强多了。”
写在最后:35岁的程序员还在折腾,图个啥?
可能有人觉得,都这岁数了,何必折腾这些?用现成的模板不好吗?
但我觉得,保持对技术的好奇和掌控感,才是程序员不被淘汰的关键。最近研究 Rust 也是这个原因——不是为了转行,而是想理解系统底层怎么运作。前端越来越复杂,如果不深挖原理,迟早会被框架牵着鼻子走。
这次从零搭项目,虽然累,但每一步都踏实。GitHub 提交记录清清楚楚,代码结构经得起 review,性能数据拿得出手——这才是一个成熟工程师该交付的东西。
至于跳槽?简历已经投出去了。不管结果如何,至少这个项目,我能挺直腰板说:“这是我亲手搭的,没糊弄。”
(完)

评论 0