从零搭个现代前端项目,我踩过的坑都给你标红了
今天早上八点刚到公司,泡了杯速溶咖啡(别笑,三线城市没那么多精品咖啡店),打开电脑发现昨晚刷 LeetCode 的时候把终端关了——又白跑了一次力扣。不过还好,今天不用赶着改产品提的“小需求”(你懂的,就是那种“就加个按钮,五分钟搞定”的需求)。趁着晨会前这段安静时间,我想写点实在的东西:怎么从零开始搭一个现代化的前端项目。
为什么突然想写这个?上周五我们团队接了个新项目,要给本地一家制造企业做内部管理系统。产品经理画完原型图甩过来一句:“这次用新技术吧,显得我们有实力。” 我心里苦笑——嘴上说着“新技术”,预算和工期却比双11大促还紧。但没办法,既然要做,那就得做得像样点。正好我也在准备跳槽,简历上总不能只写“熟练使用 jQuery 吧”。
别再从 create-react-app 无脑开始了
很多人一说新建项目,直接 npx create-react-app my-app,完事。这在 2018 年没问题,但现在?真不够看了。
现代前端项目不只是“能跑就行”,还得考虑:
- 开发体验:热更新快不快?报错提示友好吗?
- 构建性能:CI/CD 能不能跑得动?打包体积压得住吗?
- 可维护性:半年后新人接手会不会骂娘?
- 扩展性:以后要加微前端、SSR、PWA 怎么办?
所以我们这次没用 CRA,而是基于 Vite + TypeScript + PNPM + ESLint + Prettier + Husky 搭了一套脚手架。听起来花里胡哨?其实配置起来也就半小时,而且后续省下的时间远超这点投入。
初始化:用 Vite 真香
pnpm create vite@latest my-project -- --template react-ts
cd my-project
pnpm install
为啥选 Vite?两个字:快。本地开发启动从 CRA 的 15 秒降到 300 毡秒,HMR(热更新)几乎无感。对于我们这种经常被产品经理临时叫去改 UI 的苦命人来说,省下的每一秒都是生命。
而且 Vite 原生支持 TS、CSS Modules、JSON 导入,连 Webpack 配置都不用碰——终于不用再和 webpack.config.js 死磕到凌晨三点了。
代码规范:别让队友在 PR 里吵架
以前我们团队因为缩进是 2 还是 4 空格吵过三次,最后决定:工具说了算。
我们在项目根目录加了这几个文件:
.eslintrc.cjs:ESLint 规则,用了@typescript-eslint/recommended.prettierrc:统一格式化风格.editorconfig:让不同编辑器行为一致husky+lint-staged:提交前自动 lint 和 format
关键配置片段:
// .prettierrc
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}
// lint-staged.config.js
export default {
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
'*.{css,scss,json,md}': ['prettier --write']
}
现在谁要是提交一堆格式问题,Git hooks 直接拦住,根本推不上 GitHub。产品再急,也得等代码干净了再说。
Git 提交规范:别再写 “fix bug” 了
说到 GitHub,我们强制要求 Conventional Commits。不是为了装,是真的有用。
以前看 Git log 是这样的:
fix bug
update
done
ok
现在是这样的:
feat(auth): add login page
fix(api): handle 401 redirect
chore(deps): update axios to v1.6
配合 commitlint 和 @commitlint/config-conventional,提交信息不符合规范直接不让 commit。虽然一开始大家骂骂咧咧,但三个月后回溯问题时,所有人都闭嘴了——因为能精准定位到哪个 commit 引入了问题。
而且,自动生成 CHANGELOG 和语义化版本号也成了可能。虽然我们是内部系统用不上,但跳槽面试的时候,这套流程写进简历绝对加分。
组件设计:别重复造轮子,但也别乱引依赖
我们用了 shadcn/ui 而不是 Ant Design 或 Element Plus。原因很简单:按需引入 + 完全可控。
shadcn/ui 不是 npm 包,而是把组件代码直接 copy 到你项目里。听起来反直觉?但好处巨大:
- 样式可以随意改,不用覆盖一堆 CSS
- 不会因为某个 UI 库升级导致整个页面崩掉
- 打包体积只包含你用到的部分
比如一个按钮:
// components/ui/button.tsx
import { cn } from '@/lib/utils'
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'outline'
}
export function Button({ className, variant = 'default', ...props }: ButtonProps) {
return (
<button
className={cn(
'px-4 py-2 rounded-md',
variant === 'default' && 'bg-blue-500 text-white',
variant === 'outline' && 'border border-blue-500 text-blue-500',
className
)}
{...props}
/>
)
}
完全透明,想加 loading 状态?直接改就行。再也不用去看 UI 库源码找哪个 prop 叫 loadingIndicator 了。
测试:不是可有可无的摆设
我知道,很多中小公司根本不管测试。但我们这次咬牙上了 Vitest + Testing Library。
为什么?因为上个月上线一个报表功能,因为没处理空数据,导致整个页面白屏。运维半夜打电话,我当时真的想砸电脑。
现在核心组件都有快照测试和交互测试:
// __tests__/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from '@/components/ui/button'
test('calls onClick when clicked', () => {
const handleClick = vi.fn()
render(<Button onClick={handleClick}>Click me</Button>)
fireEvent.click(screen.getByText('Click me'))
expect(handleClick).toHaveBeenCalled()
})
CI 里跑一遍,确保不会因为改个样式把功能搞挂。虽然多花点时间,但比起线上事故,这成本太低了。
部署:一键发布到 GitHub Pages
因为是内部系统,客户没给服务器,只能先放 GitHub Pages 凑合用(别笑,三线城市客户真就这么干)。
Vite 打包后,一行命令搞定:
pnpm build
gh-pages -d dist
配合 GitHub Actions,每次 push 到 main 分支自动部署:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm build
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
第二天早上客户就能看到最新版本,再也不用我们手动发 zip 包了。
最后一点真心话
说实话,作为一个三线城市的“技术负责人”(其实就是最老的前端),我深知资源有限、人力紧张。但我们不能因此就放弃工程化。现代化前端不是炫技,而是用工具减少重复劳动、降低出错概率、提升交付质量。
这套架子搭完后,新同事入职第一天就能跑起来项目,改个页面不用问“这个变量在哪定义的”。上周五加班到九点,但因为有完善的类型检查和测试,上线一次过——回家路上还能刷两道算法题,为跳槽做准备。
如果你也在小公司挣扎,别觉得“我们项目小,没必要”。恰恰是因为小,才更需要靠工具和规范来弥补人力的不足。
对了,所有代码我都放 GitHub 上了,欢迎 star(不是为了流量,是真的希望帮到同行): https://github.com/yourname/modern-frontend-boilerplate
(当然,链接是假的,真项目不能公开 😅)
写完这篇已经是中午十二点,产品又在群里@我:“那个筛选条件能不能加个‘全部’选项?”
我深吸一口气,回了个“OK”,然后默默打开了项目代码——幸好昨天配好了 Git hooks,至少格式不会乱。

评论 0