On this page
Preview deployments let reviewers test an interface before it reaches production. They also create temporary applications that may run untrusted code, expose unfinished features, and consume shared services. A useful preview system makes review easier without quietly expanding the production security boundary.
Decide what a preview is allowed to access
Define previews as disposable environments with their own identity, data rules, and cleanup owner. A branch-specific URL is not isolation by itself. If that URL connects to the production database or sends real customer emails, the environment is still capable of production damage.
Use the API Latency Tester for an initial status and timing check against a public, non-sensitive preview endpoint. Browser-based checks can encounter CORS restrictions and cannot replace server-side smoke tests. Never paste private authorization tokens into an ad hoc measurement workflow.
Write down which pull requests can create previews. Internal contributors and external forks may need different paths. A trusted maintainer can approve an untrusted contribution for review, but that approval should not grant the contribution access to production secrets.
Reviewers should know whether the environment contains fixture data, shared staging data, or an isolated database. Display that distinction in the deployment comment and inside the application where appropriate.
Isolate configuration and external side effects
Give each preview a narrowly scoped configuration set. Route email to a capture service, disable real payment collection, and use sandbox credentials for integrations. Replace destructive background jobs with inert behavior unless the preview has a fully isolated queue and database.
For data, choose between per-preview databases, per-preview schemas with meaningful access restrictions, or immutable fixture datasets. Shared staging databases are simpler but can make parallel reviews interfere. A schema name alone is not a strong boundary if every preview credential can access every schema.
The Vercel environment variable documentation describes one platform's environment separation. Whatever the provider, verify whether variables are resolved during build or at runtime. Frontend-exposed values become public application output and must never carry a secret.
Use synthetic data, not a casual production copy. Sanitization should remove credentials, personal information, and live integration identifiers. Test that password-reset messages, webhooks, and analytics events cannot escape into production systems.
Build a minimal lifecycle contract
A preview controller should create, update, report, and delete the environment. Use a stable pull-request identifier as the lifecycle key while recording the specific commit deployed. Avoid deriving trusted shell commands or resource names directly from a contributor-controlled branch label.
A conceptual deployment record might look like this:
{
"pullRequest": 184,
"commit": "4f8b1a2",
"environment": "preview-pr-184",
"url": "https://pr-184.preview.example.com",
"dataMode": "fixtures",
"expiresAt": "2026-03-01T18:00:00Z"
}Store this record in the deployment system rather than relying on a comment as the only source of truth. Comments are useful navigation, but they can become stale after a failed update. Mark a preview ready only after the expected commit passes checks.
Use concurrency control so rapid pushes do not leave an older build overwriting a newer one. Cancellation must also handle provisioned resources. A cancelled job that already created a database still needs cleanup.
Protect access and verify review behavior
A random-looking preview URL is not authentication. Apply access protection appropriate to the sensitivity of the work. For external reviewers, choose an explicit sharing mechanism rather than disabling all controls for convenience. Review the platform's behavior for static assets, generated screenshots, and alternate hostnames too.
Keep previews out of search results with suitable indexing directives, but do not confuse indexing controls with authorization. The Google guidance on blocking indexing explains why crawler directives are not a privacy mechanism.
Run smoke tests against the deployed URL: page load, asset load, authentication flow, and one representative read or write using fixture accounts. For visual review, use the existing multi-device testing guide to choose useful viewport and interaction checks.
Confirm that callback URLs, cookie domains, and cross-origin settings are scoped correctly. A production-wide cookie domain can accidentally send authentication material to a preview. OAuth integrations may need a controlled callback gateway or dedicated sandbox configuration instead of wildcard permissions.
Clean up reliably and keep costs visible
Delete previews when pull requests close, but also run a scheduled expiry sweep. Webhook delivery, workflow cancellation, provider outages, and deleted branches can all bypass the normal happy path. Resource labels should identify the repository, pull request, owner, and expiry time.
Cleanup should be idempotent: deleting an already-removed resource should not halt removal of the remaining database, queue, and storage. Log partial failures and retry them with bounded backoff. A successful application deletion does not prove all billable resources disappeared.
Set limits for active previews, build concurrency, database size, and retained logs. Decide how long closed previews remain available and communicate that retention window to reviewers. The GitHub deployment environment documentation is useful when connecting review protections to workflow jobs.
Maintain a manual teardown procedure for provider incidents. It should identify resources from the lifecycle record without requiring someone to inspect every cloud project. Keep preview provisioning separate from server hardening; managed previews should not turn each pull request into a new unmanaged server.
Finally, ask reviewers whether the environment answers their questions. Include test-account instructions, the deployed commit, known limitations, and a concise checklist. A technically isolated preview that nobody can navigate still fails its primary purpose: reducing uncertainty before release.
Prevent stale builds from replacing newer previews
Consider two commits pushed to the same pull request a minute apart. The older build may finish last and overwrite the preview URL after reviewers have already seen the newer commit's successful checks. Attach the source commit to each deployment and verify that it is still the intended head before promoting the URL. A concurrency group can cancel obsolete work, but cancellation alone does not undo an external deployment that already started.
Use deployment identity consistently in comments, smoke-test reports, and cleanup records. A reviewer should be able to compare the displayed commit with the pull request without guessing which workflow last updated the application. If a URL points to a moving environment, label it differently from an immutable deployment URL.
Reopening a pull request deserves a separate test. Cleanup may have removed its database or revoked credentials, while a stale bot comment still advertises the old address. Provision the required resources again, run readiness checks, and update the review comment only when the replacement is usable. Prefer an explicit unavailable state over a green historical link that now serves an unrelated or broken environment.
Key takeaways
- Treat previews as untrusted workloads until their permissions prove otherwise.
- Isolate data, credentials, and external side effects.
- Publish readiness only for the exact deployed commit.
- Protect access independently from search indexing.
- Combine event-driven deletion with scheduled expiry cleanup.
Reach designers & developers at work
Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.
Become a sponsor