Preview Environments for Static Sites

A live URL for every branch makes visual review part of the release process. This chapter describes a static-site pattern using GitHub Actions, S3, and CloudFront, and explains what changes when the application also needs a WordPress runtime.

Talk to an Engineer

What This Actually Looks Like

A developer pushes a branch, the workflow deploys its files, and the review gets a URL that points to that revision. A pull request can have a separate preview prefix such as /pr-2/, while a branch uses its own prefix. Reviewers can open the result, check the layout, and test interactions before the change reaches production.

The deployment is ready when the upload, routing, and cache invalidation have completed and a smoke check has passed. That is the useful promise; a universal thirty- or forty-second timer is not. Queue time, artifact size, and CDN propagation vary, so the workflow should report the commit and readiness result alongside the URL.

For a static site, the moving parts are a GitHub Actions workflow, an S3 prefix for each preview, and a CloudFront function that maps browser paths to the right files. Closing a pull request or deleting a branch triggers scoped cleanup. The same automation must verify the prefix before deleting anything, so a cleanup event cannot remove another preview or production assets.

Why This Matters More Than It Sounds

Preview environments are usually pitched to large engineering teams, which quietly tells everyone else the tool is not for them. It is. Anyone who ships a site or an app from a repository benefits, and the reason is simple: preview environments matter because they are the difference between reviewing code and reviewing the thing the code produces.

A diff shows you that a CSS class changed. A preview environment shows you that the heading is now too wide on mobile. A diff shows you that a video source attribute was updated. A preview environment shows you whether the new video actually plays. For static sites, where the entire deliverable is what the browser renders, reviewing without a preview is reviewing the recipe instead of tasting the food.

The second reason is less obvious but more valuable over time: preview environments make non-developers part of the review process. A scientist who builds instruments, a stakeholder who cares about the copy, a designer who notices that the spacing is off, none of them will read a diff, but all of them can click a link. The preview URL turns a pull request from something only engineers participate in into a checkpoint the whole team can join.

The Infrastructure Behind the URL

Each preview has a distinct storage prefix and a URL mapped to it. Upload only the built public artifacts; repository credentials, source configuration, and private data do not belong in that bucket. Keep the bucket private behind CloudFront, and add access controls to the preview when its content is not public.

A sync with deletion enabled makes the destination prefix match the uploaded files, but it does not remove cached objects from the CDN. Invalidate the affected preview paths after the sync, then request the entry page and one changed asset through the public preview URL. Associate that check with the deployed commit rather than merely reporting that the upload command succeeded.

Routing is part of the deployment. An S3 REST origin does not automatically supply a directory index for every nested route; the edge routing function must map those routes to the appropriate entry document. Certificate coverage, redirects, and asset paths also need to work on the preview hostname. Sharing a CDN with production makes the delivery path more representative, but does not make the two environments identical.

What We Got Wrong the First Time

The concurrency model. GitHub Actions lets you group workflow runs so that a new push cancels the in-progress run for the same branch, which prevents stale deploys from overwriting fresh ones. We scoped the concurrency group by branch name alone, which seemed right until a PR was opened against a branch that already had a push deploy running. The PR event cancelled the branch deploy, and the next branch push cancelled the PR deploy, and neither finished reliably.

The fix was one line: scope the concurrency group by both event type and branch name, so push deploys and PR deploys run in separate lanes. It took longer to diagnose than to fix, and the diagnosis was worth the cost, because the bug only appears when a team is actively using the system, exactly the moment you want it to be reliable.

What This Costs

Small static previews can be inexpensive, but they are not inherently free. Budget for storage, requests, data transfer, build minutes, and any logging, security, or account-level plans you enable. Deleted branches should have a retention policy, and failed cleanup should be visible; a preview that no one opens can still leave stored artifacts behind.

Keep three clocks separate: the time to configure the system, the workflow execution time, and the time until the deployed revision is verified at the edge. Record those measurements in the workflow for your own site. Reusing a tested configuration reduces setup work, but neither a short upload nor a copied workflow proves a complete, ready deployment.

When You Do and Do Not Need This

A small site with one editor may be well served by a local preview and a reliable deployment check. A shared preview becomes more valuable when several people contribute, when approval involves non-developers, or when visual and interactive changes cannot be judged from a diff. Choose the smallest workflow that lets the people responsible review the actual result.

The S3 pattern described here serves static files. It can host the output of a static export, but it cannot run PHP, a WordPress database, or an application that requires server-side rendering at request time. A WordPress preview also needs an isolated runtime, an appropriate database copy, media access, and environment-specific secrets. Disable outbound email, payment processing, and other production side effects before making that copy available.

On udx.io, WordPress runs in containers and media lives in object storage; that does not make the application a static S3 site. Chapter 02 covers the runtime boundary, and Chapter 10 covers restoring state into an isolated environment. A preview must represent the behavior under review without inheriting production's ability to affect real customers.

Where This Fits in Our Practice

Array

The parts of our practice this pattern draws on.

Secure DevOps Implementation

CI/CD pipelines with security and preview environments built in.

Rabbit

GitOps automation, including the IaC that defines these staging environments.

Cloud Application Engineering

The broader architecture practice preview environments are part of.

WordPress at Scale

The same preview pattern on WordPress, where the build step is longer.

WordPress Security

Security architecture for the production side of the same pipeline.

Shipping to the Azure Marketplace

Where this preview discipline ends up: a certified Kubernetes app, rehearsed end to end.

"

Part of the UDX handbook

Chapter 07 of 10 / Pipeline layer

How to run WordPress on Linux, properly

This page is the pipelines and environments chapter of a ten-chapter handbook on running WordPress on Linux, written from our own operating record; start at the handbook for the layer map, the incident record and the other nine chapters.

Talk to an Expert

Connect with our diverse group of UDX experts that can help you implement successful DevOps practices within your organization.