Optimizations for the Website service (#9)

This PR contains the work done to provide optimizations to the current service, such as a health-check endpoint, pre-renders static HTML pages, and hardens the error page's CSP.

To provide further details about the work:

* Added the `HealthController` controller serving GET `/health` with a static JSON payload.
* Added the `CachedHTMLResponse` response, which renders a static HTMLDocument to bytes once and reuses them per request (no Content-Length, so responses stay compressible).
* Integrated the response into the `RootController` and the  `NotFoundMiddleware` middleware to avoid re-rendering on hot paths.
* Added a `RouterMethods.addRoutes(_:)` extension and switched the router in App+build to use it.
* Moved the inline style from the `ErrorPage` page into a dedicated style file so the CSP needs no inline-style escape hatch.
* Fixed the `IndexPage` page path inconsistencies.
* Written the `README` file.

Reviewed-on: rock-n-code/loud-amsterdam#9
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
This commit is contained in:
2026-06-28 14:18:04 +00:00
committed by javier
parent 6b6389cb0f
commit 847058d642
16 changed files with 508 additions and 95 deletions
@@ -1,5 +1,4 @@
import Hummingbird
import HummingbirdElementary
/// Serves the website's root routes.
///
@@ -12,14 +11,21 @@ import HummingbirdElementary
///
/// - Note: `Context` is the request context the routes are resolved against, and must match the
/// context of the router the routes are added to.
public struct RootController<Context: RequestContext> : Sendable{
public struct RootController<Context: RequestContext>: Sendable {
// MARK: Properties
/// The landing page, rendered once at initialization and reused for every request.
private let cache: CachedHTMLResponse
// MARK: Initializers
/// Creates a root controller.
public init() {}
public init() {
self.cache = .init(IndexPage())
}
// MARK: Properties
// MARK: Computed
/// The routes served by the controller.
///
@@ -40,29 +46,27 @@ public struct RootController<Context: RequestContext> : Sendable{
// MARK: - Helpers
private extension RootController {
// MARK: Methods
/// Handles a request for the landing page.
/// - Parameters:
/// - request: the incoming request.
/// - context: the context the request is resolved against.
/// - Returns: an HTML response that renders the ``IndexPage``.
/// - Returns: the cached ``IndexPage`` response.
@Sendable
func index(
request: Request,
context: some RequestContext
) -> HTMLResponse {
.init {
IndexPage()
}
) -> Response {
cache.response()
}
}
// MARK: - Constants
extension RouterPath {
private extension RouterPath {
/// A namespace for the ``RootController`` route paths.
enum Root {
/// The path of the landing page.