Site Builder: Future SuperSpeed Site Updates

Site Builder: Future SuperSpeed Site Updates

Craig Nielsen

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

arch-diagram-builder

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:

  • set -e
    stops the script immediately if any command fails, preventing a broken build from being copied to the web root
  • npm run build
    requires
    output: 'export'
    in
    next.config.js
    so Next.js writes static files to
    /out
  • The web root is cleared before copying to avoid stale files from previous builds

next.config.js
requirement:

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

  1. Go to Settings → Webhooks in your Contentful space
  2. Click Add Webhook
  3. Configure as follows:
FieldValue
Name
Deploy siteA
URL
https://yourdomain.com/hooks/YOUR-SECRET-HERE/deploy-siteA
Method
POST
TriggersEntry — Publish
  1. 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

sequencediagram1

Adding a new site

  1. Create the site directory:
    mkdir /home/builder/sites/siteC
  2. Add
    rebuild.sh
    with the correct paths
  3. Add a new hook entry to
    /etc/webhook/hooks.json
  4. Reload webhook:
    sudo systemctl restart webhook
  5. Add a new
    location
    block to the Nginx config and reload Nginx
  6. Add a new webhook in Contentful pointing to the new URL