Portfolio — 从设计到上线的实现思路
一个设计师视角的 Portfolio 系统:为什么用 Notion 当 CMS,内容如何流动,视觉决定从何而来。
作为设计师,个人网站往往是最难做的一个"项目"——不是因为技术难度,而是因为它要同时承载你对自己工作的理解、对展示方式的判断,以及一套能持续维护下去的工作流。
这篇文章记录的是 diw.craft 这个站点当前的实现思路,从内容管理、卡片布局、视觉决定,到发布工作流,尽量用设计师能理解的语言来说清楚。
一、为什么不用现成工具
市面上给设计师用的 Portfolio 工具不少——Behance、Readymag、Cargo——但我最终选择自建,原因很具体:我想要对展示结构有完全的控制。
商业工具的模版逻辑是"让大多数人的作品看起来不错",但这也意味着它很难呈现不同类型内容之间的关系——项目案例、设计随笔、实验性探索、照片集,这些内容应该能放在同一个框架下,形成有结构的整体,而不是各自孤立的页面。
自建的代价是需要维护。为了让这个代价可接受,我把内容管理交给了 Notion。
二、Notion 作为内容管理系统
我日常用 Notion 管理项目、写文档、做规划。把它作为内容数据库是很自然的延伸——在哪里思考,就在哪里写作。
站点所有内容存放在一个 Notion 数据库里,每一条记录对应一个展示单元,配置字段如下:
- Title:标题
- Slug:URL 路径(比如
yami-design-system) - Description:摘要,显示在卡片 hover 时
- Category:分类——project / writing / photo / experiment
- Style:卡片样式——text-only / image-only / image-text
- Card Size:展示尺寸——1x1 标准 / 2x1 宽卡
- Cover Image:封面图
- Tags:标签(可多选)
- Status:发布状态——draft / published / protected / archived
- Sort Order:手动排序权重 在 Notion 里可以用多维筛选视图管理不同状态、不同类型的内容,发布不需要任何代码操作。
三、内容如何流到网站
内容从 Notion 到网站,走的是显式同步路径,而不是实时拉取:
npm run sync
这个命令执行 scripts/sync-notion.ts,做三件事:
- 从 Notion 拉取所有 published + protected 状态的页面
- 下载封面图,用 Sharp 压缩成 WebP,存到
public/content-images/{slug}/ - 生成本地缓存:
content/metadata.json(所有条目元数据)+content/items/{slug}/body.md(正文) 同步是增量的——它会比对每个页面的last_edited_time,跳过没有改动的内容,所以即使内容库很大,sync 也很快。
同步完成后,提交到 Git,Vercel 自动拉取并部署,整个过程 2-3 分钟。
四、卡片布局:展示权重的视觉语言
首页是一个 Bento 网格,内容以卡片形式排列。我用三种卡片样式对应不同的内容性质:
- text-only:纯文字卡,适合写作和思考类内容,排版即内容
- image-only:纯图片卡,适合照片集,让视觉直接说话
- image-text:图文结合,适合项目案例,封面图提供视觉钩子,文字做补充 卡片尺寸有两种——1x1 标准和 2x1 宽卡。宽卡能传递"这条内容更重要"的信号,但滥用会破坏节奏感。算法上我设了一个约束:宽卡最多占总数的 25%,且不允许连续出现两张宽卡。
图片封面有一个细节:sync 时会分析封面图底部区域的亮度,生成一个 blur placeholder(低分辨率模糊预览),并记录底部应该用深色还是浅色 overlay——这样图片加载前,卡片就已经有正确的色调基底,避免文字和图片抢色的问题。
五、视觉决定
字体:中英文混排用了三套字体——Bitter 作为英文衬线(Variable 字重,能在标题和正文之间灵活切换)、Noto Serif SC 覆盖中文(多字重支持排版层级)、Caveat 作为签名手写字体。英文衬线 + 中文宋体的组合,在保持阅读舒适度的同时,给站点一种有质感的气质。
深色模式:通过 HTML 根元素的 .dark class 切换,用户偏好持久化到 localStorage。没有跟随系统自动切换——我认为这类站点的深浅色状态应该是用户的主动选择。
背景:全局有一个轻量的 BackgroundFx 组件,提供微妙的视觉纹理,避免纯白/纯黑背景的单调感,同时不喧宾夺主。
六、搜索:降低翻找成本
内容多了之后,依赖分类导航很低效。站点集成了 MiniSearch 做本地全文搜索,按 Cmd+K 触发。
搜索索引在 sync 时生成,静态 JSON 文件部署后由浏览器直接加载,不走服务器,延迟极低。搜索对象包括标题、摘要、标签和正文前 100 字,覆盖绝大多数检索意图。
SearchDialog 组件用 dynamic import 延迟加载,不影响首屏性能。
七、当前的 tradeoff
显式同步 vs 实时发布:sync 是手动触发的,这意味着在 Notion 里改完内容后,需要本地跑一次命令再提交。好处是发布过程透明可控,坏处是多了一个步骤。如果要优化,可以做一个 GitHub Action 在定时或 webhook 触发时自动 sync。
内容格式锁定在 Markdown:Notion 正文经过 blocks → Markdown 的转换,格式基本保留,但 Notion 里的某些富文本特性(比如彩色文字、toggle block)会在转换中丢失。对我来说这是可接受的取舍——Markdown 的结构性更利于渲染和维护。
图片托管在本地:封面图下载后存在项目仓库,随代码一起部署到 Vercel CDN。这避免了 Notion 图片 URL 过期的问题,但会增加仓库体积,大量高质量图片后续可能需要迁移到专用的图片服务。