
1. 项目概述一个普通前端的半年实践去年下半年我给自己定了个小目标不再只是被动地完成业务需求而是主动去探索一些能提升自己“手感”和“工程感”的项目。我把这种状态称为“vibe coding”——不是为了赶工期也不是为了应付面试纯粹是跟着感觉走去折腾一些自己觉得有意思、有挑战或者能解决实际痒点的东西。半年下来陆陆续续做了四个小项目从重构个人博客到尝试全栈开发从工具链优化到探索新的交互范式。回头看看这段“vibe coding”的经历比刷任何面试题都让我对前端这个行当有了更深的理解。它不是关于掌握了某个炫酷的新框架而是关于如何把一个想法从零到一地实现出来并在过程中踩坑、填坑最终形成肌肉记忆和工程直觉。今天我就把这四个项目的实践心得拆开揉碎了分享给你希望能给同样在摸索中的你一些参考。2. 核心思路为什么是这四个项目选择项目尤其是这种自我驱动的“vibe coding”项目核心思路不在于追新求异而在于“补短板”和“探边界”。我的出发点很明确作为一个主要写业务逻辑的前端我的舒适区是React生态和UI搭建。那么我的短板在哪里边界又在哪里我梳理了这么几个方向工程化深度、全栈能力、类型安全实践、以及开发体验的极致优化。这四个项目正是围绕这些方向展开的。第一个项目我选择用Next.js 14 (App Router) TypeScript彻底重构了我的个人博客。这不只是为了换个新框架而是想深入体验App Router的数据获取、服务端组件(Server Components)和流式渲染(Streaming)。在业务中可能没机会大规模用但在个人项目里可以大胆试错。第二个项目我尝试用Next.js Supabase构建了一个简易的“灵感速记”全栈应用。目标很单纯亲手打通从前端到数据库的完整链路理解服务端如何安全操作数据库以及客户端状态如何同步。第三个项目我聚焦于TypeScript的高级类型体操和项目配置。我给自己出了几道“题”比如实现一个类型安全的表单验证器、封装一个完善的API请求层并深入研究tsconfig.json的每一个重要选项特别是处理了那个恼人的“baseUrl已弃用”警告。第四个项目则偏向于“工匠精神”我探索了纯前端如何利用Web Worker处理大文件如PDF预览优化、如何通过构建配置和VSCode插件极致提升本地开发体验。这四个项目看似独立实则环环相扣。博客重构让我熟悉了Next.js的现代模式全栈应用让我把博客中学到的服务端能力落地到真实数据交互TypeScript深度实践为前两个项目提供了坚实的类型安全保障而工具链和性能优化则是让所有开发过程变得更顺畅、更专业的催化剂。它们共同构成了一个普通前端向“更扎实、更全面”方向迈进的一个切面。3. 项目一用Next.js 14与TypeScript重构个人博客3.1 技术选型与架构动机为什么是Next.js 14的App Router在它发布初期各种概念Server/Client Components, Streaming, Server Actions确实让人眼花缭乱。但在业务中由于历史包袱和稳定性考量团队往往倾向于观望。个人项目就成了最好的试验田。App Router最大的吸引力在于它基于文件系统的路由和默认的服务端渲染能力这让构建一个内容驱动型的博客变得异常简单和高效。我不再需要手动配置SSG静态站点生成路径fetchAPI在服务端组件中默认会被缓存配合generateStaticParams能轻松实现高性能的静态生成。TypeScript则是项目的基石。对于博客这种内容结构相对固定的项目定义清晰的类型如Post类型包含id,title,content,date等字段能从开发伊始就避免许多低级错误。更重要的是我想实践更严格的类型策略比如使用zod库进行运行时数据验证确保从Markdown文件解析出来的数据完全符合类型定义实现从文件到UI的端到端类型安全。3.2 核心实现细节与踩坑记录实现过程并非一帆风顺。首先是将原有的Markdown内容迁移过来。我使用了remark和remark-html这套强大的处理链。关键步骤是在/app/blog/[slug]/page.tsx这个服务端组件中通过params.slug读取文件名然后使用Node.js的fs模块读取对应的Markdown文件。// 这是一个简化的示例 import fs from fs/promises; import path from path; import matter from gray-matter; import { remark } from remark; import html from remark-html; interface Post { id: string; title: string; date: string; contentHtml: string; } export default async function BlogPost({ params }: { params: Promise{ slug: string } }) { const { slug } await params; const fullPath path.join(process.cwd(), posts, ${slug}.md); const fileContents await fs.readFile(fullPath, utf8); const matterResult matter(fileContents); const processedContent await remark() .use(html) .process(matterResult.content); const contentHtml processedContent.toString(); const post: Post { id: slug, title: matterResult.data.title, date: matterResult.data.date, contentHtml, }; return ( article h1{post.title}/h1 div dangerouslySetInnerHTML{{ __html: post.contentHtml }} / /article ); }这里第一个坑就出现了服务端组件的异步数据获取。在App Router中页面组件默认是服务端组件可以直使用async/await。但一开始我忘了将组件声明为async导致await报错。第二个坑是关于缓存。Next.js默认对fetch请求进行缓存但我这里用的是fs读文件。为了获得类似的静态生成效果我必须在页面或generateStaticParams中明确声明。我选择了在page.tsx中导出generateStaticParams函数来告诉Next.js所有可能的slug从而实现构建时预渲染所有博客文章。export async function generateStaticParams() { const posts await getAllPostSlugs(); // 一个获取所有文章slug的函数 return posts.map((post) ({ slug: post.id, })); }第三个坑是样式管理。我尝试了Tailwind CSS虽然效率高但觉得在内容为主的博客中编写复杂的自定义样式时类名字符串会变得很长。后来我转向了CSS Modules它为每个组件提供局部作用域的样式配合PostCSS开发体验更贴近我的习惯。这让我明白技术选型没有绝对好坏只有是否适合当前场景和个人偏好。3.3 性能优化与部署心得重构的一大目标是提升性能。Next.js 14在这方面开箱即用但我还是做了一些额外工作。首先图片优化。我使用next/image组件处理博客中的图片它能自动提供WebP等现代格式、按需加载和尺寸优化。关键是正确设置remotePatterns来允许图片域名。其次字体优化。我使用了next/font中的Google字体它会自动将字体文件静态托管并内联CSS消除字体加载时的布局偏移。部署我选择了Vercel它与Next.js的集成是无缝的。只需要关联Git仓库它就能自动检测框架、运行构建命令并部署。在Vercel的控制台我可以清晰地看到每个部署的性能指标如LCP (Largest Contentful Paint)、FID (First Input Delay)等。通过这次部署我直观地感受到了从代码提交到线上可用的完整CI/CD流程这对于前端开发者理解运维侧的工作非常有帮助。注意在将Markdown转换为HTML时务必注意XSS跨站脚本攻击风险。remark-html本身不会清理危险标签。在生产环境中强烈建议使用rehype-sanitize插件来处理HTML内容确保只有安全的标签和属性被保留。4. 项目二基于Next.js与Supabase的轻量全栈应用4.1 全栈初体验技术栈搭配逻辑当我掌握了服务端渲染后自然想往“数据持久化”走一步。Supabase进入了我的视野。它宣称是开源的Firebase替代品提供了PostgreSQL数据库、实时订阅、身份验证、存储等一整套后端服务。对于前端开发者来说它的最大优势是提供了一个强大的JavaScript/TypeScript客户端库让你能以类似前端操作状态的方式操作数据库。我的项目是一个“灵感速记”应用功能极简用户登录后可以创建、编辑、删除和查看自己的文字片段。技术栈很清晰Next.js 14 (App Router) 作为全栈框架Supabase作为BaaS (Backend as a Service)前端状态管理使用React Context useReducerUI组件则用了Radix UI这样的底层UI原语库以便更精细地控制样式。选择Supabase而非直接写Node.js后端是基于快速验证和降低复杂度的考虑。我不需要自己搭建服务器、处理数据库连接池、设计REST API路由。Supabase的客户端库直接提供了supabase.from(notes).select(*)这样的方法配合行级安全策略(Row Level Security, RLS)可以在服务端安全地执行查询。这让我能集中精力学习前后端数据流和权限设计而不是被基础设施困扰。4.2 数据库设计与安全策略实践在Supabase控制台我创建了一张notes表包含id(UUID),user_id(UUID, 关联auth.users),title(text),content(text),created_at(timestamptz)等字段。真正的核心在于行级安全策略RLS。这是Supabase安全模型的基石。如果不启用RLS任何能拿到项目匿名密钥的人都可以随意增删改查你的数据这是极其危险的。我为notes表启用了RLS并创建了以下策略-- 策略1用户只能插入属于自己的笔记 CREATE POLICY 用户可插入自己的笔记 ON notes FOR INSERT WITH CHECK (auth.uid() user_id); -- 策略2用户只能查看自己的笔记 CREATE POLICY 用户可查看自己的笔记 ON notes FOR SELECT USING (auth.uid() user_id); -- 策略3用户只能更新自己的笔记 CREATE POLICY 用户可更新自己的笔记 ON notes FOR UPDATE USING (auth.uid() user_id); -- 策略4用户只能删除自己的笔记 CREATE POLICY 用户可删除自己的笔记 ON notes FOR DELETE USING (auth.uid() user_id);这些SQL策略确保了数据库层面的数据隔离。auth.uid()是Supabase提供的一个函数返回当前请求用户的ID。这样无论前端直接调用客户端库还是在Next.js的服务端组件/Server Action中调用查询都会被自动加上WHERE user_id auth.uid()的条件。在Next.js中我主要在两种场景下操作数据服务端组件中获取数据在/app/notes/page.tsx中我创建一个服务端组件使用supabase/ssr包提供的createServerClient来安全地通过cookies获取当前用户然后查询其笔记。因为是在服务端执行数据库密钥不会暴露给浏览器。客户端交互CRUD对于创建、编辑、删除操作我使用了Next.js的Server Actions。在表单的action属性中调用一个在服务端定义的异步函数。在这个Server Action内部我同样使用createServerClient来验证用户并执行数据库操作。这避免了创建单独的API路由让数据变更逻辑更贴近使用它的组件。4.3 状态同步与实时功能浅尝基本的CRUD实现后我想试试Supabase的实时功能。实现起来出乎意料地简单。在笔记列表页面我在useEffect中订阅了针对notes表的变更useEffect(() { const channel supabase .channel(schema-db-changes) .on( postgres_changes, { event: *, // INSERT, UPDATE, DELETE schema: public, table: notes, filter: user_ideq.${userId}, }, (payload) { // 根据payload.eventType更新本地状态 console.log(收到实时变更!, payload); // 例如如果是INSERT将新笔记添加到列表 if (payload.eventType INSERT) { setNotes(prev [payload.new as Note, ...prev]); } // 如果是DELETE从列表中移除 if (payload.eventType DELETE) { setNotes(prev prev.filter(note note.id ! payload.old.id)); } // UPDATE类似 } ) .subscribe(); return () { supabase.removeChannel(channel); }; }, [supabase, userId]);这样当我在一个浏览器标签页中新增或修改笔记时另一个打开的标签页能几乎实时地看到列表更新。这个体验非常棒让我对“实时应用”有了最直接的感受。当然在更复杂的生产环境中需要考虑状态冲突、乐观更新等更复杂的逻辑但这个入门实验为我打开了新世界的大门。实操心得Supabase客户端在服务端和客户端的使用方式不同。在服务端如Server Action、路由处理器你需要使用createServerClient并正确处理cookies的传入和传出。在客户端则使用普通的createBrowserClient。混淆两者会导致认证失败。建议将客户端创建逻辑封装成工具函数根据环境返回正确的客户端实例。5. 项目三深入TypeScript类型系统与工程化配置5.1 应对“baseUrl”弃用tsconfig.json深度调优在搭建项目时我遇到了VSCode的警告“选项‘baseUrl’已弃用并将停止在TypeScript 7.0中运行。” 这促使我系统地去研究tsconfig.json。baseUrl和paths曾经是配置模块别名alias的标准方式让你能用/components/Button这样的路径代替冗长的相对路径../../../components/Button。TypeScript团队推荐转向使用**moduleResolution策略为bundler**并配合打包器如Webpack、Vite、Next.js自身的别名解析功能。以Next.js为例它内部使用Turbopack/Rust编译器其别名是在next.config.js中配置的// next.config.js const path require(path); /** type {import(next).NextConfig} */ const nextConfig { webpack: (config) { config.resolve.alias { ...config.resolve.alias, : path.resolve(__dirname, ./), }; return config; }, // 或者使用实验性的turbopack别名如果使用Turbopack experimental: { turbo: { resolveAlias: { : ./, }, }, }, }; module.exports nextConfig;同时在tsconfig.json中我做了如下配置{ compilerOptions: { target: ES2022, lib: [dom, dom.iterable, esnext], allowJs: true, skipLibCheck: true, strict: true, noEmit: true, // Next.js负责编译这里不输出文件 esModuleInterop: true, module: esnext, moduleResolution: bundler, // 关键使用bundler解析策略 resolveJsonModule: true, isolatedModules: true, jsx: preserve, incremental: true, plugins: [ { name: next } ], // 不再需要 baseUrl 和 paths // baseUrl: ., // paths: { // /*: [./*] // } }, include: [next-env.d.ts, **/*.ts, **/*.tsx, .next/types/**/*.ts], exclude: [node_modules] }这样TypeScript语言服务会依赖Next.js或你的打包器的解析逻辑baseUrl的弃用警告也就解决了。这个探索过程让我明白现代前端工具链是一个整体TypeScript的配置需要与构建工具、框架的配置协同工作而不是孤立存在。5.2 高级类型实践构建类型安全的工具函数为了提升类型体操能力我尝试实现一些通用的类型安全工具。其中一个例子是实现一个类型安全的pick和omit函数。虽然TypeScript内置了Pick和Omit类型但有时我们需要在运行时也能进行安全的属性选择。// 一个增强版的pick函数返回类型是精确的 function pickT extends object, K extends keyof T(obj: T, ...keys: K[]): PickT, K { const result {} as PickT, K; keys.forEach(key { if (key in obj) { result[key] obj[key]; } }); return result; } interface User { id: number; name: string; age: number; email: string; } const user: User { id: 1, name: Alice, age: 30, email: aliceexample.com }; // pickedUser 的类型被推断为 { name: string; age: number; } const pickedUser pick(user, name, age); console.log(pickedUser); // { name: Alice, age: 30 } // pickedUser.id // 错误Property id does not exist on type { name: string; age: number; }.另一个实践是封装一个带自动错误处理和类型推断的API请求层。我使用zod定义响应数据的模式schema然后利用TypeScript的泛型让请求函数的返回值能根据传入的schema自动推断。import { z } from zod; // 定义一个创建API客户端的函数 const createApiClient (baseURL: string) { return { async getT extends z.ZodTypeAny(endpoint: string, schema: T): Promisez.inferT { const response await fetch(${baseURL}${endpoint}); if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } const data await response.json(); // 使用zod进行运行时验证确保数据符合类型 return schema.parse(data); }, // 类似地实现 post, put, delete... }; }; // 定义文章数据的schema const postSchema z.object({ id: z.number(), title: z.string(), body: z.string(), userId: z.number(), }); type Post z.infertypeof postSchema; // 从schema推导出TypeScript类型 const api createApiClient(https://jsonplaceholder.typicode.com); // 调用时response的类型自动被推断为 Post[] async function fetchPosts() { try { const posts await api.get(/posts, z.array(postSchema)); console.log(posts[0].title); // 类型安全有自动补全 // console.log(posts[0].unknownProp); // 错误 } catch (error) { console.error(Fetch failed:, error); } }这种方式将编译时类型检查和运行时数据验证结合了起来极大地增强了代码的健壮性。在团队协作中后端API的响应格式一旦在zod schema中定义前端所有使用该数据的地方都会获得准确的类型提示和错误预警。5.3 严格模式与代码质量工具链集成我开启了TypeScript最严格的检查选项并在项目中集成了ESLint和Prettier形成了自动化的代码质量守护链。在tsconfig.json中strict: true是总开关它包含了一系列严格检查如noImplicitAny禁止隐式的any类型、strictNullChecks严格的null检查等。一开始这很痛苦很多地方报错但强迫我处理了所有可能的空值和未定义情况代码质量显著提升。ESLint配置我使用了typescript-eslint推荐规则并添加了一些个人偏好规则如要求函数显式声明返回类型explicit-function-return-type这有助于理解函数的作用。Prettier则负责统一的代码格式化。我将这些工具与VSCode和Git钩子集成VSCode安装ESLint和Prettier插件并设置editor.codeActionsOnSave为source.fixAll.eslint保存时自动修复可修复的ESLint错误并格式化代码。Git钩子使用husky和lint-staged在pre-commit钩子中对暂存区的文件运行ESLint和Prettier确保提交到仓库的代码都是符合规范的。// package.json 片段 { scripts: { lint: eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0, format: prettier --write \**/*.{ts,tsx,md,json}\ }, lint-staged: { *.{ts,tsx}: [eslint --fix, prettier --write] }, devDependencies: { husky: ^9.0.0, lint-staged: ^15.0.0, // ... 其他依赖 } }这套组合拳下来虽然初期配置繁琐但一旦跑通它就像一位不知疲倦的代码审查员能自动捕捉大多数低级错误和风格问题让我能更专注于逻辑实现。6. 项目四开发体验优化与前端性能深潜6.1 利用Web Worker处理前端重型任务在业务开发中我遇到过PDF文件渲染导致页面卡顿的问题。主线程被大量计算阻塞用户界面无法响应。Web Worker为解决这类问题提供了思路。它允许你在后台线程运行脚本与主线程并行从而不阻塞UI。我设计了一个实验一个模拟的大文件比如一个巨大的JSON或文本文件处理场景。主线程负责UI交互和任务派发Web Worker负责繁重的文件解析或计算工作。首先创建Worker文件file-processor.worker.ts// file-processor.worker.ts self.onmessage async (event: MessageEvent) { const { file, task } event.data; let result; switch (task) { case parseLargeJson: // 模拟解析一个巨大的JSON字符串 const text await file.text(); try { const hugeObject JSON.parse(text); // 这是一个耗时的操作 result { success: true, data: 解析完成共${Object.keys(hugeObject).length}条数据 }; } catch (error) { result { success: false, error: (error as Error).message }; } break; case calculateHash: // 模拟计算文件哈希这里简化了 const buffer await file.arrayBuffer(); // ... 使用Web Crypto API进行哈希计算 result { success: true, data: 模拟哈希值 }; break; default: result { success: false, error: 未知任务 }; } self.postMessage(result); };然后在主线程中动态创建和使用Worker// 在主组件中 const processFileWithWorker (file: File, task: string) { // 注意在生产环境中Worker文件需要正确打包和引用。这里假设使用Vite/Webpack的worker-loader或内联方式。 // 一种常见的方式是new Worker(new URL(./file-processor.worker.ts, import.meta.url)); // 为了示例简化我们假设已经配置好 const worker new Worker(./file-processor.worker.ts); worker.postMessage({ file, task }); worker.onmessage (event: MessageEvent) { const { success, data, error } event.data; if (success) { console.log(Worker任务完成:, data); // 更新UI状态 } else { console.error(Worker任务失败:, error); } worker.terminate(); // 任务完成后终止Worker释放资源 }; worker.onerror (error) { console.error(Worker发生错误:, error); worker.terminate(); }; };通过这个实验我深刻理解了Web Worker的通信模型基于postMessage和onmessage以及它的局限性Worker不能直接操作DOM与主线程的数据传递是“值拷贝”而非“共享内存”除非使用Transferable对象或SharedArrayBuffer但这更复杂。对于大文件传递整个文件数据可能本身就有性能开销。因此更优的方案可能是使用FileReader在Worker中直接读取文件或者使用createObjectURL。6.2 极致化VSCode开发环境工欲善其事必先利其器。我花了不少时间打磨我的VSCode配置目标是让编码过程行云流水。TypeScript智能感知确保typescript和types/node等类型定义包正确安装。在.vscode/settings.json中配置typescript.preferences.autoImportSpecifier: shortest让自动导入使用最短路径。启用typescript.suggest.completeFunctionCalls: true可以在输入函数名时自动补全括号和参数。代码片段Snippets我为常用的代码模式创建了自定义片段。例如一个快速创建React函数组件的片段{ React Functional Component with TypeScript: { prefix: rfcts, body: [ interface ${1:ComponentName}Props {, ${2:propName}: string;, }, , export const ${1:ComponentName} ({ ${2:propName} }: ${1:ComponentName}Props) {, return (, div, {${2:propName}}, /div, );, }; ], description: 创建一个带TypeScript类型的React函数组件 } }这能节省大量重复输入的时间。调试配置对于Next.js项目在.vscode/launch.json中配置调试器非常有用。{ version: 0.2.0, configurations: [ { name: Next.js: debug server-side, type: node-terminal, request: launch, command: npm run dev }, { name: Next.js: debug client-side, type: chrome, request: launch, url: http://localhost:3000 } ] }这样我就可以在VSCode里直接打断点调试服务端Node.js代码和客户端浏览器代码。插件精选Error Lens将ESLint和TypeScript错误直接内联显示在代码行尾无比直观。GitLens增强的Git功能可以快速查看代码的作者、历史。PostCSS Language Support更好地支持CSS Modules等PostCSS语法。Tailwind CSS IntelliSense如果使用Tailwind这个插件必不可少。ES7 React/Redux/React-Native snippets提供更多有用的React代码片段。6.3 构建配置与性能分析实战最后我深入项目的构建配置尝试优化产物体积和性能。在Next.js中这主要涉及next.config.js和如何分析打包结果。包分析使用next/bundle-analyzer可以可视化地查看每个依赖包在最终bundle中所占的体积。npm install next/bundle-analyzer --save-dev// next.config.js const withBundleAnalyzer require(next/bundle-analyzer)({ enabled: process.env.ANALYZE true, }); module.exports withBundleAnalyzer({ // 你的其他Next.js配置... });运行ANALYZEtrue npm run build它会生成一个交互式图表帮助你发现哪些包过大进而考虑是否可以用更轻量的替代品、动态导入dynamic import或进行代码分割。动态导入Code Splitting对于非首屏必需的组件或大型库使用Next.js的dynamic导入。import dynamic from next/dynamic; const HeavyChartComponent dynamic(() import(../components/HeavyChart), { loading: () p图表加载中.../p, ssr: false, // 如果组件依赖浏览器API需要禁用服务端渲染 }); export default function Dashboard() { return ( div h1仪表盘/h1 {/* HeavyChartComponent 会被单独打包成一个chunk按需加载 */} HeavyChartComponent / /div ); }图片与字体优化如前所述坚持使用next/image和next/font。对于自定义字体考虑使用font-display: swapCSS属性并预连接到关键字体资源。性能监测在部署后利用Vercel Analytics、Lighthouse CI或自定义的性能监测脚本持续关注核心Web指标Core Web Vitals如LCP、FID、CLS。根据这些数据回头调整代码分割策略、图片尺寸、第三方脚本的加载时机等。通过这一系列的优化我不仅让应用跑得更快更重要的是建立了一套从本地开发到构建部署的性能感知和优化方法论。这让我在应对业务中真实的性能问题时能有章可循而不是盲目猜测。