
Site Builder: Future SuperSpeed Site Updates

Craig Nielsen
February 23, 2026
Builder — Automated Site Deploy System
Builder is a three-part system that automates the build and deployment of static Next.js sites when content is published in a CRM. The parts are: site structure and build scripts, a webhook listener, and CRM(Contentful) triggers.
Table of Contents
Diagram: High level arch diagram
Part 1: Site Structure and Build Scripts
Each site lives in its own directory under
/home/builder/sites/:
/home/builder/sites/ ├── siteA/ │ ├── rebuild.sh │ └── (Next.js project files) ├── siteB/ │ ├── rebuild.sh │ └── (Next.js project files)
Each site has a
rebuild.sh script that does two things: runs the Next.js build, then copies the output to the Nginx web root.
Example —
:/home/builder/sites/siteA/rebuild.sh
#!/bin/bash set -e SITE_DIR="/home/builder/sites/siteA" WEB_ROOT="/var/www/siteA" echo "[$(date)] Starting build for siteA..." cd "$SITE_DIR" npm run build echo "[$(date)] Build complete. Copying output..." rm -rf "$WEB_ROOT"/* cp -r "$SITE_DIR/out/." "$WEB_ROOT/" echo "[$(date)] Deploy complete."
Key points:
stops the script immediately if any command fails, preventing a broken build from being copied to the web rootset -e
requiresnpm run build
inoutput: 'export'
so Next.js writes static files tonext.config.js/out- The web root is cleared before copying to avoid stale files from previous builds
requirement:next.config.js
const nextConfig = { output: 'export', // ... rest of config };
Part 2: Webhook Listener
The webhook listener receives HTTP requests from Nginx and triggers the appropriate site's
rebuild.sh.
Installing webhook
sudo apt install webhook
hooks.json
Each site gets its own hook entry in
/etc/webhook/hooks.json:
[ { "id": "deploy-siteA", "execute-command": "/home/builder/sites/siteA/rebuild.sh", "command-working-directory": "/home/builder/sites/siteA" }, { "id": "deploy-siteB", "execute-command": "/home/builder/sites/siteB/rebuild.sh", "command-working-directory": "/home/builder/sites/siteB" } ]
Adding a new site means adding a new object to this array and creating the corresponding
rebuild.sh.
Running webhook as a service
Create a systemd service so webhook starts on boot and restarts on failure:
:/etc/systemd/system/webhook.service
[Unit] Description=Webhook listener After=network.target [Service] User=builder ExecStart=/usr/bin/webhook -hooks /etc/webhook/hooks.json -port 9000 -verbose Restart=always [Install] WantedBy=multi-user.target
Enable and start it:
sudo systemctl enable webhook sudo systemctl start webhook sudo systemctl status webhook
Nginx configuration
Nginx exposes a single secret base path and routes each site's deploy to its corresponding webhook hook ID. The secret in the URL means the endpoints are not guessable:
location /hooks/YOUR-SECRET-HERE/deploy-siteA { proxy_pass http://127.0.0.1:9000/hooks/deploy-siteA; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /hooks/YOUR-SECRET-HERE/deploy-siteB { proxy_pass http://127.0.0.1:9000/hooks/deploy-siteB; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }
The full public URLs become:
https://yourdomain.com/hooks/YOUR-SECRET-HERE/deploy-siteA https://yourdomain.com/hooks/YOUR-SECRET-HERE/deploy-siteB
These are the URLs that Contentful will call in Part 3.
Testing a hook manually
curl -X POST https://yourdomain.com/hooks/YOUR-SECRET-HERE/deploy-siteA
Check the webhook logs to confirm it fired:
sudo journalctl -u webhook -f
Part 3: Contentful Webhook Triggers
Contentful can call a URL whenever content is published. Each site gets its own webhook in Contentful pointing to its deploy URL.
Setting up a webhook in Contentful
- Go to Settings → Webhooks in your Contentful space
- Click Add Webhook
- Configure as follows:
| Field | Value |
|---|---|
| Name | |
| URL | |
| Method | |
| Triggers | Entry — Publish |
- Repeat for each site, pointing each webhook at its own deploy URL
Trigger filter (recommended)
If multiple sites share the same Contentful space, you can add a filter so siteA only rebuilds when its own content is published, not every time anything is published in the space. Under Filters in the Contentful webhook config, add a condition such as:
Content type equals siteAPageType
This prevents unnecessary rebuilds when unrelated content is updated.
Full flow when an editor clicks Publish

Adding a new site
- Create the site directory:
mkdir /home/builder/sites/siteC - Add
with the correct pathsrebuild.sh - Add a new hook entry to
/etc/webhook/hooks.json - Reload webhook:
sudo systemctl restart webhook - Add a new
block to the Nginx config and reload Nginxlocation - Add a new webhook in Contentful pointing to the new URL