Separate the three kinds of configuration
Conflating them is the root of most configuration trouble. There are three distinct categories, with different owners, different review requirements and different storage:
- Product configuration. Things true of the site regardless of where it runs: the colour palette, the navigation structure, the component defaults. Version-controlled, reviewed like code.
- Environment configuration. Things that differ per deployment: the canonical site URL, whether search engines may index, which endpoint the contact form posts to. Version-controlled, but per environment.
- Secrets. Things that must never appear in a repository or in a browser. Held in a vault, injected at deployment, never on a static site at all.
Watch out
On a static site the first two categories are downloaded by the browser and are therefore public. The third category cannot exist. Any design that puts a secret into a JSON file the site fetches has published it.
Make configuration declarative and typed by convention
A configuration file that a non-developer can edit needs an obvious shape and a comment
explaining what each value does. JSON has no comment syntax, so we use a reserved
_notice key at the top of every file.
config/seo.json
{
"_notice": "Environment-specific. siteUrl must match the deployed domain exactly, with no trailing slash.",
"seo": {
"siteUrl": "https://www.example.co.uk",
"defaultTitle": "Northbridge Digital Ltd",
"titleTemplate": "%s | Northbridge Digital Ltd",
"robots": "index, follow"
}
}
Never let a missing value break the page
Configuration is fetched at runtime, and fetches fail. A site that renders a blank header because one JSON file returned a 404 has converted a small problem into an outage.
Safe read with a fallback
export function read(source, path, fallback = '') {
const value = path
.split('.')
.reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), source);
return value === undefined || value === null ? fallback : value;
}
// Usage: never throws, always renders something sensible.
const name = read(company, 'company.name', 'Our company');
The fallback is not laziness. It is the difference between a page with a slightly generic heading and a page with no heading at all.
One file per concern
A single large configuration file becomes a merge conflict magnet and an all-or-nothing permission decision. Splitting by concern lets a marketing colleague edit navigation without touching anything that governs privacy, and lets you review the two differently.
| File | Category | Typical owner |
|---|---|---|
| company.json | Product | Company owner |
| theme.json | Product | Design |
| navigation.json | Product | Marketing |
| seo.json | Environment | Engineering |
| analytics.json | Environment | Engineering, with data protection sign-off |
| privacy.json | Product | Legal or data protection |
Default to the safe value
Where a setting has a privacy, security or SEO consequence, the checked-in default
should be the conservative one. Analytics disabled. Non-production environments
noindex. Cookie categories off until consented.
The reason is asymmetry of failure. Forgetting to enable analytics in production costs
you a fortnight of data. Forgetting to disable it in a test environment, or forgetting
noindex on a staging site, is a data protection question and a duplicate
content problem respectively. Choose the default whose failure mode is cheapest.
Note
Write the environment differences down in a single table in your documentation. Three values that differ between staging and production is a manageable fact; three values that differ and are undocumented is the beginning of an incident report.
Validate it in the pipeline
At minimum: every file parses, every required key is present, and the environment-
specific values match the environment being deployed to. A twenty-line check that
asserts seo.siteUrl equals the domain you are publishing to would have
prevented more canonical-tag incidents than any amount of care.
Treat configuration changes as changes
The last habit is cultural. A pull request that alters one line of JSON deserves the same review as one that alters one line of JavaScript, because it can do the same damage. Organisations that exempt configuration from review are not saving time; they are deferring it until the outage.