Most AI web apps follow the same deployment pattern: a Next.js frontend on Vercel, backend APIs on a managed cloud provider, object storage somewhere in the same region. It works fine. It's predictable. It's what you know.
When building 123audio.org, we went a different direction. Everything runs on Cloudflare's edge infrastructure — Workers for compute, R2 for file storage, D1 for the database, Pages/OpenNext for the frontend. No traditional backend server. No centralized origin that all requests route through.
Here's the reasoning behind that choice, and what the tradeoffs actually look like in practice.
Why Edge Made Sense for Audio Processing
The core operation in a transcription tool is: user uploads a large file, your system sends it to an ASR API, you get back a large text response, you do something with it.
That sounds like it should happen at a centralized server. And for most apps, it would. But consider what happens when a user in Singapore uploads a 200MB audio file to a server in us-east-1:
- The file travels from Singapore to Virginia
- The server accepts it, does some validation, maybe stores it
- The server makes an API call to the ASR provider
- The response comes back
- The result is sent back to Singapore
The latency on that first hop — user to origin — is significant. It's also completely avoidable. Cloudflare Workers runs at 300+ edge locations worldwide. The same upload from Singapore goes to the nearest edge node, which is a few milliseconds away instead of a few hundred.
For audio specifically, this matters more than it would for a CRUD app. Files are larger. The upload itself is the user-facing latency, not just the API response time.
The Storage Decision: R2
Audio files present specific storage requirements:
- They're large (a 60-minute recording at standard quality is 50-100MB)
- They need to be available for processing immediately after upload
- They need to be retrievable by the user afterward
- They should be deletable when no longer needed
R2 is Cloudflare's S3-compatible object storage. The key advantage: no egress fees. S3 charges for data transferred out; R2 doesn't. For an audio platform where users are uploading and downloading files regularly, this isn't a footnote — it's a significant portion of the cost model at scale.
R2 also integrates natively with Workers, which means the upload, processing, and retrieval all happen within the same infrastructure layer. No cross-cloud data transfer, no latency spike when a Worker reads from storage.
The Database Layer: D1
Cloudflare D1 is SQLite-based, distributed across the edge. For most developers coming from Postgres or MySQL, this feels like a step sideways — SQLite is what you use for development environments, not production, right?
The reframe: D1 isn't trying to be Postgres. It's trying to be the database that lives closest to your compute. For the data that an audio platform needs to read and write on every request — user sessions, usage records, credit balances, transcription history — you want that data as close to the execution environment as possible.
The D1 schema for 123audio includes tables for app_users, usage_ledger, transcription_history, credit_balances, and subscription records. These are read-heavy, mostly small records, exactly the profile where D1's edge locality is an advantage over a centralized relational database.
For complex analytical queries or large relational joins, D1 isn't the answer. But for the operational data layer of a transactional app, it's faster and cheaper than it looks on paper.
The Next.js on Cloudflare Problem
This is the part that required the most engineering time.
Next.js 16 with App Router is built with Vercel as the deployment target. Most of its server-side capabilities — Server Components, API routes, ISR, image optimization — are designed around Vercel's Node.js runtime. Cloudflare Workers runs a V8 isolate, not Node.js. These are meaningfully different environments.
The bridge is OpenNext, an open-source project that adapts Next.js for non-Vercel deployment targets. It handles the translation between Next.js's expectations and what Cloudflare's runtime can provide.
Getting this working correctly required understanding which Next.js features are compatible with the Workers runtime and which require workarounds. A few things we ran into:
No native Node.js APIs. Workers don't have access to the fs module, child_process, or anything that assumes a persistent filesystem. Any code that reaches for these breaks. The fix is mostly about being deliberate: use Web APIs (fetch, Request, Response, ReadableStream) instead of Node equivalents everywhere.
Cold start behavior. V8 isolates have different cold start characteristics than containerized Node.js. For most requests this is imperceptible, but for very first requests after a period of inactivity, there's a brief warm-up. This is a known characteristic of the Workers model, not a bug.
Environment variable handling. Cloudflare's environment variable model (bindings) is different from Node's process.env. The OpenNext adapter handles most of this, but secrets for API integrations needed explicit binding configuration in wrangler.toml.
Auth: Supabase for What It's Good At
Authentication is handled by Supabase, shared across projects. This was an explicit decision to separate concerns: Supabase is good at auth, and rebuilding auth from scratch has no upside.
The separation is clean: Supabase handles user identity (sign up, sign in, session management). Everything else — usage data, subscription records, transcription history — lives in D1. The user's Supabase ID is the foreign key that links the two systems.
This has a practical benefit beyond cleanliness: the business data layer is independent of the auth provider. If Supabase pricing or reliability becomes a concern later, migrating to a different auth provider doesn't touch the core application data.
What the Architecture Gets Right
In production, the edge deployment model handles the actual performance characteristics that matter for audio:
Large file uploads are fast because they go to the nearest edge node, not a distant origin.
Database reads are low-latency because D1 runs at the same edge location as the Workers compute.
Cost is predictable because R2's no-egress-fee model means file-heavy workloads don't produce surprise bills.
Scaling is implicit because Workers scale to handle traffic spikes without capacity planning.
What It Gets Wrong (Or at Least Makes Harder)
Local development experience. Simulating the full Cloudflare stack locally requires wrangler dev, which is functional but not identical to production. Edge cases exist where local behavior diverges from what Workers actually do.
Observability. Cloudflare's logging and tracing tooling is improving but isn't as mature as AWS CloudWatch or GCP's operations suite. For debugging production issues, you sometimes have to work harder to get the signal you need.
D1 limitations. SQLite's concurrency model and write throughput aren't appropriate for every workload. For 123audio's read-heavy operational data, it's a good fit. For a use case requiring high-frequency concurrent writes, you'd need a different database strategy.
The Bottom Line
The Cloudflare-native stack isn't the right choice for every project. But for a consumer audio tool where upload latency, storage costs, and global performance all matter, it aligns well with the actual technical requirements.
If you're building something similar and evaluating deployment targets, the decision framework is straightforward: if you need Node.js-specific libraries or complex server-side state, go Vercel. If you're working primarily with Web-standard APIs and care about global edge performance and predictable costs, the Cloudflare stack is worth the learning curve.
123audio.org is live and running entirely on this architecture. If you want to see it in practice, the free tier handles one transcription per day without requiring an account.




Top comments (0)