Deploy Previews
Most hosting services build the site again whenever a commit lands, and many build a separate copy for each pull request. Sveltia CMS asks the Git backend where those builds ended up, so an editor can open the page they just worked on without hunting for the URL — and can tell whether the build has finished yet.
This works in both production workflows, with a different meaning in each:
- In Editorial Workflow, an unpublished entry links to the deploy preview built for its pull request, so editors can see a draft before it goes live.
- In Simple Workflow, where changes are committed straight to the configured branch, an entry links to the live site, and the CMS reports whether the build for the latest change has finished.
Requirements
- A GitHub or GitLab backend.
- A CI/CD provider connected to the repository. See CI/CD Integration.
- A
preview_pathon each collection you want entry links for. Without it, an unpublished entry links to the root of its deploy preview, from where the page can be found manually, and a published entry has no link.
Date tags need a date field
When preview_path uses {{year}}, {{month}} or another date tag, the CMS reads it from the first DateTime field of the collection, or of the file in a file collection — or the top-level DateTime field named by preview_path_date_field. If no such field exists, a configuration warning says so, because the preview link would otherwise go missing with no explanation. A field that exists but is left empty on an entry has the same effect, and can only be spotted on the entry itself.
Future Plans
Support for the Gitea/Forgejo backend may be added in the future. Until then, entries on that backend link to the live site as before, with no build state reported.
Configuration
There’s nothing to turn on. As long as the requirements above are met, deploy preview links appear on their own.
The options below shape what the links do:
| Option | Where | What it does |
|---|---|---|
preview_path | Entry collection, or each file of a file collection | Path template appended to the site or preview URL. Without it, only the root of a deploy preview is linked |
preview_path_date_field | Entry collection, or each file of a file collection | Which top-level DateTime field the {{year}}, {{month}} and similar tags read. Default: the first DateTime field |
site_url | Top level | Base URL of the live site |
show_preview_links | Top level | Set to false to hide every preview link. Default: true |
preview_context | backend | Names the exact commit status or environment that carries the preview URL |
If site_url isn’t set, the CMS falls back to the URL reported by the production deployment, so links can still work without it. When site_url is set, it always wins.
How It Works
Every entry belongs to a commit: the head of its pull request in Editorial Workflow, or the head of the configured branch otherwise. The CMS asks the backend what the CI/CD provider reported for that commit, takes the URL from the answer, and appends the collection’s preview_path.
So an entry whose preview_path is /blog/{{slug}} links to https://example.com/blog/my-post on the live site, and to https://cms-posts-hello.example.pages.dev/blog/my-post on a deploy preview built by Cloudflare Pages.
Where the URL Comes From
Providers report a deployment in one of three ways, and Sveltia CMS reads all of them:
| Source | Known to use it |
|---|---|
| Deployments (GitHub) and environments (GitLab) | Vercel, GitHub Pages, GitLab Review Apps |
| Commit statuses | Vercel, and CI services that post a build status |
| Check runs (GitHub only) | Cloudflare Pages, AWS Amplify |
All three are read whichever provider is used, so one that isn’t listed still works as long as it reports through any of them. Equally, a provider that reports nowhere the Git host can see — several publish only to their own dashboard, or to a pull request comment — can’t be detected at all. If you’re unsure which applies, open a recent commit on your Git host and see whether anything is attached to it.
When more than one reports on the same commit, the CMS first sets aside the ones that don’t look like a deployment: a commit status or check run whose name doesn’t mention a known provider or a word like “deploy”, “preview” or “pages” is most likely a test suite or a linter. GitLab posts a commit status for every CI job, and CI services like CircleCI do the same on GitHub. Such a candidate is still reported if nothing else is there, so a provider these rules have never heard of still works, but a finished test suite can’t stand in for a deploy preview that’s still building. Among the rest, the CMS prefers a finished build with a page to open, then the source whose URL is most reliable — a deployment’s environment URL is always the site, while a commit status URL is sometimes a build log. It then prefers an environment whose name matches what it’s looking for, so a production environment isn’t passed over for a preview one on the live branch. Ties go to the newest.
An address is only taken from a finished build. Several providers hand out a placeholder while they work — Cloudflare Pages reports its own dashboard until the build succeeds, then replaces it with the preview address — so nothing is offered until there’s a page behind it. A URL leading back to the Git host is ignored for the same reason: that’s a job log, which every GitLab CI job reports.
A build that was canceled or skipped is ignored, even when the provider reports it as successful. This happens in a monorepo, where a site that the commit didn’t touch still reports a result, and its URL leads to the build log rather than a page.
How check runs are read
A check run’s own link usually leads to a build log rather than a site, so it takes more care than the other two sources.
Every run reports its build state, so a provider these rules have never heard of still reports that a build on the commit is running or has failed. Offering an address is another matter: only a run whose name suggests a deployment does that, and one that doesn’t is ranked below every one that does — so a green test suite can’t stand in for a build that hasn’t finished. For a run that does look like a deployment, the address is taken in this order:
- A URL published in the run’s output. Cloudflare Pages writes a table of preview URLs into its check summary while linking the check itself at the Cloudflare dashboard, so that table is read and dashboard links in it are passed over.
- The run’s own link, but only if the name says “preview”. AWS Amplify reports “AWS Amplify Console Web Preview” and links straight to the site.
- Neither. The build state is still reported — so the editor sees that a build is running or has failed — but no address is offered.
If your provider’s naming defeats this, name the check explicitly with preview_context.
Build States
What the control does depends on whether a preview is still on its way.
While a preview is being built for an unpublished entry, the button reads Checking for Preview, is disabled, and shows a turning icon. The live site isn’t where that entry can be seen — it holds the published version, or nothing at all when the entry is new — so offering that link would send the editor somewhere else.
Otherwise the button is a link, reading View Preview when it points at a deploy preview and View on Live Site when it points at the live site. A build that’s still running or has failed is described on the control for screen readers, and shown as a badge on the Editorial Workflow page:
| Build state | Badge on the workflow card |
|---|---|
| Building | Building… |
| Failed | Build Failed |
| Being queried, finished, or nothing reported | none |
A published entry is never made to wait, whatever its build is doing: the live site genuinely holds that page, so the link stays available.
When no CI/CD provider reports anything — because none is connected, or because it reports in a way the backend doesn’t expose — nothing is lost. The same live-site link as before is shown.
While a build is running, the CMS checks again every 5 seconds, so a preview is offered as soon as it exists. On GitHub each check is a single request; on GitLab it costs one shared call plus one per commit being watched. It gives up after 10 minutes: the icon stops turning, the link comes back, and a Check for Preview action appears in the entry editor’s options menu. Reopening the entry starts a fresh round of checks, so a build longer than that isn’t lost.
Checking Whether the Page Is Live
A finished build isn’t quite the same as a page that can be opened: a CDN may not have caught up, and a brand-new entry can 404 for a moment after publishing. Where it can, the CMS requests the page itself and keeps treating the build as unfinished until it answers.
This check only runs when the page is on the same origin as the CMS — the usual case where the CMS is served from /admin on the site it edits. A browser can’t read a cross-origin response without permission from that server, and no major static host grants it, so the request is skipped rather than sent to learn nothing. Deploy previews are almost always on another origin, so their state comes from the provider alone.
Where Links Appear
- In the entry editor toolbar, for the default locale.
- In each locale pane’s options menu, so a multilingual entry links to the right translation. See Managing Preview Paths with I18n.
- On the cards of the Editorial Workflow page, as an icon button, with a badge when a build is running or has failed.
Specifying a Status Context
If several providers report on the same commit, or the CMS picks the wrong one, name the one you want with preview_context in the backend section. Only that commit status, check run or environment is then considered.
backend:
name: github
repo: user/repo
preview_context: Cloudflare Pages[backend]
name = "github"
repo = "user/repo"
preview_context = "Cloudflare Pages"{
"backend": {
"name": "github",
"repo": "user/repo",
"preview_context": "Cloudflare Pages"
}
}{
backend: {
name: 'github',
repo: 'user/repo',
preview_context: 'Cloudflare Pages',
},
}The name is matched exactly first, and as a partial match if nothing matches exactly — so cloudflare finds Cloudflare Pages too. Matching is case-insensitive.
Setting this option narrows the search deliberately, so if nothing matches, the CMS reports no preview rather than falling back to a provider that wasn’t requested. Check the name against the status or environment as it appears on your repository if a link stops showing up.
Limitations
- On GitHub, a workflow that deploys the site but reports no deployment, no commit status and no check run can’t be detected. Publishing to GitHub Pages with the official actions creates a deployment, so it works; a hand-rolled workflow that only uploads files may not.
- Cloudflare Workers publishes its preview URL in a pull request comment, which no API surfaces alongside the commit. Its check run still reports whether the build succeeded, so the build state is available without a preview link. Cloudflare Pages, which writes the address into its check output, works fully.
- Reading a preview URL out of a check run’s output means reading what that provider chose to write. If the format changes, the address is no longer found and the entry falls back to its live-site link — degraded rather than broken.
- On GitLab, deployments can’t be filtered by commit through the API, so the CMS scans the most recent 100 from the past week and matches them to the branch. A project that deploys more often than that may push a Review App out of range, in which case its commit status still covers it.
- A preview behind access control, such as Vercel’s Deployment Protection, answers the liveness check with an authentication error rather than a page. The CMS treats that as “can’t tell” and leaves the link alone, since it works for anyone signed in.