🔧 AI Nachrichten Debian is Voting on Whether to Allow AI-Assisted Contributions(23.08.2026 um 09:34 Uhr)
🔧 AI Nachrichten The Linux Kernel Is Approaching 2,000 CVEs Per Release(29.08.2026 um 20:00 Uhr)
⚠️ Malware / Trojaner / VirenCitrix Adds a Linux-Powered Escape Hatch For Compromised Windows PCs(30.08.2026 um 17:34 Uhr)
🔧 AI Nachrichten Debian is Voting on Whether to Allow AI-Assisted Contributions(23.08.2026 um 09:34 Uhr)
🔧 AI Nachrichten The Linux Kernel Is Approaching 2,000 CVEs Per Release(29.08.2026 um 20:00 Uhr)
⚠️ Malware / Trojaner / VirenCitrix Adds a Linux-Powered Escape Hatch For Compromised Windows PCs(30.08.2026 um 17:34 Uhr)

🔧 Programmierung 🕛 vor 1 Monat 9 Min Lesezeit
0

I Moved My Portfolio Off a Raspberry Pi to Cloudflare Workers

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

For about a year, my portfolio at is that new projects should use Workers — that's where the investment goes. Starting on Pages today means migrating later for no reason.



No GitHub Actions. I planned to write a workflow. I didn't need one. Cloudflare has native Git integration: connect the repo, and every push to main deploys. No workflow file, no API token in secrets, no CI minutes. For a site with no build step, Actions is a moving part that buys nothing. Add it the day you actually have a build.



The entire config is nine lines:




CODE
// wrangler.jsonc
{
"name": "my-site",
"compatibility_date": "2026-08-03",
"assets": {
"directory": "./public"
}
}






No main entry. A purely static site needs no Worker script at all.









Gotcha 1: everything in your assets directory is public



That "directory": "./public" matters more than it looks.



Workers serves every file in the assets directory. My repo root contained a deploy script with my Pi's SSH target in it. Had I pointed assets.directory at the repo root, that file would have been fetchable at example.com/deploy.ps1.



So: site files go in public/, everything else stays outside it.




CODE
public/              <- ONLY this is servable
index.html
styles.css
assets/
wrangler.jsonc <- not servable
.env <- not servable
deploy.ps1 <- not servable






And then verify it, rather than trusting the layout:




CODE
for f in deploy.ps1 .env wrangler.jsonc .gitignore; do
echo "$(curl -s -o /dev/null -w '%{http_code}' https://example.com/$f) $f"
done









CODE
404  deploy.ps1
404 .env
404 wrangler.jsonc
404 .gitignore






This is now a permanent check in my deploy notes. It takes four seconds and it's the difference between "I think it's fine" and "I know it's fine."






Gotcha 2: my hotlinked images had already broken



My site showed logos for four companies I've worked at, each hotlinked straight from the employer's own CDN. I'd never thought about it again.




CODE
for u in "$EXL_URL" "$BNY_URL" "$COGNIZANT_URL" "$TCS_URL"; do
curl -s -o /dev/null -w "%{http_code}\n" -A "Mozilla/5.0" "$u"
done









CODE
200
200
200
403 <- TCS






One had been returning 403 — hotlink protection — for who knows how long. My live site had a broken image and I had no idea.



This is worth internalizing beyond my specific case: hotlinked assets are an availability dependency on someone else's infrastructure. I was migrating for reliability while four images sat hostage to four other companies' CDNs. Any of them can add hotlink protection on a random Tuesday.



Downloading them into assets/logos/ fixed the broken one, removed four third-party dependencies, and stopped leaking visitor IPs to those companies. Should have done it years ago.






Gotcha 3: an SVG without a viewBox won't scale



The TCS logo needed a new source. I found one on Wikimedia Commons, dropped it in, and then noticed this:




CODE
<svg
width="956.69287"
height="342.99213"
id="svg2">






width and height, but no viewBox.



That combination breaks CSS sizing. With style="height:30px" and no viewBox, the SVG's internal coordinate system stays fixed at 956×343 user units while the viewport shrinks to 30px tall — so you see the top-left corner of the drawing, clipped, rather than the whole logo scaled down.



The fix is one attribute:




CODE
viewBox="0 0 956.69287 342.99213"






Same numbers as the width and height. Now the content scales to fit whatever box CSS gives it.



I verified the framing was right by extracting every coordinate from the path data and checking the bounds:




CODE
layer transform : -161.07883 -76.646639
after transform : x[35.4, 921.3] y[35.4, 307.6]
viewBox : 0 0 956.7 343.0
content fits : YES






A symmetric 35.4-unit margin on all four sides — the viewBox was correct.



That padding had a second consequence: the mark only fills 79% of its box, so at height:30px it rendered noticeably smaller than the neighbouring logos. Bumping it to 38px (30 × 343/272) matched their visual weight.






Gotcha 4: www was a whole saga



The apex went live in one click. www fought me for an hour.



First: adding www as a Custom Domain failed with Hostname already has externally managed DNS records. Fair — the tunnel owned that record. I deleted it.



Then: No zones match www.example.com. Start by adding the domain to Cloudflare.



This one is a trap. The domain was already on Cloudflare — the apex Custom Domain had succeeded on the same Worker in the same zone minutes earlier. Following that "add the domain" prompt would have tried to onboard a duplicate zone. If you see this and you know your domain is already there, don't follow the prompt.



I gave up on the dialog and used a proxied CNAME plus a Redirect Rule instead. Which produced:



HTTP 522. Connection timed out.



The reason is worth understanding: a proxied CNAME www → example.com does not chain through to the Worker. www had no Worker binding and no real origin, so Cloudflare tried to fetch from an origin that didn't exist and timed out.



The Redirect Rule is what fixes it — redirect rules fire at the edge before Cloudflare attempts any origin fetch. The CNAME alone will always 522. You need both.



Then the rule matched only the homepage. Every other path still 522'd. The cause was the match field:




CODE
❌ URI Full  wildcard  "www.example.com"
✅ Hostname equals "www.example.com"






URI Full is the entire URI — https://www.example.com/styles.css. A bare hostname pattern never matches it. Matching on Hostname covers every path on that host.



Final working configuration:




CODE
Match:  http.host eq "www.example.com"
Action: Dynamic redirect
Value: concat("https://example.com", http.request.uri.path)
Status: 301, preserve query string






One last trap: right after saving, results were mixed — some paths 301, others still 522. That's propagation across edge nodes, not a broken rule. Test twice, a minute apart, before concluding anything is wrong. I nearly rewrote a rule that was already correct.






Where it landed











































Before After
HTML document
DYNAMIC, round-trip to my house

HIT at the edge
Deploy
scp from my laptop
git push
Rollback re-run scp and hope one click, every deploy retained
Availability one Pi, one ISP, one SD card Cloudflare's anycast network
Broken TCS logo yes fixed
Cost electricity $0


After every change I re-ran the same check rather than trusting that it had worked:




CODE
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/
curl -sI https://example.com/ | grep -i cf-cache-status
curl -sL https://example.com/ | wc -c






Status, cache state, and byte count — three seconds, and it catches the difference between "deployed" and "actually serving what I think it's serving."






Takeaways





  1. Measure before you migrate. Half my performance justification was already false.


  2. If you're already behind Cloudflare, cutover risk is near zero — the edge IPs never change.


  3. Everything in your assets directory is public. Put site files in a subdirectory and verify with curl that the rest 404s.


  4. Hotlinked images are an availability dependency. One of mine had already broken silently.


  5. An SVG with width/height but no viewBox will not scale — it clips.


  6. www is a separate hostname, not a synonym. It needs its own configuration and it will not fall back to your apex.



The migration itself was maybe ten minutes of actual work. The rest was discovering what had quietly rotted while the site sat there working — a dead image, an unscalable SVG, a performance story I'd told myself without checking. That part was more useful than the move.






The site is rubarajan.dev, now served entirely from Cloudflare's edge and deployed with git push.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Bits und so #1021 (Passwort für Laufwerk)
1 Quelle
Bits und so #1022 (Wie Weißbier)
1 Quelle
KI-Agenten entdecken deutsches Wiki als Kommunikationskanal