三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Vibe Coding实践:周末用Next.js+Supabase打造个人简历管理系统

Vibe Coding实践:周末用Next.js+Supabase打造个人简历管理系统

1. 从“周末项目”到“简历管家”:一个开发者的效率革命

上周五晚上,我盯着自己那份躺在硬盘角落、尘封了近两年的简历PDF,突然意识到一个尴尬的现实:每次求职或者想更新履历,我都得重新打开那个陈旧的Word文档,手动调整格式、补充项目、计算时间线,整个过程繁琐且容易出错。更麻烦的是,我手头还有几个朋友托我帮忙看看他们的简历,格式五花八门,信息更新不及时。一个念头冒了出来:为什么不能有一个轻量、专注、完全由我自己掌控的简历维护系统?它能让我和朋友们在一个地方集中管理简历信息,一键生成不同风格的PDF,甚至能追踪投递记录。

这个想法并不新鲜,市面上有无数在线的简历制作工具。但作为一个开发者,我对那些需要注册、有订阅费用、并且数据存在别人服务器上的工具总有些抗拒。我想要的是完全的控制权:数据在我自己的服务器(甚至本地),界面简洁到极致,功能直击痛点——就是增删改查简历内容,然后导出。按照传统的开发流程,从设计数据库、搭建后端API、到编写前端界面和实现PDF生成,没个一两周根本下不来,周末两天?想都别想。

直到我尝试了最近在开发者圈子里热度颇高的Vibe Coding。它被许多人描述为一种“心流编码”或“氛围编码”的实践,核心不在于某个具体的框架或工具,而是一种高度聚焦、工具链深度整合、以结果为唯一导向的开发状态。简单说,就是摒弃一切不必要的仪式感(比如过度设计、纠结技术选型),用最顺手、最高效的工具组合,直奔最终可运行的产品。我决定用这个周末,亲身实践一次“Vibe Coding”,目标是周日晚上,一个能跑起来的简历系统必须上线。

2. Vibe Coding 心法:极简主义的技术选型与组合拳

Vibe Coding 不是魔法,它不会自动生成代码。它的威力在于决策效率。在项目开始的头一个小时里,我不再纠结于“用React还是Vue?”、“后端是Go还是Node.js?”,而是快速问了自己几个问题,并基于答案锁定了技术栈:

  1. 核心需求是什么?管理简历数据(CRUD)、生成PDF。不需要实时协作,不需要复杂权限。
  2. 如何最快实现交互界面?我需要一个能快速搭建表单、表格,并且美观的UI框架。Shadcn/ui或基于Tailwind CSS的组件库是首选,因为它们提供的是可直接复制粘贴的组件代码,无需安装庞大的npm包,风格现代且可定制性极强。
  3. 如何最快实现后端逻辑和数据库?为了极致简化,我决定采用“全栈框架”思路。Next.js (App Router)成了不二之选。它允许我在同一个项目里写React前端和API路由,内置了路由、渲染优化,并且部署到Vercel等平台简单到令人发指。数据库方面,为了免去自己搭建数据库服务的麻烦,我选择了SupabaseVercel Postgres。它们提供了云端的PostgreSQL数据库,并有非常友好的JavaScript/TypeScript SDK,从建表到查询,几行代码就能搞定。
  4. 如何生成PDF?这是一个关键点。在浏览器端生成PDF体验好但库可能较重;在服务端生成更稳定。我选择了@react-pdf/renderer。这个库允许我使用React组件的方式来“声明式”地描述我的PDF文档结构(就像写JSX一样),然后在Node.js环境(即Next.js的API路由中)渲染成PDF文件。这种方式将UI设计和PDF生成完美统一,维护起来特别方便。
  5. 如何部署?Vercel。它与Next.js是天作之合,支持自动从Git仓库部署,并提供预览环境、自动SSL证书。我写完代码,git push一下,几分钟后产品就上线了。

这个选型过程可能只花了15分钟。没有画架构图,没有写设计文档。Vibe Coding的核心心法就是:基于最熟悉、社区最活跃、集成最顺畅的工具,组合出一套能跑通最小闭环的“特遣队”。任何在这个闭环之外的技术诱惑(比如“要不要试试新的状态管理库?”),都被我刻意忽略了。

3. 核心实现:从数据模型到一键生成的完整链路

确定了“特遣队”,接下来就是实现。我按照功能模块,以“实现-测试-迭代”的微循环推进。

3.1 定义数据模型:Supabase表的五分钟设计

简历数据相对结构化。我在Supabase控制台快速创建了一张profiles表,用于存用户基本信息(姓名、邮箱、电话等),和一张resume_items表,用于存工作经历、教育背景、项目经验等。表结构设计力求简单:

-- profiles 表 id (uuid, primary key) full_name (text) email (text) phone (text) linkedin_url (text) github_url (text) -- 其他基本信息... -- resume_items 表 id (uuid, primary key) profile_id (uuid, foreign key -> profiles.id) item_type (text) -- ‘work’, ‘education’, ‘project’, ‘skill’ title (text) subtitle (text) -- 如公司名、学校名 description (text) -- 详细描述,支持Markdown start_date (date) end_date (date) -- 可为空,表示“至今” sort_order (integer) -- 用于自定义排序

这里的一个Vibe Coding技巧是:不过度设计。我没有把技能单独拆表,而是用item_type='skill'来区分,描述字段里用逗号分隔技能项。初期够用就行,复杂度是后来长出来的,不是一开始设计进去的。

3.2 构建管理后台:Next.js + Shadcn/ui 的闪电开发

在Next.js项目中,我使用npx shadcn@latest add button card dialog form input ...命令,快速添加了所需的UI组件。然后,在/app/dashboard/page.tsx下,我开始搭建管理界面。

  1. 数据获取:在服务端组件中,直接使用Supabase的Server Client获取当前用户的简历数据。得益于Next.js的Server Component,我可以在组件函数顶部直接await数据库查询,代码简洁安全。
  2. 表单构建:使用Shadcn/ui提供的FormInputTextareaButton组件,配合react-hook-form进行表单管理。为resume_items设计了一个动态表单,可以添加、删除、排序多条经历。
  3. 状态同步:为了获得即时反馈的体验,我使用了Optimistic Updates(乐观更新)。当用户点击“保存”时,前端界面立即显示更新后的数据,同时向后端发起请求。如果请求失败,再回滚界面状态并提示错误。这个小小的技巧极大地提升了操作流畅感,是Vibe Coding中注重用户体验的体现。
// 示例:乐观更新工作经历 const [items, setItems] = useState(initialItems); const { mutate: updateItem } = useMutation({ mutationFn: (updatedItem) => api.updateItem(updatedItem), onMutate: async (newItem) => { // 1. 在请求发出前,立即更新本地状态 const previousItems = items; setItems(items.map(item => item.id === newItem.id ? newItem : item)); return { previousItems }; }, onError: (err, newItem, context) => { // 2. 如果失败,回滚到之前的状态 setItems(context.previousItems); toast.error('更新失败'); }, // onSuccess: 可选,如果需要处理成功后的额外逻辑 });

整个管理后台的页面,大约只花了周六一个下午就完成了核心的增删改查和列表展示。界面清爽,操作跟手。

3.3 PDF生成引擎:用React写PDF的艺术

这是最有趣也最具挑战的一环。我在/app/api/generate-pdf/route.ts创建了一个API路由。

  1. 设计模板组件:我创建了一个ResumePDFDocument.tsx组件。这个组件不渲染在浏览器里,它只被@react-pdf/renderer使用。它接收profileitems作为props,内部使用PDF库提供的<Document>,<Page>,<View>,<Text>,<Image>等组件来布局。这就像是在用React写一个打印专用的视图层。
// ResumePDFDocument.tsx import { Document, Page, Text, View, StyleSheet } from '@react-pdf/renderer'; const styles = StyleSheet.create({ page: { padding: 30 }, name: { fontSize: 24, fontWeight: 'bold' }, section: { marginBottom: 15 }, }); const ResumePDFDocument = ({ profile, items }) => ( <Document> <Page size="A4" style={styles.page}> <Text style={styles.name}>{profile.full_name}</Text> {/* 渲染联系方式、经历等 */} {items.map(item => ( <View key={item.id} style={styles.section}> <Text>{item.title} @ {item.subtitle}</Text> <Text>{item.description}</Text> </View> ))} </Page> </Document> );
  1. API路由实现:在API路由中,我查询数据库,获取数据,然后使用pdf对象的renderToStream方法,将React组件渲染成PDF流,最后通过Response返回。
// app/api/generate-pdf/route.ts import { NextRequest } from 'next/server'; import { renderToStream } from '@react-pdf/renderer'; import ResumePDFDocument from '@/components/ResumePDFDocument'; import { getProfileData } from '@/lib/supabase'; export async function GET(request: NextRequest) { const profileId = request.nextUrl.searchParams.get('profileId'); const { profile, items } = await getProfileData(profileId); const pdfStream = await renderToStream( <ResumePDFDocument profile={profile} items={items} /> ); return new Response(pdfStream, { headers: { 'Content-Type': 'application/pdf', 'Content-Disposition': `attachment; filename="${profile.full_name}_Resume.pdf"`, }, }); }
  1. 前端触发:在管理后台,一个“导出PDF”按钮直接链接到这个API端点,或者用fetch调用后通过Blob对象触发浏览器下载。

踩坑实录@react-pdf/renderer的样式系统是自有的,不完全等同于CSS。特别是布局,它主要依赖Flexbox,但属性名是驼峰式(如flexDirection)。最初我按Web CSS来写,导致布局错乱。解决办法是仔细阅读其样式文档,并先在浏览器中用类似的div结构模拟PDF布局,确认后再移植到PDF组件中。

3.4 身份认证与数据隔离:轻量而安全的方案

虽然初期可能自用,但考虑到要给朋友用,基础的安全和隔离必须有。我继续利用Supabase,它内置了完整的身份认证系统。

  1. 启用Supabase Auth:在控制台启用邮箱密码登录或第三方登录(如GitHub)。
  2. 集成到Next.js:使用@supabase/ssr包和Next.js的Middleware,可以轻松实现服务端和客户端的认证状态同步。我创建了一个/app/auth目录来处理登录/注册页。
  3. 数据关联:在profiles表中,增加一个user_id字段,与Supabase Auth的用户ID关联。所有的数据库查询都加上where user_id = auth.uid()的条件,这样就天然实现了数据隔离,用户只能看到和操作自己的简历数据。

这部分花了周六晚上的一些时间,但Supabase的文档和示例非常清晰,集成过程基本是“复制-粘贴-微调”。

4. 效率提升的关键:那些“少即是多”的开发决策

回顾整个开发过程,真正让我在两天内完成系统的,不是写了多少行酷炫的代码,而是做对了一系列“不做”和“简化”的决策。

  1. 放弃状态管理库:这个应用的状态非常简单(表单数据、列表数据),React的useStateuseContext完全够用。引入Redux或Zustand只会增加复杂度。
  2. 放弃独立的API服务器:Next.js的App Router将API路由和页面路由统一,省去了配置CORS、管理两个服务器进程的麻烦。开发体验高度统一。
  3. 使用云数据库服务(DBaaS):自己搭建和维护PostgreSQL,哪怕是Docker,也会消耗宝贵的时间。Supabase/Vercel Postgres让我在5分钟内就拥有了一个可用的、带管理后台的数据库。
  4. CSS方案锁定Tailwind:无需在不同的CSS方案间切换。Tailwind的实用性优先类名,让我在调整边距、颜色、排版时速度极快,几乎不需要在CSS文件和组件文件间跳转。
  5. 部署流程自动化:连接GitHub仓库到Vercel,设置自动部署。每次git push后,我不需要手动构建、上传、重启服务。这节省了巨大的心智负担和时间,让我可以专注于代码本身。

这些决策共同构成了Vibe Coding的实践:选择那些能让你忘记“工具存在”的工具。它们不应该成为你思考的负担,而应该像空气和水一样自然,让你全部的注意力都集中在解决实际问题上。

5. 从周末原型到可分享的产品:后续优化思路

周日晚上,系统基本功能全部跑通。我用自己的简历数据测试了增删改查和PDF生成,一切顺利。但这只是一个MVP(最小可行产品)。如果我想把它变成一个真正可长期使用、甚至分享给更多人的工具,还有一些方向可以优化:

  1. 多模板支持:目前的PDF模板只有一种风格。可以抽象出模板系统,让用户选择不同的主题(经典、现代、创意等)。这需要将ResumePDFDocument组件进一步模块化,通过配置来驱动样式和布局。
  2. 简历版本管理:很多时候我们针对不同职位需要微调简历。可以增加“复制一份简历”并稍作修改的功能,形成不同的版本,方便管理和投递。
  3. 投递记录追踪:在系统内增加一个简单的模块,记录简历投递的公司、职位、日期、状态(已投递/面试中/已拒绝等),让求职过程更可视化。
  4. 性能与缓存:PDF生成是CPU密集型操作。可以考虑对生成的PDF进行缓存(例如,在用户保存简历后,后台预生成一次PDF并存储到对象存储如Supabase Storage或Vercel Blob中),当用户再次下载时直接返回缓存文件,提升响应速度。
  5. 离线支持与PWA:考虑到有时可能在网络不好的环境下使用,可以将其改造为渐进式Web应用(PWA),利用Service Worker缓存关键资源,实现基本的离线查看和编辑。

不过,这些都属于“锦上添花”。Vibe Coding教会我最重要的一课是:先做出一个能解决核心问题的、可用的东西,比规划一个完美无缺但永远做不出来的东西重要一万倍。这个周末项目让我不仅收获了一个实用的私人工具,更体验了一种高效、愉悦的开发节奏。下次再有一个小想法时,我知道,一个周末的时间,或许真的足够了。

← 返回列表