diff --git a/.github/workflows/smoke.yaml b/.github/workflows/smoke.yaml
new file mode 100644
index 0000000000..fb0e71062a
--- /dev/null
+++ b/.github/workflows/smoke.yaml
@@ -0,0 +1,44 @@
+name: smoke
+
+on:
+  push:
+    branches: [ main ]
+  pull_request:
+  schedule: # midnight every day
+    - cron:  '0 0 * * *'
+  workflow_dispatch:
+
+jobs:
+  test:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [windows-latest, ubuntu-latest]
+    env:
+      BASE_REPO: ${{ github.repository }}
+      BRANCH: ${{ github.head_ref }}
+      PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
+      SHA: ${{ github.sha }}
+    # TODO: drop PR testing under Windows because it's too slow?
+    # if: github.event_name != 'pull_request' && matrix.os != 'windows-latest'
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-node@v4
+        with:
+          node-version-file: .nvmrc
+          cache: npm
+          cache-dependency-path: package.json
+      - run: tools/install-hugo.sh
+        shell: bash
+      - name: Make site (non-PR)
+        if: github.event_name != 'pull_request'
+        run: |
+          mkdir tmp && cd tmp && set -x
+          ../tools/make-site.sh -p $BASE_REPO -v $SHA
+        shell: bash
+      - name: Make site from PR
+        if: github.event_name == 'pull_request'
+        run: |
+          mkdir tmp && cd tmp && set -x
+          ../tools/make-site.sh -p $PR_REPO -v $BRANCH
+        shell: bash
diff --git a/.gitignore b/.gitignore
index f2a1c17703..b578b779ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 node_modules/
 package-lock.json
+/tmp
 
 # Hugo
 .hugo_build.lock
diff --git a/tools/install-hugo.sh b/tools/install-hugo.sh
new file mode 100755
index 0000000000..9544797a13
--- /dev/null
+++ b/tools/install-hugo.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Install the hugo-extended NPM package if not already present.
+
+set -e
+
+PKG_JSON=${1:-package.json}
+
+if ! npm ls hugo-extended; then
+  _HUGO_EXTENDED_PKG=`perl -ne 'print "$1\@$2" if /"(hugo-extended)":\s*"\D*(.+?)"/' $PKG_JSON`
+  (set -x && npm install --save-exact -D $_HUGO_EXTENDED_PKG --omit=optional)
+fi
diff --git a/tools/make-site.sh b/tools/make-site.sh
new file mode 100755
index 0000000000..c6911ad225
--- /dev/null
+++ b/tools/make-site.sh
@@ -0,0 +1,91 @@
+#!/bin/bash
+
+set -eo pipefail
+
+DEPS="autoprefixer postcss-cli"
+FORCE_DELETE=false
+: ${HUGO:=npx hugo}
+NPM_PKG_VERS=
+NPM_PKG="google/docsy"
+SITE_NAME="test-site"
+THEMESDIR=node_modules
+
+function _usage() {
+  echo
+  echo "Usage: `basename $0` [options]"
+  echo
+  echo "  Creates a Docsy-themed site under SITE_NAME using the Hugo new command."
+  echo "  Docsy is fetched as an NPM module from GitHub, unless the -l flag is used."
+  echo
+  echo "  -f            Force delete SITE_NAME if it exists before recreating it"
+  echo "  -h            Output this usage info"
+  echo "  -l PATH       Use Docsy at PATH rather than as an NPM package"
+  echo "  -n SITE_NAME  Name of directory to create for the Hugo generated site. Default: $SITE_NAME"
+  echo "  -p NPM_PKG    GitHub repo to fetch Docsy from as an NPM module"
+  echo "                Format: GITHUB_USER/DOCSY_REPO. Default: $NPM_PKG"
+  echo "  -v VERS       Docsy NPM package version. Default: ''."
+  echo "                Examples: semver:0.8.0, some-branch-name"
+  echo
+}
+
+function usage() {
+  local status=${1:-0}
+  _usage 1>&2
+  exit $status
+}
+
+# Process command line arguments
+while getopts "fhl:n:p:v:" opt; do
+  case $opt in
+    f)
+      FORCE_DELETE=true
+      ;;
+    h)
+      usage
+      ;;
+    l)
+      NPM_PKG=""
+      THEMESDIR="$OPTARG"
+      ;;
+    n)
+      SITE_NAME="$OPTARG"
+      ;;
+    p)
+      NPM_PKG="$OPTARG"
+      ;;
+    v)
+      NPM_PKG_VERS="#$OPTARG"
+      ;;
+  esac
+done
+
+# Create project directory, checking if it exists first
+if [ -e "$SITE_NAME" ]; then
+  if [ "$FORCE_DELETE" = true ]; then
+    echo "[INFO] Directory '$SITE_NAME' already exists. Deleting it as requested (-f)."
+    (set -x; rm -rf "$SITE_NAME")
+  else
+    echo "[ERROR] Directory '$SITE_NAME' already exists. Remove it or use -f to force delete."
+    exit 1
+  fi
+fi
+
+DOCSY_NPM_PKG=$NPM_PKG$NPM_PKG_VERS
+
+set -x
+
+# Setup site
+$HUGO new site --format yaml --quiet "$SITE_NAME"
+cd "$SITE_NAME"
+npm init -y > /dev/null
+npm install --save-dev $DOCSY_NPM_PKG $DEPS
+
+echo "theme: docsy" >> hugo.yaml
+echo "themesDir: $THEMESDIR" >> hugo.yaml
+
+# Generate site
+$HUGO
+
+set +x
+
+echo "[INFO] $SITE_NAME has been successfully created, set up, and built."