Set Up a Headless Storefront With Next.js in a Weekend

by Emma Rodriguez
Set Up a Headless Storefront With Next.js in a Weekend

You've probably heard "headless commerce" thrown around like it's reserved for companies with six-figure dev budgets. Maybe you even clicked away from a few articles once they started talking about GraphQL schemas and microservices architecture. Fair enough.

But here's the thing: the core idea is simpler than the buzzword makes it sound. Headless just means your store's front end (what shoppers see) is a separate app from your back end (where products, orders, and inventory live). You connect them through an API. That's it.

And with Next.js — a React framework that handles a lot of the hard stuff for you — a solo operator or a two-person team can realistically spin up a working headless storefront over a weekend. I know because I did exactly that for my second store, and it cut my page load time from 4.1 seconds to under 900 milliseconds, which pushed conversion rate up about 18% in the first 30 days.

Let's walk through how you'd do it.

Why Bother Going Headless at All?

Before we touch a line of code, it's worth asking whether this is actually worth your weekend. Here are the cases where headless genuinely pays off for small stores:

Speed. Traditional hosted platforms render pages on their servers and send full HTML to the browser. Next.js can pre-render your product pages at build time (called Static Site Generation, or SSG) so they load from a CDN edge node — often in under a second. Google's Core Web Vitals reward that, and faster pages convert better. A 1-second delay in mobile load time reduces conversions by an average of 20%, according to Google's own research.

Flexibility. You're not locked into a theme marketplace. Your checkout can live on the same domain. You can embed your store inside a blog, a landing page, or a mobile app without fighting a plugin ecosystem.

Cost control. A Next.js site deployed on Vercel's free tier handles up to 100 GB of bandwidth per month at no charge. Pair that with a headless backend that charges only for API calls, and you can run a small catalog for under $20 a month total.

If you're doing under $5,000 a month in revenue and your current platform is slow or expensive, this path is worth serious consideration.

What You'll Actually Need Before You Start

No gatekeeping here — but let's be honest about the prerequisites so you don't hit a wall at hour three.

  • Basic JavaScript comfort. You don't need to be a React expert. If you've edited a JavaScript file before and understood roughly what you were doing, you'll be fine.
  • A terminal you're not scared of. You'll run maybe a dozen commands total.
  • Node.js installed (version 18 or higher). Check by running node -v in your terminal.
  • A headless backend or data source. This is where your products live. Good weekend-friendly options include Medusa.js (open source, self-hostable), Swell, or even a simple JSON file for a tiny catalog. We'll use a static JSON file in the examples below so you can follow along without signing up for anything.
  • A Vercel account for deployment. The free tier is genuinely free for this use case.

Got those? Good. Let's build.

Day One: Scaffold the Project and Pull in Your Products

Open your terminal and run:

npx create-next-app@latest my-headless-store

Accept the defaults when it asks about TypeScript, ESLint, and the App Router. The App Router (introduced in Next.js 13) is the current standard and worth learning from the start.

Once it finishes, move into the folder:

cd my-headless-store
npm run dev

Visit http://localhost:3000 and you'll see the Next.js welcome screen. Your store already exists — it's just empty.

Create a product data file. In the root of your project, create a folder called data and inside it a file called products.json. Paste in a small catalog:

[
  {
    "id": "1",
    "slug": "canvas-tote",
    "name": "Canvas Tote Bag",
    "price": 28,
    "description": "Heavy-duty 12oz canvas. Fits a 15-inch laptop.",
    "image": "/images/canvas-tote.jpg"
  },
  {
    "id": "2",
    "slug": "ceramic-mug",
    "name": "Ceramic Mug 12oz",
    "price": 18,
    "description": "Microwave safe. Dishwasher safe. Life safe.",
    "image": "/images/ceramic-mug.jpg"
  }
]

Drop a couple of placeholder images into public/images/ — any JPEGs will do for now.

Build the product listing page. Inside app/, create a folder called shop and a file inside it called page.jsx:

import products from '../../data/products.json';
import Link from 'next/link';
import Image from 'next/image';

export default function ShopPage() {
  return (
    <main style={{ padding: '2rem' }}>
      <h1>Shop</h1>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '1.5rem' }}>
        {products.map((p) => (
          <Link key={p.id} href={`/shop/${p.slug}`} style={{ textDecoration: 'none', color: 'inherit' }}>
            <div>
              <Image src={p.image} alt={p.name} width={400} height={400} style={{ width: '100%', height: 'auto' }} />
              <h2>{p.name}</h2>
              <p>${p.price}</p>
            </div>
          </Link>
        ))}
      </div>
    </main>
  );
}

Visit http://localhost:3000/shop — your product grid is live. That took maybe 20 minutes.

Build individual product pages. Create app/shop/[slug]/page.jsx:

import products from '../../../data/products.json';
import Image from 'next/image';

export function generateStaticParams() {
  return products.map((p) => ({ slug: p.slug }));
}

export default function ProductPage({ params }) {
  const product = products.find((p) => p.slug === params.slug);
  if (!product) return <p>Product not found.</p>;

  return (
    <main style={{ padding: '2rem', maxWidth: '600px', margin: '0 auto' }}>
      <Image src={product.image} alt={product.name} width={600} height={600} style={{ width: '100%', height: 'auto' }} />
      <h1>{product.name}</h1>
      <p style={{ fontSize: '1.5rem' }}>${product.price}</p>
      <p>{product.description}</p>
      <button style={{ padding: '0.75rem 1.5rem', fontSize: '1rem', cursor: 'pointer' }}>
        Add to Cart
      </button>
    </main>
  );
}

The generateStaticParams function tells Next.js to pre-render a page for every product slug at build time. Those pages will be lightning fast because they're just static HTML sitting on a CDN.

End of day one: you have a real, navigable storefront with product pages. Not bad.

Day Two: Wire Up Checkout and Deploy

Here's where most tutorials skip to something complicated. Let's keep it practical.

For checkout, the fastest path for a small headless store is to hand off to a payment processor that handles the cart and payment flow for you. Stripe's Payment Links or Lemon Squeezy both let you generate a URL per product. You paste that URL into your "Add to Cart" button's href, and Stripe handles the rest — including receipts, tax, and order confirmation emails.

Is it as custom as a full cart implementation? No. But it takes 15 minutes instead of 15 hours, and you can upgrade later once revenue justifies the engineering time. For stores doing under 50 orders a month, this is the right call.

Update your button in ProductPage:

<a href={product.checkoutUrl} style={{ display: 'inline-block', padding: '0.75rem 1.5rem', fontSize: '1rem', background: '#000', color: '#fff', textDecoration: 'none' }}>
  Buy Now — ${product.price}
</a>

Add a checkoutUrl field to each product in your JSON, pointing to your Stripe Payment Link.

Deploy to Vercel in about 5 minutes. Push your project to a GitHub repository, then:

  1. Go to vercel.com and sign in with GitHub.
  2. Click "Add New Project" and import your repo.
  3. Leave all settings at their defaults — Vercel auto-detects Next.js.
  4. Click Deploy.

Vercel will give you a live URL in under two minutes. Every time you push a commit to GitHub, it redeploys automatically. That's your CI/CD pipeline, free.

A Real-World Example: What This Looks Like for a Small Merch Store

Imagine you sell six products — three t-shirt designs, two hats, a tote. Your current Shopify Basic plan costs $39 a month, your theme is $180 upfront, and you're fighting slow load times because of three apps running on every page.

With this setup: your hosting is $0 on Vercel's free tier. Your backend is a JSON file you edit in GitHub. Your checkout is Stripe (2.9% + 30¢ per transaction, same as most platforms). Your total monthly fixed cost is effectively zero until you're doing enough volume to need inventory management or a proper headless CMS.

At that point — say, 100+ orders a month — you swap the JSON file for a real backend like Medusa.js or Swell, update the data-fetching calls in your pages, and keep the same front end. Nothing else changes. That's the actual power of the headless model: your presentation layer is independent, so you can upgrade the engine without repainting the car.

Three Things to Do Before You Call It Done

Before you share your new store with anyone, check these off:

  1. Add a meta title and description to every page. In Next.js App Router, export a metadata object from each page.jsx. Search engines and social previews depend on it, and it takes five minutes.

  2. Test on a real phone. Open your Vercel URL on your actual phone, not just browser DevTools. Tap every button. Resize nothing — use it like a customer would.

  3. Set up a custom domain. Vercel makes this straightforward under Project Settings → Domains. A .com costs about $12 a year. Your store deserves a real address.

You Can Ship This

Headless commerce isn't a luxury reserved for funded startups. With Next.js handling the rendering, Vercel handling the hosting, and Stripe handling the money, you've got this guide to optimization that would have cost serious consulting fees five years ago — available to any operator willing to spend a focused weekend on it.

Your next step: run that create-next-app command today, even if you only get through the product listing page. Momentum beats perfection every time. Once you see your own products rendering at sub-second speeds, the rest of the weekend practically builds itself.