Deployment
Gustwind sites can be deployed to any static host. The most difficult part is building the site and you can either push this problem to a CI provider or build at the host itself. The preferred deployment path is now the Node.js build.
The general approach is to first set up a build script to install Node.js dependencies and run Gustwind like this:
build.sh
#!/usr/bin/env bash
npm ci
npm run build:release
Also make the file executable with chmod +x and point your CI environment to use the file when building. Usually there's a field for that in their user interface somewhere.
The benefit of using a simple script like this is that it lets you control versions of both Node.js and Gustwind. Gustwind is still a moving target, so pinning dependency versions in your lockfile is recommended. npm run build remains useful for a faster local build loop, but deployments should use npm run build:release so Pagefind search assets are generated.
Netlify🔗
To configure Netlify, set up a file as follows:
netlify.toml
[functions]
included_files = ["node_modules/sharp/**/*", "node_modules/@img/sharp-linux-x64/**/*", "netlify/functions/layouts/**", "site/meta.json"]
[build]
base = ""
publish = "build"
command = "npm ci && DEBUG=1 node ./gustwind-node/cli.ts --build --output ./build"
The related build command would install dependencies and run the Node build. Alternatively you can leverage build.sh as above.
Vercel🔗
For Vercel, point to build.sh through their user interface.
Cloudflare Workers🔗
For Worker deployments, Gustwind now exposes a Cloudflare adapter through gustwind/workers/cloudflare. It wraps the edge-compatible render path into a Worker fetch() handler and can optionally serve built assets from the standard ASSETS binding.
import { createCloudflareWorker } from "gustwind/workers/cloudflare";
import { plugin as metaPlugin } from "gustwind/plugins/meta";
import { plugin as scriptPlugin } from "gustwind/plugins/script";
import { plugin as edgeRendererPlugin } from "gustwind/plugins/htmlisp-edge-renderer";
import { plugin as edgeRouterPlugin } from "gustwind/routers/edge-router";
import scriptAssets from "./build/.gustwind/script-assets.json" with { type: "json" };
export default createCloudflareWorker({
initialPlugins: [
[edgeRouterPlugin, {
routes: {
"/": {
layout: "Home",
context: { headline: "Hello from Gustwind" },
},
},
}],
[metaPlugin, { meta: { title: "Cloudflare Worker" } }],
[scriptPlugin, { scriptAssets }],
[edgeRendererPlugin, {
components: {
Home: "<html><body><h1 &children=\"meta.title\"></h1><p &children=\"context.headline\"></p></body></html>",
},
componentUtilities: {},
globalUtilities: { init: () => ({}) },
}],
],
});
If you are also publishing a static build/ directory, keep Wrangler assets enabled so CSS, JS, images, and other emitted files are served directly by the ASSETS binding while page requests fall through to Gustwind rendering.
Dynamic edge-rendered pages can use a stable stylesheet URL by setting
stableCssPath on the Tailwind plugin, for example
"/assets/site.css". Static pages still receive the hashed production
stylesheet link, and Gustwind writes the stable copy for custom edge handlers.
The script plugin writes .gustwind/script-assets.json during the Node build. Pass that manifest back to the Worker script plugin as scriptAssets so route-level scripts entries resolve to the same hashed module URLs that the static build uses.
For custom Worker bundles, generateCloudflareManifest(...) from the Node API
can generate a module containing explicit .html and .server.ts component
imports. This keeps Wrangler/esbuild from guessing extensionless imports.
If you need a custom Pages Function or Worker handler, import initRender from
gustwind/workers/cloudflare instead of the root gustwind entry. The
Cloudflare entry only depends on edge-safe runtime code, so it avoids pulling
Node build tooling into the Worker bundle.
import { initRender } from "gustwind/workers/cloudflare";
const render = await initRender(initialPlugins);
export default {
async fetch(request) {
const { markup } = await render(new URL(request.url).pathname, {});
return new Response(markup, {
headers: { "content-type": "text/html; charset=UTF-8" },
});
},
};
GitHub Pages🔗
For GitHub Pages, it's a good idea to follow Pagic documentation. You can point to the build script within GitHub YAML configuration.