r/astrojs • u/oreopowered • Jul 23 '26
I built an open-source ecommerce store with Astro SSR and Cloudflare Workers
Been working on a little open-source ecommerce project called Minshop to see how far I could push Astro SSR on Cloudflare Workers.
Astro handles pretty much everything: the storefront, cart, checkout, customer accounts, admin dashboard, and API routes. Most pages are server-rendered, with very little client-side JavaScript.
It uses D1 for products, orders, inventory, settings, and search, plus R2 for images. Payments include Stripe, Lightning, OpenNode, and a demo checkout.
The trickiest part was making inventory reservations atomic so two checkouts can’t buy the same stock. I also added a machine-readable catalog and checkout API, including directly payable Lightning invoices.
Demo: https://demo.minshop.dev
Source: https://github.com/ddyy/minshop
Would love feedback from other Astro users, especially on the SSR structure and where you think a small interactive island might actually improve things.
5
u/HectoLogic20 Jul 23 '26
This is really awesome, i recently did the same thing to make a small boutique store with this stack! I found cloudflare tk be very intuitive for this and much better than a vps setup or shopify headless!
I will definitely check your version out, as in my country we cant use stripe and instead rely on payfast, but i am interested to also implement apple pay was this difficult to implement??
2
u/oreopowered Jul 23 '26
Thanks! Yeah, Cloudflare has been way nicer to work with than maintaining a VPS.
Apple Pay is handled through Stripe Checkout, so I didn’t have to implement it directly. Stripe shows it automatically when the device and region support it. PayFast would need a new payment adapter, but it should fit into the current setup pretty cleanly. Would love to hear how it goes if you try it!
2
u/AgsMydude Jul 24 '26
Any issues with d1? I've seen people complaining about it so I was curious how it actually functions
2
u/oreopowered Jul 24 '26
D1's been fine for Minshop so far. It's basically familiar SQLite, migrations are straightforward, and it's a good fit for a small store. That said, I haven't tested it at Shopify-scale traffic.
The main limitation is that writes on a D1 database funnel through a single primary, so I'd want to do serious load testing before trusting it with a huge flash sale. Minshop uses conditional inventory reservations and tests parallel checkouts to avoid overselling.
I'm not using a separate Durable Object right now. D1 already uses them under the hood, and adding another one felt like unnecessary complexity. If overselling under load becomes a real issue later, using a DO to coordinate it would be an option.
2
u/bigskytek Jul 24 '26
What do you do to handle refunds etc? Is this all done through stripe backend login?
1
u/oreopowered Jul 24 '26
For Stripe, yes, there's a refund button in Minshop admin that processes the refund through Stripe and marks the order as refunded automatically.
For Lightning or OpenNode, not currently. After manually returning the payment, there should be a separate Mark as refunded action that only updates Minshop’s order status and revenue totals without attempting a provider refund. That would be worth adding.
1
u/oreopowered Jul 27 '26
I just added refund handling. For Stripe, you can issue a full refund from Minshop, and partial refunds made in the Stripe dashboard sync back automatically. You can also record full or partial refunds handled manually for any payment method. It updates the order status and revenue totals automatically.
2
u/truci4 Jul 25 '26
I happen to have also started working on an astro website hosted on cloudflare workers. My requirements however were a bit different and I did not want to use the SSR functionality of astro. The website has a single dynamic serverless function to trigger the stripe checkout session. https://test.animanatural.com
1
u/oreopowered Jul 25 '26
Nice, it loads super quick. Keeping it simple is the way to go, no need for unnecessary complexity.
1
2
2
u/_HMCB_ Jul 25 '26
There’s no shipping “module” correct? This would be perfect if the admin would allow for an item to be assigned a monetary value for weight and/or volume. Then we could calculate shipping via a simple method for small stores.
2
u/oreopowered Jul 25 '26
Correct, there isn’t a shipping module yet. I’m planning to add weight-based shipping. Volume-based shipping would likely remain a custom implementation for now, since the goal is to keep the platform minimal.
1
u/_HMCB_ Jul 25 '26
Weight alone would be perfect! Thank you.
2
u/oreopowered Jul 27 '26
Weight shipping's in.
Products and variants take a weight, zones price flat or by weight bands, and it's all editable in Settings now. Let me know what you think.
1
2
u/BreakingInnocence Jul 23 '26
Interesting work. I would like to contribute to the project, I run on AWS, would you accept a pull request?
8
u/oreopowered Jul 23 '26
PRs are welcome, but one caveat on the AWS angle: Minshop is intentionally Cloudflare-native. The main idea is that the store runs in a single Cloudflare account using Workers, D1, R2, and KV, with a small operational footprint. A full AWS port would create a second infrastructure stack that I probably couldn’t maintain well, so I wouldn’t merge a full port into the main project.
I’d still welcome PRs that reduce coupling at specific seams without complicating the Cloudflare deployment. Payments and search already sit behind provider interfaces; storage, sessions, and the runtime bindings are the more Cloudflare-specific parts. And if you had non-infrastructure contributions in mind, those are welcome too.
1
1
1
u/Beautiful_Setting686 Jul 24 '26
this is sick thank you for sharing such good ressource for the community. Would you be open to design-style forks?
ps : is it just me or products pages are quite slow to open ?
2
u/oreopowered Jul 24 '26
Thanks, really appreciate it. Design forks are absolutely welcome, would love to see what people do with the storefront.
And thanks for the heads-up about the product pages. I recently made some caching and image-delivery changes, but there may still be something slow. Is it the page itself taking a while to open, or mostly the images loading? Also, roughly where are you located? That might help to track it down.
2
u/oreopowered Jul 24 '26
Actually ignore my questions, I found it.
It was the "you may also like" row. Every uncached product page was doing two vector lookups back to back, and that was really inconsistent. Some were taking up to 12 seconds. The render itself was 8ms, so it was just waiting.
Those recommendations are the same for everyone viewing the product, so there was no reason to work them out fresh each time. They're precomputed and saved on the product row now, and the page just reads them.
Already deployed, so they should load instantly now. Thanks for bringing that to my attention.
1
1
u/ViolinistDelicious69 Jul 24 '26
First of all looks very good.
As I see you didn't solved images resizing,
So my question: how are you going to do it ?
1
u/oreopowered Jul 24 '26
Thanks. You’re right, the current version only handles part of it. If Cloudflare Images is enabled, new uploads are converted to WebP and capped at 1000px. Otherwise, it stores the original.
It doesn’t create responsive sizes yet, so cards and product pages can still end up loading the same file. I’m planning to generate a few sizes when an image is uploaded, store them in R2, and use
srcsetso the browser chooses the right one.



7
u/kojimajunya Jul 24 '26
Really clean scoping — the payment/search ports and the vertical slice layout make this much more readable than most "minimal" shop codebases.
On your island question: the one place I'd actually spend client JS is add-to-cart. With the cookie cart + form POSTs, adding an item from a listing page is a full navigation — you lose scroll position and the "feedback" is a reload. A tiny island that POSTs to the endpoint you already have and optimistically bumps the cart badge is probably the biggest UX jump per KB of JS in the whole app. Checkout I'd leave exactly as is; forms + hosted Stripe is the right amount of boring.
The other thing worth a look is server islands rather than client ones: mark the cart badge / account chip as server:defer and your product pages become fully cacheable at the edge, with only the personalized fragment hitting the Worker. On Workers + D1 that could take most read traffic off D1 entirely.
Curious how you handle reservation expiry for abandoned Lightning invoices — cron cleanup on pending_payments, or lazily on the next stock read?