返回博客

/ 杂谈

第一篇技术博客:用 Codex vibe coding 一个个人网站

从简历装不下自己开始,到用 Next.js、MDX、Tailwind 和 GitHub Pages 搭建一个更动态的个人展示载体。

6 minPersonal Site · Codex · Next.js · MDX

我开始做这个网站,并不是因为“我需要一个更好看的简历”。恰恰相反,是因为我越来越觉得,简历太像一个压缩包:它必须短、必须规整、必须把经历塞进几行 bullet 里。项目里真正困难的地方、一次调试背后的判断、一个系统从想法到落地的路径,往往都被压缩成了几个动词:负责、参与、实现、优化。

但人的经历不是这样展开的。

比如一次实习,不只是公司名和岗位名;一次科研,也不只是论文标题和作者顺序;一个工程项目,更不只是“使用某某框架完成某某功能”。它们背后有问题意识、有取舍、有失败、有复盘,也有很多很小但很能说明能力的细节。所以我需要一个比简历更丰富、更动态、也更像“长期工作台”的载体。

于是我决定做一个个人网站。

更准确地说,我用 Codex vibe coding 了一个个人网站。

1. 从“简历页面”到“个人系统”#

一开始,我想象中的页面很简单:头像、教育经历、项目、论文、联系方式。这样的页面当然可以用模板很快搭出来,但我很快意识到,如果只是把简历搬到网页上,它并没有解决我的问题。

我真正想要的是一个可以持续生长的系统:

  • 首页负责让别人快速理解“我是谁、我关注什么、我适合做什么”。
  • 经历页面负责展开项目背景、技术栈、方法和结果。
  • 论文页面负责记录研究问题和学术产出。
  • 博客页面负责保存更过程化的内容:我怎么搭建、怎么调试、怎么思考。
  • 中英文页面负责面向不同读者,而不是只做一次性的翻译。

这也是为什么我没有把它做成一个静态海报,而是选择了更工程化的方式:用 Next.js 组织页面,用数据文件管理内容,用 MDX 写博客,用 Tailwind CSS 控制视觉和响应式布局,最后通过 GitHub Pages 做静态部署。

2. 技术选型:为什么是 Next.js + MDX + Tailwind#

这个网站目前看起来像一个展示页,但它的底层更像一个小型内容系统。

Next.js:把页面拆成可维护的模块#

Next.js 的好处是,它既能写 React 组件,又能做静态站点导出。对于个人网站来说,这一点很合适:页面不需要复杂后端,但需要清晰的路由、组件复用和构建流程。

我把页面拆成了几个层次:

  • src/app/:负责路由结构,比如首页、博客、经历、论文等页面。
  • src/features/:负责具体页面的组合逻辑,比如 home-page.tsxblog-post-page.tsx
  • src/components/:负责可复用组件,比如站点外壳、章节标题、经历卡片、论文条目。
  • src/data/:负责结构化内容,比如个人信息、项目经历、论文列表、兴趣爱好。
  • content/blog/:负责用 MDX 存放博客文章。

这样做的好处是,当我想改某段经历,不需要去 JSX 里到处找文案;当我想改页面样式,也不用动数据源。内容和呈现被拆开,网站就更容易长期维护。

MDX:让博客既像文章,也像代码#

技术博客最怕变成纯文本存档。纯 Markdown 足够写文章,但如果以后想插入组件、代码演示、实验结果卡片,MDX 会更灵活。

这篇文章本身就是一个 .mdx 文件。它顶部有一段 frontmatter:

title:
  zh: "第一篇技术博客:用 Codex vibe coding 一个个人网站"
  en: "First Technical Blog: Vibe Coding a Personal Site with Codex"
date: "2026-06-26"
tags:
  - Personal Site
  - Codex
  - Next.js

这些元信息会被 gray-matter 解析出来,用于博客列表、文章标题、日期和标签展示。正文则交给 next-mdx-remote 渲染。这样一篇文章既是内容,也是可被程序读取和组织的数据。

Tailwind CSS:快速试错,也能保持统一#

这个网站的视觉不是一次性设计出来的,而是在多轮修改中逐渐收敛的。比如首页主视觉、个人形象模块、卡片间距、移动端导航,都经历过调整。

Tailwind 的优势在于反馈很快:我可以直接在组件里调整 px-5py-20grid-cols-5bg-cloud/86 这样的 class,然后立刻看到布局变化。对于 vibe coding 来说,这种短反馈链很重要。

但 Tailwind 不是随便堆 class。真正需要注意的是统一性:

  • 常用颜色放进 tailwind.config.ts,比如 inkpapercloudcoralteal
  • 常用字体通过 CSS 变量管理。
  • 卡片、边框、背景透明度保持相近节奏。
  • 桌面端和移动端分别控制布局密度,避免手机端拥挤。

3. 和 Codex 一起 vibe coding 是什么感觉#

所谓 vibe coding,并不是“完全不管代码,让 AI 自动生成一个网站”。更准确地说,它像是一种快速对话式开发:我描述方向,Codex 给出实现;我指出不满意的地方,它继续修改;我再根据页面观感调整优先级。

这个过程中,最重要的不是一次生成完美代码,而是把模糊的审美和产品判断逐渐变成具体改动。

例如:

  • 我觉得简历信息太单薄,于是补充真实经历和项目内容。
  • 我觉得首页需要更像“个人形象”,于是加入照片展示模块。
  • 我不满意自动生成的头像,就改用自己指定的 me.png
  • 我觉得“最新写作”和“兴趣爱好”挤在一起,就把它们拆成两个独立模块。
  • 我尝试过把背景图元素融入全站,但发现影响整体观感,又把它撤掉。

这些修改都不是单纯的代码问题,而是“这个网站应该如何表达我”的问题。Codex 的价值在于,它可以快速把这些判断落到文件、组件、样式和构建结果里。

4. 网站里的几个具体编程知识点#

4.1 静态站点导出#

这个网站使用的是静态导出模式,适合部署到 GitHub Pages。静态站点的好处是部署简单、访问稳定、没有服务器维护成本。

核心思路是:构建时把页面预渲染成 HTML、CSS 和 JavaScript 资源,部署时只需要托管这些静态文件。

这要求页面尽量避免依赖运行时后端逻辑。博客、经历、论文这些内容都在构建阶段从本地文件或数据文件读取,因此很适合静态化。

4.2 国际化路由#

网站支持中文和英文页面。它不是简单地在一个页面里切换文字,而是通过 locale 路由组织内容,例如 /zh/en

内容层面,我使用类似这样的结构:

name: {
  zh: '徐天乐',
  en: 'Tianle Xu',
}

渲染时通过 text(value, locale) 选择对应语言。这样做有两个好处:一是不会把中英文写散;二是未来新增页面时,可以复用同一套本地化逻辑。

4.3 数据驱动页面#

首页的技能、经历、论文、兴趣、个人照片,并不是硬编码在页面里的,而是从 src/data/ 里的结构化数据读取。

例如个人形象模块本质上是一个数组:每张照片有 imagetitlecaption。页面只负责遍历它们:

{profile.visualGallery.map((photo) => (
  <figure key={photo.image}>...</figure>
))}

这就是数据驱动页面的好处:新增一张照片或改一句描述,不需要重写布局。

4.4 响应式布局#

个人网站不能只在电脑上好看。很多 HR 或同学可能会直接在手机上打开链接,所以移动端体验也很重要。

响应式布局里比较关键的点包括:

  • 首页 hero 在桌面端左右分栏,移动端自然上下堆叠。
  • 导航在桌面端显示横向菜单,在移动端变成可横向滚动的按钮组。
  • 个人形象模块在桌面端压缩成两排展示,移动端自动变成更适合阅读的列数。
  • 图片使用 object-coverobject-contain 控制裁切方式,避免重要内容被截掉。

这些细节看起来很小,但它们决定了网站是不是一个真正可访问、可分享的作品。

4.5 构建验证#

每次改完,我都会跑两个检查:

npm run type-check
npm run build

前者用 TypeScript 检查类型问题,后者验证 Next.js 能否完成生产构建。对于个人网站这种展示型项目,构建通过并不代表内容完美,但至少说明页面、路由、MDX、图片引用没有明显断裂。

5. 个人网站不是终点,而是一个持续更新的接口#

做完第一版之后,我对个人网站的理解变了。

它不是一个“上线之后就结束”的项目,而是一个持续更新的接口:别人通过它理解我,我也通过它整理自己。

简历负责回答“你做过什么”;个人网站可以继续回答:

  • 你为什么做这些事?
  • 你怎么思考问题?
  • 你怎么把想法变成系统?
  • 你如何复盘失败和迭代方案?
  • 你在技术之外是什么样的人?

所以这篇博客不仅是在记录一个网站的搭建过程,也是在给后面的内容开一个入口。以后我希望这里能继续出现项目复盘、论文阅读、推理系统调试、多模态模型实践、医学影像算法记录,以及一些不那么正式但真实的技术思考。

如果简历是一页纸,那么这个网站就是一个可以不断展开的版本。

这就是我从个人主页开始写第一篇技术博客的原因。