Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Windows Tipps & SecurityGarmin Cirqa im Test: Fitness-Tracker ohne Display(22.09.2026 um 10:30 Uhr)
Windows Tipps & SecuritySicher online bezahlen: 7 Methoden im Praxis-Check(22.09.2026 um 09:00 Uhr)
Sichere Programmierunghas_tokens: true is a boolean. 476 of 791 have no market behind them.(22.09.2026 um 10:00 Uhr)
Sichere ProgrammierungClaude Code Hooks – Safety Through Invariants(22.09.2026 um 10:04 Uhr)
Sichere ProgrammierungYour MCP Tool Just Returned a Secret. Did It Need To?(22.09.2026 um 10:05 Uhr)
Windows Tipps & SecurityGarmin Cirqa im Test: Fitness-Tracker ohne Display(22.09.2026 um 10:30 Uhr)
Windows Tipps & SecuritySicher online bezahlen: 7 Methoden im Praxis-Check(22.09.2026 um 09:00 Uhr)
Sichere Programmierunghas_tokens: true is a boolean. 476 of 791 have no market behind them.(22.09.2026 um 10:00 Uhr)
Sichere ProgrammierungClaude Code Hooks – Safety Through Invariants(22.09.2026 um 10:04 Uhr)
Sichere ProgrammierungYour MCP Tool Just Returned a Secret. Did It Need To?(22.09.2026 um 10:05 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Building a Freelance Platform on Cloudflare Workers: The Good, The Bad, and The Pyodide

Building a Freelance Platform on Cloudflare Workers: The Good, The Bad, and The Pyodide Six months ago I sat down to build aiomniu — a freelance marketplace for AI and tech projects. The constraints were simple: zero budget, fast load t…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!




Building a Freelance Platform on Cloudflare Workers: The Good, The Bad, and The Pyodide



Six months ago I sat down to build aiomniu — a freelance marketplace for AI and tech projects. The constraints were simple: zero budget, fast load times, and a backend I could write in Python.



The stack I ended up with: Cloudflare Workers + Pyodide + FastAPI on the backend, Next.js 15 + OpenNext on the frontend, and Cloudflare D1 for the database.



This is what I learned — the parts that worked, the parts that broke, and the parts I'm still not sure were worth it.









Why Cloudflare Workers?



I did the obvious comparison:






































Platform Cost for MVP Cold Start Python Support
Vercel Free (but DB adds up) Fast Serverless functions only
AWS Lambda Cheap but complex 200ms-1s Good (via Layers)
Railway $5/mo Fast Native
Cloudflare Workers Free tier is generous Sub-ms Pyodide (Python→Wasm)


For me it came down to two things: free tier and edge distribution. Cloudflare Workers gives you 100k requests/day at no cost, and your code runs in 330+ locations. Hard to beat for a bootstrap budget.



The catch is that Python on Workers runs through Pyodide — CPython compiled to WebAssembly. It's not serverless Python in the traditional sense. It's Python that has been through a compiler, stuffed into a Wasm binary, and told to behave. Most of the time it does. Sometimes it doesn't.









The Good Parts






1. Deploying is Surprisingly Simple



Once you have the pipeline, deployment is one command:




uvx --from workers-py pywrangler deploy






It reads your pyproject.toml, bundles FastAPI and all its dependencies, and pushes it to the edge. No Docker. No Lambda Layers. No VPC configs. Coming from AWS, this felt like cheating.






2. D1 is Just SQLite — and That's Great



D1 is Cloudflare's serverless SQLite. It supports proper SQL, joins, indexes, transactions. For an MVP with ~60 tables, it's been rock solid.



The best part is local development. I keep a local aiomniu.db file and switch connection logic based on the environment:




# db.py
def _get_local_conn():
conn = sqlite3.connect("aiomniu.db")
conn.row_factory = sqlite3.Row
return conn

# On Workers, same SQL through D1
async def query_all(env, sql, *params):
db = env.DB
result = await db.prepare(sql).bind(*params).all()
return [_clean_nulls(dict(r)) for r in result["results"]]






Same SQL. Same schema. Just different connection code. This saved me countless hours during development.






3. Next.js + OpenNext Just Works



The frontend is a separate Worker built with @opennextjs/cloudflare. It converts the Next.js build output into a Workers-compatible bundle. SSG pages pre-render at build time, ISR pages revalidate hourly.



The SEO setup is clean — each page uses generateMetadata() for dynamic meta tags and JSON-LD:




export async function generateMetadata({ params }): Promise<Metadata> {
const { slug } = await params;
return {
title: `Hire ${SKILLS[slug].name} Developers | aiomniu`,
description: `Find pre-vetted ${SKILLS[slug].name} developers for your project.`,
};
}









4. Programmatic SEO Actually Works



With zero domain authority, I couldn't compete head-to-head. So I built alternatives pages — targeting people searching for competitors:
























Page What It Targets
/alternatives/upwork "Upwork alternatives"
/alternatives/fiverr "Fiverr alternatives"
/hire/react-developer/in/sydney "Hire React developer Sydney"


Each page is data-driven. Add one line to a config array, and a new SEO page appears with its own meta tags, content, and JSON-LD:




SKILL_CITY_COMBOS = [
('react-developer', 'shanghai'),
('python-developer', 'beijing'),
('react-developer', 'sydney'),
# 250+ more combos
]






This generates 300+ SEO pages from about 50 lines of configuration. Google has indexed most of them. The long-tail strategy is working — we get impressions for queries we'd never rank for on the homepage.









The Not-So-Good Parts






1. Pyodide is NOT Python 3.12 on Your Laptop



This is the biggest trap. It looks like Python, but:




  • No thread pool. At all. Every route handler must be async def. Synchronous routes crash with RuntimeError: can't start new thread — because there's literally no thread to start.


  • Dynamic imports don't work. __import__() and importlib.import_module() fail silently. All imports must be static from X import Y at the module top level.


  • The ASGI bridge was broken for months. The asgi import in workers-py had a version check bug that rejected valid versions. It was fixed in workers-py 1.11.0, but I spent a week debugging before figuring that out.


  • JsProxy is everywhere. Every JavaScript value arrives as a JsProxy object. You need explicit conversion functions:





def _to_py(val):
try:
from pyodide.ffi import to_js as _
if hasattr(val, 'to_py'):
val = val.to_py()
except ImportError:
pass # local dev — no Pyodide
return val

def _null(val):
"""D1 NULL comes through as empty dict {}. Convert back to None."""
if isinstance(val, dict) and len(val) == 0:
return None
return val






I ended up writing five utility functions just to handle Pyodide type conversions: _to_py(), _deep_to_py(), _null(), _clean_nulls(), and _convert_bind_params(). Each one addresses a different flavor of the same problem.






2. The 138KB Upload Wall



Pyodide's multipart form parsing dies at roughly 138KB. Not gracefully — it just hangs or crashes.



I had to add explicit file size checks before any upload:




async def upload_file(request: Request, ...):
content_length = int(request.headers.get("content-length", 0))
if content_length > 138 * 1024:
return JSONResponse({"error": "File too large"}, status_code=413)






This forced me to use Cloudflare R2 for file storage — uploads go directly via presigned URLs instead of through the Python Worker. It's the right architecture for production, but discovering it through a silent crash was frustrating.






3. The Worker Bundle Size Limit



The free plan limits Worker code to 10MB gzipped. My FastAPI + Pydantic + dependencies bundle is ~2.15MB, so I had room. But it's a hard ceiling — every new dependency needs a pip install size check.









The One Bug That Cost Me Days



D1 returns SQL NULL values as empty JsProxy objects, which Pyodide's .to_py() converts to empty Python dicts {}. Not None. An empty dict.



So a query like:




SELECT bio, avatar FROM users WHERE id = ?






Where bio is NULL returns {"bio": {}, "avatar": {}} instead of {"bio": None, "avatar": None}.



I discovered this when I saw "{}" rendered on a user profile page. Not a great look.



The fix runs on every single query result:




def _clean_nulls(d: dict) -> dict:
if not isinstance(d, dict):
return d
return {k: _null(v) for k, v in d.items()}






It works. But it's the kind of thing you only discover by accidentally shipping it to production.









What I'd Do Differently






1. Skip Pyodide for New Projects



If I were starting today, I'd use Workers with JavaScript/TypeScript for the API layer, or Hono (the framework built for Workers). Pyodide's quirks add too much overhead. The "write Python, deploy to edge" promise is real, but the edge is jagged.






2. Go Next.js from Day One



The site originally had a Vue 3 frontend. When I migrated to Next.js 15, everything got faster — SSG eliminated initial load time, SEO metadata became cleaner, and the developer experience was noticeably better.






3. Connect GSC Earlier



I didn't set up Google Search Console until after the first 100 pages were indexed. I have no idea what the early crawl behavior was. Connect it on day one — it's free and gives you crawl stats, index coverage, and search query data.









The Numbers



After 3 months of zero-budget operation:





  • ~300 SEO pages indexed (mostly long-tail skill/city combos)

  • Sub-100ms median response time


  • ~$0 hosting cost (still on the free tier)



The site is live at aiomniu.top if you want to see it in action.









Resources








Built something on Workers + Pyodide? What broke for you? I'd honestly love to hear — misery loves company.



— aiomniu-pilot

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Building a Freelance Platform on Cloudflare Workers: The Good, The Bad, and The Pyodide

Thematisch verwandte Begriffe: Building, Freelance, Platform, Cloudflare · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-94426 | A vulnerability was determined in xuxueli xxl-job up to 3.5.0. The impac…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick