从零开始构建一个现代化前端项目:一个被简历逼疯的研二狗的自救指南
上周五晚上十一点半,实验室只剩我一个人。窗外下着小雨,键盘上泛着幽幽蓝光,耳机里放着《The Final Countdown》——不是因为多燃,纯粹是因为每次快跑路的时候都听这歌,算是某种玄学仪式了。
我盯着屏幕上刚收到的 HR 邮件:“感谢投递,但您的项目经历与岗位要求存在一定差距……”
淦!又挂了。这次是一家做低代码平台的 startup,职位描述写得天花乱坠:“熟悉现代前端工程化体系、有完整项目落地经验者优先”。我点开自己的 GitHub,除了课程作业和一个仿知乎的 React 小练习,几乎一片空白。
“简历上连个像样的项目都没有,怎么跟人竞争?”
那一刻,我突然明白了什么叫“代码人生”的残酷真相——不是你写了多少行代码,而是有没有能拿得出手的完整闭环。
于是,我决定:从零开始,亲手搭一个现代化前端项目,不靠脚手架糊弄,每一步都要搞懂原理。
为啥不用 Create React App?因为它太“黑盒”了!
说实话,我之前也用 CRA(Create React App)快速起过项目。一键生成,热更新、ESLint、Babel 全配好了,美滋滋。但问题来了——一旦遇到配置冲突、性能瓶颈,或者想加个 Web Worker、自定义 Webpack 插件,你就得 eject,然后面对上千行的配置文件瑟瑟发抖。
在公司里,这种“魔法”是不被允许的。
我入职这家做 SaaS 工具的新公司已经两个月了。团队虽然小(就 5 个前端),但对工程规范极其严格。Leader 是个 Rust 转前端的老哥,天天念叨:“不要把工具当黑箱,否则线上炸了你只能祈祷。”
果然,上个月双11大促前,我们一个页面加载慢到用户投诉。查了半天,发现是 CRA 默认没开代码分割(code splitting),首页 bundle 快 3MB!临时 hack 了一堆 dynamic import 才救回来。那天凌晨三点,我在 Slack 里发了个“我恨黑盒”,被运维老哥回了个“+1,我也恨”。
所以这次,我铁了心要手搓一套现代化前端架构,目标明确:
- ✅ 使用 React 18 + TypeScript
- ✅ 支持 Vite(快!真的快!)
- ✅ 完整的 Linting & Formatting(告别团队 PR 里互相改格式)
- ✅ 自动化测试(至少单元测试不能少)
- ️ 部署 CI/CD(虽然只是 GitHub Pages,但流程得走通)
第一步:初始化项目结构 —— 别再用 npm init 了!
以前我都是 npm init -y 走起,现在直接上 pnpm。为啥?速度快、磁盘省、依赖扁平化清晰。公司内部早统一用 pnpm 了,连测试同学都夸“装包终于不用等十分钟了”。
pnpm create vite my-resume-site -- --template react-ts
cd my-resume-site
pnpm install
📌 小贴士:Vite 的模板其实很干净,比 CRA 少了 80% 的冗余配置。而且默认支持 TS、JSX、CSS Modules,开箱即用。
但别急着跑 pnpm dev!先看看目录结构:
my-resume-site/
├── public/ # 静态资源(favicon、robots.txt)
├── src/
│ ├── assets/ # 图片、字体等
│ ├── components/ # 组件
│ ├── App.tsx
│ └── main.tsx
├── index.html # 唯一 HTML 入口
├── tsconfig.json
└── vite.config.ts # 核心配置文件
清爽!没有 node_modules 炸弹,没有隐藏的 .env.local 陷阱。
第二步:工程化基建 —— 让代码“长得好看”
在公司,PR(Pull Request)第一关就是 ESLint + Prettier。要是格式不对,CI 直接红掉,连 review 的机会都没有。所以我立马加上:
pnpm add -D eslint prettier typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin
然后一顿配置:
// .eslintrc.json
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier" // 必须放在最后,覆盖其他规则
],
"rules": {
"@typescript-eslint/no-explicit-any": "off", // 现实点,any 有时候真香
"react/react-in-jsx-scope": "off" // React 17+ 不需要显式 import
}
}
// .prettierrc
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}
再加个 Git Hook,提交前自动 fix:
pnpm add -D husky lint-staged
npx husky install
npx husky add .husky/pre-commit "npx lint-staged"
// package.json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"]
}
}
搞定!从此再也不用担心同事吐槽“你这缩进是 tab 还是空格?”
第三步:组件设计 —— 把简历变成可交互的艺术品
既然是做简历项目,那 UI 得有点意思。我参考了几个 Dribbble 上的设计,决定做成单页滚动 + 暗色主题 + 微交互动效。
关键组件拆分:
<Header />:带滚动变色的导航栏<Hero />:自我介绍 + 动态打字效果<Projects />:卡片式项目展示(带 GitHub Star 数)<Timeline />:教育/工作经历时间轴<Contact />:表单 + 邮箱验证
滚动监听?别自己造轮子!
以前我傻乎乎地用 window.addEventListener('scroll', ...),结果性能差到掉帧。后来被 Leader 骂了一顿:“Intersection Observer 都出来多少年了?”
现在直接上 react-intersection-observer:
import { useInView } from 'react-intersection-observer'
const ProjectCard = ({ project }) => {
const { ref, inView } = useInView({
triggerOnce: true,
threshold: 0.1,
})
return (
<div
ref={ref}
className={`project-card ${inView ? 'animate-fade-in' : ''}`}
>
{/* 内容 */}
</div>
)
}
配合 CSS 动画,丝滑进场,毫无压力。
第四步:性能优化 —— 别让用户等成化石
公司最近搞 Lighthouse 评分竞赛,低于 90 分的请喝奶茶。我可不想破费,所以提前优化:
1. 图片懒加载 + WebP
简历里的项目截图全转 WebP,体积减少 60%。用 loading="lazy" 原生支持:
<img
src="/projects/chat-app.webp"
alt="Chat App"
loading="lazy"
width="400"
height="240"
/>
2. 代码分割(Code Splitting)
首页只加载必要组件,其他路由动态导入:
const Projects = lazy(() => import('./components/Projects'))
const Timeline = lazy(() => import('./components/Timeline'))
function App() {
return (
<Suspense fallback={<Spinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/projects" element={<Projects />} />
</Routes>
</Suspense>
)
}
3. 预加载关键资源
在 index.html 里预加载字体和首屏 CSS:
<link rel="preload" href="/fonts/Inter.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/styles/main.css" as="style">
跑了个 Lighthouse,Performance 96,Accessibility 100 —— 可以去茶水间炫耀了。
第五步:测试 —— 别让 Bug 上线背锅
在公司,测试覆盖率低于 70% 的 PR 直接被打回。虽然我只是做个个人项目,但习惯不能丢。
用 Vitest + React Testing Library:
pnpm add -D vitest @testing-library/react @testing-library/jest-dom jsdom
写个简单的 Header 测试:
// __tests__/Header.test.tsx
import { render, screen } from '@testing-library/react'
import Header from '../src/components/Header'
test('renders navigation links', () => {
render(<Header />)
expect(screen.getByText('Projects')).toBeInTheDocument()
expect(screen.getByText('Contact')).toBeInTheDocument()
})
跑一下:
pnpm test
# PASS __tests__/Header.test.tsx
# ✓ renders navigation links (12ms)
虽然只有 30% 覆盖率(别笑,总比没有强),但至少核心逻辑有保障。上线前心里踏实多了。
第六步:部署 —— 一键发布到全世界
最后一步,部署。公司用的是 AWS Amplify,但我个人项目图省事,直接上 GitHub Pages。
Vite 官方提供了 vite-plugin-pages 和 gh-pages 集成方案:
pnpm add -D gh-pages
// package.json
{
"scripts": {
"deploy": "vite build && gh-pages -d dist"
},
"homepage": "https://yourname.github.io/my-resume-site"
}
注意:Vite 默认输出路径是 /,但 GitHub Pages 是子路径,所以得在 vite.config.ts 里加:
export default defineConfig({
base: '/my-resume-site/', // 必须和 repo 名一致
})
然后:
pnpm run deploy
5 秒后,我的简历网站 live 了!
链接发到朋友圈,收获一堆“666”和“求源码”。
成果对比:手搓 vs 脚手架
| 项目 | CRA 默认 | 手搓 Vite + TS |
|---|---|---|
| 首次启动时间 | 8s | 1.2s |
| 生产包大小 (gzip) | 1.8MB | 420KB |
| Lighthouse Performance | 72 | 96 |
| 自定义能力 | 低(需 eject) | 高 |
| 学习成本 | 低 | 中(但值得) |
吐槽与感悟:这届前端,卷不动也得卷
说实话,整个过程踩了不少坑:
- 忘记在
vite.config.ts里配base,导致 GitHub Pages 白屏,折腾半小时 - Intersection Observer 在 Safari 旧版本不兼容,得加 polyfill
- 动态导入的 Suspense fallback 忘了写,网络慢时页面空白,差点被自己吓死
但每解决一个问题,对“现代化前端”的理解就深一层。不再是“会用 React 就行”,而是知道为什么这么配、怎么调优、如何保障质量。
更重要的是——我的简历终于有了一个能点开看的项目!
上周我又投了一家公司,这次在简历里加了这个项目的链接和 GitHub 地址。HR 回复很快:“项目体验很好,技术栈也很新,约个面试吧?”
那一刻,我觉得熬夜写代码值了。
最后的小建议(给同样挣扎的你)
- 别怕从零开始:CRA/Vite CLI 虽好,但理解底层才能应对复杂场景
- 工程化不是炫技:它是为了解决协作、维护、性能这些真实痛点
- 简历项目 ≠ 完美项目:但一定要体现你的思考和解决问题的能力
- Rust 虽香,前端也得精进:毕竟我现在主业还是写 React(虽然晚上偷偷看《Rust 权威指南》)
对了,如果你也正在找工作,不妨花一周时间,亲手搭一个属于自己的现代化前端项目。不用多复杂,但一定要完整——从 init 到 deploy,每一步都留下你的思考痕迹。
毕竟,在这个“代码即简历”的时代,你的 GitHub,就是你最好的作品集。
作者碎碎念:我是某 211 软件工程研二狗,白天在实验室调模型,晚上在公司写 React,凌晨研究 Rust。
本文项目已开源:github.com/yourname/modern-resume(别点,假链接,但你可以自己建一个!)
如果觉得有用,欢迎点赞、转发、或者请我喝杯瑞幸(穷学生在线乞讨)。
下期预告:《用 Rust 写前端?WASM 实践踩坑实录》—— 敬请期待!

评论 0