Removing friction to better create
When I was younger, I took up film photography as a creative outlet. Learning to develop film in a dark bathroom tub was my first exploration in “shipping” something and hoping it came out like I intended.
Ever since those early days, I’ve developed a strong affinity for keeping photography away from a computer. Even when I was a professional wedding photographer, creating the image during the ceremony was the exciting part. I dreaded the follow up task of sitting at my computer tweaking color and contrast. I wanted to spend more time in the act of photographing.
I get enough screen time from work — photography, for me, should be the opposite of that.

That's a big reason I shoot with a Ricoh nowadays. Not only does it fit in my pocket, but the built-in color science is good enough that I don't need to edit. Every photo I post is straight out of camera. No processing, no presets, no desktop app in the loop.
Shoot it, like it, done.
So when I wanted a place to post those photos — not a portfolio, just a feed — the last thing I needed was a workflow that pulled me back to a screen. But that's exactly what a CMS-based website or service does.
You have to set up an account, download your images, edit them (God forbid), upload and hit publish.
That's a lot of something for what is essentially: photo goes up, photo shows on page.
The CMS dilemma
A CMS solves content management. But not every piece of content needs to be managed. Some content just needs to exist. A photo feed is closer to a log than a document — share it, upload it, good.
Reaching for a CMS here was like building infrastructure for something that wasn’t needed. An overbearing workflow.
You get an admin panel you'll open once a week, a content model with fields you don't need, and a login screen standing between you and posting a photo you took thirty seconds ago.
The friction isn't technical. It's motivational. Every extra step between "I took a photo I like" and "it's on the site" is a reason to not bother. And so the photo never gets shared.
An API endpoint > CMS
If you strip a CMS down to its core job — accept content, store it, make it available — that's just an API endpoint. So that's what I built.
The entire backend for photos.calebhill.me is a single Next.js route handler:
export async function POST(req: Request) {
const auth = req.headers.get("authorization");
if (auth !== `Bearer ${process.env.UPLOAD_SECRET}`)
return Response.json({ error: "Unauthorized" }, { status: 401 });
const form = await req.formData();
const file = form.get("file") as File;
const caption = form.get("caption") as string;
const blob = await put(
`photos/${Date.now()}-${file.name}`,
file,
{ access: "public" }
);
await savePhotoRecord({
id: crypto.randomUUID(),
imageUrl: blob.url,
caption: caption || null,
createdAt: new Date().toISOString(),
});
return Response.json({ success: true, url: blob.url });
}Vercel Blob handles storage. A bearer token handles auth. The metadata is a simple JSON record — id, image URL, caption, timestamp. No schema migrations, no content types, no plugins.
The Shortcut is the client
The second piece is an iOS Shortcut that lives as a quick action in the pull-down menu from the lock screen. When I take a photo I want to post, I pull down, tap the Shortcut, optionally type a caption, and it hits the endpoint. Done.
The Shortcut does four things:
- Receives the image
- Prompts for an optional caption
- Sends a multipart POST to
/api/uploadwith the bearer token - Shows a confirmation
That's it. The lock screen becomes the publishing interface, and the Shortcut is maybe fifteen actions long. I'm planning to add it to the share sheet too, so I can post straight from the Photos app when I'm browsing older shots — one more way to shave off a step.

The feed page is just a fetch
The frontend is equally minimal. The home page reads the photo records and renders them in a grid. Each photo has a hover state that displays that optional caption. The whole site is static-ish — it revalidates on a short interval so new photos show up without a redeploy.
There's no admin panel because there's nothing to admin. The API endpoint is the admin panel. The Shortcut is the content entry form.

Less tooling, more creating
I know this isn't novel architecture. It's a POST endpoint and a cron-job-like fetch. The interesting part isn't the code — it's the decision to keep it simple so that I can focus on what I want to do.
When you encounter a workflow that doesn't fit neatly into existing tools, the instinct is to find a tool that's close enough and bend your workflow to fit it. But sometimes the workflow is simple enough that the right tool is no tool at all — just the primitives your stack already gives you.
Next.js gives you API routes. Vercel gives you blob storage. iOS gives you Shortcuts. Each of those is a building block that already exists in your environment. The "CMS" is figuring out how to get those few things to talk to each other.
The Ricoh got me started on this path. I stopped editing photos not because I don't care about quality, but because the editing was getting in the way of taking new photos. The less I had to do after pressing the shutter, the more I pressed the shutter.
Not every workflow needs a product. Some just need a function.