Choosing Boring Architecture for the Right Reasons
A decision record for choosing a generated static site and CDN before adding application infrastructure.
Simplicity should be an outcome of analysis
Calling an architecture boring can sound like avoiding engineering. The opposite is usually true. A small system is defensible only when someone has made its requirements, failure modes, operating cost, and evolution path explicit.
This portfolio is a useful example. It needs strong visual design, reusable layouts, project case studies, technical articles, responsive behavior, and fast global delivery. It does not currently need accounts, personalized data, server-side business logic, or real-time collaboration. Those constraints point toward generated static files served through a CDN.
That is not zero infrastructure. Source control, a build environment, DNS, TLS, and an edge delivery platform still exist. The important distinction is that there is no application runtime or database to operate for each request.
Scenario: choosing the first production shape
Imagine three candidate designs for the same portfolio.
The first is a full application stack: a Next.js frontend, API service, PostgreSQL database, container platform, secrets, migrations, dashboards, and deployment pipelines. It can model content dynamically, but almost none of that capability is required.
The second is a hosted Next.js application with a content management system. It offers preview workflows and convenient editing, but introduces a runtime and an external content dependency before there is an editorial team or publishing frequency that needs them.
The third is a small source tree with shared HTML templates, Markdown content, CSS and JavaScript assets, and a deterministic Python build. The build writes plain HTML pages. Git is the content history and review workflow. Cloudflare serves the generated output at the edge.
All three can render the same page. The decision is about what must remain true after launch.
Start with the requirements
The portfolio's current functional requirements are straightforward:
- Present a landing page, resume, project collection, blog, and contact route.
- Reuse navigation, footer, metadata, and page templates consistently.
- Support rich case-study content and responsive architecture diagrams.
- Deliver a living visual background without making content unreadable.
- Work without authentication, per-user state, or server-side writes.
- Deploy from version-controlled content with a reversible history.
Its operational requirements matter just as much:
- Fast first load from geographically distributed locations.
- Minimal attack surface and no secret material in the client bundle.
- Predictable builds and inexpensive idle operation.
- A small maintenance burden for one owner.
- Graceful behavior if JavaScript fails or motion is disabled.
Once these are written down, a database and application server have no job to do.
The chosen architecture
Content lives in Markdown and HTML fragments. Shared templates own the document shell and repeated page structure. A Python generator discovers posts and projects, parses front matter, renders collection cards, applies the correct relative paths, and writes the public HTML tree.
CSS provides the responsive layout, glass surfaces, typography, and architecture diagrams. Client-side JavaScript is an enhancement layer for the animated Dawn gradient, film grain, mobile navigation, and project filtering. The primary content remains in the generated document, so reading and navigation do not depend on a framework booting in the browser.
The deployable artifact is a directory of HTML, CSS, JavaScript, images, and documents. A CDN can cache those files close to readers. A deployment is an immutable source revision plus a reproducible build, and a rollback is a return to a known revision rather than a database restoration exercise.
A request path with fewer failure modes
For a reader, the request path is intentionally short:
- DNS resolves the portfolio domain.
- The edge terminates TLS and selects the deployed asset version.
- The CDN returns the requested HTML and cacheable assets.
- The browser renders the document and progressively applies visual enhancements.
There is no origin database connection, application process, session lookup, or internal API hop on the critical path. Removing those components removes their latency and failure modes as well as their features.
The remaining dependencies still deserve attention. A bad build can create broken links. An incorrect cache policy can serve stale assets. DNS and the hosting provider can fail. Third-party fonts or scripts can slow rendering. Static architecture narrows the operational surface; it does not abolish operations.
What the full stack would cost
The cost of an unnecessary component is larger than its hosting bill. A database adds schema ownership, access control, backup and restore, patching, connection management, migrations, and data-retention decisions. An API adds authentication boundaries, validation, versioning, logging, abuse protection, deployment health, and an on-call surface. A container platform adds its own control plane, policies, upgrades, observability, and failure modes.
Managed services reduce some mechanics, but the owner still has to understand their security model, configuration, billing, data lifecycle, and recovery path. For a read-only publishing site, those responsibilities buy no user-visible capability.
This is the central test for architecture additions: what current requirement does this component satisfy, and is that requirement worth the permanent operational obligation?
Static does not mean unstructured
A pile of copied HTML files becomes difficult to maintain. Static generation avoids runtime complexity while retaining engineering discipline:
- Shared templates prevent navigation and metadata from drifting between pages.
- Front matter supplies titles, descriptions, tags, order, and collection summaries.
- Content files remain readable and reviewable without a browser.
- The generator produces predictable output paths and relative links.
- A link checker validates the generated internal graph before release.
- Source control records who changed content and makes rollback straightforward.
The generator is deliberately small. It supports the content shapes the site uses instead of attempting to become a general-purpose CMS. Its limited Markdown parser is a conscious boundary: when editorial needs become more complex, adopting a maintained parser may be more sensible than growing a custom one indefinitely.
Performance and resilience by construction
Static files are easy to cache because a given deployment's content does not change per user. HTML can be returned without waiting for server rendering, and assets can use long-lived caching when their URLs are versioned.
The page should also remain useful when enhancements fail. The portfolio content is present in HTML; JavaScript does not fetch it after load. Reduced-motion preferences can place background elements once and use static grain. Text and cards have their own contrast surfaces so the animated gradient never becomes a readability dependency.
This is progressive enhancement used as a reliability technique. The premium experience can be dynamic while the information architecture remains boring.
Security changes when there is no runtime
Removing a backend eliminates many common attack paths: no application login, session store, database query surface, server-side template input, or long-lived workload credentials are required to read the site.
The remaining supply chain still matters. Build dependencies and deployment credentials need control. The repository should prevent accidental publication of secrets or private documents. Browser-delivered code needs sensible content security, and the contact route should avoid exposing more personal information than intended.
The threat model becomes smaller and easier to reason about, not empty.
Delivery without ceremony
A practical pipeline for this site can remain short:
- Validate the source front matter and required files.
- Run the static generator.
- Check that generated local links resolve.
- Optionally validate HTML and inspect a preview at desktop and mobile widths.
- Publish the generated artifact through the hosting provider's Git integration.
- Run a small smoke check against the deployed landing page and key routes.
Because there is no mutable production data, rollback is unusually clean. Re-deploying the previous good commit restores both content and presentation. That simplicity is a real reliability feature for a single-owner site.
When static-first stops being enough
Architecture should evolve when a concrete requirement invalidates an earlier assumption. Useful triggers include:
- Authenticated users need private or personalized content.
- Visitors create data that must be validated and stored server-side.
- A search corpus grows beyond practical client-side indexing.
- Preview, scheduling, localization, or multi-author approval needs justify a CMS.
- Contact workflows require protected server-side integration, abuse controls, and durable state.
- Content changes frequently enough that rebuilding the site becomes a delivery bottleneck.
- Real-time collaboration or live operational data becomes central to the experience.
Even then, evolution does not require replacing everything. A managed form endpoint can handle contact submission while the rest remains static. A build-time CMS can improve editing without adding a runtime content dependency. Search can move to a dedicated service independently. Add the smallest boundary that satisfies the new constraint.
The same decision pattern inside a platform team
Consider an internal dashboard that summarizes a daily policy report. If every viewer sees the same approved snapshot, a scheduled job can publish a static artifact to authenticated object storage. Building a long-running API, database, and Kubernetes deployment would create availability and patching obligations without making the report more accurate.
If users later need live filtering over millions of records or must acknowledge findings, those are real stateful requirements. The architecture can then gain an API and data store with a clear reason for each one.
This pattern scales beyond websites: separate the generation of information from its delivery, keep mutable state out of the serving path when possible, and add control planes only when there is something meaningful to control.
A decision record, not a permanent ideology
Static-first is the right answer for this portfolio because the content is public, read-only, versioned, and relatively small. The choice would be wrong for the self-service Platform Console, where durable workflow state, authorization, approvals, retries, and integrations are the product.
Good architecture is contextual. The staff-level habit is not always choosing fewer components; it is refusing to add a component before its responsibility exists, and knowing exactly which signal would justify it later.