从简历项目到线上系统:我的现代化 React 前端搭建全记录
去年底接了个外包活儿——给一个做招聘爬虫的初创团队搭前端。对方老板拿着一份写满“精通React、熟悉Node、会写爬虫”的简历找上门,说后端 API 已经跑起来了,就差一个“能上线、能维护、别崩”的前端界面。我瞅了眼他们的需求文档(其实就三页 PPT),心里咯噔一下:这不就是典型的“简历项目”吗?但转念一想,反正我在家写代码、用 Mac 开发、Windows 只用来测兼容性,副业嘛,接了!
干了快两年外包,最怕那种“先做个 MVP,后面再重构”的甲方。结果这次还真让我碰上了。不过也好,正好借这个机会,把这两年摸索出的一套现代化前端项目搭建流程完整走一遍。今天就来复盘下:从零开始,怎么搞出一个既能让产品经理点头、又能让后端兄弟不骂娘、还能在简历上写“主导架构设计”的 React 项目。
别一上来就 npx create-react-app
很多新手(包括一年前的我)拿到需求第一反应是:
npx create-react-app my-awesome-resume-project
然后疯狂装包、写组件、调接口……结果两周后发现:路由没规范、状态管理乱成一锅粥、打包体积 5MB 起步,测试覆盖率 0%,CI/CD 全靠手动 scp。这种项目,别说上线,连自己都不想维护。
这次我学乖了。第一步不是写代码,而是对齐边界。
明确分工:前端只管“呈现”,别越俎代庖
他们的后端是个 Python 爬虫服务,每天定时抓取各大招聘网站数据,存进 MongoDB,然后通过 FastAPI 暴露几个 REST 接口。我的任务很明确:展示职位列表、支持搜索筛选、查看详情。
重点来了:前端绝不碰数据采集逻辑!之前有甲方让我“顺手加个自动刷新爬虫按钮”,我直接回绝:“那是后端的事,我这前端按钮点一下,你们服务器被反爬封 IP 了算谁的?”——程序员的边界感,得有。
脚手架选型:Vite + TypeScript 是底线
我放弃了 CRA,改用 Vite。原因很简单:快。本地开发启动从 15 秒降到 800ms,HMR 几乎无感。而且原生支持 TS、JSX、CSS Modules,不用折腾 webpack 配置(虽然我自认 webpack 配得还行,但谁不想少掉点头发呢?)。
初始化命令:
npm create vite@latest job-portal -- --template react-ts
然后立刻做几件事:
- 启用 ESLint + Prettier:统一代码风格,避免团队里有人用 2 空格有人用 tab(血泪教训)
- 配置路径别名:告别
../../../components// vite.config.ts resolve: { alias: { '@': path.resolve(__dirname, './src') } } - 集成 Husky + lint-staged:提交前自动格式化,保证主分支干净
有个细节:我把
eslint-config-react-app换成了@typescript-eslint+eslint-plugin-react-hooks,因为 CRA 的规则太宽松,连any都不报错——这在注重可维护性的我看来,简直是犯罪。
目录结构:别让 src 变成垃圾场
很多人把所有文件堆在 src 下,结果一个月后连自己都找不到 UserProfileModal.jsx 在哪。我的结构长这样:
src/
├── assets/ # 静态资源
├── components/ # 通用组件(Button, Card...)
├── features/ # 业务功能模块(按领域划分)
│ ├── job-list/
│ └── job-detail/
├── hooks/ # 自定义 Hooks
├── lib/ # 工具函数、第三方封装
├── services/ # API 调用层(Axios 封装)
├── store/ # 状态管理(Zustand)
├── types/ # TS 类型定义
└── App.tsx
关键点:按功能(feature)而非类型(component/page)组织代码。这样未来要删掉“收藏职位”功能?直接删 features/favorites 文件夹,干净利落。
数据流:别让 useEffect 成为万能胶水
早期我总爱在组件里直接写:
useEffect(() => {
fetch('/api/jobs').then(setJobs)
}, [])
结果导致:
- 无法复用数据逻辑
- loading/error 状态散落在各处
- 测试困难
这次我用了 React Query(现在叫 TanStack Query)。它简直是为“消费后端 API”而生的。
// services/jobService.ts
export const fetchJobs = (params: JobParams) =>
axios.get<Job[]>('/api/jobs', { params }).then(res => res.data)
// features/job-list/useJobs.ts
import { useQuery } from '@tanstack/react-query'
export const useJobs = (filters: JobFilters) => {
return useQuery({
queryKey: ['jobs', filters],
queryFn: () => fetchJobs(filters),
staleTime: 5 * 60 * 1000 // 5分钟内不重复请求
})
}
好处太多了:
- 自动缓存、去重请求
- 内置 loading/error 状态
- 支持后台刷新(refetch on window focus)
- 测试时 mock 一个 query client 就行
产品经理上周五晚上突然说“要加个实时更新”,我差点以为要上 WebSocket。结果发现 React Query 的 refetchInterval 就够了——有时候不是技术不行,是你不知道有轮子。
样式方案:CSS-in-JS?No,Tailwind + CSS Modules 更香
我知道很多大厂在推 CSS-in-JS(比如 styled-components),但外包项目讲究快、稳、小。我选了 Tailwind 作为基础样式库,配合 CSS Modules 写局部样式。
为什么?
- Tailwind 提供原子类,快速搭建 UI
- CSS Modules 避免样式冲突,且支持变量、嵌套(配合 PostCSS)
- 打包后 CSS 体积小(PurgeCSS 自动移除未用类)
举个栗子:
// JobCard.module.css
.card {
@apply rounded-lg shadow-md p-4;
transition: transform 0.2s;
}
.card:hover {
@apply transform scale-[1.02];
}
// JobCard.tsx
import styles from './JobCard.module.css'
export const JobCard = ({ job }) => (
<div className={styles.card}>
<h3>{job.title}</h3>
{/* ... */}
</div>
)
既享受了 Tailwind 的开发速度,又保留了 CSS 的可维护性。而且设计师给的 Figma 稿,基本能 1:1 还原。
构建与部署:让 CI/CD 成为你的加班终结者
外包最怕啥?甲方临时改需求,你刚打包完他又说“字体颜色再深一点”。所以自动化部署必须安排上。
我在 GitHub Actions 配了这么个流程:
- push 到 main 分支
- 自动运行 lint + test
- 构建生产包
- 上传到 AWS S3 + CloudFront
关键配置:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci
- run: npm run build
- name: Deploy to S3
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- run: aws s3 sync ./dist s3://my-job-portal-bucket --delete
现在每次合并 PR,5 分钟后线上就更新了。再也不用半夜爬起来 scp 文件——自动化是程序员对抗熵增的终极武器。
性能优化:别让用户等得想关页面
上线前我用 Lighthouse 跑了分,初始只有 60 多。主要问题:
- 首屏加载慢(主包太大)
- 图片未优化
- 无代码分割
解决方案:
- 动态导入路由组件
const JobList = lazy(() => import('@/features/job-list/JobList')) - 图片用
next/image?不,用原生<img loading="lazy">+ WebP - API 响应加缓存头(和后端兄弟沟通,他们加了
Cache-Control: max-age=300)
优化后 Lighthouse 分飙到 92,首屏从 3.2s 降到 1.1s。用户停留时间涨了 40%——性能就是用户体验,用户体验就是钱。
最后:这项目真能写进简历吗?
当然能!但别写“使用 React 开发招聘网站”,要写:
主导从 0 到 1 搭建现代化前端架构,采用 Vite + TS + React Query 技术栈,实现 90+ Lighthouse 性能分,支撑日均 10k+ 爬虫数据展示,交付后 3 个月零严重 Bug。
你看,关键词全塞进去了:简历、后端、爬虫、React,还显得很专业。
其实做外包最大的收获不是钱,而是在有限时间内逼自己用最佳实践解决问题。毕竟,没人替你擦屁股,代码烂了只能自己重构。
哦对了,上周那个甲方又来找我了,说想加个“AI 自动匹配简历”功能……我看了看日历,离跳槽面试还有两周,先接了再说 😅

评论 0