arthur@homelab:~$ cat ~/posts/serverless-without-another-platform.md
Serverless Without Another Platform Layer
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:
- infrastructure definitions and repeatable environments;
- least-privilege IAM roles;
- cache keys and invalidation;
- secure cookies, CSRF protection, and callback handling;
- S3 object versioning and rollback;
- deployment ordering between code and content schemas;
- logs, alarms, and cost limits;
- local development and preview environments.
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:
- CloudFront serving a static index from a private S3 bucket.
- One authenticated API endpoint.
- One Lambda that validates a tiny content payload.
- One generated HTML fragment written to a versioned S3 key.
- 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.