arthur@homelab:~$ whoami

Arthur Sommer

/home/arthur · homelab operator · software builder · perpetual tinkerer

arthur@homelab:~$ cat ~/posts/serverless-without-another-platform.md

Serverless Without Another Platform Layer

Revisiting the AWS primitives behind a small serverless publishing system: CloudFront, S3, Lambda, API Gateway, and Cognito.

mtime 2025-03-14 · 3 min read

In high school, around 2018, I built a serverless web application for a friend using the then-new-to-me AWS Lambda. It was assembled directly from AWS services, before I had a polished platform telling me where functions, static assets, authentication, and deployments should live.

Years later, developer platforms made that experience dramatically nicer. I understand the appeal, but I am still interested in the primitives underneath. How small can a content application remain if I use AWS directly and avoid an additional platform layer?

This is an architecture sketch, not a framework announcement.

One distribution, two kinds of origin

CloudFront can be the public entry point for both static content and an API:

                         +--> S3: HTML, CSS, assets, fragments
browser --> CloudFront --|
                         +--> API Gateway --> Lambda

Most requests should terminate at S3. Lambda should handle authenticated changes and control-plane work, not render the same public HTML on every page view.

That distinction keeps the read path boring and cacheable.

Publishing content instead of rendering it repeatedly

My first thought was to have functions query a database and return HTML fragments on demand. For a small site whose changes are mostly editorial, that is wasteful. A write can render the changed content once and store the result in S3.

The public request then becomes:

GET /
browser -> CloudFront -> S3 -> CloudFront -> browser

If the page uses independently cached fragments, those are ordinary S3 objects too:

GET /fragments/project-list.html
browser -> CloudFront -> S3 -> CloudFront -> browser

This resembles an MPA with small islands of independently updated content, but the public system remains static at request time.

Authentication and writes

An administrator can authenticate through Cognito’s hosted UI. After the callback establishes a secure session, write requests go through API Gateway to Lambda:

POST /api/pages/1/sections/1
browser
  -> CloudFront
  -> API Gateway
  -> authentication
  -> Lambda
  -> render and write an S3 object
  -> invalidate or version the affected cache path

The browser’s editing interface is another static asset loaded only for an authenticated session. Public visitors never need the administration bundle.

The parts a platform normally hides

Using primitives does not remove complexity. It makes me own it:

A platform earns its keep by making many of those decisions coherent. The direct-AWS version is worthwhile only if its smaller dependency graph is more valuable than the developer experience I must build myself.

What I would build first

I would start with the narrowest vertical slice:

  1. CloudFront serving a static index from a private S3 bucket.
  2. One authenticated API endpoint.
  3. One Lambda that validates a tiny content payload.
  4. One generated HTML fragment written to a versioned S3 key.
  5. A deployment definition that can recreate everything.

No general framework, plugin system, or visual editor until that loop is secure, observable, and pleasant to operate.

The lesson is not that nobody needs a platform. It is that the underlying architecture is approachable, and understanding it makes the platform’s value and tradeoffs much easier to judge.