Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
I’ve spent the last seven years elbow-deep in WordPress—fortune-500 landing pages, SaaS dashboards, you name it. Last quarter a long-time client (let’s call them “Bloom Coffee Co.”) slid into my inbox with a familiar panic:
“Black-Friday traffic is coming. Our checkout is already wheezing and our AWS bill just eclipsed our bean budget.”
Challenge accepted.
I promised:
Here’s the exact playbook I followed
Traditional WooCommerce = PHP + MySQL on every request = expensive autoscaling.
Headless WooCommerce = WordPress builds JSON once, Netlify’s edge CDN serves static pages and serverless functions handle the cart/checkout.
Bloom’s traffic spikes to 30 k sessions on launch days—Netlify’s free tier still fits because static assets don’t spin up servers.
Layer | Tool | Cost / mo |
---|---|---|
WordPress back-end | Kinsta “Application” plan | \$7 (yes, really) |
Front-end framework | Next.js 14 (App Router) | \$0 |
Theme / UI | Astra Pro + Elementor Pro | \$59 + \$59 / yr (affiliate) |
Product images | Cloudinary free tier | \$0 |
Payments | Stripe Checkout (serverless) | 2.9 % per txn (same as before) |
(Affiliate honesty: Astra & Elementor links pay me ~30-50 % if you upgrade—at no extra cost to you.)
wp-json/wc/v3/
) via functions.php
.npx create-next-app@latest bloom-headless --typescript --tailwind --eslint --app --src-dir
cd bloom-headless
npm install @apollo/client graphql stripe
Create .env.local
:
WP_GRAPHQL_ENDPOINT=https://admin.bloomcoffee.co/graphql
NEXT_PUBLIC_STRIPE_PK=pk_live_xxxxxxxx
STRIPE_SK=sk_live_xxxxxxxx
lib/woocommerce.ts
import { ApolloClient, InMemoryCache } from '@apollo/client';
export const wooClient = new ApolloClient({
uri: process.env.WP_GRAPHQL_ENDPOINT,
cache: new InMemoryCache({
typePolicies: {
Product: { keyFields: ['id'] },
},
}),
});
app/products/[slug]/page.tsx
import { wooClient } from '@/lib/woocommerce';
import { gql } from '@apollo/client';
import AddToCart from '@/components/AddToCart';
async function getProduct(slug: string) {
const { data } = await wooClient.query({
query: gql`
query GetProduct($slug: ID!) {
product(id: $slug, idType: SLUG) {
id
name
description
price(format: RAW)
images {
nodes {
sourceUrl
}
}
}
}
`,
variables: { slug },
});
return data.product;
}
export default async function ProductPage({ params }: { params: { slug: string } }) {
const product = await getProduct(params.slug);
return (
<main className="max-w-6xl mx-auto p-8">
<h1 className="text-4xl font-bold">{product.name}</h1>
<img src={product.images.nodes[0]?.sourceUrl} className="w-1/3 rounded" />
<div dangerouslySetInnerHTML={{ __html: product.description }} />
<p className="text-2xl font-semibold mt-4">${product.price}</p>
<AddToCart productId={product.id} />
</main>
);
}
components/AddToCart.tsx
'use client';
import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PK!);
export default function AddToCart({ productId }: { productId: string }) {
const handleClick = async () => {
const stripe = await stripePromise;
const { sessionId } = await fetch('/api/checkout', {
method: 'POST',
body: JSON.stringify({ productId }),
}).then((r) => r.json());
stripe?.redirectToCheckout({ sessionId });
};
return (
<button
onClick={handleClick}
className="mt-4 bg-amber-600 text-white px-6 py-3 rounded hover:bg-amber-700"
>
Buy Now
</button>
);
}
Serverless checkout API route lives inside app/api/checkout/route.ts
.
git init && git remote add origin git@github.com:bloomco/bloom-headless
netlify deploy --prod --dir=.next
Netlify settings:
npm run build
.next
.env.local
.Bloom adds new roasts daily. We don’t want to rebuild the whole site every time.
save_post
with 60-second grace period.Metric | Old AWS EC2 | New Stack |
---|---|---|
Hosting | \$320/mo | \$7 Kinsta + \$0 Netlify |
Average TTFB | 1.9 s | 76 ms |
Lighthouse | 68 | 99 |
Cart Abandonment | 73 % | 58 % |
Build time (2,000 products) | N/A | 90 s |
Host | Plan | Why You’d Upgrade |
---|---|---|
Kinsta | Application \$14/mo | Dockerized Node, autoscale workers |
WP Engine | Headless \$20/mo | Atlas CDN, built-in preview URLs |
Affiliate links:
Headless WooCommerce looks simple on paper, but Stripe webhooks, stock sync, and preview modes can eat weekends.
If you’d rather sell coffee than wrestle GraphQL:
📧 Email arnab.hstu@gmail.com with the subject “HEADLESS ECOMMERCE” and I’ll send a fixed-price quote or hop on a 15-min call.
Now go forth and caffeinate the internet—without caffeinating your AWS bill.