@tanglemedia/directus-extension-bundle-preview
v0.0.2
Published
A Directus extension bundle that adds a Build Preview module. The button triggers a Hugo build on an external build service and opens a preview page that redirects to the built site once ready. The shared secret stays server side in the bundled endpoint,
Readme
@tanglemedia/directus-extension-bundle-preview
Adds a Build Preview module to Directus. An editor clicks the button, a Hugo build starts on an external build service, and a preview tab opens that shows progress and redirects to the freshly built site when it's ready.
The bundle is deliberately split so the shared secret never reaches the browser:
| Entry | Type | Runs | Holds the secret |
|---|---|---|---|
| preview-build-module | module | browser | no |
| preview-build | endpoint | Directus server | yes |
The module has no credentials at all. It asks the endpoint for a preview URL,
opens it, and posts to the endpoint to start a build; only the endpoint attaches
PREVIEW_BUILD_SECRET when calling the build service.
Requirements
An HTTP build service exposing:
POST /trigger-build— acceptsAuthorization: Bearer <PREVIEW_BUILD_SECRET>GET /build-status— returns{status, step, error, branch, commit}GET /preview— loading page that polls status and redirects when ready
Environment variables
Set these on the Directus container.
| Variable | Required | Purpose |
|---|---|---|
| PREVIEW_BUILD_SERVICE_URL | yes | How Directus reaches the build service, e.g. http://preview:3001. Server side only. |
| PREVIEW_BUILD_SECRET | yes | Shared secret sent to /trigger-build. Never exposed to the browser. |
| PREVIEW_PUBLIC_URL | recommended | How the editor's browser reaches the build service, e.g. http://localhost:3001. Falls back to PREVIEW_BUILD_SERVICE_URL, which is wrong whenever that is an internal hostname. |
Two URLs are needed because a container hostname such as preview:3001
does not resolve from the editor's machine.
Routes
All require an authenticated Directus user; anonymous requests get 401.
| Method | Path | Returns |
|---|---|---|
| GET | /preview-build/config | {configured, previewUrl} — no secret |
| POST | /preview-build/trigger | {message, alreadyRunning, previewUrl}; 409 when a build is already running |
| GET | /preview-build/status | proxied build status |
One route is for the build service rather than the browser, and takes the shared
secret as X-Build-Secret instead of a user session:
| Method | Path | Returns |
|---|---|---|
| GET | /preview-build/branding | {name, color, logo} from directus_settings; logo is a data URI or null |
It exists because the preview page is served from the build service, which has
no database and whose Directus token cannot read directus_settings — so the
project name, colour and logo that make the page match the Publish page have to
be fetched from here. The secret travels in a custom header on purpose:
Directus authenticates Authorization: Bearer in middleware that runs before
any extension route, and PREVIEW_BUILD_SECRET is not a Directus token.
Install
pnpm install
pnpm --filter @tanglemedia/directus-extension-bundle-preview buildThen either publish and install it into the Directus image like the other packages in this monorepo, or mount the built package during development:
volumes:
- ../packages/directus-extension-bundle-preview:/directus/extensions/directus-extension-bundle-preview:roRestart Directus afterwards — extensions are only loaded at startup. Look for
Loaded extensions: directus-extension-bundle-preview in the logs.
Where the button appears
In the module bar, as Build Preview (rocket icon). If the environment variables above are missing, the module shows a warning notice instead of the button rather than failing on click.
Notes
The preview tab is opened synchronously in the click handler, before the trigger
request is awaited — browsers block window.open once an await has intervened.
The preview page polls, so arriving slightly before the build starts is harmless.
A 409 from the build service is treated as "a build is already running" and
surfaced as an informational toast, not an error.
