Environment markers DEV/STAGING/PROD — 5 ways to stop nuking production
You click "Delete all accounts" thinking you're on staging. Turns out it was production. Happens to every DevOps and engineer at least once. Five visual environment-marking techniques so you never fall into that trap again.
Method 1 — red banner across the top
Most obvious and most effective. A stripe on top of every page, impossible to ignore:
body::before {
content: "DEV ENVIRONMENT — data may not be production";
position: fixed;
top: 0; left: 0; right: 0;
background: repeating-linear-gradient(
-45deg, #dc2626, #dc2626 12px, #b91c1c 12px, #b91c1c 24px
);
color: #fff;
text-align: center;
padding: 6px 12px;
font: 700 13px/1.4 ui-monospace, monospace;
letter-spacing: .5px;
z-index: 999999;
text-shadow: 0 1px 2px rgba(0,0,0,.3);
}
/* Body padding so content doesn't slide under the banner */
body { padding-top: 32px !important; }
The repeating gradient gives a "caution tape" pattern like construction sites — evolutionarily, this means danger. Without the gradient it would be a regular info bar; with it, nobody ignores it.
Method 2 — floating badge in the corner
The top banner is aggressive and takes space. Sometimes you want gentler — a small corner badge:
body::after {
content: "DEV";
position: fixed;
bottom: 16px; left: 16px;
background: #dc2626;
color: #fff;
padding: 6px 12px;
border-radius: 8px;
font: 700 12px ui-monospace, monospace;
letter-spacing: 1px;
z-index: 999999;
box-shadow: 0 4px 12px rgba(220, 38, 38, .4);
pointer-events: none; /* don't block clicks */
}
Pinned to the corner, visible but unobtrusive. pointer-events: none is critical — without it the badge would block buttons underneath.
Method 3 — swap the favicon
A banner only helps when you're looking at the page. The browser tab is visible even after Cmd+Tab. Swap the favicon:
// Generate a new favicon (red circle with letter D)
const canvas = document.createElement('canvas');
canvas.width = 32; canvas.height = 32;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#dc2626';
ctx.beginPath();
ctx.arc(16, 16, 14, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.font = 'bold 18px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('D', 16, 17);
// Remove old favicons
document.querySelectorAll('link[rel*="icon"]').forEach(el => el.remove());
// Add the new one
const link = document.createElement('link');
link.rel = 'icon';
link.type = 'image/png';
link.href = canvas.toDataURL();
document.head.appendChild(link);
Red D in the tab, always visible. Also works in bookmarks — staging vs production bookmarks become trivially distinguishable.
Method 4 — subtle viewport tinting
The most elegant — a light coloured overlay giving the whole page a barely-noticeable tint:
/* Orange tint for STAGING, red for DEV */
html::before {
content: '';
position: fixed;
inset: 0;
background: rgba(245, 158, 11, .04);
pointer-events: none;
z-index: 999998;
mix-blend-mode: multiply;
}
rgba(..., .04) = nearly invisible, but after a few seconds your brain knows "something's different". After a few days you notice instantly. Without banner aggression.
Method 5 — title-tab prefix
The browser tab shows document.title. Add a prefix:
// Title prefix + watch out for SPAs changing it dynamically
const PREFIX = '[DEV] ';
function ensurePrefix() {
if (!document.title.startsWith(PREFIX)) {
document.title = PREFIX + document.title;
}
}
ensurePrefix();
// SPAs often change the title on route change
new MutationObserver(ensurePrefix)
.observe(document.querySelector('title'), { childList: true });
The MutationObserver catches SPAs that rewrite the title on route change. Without the observer the prefix vanishes after the first navigation.
Pitfalls
- Don't apply on production — the URL pattern must be narrow:
https://staging.*,https://*-dev.*,http://localhost:*. Not*. - z-index conflict — some cookie banners or modals use z-index 9999. Your markers must be higher (999999 should suffice).
- Print stylesheet — the banner will appear in a printed PDF. Add
@media print { body::before { display: none } }to skip. - iframe — a page embedded in an iframe doesn't inherit the parent's CSS. The URL pattern must also match iframe content for the marker to show inside.
How to plug into JustZix
- Install JustZix.
- Folder "Env markers". Rule per environment:
- Pattern
https://*-staging.company.com/*→ Method 1 (banner) + Method 3 (favicon). - Pattern
http://localhost:*/*→ Method 2 (badge) + Method 5 (title prefix).
- Pattern
- Share link to the whole team — everyone gets the same environment markers.
Install JustZix for free and stop nuking production by "accidental clicks".
Rate this post
No ratings yet — be the first.