Daniel sent us this one — he's been migrating a personal site from WordPress to a static framework, and he had what he describes as the most insanity-inducing all-nighter of his life trying to debug a Vercel deployment with an early agentic coding tool. He made it through, but the experience got him thinking about where the real pain points are for people making that WordPress-to-serverless leap, especially around media. His core question is: when you're serving images and video in this new world, where do they actually live? Repo, object storage, CDN — and how do you know when to move up the chain? Plus, he wants to talk backup strategy across a distributed architecture, because nothing's worse than exporting your blog and discovering the images stayed behind.
That last point is the one that keeps me up at night. Everyone obsesses over the build pipeline and nobody thinks about the disaster recovery until they need it. I've had friends who migrated entire publications — years of work — and only realized six months later that their "complete export" was actually just a pile of HTML files pointing at URLs that would eventually rot. It's the kind of mistake you only make once, but once is enough. Let's start with the repo approach, because that's where everybody starts and that's where the first mistake gets made. The instinct is totally rational — you're coming from WordPress, where everything lived in one database and one uploads directory, and the mental model is "my site is one thing." So you think, fine, I'll put the images in the assets folder, commit them, push, done. And for a small site, that actually works. Until it doesn't.
Which is the title of approximately seventy percent of technical blog posts.
It really is. The specific break point is usually a video file, because Git has a hard file size limit. GitHub caps individual files at a hundred megabytes. GitLab's the same. Bitbucket's the same. So the moment someone uploads a two-minute product demo in 4K, the push fails and they discover Git LFS exists. And Git LFS — Large File Storage — it doesn't actually store your files in the repository. It stores pointers. Little text files that say "the real file is over here on a separate LFS server." So now you've got a split architecture whether you wanted one or not.
It's the distributed systems equivalent of finding out your starter home has a basement you never asked for and it's full of someone else's wiring.
And the pricing on Git LFS is where people get surprised. GitHub gives you one gigabyte of free LFS storage and one gigabyte of bandwidth per month. After that, it's five dollars per month for fifty gigs of storage and fifty gigs of bandwidth. Which sounds fine until you realize that every time someone clones your repo with LFS, it counts against your bandwidth. A ten-megabyte image served a thousand times from a CDN costs pennies. A ten-megabyte image pulled a thousand times through Git LFS burns through your quota in a week.
This is where the clone behavior gets really counterintuitive. Most people assume that when you clone a repo, you're just getting the current state of things. But with LFS, the default clone pulls down every version of every large file that ever existed in the history. You're not just downloading your current hero image — you're downloading the three previous versions of it from when you were iterating on the design.
That's a crucial point that the documentation doesn't surface well. There's a flag — GIT_LFS_SKIP_SMUDGE — that lets you clone without pulling the LFS objects, and then you fetch them selectively. But that's exactly the kind of thing you don't know until you've already blown through your bandwidth quota and you're reading a Stack Overflow answer from 2019 written by someone who is clearly furious.
The tone of those LFS Stack Overflow threads is its own genre. "Why is my bandwidth gone" — asked in despair, answered in exhaustion.
The repo approach isn't wrong so much as it's a ticking clock. It works until your site gets traffic or your media library gets large, whichever comes first.
There's a secondary problem that's less obvious: build times. Every image in your repository has to be processed during the build step. If you're using something like Astro with Sharp for image optimization — which you should be, Sharp is fantastic — every additional image adds to the build time. A site with fifty images might build in thirty seconds. A site with five hundred images might take four or five minutes. At some point your deployment pipeline starts feeling like you're compiling a kernel.
That build time isn't just an annoyance. It changes how you work. When the build takes thirty seconds, you push changes and move on with your life. When it takes five minutes, you start batching your edits, which means you're making bigger, riskier commits. Or you start finding excuses not to publish at all. I've seen small teams where the build time became the de facto reason a blog went dormant. Nobody said "we stopped writing because the pipeline was slow," but the friction was real.
It's death by a thousand paper cuts, and each paper cut is a resized JPEG.
Which brings us to the business user problem Daniel mentioned. The repo approach assumes the person writing blog posts is comfortable with VS Code, Git, and markdown. That's fine for developers. It is absolutely not fine for the marketing team.
I want to push on this a little, because I think there's a specific scenario where it falls apart that's worth describing. Imagine a marketing manager who needs to publish a press release with three embedded product photos. In WordPress, she logs in, creates a new post, drags the images in, hits publish. Total time: maybe four minutes. In a repo-based static workflow, she has to find the images, resize them to reasonable dimensions, name them according to whatever convention the project uses, place them in the correct directory, write markdown image tags with the right relative paths, commit, and push. And if she gets any of that wrong — wrong directory, wrong filename, wrong syntax — the images are broken and she might not even know until after the post goes live.
What's worse, she might break the build entirely. A malformed markdown file can fail the deployment, and now the press release isn't delayed — it's just not happening until someone who understands the pipeline fixes it. That's the moment where the marketing director calls you and says "I thought we migrated to something better.
This is the part of the static site migration that everyone hand-waves past in the tutorials. They show you how to set up Astro with a headless CMS in fifteen minutes, and they gloss over the fact that the content editor experience is the entire ballgame. If the marketing director can't drag and drop an image into a post, the whole thing is dead on arrival. WordPress won that war a decade ago. It's not the best architecture, but the editing experience is genuinely good for non-technical users.
They won it by being opinionated in exactly the right places. WordPress makes assumptions about how media should work — upload it here, we'll handle the thumbnails, we'll handle the srcset, we'll handle the metadata. Those assumptions are constraints, but constraints are what make a tool usable by people who don't want to think about image processing pipelines.
WordPress is the Toyota Camry of content management. Nobody's excited about it, but you can hand the keys to absolutely anyone and they'll get where they're going.
That's the tension Daniel's pointing at. The static site architecture is better — faster, more secure, cheaper to host — but the editing experience requires you to solve the CMS problem, and the media problem is the sharpest edge of that. Uploading an image in WordPress is one click. In a static site workflow, you're either committing it to a repo, uploading it to an S3 bucket, or relying on a headless CMS to handle it, and each of those has its own friction.
Let's talk about the second tier. Daniel mentioned Vercel Blob, which is their managed object store. What's actually happening under the hood there?
Vercel Blob is built on top of Cloudflare R2, which is itself an S3-compatible object store. The pitch is that you get object storage without having to configure AWS. You run one CLI command, you get a bucket, you get an API endpoint, and you can upload files directly from the browser or from your serverless functions. The pricing is twenty cents per gigabyte stored and three dollars per hundred gigs of egress. That's competitive with raw S3, but the real value is the integration. From a Next.js or Astro project deployed on Vercel, it's basically automatic.
Here's where I want to pause on something that sounds trivial but actually matters a lot in practice. That "upload directly from the browser" feature — what does that actually look like for the person using it?
It means you can have a file input on a web page, the user selects an image, and JavaScript uploads it straight to the blob store without going through your server first. Vercel gives you a presigned URL — a temporary upload permission — and the browser sends the file directly. Your serverless function never touches the file bytes. That matters because serverless functions have payload limits. Vercel functions cap at four and a half megabytes for the request body. If you tried to upload a ten-megabyte image through a function, it would fail silently or throw an error that's maddeningly vague. Direct browser uploads bypass that entirely.
You avoid the "why is my upload failing" debugging session that would otherwise be inevitable the first time someone tries to upload a high-res photo from their phone.
And for the business user problem, you'd pair this with a headless CMS that knows how to talk to the object store, right?
Something like Sanity or Strapi or even a hosted solution like Contentful. The CMS provides the WordPress-like editing interface, and when an editor uploads an image, the CMS handles pushing it to the object store and generating the URL. The editor never sees a bucket name or an API key. They just drag, drop, publish.
Let's get concrete about what that integration actually involves, because "the CMS handles it" is doing a lot of work in that sentence. In Sanity, for example, you configure an asset source — you tell Sanity "when someone uploads an image, use this Vercel Blob endpoint." That's a few lines of configuration. But you also need to handle the URL transformation. Sanity stores a reference to the asset, and your frontend code has to resolve that reference into an actual URL that points to your object store. If you get that resolution wrong, your site renders fine in development and has broken images in production.
Which is the kind of bug that makes it through code review because the reviewer is looking at a development environment where everything works.
It's exactly the kind of thing that would have caused Daniel's all-nighter. He probably had the images uploading correctly, but the URL resolution was pointing at a Sanity CDN URL that wasn't the one Vercel expected, or vice versa. The agentic coding tool would have been suggesting fixes based on patterns it had seen, but those patterns might have been for a different CMS or a different storage provider. The subtle incompatibilities are what eat hours.
The elegant solution — CMS plus object storage — sounds clean in a conference talk and has about seventeen sharp edges in practice. It works, but you have to know which edges to file down.
Which sounds elegant until you realize you're now paying for a headless CMS, object storage, and a deployment platform, and you've assembled a Rube Goldberg machine to replicate what WordPress did in one click. The elegance is real, but so is the complexity.
That's the trade-off. You're trading operational simplicity for architectural purity. The question is whether the trade is worth it for your use case. For a developer's personal blog, probably not — you can just put images in the repo and call it a day. For a small business site with non-technical editors and moderate traffic, the object storage plus headless CMS approach is probably the sweet spot. And for high-traffic production sites, you need to go further.
Which brings us to the CDN layer. And Daniel's question is interesting — he associates CDNs with the BBC and Reuters, and wonders whether blob storage is good enough for most people. I think there's a misconception baked into that framing.
There absolutely is. A CDN isn't just for massive scale. The value proposition of a CDN isn't just "serve lots of files fast" — it's "serve files from a location close to the user." And that matters at surprisingly low traffic levels if your audience is geographically distributed. If your site is hosted on Vercel and your object storage is in us-east-one, a visitor in Sydney is making a round trip to Virginia for every image. That's two hundred milliseconds of latency per asset, minimum. A CDN puts that image in a data center in Sydney, and the latency drops to single digits.
Two hundred milliseconds doesn't sound like much in isolation, but let's put it in context. If your page has a hero image, three thumbnail images, and a logo — that's five round trips. Five times two hundred milliseconds is a full second of latency just for the images. Add that to the time it takes to load the HTML, the CSS, and the JavaScript, and your "fast static site" is taking three or four seconds to load for someone in Australia. Users feel that. They don't know why it's slow, but they know it's slow.
It's the website equivalent of showing up to a party in a tuxedo jacket and sweatpants. Half of you is optimized.
And Cloudflare has been really aggressive about blurring the line between object storage and CDN. Cloudflare R2 — their object storage — has zero egress fees. That's the headline feature. You pay for storage, but you don't pay for bandwidth. And because R2 is integrated with Cloudflare's CDN, your files are automatically served from the edge with no additional configuration. It's object storage that behaves like a CDN by default.
Zero egress fees is a fascinating business decision. Amazon charges for egress. Google charges for egress. Cloudflare is essentially saying "we'll eat that cost to get you into our ecosystem." It's the razor blade model inverted — give away the blades, sell the handle.
It's working. R2 adoption has been significant, especially among developers who got burned by surprise egress bills on S3. There are horror stories of people running up thousands of dollars in bandwidth charges because a blog post went viral or a GitHub repo got popular and suddenly their images were being served millions of times. With R2, that scenario costs you nothing extra.
I want to underscore how easy it is to hit that scenario without realizing it. Someone posts your article on Hacker News, it sits on the front page for a few hours, and suddenly your S3 bucket has served three hundred thousand requests for a two-megabyte header image. That's six hundred gigabytes of egress. On S3, that's about fifty-four dollars — not ruinous, but not nothing, and it happened while you were sleeping. If the same post gets picked up by a few news aggregators over the course of a week, you can easily cross into four figures.
AWS does provide billing alerts, but the default alert threshold is set by you, and most people don't set it until after the first surprise. It's a rite of passage at this point. You haven't really done cloud infrastructure until you've woken up to a billing email that made you question your career choices.
The practical recommendation is: if you're on Vercel, Vercel Blob is the path of least resistance and it's fine for most use cases. If you're worried about egress costs or you want more control, Cloudflare R2 is the better choice and it gives you CDN distribution automatically. And if you're on Netlify, they have their own equivalent in Netlify Large Media.
Netlify Large Media is actually interesting because it's built on Git LFS under the hood but manages it transparently. You configure it once and then you just use git push like normal, and Netlify handles the LFS backend for you. It's a clever way to paper over the Git LFS pain points, but it does lock you into Netlify.
Everything locks you into something. The question is which lock you're comfortable living with.
That's the real subtext of this entire conversation. Every architectural choice is a bet on which vendor you're willing to be dependent on. WordPress locks you into a PHP host and a MySQL database. Vercel Blob locks you into Vercel. R2 locks you into Cloudflare. Self-hosting locks you into your own ability to not break things at 3 AM.
That last one — the self-hosting lock — is the one people romanticize. "I control my own infrastructure.You also control your own outages, your own security patches, and your own 3 AM alerts when a disk fills up. There's no free lunch. There's just different people paying for the lunch.
Which is a perfect segue to the backup question, because backup strategy is where vendor lock-in becomes existential. Daniel's nightmare scenario is real — you export your blog posts, the export looks complete, and then you discover six months later that every image reference is a broken link because the export didn't include the actual files.
I've seen this happen. Medium's export is notorious for it. You get a zip file with your posts in HTML, and the images are still hosted on Medium's CDN. If Medium ever goes away or changes their URL structure, all your images vanish. And because you thought you had a backup, you didn't keep local copies. It's the data equivalent of finding out your insurance policy doesn't cover flooding the day after the hurricane.
Medium is just the most famous example. This pattern shows up everywhere. Shopify exports product data but not product images. Squarespace gives you page content but not the media library. The export button gives you the illusion of completeness, and the illusion holds until you actually try to reconstruct your site from the export and discover it's a skeleton.
Let's walk through what a real backup strategy looks like for a distributed static site architecture. You've got at least three things that need backing up: the codebase, the content, and the media assets. And they live in different places.
The codebase is the easy one. It's in Git. You've got the remote on GitHub or GitLab, and you've probably got local clones on multiple machines. That's effectively backed up already. If your house burns down and GitHub gets hit by an asteroid on the same day, you've got bigger problems than your website.
Though I will say — and this is a small thing but it's burned people — if you're using environment variables for API keys and you're not storing those anywhere outside of your deployment platform's dashboard, you should fix that. Your Git repo doesn't have your.env file, which is correct security practice, but it means your "backup" is missing the configuration that makes the site actually run. You need a separate, secure store for those.
That's a great catch. A password manager or a dedicated secrets manager. Something encrypted that isn't your repo. Otherwise you'll restore the code and the content and then spend an afternoon resetting API keys.
The content is the middle tier. If you're using a headless CMS, your blog posts live in their database. Most headless CMS platforms have export functionality, but the quality varies wildly. Sanity lets you export your entire dataset as newline-delimited JSON. Contentful has a CLI export tool. Strapi gives you database-level access if you're self-hosting. The key is to actually test the export and verify that it contains everything — body text, metadata, author information, and image references.
The image references are the critical link. Your content export should give you the URLs of every image in every post. You then need a separate process that takes that list of URLs and downloads the actual files.
Which is where Daniel's instinct about custom scripting is probably correct, and also where it feels hacky because it is hacky. There's no standard tool for "back up my entire distributed website." You wind up writing a script that queries the CMS API, extracts all the image URLs, downloads them from the object store, and packages everything into a tidy archive. It's not complicated code, but it's code you have to write and maintain.
That's the part everyone skips. You write the backup script once, it works, you set up a cron job, and then six months later your CMS changes their API and the backup has been silently failing for five months. You need to actually restore from the backup periodically and verify that the site comes back up with all its images intact. Netflix calls this "chaos engineering." For a personal blog, it's just basic hygiene.
How often is "periodically" in practice? If someone's listening and thinking "I should do this," what's a reasonable cadence?
For a personal site that updates a few times a month, quarterly is probably fine. Run the restore test on a Sunday afternoon, make sure everything works, update the script if the API changed. For a business site with daily updates and a marketing team depending on it, I'd do it monthly at minimum. The cost of the test is an hour of your time. The cost of not testing is potentially losing months of content.
There's a simpler approach if you're willing to accept some constraints. If you're using a Git-based CMS like Decap CMS or TinaCMS, your content lives in the repository as markdown files. Your images might still be in object storage, but at least the content and the code are in the same place. That collapses your backup surface from three things to two.
For the images specifically, if you're on Cloudflare R2, there's a tool called rclone that can sync an R2 bucket to local storage or to another cloud provider. It's basically rsync for cloud object stores. You point it at your R2 bucket, it downloads everything, and you can run it on a schedule. The same tool works for S3, Google Cloud Storage, Backblaze B2, and about forty other backends. It's the closest thing to a standard solution for this problem.
Rclone is one of those tools that, once you know it exists, you wonder how you lived without it. It's the Leatherman of cloud storage.
It's free and open source. The syntax is straightforward — rclone sync remote:bucket-name /local/backup/images. That's it. You put that in a cron job or a GitHub Action, and you've got automated image backups. The only thing to watch out for is that rclone sync is one-directional by default. If you delete an image from your bucket and then run sync, it won't delete it from your backup. That's actually what you want for a backup, but it's worth knowing that rclone copy and rclone sync behave differently.
The recommended backup stack is: Git for code, CMS export for content, rclone for media, all triggered by a scheduled job, with periodic restore tests. That's not terrible. It's more moving parts than a WordPress backup plugin that dumps everything to a single zip file, but it's also more resilient because each component is independently restorable.
There's an argument that the distributed architecture actually makes backups safer. In the WordPress model, everything is in one database. If that database gets corrupted, you lose everything — posts, pages, images, settings, the works. In the distributed model, a corrupted image bucket doesn't touch your content. A corrupted CMS database doesn't touch your code. The blast radius of any single failure is smaller.
That's the optimistic framing. The pessimistic framing is that you now have three blast radii to worry about instead of one.
This is why I love you, Corn. You always find the cloud inside the silver lining.
It's a gift. So let's pull this together. We've got three tiers for serving media. Tier one: put images in the repository. This is fine for developer personal sites with low traffic and no non-technical editors. It breaks down when you hit Git's file size limits, when your build times get painful, or when you need to hand the keys to a marketing team. Tier two: object storage. This is the sweet spot for most small to medium sites. Pair it with a headless CMS for the editing experience, accept that you're paying for a few services, and sleep well knowing your repo isn't bloated. Tier three: add a CDN layer, which in practice means either using a platform that includes CDN distribution automatically — Vercel, Netlify, Cloudflare — or configuring one explicitly. The inflection point for tier three is geography, not traffic volume. If your users are on multiple continents, you want edge distribution.
I'd add a tier zero, which is: stay on WordPress. If you don't have a specific reason to move — if your site is performant, your hosting is cheap, and your editors are happy — the static site migration might not be worth the complexity. The architecture is better in the abstract, but better architecture that nobody asked for is just a hobby project.
The glockenspiel of web development. Beautiful, precise, and completely unnecessary for the song you're actually playing.
I was going to say the sous-vide of content management, but glockenspiel works.
Daniel also mentioned an all-nighter debugging a deployment with an early agentic coding tool. And that's worth touching on, because the tooling has improved in a short time. A year ago, asking an AI agent to debug a Vercel deployment was an exercise in circular frustration. The agent would suggest a fix, the fix would break something else, and you'd go around in loops until 4 AM.
The fundamental problem was that the agents didn't have access to build logs. They were guessing. They'd see an error message, suggest a configuration change, and have no way to verify whether the change actually resolved the issue. You were the feedback loop, and you were a slow and increasingly irritated feedback loop.
The irritation compounds because the agent doesn't get tired and you do. At hour one, you're collaborative. At hour three, you're just mechanically copying the agent's suggestions and pasting error messages back. At hour five, you're questioning your career, your life choices, and whether the internet was a mistake.
Now the agents can read logs, inspect deployment output, and iterate with actual data. It's not perfect — you still get the occasional death spiral where the agent tries the same fix three times — but the success rate on first-shot deployments has gone from "coin flip" to "usually works." That changes the calculus for someone considering the WordPress migration. A year ago, you needed to understand the deployment pipeline well enough to debug it yourself. Now you can rely on the agent to handle a lot of that.
That's the broader shift Daniel's pointing at. The objection "we don't have a development team, so we're sticking with WordPress" is losing force. Not because the architecture got simpler — it didn't — but because the tools for managing the complexity got dramatically better. Claude Code and similar agents can scaffold a project, configure the CMS integration, set up the object storage, and debug the deployment. You still need to know enough to verify what it's doing, but you don't need to know enough to do it from scratch.
It's the difference between needing to be a mechanic and needing to know when the mechanic is lying to you. The bar is lower.
Though I'd argue the second skill is harder to acquire. But that's a different episode.
Let's talk about one more thing before we wrap. Daniel mentioned the plugin problem — that moment in WordPress where you realize you've installed fifteen plugins to get the CMS to do what you want, and you're managing compatibility updates and security patches for code you didn't write and barely understand.
The plugin spiral. It starts innocently — you need a contact form, so you install a forms plugin. Then you need SEO metadata, so you install Yoast. Then you need caching, then image optimization, then a page builder, then custom post types, and suddenly your WordPress install is a Jenga tower of third-party code. And every update is a prayer.
The prayer isn't always answered. I've seen a single plugin update take down an entire e-commerce site because it introduced a conflict with the theme's version of jQuery. The store was down for six hours while someone figured out which of the twenty-eight plugins was the culprit. That's not a theoretical risk — that's a Tuesday in WordPress land.
The static site approach inverts this. Instead of adding plugins to extend WordPress, you're composing services that each do one thing. Your CMS handles content. Your object store handles media. Your CDN handles distribution. Your build tool handles optimization. Each piece is simpler and more focused, but you now have orchestration complexity instead of plugin complexity.
Orchestration complexity is the kind of complexity that developers are better at managing. Plugin complexity is opaque — you don't know what that caching plugin is doing under the hood, and when it conflicts with the image optimization plugin, you're debugging a black box. Orchestration complexity is transparent — you can see how the pieces connect, and when something breaks, you can trace the failure through the system.
It's the difference between a car with a sealed hood and a car where you can pop the hood and see the engine. Both can break, but only one lets you diagnose the problem.
For the record, I am not a mechanic. I'm a retired pediatrician who DJs on weekends. But I know which hood I'd rather open.
To answer the prompt directly: start with object storage unless your site is tiny and you're the only editor. Reach for the CDN when your audience spans continents, not when your traffic hits some magic number. Back up with Git for code, CMS export for content, and rclone for media. Test your backups. And if none of this sounds appealing, WordPress is still there, still works, and still powers forty-three percent of the web for a reason.
That forty-three percent number is from W3Techs, by the way. It's actually gone down slightly in the last couple of years as headless and static architectures have grown, but it's still the dominant player by an enormous margin. Shopify is second at around six percent. The drop-off is a cliff.
Which is its own kind of lock-in, but that's a conversation for another day.
One last thing on the backup front. If you're using Cloudflare R2, there's a feature called "Object Lifecycle Management" that's worth mentioning. You can set rules to automatically transition older objects to cheaper storage tiers, or to automatically delete objects after a certain period. That's not backup — it's the opposite of backup — but it's useful for managing costs on media that doesn't need to live forever. Product screenshots from three years ago probably don't need to be in hot storage.
The counterpoint: storage is so cheap that the mental overhead of deciding what to keep and what to delete often costs more than just keeping everything. Twenty cents per gigabyte per month means a terabyte costs two hundred dollars a year. For most small sites, their entire media library fits in a few gigs. The annual storage cost rounds to a nice dinner.
That's a good way to think about it. If your annual storage bill is less than the cost of the pizza you ordered during that all-nighter debugging session, you're optimizing the wrong thing.
Now: Hilbert's daily fun fact.
Now: Hilbert's daily fun fact.
Hilbert: In the 1940s, researchers studying lichens in the deserts of Niger discovered that certain crustose species exhibit a phenomenon called "goniodomain fluorescence," where the algal layer within the lichen thallus emits a faint amber glow under ultraviolet light — an optical property caused by secondary metabolites that act as a natural sunscreen for the symbiotic algae.
...right.
This has been My Weird Prompts. Thanks to our producer Hilbert Flumingtop. If you enjoyed this episode, you can find more at myweirdprompts.We'll be back next week.