Skip to content
Back to Deployment

Netlify Deploy with Continuous Integration

Git-based deploys, build plugins, and CI/CD pipelines with Netlify Functions and deploy previews.

CI/CDIntermediate

Why

Netlify's git-connected workflow gives every PR an isolated preview deploy with its own URL, enabling stakeholder review before merge. Build plugins extend the pipeline (Lighthouse, a11y, bundle analysis) without touching your code, and deploy contexts let you vary config per branch.

DORA's 2024 State of DevOps report shows elite teams deploy 973x more frequently than low performers. CI/CD pipelines with automated testing reduce change failure rate to 5-15% versus 40-60% for manual deployments.

When

Use Netlify when you want preview deploys per PR, serverless functions alongside static, and built-in form handling without a backend. Skip if you need long-running servers or containers (use Fly.io or Railway).

How

1. Connect repo — app.netlify.com → Add site → import from GitHub/GitLab/Bitbucket 2. Configure build — netlify.toml or dashboard: build command, publish dir, functions dir 3. Deploy contexts — [context.production] / [context.deploy-preview] for per-env overrides 4. Functions — /netlify/functions/*.ts auto-deployed as serverless, import from @netlify/functions 5. Build plugins — netlify.toml [[plugins]] for @netlify/plugin-lighthouse, plugin-sitemap 6. CI integration — Netlify builds on push; for external CI, use netlify deploy --prod --auth=$TOKEN 7. Forms — add netlify attribute to <form>, submissions land in dashboard + can POST to a webhook 8. Redirects/headers — _redirects file or netlify.toml [[redirects]] for SPA fallback, security headers

Example

# netlify.toml — full configuration [build] command = "npm run build" publish = "dist" functions = "netlify/functions" [build.environment] NODE_VERSION = "20" NPM_FLAGS = "--legacy-peer-deps" # Per-context overrides [context.deploy-preview] command = "npm run build:preview" [context.production] command = "npm run build:prod" # Build plugins [[plugins]] package = "@netlify/plugin-lighthouse" [plugins.inputs] threshold = 0.85 [[plugins]] package = "@netlify/plugin-sitemap" # Redirects + headers [[redirects]] from = "/*" to = "/index.html" status = 200 force = false conditions = {Role = ["admin"]} [[headers]] for = "/*" [headers.values] X-Frame-Options = "DENY" Content-Security-Policy = "default-src 'self'" // netlify/functions/api.ts — serverless function import { handler } from "@netlify/functions"; export const handler = handler(async (event) => { return { statusCode: 200, body: JSON.stringify({ ok: true }) }; });

Common Problems

  • ⚠Build passes locally but fails on Netlify — Node version mismatch, set NODE_VERSION in toml
  • ⚠Functions 502 in production — forgot to export handler, or used Node API not in runtime
  • ⚠Plugin slows builds by 5 min — Lighthouse plugin runs on every deploy, gate to production only
  • ⚠Form submissions not appearing — <form> missing netlify attribute or name, or SPA re-renders it

Best Practices

  • ✓Pin NODE_VERSION in netlify.toml — never rely on Netlify default, it changes over time
  • ✓Gate expensive plugins to production context — previews don't need full Lighthouse audit
  • ✓Use deploy previews for QA — share preview URL with stakeholders before merging
  • ✓Set NPM_FLAGS=--legacy-peer-deps only if needed — it masks real dependency conflicts

FAQ

How do I trigger a deploy from GitHub Actions instead of Netlify's git integration?

Use the netlify-actions/deploy action with a NETLIFY_AUTH_TOKEN secret. Disable Netlify's built-in git builds in Site settings. This avoids double builds on every push.

Why are my Netlify Functions timing out?

Netlify Functions have a 10s timeout on the free plan (26s on Pro). Move long tasks to a background function (async, returns immediately) or to a dedicated worker. Check function logs in the Functions tab.

How do I use environment variables in Netlify CI?

Set them in Site settings, or via netlify.toml [build.environment]. For secrets, use the dashboard only — never commit netlify.toml with secrets. Scope vars to deploy contexts (production, deploy-preview).

Why does my SPA return 404 on refresh?

Client-side routing needs a redirect rule. Add a public/_redirects file with /* /index.html 200. Without it, Netlify serves the 404 page on any deep link refresh.

How do I cancel a stuck deploy?

Go to Deploys tab, click the deploy, then Stop deploy. To prevent future stuck builds, set a build timeout in netlify.toml ([build] timeout = 300). Stuck builds usually mean an infinite loop or a hanging postinstall script.