Preview
The preview feature lets you open a rendered version of the site — built from the current merge request's CI artifacts — in a separate browser tab. It works entirely in the browser: no extra server is involved.
The preview belongs to a draft. It reads the artifact of the merge request's pipeline, so there is nothing to preview while you are on the published branch with no draft open — the pipeline there may well have built an artifact, but the published state is what deployment is for.
How it works
When a CI pipeline on the merge request succeeds and produces a build artifact, a Vorschau (Preview) button appears in the branch / merge request panel. Clicking it:
- Downloads the artifact ZIP from GitLab.
- Extracts and caches the built files in the browser using the Cache API.
- Opens a new tab on the preview origin (see below), which registers a service worker and navigates to the site's entry page.
All preview traffic is intercepted by the service worker and served from the in-browser cache. Apart from the preview origin's own handful of files, nothing is fetched from a server — the site being previewed never leaves the browser.
Preview origin
The preview runs on a separate origin from the editor itself. This matters for two reasons:
- Isolation. The service worker can claim scope
/on its own origin without affecting the editor's own routes or session. - Security. The previewed site runs scripts of its own. A separate origin keeps them away from the editor's session storage, where the GitLab token lives.
Without further configuration the preview origin is derived from the editor's hostname by prefixing a preview. label — http://localhost:5174 becomes http://preview.localhost:5174.
Set VITE_PREVIEW_ORIGIN at build time to use a different hostname:
VITE_PREVIEW_ORIGIN=https://preview-editor.cratly.io bun run build:editorOr in .env.local for local development:
VITE_PREVIEW_ORIGIN=http://preview.localhost:5174The hosted editor does exactly that and runs its preview on preview-editor.cratly.io — a sibling hostname rather than a subdomain of the editor. The reason is the hosting: s3-http derives the storage location from the hostname by stripping its first label, so preview.editor.cratly.io would resolve to a bucket named editor.cratly.io, while preview-editor.cratly.io lands in the existing cratly.io bucket next to the editor itself. Nothing in the editor requires the preview to be a subdomain: the artifact is handed over with postMessage, which works across origins.
Keep the naming pattern
The installer accepts the artifact only from the editor origin, which it derives from its own hostname by removing a leading preview. or preview- label. A preview hostname that follows neither pattern cannot determine which origin to trust and refuses the payload with an error.
Deployment requirements
The preview origin does not host the previewed site — those files only ever exist in the browser's cache. It serves four files of its own:
| File | Why |
|---|---|
preview-installer.html | The page the editor opens; receives the artifact and fills the cache |
preview-installer.js | Its script, kept in a file so the origin can run under script-src 'self' |
preview-sw.js | The service worker that answers every other request from the cache |
index.html | Shown when no preview is loaded — and required by s3-http, which associates a hostname with stored content only if an index.html is present |
These are the only paths the service worker passes through to the network. Everything else on that origin is answered from the cache, and a cache miss becomes a synthesized 404 — there is no backend to fall through to.
Deploying them takes three things:
- A DNS record for the preview hostname, pointing at the same server as the editor.
- A route for that hostname on the reverse proxy in front of the storage, so it terminates TLS and reaches the same backend as the editor. Caddy provisions the certificate automatically once the hostname is part of its site block.
- The four files under the key prefix the hostname resolves to — for
preview-editor.cratly.iothat ispreview-editor/in thecratly.iobucket. The editor's own deploy does not reach that prefix; its pipeline mirrors them separately whenS3_PREVIEW_PREFIXis set.
Until all three exist, the editor's start-up probe for the preview origin fails and the preview button stays hidden — deliberately, but silently.
Local development
*.localhost subdomains resolve to the loopback address in all modern browsers, so preview.localhost:5174 works out of the box with no DNS changes. The Vite dev server is configured to bind to 127.0.0.1, which responds to both localhost and preview.localhost.
Limitations
- The preview reflects the latest successful pipeline artifact for the current merge request. Uncommitted local edits are not included.
- Browser security policies must allow popups from the editor origin (the preview opens in a new tab). If the browser blocks it, a notification is shown.
- Sites that require server-side logic (redirects, authentication, API proxying) will not work in the preview — only the static build output is served.