想象一下:世界杯球衣投票站,700个页面、86篇博客、实时排行榜,全部靠静态HTML+一个数据库表跑起来。作者是个16岁英国高中生,课余时间搭的,服务器成本几乎为零。
这不是又一个"我做了个网站"的流水账。作者把真实的技术选型、踩过的坑、省下的钱,全摊开了讲。对于想快速上线又不愿被复杂架构绑架的开发者,这份笔记比大多数架构大会的演讲都实在。
静态导出:为什么放弃服务端渲染
作者选了Next.js 14.2.5的静态导出模式(output: 'export'),托管在Vercel。这个决定背后是三件事的权衡:
速度——每个路由预渲染成HTML,从边缘节点直接下发。成本——静态导出+Vercel免费 tier,服务器成本趋近于零。SEO——Google抓取的是真实HTML,不是水合后的单页应用外壳。
Next.js 14的静态导出比任何时候都干净。app/目录下的路由直接构建成out//index.html,路由、元数据API、图片组件全部可用。唯一的代价:没有运行时API路由。但作者发现,这根本不是问题。
700个静态页面就这么生成的:42个球队页面、25个三方对比页、86篇博客,加上各类索引页。没有CMS,没有无头后端,没有WordPress。就是CDN上的静态HTML,配一张Supabase表存实时投票。
投票系统的"作弊"设计
静态导出意味着"没有API路由",但作者想要的是全球实时对战投票:用户点一件球衣,票数记录,排行榜跨会话同步更新。
传统静态站方案是接第三方服务。作者选了Supabase,理由很具体:
匿名密钥可以安全地发到客户端——行级安全(Row-Level Security,行级安全)做真正的门禁。实时订阅让排行榜无需轮询就能更新。Postgres远程过程调用(RPC,远程过程调用)支持原子递增和聚合查询。
数据库表结构极简:
kit_id作主键,wins和losses默认0,last_voted记时间戳。投票函数用SQL写:赢方wins+1,输方losses+1,冲突时更新。行级安全策略只允许匿名角色执行函数,不能直接读写表——用户只能投票,看不到原始数据,也动不了别人的行。
客户端就一行:supabase.rpc('record_vote', { winner_id, loser_id })。完事。
构建时排行榜的 trick
排行榜页面在构建时拉数据:supabase.from('global_votes').select('*').order('wins', { ascending: false })。这是作者卡了一阵子才想通的点。
静态导出的构建是离线的,但Supabase调用需要环境变量。作者的做法:构建脚本里读SUPABASE_SERVICE_ROLE_KEY,只用于生成静态页面。运行时投票用匿名密钥,权限隔离得清清楚楚。
这样排行榜是"构建时快照",不是实时数据。但作者接受这个 trade-off:页面秒开,SEO友好,投票数据通过实时订阅单独更新。用户看到的排名是"截至上次构建"的,但点进具体对战时,实时票数会同步过来。
AI生图:Gemini 2.5 Flash 的性价比实验
网站需要大量球衣生活场景图——球员穿着新球衣在场边、在更衣室、在街头。传统做法是买图库或请摄影师,成本和时间都是硬伤。
作者用了Google的Gemini 2.5 Flash,图像到图像(image-to-image,图像到图像)模式。输入参考图,输出风格化场景。具体流程:
先用球队官方球衣图作底图,Prompt控制场景和氛围,批量生成不同角度的"生活照"。Sharp和Pillow做WebP转换,压缩体积。静态导出时直接嵌入,没有运行时生成成本。
作者没提具体生成成本,但强调了"zero budget"(零预算)的整体架构。Gemini Flash系列的设计目标就是快和便宜,图像到图像比纯文本到图像更可控——对于球衣这种需要保留logo、配色、赞助商标志的场景,可控性比创意发散更重要。
这套方案的隐性成本是人工筛选。AI生图会出废片,需要人过一遍挑能用的。但对于个人项目,时间换钱是划算的 trade-off。
TypeScript + Tailwind:没有意外的基础设施
技术栈的其余部分都是"不犯错"的选择。TypeScript全栈,类型安全在700个页面里省下的调试时间,作者没量化,但暗示了"没它不行"。
Tailwind CSS负责样式。作者没展开,但从"零CMS"的架构来看,Tailwind的utility-first适合手写HTML、没有设计系统约束的个人项目。没有组件库,没有主题配置,就是class字符串堆出来的界面。
这种"极简栈"的副作用是维护负担全在自己。没有CMS意味着改内容要改代码,发博客要新建MDX文件。但作者显然接受了:86篇博客全是静态文件,构建时变成HTML,没有数据库查询、没有缓存策略、没有后台登录。
什么坏了,什么意外好用
作者坦诚讲了几个痛点:
Next.js 14的静态导出在图片优化上有摩擦。next/image组件需要配置unoptimized模式,因为静态导出没有图片服务器。Sharp预转换WebP是必需的,否则体积爆炸。
Supabase的实时订阅在票数激增时有延迟。作者没给具体数字,但提到"需要接受最终一致性"——用户A投完票,用户B的屏幕不会立即跳,而是几百毫秒内同步。
意外好用的部分:Vercel的CI/CD完全免费,Git push自动构建,700个页面的导出在几分钟内完成。Postgres的RPC原子操作让投票逻辑无需应用层事务,代码极简。静态页面的缓存命中率几乎是100%,边缘节点直接命中,源站压力为零。
给同类项目的实操建议
如果你也想搭一个"内容重、交互轻"的站点,作者的路线可以抄:
先确认内容是否适合静态化。博客、产品目录、投票结果——这些读多写少、可以预渲染的场景,静态导出是甜点区。需要用户登录、实时协作、复杂权限的,别硬上。
Supabase的匿名密钥+行级安全模式,把"静态站需要后端"的门槛降到极低。一个表、一个函数、几行客户端代码,就能跑起全球实时投票。关键是把读写权限拆干净:构建时用服务密钥拉数据,运行时用匿名密钥只给最小权限。
AI生图选图像到图像而非文本到图像,对于需要保留品牌元素的场景更可控。Gemini Flash的速度适合批量生成,但预留人工筛选时间。
最后,接受 trade-off。构建时排行榜不是实时数据,静态博客没有后台,AI图需要人工挑。这些"不完美"换回来的是:零服务器成本、秒开页面、几乎为零的运维负担。
对于独立开发者和小团队,这笔账通常是赚的。
热门跟贴