Build an Ecommerce Store with Google Sheets + Next.js (Complete Guide)
Build an Ecommerce Store with Google Sheets + Next.js (Complete Guide)
Shopify's $29/month plan too expensive for your side project? Tired of monthly fees eating into your profit margins?
There's a better way. Build a fully functional ecommerce store using Google Sheets as your product database and Next.js as your storefront. Total cost: $0-20/month.
In this guide, you'll learn how to build a complete ecommerce system that your non-technical team members can manage.
🤔 Why Google Sheets + Next.js for Ecommerce?
Free Forever (or nearly free)
No monthly subscription. Hosting on Vercel is free. Google Sheets is free.
Non-Tech Team Can Manage Products
Your marketing team updates the spreadsheet, the website updates instantly.
Full Customization
Build exactly what you want. No limitations from Shopify's templates.
🏗️ Architecture: How It All Works Together
Your Customers
↓
Next.js Storefront
↓
SheetToAPI (REST API)
↓
Google Sheets (Product Database)
↓
Stripe (Payment Processing)
↓
Zapier (Order Notifications & Fulfillment)
Each layer explained:
- Next.js: Your customer-facing storefront (fast, modern, SEO-friendly)
- SheetToAPI: Converts your spreadsheet into a REST API
- Google Sheets: Your product catalog (updated by your team)
- Stripe: Handles payments securely
- Zapier: Automates order notifications and fulfillment
📋 Step 1: Structure Your Google Sheet
Create a new Google Sheet with the following columns:
| id | name | description | price | category | image_url | in_stock | inventory |
|----|-----------------|----------------------------|-------|-------------|-----------|----------|-----------|
| 1 | Wireless Mouse | Ergonomic, 2.4GHz USB | 29.99 | electronics | https://... | true | 50 |
| 2 | USB-C Cable | 2m, 60W charging | 12.99 | accessories | https://... | true | 120 |
| 3 | Laptop Stand | Aluminum, adjustable angle | 49.99 | accessories | https://... | false | 0 |
Column Reference:
- id: Unique identifier (use
=ROW()-1formula to auto-increment) - name: Product title
- description: Short product description
- price: Retail price (numbers only, no $ symbol)
- category: Product category for filtering (electronics, accessories, etc.)
- image_url: Full URL to product image (host on Cloudinary or similar)
- in_stock: TRUE or FALSE
- inventory: Quantity available
Pro Tips:
- Keep descriptions under 200 characters for better mobile display
- Use high-quality images (800×600px minimum, optimized)
- Add more columns as needed:
sku,supplier,cost,rating,reviews
🔌 Step 2: Connect to SheetToAPI
- Go to sheettoapi.com
- Click "Create New API"
- Paste your Google Sheet URL
- Give it a name:
ecommerce-store - Copy your endpoint and API key
- Save to
.env.local:
NEXT_PUBLIC_SHEETTOAPI_ENDPOINT=https://api.sheettoapi.com/api/v1/data/ecommerce-store
NEXT_PUBLIC_SHEETTOAPI_KEY=your_api_key_here
STRIPE_SECRET_KEY=sk_test_your_stripe_key
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_stripe_key
💻 Step 3: Build Your Next.js Store
Create a New Next.js Project:
npx create-next-app@latest ecommerce-store
cd ecommerce-store
npm install @stripe/react-stripe-js stripe
Create API Helper (lib/api.ts):
const API_ENDPOINT = process.env.NEXT_PUBLIC_SHEETTOAPI_ENDPOINT!;
const API_KEY = process.env.NEXT_PUBLIC_SHEETTOAPI_KEY!;
export interface Product {
id: string;
name: string;
description: string;
price: number;
category: string;
image_url: string;
in_stock: boolean;
inventory: number;
}
export async function getProducts(category?: string): Promise<Product[]> {
let url = API_ENDPOINT;
if (category) {
url += `?category=${encodeURIComponent(category)}`;
}
const response = await fetch(url, {
headers: { 'X-API-Key': API_KEY }
});
if (!response.ok) throw new Error('Failed to fetch products');
const data = await response.json();
return data.rows;
}
export async function getProduct(id: string): Promise<Product> {
const products = await getProducts();
const product = products.find(p => p.id === id);
if (!product) throw new Error('Product not found');
return product;
}
Product Listing Page (app/products/page.tsx):
'use client';
import { useEffect, useState } from 'react';
import { getProducts, Product } from '@/lib/api';
import Image from 'next/image';
import Link from 'next/link';
export default function ProductsPage() {
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchProducts = async () => {
try {
const data = await getProducts();
setProducts(data);
} catch (error) {
console.error('Failed to load products:', error);
} finally {
setLoading(false);
}
};
fetchProducts();
}, []);
if (loading) return <div className="text-center p-8">Loading products...</div>;
return (
<div className="max-w-6xl mx-auto p-8">
<h1 className="text-4xl font-bold mb-8">Our Products</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{products.filter(p => p.in_stock).map(product => (
<Link href={`/products/${product.id}`} key={product.id}>
<div className="border rounded-lg overflow-hidden hover:shadow-lg transition">
<Image
src={product.image_url}
alt={product.name}
width={300}
height={300}
className="w-full h-64 object-cover"
/>
<div className="p-4">
<h3 className="font-bold text-lg">{product.name}</h3>
<p className="text-gray-600 text-sm mb-2">{product.description}</p>
<div className="flex justify-between items-center">
<span className="text-2xl font-bold">${product.price}</span>
<span className="text-green-600 text-sm">In Stock</span>
</div>
</div>
</div>
</Link>
))}
</div>
</div>
);
}
Product Detail & Checkout (app/products/[id]/page.tsx):
'use client';
import { useEffect, useState } from 'react';
import { getProduct, Product } from '@/lib/api';
import { loadStripe } from '@stripe/stripe-js';
import Image from 'next/image';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
export default function ProductPage({ params }: { params: { id: string } }) {
const [product, setProduct] = useState<Product | null>(null);
const [quantity, setQuantity] = useState(1);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchProduct = async () => {
try {
const data = await getProduct(params.id);
setProduct(data);
} catch (error) {
console.error('Failed to load product:', error);
} finally {
setLoading(false);
}
};
fetchProduct();
}, [params.id]);
const handleCheckout = async () => {
const stripe = await stripePromise;
if (!product || !stripe) return;
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
product: product,
quantity: quantity
})
});
const { sessionId } = await response.json();
await stripe.redirectToCheckout({ sessionId });
};
if (loading) return <div className="text-center p-8">Loading...</div>;
if (!product) return <div className="text-center p-8">Product not found</div>;
return (
<div className="max-w-4xl mx-auto p-8 grid grid-cols-2 gap-8">
<div>
<Image
src={product.image_url}
alt={product.name}
width={500}
height={500}
className="w-full rounded-lg"
/>
</div>
<div>
<h1 className="text-4xl font-bold mb-4">{product.name}</h1>
<p className="text-gray-600 mb-6">{product.description}</p>
<p className="text-5xl font-bold text-green-600 mb-6">${product.price}</p>
<div className="mb-6">
<label className="block mb-2">Quantity:</label>
<input
type="number"
min="1"
max={product.inventory}
value={quantity}
onChange={(e) => setQuantity(parseInt(e.target.value))}
className="border px-4 py-2 rounded w-20"
/>
</div>
<button
onClick={handleCheckout}
disabled={!product.in_stock}
className="w-full bg-blue-600 text-white py-4 rounded-lg font-bold text-lg hover:bg-blue-700 disabled:bg-gray-400"
>
{product.in_stock ? 'Add to Cart' : 'Out of Stock'}
</button>
</div>
</div>
);
}
💳 Step 4: Set Up Stripe Payments
Create Checkout API Route (app/api/checkout/route.ts):
import Stripe from 'stripe';
import { NextRequest, NextResponse } from 'next/server';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(request: NextRequest) {
const { product, quantity } = await request.json();
try {
const session = await stripe.checkout.sessions.create({
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: product.name,
images: [product.image_url],
},
unit_amount: Math.round(product.price * 100),
},
quantity: quantity,
},
],
mode: 'payment',
success_url: `${request.headers.get('origin')}/success`,
cancel_url: `${request.headers.get('origin')}/products`,
});
return NextResponse.json({ sessionId: session.id });
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
🚀 Step 5: Deploy to Vercel
npm install -g vercel
vercel
- Connect your GitHub repo
- Add environment variables in Vercel dashboard
- Deploy!
Your ecommerce store is now live.
🔄 Step 6: Automate Order Notifications with Zapier
Create a Zap that sends you an email every time someone places an order:
- Trigger: Stripe payment successful
- Action 1: Create row in Google Sheets (log order)
- Action 2: Send you an email notification
- Action 3: Send customer a receipt email
📊 Real-World Pricing Comparison
Building a small ecommerce store:
| Component | Cost/Month |
|---|---|
| Shopify | $29-299 |
| Google Sheets + Next.js | $0-20 |
| Stripe fees | 2.9% + $0.30/transaction (same for all) |
On $10k/month revenue:
- Shopify: $29/month subscription + $300 transaction fees = $329
- Google Sheets + Next.js: $0 + $300 transaction fees = $300
- Savings: $29/month ($348/year)
Plus unlimited design freedom and no rate limits.
⚡ Advanced Features (via Zapier + Integrations)
- Inventory Sync: Use Zapier to update Sheets when stock runs low
- Email Receipts: Send automated order confirmations
- SMS Notifications: Notify customers when items ship
- Abandoned Cart: Remind customers about unpurchased items
- Analytics Dashboard: Sync orders to Google Sheets for reporting
🎯 When to Scale Up
This setup handles:
- ✅ Up to 50k products
- ✅ 1-5k orders/month
- ✅ Single-currency sales
When you need:
- ❌ Inventory management at scale
- ❌ Shipping integrations (FedEx, UPS)
- ❌ Subscription products
- ❌ Multiple currencies
→ Migrate to Shopify, WooCommerce, or Medusa
🏆 Conclusion
You now have a complete, free ecommerce store that:
- Costs nearly nothing to run
- Can be managed by non-technical team members
- Offers unlimited design flexibility
- Handles hundreds of products and orders
Get started today: Create a Google Sheet, connect it to SheetToAPI, and deploy your Next.js store to Vercel in under 30 minutes.
Your side project just became a real business. 🚀
Pikebyte.dev
Expert contributor sharing insights and best practices for building with SheetToAPI.
Ready to build?
Start using SheetToAPI today and turn your spreadsheets into powerful APIs.
Explore Documentation
