Delivery & Operations
Security headers and site hardening
A public marketing website may not store payment information or customer accounts. That does not make security irrelevant. It changes the threat model. The most useful security work often starts by asking what the site can avoid exposing, storing, or executing in the first place.
Attack surface is an architecture choice
Every system we add creates something else that can fail or be abused. A public admin panel is an attack surface. A database is an attack surface. A plugin ecosystem is an attack surface. A third-party script is an attack surface. An API key exposed to the browser is an attack surface.
The safest unnecessary feature is often the feature that never gets added. This is one reason we do not add a database to a marketing site simply because databases sound professional. Read Why we do not add a database by default.
Static and server-rendered pages have a useful security property
A statically generated service page does not need a live content database query. There is no public CMS login involved in rendering it. There is no content API token required by the browser. The deployed artifact can remain available even if the editorial workflow is offline.
That does not eliminate every risk. It does remove entire categories of runtime dependency.
Secrets stay on the server
Credentials do not belong in public client bundles. When a feature needs a secret, the logic using that secret should run in a server environment. Environment variables supply the value at deployment time. The repository can remain transferable without containing the credential.
This also creates a clearer handoff because the code explains which capability exists while the environment controls which credential grants access.
Forms should collect only what they need
Data minimization is a security control. If a contact form only needs a name, email, phone number, and project description, adding ten more personal fields does not make the lead better automatically. It does create more data that has to travel through the system.
For many of our marketing builds, form submissions are delivered to a specialized provider instead of stored in a local website database. The reasoning is covered in Why many of our forms hand data to a provider.
Validation belongs at the trust boundary
Client-side validation helps the user. It does not make a request trustworthy. A server route or downstream provider still needs to validate what it receives. One of our gated-resource flows checks that the request body can be parsed, normalizes the email value, verifies explicit consent, and rejects bad input before forwarding anything.
That is a simple feature with a clear trust boundary.
Browser security headers are useful when they match the site
Headers can reduce certain browser behaviors or tighten how the site is embedded and interpreted. Examples can include:
- frame restrictions
- MIME type protections
- referrer policy
- permissions policy
- content security policy
The exact set should reflect the application. A copied header bundle can break legitimate scripts, images, forms, fonts, embeds, or third-party integrations. We prefer deliberate configuration over "security header score" chasing.
Removing unnecessary platform disclosure is easy hygiene
Some of our Next.js configs disable the X-Powered-By header. That does not make the framework secret. It simply avoids sending an unnecessary response header that provides no user value. This is a small example of the general rule: do not expose or enable things merely because the default allows them.
Preview environments have their own security questions
A preview marked noindex is still reachable if it is not access-protected. If the branch contains sensitive information, search directives are not enough. Use authentication or deployment protection. Noindex protects search behavior. Access control protects confidentiality. We treat those as different systems.
Dependencies still require maintenance
A custom-coded site is not magically immune to dependency risk. Frameworks, packages, build tooling, and third-party integrations need updates. The advantage is that the dependency graph can remain relatively small and visible. We prefer a smaller number of well-understood dependencies over a stack of overlapping plugins whose interactions are difficult to reason about.
Security and performance often point in the same direction
Fewer scripts mean less browser work and less third-party code. No unnecessary database means less runtime infrastructure and less stored data. No public CMS means fewer credentials and fewer application surfaces. Local assets reduce external requests. Server-first rendering reduces the amount of application logic exposed to the browser.
Security and performance are not the same goal, but simpler architecture frequently helps both.
Hardening is not a claim that the site cannot be breached
No serious engineering team can promise that. Hardening means reducing avoidable risk, using secure boundaries, keeping dependencies current, validating inputs, controlling secrets, minimizing stored data, and responding to the actual threat model. The best security decision is often architectural and boring. That is exactly why we like it.
From explanation to proof
Where this connects to the work
This category documents the work that keeps a custom site maintainable after launch and makes handoff possible without technical hostage-taking.