用 JAMstack 复刻 90 年代的访客留言本:我如何在静态 11ty 网站中添加动态评论
I’m happy to translate the article for you, but I’ll need the full text you’d like translated (everything after the source line). Please paste the content here, and I’ll provide a Simplified Chinese version while preserving the formatting, code blocks, URLs, and technical terms as requested.
架构:三个简单组成部分
解决方案由三个相互关联、协同工作的组件组成:
1. 表单(前端)
一个利用 Netlify Forms 的简易 HTML 表单:
<form name="guestbook" method="POST" data-netlify="true">
<p>
<label>Name *</label>
<input type="text" name="name" required />
</p>
<p>
<label>Message *</label>
<textarea name="message" required></textarea>
</p>
<p>
<button type="submit">Sign Guestbook</button>
</p>
</form>
这里的关键是 data‑netlify="true" —— 这个属性告诉 Netlify 拦截表单提交并存储,无需后端。
2. Webhook(无服务器函数)
当有人提交表单时,Netlify 会触发一个 webhook,使用新条目重新构建站点:
// netlify/functions/guestbook-webhook.js
const fetch = require('node-fetch');
exports.handler = async function (event, context) {
if (event.httpMethod !== 'POST') {
return { statusCode: 405, body: 'Method Not Allowed' };
}
try {
const payload = JSON.parse(event.body);
if (payload.type === 'submission' && payload.data?.name === 'guestbook') {
console.log('New guestbook submission received:', payload.data);
// Wait a bit before triggering rebuild
console.log('Waiting 5 seconds before triggering rebuild...');
await new Promise(resolve => setTimeout(resolve, 5000));
const buildHookUrl = process.env.NETLIFY_BUILD_HOOK_URL;
if (buildHookUrl) {
const response = await fetch(buildHookUrl, {
method: 'POST',
body: JSON.stringify({ trigger: 'guestbook_submission' }),
headers: { 'Content-Type': 'application/json' }
});
if (response.ok) {
console.log('Build triggered successfully');
}
}
}
return {
statusCode: 200,
body: JSON.stringify({ received: true })
};
} catch (error) {
console.error('Webhook error:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: 'Internal Server Error' })
};
}
};
3. 数据获取器(11ty 数据文件)
在构建阶段,11ty 会从 Netlify 的 API 获取所有提交:
// src/_data/guestbook.js
const fetch = require('node-fetch');
module.exports = async function () {
const siteId = process.env.NETLIFY_SITE_ID;
const token = process.env.NETLIFY_FORMS_ACCESS_TOKEN;
if (!token || !siteId) {
console.warn('No Netlify API credentials found. Using sample data.');
return getSampleEntries();
}
// Get form ID first
const formsUrl = `https://api.netlify.com/api/v1/sites/${siteId}/forms`;
const formsResponse = await fetch(formsUrl, {
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': 'curl/7.79.1'
}
});
const forms = await formsResponse.json();
const guestbookForm = forms.find(form => form.name === 'guestbook');
// Fetch submissions with retry logic
const url = `https://api.netlify.com/api/v1/sites/${siteId}/forms/${guestbookForm.id}/submissions`;
let response;
let retries = 3;
let delay = 2000;
while (retries > 0) {
const submissionsResponse = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': 'curl/7.79.1'
}
});
if (submissionsResponse.ok) {
response = await submissionsResponse.json();
break;
} else if (retries > 1) {
console.log(`Retrying in ${delay}ms... (${retries} attempts left)`);
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2;
retries--;
}
}
// Transform and return entries
return response.map(submission => ({
name: submission.data.name,
message: submission.data.message,
website: submission.data.website || '',
date: new Date(submission.created_at),
id: submission.id
}));
};
神秘现象:移动端提交消失
客人簿在笔记本电脑上运行完美,但在移动设备上提交时出现加载旋转图标,跳转到成功页面,却从未出现在现场页面。该条目在 Netlify 的表单仪表盘中可见,但在站点上没有显示。
根本原因是 分布式系统中的最终一致性。
教会我一课的竞争条件
- 用户提交表单 → Netlify 立即存储。
- Webhook 立即触发 → 网站重建开始。
- 网站查询 Netlify API 获取提交 → 太快了!
- API 返回旧数据(提交尚未被索引)。
- 网站在没有新条目的情况下重建。
桌面测试通常比较走运;移动网络则暴露了时间问题。
解决方案:耐心与重试
- 延迟重建 – 在调用构建钩子之前添加一个短暂、可配置的等待(例如 5 秒)。
- 重试获取提交 – 在 11ty 数据文件中实现指数退避重试。
这些更改可确保来自任何设备的提交可靠地显示在线上站点上。
为什么这很重要:IndieWeb 哲学
访客簿带有怀旧感,并体现了 IndieWeb 的 拥有你的内容 原则。与第三方评论系统不同,所有数据都存储在 Netlify 账户中,可以导出,并且不会被锁定在其他平台上。
Lessons Learned
- 静态并不等于静态: Serverless 函数为静态站点添加动态功能。
- 时机很重要: 分布式系统并非瞬时完成;需要考虑竞争条件。
- 在真实网络上测试: 桌面 Wi‑Fi 与移动 4G 不同。
- 简洁即强大: 三个小文件即可取代整个后端。
完整流程
- 用户填写表单 → Netlify 存储提交。
- Netlify 发送 webhook → 无服务器函数接收。
- 函数等待 5 秒 → 触发构建钩子。
- 11ty 构建站点 → 使用重试逻辑获取提交。
- 站点部署 → 新消息自动出现。
亲自尝试
想在你的 11ty 网站上添加留言簿吗?以下是你需要的:
- 一个已启用 Forms 的 Netlify 站点。
- 一个 构建钩子 URL(站点设置 → 构建与部署 → 构建钩子)。
- 一个具有
forms:read权限的 Netlify 个人访问令牌。 - 上文提到的三个代码文件。
环境变量
在 Netlify 中设置这些:
NETLIFY_FORMS_ACCESS_TOKENNETLIFY_SITE_IDNETLIFY_BUILD_HOOK_URL
就这样。无需数据库、服务器或维护——只有 JAMstack 的魔力。
本文最初发布在我的博客。关注我,获取更多 IndieWeb 前沿的故事!