从零开始构建一个现代化前端项目的实战经验分享
开篇:为什么这次重构让我重新思考了前端开发的起点

今年年初,我参与了一个全新的企业级 SaaS 应用项目,目标是为一家中型制造企业提供生产流程数字化解决方案。虽然公司内部已经有几个老系统在运行,但性能瓶颈和维护成本逐渐显现出来,管理层决定推倒重来——从头构建一个全新的前端架构。
说白了,这个任务听起来很爽:“从零开始”意味着可以摆脱历史包袱、使用新技术、按照理想方案来设计。但在实际执行过程中,我发现事情远没有那么简单。作为一个全栈开发者,不仅要兼顾前后端的协同,更要站在团队的角度考虑技术选型、开发效率和长期可维护性。
这篇文章我想通过这次真实的项目经历,聊聊如何真正地“从零开始”构建一个现代化前端项目,并不是简单地敲几条命令生成脚手架工程,而是从底层架构到上层业务的一整套设计与实现思路。
背景:项目背景与挑战初现

新项目的目标用户群体明确:一线工厂操作人员、班组长和工厂管理者。界面需要支持数据展示、表单提交、权限控制、图表分析等一系列功能。同时产品侧要求:
- 主要浏览器兼容(Chrome 60+,Edge 最新版,Safari)
- 支持低网速下的基础可用
- 移动端优先,部分功能需适配平板操作
- 需要集成微前端架构以对接未来可能接入的其他系统模块
最初,我们只有一张画在 Figma 上的 UI mockup 和一版不太详细的产品需求文档。这意味着我必须从头规划整个前端结构,同时还要确保后续开发人员能够快速上手并扩展系统功能。
挑战1:如何选择现代框架 + 微前端架构?

第一个难题就是技术选型。
团队成员普遍有 React 的使用经验,但也有对 Vue 的兴趣。最终我们选择 React,原因是:
- 大厂生态支持(如 Next.js、React Query、TanStack Router 等工具链完善)
- 社区活跃度高,遇到问题解决路径清晰
- 适合做组件化开发,便于未来拆成微前端模块
但我们还需要考虑未来的微前端架构集成能力。因此,在项目初期就引入了 Module Federation(Webpack 5 原生支持) 技术作为微前端方案的基础。
说实话,在项目刚开始时很多人不理解为何需要这么早就考虑微前端的问题。我的逻辑很简单:
“一个现代化前端应用不应该一开始就设计成孤岛,而应具备灵活组合的能力。”
于是我们在项目骨架搭建时,不仅选用了 Vite + React 的组合,还提前配置了 Module Federation 插件,让每一个 feature module 尽早拥有被独立部署的能力。
不过这也带来了新的复杂度,比如跨模块通信、样式隔离、路由管理等问题,这后面再讲。
挑战2:从零开始,到底需要哪些基础设施?
接下来就是搭建项目的初始结构。我习惯的做法是:
npm create vite@latest my-saas --template react-ts
然后手动添加 ESLint、Prettier、Jest、React Testing Library、TypeScript 支持等。这些看似繁琐,却是保障代码质量的关键。
文件结构建议(参考 Airbnb 和一些开源项目)
我们的结构如下:
src/
├── assets/ # 图片、字体、SVG图标资源
├── components/ # 公共组件
│ ├── atoms/ # 原子组件,按钮、输入框等
│ ├── molecules/ # 分子组件,由原子组装而成的功能单元
│ └── organisms/ # 组织组件,页面级别的组合
├── hooks/ # 自定义 Hook
├── layouts/ # 页面布局组件
├── pages/ # 各个页面文件
├── services/ # 接口封装(Axios)
├── stores/ # Zustand / Redux Toolkit 状态管理
├── routers/ # 路由定义(TanStack Router)
├── utils/ # 工具函数,如 dateFormat、formatCurrency 等
└── App.tsx
这种结构在团队协作中非常实用,新人可以快速找到自己负责的部分。
但当时我们也走过弯路。一开始我们尝试用 pages/* 直接作为路由自动加载模块,后来发现随着业务增长,这种方式难以管理权限、懒加载和动态导入。
最终我们改用 TanStack Router + 动态导入的方式实现按需加载,代码简洁且可拓展性强。
挑战3:状态管理和接口分层怎么做最合理?
状态管理方面,我们选择了 Zustand。相较于 Redux Toolkit,它更轻量,写法也更接近于 React 的心智模型。而且配合 TypeScript 使用起来非常舒服。
示例代码:
import { create } from 'zustand'
type UserState = {
user: User | null
fetchUser: (id: string) => void
}
const useUserStore = create<UserState>((set) => ({
user: null,
fetchUser: async (id) => {
const res = await api.get(`/users/${id}`)
set({ user: res.data })
},
}))
export default useUserStore
接口分层这块,采用的是经典的 Repository Pattern + Axios Interceptor。
// src/services/api.ts
import axios from 'axios'
const apiClient = axios.create({
baseURL: '/api',
timeout: 5000,
})
// 请求拦截器,添加 token
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem('auth_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器处理错误码
apiClient.interceptors.response.use(
(res) => res,
(error) => {
if (error.response?.status === 401) {
// 执行跳转登录页等操作
}
return Promise.reject(error)
}
)
export default apiClient
然后每个业务模块都有自己的 service 文件,例如:
// src/services/user.service.ts
import apiClient from './api'
export const getUser = async (userId: string) => {
const res = await apiClient.get(`/users/${userId}`)
return res.data
}
这种方式让接口请求统一归口,便于调试和维护。
挑战4:用户体验细节不容忽视
作为一名经历过多个交付项目的工程师,我深知有时候一个小小的体验优化就能大幅提升用户满意度。特别是在工业场景中,很多工人会反复进行某些固定操作,哪怕是多一次点击或等待时间长了一点,都会影响他们的效率。
我们做了以下几点体验提升:
1. 表单校验增强 + 实时反馈
使用了 React Hook Form,配合 Yup 做 schema 校验。
优势在于无需频繁触发渲染,性能好;并且可以在后端返回格式错误时精准定位提示。
import { useForm } from 'react-hook-form'
import * as yup from 'yup'
import { yupResolver } from '@hookform/resolvers/yup'
const schema = yup.object().shape({
name: yup.string().required('必填'),
email: yup.string().email('请输入正确的邮箱').required(),
})
function MyForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({ resolver: yupResolver(schema) })
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
{errors.name && <span>{errors.name.message}</span>}
</form>
)
}
2. Loading 状态精细化处理
对于关键操作(如保存数据),我们采用了全局 loading + 局部 loading 区分,防止用户误操作。
全局 loading 我们用的是 Zustand + Context Provider 构建的状态共享机制。
局部 loading 则是在按钮级别控制,比如提交之后禁用按钮防止重复提交。
3. 防止页面无响应(FOUC)
这个问题在使用动态导入路由时偶尔出现,尤其是 Safari 浏览器上更为明显。我们通过预加载策略 + 骨架屏组件缓解了这个问题。
挑战5:性能优化与兼容性考量
前端项目的终极考验之一永远是性能表现,尤其是在国内复杂的网络环境和老旧设备上。
我们主要做了以下几件事:
1. 使用 Web Vitals 进行监控
通过引入 Google 的 Web Vitals 检测库,实时收集用户的 LCP、CLS、FID 等指标。
import { onCLS, onFID, onLCP } from 'web-vitals'
onCLS(console.log)
onFID(console.log)
onLCP(console.log)
2. 代码分割 + 图片懒加载
所有非首屏内容都进行了懒加载处理,图片资源则使用 <img loading="lazy" /> 或者 <LazyLoad> 组件包裹。
3. Tree Shaking & Polyfill 降级处理
Vite 默认已经做了 Tree Shaking,但对于老版本浏览器,我们还是引入了 Babel 和 core-js 来垫一部分 ES 特性。
// babel.config.json
{
"presets": [
["@babel/preset-env", { "useBuiltIns": "usage", "corejs": 3 }],
"@babel/preset-react"
]
}
这样能保证 Chrome 60 这种旧浏览器也能顺利运行。
挑战6:团队协作与开发效率提升
项目做到中期时,随着团队扩大,沟通和代码风格冲突成为大问题。于是我推动了几项制度化的实践:
1. Git Commit 规范 + 提交前检查
我们制定了基于 Angular 的 commit message 规范(feat:xxx、fix:xxx 等),并通过 husky + lint-staged 在每次提交前进行代码格式检查。
npx mrm@2 lint-staged
这样即使多人协作也不会出现严重的风格混乱。
2. Storybook 可视化组件文档
为了让设计师和开发都能看到最新组件,我们接入了 Storybook 并逐步将通用组件纳入其中。
这对于 UI Review 十分有帮助,特别是当设计师想要快速查看组件 API 是否符合预期时。
3. E2E 测试自动化探索
虽然项目初期没有完全覆盖 E2E 测试,但我们使用了 Cypress 搭建了核心流程的测试用例,例如登录 + 查看列表 + 编辑信息等闭环操作。
效果总结:稳定、高效、可扩展的现代化前端架构
项目上线半年以来,整体反馈良好:
- 首次加载平均耗时减少 38%
- 用户操作卡顿投诉下降 85%
- 新人学习成本降低约 40%,因为结构清晰、文档齐全
- 支持多入口部署,已有两个模块作为微前端独立上线
最关键的是,我们避免了早期因架构设计不合理导致的大规模返工,也为后期的持续迭代打下了坚实基础。
经验分享:给正在从零搭建项目的你的建议
如果你也在准备启动一个新项目,不妨参考以下几个建议:
✅ 不怕慢,先定好结构和规范
刚开项目别急着写功能,先把目录结构、命名规则、代码规范、分支策略定下来。越早建立标准,越能避免后期大规模重构。
✅ 技术选型要适度前瞻,但不可盲目追求时髦
比如我们可以选择 React,因为它生态成熟,而不是为了尝鲜去尝试一个尚未稳定的框架。如果只是 CRUD 页面,甚至可以考虑 Vue 或者原生 JavaScript。
✅ 性能优化要前置,不是上线后再考虑
Lighthouse 是个好帮手,从开发第一天起就开始关注 performance score,不要等到用户吐槽才补救。
✅ 前端不止是写代码,更是用户体验的守护者
有时候比实现功能更重要的,是思考用户真实的操作场景。例如移动端键盘弹出遮挡输入框、网络延迟下提示语的设计、防抖节流策略的选择等,都是体现专业度的地方。
结语:从零开始不是终点,而是起点
回过头来看,这一次项目从零开始的过程,其实并不是简单的技术堆叠,而是对现代前端工程的一次全面理解和实践。在这个过程中,我经历了从最初的迷茫、纠结,到后来的坚定和技术自信。
或许下次你们接手类似任务的时候,也可以少走些弯路。愿你我都能写出既优雅又实用的代码,不只是完成工作,而是把前端这件事,做得更有价值。
最后送大家一句话:
“真正的现代化前端项目,不是技术堆砌出来的,而是在一次次权衡、妥协和坚持中打磨出来的。”
如果你喜欢这类实战向的文章,欢迎留言交流或者分享你的经验!

评论 0