The first time I pushed a staging build live on Netlify, I thought everything looked flawless — until during testing I noticed production users were suddenly hitting staging APIs. If you’ve ever felt your stomach drop at that kind of mistake, you know how real “configuration bleed” is. This article is my attempt to distill hard lessons learned into a strategy for keeping staging, production, and everything in between completely isolated. After experiencing these mishaps managing deployments on Netlify, I have learned the platform’s flexibility can become a liability when we don’t establish clear boundaries between environments.
Configuration bleed is not just an inconvenience - it’s a security vulnerability waiting to happen. With this misses you might accidentally expose staging API keys in production, leak sensitive user data between environments, and push untested features to live users.
What is Netlify?
Netlify is essentially a deployment pipeline bundled into a developer-friendly platform. At its core, it takes your code from a Git branch, runs your build commands, and instantly publishes a live version of the app. What makes it attractive to many teams is the removal of infrastructure headaches — you don’t manage servers or worry about CI integrations. Instead, you get a system where commits can translate almost immediately into working previews or production-ready deployments.
The Hidden Dangers of Poor Environment Separation:
When environments blur together, the problems don’t just stay technical — they ripple into security and user trust. A few scenarios I’ve personally seen (or narrowly avoided):
From a maintenance perspective, configuration bleed creates operational nightmares:
The Root Problem: Single-Site Thinking
As a developer when we start with Netlify we create a single site connected to the main repo branch. This works perfectly for simple projects, but as applications grow and the complexity grows, this approach becomes problematic. The inclination will be to use Netlify’s context-based configuration to handle different environments within the same site, but this creates shared state that inevitably leads to configuration bleed.
The fundamental issue is treating environments as configurations rather than completely separate deployments. As a developer when one thinks of staging and production as different versions of the same site, naturally they will inherit all the coupling problems that come with the shared infrastructure.
The Solution: True Environment Isolation
After countless hours debugging configuration issues, I have settled on a simple principle: separate sites per environment. This means creating distinct Netlify sites for production, staging, and any other long-lived environments you need.
Here’s why this approach is superior:
Security Benefits:
Maintenance Advantages:
Recommended Architecture:
Site Structure:
Create two separate Netlify sites:
main or production branchstaging or develop branchEach site should have its own:
Configuration Strategy:
Here’s a stripped-down netlify.toml example I use in a monorepo. It’s adapted from Netlify’s own docs, but tweaked to clarify environment-specific overrides:
\
toml [build] #Set frontend app folder (for monorepos) base = “frontend” command = “npm ci && npm run build” publish = “frontend/dist” [build.environment] NODE_VERSION = “20” #Environment-specific API endpoints: [context.production.environment] API_BASE_URL = "https://api.example.com" FRONTEND_ORIGIN = "https://app.example.com" [context.staging.environment] API_BASE_URL = "https://api-staging.example.com" FRONTEND_ORIGIN = "https://staging.example.com" [context.deploy-preview.environment] API_BASE_URL = "https://api-preview.example.com" FRONTEND_ORIGIN = "https://deploy-preview-<id>--example.netlify.app" # Essential SPA fallback to prevent 404s on route refresh [[redirects]] from = "/*" to = "/index.html" status = 200
\ Environment variable management:
Store environment variables directly in each site’s Netlify dashboard, not in netlify.toml . This includes:
Public configuration like API base URLs can live in netlify.toml for transparency, but anything sensitive should be isolated per site.
Backend CORS Configuration:
To prevent unintended cross-environment API access, I rely on a CORS setup derived by FastAPI’s guide but customized for deploy previews:
from fastapi.middleware.cors import CORSMiddleware import os # Environment-specific allowed origins allowed_origins = [] if os.getenv("ENVIRONMENT") == "production": allowed_origins = ["https://app.example.com"] elif os.getenv("ENVIRONMENT") == "staging": allowed_origins = ["https://staging.example.com"] elif os.getenv("ENVIRONMENT") == "preview": # For deploy previews, pattern match allowed_origins = ["https://deploy-preview-*--example.netlify.app"] app.add_middleware( CORSMiddleware, allow_origins=allowed_origins, allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE"], allow_headers=["*"], )
This approach ensures that your staging API cannot be called from production (and vice versa), preventing data leakage and unauthorized access.
Deployment Flow Architecture:

This architecture ensures complete isolation between environments while maintaining clear data flow and access controls.
\ Common Pitfalls and Solutions:
base and publish directories for each site. Do not rely on Netlify’s auto-detection [build] base = "packages/frontend" # Explicit path to app publish = "packages/frontend/dist" # Explicit publish directory
Environment Variable Source Confusion:
Problem: Variables defined in both netlify.toml and the Netlify UI, causing unexpected overwrites.
Solution: Establish a clear hierarchy and documentation:
netlify.tomlHard-coded URLs in Application Code:
Problem: hard-coded API endpoints, making environment separation ineffective.
Solution: Always use environment variables for external resources:
// Bad const API_BASE = 'https://api.example.com'; // Good const API_BASE = process.env.REACT_APP_API_BASE_URL || 'http://localhost:3001'; Forgetting Deploy Preview Origins:
Problem: Deploy previews fail CORS checks because backend doesn’t allow their dynamic URLs.
Solution: Either configure pattern matching for preview URLs or disable deploy previews for sensitive projects:
# Pattern matching for deploy previews import re allowed_origin_patterns = [ r"https://deploy-preview-\d+--yoursite.netlify.app" ] def is_allowed_origin(origin): return any(re.match(pattern, origin) for pattern in allowed_origin_patterns)
Maintenance Workflow Benefits:
Once properly implemented, this separation strategy provides significant operational advantages:
Faster Debugging: When issues arise, you immediately know which environment and configuration set to examine. No more wondering if a production issue is caused by staging configuration bleed.
Confident Deployments: With true environment isolation, you can deploy to production knowing that staging testing was performed against identical infrastructure and configuration patterns.
Easier Rollbacks: Each environment maintains its own deployment history, making rollbacks surgical and predictable.
Team Collaboration: Multiple developers can work on different features in staging without stepping on each other’s toes or affecting production stability.
Beyond Basic Separation:
As your application grows, consider additional environment separation strategies:
Feature-branch environments: Temporary Netlify sites for long-running feature development
Load testing environments: Separate infrastructure for performance testing
Security testing environments: Isolated environments for penetration testing and security audits
Client demo environments: Stable environments for customer demonstrations
Looking back, every major “ops nightmare” I’ve had with Netlify could be traced to one root cause: thinking of staging and production as the same thing with slightly different variables. The moment I started treating each environment as its own independent deployment — with its own guardrails, credentials, and access rules — the chaos nearly disappeared. My takeaway is simple: isolation is cheaper than cleanup. The few extra minutes spent setting up separate sites, adding environment-specific CORS rules, or double-checking your Netlify config is nothing compared to the time you’ll lose debugging tangled environments. If anything, strict separation buys you confidence: confidence that staging experiments won’t leak, confidence that rollbacks are clean, and confidence that production stays stable. As applications keep getting more complex, I expect environment isolation to matter even more. You don’t just protect code this way — you also protect your team’s velocity and your users’ trust. And if you’re a small team building big things, that trade-off is always worth it.
\
\


