Bring Back the 90's Guestbook with JAMstack: How I Added Dynamic Comments to My Static 11ty Site
Reviving the classic guestbook for a static site using Netlify Forms and serverless functions, with lessons on distributed systems and race conditions.
ne of the first things I wanted to do when I began building my personal site and IndieWeb blog was to add a guestbook. There are services, particularly for NeoCities sites, but some have [shut down](https://www.123guestbook.com/news.php?id=closure). I wanted a solution that would work long-term without relying on third-party services.
An issue is that static sites inherently can't handle dynamic content without a backend of some sort. How do you accept user submissions on a site that doesn't run server code?
I decided that this would be easiest to tackle with Netlify forms. This guide is meant for others on the IndieWeb looking to build in similar functionality to their site!
You can visit my [guestbook page](/guestbook) to see it in action, or read on to learn how I built it.
## The Architecture
The solution I found consists of three interconnected components:
### 1. The Form (Frontend)
A simple HTML form that leverages Netlify Forms:
```html <form name="guestbook" method="POST" data-netlify="true" action="/guestbook-success" class="guestbook-form"> <div class="form-group"> <label for="name">Name *</label> <input type="text" id="name" name="name" required> </div> <div class="form-group"> <label for="message">Message *</label> <textarea id="message" name="message" rows="4" required></textarea> </div> <button type="submit" class="btn">Sign Guestbook</button> </form> ```
`data-netlify="true"` is an attribute that tells Netlify to intercept form submissions and store them, eliminating the need for a backend.
### 2. The Webhook (Serverless Function)
This way, when someone submits the form, Netlify triggers a webhook that rebuilds the site with the new entry:
```javascript // 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'); } }
Did you enjoy this article?
Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.