Technical Decisions
Software architecture is a collection of tradeoffs. The choices made for this application reflect its current requirements, expected maintenance, and operating environment rather than an attempt to use every technology that might eventually be useful.
This page records some of the more significant decisions made while building the site. The purpose is not only to document what was chosen, but also why it was chosen, what alternatives were considered, and what conditions might justify revisiting the decision.
Django as the Application Platform
The site is built as a Django application rather than treating the content management system as the entire application.
Django provides the underlying application framework, including URL routing, forms, authentication, database access, configuration, and other application-level functionality. This allows features that do not naturally belong in the CMS to remain ordinary Django components.
Wagtail is used as a content management layer within that application.
This distinction is intentional. Most public-facing content is managed through Wagtail, while functionality such as site search can be implemented as a conventional Django view when there is no benefit to representing it as CMS-managed content.
This keeps the responsibilities of the framework and CMS clear while allowing either approach to be used where it fits best.
Wagtail for Structured Content
Wagtail was selected because the site contains content that benefits from editorial control while still requiring predictable structure and presentation.
Rather than giving editors unrestricted control over page markup and layout, the site provides reusable content components for common structures such as headings, rich text, cards, images, screenshots, notes, and architecture diagrams.
Editors describe the content and its meaning; the application remains responsible for its HTML structure, responsive behavior, and accessibility.
For a site maintained by a single developer, a simpler approach could have been sufficient. Wagtail is used here partly because the same content model scales well to applications maintained by larger groups of content editors, and because it reflects the architecture used in many of the projects this site documents.
Conventional Django Views Where Appropriate
Not every URL needs to correspond to a Wagtail page.
For example, search results are generated by a conventional Django view. The page exists in response to a query and does not contain content that a content manager needs to create, organize, or publish.
Global components such as navigation, search, and SEO metadata are designed to work in both Wagtail page views and conventional Django views.
This avoids introducing CMS objects simply to satisfy an implementation pattern and keeps application behavior separate from managed content where appropriate.
Private S3 Storage Behind CloudFront
Static files and uploaded media are stored in Amazon S3 and delivered through CloudFront.
The S3 bucket is not publicly accessible. CloudFront is given permission to retrieve objects from the bucket using Origin Access Control, while the application uses separate credentials when it needs to write files.
This creates a clear separation between storage and public delivery and avoids making the storage bucket itself a public endpoint.
For a site of this size, a simpler public S3 configuration would work. The private-bucket approach adds relatively little operational complexity, however, and provides a better default security boundary.
Heroku for Application Hosting
The application is deployed on Heroku using managed application and PostgreSQL services.
Other hosting platforms could run the application at lower direct infrastructure cost. Heroku was selected because it provides a predictable deployment model, managed database services, environment-specific configuration, Review Apps, and pipelines with relatively little platform administration.
It is also the platform used for many of the applications represented in this portfolio.
The decision therefore considers operational cost as well as hosting cost. For this application, reducing the amount of infrastructure that must be configured and maintained is worth more than minimizing the monthly hosting bill.
No Background Worker Infrastructure
The application does not currently use Celery, Redis, or dedicated worker processes.
Those tools are useful when an application has asynchronous work such as large imports, external API synchronization, queued email, or other tasks that should not execute during an HTTP request. This site currently has no workload that requires that infrastructure.
Adding those services in anticipation of a hypothetical future requirement would increase deployment, monitoring, documentation, and maintenance requirements without providing a current benefit.
The application can add background processing later if its requirements change. The absence of that infrastructure today does not prevent it from being introduced when there is a concrete reason to do so.
Lightweight SEO Implementation
The site uses Wagtail’s existing page metadata together with a small application-specific SEO layer rather than adding a larger SEO package.
The implementation provides the metadata the site currently requires, including page titles, descriptions, canonical URLs, robots directives, social images, and Open Graph and Twitter metadata.
A third-party package was considered, but the available options either provided substantially more functionality than this site required or did not yet provide the desired level of support for the version of Wagtail used by the application.
Because the required functionality is small and based largely on existing Wagtail fields, maintaining the implementation locally currently represents less complexity than introducing another dependency.
This decision can be revisited if the site’s SEO requirements become more complex or an appropriate package provides a clear maintenance advantage.
Plain CSS and Minimal Front-End Tooling
The site’s custom styling is written in CSS and uses Bootstrap for its general layout and component foundation.
No Sass compilation, JavaScript bundler, or Node-based build pipeline is currently required.
Those tools can provide substantial value for applications with larger front-end codebases. For the relatively small amount of custom CSS and JavaScript used by this site, however, a separate build toolchain would add another collection of dependencies and maintenance requirements without solving a current problem.
Front-end tooling can be introduced later if the size or complexity of the front-end code makes that tradeoff worthwhile.
First-Party Analytics Consent
The site uses a small first-party consent mechanism for Google Analytics rather than a general-purpose consent-management platform.
Several third-party consent-management solutions were considered. The site’s requirements, however, are intentionally narrow: Google Analytics is currently the only optional tracking service. Introducing a larger consent framework would add a dependency and additional abstraction for a single binary preference.
The first-party implementation therefore:
asks only whether analytics may be enabled;
stores the preference in browser local storage;
prevents the Google Analytics library from loading before consent;
allows the preference to be changed from the site footer;
disables analytics and removes its cookies when consent is revoked; and
keeps the Google Analytics measurement ID environment-specific.
This is deliberately not intended to be a general-purpose consent-management platform. If the site later requires multiple optional services, advertising technology, regional consent behavior, or substantially more complex privacy requirements, the decision should be revisited and a maintained consent management platform considered.
Build for Current Requirements
Several of these decisions reflect the same general principle: build for the requirements that exist while avoiding choices that unnecessarily restrict reasonable future changes.
This does not mean anticipating and implementing every feature an application might eventually need. Background workers, dedicated search infrastructure, distributed caching, additional monitoring services, and other components should be introduced when they solve an actual problem.
At the same time, today’s simplest solution should not create an unnecessary obstacle to tomorrow’s reasonable requirement.
Conventional frameworks, modular components, clear boundaries, and well-defined interfaces often provide enough flexibility without requiring additional abstraction or infrastructure.
The objective is to keep the application understandable and appropriately simple while leaving reasonable paths for it to evolve.