← All guidesGitHub
Web Development

Deploy a Next.js App with Pull Request Previews

Deploy a Next.js app from GitHub, create an isolated pull-request preview, and verify its health and changed route before production changes.

Jump to stepsGitHub: TylorMayfield/digitalocean-nextjs-pr-previews

What this deployment path proves

A pull-request preview gives reviewers a deployed URL for one proposed change before it lands on the production branch. It does not make a change safe by itself: tests, code review, migrations, access controls, and production monitoring still matter. Its value is narrower and practical: someone can inspect the rendered route, sign-in boundary, form behavior, and mobile layout on the exact commit under review.

This guide uses App Platform for a Next.js web service and GitHub Actions for short-lived pull-request previews. Keep preview data disposable. Do not point a preview at production databases, production API keys, live payment credentials, or customer data. A preview is a test environment, not a copy of production.

  • A Next.js repository whose build succeeds locally with the lockfile installed.
  • A GitHub repository where you can add Actions secrets and pull-request workflows.
  • A DigitalOcean team that can create App Platform apps and a narrowly scoped API token.
  • A harmless route, such as /health, that returns success without writing data or sending email.
  • A cost boundary: each preview is a separate temporary app, so confirm the current App Platform pricing and make cleanup part of the definition of done.

Add a safe health route before automating previews

A health route is a small agreement about what a successful deployment means. For a content app it may only confirm that the server can answer a request. If an app depends on a database or external API, distinguish a liveness check from a deeper readiness check; do not make an external dependency outage look like a bad application deployment without saying so.

The example below deliberately returns no secrets and performs no external request. Put it in the App Router only if your project does not already have an equivalent route. Visit it locally before you configure App Platform.

app/health/route.tsFile contents
import { NextResponse } from "next/server";

export function GET() {
  return NextResponse.json({ ok: true });
}

Choose the production boundary before you connect GitHub

Create the production app from the main branch in App Platform first. Select the repository, its source directory if it is in a monorepo, and the web-service component. Confirm the detected build and run commands rather than accepting them on faith. App Platform can build from a Git repository and redeploy when the chosen branch changes; that convenience makes a deliberate branch boundary important.

Next.js inlines NEXT_PUBLIC_ variables into browser bundles. Other variables can still leak if your code passes them to client components or returns them from an API. Keep secrets server-side and inspect what the page and routes actually return.

On the source screen, choose Git repository, select GitHub, and choose the repository that contains your Next.js app. If it is missing, use Edit your GitHub permissions before continuing. Selecting a repository only lets App Platform inspect the source; it does not create an app or start a deployment.

  1. Create an App Platform app from the repository’s main branch and wait for its first deployment to finish.
  2. Open the component settings and record the service name, HTTP port, build command, run command, and production URL.
  3. Add encrypted runtime variables through the control panel; keep production-only secrets out of GitHub preview secrets.
  4. Set a health check to a route that is fast, unauthenticated, and free of side effects. Do not use a route that writes a record merely to prove deployment.
DigitalOcean App Platform source-selection screen with Git repository selected, GitHub selected as provider, and the repository picker open.
Start here: choose Git repository, select GitHub, then select the repository containing the Next.js app. No resource has been created at this stage.

Create one preview per pull request

The companion starter includes the .do/app.yaml base spec, the preview workflow, and the cleanup workflow. DigitalOcean’s GitHub Action can create a unique App Platform app for each pull request and expose its URL as an Action output. Store the DigitalOcean API token as a GitHub Actions secret. Give it only the permissions required for App Platform, rotate it when ownership changes, and never echo it in a workflow or deployment log.

Run previews only for pull requests from the same repository unless you have designed a separate, untrusted-fork workflow. A workflow that runs with privileged secrets against a fork can turn a preview into a credential leak. The preview uses preview-only variables; it should be able to render and exercise a harmless test path without access to production systems.

  1. Copy .do/app.yaml and .github/workflows/delete-preview.yml from the linked files into the same paths in your repository. Use either the deploy workflow below or the linked deploy-preview.yml, never both.
  2. In .do/app.yaml, replace github.repo with YOUR_OWNER/YOUR_REPOSITORY and choose your region and app name. Match source_dir for a monorepo, build_command to your lockfile, run_command to your package scripts, http_port to the listening port, and health_check.http_path to /health. Review instance_size_slug pricing before running.
  3. In GitHub Settings → Secrets and variables → Actions, create the repository secret DIGITALOCEAN_APP_PLATFORM_TOKEN. Both workflows use this exact name. Use a team/token restricted to the required preview resources and permissions, including cleanup. Never place its value in the app spec.
  4. Commit the app spec, health route, and both workflows to the default branch before the first test PR. Match the preview workflow’s branches filter to your production branch if it is not main. Open one same-repository PR with a harmless visible change.

Before continuing: Use preview-only secrets and keep this workflow restricted to pull requests from the same repository. Do not expose a privileged token to forks.

.github/workflows/app-platform-preview.ymlFile contents
name: App Platform preview

on:
  pull_request:
    branches: [main]

permissions:
  contents: read
  pull-requests: write

jobs:
  preview:
    if: github.event.pull_request.head.repo.full_name == github.repository
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: digitalocean/app_action/deploy@v2
        id: deploy
        with:
          deploy_pr_preview: "true"
          token: ${{ secrets.DIGITALOCEAN_APP_PLATFORM_TOKEN }}
      - uses: actions/github-script@v7
        env:
          PREVIEW_URL: ${{ fromJson(steps.deploy.outputs.app).live_url }}
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Preview: ${process.env.PREVIEW_URL}`
            })

Verify the preview before you merge

Open the preview link from the pull request and check the changed route at a phone-sized viewport as well as a desktop viewport. Then request the health URL. A successful HTTP response proves that this preview can serve that endpoint; it does not prove every background job, authentication provider, checkout, or production integration works.

If the preview fails, read the linked build and deploy logs before changing configuration. Compare the preview’s environment variables with the minimum documented preview set, not production. Fix the source or preview configuration, push a new commit, and let the existing pull request create a fresh preview. Do not merge because a previous commit happened to have a URL.

Start with the first failed phase. A build failure is usually a source, dependency, or build-command question; a deployed preview that fails /health is a service port, health-path, or runtime-variable question. Change one boundary at a time and rerun the same pull request. Do not add a production secret merely to make a preview pass: a passing preview with the wrong data boundary is still a failed safety check.

Check the harmless health endpoint from your terminalLocal terminal
Replace every highlighted value before running this command.
curl --fail --silent --show-error "https://YOUR_PREVIEW_HOST/health"

Remove previews and keep production intentional

Add the companion cleanup workflow from DigitalOcean’s preview-deployment documentation so the preview is deleted after the pull request closes. Check its first run in the App Platform dashboard. Leaving previews alive creates confusing URLs, needless cost, and an expanding surface for old dependencies.

Production deployment should remain a separate decision: merge only after the preview result, review, and required checks are satisfactory. After deployment, verify the production health route and the changed user flow. If a release exposes a problem, stop further changes, preserve the evidence, and use the project’s normal recovery procedure rather than treating a preview as a rollback mechanism.

  1. Close the test PR without merging. Open its Delete App Platform preview Actions run and confirm success.
  2. In App Platform, confirm the temporary app for this PR is gone and the production app still exists. If deletion failed, inspect the token permissions and app identity, then delete only that preview manually and repair cleanup before opening more PRs.

Check your result

Expected result
The pull request receives a preview URL, the changed route renders there, and the preview /health endpoint returns a successful response without writing data.
Stop if
Stop if a preview has production credentials or customer data, a fork can receive the token, the health route changes state, or the deployment logs expose a secret.
Next step
Review the preview on desktop and mobile, merge only after required checks pass, and confirm the preview cleanup workflow removes the temporary app when the pull request closes.