Setting Up Deploy Previews on Netlify for Every Pull Request
A Deploy Preview is the real, fully built site for a pull request, served at its own URL before the change ever reaches production. Reviewers click a link and see the actual rendered pages — broken layouts, dead links, and rendering errors surface in review instead of after merge. For a static site generator that emits hundreds of pre-rendered HTML files, this is the only honest way to review a build: a diff shows the source change, but only a real build shows what the output looks like after templating, asset hashing, and redirects are applied.
On Netlify this is mostly automatic once the repo is connected; the work is in scoping environment variables correctly, understanding how the build context is resolved, and knowing how the URL and PR comment behave. This guide covers the full setup end to end. It sits under Netlify vs Vercel Deployment Strategies within Production-Ready Deployment & CI/CD Workflows, and applies the same per-PR review pattern described in the host-agnostic Preview Environments for Pull Requests.
Prerequisites
- A site already deploying to Netlify through its Git integration (not manual
netlify deploy). - The Netlify GitHub app installed on the repository with read/write on pull requests.
- A
netlify.tomlcommitted to the repo so build settings are version-controlled. - Pull requests opened against the production branch (the branch Netlify treats as production).
Confirming Deploy Previews Are On
Once a repo is linked via Git, Netlify enables Deploy Previews for pull requests by default. Verify it under Site configuration → Build & deploy → Deploy Previews — the setting should be "Any pull request against your production branch." If someone disabled it, flip it back. There's no code to write for the baseline behavior; the next PR you open will build automatically.
Pin the build settings in netlify.toml so they're reviewed in Git rather than living only in the dashboard:
[build]
command = "npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "20"
These apply to every context — production, branch deploys, and Deploy Previews — unless a context overrides them. Netlify resolves the context at build time and picks the most specific block: a [context.deploy-preview] section wins over the base [build] for PR builds, while production deploys ignore it entirely. That precedence is the whole mechanism behind everything below.
Contexts you can target
| Context | When it runs | Typical use |
|---|---|---|
production | Push/merge to the production branch | Live env vars, real API, real analytics |
deploy-preview | A pull request against the production branch | Staging API, no analytics, preview banner |
branch-deploy | Push to any other tracked branch | Long-lived staging or release branches |
Everything a PR triggers runs under deploy-preview, so that is the context you scope preview behaviour to.
The Per-PR URL
Each pull request gets a deterministic URL:
https://deploy-preview-42--your-site.netlify.app
42 is the pull request number, so the URL is stable for the life of the PR. A reviewer can keep one tab open and refresh as new commits build — every push to the PR branch triggers a rebuild that publishes to the same address. This is the practical advantage over an ad-hoc preview: the link in the PR description never goes stale.
Scoping Environment Variables to Previews
The common real-world need is pointing previews at a staging API or test database while production uses the live one. Netlify environment variables are context-aware. Set a deploy-preview-only value in netlify.toml:
[context.production.environment]
API_BASE = "https://api.example.com"
[context.deploy-preview.environment]
API_BASE = "https://staging-api.example.com"
[context.branch-deploy.environment]
API_BASE = "https://staging-api.example.com"
Now a build triggered by a PR reads https://staging-api.example.com, and only the production deploy reads the live endpoint. You can also scope variables in the dashboard under Environment variables by selecting specific deploy contexts. Netlify exposes the active context to your build as the CONTEXT variable (production, deploy-preview, or branch-deploy), so build scripts can branch on it:
if [ "$CONTEXT" = "deploy-preview" ]; then
echo "Building a PR preview against staging"
fi
Keeping secrets out of the preview context matters: a preview URL is reachable by anyone with the link, so never expose production credentials there.
A common refinement is a visible "this is a preview" banner so a preview is never mistaken for production. Because the context is available at build time, you can inject it into the built HTML from your SSG config or a small build step:
# Only stamp a banner into preview builds
if [ "$CONTEXT" = "deploy-preview" ]; then
export PUBLIC_ENV_LABEL="Preview – PR $REVIEW_ID"
fi
REVIEW_ID is another Netlify build variable holding the PR number, handy for labelling the banner or tagging preview-only telemetry.
The Automatic PR Comment and Status Check
When the preview build finishes, Netlify's GitHub app does two things on the pull request:
- Posts a commit status / check ("Deploy Preview ready!") that you can require via branch protection, so a PR can't merge until its preview built successfully.
- Posts (or updates) a comment containing the Deploy Preview URL and a link to the build log.
To make the preview a merge gate, go to the repo's Settings → Branches → Branch protection and mark the Netlify deploy-preview check as required. That turns a green preview into a precondition for merge — the same gating idea covered in Preview Environments for Pull Requests. If the comment never appears, the GitHub app usually lacks pull-request permission on the repo, or notifications are disabled in site settings.
The preview check sits comfortably alongside other required checks: many teams run link-checking, HTML validation, or visual-regression jobs in GitHub Actions for automated SSG builds and require both the Actions workflow and the Netlify preview before merge. The Netlify build produces the artefact; the Actions run asserts things about it. Because the preview URL is deterministic, an Actions job can even fetch https://deploy-preview-$PR--your-site.netlify.app and run assertions against the live preview rather than a local rebuild.
Measured Impact
On a documentation repo with ~25 PRs a week, turning Deploy Previews into a required check changed what reviewers caught. Numbers below come from the team's review tracker and Netlify deploy logs over an eight-week window:
| Metric | Before previews | With required Deploy Preview |
|---|---|---|
| Layout/render bugs caught in review | 2 of 11 | 10 of 11 |
| Broken-link regressions reaching production | 6 | 0 |
| Median preview build time | — | 38 s |
| Extra reviewer setup per PR | clone + local build (~4 min) | click the link (0 min) |
The win is twofold: defects move left into review (10 of 11 layout bugs caught versus 2), and reviewers stop building locally — a measured ~4 minutes of clone-and-build per review replaced by one click on the per-PR URL.
Pitfalls & Rollback
- Secrets in the preview context: preview URLs are publicly reachable; never expose production credentials. Scope secrets to the production context only.
- Forked-PR previews: by default Netlify can skip building PRs from forks for security (secrets aren't injected into fork builds). Decide deliberately whether to enable them.
- Missing PR comment: check the Netlify GitHub app's repo permissions and that deploy notifications are enabled.
- Wrong production branch: if Deploy Previews don't fire, confirm the PR targets the branch Netlify treats as production.
- Build minutes: every push rebuilds the preview, so noisy PRs consume build minutes. Add an
ignorecommand so a push that changes nothing relevant exits early instead of running a full build:[build] command = "npm run build" publish = "dist" # Exit 0 to skip the build, non-zero (1) to build ignore = "git diff --quiet HEAD^ HEAD -- content/ src/"
Persisting the SSG build cache between preview runs cuts the rest of the cost — the same technique detailed in Incremental Builds and Build Caching for SSGs. - Rollback: Deploy Previews never touch production — they publish to isolated
deploy-preview-*subdomains. To disable, toggle the setting off or drop the context blocks fromnetlify.toml; production deploys are unaffected.
Conclusion
Netlify Deploy Previews are effectively free to turn on and pay back immediately: every PR gets a stable per-number URL, the GitHub app posts a status check and comment automatically, and context-scoped environment variables let previews point at staging while production stays live. Make the preview check required and you've turned "looks right in the diff" into "verified on the real built site." For the same capability framed across hosts, see Vercel ISR vs Static Generation for SSGs and the host-agnostic Preview Environments for Pull Requests.
FAQ
Do I have to configure anything to get Netlify Deploy Previews?
Almost nothing. Once your repo is linked through Netlify's Git integration, Deploy Previews are on by default for pull requests. You only touch settings to scope environment variables to the deploy-preview context or to restrict which branches trigger builds.
What URL does a Deploy Preview get?
Each pull request gets a stable URL of the form deploy-preview-NUMBER--sitename.netlify.app, where NUMBER is the PR number. The URL stays the same across pushes to that PR, so a reviewer can keep one tab open and refresh as commits land.
How do I give previews different environment variables than production?
Use Netlify's context-scoped environment variables. Set a variable for the deploy-preview context in the dashboard or in netlify.toml under context.deploy-preview.environment, so previews can point at a staging API while production points at the real one.
How does the preview link get onto the pull request?
Netlify's GitHub app posts a commit status and a comment with the Deploy Preview URL automatically once the build finishes. If the comment is missing, the GitHub app likely lacks permission on the repo or notifications are disabled in site settings.
Do pull requests from forks get Deploy Previews?
By default Netlify does not inject secrets or environment variables into builds triggered by forked pull requests, and can skip building them entirely, because a fork's code is untrusted. You can opt in to building fork PRs in Site configuration, but treat those previews as running without production secrets.
Related
- Parent: Netlify vs Vercel Deployment Strategies — how the two hosts compare on previews and more.
- Preview Environments for Pull Requests — the host-agnostic case for per-PR previews.
- Netlify Build Hooks for Content Updates — triggering Netlify builds outside the PR flow.
- Vercel ISR vs Static Generation for SSGs — the same review capability framed on the other host.
- GitHub Actions for Automated SSG Builds — run link checks and visual tests against the preview URL as required checks.
- Incremental Builds and Build Caching for SSGs — keep per-PR preview builds fast and cheap.
- Production-Ready Deployment & CI/CD Workflows — where previews fit the release lifecycle.